| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314 |
- import { useState } from 'react';
- import { Button } from '@/client/components/ui/button';
- import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/client/components/ui/card';
- import { Avatar, AvatarFallback, AvatarImage } from '@/client/components/ui/avatar';
- import { Badge } from '@/client/components/ui/badge';
- import { Separator } from '@/client/components/ui/separator';
- import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/client/components/ui/tabs';
- import { Input } from '@/client/components/ui/input';
- import { Label } from '@/client/components/ui/label';
- import { Switch } from '@/client/components/ui/switch';
- import { Alert, AlertDescription } from '@/client/components/ui/alert';
- import {
- User,
- Key,
- CreditCard,
- Clock,
- CheckCircle,
- AlertCircle,
- Eye,
- EyeOff
- } from 'lucide-react';
- import { format } from 'date-fns';
- import { zhCN } from 'date-fns/locale';
- import { useAuth } from '@/client/home/hooks/AuthProvider';
- import { useNavigate } from 'react-router-dom';
- import RechargeRecords from './RechargeRecords';
- interface UserInfoModalProps {
- isOpen: boolean;
- onClose: () => void;
- }
- // PaymentRecord类型已在RechargeRecords中定义
- export default function UserInfoModal({ isOpen, onClose }: UserInfoModalProps) {
- const { user, logout } = useAuth();
- const navigate = useNavigate();
- const [activeTab, setActiveTab] = useState('profile');
- const [showCurrentPassword, setShowCurrentPassword] = useState(false);
- const [showNewPassword, setShowNewPassword] = useState(false);
- const [showConfirmPassword, setShowConfirmPassword] = useState(false);
- const [passwordForm, setPasswordForm] = useState({
- currentPassword: '',
- newPassword: '',
- confirmPassword: ''
- });
- const [passwordError, setPasswordError] = useState('');
- const [passwordSuccess, setPasswordSuccess] = useState('');
- // 使用实际的充值记录组件
- if (!isOpen || !user) return null;
- const handlePasswordChange = () => {
- setPasswordError('');
- setPasswordSuccess('');
- if (!passwordForm.currentPassword || !passwordForm.newPassword || !passwordForm.confirmPassword) {
- setPasswordError('请填写所有必填字段');
- return;
- }
- if (passwordForm.newPassword !== passwordForm.confirmPassword) {
- setPasswordError('新密码与确认密码不匹配');
- return;
- }
- if (passwordForm.newPassword.length < 6) {
- setPasswordError('新密码至少6位字符');
- return;
- }
- // 这里应该调用修改密码的API
- setPasswordSuccess('密码修改成功!');
- setPasswordForm({ currentPassword: '', newPassword: '', confirmPassword: '' });
-
- setTimeout(() => {
- setPasswordSuccess('');
- }, 3000);
- };
- const handleLogout = async () => {
- await logout();
- onClose();
- navigate('/');
- };
- const getStatusBadge = (status: string) => {
- switch (status) {
- case 'completed':
- return <Badge className="bg-green-100 text-green-800">已完成</Badge>;
- case 'pending':
- return <Badge className="bg-yellow-100 text-yellow-800">处理中</Badge>;
- case 'failed':
- return <Badge className="bg-red-100 text-red-800">失败</Badge>;
- default:
- return <Badge>{status}</Badge>;
- }
- };
- return (
- <div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
- <div className="bg-white rounded-lg max-w-2xl w-full max-h-[90vh] overflow-hidden shadow-2xl">
- <div className="flex justify-between items-center p-6 border-b">
- <h2 className="text-2xl font-bold">用户中心</h2>
- <Button variant="ghost" size="sm" onClick={onClose} className="text-gray-500">
- ✕
- </Button>
- </div>
- <div className="overflow-y-auto" style={{ maxHeight: 'calc(90vh - 80px)' }}>
- <Tabs value={activeTab} onValueChange={setActiveTab} className="w-full">
- <TabsList className="w-full justify-start rounded-none border-b bg-transparent p-0">
- <TabsTrigger value="profile" className="rounded-none border-b-2 border-transparent data-[state=active]:border-blue-500">
- <User className="h-4 w-4 mr-2" />
- 个人信息
- </TabsTrigger>
- <TabsTrigger value="security" className="rounded-none border-b-2 border-transparent data-[state=active]:border-blue-500">
- <Key className="h-4 w-4 mr-2" />
- 密码修改
- </TabsTrigger>
- <TabsTrigger value="recharge" className="rounded-none border-b-2 border-transparent data-[state=active]:border-blue-500">
- <CreditCard className="h-4 w-4 mr-2" />
- 充值记录
- </TabsTrigger>
- </TabsList>
- <TabsContent value="profile" className="p-6">
- <div className="space-y-6">
- <div className="flex items-center space-x-4">
- <Avatar className="h-20 w-20">
- <AvatarImage
- src={user.avatar || `https://avatar.vercel.sh/${user.username}`}
- alt={user.nickname || user.username}
- />
- <AvatarFallback className="text-2xl">
- {user.username?.charAt(0).toUpperCase()}
- </AvatarFallback>
- </Avatar>
- <div>
- <h3 className="text-xl font-semibold">{user.nickname || user.username}</h3>
- <p className="text-gray-500">@{user.username}</p>
- <Badge className={user.userType === 'premium' ? 'bg-purple-100 text-purple-800' : 'bg-gray-100 text-gray-800'}>
- {user.userType === 'premium' ? '高级会员' : '普通会员'}
- </Badge>
- </div>
- </div>
- <Card>
- <CardHeader>
- <CardTitle className="text-lg">账户信息</CardTitle>
- </CardHeader>
- <CardContent className="space-y-4">
- <div className="grid grid-cols-2 gap-4">
- <div>
- <Label className="text-sm text-gray-500">邮箱</Label>
- <p className="font-medium">{user.email || '未设置'}</p>
- </div>
- <div>
- <Label className="text-sm text-gray-500">手机号</Label>
- <p className="font-medium">{user.phone || '未设置'}</p>
- </div>
- </div>
- <div>
- <Label className="text-sm text-gray-500">注册时间</Label>
- <p className="font-medium">
- {format(new Date(user.createdAt), 'yyyy年MM月dd日 HH:mm', { locale: zhCN })}
- </p>
- </div>
- </CardContent>
- </Card>
- <Card>
- <CardHeader>
- <CardTitle className="text-lg">会员到期时间</CardTitle>
- </CardHeader>
- <CardContent>
- <div className="flex items-center justify-between">
- <div>
- <p className="text-3xl font-bold text-blue-600">
- {user.expireAt ? format(new Date(user.expireAt), 'yyyy年MM月dd日', { locale: zhCN }) : '未开通'}
- </p>
- <p className="text-sm text-gray-500">
- {user.expireAt ? `剩余 ${Math.ceil((new Date(user.expireAt).getTime() - new Date().getTime()) / (1000 * 60 * 60 * 24))} 天` : '请开通会员'}
- </p>
- </div>
- <div className="text-right">
- <p className="text-sm text-gray-500">剩余次数</p>
- <p className="font-semibold">{user.remainingCount || 0} 次</p>
- </div>
- </div>
- <Button
- className="w-full mt-4"
- onClick={() => {
- onClose();
- navigate('/recharge');
- }}
- >
- <CreditCard className="h-4 w-4 mr-2" />
- {user.expireAt ? '续费会员' : '立即开通'}
- </Button>
- </CardContent>
- </Card>
- <Button
- variant="outline"
- className="w-full text-red-600 hover:text-red-700"
- onClick={handleLogout}
- >
- 退出登录
- </Button>
- </div>
- </TabsContent>
- <TabsContent value="security" className="p-6">
- <div className="space-y-6">
- <Card>
- <CardHeader>
- <CardTitle className="text-lg">修改密码</CardTitle>
- <CardDescription>定期修改密码可以提高账户安全性</CardDescription>
- </CardHeader>
- <CardContent className="space-y-4">
- {passwordError && (
- <Alert variant="destructive">
- <AlertCircle className="h-4 w-4" />
- <AlertDescription>{passwordError}</AlertDescription>
- </Alert>
- )}
- {passwordSuccess && (
- <Alert className="bg-green-50 text-green-800">
- <CheckCircle className="h-4 w-4" />
- <AlertDescription>{passwordSuccess}</AlertDescription>
- </Alert>
- )}
-
- <div className="space-y-4">
- <div className="space-y-2">
- <Label htmlFor="currentPassword">当前密码</Label>
- <div className="relative">
- <Input
- id="currentPassword"
- type={showCurrentPassword ? 'text' : 'password'}
- value={passwordForm.currentPassword}
- onChange={(e) => setPasswordForm({...passwordForm, currentPassword: e.target.value})}
- placeholder="请输入当前密码"
- />
- <button
- type="button"
- className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600"
- onClick={() => setShowCurrentPassword(!showCurrentPassword)}
- >
- {showCurrentPassword ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
- </button>
- </div>
- </div>
- <div className="space-y-2">
- <Label htmlFor="newPassword">新密码</Label>
- <div className="relative">
- <Input
- id="newPassword"
- type={showNewPassword ? 'text' : 'password'}
- value={passwordForm.newPassword}
- onChange={(e) => setPasswordForm({...passwordForm, newPassword: e.target.value})}
- placeholder="请输入新密码(至少6位)"
- />
- <button
- type="button"
- className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600"
- onClick={() => setShowNewPassword(!showNewPassword)}
- >
- {showNewPassword ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
- </button>
- </div>
- </div>
- <div className="space-y-2">
- <Label htmlFor="confirmPassword">确认密码</Label>
- <div className="relative">
- <Input
- id="confirmPassword"
- type={showConfirmPassword ? 'text' : 'password'}
- value={passwordForm.confirmPassword}
- onChange={(e) => setPasswordForm({...passwordForm, confirmPassword: e.target.value})}
- placeholder="请再次输入新密码"
- />
- <button
- type="button"
- className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600"
- onClick={() => setShowConfirmPassword(!showConfirmPassword)}
- >
- {showConfirmPassword ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
- </button>
- </div>
- </div>
- </div>
- <Button onClick={handlePasswordChange} className="w-full">
- 确认修改
- </Button>
- </CardContent>
- </Card>
- </div>
- </TabsContent>
- <TabsContent value="recharge" className="p-6">
- <RechargeRecords userId={user.id} />
- </TabsContent>
- </Tabs>
- </div>
- </div>
- </div>
- );
- }
|