|
|
@@ -1,11 +1,24 @@
|
|
|
-import debug from 'debug';
|
|
|
import React from 'react';
|
|
|
-import { UserIcon, PencilIcon } from '@heroicons/react/24/outline';
|
|
|
-import { useParams, useNavigate } from 'react-router-dom';
|
|
|
-import { useQuery } from '@tanstack/react-query';
|
|
|
-import type { InferResponseType } from 'hono/client';
|
|
|
-import { userClient } from '@/client/api';
|
|
|
-import { useAuth, User } from '@/client/home/hooks/AuthProvider';
|
|
|
+import { useNavigate } from 'react-router-dom';
|
|
|
+import { useAuth } from '@/client/home-shadcn/hooks/AuthProvider';
|
|
|
+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 {
|
|
|
+ User,
|
|
|
+ MapPin,
|
|
|
+ Globe,
|
|
|
+ Calendar,
|
|
|
+ LogOut,
|
|
|
+ Settings,
|
|
|
+ UserCog,
|
|
|
+ ShieldCheck,
|
|
|
+ Clock
|
|
|
+} from 'lucide-react';
|
|
|
+import { format } from 'date-fns';
|
|
|
+import { zhCN } from 'date-fns/locale';
|
|
|
|
|
|
const MemberPage: React.FC = () => {
|
|
|
const navigate = useNavigate();
|
|
|
@@ -13,137 +26,205 @@ const MemberPage: React.FC = () => {
|
|
|
|
|
|
if (!user) {
|
|
|
return (
|
|
|
- <div className="text-center py-12">
|
|
|
- <h2 className="text-2xl font-bold text-gray-900 mb-4">用户不存在</h2>
|
|
|
- <button
|
|
|
- onClick={() => navigate('/')}
|
|
|
- className="bg-blue-600 text-white px-4 py-2 rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
|
|
|
- >
|
|
|
- 返回首页
|
|
|
- </button>
|
|
|
+ <div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-slate-50 to-slate-100 px-4">
|
|
|
+ <Card className="max-w-md">
|
|
|
+ <CardHeader>
|
|
|
+ <CardTitle>用户不存在</CardTitle>
|
|
|
+ <CardDescription>请先登录后再访问此页面</CardDescription>
|
|
|
+ </CardHeader>
|
|
|
+ <CardContent>
|
|
|
+ <Button onClick={() => navigate('/')} className="w-full">
|
|
|
+ 返回首页
|
|
|
+ </Button>
|
|
|
+ </CardContent>
|
|
|
+ </Card>
|
|
|
</div>
|
|
|
);
|
|
|
}
|
|
|
|
|
|
return (
|
|
|
- <div className="min-h-screen bg-gray-50">
|
|
|
- <div className="container mx-auto px-4 py-8 max-w-4xl">
|
|
|
- {/* 用户资料卡片 */}
|
|
|
- <div className="bg-white rounded-lg shadow-sm p-6 mb-8">
|
|
|
- <div className="flex flex-col items-center">
|
|
|
- <div className="w-24 h-24 rounded-full bg-gray-200 flex items-center justify-center mb-4">
|
|
|
- {user.avatar ? (
|
|
|
- <img src={user.avatar} alt={user.nickname || user.username} className="h-full w-full object-cover rounded-full" />
|
|
|
- ) : (
|
|
|
- <UserIcon className="h-12 w-12 text-gray-500" />
|
|
|
- )}
|
|
|
- </div>
|
|
|
-
|
|
|
- <h1 className="text-2xl font-bold text-gray-900 mb-1">{user.nickname || user.username}</h1>
|
|
|
-
|
|
|
- <div className="flex space-x-8 my-4">
|
|
|
- <div className="text-center">
|
|
|
- <p className="text-2xl font-semibold text-gray-900">0</p>
|
|
|
- <p className="text-sm text-gray-500">内容</p>
|
|
|
+ <div className="min-h-screen bg-gradient-to-br from-slate-50 to-slate-100">
|
|
|
+ <div className="container py-8">
|
|
|
+ <div className="mx-auto max-w-4xl space-y-8">
|
|
|
+ {/* 用户资料卡片 */}
|
|
|
+ <Card className="border-0 shadow-lg">
|
|
|
+ <CardContent className="pt-6">
|
|
|
+ <div className="flex flex-col items-center space-y-4">
|
|
|
+ <Avatar className="h-24 w-24">
|
|
|
+ <AvatarImage
|
|
|
+ src={user.avatar || `https://avatar.vercel.sh/${user.username}`}
|
|
|
+ alt={user.nickname || user.username}
|
|
|
+ />
|
|
|
+ <AvatarFallback className="text-2xl bg-primary text-primary-foreground">
|
|
|
+ {user.username?.charAt(0).toUpperCase()}
|
|
|
+ </AvatarFallback>
|
|
|
+ </Avatar>
|
|
|
+
|
|
|
+ <div className="text-center space-y-2">
|
|
|
+ <h1 className="text-3xl font-bold">{user.nickname || user.username}</h1>
|
|
|
+ <p className="text-muted-foreground">@{user.username}</p>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div className="flex items-center space-x-4">
|
|
|
+ <div className="text-center">
|
|
|
+ <p className="text-2xl font-bold">0</p>
|
|
|
+ <p className="text-sm text-muted-foreground">内容</p>
|
|
|
+ </div>
|
|
|
+ <Separator orientation="vertical" className="h-8" />
|
|
|
+ <div className="text-center">
|
|
|
+ <p className="text-2xl font-bold">0</p>
|
|
|
+ <p className="text-sm text-muted-foreground">关注</p>
|
|
|
+ </div>
|
|
|
+ <Separator orientation="vertical" className="h-8" />
|
|
|
+ <div className="text-center">
|
|
|
+ <p className="text-2xl font-bold">0</p>
|
|
|
+ <p className="text-sm text-muted-foreground">粉丝</p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div className="flex items-center space-x-2">
|
|
|
+ <Button
|
|
|
+ onClick={() => navigate('/profile/edit')}
|
|
|
+ className="flex items-center space-x-2"
|
|
|
+ >
|
|
|
+ <UserCog className="h-4 w-4" />
|
|
|
+ <span>编辑资料</span>
|
|
|
+ </Button>
|
|
|
+
|
|
|
+ <Button
|
|
|
+ variant="outline"
|
|
|
+ onClick={async () => {
|
|
|
+ await logout();
|
|
|
+ navigate('/');
|
|
|
+ }}
|
|
|
+ className="flex items-center space-x-2"
|
|
|
+ >
|
|
|
+ <LogOut className="h-4 w-4" />
|
|
|
+ <span>退出登录</span>
|
|
|
+ </Button>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
- <div className="text-center">
|
|
|
- <p className="text-2xl font-semibold text-gray-900">0</p>
|
|
|
- <p className="text-sm text-gray-500">关注</p>
|
|
|
+ </CardContent>
|
|
|
+ </Card>
|
|
|
+
|
|
|
+ {/* 个人资料详情 */}
|
|
|
+ <Card className="border-0 shadow-lg">
|
|
|
+ <CardHeader>
|
|
|
+ <CardTitle className="flex items-center space-x-2">
|
|
|
+ <User className="h-5 w-5" />
|
|
|
+ <span>个人资料</span>
|
|
|
+ </CardTitle>
|
|
|
+ </CardHeader>
|
|
|
+ <CardContent className="space-y-6">
|
|
|
+ <div className="grid gap-4">
|
|
|
+ <div className="space-y-1">
|
|
|
+ <div className="flex items-center space-x-2 text-sm text-muted-foreground">
|
|
|
+ <User className="h-4 w-4" />
|
|
|
+ <span>用户名</span>
|
|
|
+ </div>
|
|
|
+ <p className="font-medium">{user.username}</p>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div className="space-y-1">
|
|
|
+ <div className="flex items-center space-x-2 text-sm text-muted-foreground">
|
|
|
+ <ShieldCheck className="h-4 w-4" />
|
|
|
+ <span>邮箱</span>
|
|
|
+ </div>
|
|
|
+ <p className="font-medium">{user.email || '未设置'}</p>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ {(user as any).location && (
|
|
|
+ <div className="space-y-1">
|
|
|
+ <div className="flex items-center space-x-2 text-sm text-muted-foreground">
|
|
|
+ <MapPin className="h-4 w-4" />
|
|
|
+ <span>位置</span>
|
|
|
+ </div>
|
|
|
+ <p className="font-medium">{(user as any).location}</p>
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+
|
|
|
+ {(user as any).website && (
|
|
|
+ <div className="space-y-1">
|
|
|
+ <div className="flex items-center space-x-2 text-sm text-muted-foreground">
|
|
|
+ <Globe className="h-4 w-4" />
|
|
|
+ <span>网站</span>
|
|
|
+ </div>
|
|
|
+ <a
|
|
|
+ href={(user as any).website}
|
|
|
+ target="_blank"
|
|
|
+ rel="noopener noreferrer"
|
|
|
+ className="font-medium text-primary hover:underline"
|
|
|
+ >
|
|
|
+ {(user as any).website}
|
|
|
+ </a>
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+
|
|
|
+ {(user as any).bio && (
|
|
|
+ <div className="space-y-1">
|
|
|
+ <div className="flex items-center space-x-2 text-sm text-muted-foreground">
|
|
|
+ <User className="h-4 w-4" />
|
|
|
+ <span>个人简介</span>
|
|
|
+ </div>
|
|
|
+ <p className="font-medium">{(user as any).bio}</p>
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
</div>
|
|
|
- <div className="text-center">
|
|
|
- <p className="text-2xl font-semibold text-gray-900">0</p>
|
|
|
- <p className="text-sm text-gray-500">粉丝</p>
|
|
|
+
|
|
|
+ <Separator />
|
|
|
+
|
|
|
+ <div className="grid gap-4 md:grid-cols-2">
|
|
|
+ <div className="space-y-1">
|
|
|
+ <div className="flex items-center space-x-2 text-sm text-muted-foreground">
|
|
|
+ <Calendar className="h-4 w-4" />
|
|
|
+ <span>注册时间</span>
|
|
|
+ </div>
|
|
|
+ <p className="font-medium">
|
|
|
+ {user.createdAt ? format(new Date(user.createdAt), 'yyyy年MM月dd日', { locale: zhCN }) : '未知'}
|
|
|
+ </p>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div className="space-y-1">
|
|
|
+ <div className="flex items-center space-x-2 text-sm text-muted-foreground">
|
|
|
+ <Clock className="h-4 w-4" />
|
|
|
+ <span>最后登录</span>
|
|
|
+ </div>
|
|
|
+ <p className="font-medium">
|
|
|
+ {user.updatedAt ? format(new Date(user.updatedAt), 'yyyy年MM月dd日 HH:mm', { locale: zhCN }) : '从未登录'}
|
|
|
+ </p>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <div className="flex">
|
|
|
- <button
|
|
|
- onClick={() => navigate('/profile/edit')}
|
|
|
- className="bg-blue-600 text-white px-4 py-2 rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 flex items-center"
|
|
|
+ </CardContent>
|
|
|
+ </Card>
|
|
|
+
|
|
|
+ {/* 设置区域 */}
|
|
|
+ <Card className="border-0 shadow-lg">
|
|
|
+ <CardHeader>
|
|
|
+ <CardTitle className="flex items-center space-x-2">
|
|
|
+ <Settings className="h-5 w-5" />
|
|
|
+ <span>账号设置</span>
|
|
|
+ </CardTitle>
|
|
|
+ </CardHeader>
|
|
|
+ <CardContent className="space-y-4">
|
|
|
+ <Button
|
|
|
+ variant="outline"
|
|
|
+ className="w-full justify-start"
|
|
|
+ onClick={() => navigate('/profile/security')}
|
|
|
>
|
|
|
- <PencilIcon className="w-4 h-4 mr-2" />
|
|
|
- 编辑资料
|
|
|
- </button>
|
|
|
+ <ShieldCheck className="h-4 w-4 mr-2" />
|
|
|
+ <span>安全设置</span>
|
|
|
+ </Button>
|
|
|
|
|
|
- <button
|
|
|
- onClick={async () => {
|
|
|
- await logout();
|
|
|
- navigate('/');
|
|
|
- }}
|
|
|
- className="bg-blue-600 text-white px-4 py-2 rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 ml-4"
|
|
|
+ <Button
|
|
|
+ variant="outline"
|
|
|
+ className="w-full justify-start"
|
|
|
+ onClick={() => navigate('/profile/preferences')}
|
|
|
>
|
|
|
- 退出登录
|
|
|
- </button>
|
|
|
-
|
|
|
- </div>
|
|
|
-
|
|
|
- {(user as any).bio && (
|
|
|
- <p className="mt-4 text-center text-gray-600 max-w-lg">
|
|
|
- {(user as any).bio}
|
|
|
- </p>
|
|
|
- )}
|
|
|
-
|
|
|
- <div className="flex items-center mt-4 space-x-4">
|
|
|
- {(user as any).location && (
|
|
|
- <div className="flex items-center text-gray-600">
|
|
|
- <svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
|
- <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z" />
|
|
|
- <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 11a3 3 0 11-6 0 3 3 0 016 0z" />
|
|
|
- </svg>
|
|
|
- <span className="text-sm">{(user as any).location}</span>
|
|
|
- </div>
|
|
|
- )}
|
|
|
- {(user as any).website && (
|
|
|
- <a
|
|
|
- href={(user as any).website}
|
|
|
- target="_blank"
|
|
|
- rel="noopener noreferrer"
|
|
|
- className="flex items-center text-blue-600 hover:text-blue-800"
|
|
|
- >
|
|
|
- <svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
|
- <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6v6m0 0v6m0-6h-6" />
|
|
|
- </svg>
|
|
|
- <span className="text-sm truncate max-w-[150px]">{(user as any).website}</span>
|
|
|
- </a>
|
|
|
- )}
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- {/* 用户内容区域 */}
|
|
|
- <div className="bg-white rounded-lg shadow-sm p-6">
|
|
|
- <h2 className="text-xl font-semibold mb-6">个人资料</h2>
|
|
|
-
|
|
|
- <div className="space-y-4">
|
|
|
- <div className="border-b border-gray-100 pb-4">
|
|
|
- <h3 className="text-sm font-medium text-gray-500 mb-1">用户名</h3>
|
|
|
- <p className="text-gray-900">{user.username}</p>
|
|
|
- </div>
|
|
|
-
|
|
|
- <div className="border-b border-gray-100 pb-4">
|
|
|
- <h3 className="text-sm font-medium text-gray-500 mb-1">电子邮箱</h3>
|
|
|
- <p className="text-gray-900">{user.email || '未设置'}</p>
|
|
|
- </div>
|
|
|
-
|
|
|
- <div className="border-b border-gray-100 pb-4">
|
|
|
- <h3 className="text-sm font-medium text-gray-500 mb-1">注册时间</h3>
|
|
|
- <p className="text-gray-900">{user.createdAt ? new Date(user.createdAt).toLocaleDateString() : '未知'}</p>
|
|
|
- </div>
|
|
|
-
|
|
|
- <div className="border-b border-gray-100 pb-4">
|
|
|
- <h3 className="text-sm font-medium text-gray-500 mb-1">最后登录</h3>
|
|
|
- <p className="text-gray-900">{user.updatedAt ? new Date(user.updatedAt).toLocaleString() : '从未登录'}</p>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <div className="mt-8">
|
|
|
- <button
|
|
|
- onClick={() => navigate('/profile/security')}
|
|
|
- className="w-full py-2 px-4 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
|
|
|
- >
|
|
|
- 安全设置
|
|
|
- </button>
|
|
|
- </div>
|
|
|
+ <Settings className="h-4 w-4 mr-2" />
|
|
|
+ <span>偏好设置</span>
|
|
|
+ </Button>
|
|
|
+ </CardContent>
|
|
|
+ </Card>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|