Files
juwan-frontend/app/(main)/player/[id]/page.tsx
T

202 lines
8.5 KiB
TypeScript

import { CheckCircle, Clock, MapPin, MessageSquare, ShoppingBag, Star } from "lucide-react"
import Link from "next/link"
import { notFound } from "next/navigation"
import { FavoriteButton } from "@/components/favorite-button"
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card"
import { Separator } from "@/components/ui/separator"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
import { listPlayers, listReviewsByTargetUser, listServicesByPlayer } from "@/lib/api"
export default async function PlayerDetailPage({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params
const player = listPlayers().find((p) => p.id === id)
if (!player) {
notFound()
}
const playerReviews = listReviewsByTargetUser(player.id)
const playerServices =
player.services && player.services.length > 0
? player.services
: listServicesByPlayer(player.id)
return (
<div className="container mx-auto py-8 px-4 max-w-5xl">
<div className="flex flex-col md:flex-row gap-8 mb-8">
<div className="flex-shrink-0">
<Avatar className="w-32 h-32 border-4 border-background shadow-[var(--shadow-card)]">
<AvatarImage src={player.user.avatar} alt={player.user.nickname} />
<AvatarFallback>{player.user.nickname[0]}</AvatarFallback>
</Avatar>
</div>
<div className="flex-grow space-y-4">
<div className="flex flex-col md:flex-row md:items-center justify-between gap-4">
<div>
<h1 className="text-3xl font-bold flex items-center gap-3">
{player.user.nickname}
<Badge
variant={player.status === "available" ? "default" : "secondary"}
className="text-sm"
>
{player.status === "available" ? "可接单" : "忙碌中"}
</Badge>
</h1>
<div className="flex items-center gap-2 mt-2 text-muted-foreground">
<div className="flex items-center text-yellow-500">
<Star className="w-4 h-4 fill-current" />
<span className="ml-1 font-medium text-foreground">{player.rating}</span>
</div>
<Separator orientation="vertical" className="h-4" />
<span> {player.totalOrders}</span>
<Separator orientation="vertical" className="h-4" />
<span> {(player.completionRate * 100).toFixed(0)}%</span>
</div>
</div>
{player.shopId && (
<Link href={`/shop/${player.shopId}`}>
<Button variant="outline" className="gap-2">
<ShoppingBag className="w-4 h-4" />
: {player.shopName}
</Button>
</Link>
)}
<FavoriteButton initialFavorited={false} targetType="player" targetId={player.id} />
</div>
<div className="bg-muted/50 p-4 rounded-xl">
<p className="text-sm leading-relaxed">
{player.user.bio || "这个打手很懒,什么都没写~"}
</p>
</div>
<div className="flex flex-wrap gap-2">
{player.tags.map((tag) => (
<Badge key={tag} variant="secondary" className="px-3 py-1">
{tag}
</Badge>
))}
</div>
</div>
</div>
<Tabs defaultValue="services" className="space-y-6">
<TabsList variant="line">
<TabsTrigger value="services"> ({playerServices.length})</TabsTrigger>
<TabsTrigger value="reviews"> ({playerReviews.length})</TabsTrigger>
</TabsList>
<TabsContent value="services" className="mt-6">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{playerServices.map((service) => (
<Card key={service.id} className="flex flex-col h-full">
<CardHeader>
<div className="flex justify-between items-start">
<Badge variant="outline">{service.gameName}</Badge>
<div className="text-lg font-bold text-primary">
¥{service.price}{" "}
<span className="text-sm text-muted-foreground font-normal">
/ {service.unit}
</span>
</div>
</div>
<CardTitle className="mt-2 text-xl">{service.title}</CardTitle>
<CardDescription className="line-clamp-2 mt-1">
{service.description}
</CardDescription>
</CardHeader>
<CardContent className="flex-grow space-y-3 text-sm">
{service.rankRange && (
<div className="flex items-center gap-2 text-muted-foreground">
<MapPin className="w-4 h-4" />
<span>: {service.rankRange}</span>
</div>
)}
<div className="flex items-start gap-2 text-muted-foreground">
<Clock className="w-4 h-4 mt-0.5" />
<div className="flex flex-col">
{service.availability.map((time) => (
<span key={time}>{time}</span>
))}
</div>
</div>
</CardContent>
<CardFooter>
<Button className="w-full" asChild>
<Link href={`/order/new?serviceId=${service.id}`}></Link>
</Button>
</CardFooter>
</Card>
))}
{playerServices.length === 0 && (
<div className="col-span-full text-center py-12 text-muted-foreground">
<p></p>
</div>
)}
</div>
</TabsContent>
<TabsContent value="reviews" className="mt-6">
<div className="space-y-6">
{playerReviews.length > 0 ? (
playerReviews.map((review) => (
<Card key={review.id}>
<CardContent className="p-6">
<div className="flex items-start gap-4">
<Avatar>
<AvatarImage src={review.fromUserAvatar} alt={review.fromUserName} />
<AvatarFallback>{review.fromUserName[0]}</AvatarFallback>
</Avatar>
<div className="flex-grow space-y-2">
<div className="flex justify-between items-start">
<div>
<div className="font-medium">{review.fromUserName}</div>
<div className="text-xs text-muted-foreground">
{new Date(review.createdAt).toLocaleDateString()}
</div>
</div>
<div className="flex items-center text-yellow-500">
{[1, 2, 3, 4, 5].map((star) => (
<Star
key={star}
className={`w-4 h-4 ${star <= review.rating ? "fill-current" : "text-muted stroke-muted-foreground"}`}
/>
))}
</div>
</div>
<p className="text-sm text-foreground/90">{review.content}</p>
{review.sealed && (
<div className="flex items-center gap-1 text-xs text-green-600 bg-green-50 w-fit px-2 py-1 rounded-full">
<CheckCircle className="w-3 h-3" />
<span></span>
</div>
)}
</div>
</div>
</CardContent>
</Card>
))
) : (
<div className="text-center py-12 text-muted-foreground">
<MessageSquare className="w-12 h-12 mx-auto mb-4 opacity-20" />
<p></p>
</div>
)}
</div>
</TabsContent>
</Tabs>
</div>
)
}