2025-12-24 16:42:58 +08:00

194 lines
8.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use client'
import { useEffect, useState } from 'react'
import { useSession } from 'next-auth/react'
import { Loader2, User, Mail, AlertCircle, PackageCheck, KeyRound, CreditCard } from 'lucide-react'
import Link from 'next/link'
import Header from '@/components/Header'
type Order = {
id: number
order_no: string
product_type: string
product_name: string
amount: number
status: string
payment_method?: string
trade_no?: string
created_at: string
paid_at?: string
metadata?: any
}
export default function AccountPage() {
const { data: session, status } = useSession()
const [orders, setOrders] = useState<Order[]>([])
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
useEffect(() => {
if (status !== 'authenticated') return
const fetchOrders = async () => {
try {
const res = await fetch('/api/orders')
const data = await res.json()
if (res.ok && data.success) {
setOrders(data.orders || [])
} else {
setError(data.message || '加载订单失败')
}
} catch (e: any) {
setError(e?.message || '加载订单失败')
} finally {
setLoading(false)
}
}
fetchOrders()
}, [status])
if (status === 'loading') {
return (
<div className="min-h-screen flex items-center justify-center">
<Loader2 className="w-8 h-8 animate-spin text-apple-blue" />
</div>
)
}
if (status !== 'authenticated' || !session) {
return (
<div className="min-h-screen flex flex-col items-center justify-center space-y-4">
<AlertCircle className="w-10 h-10 text-orange-500" />
<p className="text-apple-gray-700">请先登录后查看个人中心</p>
<Link
href="/auth/signin"
className="px-4 py-2 bg-apple-blue text-white rounded-full font-semibold"
>
去登录
</Link>
</div>
)
}
return (
<div className="min-h-screen bg-gradient-to-b from-apple-gray-50 to-white">
<Header scrolled />
<div className="max-w-6xl mx-auto px-4 py-14">
<div className="text-center mb-8">
<h1 className="text-3xl font-bold text-apple-gray-900">个人中心</h1>
<p className="text-apple-gray-600 text-sm mt-2">查看账户信息、订单和激活码</p>
</div>
<div className="grid md:grid-cols-3 gap-6 mb-8 items-start">
<div className="md:col-span-1 bg-gradient-to-br from-apple-blue/10 to-white rounded-2xl p-6 border border-apple-gray-100 apple-shadow-lg">
<div className="flex items-center space-x-3 mb-4">
<div className="w-12 h-12 rounded-full bg-apple-blue text-white flex items-center justify-center">
<User className="w-6 h-6" />
</div>
<div>
<p className="font-bold text-apple-gray-900">{session.user?.name || '未命名用户'}</p>
<p className="text-sm text-apple-gray-500">ID: {session.user?.id}</p>
</div>
</div>
<div className="flex items-center space-x-2 text-sm text-apple-gray-700 mb-2">
<Mail className="w-4 h-4 text-apple-blue" />
<span className="truncate max-w-[220px]">{session.user?.email}</span>
</div>
</div>
<div className="md:col-span-2 bg-white rounded-2xl p-6 border border-apple-gray-100 apple-shadow-lg">
<div className="flex items-center justify-between mb-4">
<h2 className="text-xl font-bold text-apple-gray-900">订单列表</h2>
{loading && <Loader2 className="w-4 h-4 animate-spin text-apple-blue" />}
</div>
{error && (
<div className="text-red-600 text-sm mb-3 flex items-center space-x-2">
<AlertCircle className="w-4 h-4" />
<span>{error}</span>
</div>
)}
{orders.length === 0 && !loading ? (
<p className="text-apple-gray-600 text-sm">暂无订单</p>
) : (
<div className="space-y-3">
{orders.map((o) => {
const license = o.metadata?.license
const licenseError = o.metadata?.licenseError
return (
<div
key={o.id}
className="border border-apple-gray-100 rounded-xl p-4 hover:border-apple-blue transition bg-gradient-to-br from-white to-apple-gray-50"
>
<div className="flex items-center justify-between mb-1">
<div className="font-mono text-sm text-apple-gray-800">{o.order_no}</div>
<div
className={`text-xs px-2 py-1 rounded-full ${
o.status === 'paid'
? 'bg-green-100 text-green-700'
: o.status === 'failed'
? 'bg-red-100 text-red-600'
: 'bg-amber-100 text-amber-700'
}`}
>
{o.status}
</div>
</div>
<div className="text-sm text-apple-gray-700">
{o.product_name} · ¥{o.amount} · {o.payment_method || '—'}
</div>
{o.trade_no && (
<div className="text-xs text-apple-gray-500 break-all mt-1">交易号:{o.trade_no}</div>
)}
{/* 激活码区域 */}
{o.status === 'paid' && (
<div className="mt-3">
{license ? (
<div className="bg-gradient-to-r from-emerald-50 to-green-50 border border-green-200 rounded-lg p-3">
<div className="flex items-center justify-between mb-1">
<div className="flex items-center space-x-1 text-sm text-green-700 font-semibold">
<KeyRound className="w-4 h-4" />
<span>激活码</span>
</div>
<span className="text-[11px] text-apple-gray-500">
{license.planName || license.plan}
</span>
</div>
<div className="font-mono text-xs break-all text-apple-gray-900">
{license.activationCode}
</div>
{license.isPermanent === false && license.expiresAt && (
<div className="text-[11px] text-apple-gray-500 mt-1">
到期:{license.expiresAt}
</div>
)}
{license.isPermanent && (
<div className="text-[11px] text-apple-gray-500 mt-1">永久有效</div>
)}
</div>
) : licenseError ? (
<div className="bg-red-50 border border-red-200 rounded-lg p-3 text-xs text-red-700">
激活码生成失败:{licenseError.message || '请联系客服'}{' '}
{licenseError.at ? `(${licenseError.at})` : ''}
</div>
) : (
<div className="bg-amber-50 border border-amber-200 rounded-lg p-3 text-xs text-amber-700">
激活码生成中,请稍后再查看
</div>
)}
</div>
)}
</div>
)
})}
</div>
)}
</div>
</div>
</div>
</div>
)
}