feat: Implement iOS-style UI, payment, referral, and reading experience improvements
This commit is contained in:
@@ -2,8 +2,7 @@
|
||||
|
||||
import type React from "react"
|
||||
import { useState, useEffect } from "react"
|
||||
import { X, CheckCircle, Bitcoin, Globe, Copy, Check, QrCode } from "lucide-react"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { X, CheckCircle, Bitcoin, Globe, Copy, Check, QrCode, Shield, Users } from "lucide-react"
|
||||
import { useStore } from "@/lib/store"
|
||||
|
||||
const WechatIcon = () => (
|
||||
@@ -65,10 +64,8 @@ export function PaymentModal({ isOpen, onClose, type, sectionId, sectionTitle, a
|
||||
}
|
||||
|
||||
const handlePayment = async () => {
|
||||
// 直接显示支付二维码/详情页面
|
||||
setShowQRCode(true)
|
||||
|
||||
// 如果有跳转链接,尝试打开
|
||||
if (paymentMethod === "wechat" && paymentConfig.wechat?.qrCode) {
|
||||
const link = paymentConfig.wechat.qrCode
|
||||
if (link.startsWith("http") || link.startsWith("weixin://")) {
|
||||
@@ -98,7 +95,6 @@ export function PaymentModal({ isOpen, onClose, type, sectionId, sectionTitle, a
|
||||
if (success) {
|
||||
setIsSuccess(true)
|
||||
|
||||
// 支付成功后跳转微信群
|
||||
const groupUrl = paymentConfig.wechat?.groupQrCode
|
||||
if (groupUrl) {
|
||||
setTimeout(() => {
|
||||
@@ -122,6 +118,7 @@ export function PaymentModal({ isOpen, onClose, type, sectionId, sectionTitle, a
|
||||
name: string
|
||||
icon: React.ReactNode
|
||||
color: string
|
||||
iconBg: string
|
||||
enabled: boolean
|
||||
extra?: string
|
||||
}[] = [
|
||||
@@ -129,21 +126,24 @@ export function PaymentModal({ isOpen, onClose, type, sectionId, sectionTitle, a
|
||||
id: "wechat",
|
||||
name: "微信支付",
|
||||
icon: <WechatIcon />,
|
||||
color: "bg-[#07C160]",
|
||||
color: "#07C160",
|
||||
iconBg: "rgba(7, 193, 96, 0.15)",
|
||||
enabled: paymentConfig.wechat?.enabled ?? true,
|
||||
},
|
||||
{
|
||||
id: "alipay",
|
||||
name: "支付宝",
|
||||
icon: <AlipayIcon />,
|
||||
color: "bg-[#1677FF]",
|
||||
color: "#1677FF",
|
||||
iconBg: "rgba(22, 119, 255, 0.15)",
|
||||
enabled: paymentConfig.alipay?.enabled ?? true,
|
||||
},
|
||||
{
|
||||
id: "usdt",
|
||||
name: `USDT (${paymentConfig.usdt?.network || "TRC20"})`,
|
||||
icon: <Bitcoin className="w-5 h-5" />,
|
||||
color: "bg-[#26A17B]",
|
||||
color: "#26A17B",
|
||||
iconBg: "rgba(38, 161, 123, 0.15)",
|
||||
enabled: paymentConfig.usdt?.enabled ?? true,
|
||||
extra: `≈ $${usdtAmount}`,
|
||||
},
|
||||
@@ -151,7 +151,8 @@ export function PaymentModal({ isOpen, onClose, type, sectionId, sectionTitle, a
|
||||
id: "paypal",
|
||||
name: "PayPal",
|
||||
icon: <Globe className="w-5 h-5" />,
|
||||
color: "bg-[#003087]",
|
||||
color: "#003087",
|
||||
iconBg: "rgba(0, 48, 135, 0.15)",
|
||||
enabled: paymentConfig.paypal?.enabled ?? false,
|
||||
extra: `≈ $${paypalAmount}`,
|
||||
},
|
||||
@@ -159,7 +160,7 @@ export function PaymentModal({ isOpen, onClose, type, sectionId, sectionTitle, a
|
||||
|
||||
const availableMethods = paymentMethods.filter((m) => m.enabled)
|
||||
|
||||
// 二维码/详情页面
|
||||
// 二维码/详情页面 - iOS毛玻璃风格
|
||||
if (showQRCode) {
|
||||
const isCrypto = paymentMethod === "usdt"
|
||||
const isPaypal = paymentMethod === "paypal"
|
||||
@@ -169,7 +170,7 @@ export function PaymentModal({ isOpen, onClose, type, sectionId, sectionTitle, a
|
||||
let address = ""
|
||||
let displayAmount = `¥${amount.toFixed(2)}`
|
||||
let title = "扫码支付"
|
||||
let hint = "支付完成后,请点击下方已完成支付按钮"
|
||||
let hint = "支付完成后,请点击下方按钮确认"
|
||||
let qrCodeUrl = ""
|
||||
|
||||
if (isCrypto) {
|
||||
@@ -193,87 +194,110 @@ export function PaymentModal({ isOpen, onClose, type, sectionId, sectionTitle, a
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
|
||||
<div className="absolute inset-0 bg-black/70 backdrop-blur-sm" onClick={onClose} />
|
||||
<div className="relative w-full max-w-md bg-[#0f2137] rounded-2xl border border-gray-700/50 shadow-2xl 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" />
|
||||
<div className="fixed inset-0 z-50 flex items-end sm:items-center justify-center">
|
||||
<div className="absolute inset-0 bg-black/60 backdrop-blur-md modal-overlay" onClick={onClose} />
|
||||
<div className="relative w-full sm:max-w-md glass-modal overflow-hidden safe-bottom modal-content sm:m-4">
|
||||
{/* 顶部把手 - 仅移动端 */}
|
||||
<div className="flex justify-center pt-3 pb-2 sm:hidden">
|
||||
<div className="w-9 h-1 rounded-full bg-[var(--app-text-tertiary)]" />
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="absolute top-4 right-4 w-8 h-8 rounded-full bg-[var(--app-bg-secondary)] flex items-center justify-center text-[var(--app-text-secondary)] hover:text-white z-10 touch-feedback"
|
||||
>
|
||||
<X className="w-4 h-4" />
|
||||
</button>
|
||||
|
||||
<div className="p-6 pt-12">
|
||||
<div className="p-6 pt-4 sm:pt-10">
|
||||
{/* 标题 */}
|
||||
<h3 className="text-xl font-semibold text-white text-center mb-4">{title}</h3>
|
||||
|
||||
<h3 className="text-xl font-semibold text-white text-center mb-2">{title}</h3>
|
||||
|
||||
{/* 金额显示 */}
|
||||
<div className="text-center mb-6">
|
||||
<p className="text-3xl font-bold text-[#38bdac]">{displayAmount}</p>
|
||||
<p className="text-4xl font-bold text-[var(--app-brand)] glow-text">{displayAmount}</p>
|
||||
</div>
|
||||
|
||||
{/* QR Code Display */}
|
||||
{(isWechat || isAlipay) && (
|
||||
<div className="flex flex-col items-center mb-6">
|
||||
<div className="w-48 h-48 bg-white rounded-xl p-3 mb-4 flex items-center justify-center">
|
||||
<div className="w-52 h-52 bg-white rounded-2xl p-4 mb-4 flex items-center justify-center shadow-lg">
|
||||
{qrCodeUrl ? (
|
||||
<img
|
||||
src={qrCodeUrl || "/placeholder.svg"}
|
||||
alt="支付二维码"
|
||||
className="w-full h-full object-contain"
|
||||
className="w-full h-full object-contain rounded-lg"
|
||||
/>
|
||||
) : (
|
||||
<div className="flex flex-col items-center text-gray-400">
|
||||
<QrCode className="w-16 h-16 mb-2" />
|
||||
<span className="text-sm">请在后台配置收款码</span>
|
||||
<span className="text-sm text-center">请在后台配置收款码</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-gray-400 text-sm">{hint}</p>
|
||||
<p className="text-[var(--app-text-tertiary)] text-sm">{hint}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Crypto/PayPal Address */}
|
||||
{(isCrypto || isPaypal) && (
|
||||
<div className="mb-6">
|
||||
<div className="bg-[#0a1628] rounded-xl p-4 border border-gray-700/30">
|
||||
<p className="text-gray-400 text-sm mb-2">
|
||||
<div className="glass-card p-4">
|
||||
<p className="text-[var(--app-text-tertiary)] text-xs 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>
|
||||
<p className="text-white text-sm break-all flex-1 font-mono bg-[var(--app-bg-secondary)] p-3 rounded-lg">
|
||||
{address || "请在后台配置收款地址"}
|
||||
</p>
|
||||
{address && (
|
||||
<button
|
||||
onClick={() => handleCopyAddress(address)}
|
||||
className="text-[#38bdac] hover:text-[#4fd4c4]"
|
||||
className="w-10 h-10 rounded-xl bg-[var(--app-brand-light)] flex items-center justify-center text-[var(--app-brand)] touch-feedback"
|
||||
>
|
||||
{copied ? <Check className="w-5 h-5" /> : <Copy className="w-5 h-5" />}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-gray-500 text-sm mt-2 text-center">{hint}</p>
|
||||
<p className="text-[var(--app-text-tertiary)] text-sm mt-3 text-center">{hint}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 提示信息 */}
|
||||
<div className="bg-[#38bdac]/10 border border-[#38bdac]/30 rounded-xl p-3 mb-4">
|
||||
<p className="text-[#38bdac] text-sm text-center">支付完成后,系统将自动开通阅读权限</p>
|
||||
<div className="glass-card p-4 mb-6 border-[var(--app-brand)]/20">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-10 h-10 rounded-xl bg-[var(--app-brand-light)] flex items-center justify-center">
|
||||
<Shield className="w-5 h-5 text-[var(--app-brand)]" />
|
||||
</div>
|
||||
<p className="text-[var(--app-text-secondary)] text-sm flex-1">
|
||||
支付完成后,系统将自动开通阅读权限
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Action Buttons */}
|
||||
<div className="flex gap-3">
|
||||
<Button
|
||||
<button
|
||||
onClick={() => setShowQRCode(false)}
|
||||
variant="outline"
|
||||
className="flex-1 border-gray-600 text-white hover:bg-gray-700/50 bg-transparent"
|
||||
className="btn-ios-secondary flex-1"
|
||||
>
|
||||
返回
|
||||
</Button>
|
||||
<Button
|
||||
</button>
|
||||
<button
|
||||
onClick={confirmPayment}
|
||||
disabled={isProcessing}
|
||||
className="flex-1 bg-[#38bdac] hover:bg-[#2da396] text-white"
|
||||
className="btn-ios flex-1 glow disabled:opacity-50"
|
||||
>
|
||||
{isProcessing ? "处理中..." : "已完成支付"}
|
||||
</Button>
|
||||
{isProcessing ? (
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
<div className="w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin" />
|
||||
处理中...
|
||||
</div>
|
||||
) : (
|
||||
"已完成支付"
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -285,98 +309,133 @@ export function PaymentModal({ isOpen, onClose, type, sectionId, sectionTitle, a
|
||||
if (isSuccess) {
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
|
||||
<div className="absolute inset-0 bg-black/70 backdrop-blur-sm" />
|
||||
<div className="relative w-full max-w-md bg-[#0f2137] rounded-2xl border border-gray-700/50 overflow-hidden">
|
||||
<div className="absolute inset-0 bg-black/60 backdrop-blur-md modal-overlay" />
|
||||
<div className="relative w-full max-w-sm glass-modal overflow-hidden modal-content">
|
||||
<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 className="w-24 h-24 mx-auto mb-6 rounded-full bg-[var(--app-brand-light)] flex items-center justify-center">
|
||||
<CheckCircle className="w-12 h-12 text-[var(--app-brand)]" />
|
||||
</div>
|
||||
<h3 className="text-xl font-semibold text-white mb-2">支付成功</h3>
|
||||
<p className="text-gray-400">{type === "fullbook" ? "您已解锁全部内容" : "您已解锁本节内容"}</p>
|
||||
{paymentConfig.wechat?.groupQrCode && <p className="text-[#07C160] text-sm mt-4">正在跳转到微信群...</p>}
|
||||
<h3 className="text-2xl font-semibold text-white mb-2">支付成功</h3>
|
||||
<p className="text-[var(--app-text-secondary)] mb-4">
|
||||
{type === "fullbook" ? "您已解锁全部内容" : "您已解锁本节内容"}
|
||||
</p>
|
||||
{paymentConfig.wechat?.groupQrCode && (
|
||||
<div className="glass-card p-4 mt-4">
|
||||
<div className="flex items-center justify-center gap-2 text-[#07C160]">
|
||||
<Users className="w-5 h-5" />
|
||||
<span className="text-sm">正在跳转到读者社群...</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// 主支付选择页面
|
||||
// 主支付选择页面 - iOS风格
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
|
||||
<div className="absolute inset-0 bg-black/70 backdrop-blur-sm" onClick={onClose} />
|
||||
<div className="relative w-full max-w-md bg-[#0f2137] rounded-2xl border border-gray-700/50 shadow-2xl overflow-hidden max-h-[90vh] overflow-y-auto">
|
||||
<div className="fixed inset-0 z-50 flex items-end sm:items-center justify-center">
|
||||
<div className="absolute inset-0 bg-black/60 backdrop-blur-md modal-overlay" onClick={onClose} />
|
||||
<div className="relative w-full sm:max-w-md glass-modal overflow-hidden safe-bottom modal-content sm:m-4 max-h-[90vh] overflow-y-auto scrollbar-hide">
|
||||
{/* 顶部把手 - 仅移动端 */}
|
||||
<div className="flex justify-center pt-3 pb-2 sm:hidden sticky top-0 bg-transparent">
|
||||
<div className="w-9 h-1 rounded-full bg-[var(--app-text-tertiary)]" />
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="absolute top-4 right-4 p-2 text-gray-400 hover:text-white transition-colors z-10"
|
||||
className="absolute top-4 right-4 w-8 h-8 rounded-full bg-[var(--app-bg-secondary)] flex items-center justify-center text-[var(--app-text-secondary)] hover:text-white z-10 touch-feedback"
|
||||
>
|
||||
<X className="w-5 h-5" />
|
||||
<X className="w-4 h-4" />
|
||||
</button>
|
||||
|
||||
{/* Header */}
|
||||
<div className="p-6 pt-12 border-b border-gray-700/50">
|
||||
<h3 className="text-lg font-semibold text-white mb-1">确认支付</h3>
|
||||
<p className="text-gray-400 text-sm">
|
||||
<div className="px-6 pt-2 sm:pt-10 pb-4">
|
||||
<h3 className="text-xl font-semibold text-white mb-1">确认支付</h3>
|
||||
<p className="text-[var(--app-text-tertiary)] text-sm">
|
||||
{type === "fullbook" ? "购买整本书,解锁全部内容" : `购买: ${sectionTitle}`}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Amount */}
|
||||
<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>
|
||||
<div className="px-6 py-6 text-center border-y border-[var(--app-separator)]">
|
||||
<p className="text-[var(--app-text-tertiary)] text-sm mb-2">支付金额</p>
|
||||
<p className="text-5xl 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>
|
||||
<p className="text-[var(--app-brand)] text-sm mt-2">
|
||||
≈ ${paymentMethod === "usdt" ? usdtAmount : paypalAmount} USD
|
||||
</p>
|
||||
)}
|
||||
{user?.referredBy && (
|
||||
<p className="text-[#38bdac] text-sm mt-2">
|
||||
通过邀请注册,{settings?.distributorShare || 90}%将返还给推荐人
|
||||
</p>
|
||||
<div className="inline-flex items-center gap-2 mt-4 px-4 py-2 rounded-full bg-[var(--app-brand-light)]">
|
||||
<Users className="w-4 h-4 text-[var(--app-brand)]" />
|
||||
<span className="text-[var(--app-brand)] text-sm">
|
||||
推荐人将获得 {settings?.distributorShare || 90}% 返佣
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Payment Methods */}
|
||||
<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 hover:bg-[#162840]"
|
||||
}`}
|
||||
>
|
||||
<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 className="p-6">
|
||||
<p className="text-[var(--app-text-tertiary)] text-sm mb-4">选择支付方式</p>
|
||||
<div className="space-y-3">
|
||||
{availableMethods.map((method) => (
|
||||
<button
|
||||
key={method.id}
|
||||
onClick={() => setPaymentMethod(method.id)}
|
||||
className={`w-full p-4 rounded-xl flex items-center gap-4 transition-all touch-feedback ${
|
||||
paymentMethod === method.id
|
||||
? "glass-card-light border-[var(--app-brand)]/30"
|
||||
: "glass-card hover:border-[var(--glass-border-light)]"
|
||||
}`}
|
||||
>
|
||||
<div
|
||||
className="w-12 h-12 rounded-xl flex items-center justify-center"
|
||||
style={{ backgroundColor: method.iconBg, color: method.color }}
|
||||
>
|
||||
{method.icon}
|
||||
</div>
|
||||
<div className="flex-1 text-left">
|
||||
<span className="text-white font-medium">{method.name}</span>
|
||||
{method.extra && (
|
||||
<span className="text-[var(--app-text-tertiary)] text-sm ml-2">{method.extra}</span>
|
||||
)}
|
||||
</div>
|
||||
{paymentMethod === method.id && (
|
||||
<div className="w-6 h-6 rounded-full bg-[var(--app-brand)] flex items-center justify-center">
|
||||
<Check className="w-4 h-4 text-white" />
|
||||
</div>
|
||||
)}
|
||||
</button>
|
||||
))}
|
||||
{availableMethods.length === 0 && (
|
||||
<p className="text-[var(--app-text-tertiary)] text-center py-8">暂无可用支付方式</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Submit Button */}
|
||||
<div className="p-6 pt-0">
|
||||
<Button
|
||||
<button
|
||||
onClick={handlePayment}
|
||||
disabled={isProcessing || availableMethods.length === 0}
|
||||
className="w-full bg-[#38bdac] hover:bg-[#2da396] text-white py-6 text-lg"
|
||||
className="btn-ios w-full glow text-lg disabled:opacity-50"
|
||||
>
|
||||
{isProcessing ? (
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex items-center justify-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>
|
||||
</button>
|
||||
<p className="text-[var(--app-text-tertiary)] text-xs text-center mt-4">
|
||||
支付即表示同意《用户协议》和《隐私政策》
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user