fediversion/frontend/app/profile/[slug]/page.tsx
fullsizemalt a87c0cc8a3
Some checks failed
Deploy Fediversion / deploy (push) Failing after 1s
fix: profile date, avatar system, UserRead schema
2025-12-29 21:26:48 -08:00

160 lines
7.1 KiB
TypeScript

"use client"
import { useEffect, useState } from "react"
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Trophy, Calendar, User, ArrowLeft } from "lucide-react"
import Link from "next/link"
import { BadgeList } from "@/components/profile/badge-list"
import { getApiUrl } from "@/lib/api-config"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
import { UserAttendanceList } from "@/components/profile/user-attendance-list"
import { UserReviewsList } from "@/components/profile/user-reviews-list"
import { UserGroupsList } from "@/components/profile/user-groups-list"
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
import { notFound } from "next/navigation"
interface UserProfile {
id: number
email: string
username: string | null
bio: string | null
joined_at: string | null
}
interface UserBadge {
id: number
badge: {
id: number
name: string
description: string
icon: string
slug: string
}
awarded_at: string
}
export default function PublicProfilePage({ params }: { params: Promise<{ slug: string }> }) {
const [id, setId] = useState<string | null>(null)
const [user, setUser] = useState<UserProfile | null>(null)
const [badges, setBadges] = useState<UserBadge[]>([])
const [loading, setLoading] = useState(true)
const [error, setError] = useState(false)
const [stats, setStats] = useState({ attendance_count: 0, review_count: 0, group_count: 0 })
useEffect(() => {
params.then(p => setId(p.slug))
}, [params])
useEffect(() => {
if (!id) return
const fetchData = async () => {
try {
// Public fetch - no auth header needed strictly, but maybe good practice if protected
const token = localStorage.getItem("token")
const headers: Record<string, string> = token ? { Authorization: `Bearer ${token}` } : {}
const userRes = await fetch(`${getApiUrl()}/users/${id}`, { headers })
if (!userRes.ok) throw new Error("User not found")
const userData = await userRes.json()
setUser(userData)
// Fetch Stats
const statsRes = await fetch(`${getApiUrl()}/users/${id}/stats`, { headers })
if (statsRes.ok) setStats(await statsRes.json())
// Fetch Badges
// Check if badges endpoint exists for specific user, otherwise /badges/me is for me.
// Assuming we default to empty badges for public profile if no specific endpoint
// Actually, let's skip badges for now or try specific endpoint if it existed.
// Assuming no public badges endpoint yet.
setBadges([])
} catch (e) {
console.error(e)
setError(true)
} finally {
setLoading(false)
}
}
fetchData()
}, [id])
if (loading) return <div className="container py-20 text-center">Loading profile...</div>
if (error || !user) return <div className="container py-20 text-center">User not found</div>
const displayName = user.username || user.email.split('@')[0]
return (
<div className="container py-10 max-w-5xl space-y-8">
<Link href="/leaderboards">
<Button variant="ghost" size="sm" className="mb-4">
<ArrowLeft className="mr-2 h-4 w-4" />
Back to Leaderboards
</Button>
</Link>
<Card className="border-0 shadow-none bg-transparent">
<div className="flex flex-col md:flex-row gap-8 items-start">
<div
className="h-32 w-32 rounded-full border-4 border-background shadow-lg flex items-center justify-center text-white text-4xl font-bold"
style={{ backgroundColor: (user as any).avatar_bg_color || '#3B82F6' }}
>
{(user as any).avatar_text || displayName.substring(0, 2).toUpperCase()}
</div>
<div className="space-y-4 flex-1">
<div>
<h1 className="text-4xl font-bold tracking-tight">{displayName}</h1>
<p className="text-muted-foreground flex items-center gap-2 mt-2">
<Calendar className="h-4 w-4" />
Member since {user.joined_at ? new Date(user.joined_at).toLocaleDateString() : 'Unknown'}
</p>
</div>
{user.bio && (
<p className="max-w-xl text-lg text-muted-foreground/80">
{user.bio}
</p>
)}
<div className="flex gap-4">
<div className="flex flex-col">
<span className="text-2xl font-bold">{stats.attendance_count}</span>
<span className="text-sm text-muted-foreground uppercase tracking-wider">Shows</span>
</div>
<div className="w-px bg-border h-10" />
<div className="flex flex-col">
<span className="text-2xl font-bold">{stats.review_count}</span>
<span className="text-sm text-muted-foreground uppercase tracking-wider">Reviews</span>
</div>
<div className="w-px bg-border h-10" />
<div className="flex flex-col">
<span className="text-2xl font-bold">{stats.group_count}</span>
<span className="text-sm text-muted-foreground uppercase tracking-wider">Groups</span>
</div>
</div>
</div>
</div>
</Card>
<Tabs defaultValue="attendance" className="w-full">
<TabsList className="grid w-full grid-cols-3 mb-8 h-12">
<TabsTrigger value="attendance" className="text-base">Attendance</TabsTrigger>
<TabsTrigger value="reviews" className="text-base">Reviews</TabsTrigger>
<TabsTrigger value="groups" className="text-base">Communities</TabsTrigger>
</TabsList>
<TabsContent value="attendance" className="space-y-6">
<UserAttendanceList userId={user.id} />
</TabsContent>
<TabsContent value="reviews" className="mt-6">
<UserReviewsList userId={user.id} />
</TabsContent>
<TabsContent value="groups" className="mt-6">
<UserGroupsList userId={user.id} />
</TabsContent>
</Tabs>
</div>
)
}