Update remote soul-content with local content
This commit is contained in:
225
components/modules/auth/auth-modal.tsx
Normal file
225
components/modules/auth/auth-modal.tsx
Normal file
@@ -0,0 +1,225 @@
|
||||
"use client"
|
||||
|
||||
import { useState } from "react"
|
||||
import { X, Phone, User, Gift } from "lucide-react"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { useStore } from "@/lib/store"
|
||||
|
||||
interface AuthModalProps {
|
||||
isOpen: boolean
|
||||
onClose: () => void
|
||||
defaultTab?: "login" | "register"
|
||||
}
|
||||
|
||||
export function AuthModal({ isOpen, onClose, defaultTab = "login" }: AuthModalProps) {
|
||||
const [tab, setTab] = useState<"login" | "register">(defaultTab)
|
||||
const [phone, setPhone] = useState("")
|
||||
const [code, setCode] = useState("")
|
||||
const [nickname, setNickname] = useState("")
|
||||
const [referralCode, setReferralCode] = useState("")
|
||||
const [error, setError] = useState("")
|
||||
const [codeSent, setCodeSent] = useState(false)
|
||||
|
||||
const { login, register } = useStore()
|
||||
|
||||
const handleSendCode = () => {
|
||||
if (phone.length !== 11) {
|
||||
setError("请输入正确的手机号")
|
||||
return
|
||||
}
|
||||
// Simulate sending verification code
|
||||
setCodeSent(true)
|
||||
setError("")
|
||||
alert("验证码已发送,测试验证码: 123456")
|
||||
}
|
||||
|
||||
const handleLogin = async () => {
|
||||
setError("")
|
||||
const success = await login(phone, code)
|
||||
if (success) {
|
||||
onClose()
|
||||
} else {
|
||||
setError("验证码错误或用户不存在,请先注册")
|
||||
}
|
||||
}
|
||||
|
||||
const handleRegister = async () => {
|
||||
setError("")
|
||||
if (!nickname.trim()) {
|
||||
setError("请输入昵称")
|
||||
return
|
||||
}
|
||||
const success = await register(phone, nickname, referralCode || undefined)
|
||||
if (success) {
|
||||
onClose()
|
||||
} else {
|
||||
setError("该手机号已注册")
|
||||
}
|
||||
}
|
||||
|
||||
if (!isOpen) return null
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
|
||||
{/* Backdrop */}
|
||||
<div className="absolute inset-0 bg-black/60 backdrop-blur-sm" onClick={onClose} />
|
||||
|
||||
{/* Modal */}
|
||||
<div className="relative w-full max-w-md bg-[#0f2137] rounded-2xl border border-gray-700/50 overflow-hidden">
|
||||
{/* Close button */}
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="absolute top-4 right-4 p-2 text-gray-400 hover:text-white transition-colors"
|
||||
>
|
||||
<X className="w-5 h-5" />
|
||||
</button>
|
||||
|
||||
{/* Tabs */}
|
||||
<div className="flex border-b border-gray-700/50">
|
||||
<button
|
||||
onClick={() => setTab("login")}
|
||||
className={`flex-1 py-4 text-center transition-colors ${
|
||||
tab === "login" ? "text-white border-b-2 border-[#38bdac]" : "text-gray-400 hover:text-white"
|
||||
}`}
|
||||
>
|
||||
登录
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setTab("register")}
|
||||
className={`flex-1 py-4 text-center transition-colors ${
|
||||
tab === "register" ? "text-white border-b-2 border-[#38bdac]" : "text-gray-400 hover:text-white"
|
||||
}`}
|
||||
>
|
||||
注册
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="p-6">
|
||||
{tab === "login" ? (
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-gray-400 text-sm mb-2">手机号</label>
|
||||
<div className="relative">
|
||||
<Phone className="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-500" />
|
||||
<Input
|
||||
type="tel"
|
||||
value={phone}
|
||||
onChange={(e) => setPhone(e.target.value)}
|
||||
placeholder="请输入手机号"
|
||||
className="pl-10 bg-[#0a1628] border-gray-700 text-white placeholder:text-gray-500"
|
||||
maxLength={11}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-gray-400 text-sm mb-2">验证码</label>
|
||||
<div className="flex gap-3">
|
||||
<Input
|
||||
type="text"
|
||||
value={code}
|
||||
onChange={(e) => setCode(e.target.value)}
|
||||
placeholder="请输入验证码"
|
||||
className="bg-[#0a1628] border-gray-700 text-white placeholder:text-gray-500"
|
||||
maxLength={6}
|
||||
/>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={handleSendCode}
|
||||
disabled={codeSent}
|
||||
className="whitespace-nowrap border-gray-600 text-gray-300 hover:bg-gray-700/50 bg-transparent"
|
||||
>
|
||||
{codeSent ? "已发送" : "获取验证码"}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{error && <p className="text-red-400 text-sm">{error}</p>}
|
||||
|
||||
<Button onClick={handleLogin} className="w-full bg-[#38bdac] hover:bg-[#2da396] text-white py-5">
|
||||
登录
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-gray-400 text-sm mb-2">手机号</label>
|
||||
<div className="relative">
|
||||
<Phone className="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-500" />
|
||||
<Input
|
||||
type="tel"
|
||||
value={phone}
|
||||
onChange={(e) => setPhone(e.target.value)}
|
||||
placeholder="请输入手机号"
|
||||
className="pl-10 bg-[#0a1628] border-gray-700 text-white placeholder:text-gray-500"
|
||||
maxLength={11}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-gray-400 text-sm mb-2">昵称</label>
|
||||
<div className="relative">
|
||||
<User className="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-500" />
|
||||
<Input
|
||||
type="text"
|
||||
value={nickname}
|
||||
onChange={(e) => setNickname(e.target.value)}
|
||||
placeholder="请输入昵称"
|
||||
className="pl-10 bg-[#0a1628] border-gray-700 text-white placeholder:text-gray-500"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-gray-400 text-sm mb-2">验证码</label>
|
||||
<div className="flex gap-3">
|
||||
<Input
|
||||
type="text"
|
||||
value={code}
|
||||
onChange={(e) => setCode(e.target.value)}
|
||||
placeholder="请输入验证码"
|
||||
className="bg-[#0a1628] border-gray-700 text-white placeholder:text-gray-500"
|
||||
maxLength={6}
|
||||
/>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={handleSendCode}
|
||||
disabled={codeSent}
|
||||
className="whitespace-nowrap border-gray-600 text-gray-300 hover:bg-gray-700/50 bg-transparent"
|
||||
>
|
||||
{codeSent ? "已发送" : "获取验证码"}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-gray-400 text-sm mb-2">邀请码 (选填)</label>
|
||||
<div className="relative">
|
||||
<Gift className="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-500" />
|
||||
<Input
|
||||
type="text"
|
||||
value={referralCode}
|
||||
onChange={(e) => setReferralCode(e.target.value)}
|
||||
placeholder="填写邀请码可获得优惠"
|
||||
className="pl-10 bg-[#0a1628] border-gray-700 text-white placeholder:text-gray-500"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{error && <p className="text-red-400 text-sm">{error}</p>}
|
||||
|
||||
<Button onClick={handleRegister} className="w-full bg-[#38bdac] hover:bg-[#2da396] text-white py-5">
|
||||
注册
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
123
components/modules/marketing/qr-code-modal.tsx
Normal file
123
components/modules/marketing/qr-code-modal.tsx
Normal file
@@ -0,0 +1,123 @@
|
||||
"use client"
|
||||
|
||||
import { X, MessageCircle, Users, Music } from "lucide-react"
|
||||
import { useStore } from "@/lib/store"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import Image from "next/image"
|
||||
import { useState, useEffect } from "react"
|
||||
|
||||
interface QRCodeModalProps {
|
||||
isOpen: boolean
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
export function QRCodeModal({ isOpen, onClose }: QRCodeModalProps) {
|
||||
const { settings, getLiveQRCodeUrl } = useStore()
|
||||
const [isJoining, setIsJoining] = useState(false)
|
||||
const [qrCodeUrl, setQrCodeUrl] = useState("/images/party-group-qr.png") // Default fallback
|
||||
|
||||
// Fetch config on mount
|
||||
useEffect(() => {
|
||||
const fetchConfig = async () => {
|
||||
try {
|
||||
const res = await fetch('/api/config')
|
||||
const data = await res.json()
|
||||
if (data.marketing?.partyGroup?.qrCode) {
|
||||
setQrCodeUrl(data.marketing.partyGroup.qrCode)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Failed to load QR config", e)
|
||||
}
|
||||
}
|
||||
fetchConfig()
|
||||
}, [])
|
||||
|
||||
if (!isOpen) return null
|
||||
|
||||
const handleJoin = () => {
|
||||
setIsJoining(true)
|
||||
// 获取活码随机URL
|
||||
const url = getLiveQRCodeUrl("party-group")
|
||||
if (url) {
|
||||
window.open(url, "_blank")
|
||||
}
|
||||
setTimeout(() => setIsJoining(false), 1000)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
|
||||
<div className="absolute inset-0 bg-black/80 backdrop-blur-md" onClick={onClose} />
|
||||
|
||||
{/* iOS Style Modal */}
|
||||
<div className="relative w-full max-w-[320px] bg-[#1a1a1a] rounded-3xl border border-white/10 overflow-hidden shadow-2xl animate-in fade-in zoom-in-95 duration-200">
|
||||
|
||||
{/* Header Background Effect */}
|
||||
<div className="absolute top-0 left-0 right-0 h-32 bg-gradient-to-b from-[#7000ff]/20 to-transparent pointer-events-none" />
|
||||
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="absolute top-4 right-4 p-2 text-white/50 hover:text-white transition-colors z-10 bg-black/20 rounded-full backdrop-blur-sm"
|
||||
>
|
||||
<X className="w-5 h-5" />
|
||||
</button>
|
||||
|
||||
<div className="p-8 flex flex-col items-center text-center relative">
|
||||
|
||||
{/* Icon Badge */}
|
||||
<div className="w-16 h-16 mb-6 rounded-full bg-gradient-to-tr from-[#7000ff] to-[#bd00ff] p-[2px] shadow-lg shadow-purple-500/20">
|
||||
<div className="w-full h-full rounded-full bg-[#1a1a1a] flex items-center justify-center">
|
||||
<Music className="w-8 h-8 text-[#bd00ff]" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 className="text-xl font-bold text-white mb-2 tracking-tight">
|
||||
Soul 创业派对
|
||||
</h3>
|
||||
|
||||
<div className="flex items-center gap-2 mb-6">
|
||||
<span className="px-2 py-0.5 rounded-full bg-white/10 text-[10px] text-white/70 border border-white/5">
|
||||
Live
|
||||
</span>
|
||||
<p className="text-white/60 text-xs">
|
||||
{settings.authorInfo?.liveTime || "06:00-09:00"} · {settings.authorInfo?.name || "卡若"}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* QR Code Container - Enhanced Visibility */}
|
||||
<div className="bg-white p-3 rounded-2xl shadow-xl mb-6 transform transition-transform hover:scale-105 duration-300">
|
||||
<div className="relative w-48 h-48">
|
||||
<Image
|
||||
src={qrCodeUrl}
|
||||
alt="派对群二维码"
|
||||
fill
|
||||
className="object-contain"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p className="text-white/40 text-xs mb-6 px-4">
|
||||
扫码加入私域流量实战群<br/>获取《私域运营100问》
|
||||
</p>
|
||||
|
||||
<Button
|
||||
onClick={handleJoin}
|
||||
disabled={isJoining}
|
||||
className="w-full bg-gradient-to-r from-[#7000ff] to-[#bd00ff] hover:opacity-90 text-white font-medium rounded-xl h-12 shadow-lg shadow-purple-900/20 border-0 transition-all active:scale-95"
|
||||
>
|
||||
{isJoining ? (
|
||||
<span className="flex items-center gap-2">
|
||||
<Users className="w-4 h-4 animate-pulse" />
|
||||
跳转中...
|
||||
</span>
|
||||
) : (
|
||||
<span className="flex items-center gap-2">
|
||||
<MessageCircle className="w-4 h-4" />
|
||||
立即加入
|
||||
</span>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
343
components/modules/payment/payment-modal.tsx
Normal file
343
components/modules/payment/payment-modal.tsx
Normal file
@@ -0,0 +1,343 @@
|
||||
"use client"
|
||||
|
||||
import type React from "react"
|
||||
import { useState } from "react"
|
||||
import { X, CheckCircle, Bitcoin, Globe, Copy, Check } from "lucide-react"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { useStore } from "@/lib/store"
|
||||
|
||||
const WechatIcon = () => (
|
||||
<svg viewBox="0 0 24 24" className="w-5 h-5" fill="currentColor">
|
||||
<path d="M8.691 2.188C3.891 2.188 0 5.476 0 9.53c0 2.212 1.17 4.203 3.002 5.55a.59.59 0 0 1 .213.665l-.39 1.48c-.019.07-.048.141-.048.213 0 .163.13.295.29.295a.326.326 0 0 0 .167-.054l1.903-1.114a.864.864 0 0 1 .717-.098 10.16 10.16 0 0 0 2.837.403c.276 0 .543-.027.811-.05-.857-2.578.157-4.972 1.932-6.446 1.703-1.415 3.882-1.98 5.853-1.838-.576-3.583-4.196-6.348-8.596-6.348zM5.785 5.991c.642 0 1.162.529 1.162 1.18a1.17 1.17 0 0 1-1.162 1.178A1.17 1.17 0 0 1 4.623 7.17c0-.651.52-1.18 1.162-1.18zm5.813 0c.642 0 1.162.529 1.162 1.18a1.17 1.17 0 0 1-1.162 1.178 1.17 1.17 0 0 1-1.162-1.178c0-.651.52-1.18 1.162-1.18zm5.34 2.867c-1.797-.052-3.746.512-5.28 1.786-1.72 1.428-2.687 3.72-1.78 6.22.942 2.453 3.666 4.229 6.884 4.229.826 0 1.622-.12 2.361-.336a.722.722 0 0 1 .598.082l1.584.926a.272.272 0 0 0 .14.047c.134 0 .24-.111.24-.247 0-.06-.023-.12-.038-.177l-.327-1.233a.582.582 0 0 1-.023-.156.49.49 0 0 1 .201-.398C23.024 18.48 24 16.82 24 14.98c0-3.21-2.931-5.837-6.656-6.088V8.89c-.135-.01-.269-.03-.406-.03zm-2.53 3.274c.535 0 .969.44.969.982a.976.976 0 0 1-.969.983.976.976 0 0 1-.969-.983c0-.542.434-.982.97-.982zm4.844 0c.535 0 .969.44.969.982a.976.976 0 0 1-.969.983.976.976 0 0 1-.969-.983c0-.542.434-.982.969-.982z" />
|
||||
</svg>
|
||||
)
|
||||
|
||||
const AlipayIcon = () => (
|
||||
<svg viewBox="0 0 24 24" className="w-5 h-5" fill="currentColor">
|
||||
<path d="M8.77 20.62l9.92-4.33c-.12-.33-.24-.66-.38-.99-.14-.33-.3-.66-.47-.99H8.08c-2.2 0-3.99-1.79-3.99-3.99V8.08c0-2.2 1.79-3.99 3.99-3.99h7.84c2.2 0 3.99 1.79 3.99 3.99v2.24h-8.66c-.55 0-1 .45-1 1s.45 1 1 1h10.66c-.18 1.73-.71 3.36-1.53 4.83l-2.76 1.2c-.74-1.69-1.74-3.24-2.93-4.6-.52-.59-1.11-1.13-1.76-1.59H4.09v4.24c0 2.2 1.79 3.99 3.99 3.99h.69v.23z" />
|
||||
</svg>
|
||||
)
|
||||
|
||||
type PaymentMethod = "wechat" | "alipay" | "usdt" | "paypal"
|
||||
|
||||
interface PaymentModalProps {
|
||||
isOpen: boolean
|
||||
onClose: () => void
|
||||
type: "section" | "fullbook"
|
||||
sectionId?: string
|
||||
sectionTitle?: string
|
||||
amount: number
|
||||
onSuccess: () => void
|
||||
}
|
||||
|
||||
export function PaymentModal({ isOpen, onClose, type, sectionId, sectionTitle, amount, onSuccess }: PaymentModalProps) {
|
||||
const [paymentMethod, setPaymentMethod] = useState<PaymentMethod>("wechat")
|
||||
const [isProcessing, setIsProcessing] = useState(false)
|
||||
const [isSuccess, setIsSuccess] = useState(false)
|
||||
const [showPaymentDetails, setShowPaymentDetails] = useState(false)
|
||||
const [copied, setCopied] = useState(false)
|
||||
|
||||
const { purchaseSection, purchaseFullBook, user, settings } = useStore()
|
||||
|
||||
const paymentConfig = settings?.paymentMethods || {
|
||||
wechat: { enabled: true, qrCode: "", account: "" },
|
||||
alipay: { enabled: true, qrCode: "", account: "" },
|
||||
usdt: { enabled: true, network: "TRC20", address: "", exchangeRate: 7.2 },
|
||||
paypal: { enabled: false, email: "", exchangeRate: 7.2 },
|
||||
}
|
||||
|
||||
const usdtAmount = (amount / (paymentConfig.usdt.exchangeRate || 7.2)).toFixed(2)
|
||||
const paypalAmount = (amount / (paymentConfig.paypal.exchangeRate || 7.2)).toFixed(2)
|
||||
|
||||
const handleCopyAddress = (address: string) => {
|
||||
navigator.clipboard.writeText(address)
|
||||
setCopied(true)
|
||||
setTimeout(() => setCopied(false), 2000)
|
||||
}
|
||||
|
||||
const handlePayment = async () => {
|
||||
if (paymentMethod === "usdt" || paymentMethod === "paypal" || paymentMethod === "wechat" || paymentMethod === "alipay") {
|
||||
setShowPaymentDetails(true)
|
||||
return
|
||||
}
|
||||
|
||||
setIsProcessing(true)
|
||||
await new Promise((resolve) => setTimeout(resolve, 1500))
|
||||
|
||||
let success = false
|
||||
if (type === "section" && sectionId) {
|
||||
success = await purchaseSection(sectionId, sectionTitle, paymentMethod)
|
||||
} else if (type === "fullbook") {
|
||||
success = await purchaseFullBook(paymentMethod)
|
||||
}
|
||||
|
||||
setIsProcessing(false)
|
||||
|
||||
if (success) {
|
||||
setIsSuccess(true)
|
||||
setTimeout(() => {
|
||||
onSuccess()
|
||||
onClose()
|
||||
setIsSuccess(false)
|
||||
}, 1500)
|
||||
}
|
||||
}
|
||||
|
||||
const confirmCryptoPayment = async () => {
|
||||
setIsProcessing(true)
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000))
|
||||
|
||||
let success = false
|
||||
if (type === "section" && sectionId) {
|
||||
success = await purchaseSection(sectionId, sectionTitle, paymentMethod)
|
||||
} else if (type === "fullbook") {
|
||||
success = await purchaseFullBook(paymentMethod)
|
||||
}
|
||||
|
||||
setIsProcessing(false)
|
||||
setShowPaymentDetails(false)
|
||||
|
||||
if (success) {
|
||||
setIsSuccess(true)
|
||||
setTimeout(() => {
|
||||
onSuccess()
|
||||
onClose()
|
||||
setIsSuccess(false)
|
||||
}, 1500)
|
||||
}
|
||||
}
|
||||
|
||||
if (!isOpen) return null
|
||||
|
||||
const paymentMethods: {
|
||||
id: PaymentMethod
|
||||
name: string
|
||||
icon: React.ReactNode
|
||||
color: string
|
||||
enabled: boolean
|
||||
extra?: string
|
||||
}[] = [
|
||||
{
|
||||
id: "wechat",
|
||||
name: "微信支付",
|
||||
icon: <WechatIcon />,
|
||||
color: "bg-[#07C160]",
|
||||
enabled: paymentConfig.wechat.enabled,
|
||||
},
|
||||
{
|
||||
id: "alipay",
|
||||
name: "支付宝",
|
||||
icon: <AlipayIcon />,
|
||||
color: "bg-[#1677FF]",
|
||||
enabled: paymentConfig.alipay.enabled,
|
||||
},
|
||||
{
|
||||
id: "usdt",
|
||||
name: `USDT (${paymentConfig.usdt.network || "TRC20"})`,
|
||||
icon: <Bitcoin className="w-5 h-5" />,
|
||||
color: "bg-[#26A17B]",
|
||||
enabled: paymentConfig.usdt.enabled,
|
||||
extra: `≈ $${usdtAmount}`,
|
||||
},
|
||||
{
|
||||
id: "paypal",
|
||||
name: "PayPal",
|
||||
icon: <Globe className="w-5 h-5" />,
|
||||
color: "bg-[#003087]",
|
||||
enabled: paymentConfig.paypal.enabled,
|
||||
extra: `≈ $${paypalAmount}`,
|
||||
},
|
||||
]
|
||||
|
||||
const availableMethods = paymentMethods.filter((m) => m.enabled)
|
||||
|
||||
// Payment details view
|
||||
if (showPaymentDetails) {
|
||||
const isCrypto = paymentMethod === "usdt"
|
||||
const isPayPal = paymentMethod === "paypal"
|
||||
const isWechat = paymentMethod === "wechat"
|
||||
const isAlipay = paymentMethod === "alipay"
|
||||
|
||||
let title = ""
|
||||
let address = ""
|
||||
let displayAmount = `¥${amount.toFixed(2)}`
|
||||
let qrCodeUrl = ""
|
||||
|
||||
if (isCrypto) {
|
||||
title = "USDT支付"
|
||||
address = paymentConfig.usdt.address
|
||||
displayAmount = `$${usdtAmount} USDT`
|
||||
} else if (isPayPal) {
|
||||
title = "PayPal支付"
|
||||
address = paymentConfig.paypal.email
|
||||
displayAmount = `$${paypalAmount} USD`
|
||||
} else if (isWechat) {
|
||||
title = "微信支付"
|
||||
qrCodeUrl = paymentConfig.wechat.qrCode || "/images/wechat-pay.png"
|
||||
} else if (isAlipay) {
|
||||
title = "支付宝支付"
|
||||
qrCodeUrl = paymentConfig.alipay.qrCode || "/images/alipay.png"
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
|
||||
<div className="absolute inset-0 bg-black/60 backdrop-blur-sm" onClick={onClose} />
|
||||
<div className="relative w-full max-w-md bg-[#0f2137] rounded-2xl border border-gray-700/50 overflow-hidden">
|
||||
<button onClick={onClose} className="absolute top-4 right-4 p-2 text-gray-400 hover:text-white z-10">
|
||||
<X className="w-5 h-5" />
|
||||
</button>
|
||||
|
||||
<div className="p-6">
|
||||
<h3 className="text-lg font-semibold text-white mb-4 text-center">{title}</h3>
|
||||
|
||||
<div className="bg-[#0a1628] rounded-xl p-4 mb-4 text-center">
|
||||
<p className="text-gray-400 text-sm mb-2">支付金额</p>
|
||||
<p className="text-3xl font-bold text-[#38bdac]">{displayAmount}</p>
|
||||
{(isCrypto || isPayPal) && <p className="text-gray-500 text-sm">≈ ¥{amount.toFixed(2)}</p>}
|
||||
</div>
|
||||
|
||||
{(isWechat || isAlipay) ? (
|
||||
<div className="flex flex-col items-center justify-center mb-6">
|
||||
<div className="bg-white p-2 rounded-xl mb-4">
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
<img src={qrCodeUrl} alt={title} className="w-48 h-48 object-contain" />
|
||||
</div>
|
||||
<p className="text-gray-400 text-sm">请使用{title === "微信支付" ? "微信" : "支付宝"}扫码支付</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="bg-[#0a1628] rounded-xl p-4 mb-4">
|
||||
<p className="text-gray-400 text-sm mb-2">
|
||||
{isCrypto ? `收款地址 (${paymentConfig.usdt.network})` : "PayPal账户"}
|
||||
</p>
|
||||
<div className="flex items-center gap-2">
|
||||
<p className="text-white text-sm break-all flex-1 font-mono">
|
||||
{address || "请联系客服获取"}
|
||||
</p>
|
||||
{address && (
|
||||
<button onClick={() => handleCopyAddress(address)} className="text-[#38bdac] hover:text-[#4fd4c4]">
|
||||
{copied ? <Check className="w-5 h-5" /> : <Copy className="w-5 h-5" />}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="bg-orange-500/10 border border-orange-500/30 rounded-xl p-4 mb-6">
|
||||
<p className="text-orange-400 text-sm text-center">
|
||||
支付完成后,请点击下方"我已支付"按钮,<br/>系统将自动开通阅读权限
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-3">
|
||||
<Button
|
||||
onClick={() => setShowPaymentDetails(false)}
|
||||
variant="outline"
|
||||
className="flex-1 border-gray-600 text-white hover:bg-gray-700/50 bg-transparent"
|
||||
>
|
||||
返回
|
||||
</Button>
|
||||
<Button
|
||||
onClick={confirmCryptoPayment}
|
||||
disabled={isProcessing}
|
||||
className="flex-1 bg-[#38bdac] hover:bg-[#2da396] text-white"
|
||||
>
|
||||
{isProcessing ? "处理中..." : "已完成支付"}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
|
||||
<div className="absolute inset-0 bg-black/60 backdrop-blur-sm" onClick={onClose} />
|
||||
<div className="relative w-full max-w-md bg-[#0f2137] rounded-2xl border border-gray-700/50 overflow-hidden">
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="absolute top-4 right-4 p-2 text-gray-400 hover:text-white transition-colors"
|
||||
>
|
||||
<X className="w-5 h-5" />
|
||||
</button>
|
||||
|
||||
{isSuccess ? (
|
||||
<div className="p-8 text-center">
|
||||
<div className="w-20 h-20 mx-auto mb-4 rounded-full bg-[#38bdac]/20 flex items-center justify-center">
|
||||
<CheckCircle className="w-10 h-10 text-[#38bdac]" />
|
||||
</div>
|
||||
<h3 className="text-xl font-semibold text-white mb-2">支付成功</h3>
|
||||
<p className="text-gray-400">{type === "fullbook" ? "您已解锁全部内容" : "您已解锁本节内容"}</p>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="p-6 border-b border-gray-700/50">
|
||||
<h3 className="text-lg font-semibold text-white mb-1">确认支付</h3>
|
||||
<p className="text-gray-400 text-sm">
|
||||
{type === "fullbook" ? "购买整本书,解锁全部内容" : `购买: ${sectionTitle}`}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="p-6 border-b border-gray-700/50 text-center">
|
||||
<p className="text-gray-400 text-sm mb-1">支付金额</p>
|
||||
<p className="text-4xl font-bold text-white">¥{amount.toFixed(2)}</p>
|
||||
{(paymentMethod === "usdt" || paymentMethod === "paypal") && (
|
||||
<p className="text-[#38bdac] text-sm mt-1">
|
||||
≈ ${paymentMethod === "usdt" ? usdtAmount : paypalAmount} USD
|
||||
</p>
|
||||
)}
|
||||
{user?.referredBy && (
|
||||
<p className="text-[#38bdac] text-sm mt-2">
|
||||
通过邀请注册,{settings?.distributorShare || 90}%将返还给推荐人
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="p-6 space-y-3">
|
||||
<p className="text-gray-400 text-sm mb-3">选择支付方式</p>
|
||||
{availableMethods.map((method) => (
|
||||
<button
|
||||
key={method.id}
|
||||
onClick={() => setPaymentMethod(method.id)}
|
||||
className={`w-full p-4 rounded-xl border flex items-center gap-4 transition-all ${
|
||||
paymentMethod === method.id
|
||||
? "border-[#38bdac] bg-[#38bdac]/10"
|
||||
: "border-gray-700 hover:border-gray-600"
|
||||
}`}
|
||||
>
|
||||
<div className={`w-10 h-10 rounded-lg ${method.color} flex items-center justify-center text-white`}>
|
||||
{method.icon}
|
||||
</div>
|
||||
<div className="flex-1 text-left">
|
||||
<span className="text-white">{method.name}</span>
|
||||
{method.extra && <span className="text-gray-400 text-sm ml-2">{method.extra}</span>}
|
||||
</div>
|
||||
{paymentMethod === method.id && <CheckCircle className="w-5 h-5 text-[#38bdac]" />}
|
||||
</button>
|
||||
))}
|
||||
{availableMethods.length === 0 && <p className="text-gray-500 text-center py-4">暂无可用支付方式</p>}
|
||||
</div>
|
||||
|
||||
<div className="p-6 pt-0">
|
||||
<Button
|
||||
onClick={handlePayment}
|
||||
disabled={isProcessing || availableMethods.length === 0}
|
||||
className="w-full bg-[#38bdac] hover:bg-[#2da396] text-white py-6 text-lg"
|
||||
>
|
||||
{isProcessing ? (
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-5 h-5 border-2 border-white border-t-transparent rounded-full animate-spin" />
|
||||
处理中...
|
||||
</div>
|
||||
) : (
|
||||
`确认支付 ¥${amount.toFixed(2)}`
|
||||
)}
|
||||
</Button>
|
||||
<p className="text-gray-500 text-xs text-center mt-3">支付即表示同意《用户协议》和《隐私政策》</p>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
76
components/modules/referral/poster-modal.tsx
Normal file
76
components/modules/referral/poster-modal.tsx
Normal file
@@ -0,0 +1,76 @@
|
||||
"use client"
|
||||
|
||||
import { X } from "lucide-react"
|
||||
import { Button } from "@/components/ui/button"
|
||||
|
||||
interface PosterModalProps {
|
||||
isOpen: boolean
|
||||
onClose: () => void
|
||||
referralLink: string
|
||||
referralCode: string
|
||||
nickname: string
|
||||
}
|
||||
|
||||
export function PosterModal({ isOpen, onClose, referralLink, referralCode, nickname }: PosterModalProps) {
|
||||
if (!isOpen) return null
|
||||
|
||||
// Use a public QR code API
|
||||
const qrCodeUrl = `https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=${encodeURIComponent(referralLink)}`
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
|
||||
<div className="absolute inset-0 bg-black/80 backdrop-blur-sm" onClick={onClose} />
|
||||
|
||||
<div className="relative w-full max-w-sm bg-white rounded-xl overflow-hidden shadow-2xl animate-in fade-in zoom-in duration-200">
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="absolute top-2 right-2 p-1.5 bg-black/20 rounded-full text-white hover:bg-black/40 z-10"
|
||||
>
|
||||
<X className="w-5 h-5" />
|
||||
</button>
|
||||
|
||||
{/* Poster Content */}
|
||||
<div className="bg-gradient-to-br from-indigo-900 to-purple-900 text-white p-6 flex flex-col items-center text-center relative overflow-hidden">
|
||||
{/* Decorative circles */}
|
||||
<div className="absolute top-0 left-0 w-32 h-32 bg-white/10 rounded-full -translate-x-1/2 -translate-y-1/2 blur-2xl" />
|
||||
<div className="absolute bottom-0 right-0 w-40 h-40 bg-pink-500/20 rounded-full translate-x-1/3 translate-y-1/3 blur-2xl" />
|
||||
|
||||
<div className="relative z-10 w-full flex flex-col items-center">
|
||||
{/* Book Title */}
|
||||
<h2 className="text-xl font-bold mb-1 leading-tight text-white">一场SOUL的<br/>创业实验场</h2>
|
||||
<p className="text-white/80 text-xs mb-6">真实商业故事 · 55个案例 · 每日更新</p>
|
||||
|
||||
{/* Cover Image Placeholder */}
|
||||
<div className="w-32 h-44 bg-gray-200 rounded shadow-lg mb-6 overflow-hidden relative">
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
<img src="/images/image.png" alt="Book Cover" className="w-full h-full object-cover" />
|
||||
</div>
|
||||
|
||||
{/* Recommender Info */}
|
||||
<div className="flex items-center gap-2 mb-4 bg-white/10 px-3 py-1.5 rounded-full backdrop-blur-sm">
|
||||
<span className="text-xs text-white">推荐人: {nickname}</span>
|
||||
</div>
|
||||
|
||||
{/* QR Code Section */}
|
||||
<div className="bg-white p-2 rounded-lg shadow-lg mb-2">
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
<img src={qrCodeUrl} alt="QR Code" className="w-32 h-32" />
|
||||
</div>
|
||||
<p className="text-[10px] text-white/60 mb-1">长按识别二维码试读</p>
|
||||
<p className="text-xs font-mono tracking-wider text-white">邀请码: {referralCode}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Footer Actions */}
|
||||
<div className="p-4 bg-gray-50 flex flex-col gap-2">
|
||||
<p className="text-center text-xs text-gray-500 mb-1">
|
||||
长按上方图片保存,或截图分享
|
||||
</p>
|
||||
<Button onClick={onClose} className="w-full" variant="outline">
|
||||
关闭
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
48
components/modules/referral/referral-share.tsx
Normal file
48
components/modules/referral/referral-share.tsx
Normal file
@@ -0,0 +1,48 @@
|
||||
"use client"
|
||||
|
||||
import { Share2 } from "lucide-react"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { useStore } from "@/lib/store"
|
||||
|
||||
interface ReferralShareProps {
|
||||
sectionTitle: string
|
||||
fullBookPrice: number
|
||||
distributorShare: number
|
||||
}
|
||||
|
||||
export function ReferralShare({ sectionTitle, fullBookPrice, distributorShare }: ReferralShareProps) {
|
||||
const { user } = useStore()
|
||||
|
||||
const handleShare = async () => {
|
||||
const url = user?.referralCode ? `${window.location.href}?ref=${user.referralCode}` : window.location.href
|
||||
const shareData = {
|
||||
title: sectionTitle,
|
||||
text: `来自Soul派对房的真实商业故事: ${sectionTitle}`,
|
||||
url: url,
|
||||
}
|
||||
|
||||
try {
|
||||
if (navigator.share && navigator.canShare && navigator.canShare(shareData)) {
|
||||
await navigator.share(shareData)
|
||||
} else {
|
||||
navigator.clipboard.writeText(url)
|
||||
alert(
|
||||
`链接已复制!分享后他人购买,你可获得${distributorShare}%返利 (¥${((fullBookPrice * distributorShare) / 100).toFixed(1)})`,
|
||||
)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error sharing:", error)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="text-gray-400 hover:text-white"
|
||||
onClick={handleShare}
|
||||
>
|
||||
<Share2 className="w-5 h-5" />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
172
components/modules/referral/withdrawal-modal.tsx
Normal file
172
components/modules/referral/withdrawal-modal.tsx
Normal file
@@ -0,0 +1,172 @@
|
||||
"use client"
|
||||
|
||||
import { useState } from "react"
|
||||
import { X, Wallet, CheckCircle } from "lucide-react"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Label } from "@/components/ui/label"
|
||||
import { useStore } from "@/lib/store"
|
||||
|
||||
interface WithdrawalModalProps {
|
||||
isOpen: boolean
|
||||
onClose: () => void
|
||||
availableAmount: number
|
||||
}
|
||||
|
||||
export function WithdrawalModal({ isOpen, onClose, availableAmount }: WithdrawalModalProps) {
|
||||
const { requestWithdrawal } = useStore()
|
||||
const [amount, setAmount] = useState<string>("")
|
||||
const [method, setMethod] = useState<"wechat" | "alipay">("wechat")
|
||||
const [account, setAccount] = useState("")
|
||||
const [name, setName] = useState("")
|
||||
const [isSubmitting, setIsSubmitting] = useState(false)
|
||||
const [isSuccess, setIsSuccess] = useState(false)
|
||||
|
||||
if (!isOpen) return null
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
|
||||
const amountNum = parseFloat(amount)
|
||||
if (isNaN(amountNum) || amountNum <= 0 || amountNum > availableAmount) {
|
||||
alert("请输入有效的提现金额")
|
||||
return
|
||||
}
|
||||
|
||||
if (!account || !name) {
|
||||
alert("请填写完整的提现信息")
|
||||
return
|
||||
}
|
||||
|
||||
setIsSubmitting(true)
|
||||
|
||||
// Simulate API delay
|
||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||
|
||||
requestWithdrawal(amountNum, method, account, name)
|
||||
|
||||
setIsSubmitting(false)
|
||||
setIsSuccess(true)
|
||||
}
|
||||
|
||||
const handleClose = () => {
|
||||
setIsSuccess(false)
|
||||
setAmount("")
|
||||
setAccount("")
|
||||
setName("")
|
||||
onClose()
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
|
||||
<div className="absolute inset-0 bg-black/80 backdrop-blur-sm" onClick={handleClose} />
|
||||
|
||||
<div className="relative w-full max-w-sm bg-white rounded-xl overflow-hidden shadow-2xl animate-in fade-in zoom-in duration-200">
|
||||
<button
|
||||
onClick={handleClose}
|
||||
className="absolute top-2 right-2 p-1.5 bg-black/10 rounded-full text-gray-500 hover:bg-black/20 z-10"
|
||||
>
|
||||
<X className="w-5 h-5" />
|
||||
</button>
|
||||
|
||||
{isSuccess ? (
|
||||
<div className="p-8 flex flex-col items-center text-center">
|
||||
<div className="w-16 h-16 bg-green-100 rounded-full flex items-center justify-center mb-4">
|
||||
<CheckCircle className="w-8 h-8 text-green-600" />
|
||||
</div>
|
||||
<h3 className="text-xl font-bold text-gray-900 mb-2">申请提交成功</h3>
|
||||
<p className="text-sm text-gray-500 mb-6">
|
||||
您的提现申请已提交,预计1-3个工作日内到账。
|
||||
</p>
|
||||
<Button onClick={handleClose} className="w-full bg-green-600 hover:bg-green-700 text-white">
|
||||
完成
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<form onSubmit={handleSubmit} className="p-6">
|
||||
<div className="flex items-center gap-2 mb-6">
|
||||
<Wallet className="w-5 h-5 text-indigo-600" />
|
||||
<h3 className="text-lg font-bold text-gray-900">申请提现</h3>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4 mb-6">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="amount">提现金额 (可提现: ¥{availableAmount.toFixed(2)})</Label>
|
||||
<div className="relative">
|
||||
<span className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-500">¥</span>
|
||||
<Input
|
||||
id="amount"
|
||||
type="number"
|
||||
min="10"
|
||||
max={availableAmount}
|
||||
step="0.01"
|
||||
value={amount}
|
||||
onChange={(e) => setAmount(e.target.value)}
|
||||
className="pl-7"
|
||||
placeholder="最低10元"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>提现方式</Label>
|
||||
<div className="flex gap-4">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setMethod("wechat")}
|
||||
className={`flex-1 py-2 px-4 rounded-lg border text-sm font-medium transition-colors ${
|
||||
method === "wechat"
|
||||
? "border-green-600 bg-green-50 text-green-700"
|
||||
: "border-gray-200 hover:bg-gray-50 text-gray-600"
|
||||
}`}
|
||||
>
|
||||
微信支付
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setMethod("alipay")}
|
||||
className={`flex-1 py-2 px-4 rounded-lg border text-sm font-medium transition-colors ${
|
||||
method === "alipay"
|
||||
? "border-blue-600 bg-blue-50 text-blue-700"
|
||||
: "border-gray-200 hover:bg-gray-50 text-gray-600"
|
||||
}`}
|
||||
>
|
||||
支付宝
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="account">{method === "wechat" ? "微信号" : "支付宝账号"}</Label>
|
||||
<Input
|
||||
id="account"
|
||||
value={account}
|
||||
onChange={(e) => setAccount(e.target.value)}
|
||||
placeholder={method === "wechat" ? "请输入微信号" : "请输入支付宝账号"}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="name">真实姓名</Label>
|
||||
<Input
|
||||
id="name"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
placeholder="请输入收款人真实姓名"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
type="submit"
|
||||
className="w-full bg-indigo-600 hover:bg-indigo-700 text-white"
|
||||
disabled={isSubmitting || !amount || !account || !name}
|
||||
>
|
||||
{isSubmitting ? "提交中..." : "确认提现"}
|
||||
</Button>
|
||||
</form>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user