Polish scan payment layout (#690)

This commit is contained in:
shenlan 2025-11-21 22:58:58 +08:00 committed by GitHub
parent ad413f2928
commit 047ace5ebe
2 changed files with 113 additions and 93 deletions

View File

@ -45,6 +45,11 @@ const methodHints: Record<BillingPaymentMethod['type'], string> = {
export default function BillingOptionsPanel() {
const [statusMessage, setStatusMessage] = useState<string | null>(null)
const [selected, setSelected] = useState<ProductOption | null>(null)
const primaryButtonClass =
'inline-flex w-full items-center justify-center rounded-md bg-[var(--color-primary)] px-4 py-2 text-sm font-semibold text-white shadow-sm transition-colors hover:bg-[var(--color-primary-strong)]'
const qrClassName = 'mx-auto h-56 w-56 object-contain'
const paymentCardClass =
'flex h-full flex-col gap-4 rounded-2xl border border-[color:var(--color-surface-border)] bg-[color:var(--color-surface)] p-5 shadow-sm'
const products = useMemo(
() => PRODUCT_LIST.filter((item) => item.billing && (item.billing.paygo || item.billing.saas)),
@ -150,32 +155,33 @@ export default function BillingOptionsPanel() {
const qrCode = method.qrCode?.trim()
return (
<div
key={`${selected?.plan.planId}-${method.type}`}
className="flex flex-col gap-4 rounded-2xl border border-[color:var(--color-surface-border)] bg-[color:var(--color-surface)] p-4 shadow-sm"
>
<div key={`${selected?.plan.planId}-${method.type}`} className={paymentCardClass}>
<div className="space-y-1">
<p className="text-xs font-semibold uppercase tracking-wide text-[var(--color-primary)]">{method.label || method.type}</p>
<p className="text-sm font-semibold text-[var(--color-heading)]">{method.type === 'ethereum' ? 'ETH 扫码支付' : 'USDTTRC20扫码'}</p>
<p className="text-xs text-[var(--color-text-subtle)]">{methodHints[method.type]}</p>
<p className="text-lg font-semibold text-[var(--color-heading)]">{method.type === 'ethereum' ? 'ETHERC20' : 'USDTTRC20'}</p>
<p className="text-sm text-[var(--color-text-subtle)]">{methodHints[method.type]}</p>
</div>
{qrCode ? (
<div className="rounded-xl bg-white p-3 text-center">
<img src={qrCode} alt={`${method.label || method.type} QR`} className="mx-auto h-44 w-44 object-contain" />
<div className="rounded-xl bg-white p-4 text-center">
<img src={qrCode} alt={`${method.label || method.type} QR`} className={qrClassName} />
</div>
) : null}
{address ? (
<div className="space-y-1 rounded-xl bg-white p-3 text-xs text-[var(--color-text)]">
<div className="flex items-center justify-between gap-2">
<div className="flex flex-col">
<span className="font-semibold text-[var(--color-heading)]"></span>
<span className="truncate font-mono" title={address}>
{address}
<div className="space-y-2 rounded-xl bg-[color:var(--color-surface-muted)] p-3 text-xs text-[var(--color-text)]">
<div className="flex items-center justify-between">
<span className="font-semibold text-[color:var(--color-heading)]"></span>
{network ? (
<span className="rounded-full bg-white px-3 py-1 text-[11px] font-semibold text-[color:var(--color-heading)]">
{network}
</span>
{network ? <span className="text-[11px] text-[var(--color-text-subtle)]"> / Network{network}</span> : null}
</div>
) : null}
</div>
<div className="flex items-center gap-2 rounded-lg bg-white/60 p-2">
<span className="flex-1 truncate font-mono" title={address}>
{address}
</span>
<button
type="button"
onClick={async () => {
@ -183,7 +189,7 @@ export default function BillingOptionsPanel() {
await navigator.clipboard.writeText(address)
setStatusMessage('已复制钱包地址,完成支付后订单将自动识别。')
}}
className="inline-flex items-center rounded-md bg-[var(--color-heading)] px-3 py-1 text-[11px] font-semibold text-white shadow-sm transition-colors hover:bg-[var(--color-primary)]"
className="inline-flex items-center rounded-md bg-white px-3 py-1 text-[11px] font-semibold text-[color:var(--color-heading)] shadow-sm transition-colors hover:bg-[color:var(--color-surface)]"
>
</button>
@ -192,11 +198,7 @@ export default function BillingOptionsPanel() {
) : null}
<div className="mt-auto flex flex-wrap gap-2">
<button
type="button"
onClick={() => handleCryptoRecord(method)}
className="inline-flex w-full items-center justify-center rounded-md bg-[var(--color-primary)] px-4 py-2 text-sm font-semibold text-white shadow-sm transition-colors hover:bg-[var(--color-primary-strong)]"
>
<button type="button" onClick={() => handleCryptoRecord(method)} className={primaryButtonClass}>
使
</button>
</div>
@ -208,16 +210,16 @@ export default function BillingOptionsPanel() {
if (!selected) return null
return (
<div className="flex flex-col gap-4 rounded-2xl border border-[color:var(--color-surface-border)] bg-[color:var(--color-surface)] p-4 shadow-sm">
<div className={paymentCardClass}>
<div className="space-y-1">
<p className="text-xs font-semibold uppercase tracking-wide text-[var(--color-primary)]">PayPal</p>
<p className="text-sm font-semibold text-[var(--color-heading)]">PayPal </p>
<p className="text-xs text-[var(--color-text-subtle)]">{methodHints.paypal}</p>
<p className="text-lg font-semibold text-[var(--color-heading)]">PayPal </p>
<p className="text-sm text-[var(--color-text-subtle)]">{methodHints.paypal}</p>
</div>
{method.qrCode ? (
<div className="rounded-xl bg-white p-3 text-center">
<img src={method.qrCode} alt="PayPal QR" className="mx-auto h-44 w-44 object-contain" />
<div className="rounded-xl bg-white p-4 text-center">
<img src={method.qrCode} alt="PayPal QR" className={qrClassName} />
</div>
) : null}
@ -265,7 +267,7 @@ export default function BillingOptionsPanel() {
</div>
<button
type="button"
className="inline-flex w-full items-center justify-center rounded-md bg-[var(--color-primary)] px-4 py-2 text-sm font-semibold text-white shadow-sm transition-colors hover:bg-[var(--color-primary-strong)]"
className={primaryButtonClass}
onClick={() => setStatusMessage('正在使用 PayPal 支付,完成后系统会自动同步订单。')}
>
使
@ -277,82 +279,89 @@ export default function BillingOptionsPanel() {
return (
<Card>
<div className="flex flex-col gap-3">
<div>
<p className="text-sm font-semibold text-[var(--color-primary)]"></p>
<h2 className="text-xl font-semibold text-[var(--color-heading)]"></h2>
<p className="text-sm text-[var(--color-text-subtle)]">
PayPal / ETH / USDTTRC20
</p>
<div className="flex flex-col gap-5">
<div className="flex items-start justify-between gap-3 flex-col md:flex-row md:items-center">
<div className="space-y-1">
<p className="text-xs font-semibold uppercase tracking-wide text-[var(--color-primary)]">Product Mode</p>
<h2 className="text-xl font-semibold text-[color:var(--color-heading)]"></h2>
<p className="text-sm text-[var(--color-text-subtle)]">
PAY-AS-YOU-GO SaaS
</p>
<p className="text-xs font-semibold text-[var(--color-heading)]">
/
</p>
</div>
{selected?.clientId ? (
<p className="rounded-full bg-[color:var(--color-surface-muted)] px-4 py-2 text-xs font-semibold text-[var(--color-heading)]">
PayPal /
</p>
) : (
<p className="rounded-full bg-[color:var(--color-surface-muted)] px-4 py-2 text-xs font-semibold text-[var(--color-heading)]">
PayPal Client ID
</p>
)}
</div>
<div className="mt-2 space-y-3">
<div className="flex items-center justify-between">
<div>
<p className="text-xs font-semibold uppercase tracking-wide text-[var(--color-primary)]"></p>
<h3 className="text-lg font-semibold text-[var(--color-heading)]"></h3>
<p className="text-sm text-[var(--color-text-subtle)]">PAYG SaaS </p>
</div>
{selected?.clientId ? (
<p className="text-xs text-[var(--color-text-subtle)]"> PayPal / </p>
) : (
<p className="text-xs text-[var(--color-text-subtle)]"> PayPal Client ID</p>
)}
</div>
<div className="grid gap-4 md:grid-cols-2">
{productOptions.map((option) => {
const isActive =
selected !== null && selected.plan.planId === option.plan.planId && selected.kind === option.kind
return (
<div
key={`${option.product.slug}-${option.kind}`}
className={`flex h-full flex-col rounded-2xl border p-4 shadow-sm transition-colors ${
isActive
? 'border-[color:var(--color-primary)] bg-[color:var(--color-surface)]'
: 'border-[color:var(--color-surface-border)] bg-[color:var(--color-surface-muted)] hover:border-[color:var(--color-primary)]'
}`}
>
<div className="flex items-start justify-between gap-3">
<div className="grid gap-4 md:grid-cols-2">
{productOptions.map((option) => {
const isActive =
selected !== null && selected.plan.planId === option.plan.planId && selected.kind === option.kind
const isSubscription = option.kind === 'subscription'
const description = isSubscription
? '全节点加速 + AI 网络优化。自动续费,可随时取消。'
: '一次购买可叠加,不自动续费。'
return (
<div
key={`${option.product.slug}-${option.kind}`}
className={`flex h-full flex-col gap-3 rounded-2xl border p-5 shadow-sm transition-colors ${
isActive
? 'border-[color:var(--color-primary)] bg-[color:var(--color-surface)]'
: 'border-[color:var(--color-surface-border)] bg-[color:var(--color-surface-muted)] hover:border-[color:var(--color-primary)]'
}`}
>
<div className="space-y-2">
<div className="flex items-start justify-between gap-2">
<div className="space-y-1">
<p className="text-xs font-semibold uppercase tracking-wide text-[var(--color-primary)]">
{kindLabel[option.kind]}
</p>
<h4 className="text-lg font-semibold text-[var(--color-heading)]">{option.plan.name}</h4>
{option.plan.description ? (
<p className="text-sm text-[var(--color-text-subtle)]">{option.plan.description}</p>
) : null}
<p className="text-xl font-bold text-[var(--color-heading)]">
{option.plan.currency} {option.plan.price.toFixed(2)}
{option.kind === 'subscription' && option.plan.interval ? ` / ${option.plan.interval}` : null}
</p>
</div>
<span className="rounded-full bg-white px-3 py-1 text-[11px] font-semibold text-[color:var(--color-heading)]">
{isSubscription ? '自动续费' : '一次性购买'}
</span>
</div>
<div className="mt-auto flex flex-wrap gap-2">
<button
type="button"
onClick={() => setSelected(option)}
className={`inline-flex w-full items-center justify-center rounded-md px-4 py-2 text-sm font-semibold shadow-sm transition-colors ${
isActive
? 'bg-[var(--color-primary)] text-white hover:bg-[var(--color-primary-strong)]'
: 'bg-white text-[var(--color-heading)] hover:bg-[var(--color-surface)]'
}`}
>
{actionLabel[option.kind]}
</button>
</div>
<p className="text-sm text-[var(--color-text-subtle)]">{description}</p>
<p className="text-2xl font-bold text-[var(--color-heading)]">
{option.plan.currency} {option.plan.price.toFixed(0)}
{isSubscription && option.plan.interval ? ` / ${option.plan.interval}` : ''}
</p>
</div>
)
})}
</div>
<div className="mt-auto">
<button
type="button"
onClick={() => setSelected(option)}
className={`${
isActive
? 'bg-[var(--color-primary)] text-white hover:bg-[var(--color-primary-strong)]'
: 'bg-white text-[var(--color-heading)] hover:bg-[color:var(--color-surface)]'
} inline-flex w-full items-center justify-center rounded-md px-4 py-2 text-sm font-semibold shadow-sm transition-colors`}
>
{actionLabel[option.kind]}
</button>
</div>
</div>
)
})}
</div>
{selected ? (
<div className="space-y-3 rounded-2xl border border-[color:var(--color-surface-border)] bg-[color:var(--color-surface-muted)] p-4">
<div className="space-y-4 rounded-2xl border border-[color:var(--color-surface-border)] bg-[color:var(--color-surface-muted)] p-5">
<div className="flex flex-col gap-1">
<p className="text-xs font-semibold uppercase tracking-wide text-[var(--color-primary)]"></p>
<h3 className="text-lg font-semibold text-[var(--color-heading)]"></h3>
<p className="text-xs font-semibold uppercase tracking-wide text-[var(--color-primary)]">Payment</p>
<h3 className="text-lg font-semibold text-[var(--color-heading)]"></h3>
<p className="text-sm text-[var(--color-text-subtle)]">
PayPal ETH/USDT
/ 使
</p>
</div>

View File

@ -8,11 +8,22 @@ export default function UserCenterSubscriptionRoute() {
<Card>
<h1 className="text-2xl font-semibold text-gray-900"></h1>
<p className="mt-2 text-sm text-gray-600">
PayPal / ETH / USDTTRC20
</p>
<p className="mt-1 text-sm text-gray-600">
PAYG / SaaS
PayPal / ETHERC20/ USDTTRC20
</p>
<div className="mt-3 grid gap-3 md:grid-cols-3">
<div className="rounded-xl bg-[color:var(--color-surface-muted)] p-3 text-sm text-gray-700 shadow-sm">
<p className="font-semibold text-[color:var(--color-heading)]"> 1</p>
<p className="text-gray-600">PAYG SaaS</p>
</div>
<div className="rounded-xl bg-[color:var(--color-surface-muted)] p-3 text-sm text-gray-700 shadow-sm">
<p className="font-semibold text-[color:var(--color-heading)]"> 2</p>
<p className="text-gray-600">PayPal / ETH / USDT </p>
</div>
<div className="rounded-xl bg-[color:var(--color-surface-muted)] p-3 text-sm text-gray-700 shadow-sm">
<p className="font-semibold text-[color:var(--color-heading)]"> 3</p>
<p className="text-gray-600"> </p>
</div>
</div>
</Card>
<BillingOptionsPanel />
<SubscriptionPanel />