252 lines
8.2 KiB
TypeScript
252 lines
8.2 KiB
TypeScript
'use client';
|
|
|
|
import { useAuth } from '@/providers/auth-provider';
|
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@repo/ui/components/card';
|
|
import { Button } from '@repo/ui/components/button';
|
|
import { Badge } from '@repo/ui/components/badge';
|
|
import { Separator } from '@repo/ui/components/separator';
|
|
import { Avatar, AvatarFallback, AvatarImage } from '@repo/ui/components/avatar';
|
|
import { LogOut, User, Mail, Phone, MapPin, Calendar, Globe } from 'lucide-react';
|
|
|
|
export function UserProfile() {
|
|
const { user, isAuthenticated, logout, isLoading } = useAuth();
|
|
|
|
if (isLoading) {
|
|
return (
|
|
<Card className="w-full max-w-2xl">
|
|
<CardContent className="p-6">
|
|
<div className="animate-pulse space-y-4">
|
|
<div className="h-4 bg-gray-200 rounded w-1/3"></div>
|
|
<div className="h-4 bg-gray-200 rounded w-1/2"></div>
|
|
<div className="h-4 bg-gray-200 rounded w-2/3"></div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|
|
|
|
if (!isAuthenticated || !user) {
|
|
return (
|
|
<Card className="w-full max-w-2xl">
|
|
<CardHeader>
|
|
<CardTitle>未登录</CardTitle>
|
|
<CardDescription>请先登录以查看用户信息</CardDescription>
|
|
</CardHeader>
|
|
</Card>
|
|
);
|
|
}
|
|
|
|
const profile = user.profile;
|
|
const formatDate = (timestamp?: number) => {
|
|
if (!timestamp) return '未知';
|
|
return new Date(timestamp * 1000).toLocaleString('zh-CN');
|
|
};
|
|
|
|
return (
|
|
<Card className="w-full max-w-2xl">
|
|
<CardHeader className="pb-4">
|
|
<div className="flex items-center justify-between">
|
|
<div className="flex items-center space-x-4">
|
|
<Avatar className="h-12 w-12">
|
|
<AvatarImage src={profile.picture} alt={profile.name} />
|
|
<AvatarFallback>{profile.name?.charAt(0) || profile.preferred_username?.charAt(0) || 'U'}</AvatarFallback>
|
|
</Avatar>
|
|
<div>
|
|
<CardTitle className="text-xl">{profile.name || profile.preferred_username || '未知用户'}</CardTitle>
|
|
<CardDescription>用户ID: {profile.sub}</CardDescription>
|
|
</div>
|
|
</div>
|
|
<Button variant="outline" onClick={logout} className="gap-2">
|
|
<LogOut className="h-4 w-4" />
|
|
登出
|
|
</Button>
|
|
</div>
|
|
</CardHeader>
|
|
|
|
<CardContent className="space-y-6">
|
|
{/* 基本信息 */}
|
|
<div>
|
|
<h3 className="text-lg font-semibold mb-3 flex items-center gap-2">
|
|
<User className="h-5 w-5" />
|
|
基本信息
|
|
</h3>
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
{profile.given_name && (
|
|
<div>
|
|
<label className="text-sm font-medium text-gray-600 dark:text-gray-400">名</label>
|
|
<p className="text-sm">{profile.given_name}</p>
|
|
</div>
|
|
)}
|
|
{profile.family_name && (
|
|
<div>
|
|
<label className="text-sm font-medium text-gray-600 dark:text-gray-400">姓</label>
|
|
<p className="text-sm">{profile.family_name}</p>
|
|
</div>
|
|
)}
|
|
{profile.nickname && (
|
|
<div>
|
|
<label className="text-sm font-medium text-gray-600 dark:text-gray-400">昵称</label>
|
|
<p className="text-sm">{profile.nickname}</p>
|
|
</div>
|
|
)}
|
|
{profile.gender && (
|
|
<div>
|
|
<label className="text-sm font-medium text-gray-600 dark:text-gray-400">性别</label>
|
|
<p className="text-sm">{profile.gender}</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
<Separator />
|
|
|
|
{/* 联系信息 */}
|
|
<div>
|
|
<h3 className="text-lg font-semibold mb-3 flex items-center gap-2">
|
|
<Mail className="h-5 w-5" />
|
|
联系信息
|
|
</h3>
|
|
<div className="space-y-3">
|
|
{profile.email && (
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<label className="text-sm font-medium text-gray-600 dark:text-gray-400">邮箱</label>
|
|
<p className="text-sm">{profile.email}</p>
|
|
</div>
|
|
<Badge variant={profile.email_verified ? 'default' : 'secondary'}>
|
|
{profile.email_verified ? '已验证' : '未验证'}
|
|
</Badge>
|
|
</div>
|
|
)}
|
|
{profile.phone_number && (
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<label className="text-sm font-medium text-gray-600 dark:text-gray-400">电话</label>
|
|
<p className="text-sm">{profile.phone_number}</p>
|
|
</div>
|
|
<Badge variant={profile.phone_number_verified ? 'default' : 'secondary'}>
|
|
{profile.phone_number_verified ? '已验证' : '未验证'}
|
|
</Badge>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{profile.address && (
|
|
<>
|
|
<Separator />
|
|
<div>
|
|
<h3 className="text-lg font-semibold mb-3 flex items-center gap-2">
|
|
<MapPin className="h-5 w-5" />
|
|
地址信息
|
|
</h3>
|
|
<div className="space-y-2">
|
|
{profile.address.formatted && (
|
|
<div>
|
|
<label className="text-sm font-medium text-gray-600 dark:text-gray-400">完整地址</label>
|
|
<p className="text-sm">{profile.address.formatted}</p>
|
|
</div>
|
|
)}
|
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
|
{profile.address.country && (
|
|
<div>
|
|
<label className="text-sm font-medium text-gray-600 dark:text-gray-400">国家</label>
|
|
<p className="text-sm">{profile.address.country}</p>
|
|
</div>
|
|
)}
|
|
{profile.address.region && (
|
|
<div>
|
|
<label className="text-sm font-medium text-gray-600 dark:text-gray-400">省/州</label>
|
|
<p className="text-sm">{profile.address.region}</p>
|
|
</div>
|
|
)}
|
|
{profile.address.locality && (
|
|
<div>
|
|
<label className="text-sm font-medium text-gray-600 dark:text-gray-400">城市</label>
|
|
<p className="text-sm">{profile.address.locality}</p>
|
|
</div>
|
|
)}
|
|
{profile.address.postal_code && (
|
|
<div>
|
|
<label className="text-sm font-medium text-gray-600 dark:text-gray-400">邮编</label>
|
|
<p className="text-sm">{profile.address.postal_code}</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</>
|
|
)}
|
|
|
|
<Separator />
|
|
|
|
{/* 其他信息 */}
|
|
<div>
|
|
<h3 className="text-lg font-semibold mb-3 flex items-center gap-2">
|
|
<Globe className="h-5 w-5" />
|
|
其他信息
|
|
</h3>
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
{profile.birthdate && (
|
|
<div>
|
|
<label className="text-sm font-medium text-gray-600 dark:text-gray-400">生日</label>
|
|
<p className="text-sm">{profile.birthdate}</p>
|
|
</div>
|
|
)}
|
|
{profile.zoneinfo && (
|
|
<div>
|
|
<label className="text-sm font-medium text-gray-600 dark:text-gray-400">时区</label>
|
|
<p className="text-sm">{profile.zoneinfo}</p>
|
|
</div>
|
|
)}
|
|
{profile.locale && (
|
|
<div>
|
|
<label className="text-sm font-medium text-gray-600 dark:text-gray-400">语言</label>
|
|
<p className="text-sm">{profile.locale}</p>
|
|
</div>
|
|
)}
|
|
{profile.updated_at && (
|
|
<div>
|
|
<label className="text-sm font-medium text-gray-600 dark:text-gray-400">更新时间</label>
|
|
<p className="text-sm">{formatDate(profile.updated_at)}</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
<Separator />
|
|
|
|
{/* Token 信息 */}
|
|
<div>
|
|
<h3 className="text-lg font-semibold mb-3 flex items-center gap-2">
|
|
<Calendar className="h-5 w-5" />
|
|
Token 信息
|
|
</h3>
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
{user.expires_at && (
|
|
<div>
|
|
<label className="text-sm font-medium text-gray-600 dark:text-gray-400">访问令牌过期时间</label>
|
|
<p className="text-sm">{new Date(user.expires_at * 1000).toLocaleString('zh-CN')}</p>
|
|
</div>
|
|
)}
|
|
<div>
|
|
<label className="text-sm font-medium text-gray-600 dark:text-gray-400">Token类型</label>
|
|
<p className="text-sm">{user.token_type}</p>
|
|
</div>
|
|
<div className="md:col-span-2">
|
|
<label className="text-sm font-medium text-gray-600 dark:text-gray-400">作用域</label>
|
|
<div className="flex flex-wrap gap-1 mt-1">
|
|
{user.scope?.split(' ').map((scope) => (
|
|
<Badge key={scope} variant="outline" className="text-xs">
|
|
{scope}
|
|
</Badge>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|