feat: consumer profile page and header profile link for all roles
This commit is contained in:
@@ -0,0 +1,159 @@
|
||||
import { MessageSquare, Star, ThumbsUp } from "lucide-react"
|
||||
import Link from "next/link"
|
||||
import { notFound } from "next/navigation"
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
||||
import { mockFavorites, mockPlayers, mockPosts, mockShops, mockUsers } from "@/lib/mock-data"
|
||||
|
||||
export default async function UserProfilePage({ params }: { params: Promise<{ id: string }> }) {
|
||||
const { id } = await params
|
||||
const user = mockUsers.find((u) => u.id === id)
|
||||
|
||||
if (!user) {
|
||||
notFound()
|
||||
}
|
||||
|
||||
const userPosts = mockPosts.filter((p) => p.author.id === user.id)
|
||||
const userFavorites = mockFavorites.filter((f) => f.userId === user.id)
|
||||
const favoritePlayers = userFavorites
|
||||
.filter((f) => f.targetType === "player")
|
||||
.map((f) => mockPlayers.find((p) => p.id === f.targetId))
|
||||
.filter((p): p is NonNullable<typeof p> => p != null)
|
||||
const favoriteShops = userFavorites
|
||||
.filter((f) => f.targetType === "shop")
|
||||
.map((f) => mockShops.find((s) => s.id === f.targetId))
|
||||
.filter((s): s is NonNullable<typeof s> => s != null)
|
||||
|
||||
return (
|
||||
<div className="container mx-auto py-8 px-4 max-w-4xl">
|
||||
<div className="flex items-center gap-6 mb-8">
|
||||
<Avatar className="w-24 h-24 border-4 border-background shadow-lg">
|
||||
<AvatarImage src={user.avatar} alt={user.nickname} />
|
||||
<AvatarFallback>{user.nickname[0]}</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="space-y-2">
|
||||
<h1 className="text-2xl font-bold">{user.nickname}</h1>
|
||||
<p className="text-sm text-muted-foreground">{user.bio || "这个人很懒,什么都没写~"}</p>
|
||||
<div className="flex gap-4 text-sm text-muted-foreground">
|
||||
<span>{userPosts.length} 帖子</span>
|
||||
<span>{userFavorites.length} 收藏</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Tabs defaultValue="posts">
|
||||
<TabsList>
|
||||
<TabsTrigger value="posts">帖子</TabsTrigger>
|
||||
<TabsTrigger value="favorites">收藏</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="posts" className="mt-4 space-y-4">
|
||||
{userPosts.length === 0 ? (
|
||||
<div className="text-center py-12 text-muted-foreground">暂无帖子</div>
|
||||
) : (
|
||||
userPosts.map((post) => (
|
||||
<Link key={post.id} href={`/post/${post.id}`}>
|
||||
<Card className="hover:shadow-md transition-shadow">
|
||||
<CardHeader className="pb-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle className="text-base">{post.title}</CardTitle>
|
||||
{post.pinned && <Badge variant="secondary">置顶</Badge>}
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-sm text-muted-foreground line-clamp-2 mb-3">
|
||||
{post.content}
|
||||
</p>
|
||||
<div className="flex items-center gap-4 text-xs text-muted-foreground">
|
||||
<span className="flex items-center gap-1">
|
||||
<ThumbsUp className="h-3 w-3" />
|
||||
{post.likeCount}
|
||||
</span>
|
||||
<span className="flex items-center gap-1">
|
||||
<MessageSquare className="h-3 w-3" />
|
||||
{post.commentCount}
|
||||
</span>
|
||||
<span>{new Date(post.createdAt).toLocaleDateString("zh-CN")}</span>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Link>
|
||||
))
|
||||
)}
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="favorites" className="mt-4 space-y-6">
|
||||
{favoritePlayers.length > 0 && (
|
||||
<div>
|
||||
<h3 className="text-sm font-medium text-muted-foreground mb-3">收藏的打手</h3>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
||||
{favoritePlayers.map((player) => (
|
||||
<Link key={player.id} href={`/player/${player.id}`}>
|
||||
<Card className="hover:shadow-md transition-shadow">
|
||||
<CardContent className="flex items-center gap-3 p-4">
|
||||
<Avatar>
|
||||
<AvatarImage src={player.user.avatar} />
|
||||
<AvatarFallback>{player.user.nickname[0]}</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="font-medium truncate">{player.user.nickname}</p>
|
||||
<div className="flex items-center gap-2 text-xs text-muted-foreground">
|
||||
<Star className="h-3 w-3 fill-yellow-500 text-yellow-500" />
|
||||
<span>{player.rating}</span>
|
||||
<span>·</span>
|
||||
<span>{player.totalOrders}单</span>
|
||||
</div>
|
||||
</div>
|
||||
<Badge
|
||||
variant={player.status === "available" ? "default" : "secondary"}
|
||||
className="text-xs shrink-0"
|
||||
>
|
||||
{player.status === "available" ? "可接单" : "忙碌"}
|
||||
</Badge>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{favoriteShops.length > 0 && (
|
||||
<div>
|
||||
<h3 className="text-sm font-medium text-muted-foreground mb-3">收藏的店铺</h3>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
||||
{favoriteShops.map((shop) => (
|
||||
<Link key={shop.id} href={`/shop/${shop.id}`}>
|
||||
<Card className="hover:shadow-md transition-shadow">
|
||||
<CardContent className="flex items-center gap-3 p-4">
|
||||
<Avatar>
|
||||
<AvatarImage src={shop.owner.avatar} />
|
||||
<AvatarFallback>{shop.name[0]}</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="font-medium truncate">{shop.name}</p>
|
||||
<div className="flex items-center gap-2 text-xs text-muted-foreground">
|
||||
<Star className="h-3 w-3 fill-yellow-500 text-yellow-500" />
|
||||
<span>{shop.rating}</span>
|
||||
<span>·</span>
|
||||
<span>{shop.totalOrders}单</span>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{favoritePlayers.length === 0 && favoriteShops.length === 0 && (
|
||||
<div className="text-center py-12 text-muted-foreground">暂无收藏</div>
|
||||
)}
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
+12
-8
@@ -142,14 +142,18 @@ export function Header() {
|
||||
<DropdownMenuLabel>{currentUser.nickname}</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuGroup>
|
||||
{currentRole === "player" && (
|
||||
<DropdownMenuItem asChild>
|
||||
<Link href={`/player/${currentUser.id}`}>
|
||||
<User className="mr-2 h-4 w-4" />
|
||||
个人主页
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
<DropdownMenuItem asChild>
|
||||
<Link
|
||||
href={
|
||||
currentRole === "player"
|
||||
? `/player/${currentUser.id}`
|
||||
: `/user/${currentUser.id}`
|
||||
}
|
||||
>
|
||||
<User className="mr-2 h-4 w-4" />
|
||||
个人主页
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem asChild>
|
||||
<Link href="/orders">
|
||||
<ShoppingBag className="mr-2 h-4 w-4" />
|
||||
|
||||
Reference in New Issue
Block a user