fenghuo/apps/web/components/user-profile.tsx

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>
);
}