/** * Component: User Profile Page * Documentation: documentation/frontend/components.md */ 'use client'; import { useMemo } from 'react'; import { Header } from '@/components/layout/Header'; import { RequestCard } from '@/components/requests/RequestCard'; import { useAuth } from '@/contexts/AuthContext'; import { useRequests } from '@/lib/hooks/useRequests'; import { cn } from '@/lib/utils/cn'; import { GoodreadsShelvesSection } from '@/components/profile/GoodreadsShelvesSection'; import { ApiTokensSection } from '@/components/profile/ApiTokensSection'; import { WatchedSeriesSection, WatchedAuthorsSection } from '@/components/profile/WatchedListsSection'; const statConfig = [ { key: 'total', label: 'Total', color: 'text-gray-900 dark:text-white' }, { key: 'active', label: 'Active', color: 'text-blue-500' }, { key: 'waiting', label: 'Waiting', color: 'text-amber-500' }, { key: 'completed', label: 'Complete', color: 'text-emerald-500' }, { key: 'failed', label: 'Failed', color: 'text-red-500' }, { key: 'cancelled', label: 'Cancelled', color: 'text-gray-400 dark:text-gray-500' }, ] as const; type StatKey = (typeof statConfig)[number]['key']; export default function ProfilePage() { const { user } = useAuth(); const { requests, isLoading } = useRequests(undefined, 50, true); const stats = useMemo(() => { if (!requests.length) { return { total: 0, completed: 0, active: 0, waiting: 0, failed: 0, cancelled: 0 }; } return { total: requests.length, completed: requests.filter((r: any) => ['available', 'downloaded'].includes(r.status)).length, active: requests.filter((r: any) => ['pending', 'searching', 'downloading', 'processing'].includes(r.status)).length, waiting: requests.filter((r: any) => ['awaiting_search', 'awaiting_import'].includes(r.status)).length, failed: requests.filter((r: any) => r.status === 'failed').length, cancelled: requests.filter((r: any) => r.status === 'cancelled').length, }; }, [requests]); const activeDownloads = useMemo(() => { return requests.filter((r: any) => ['downloading', 'processing'].includes(r.status)); }, [requests]); const recentRequests = useMemo(() => { return [...requests] .sort((a: any, b: any) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()) .slice(0, 5); }, [requests]); if (!user) { return (

Sign in required

Please log in to view your profile

); } return (
{/* Profile Card — gradient banner + avatar + info + stats */}
{/* Gradient Banner */}
{/* Profile Content — overlapping the banner */}
{/* Avatar */} {user.avatarUrl ? ( {user.username} ) : (
{user.username.charAt(0).toUpperCase()}
)} {/* Name + Email + Badge */}

{user.username}

{user.email && (

{user.email}

)}
{user.role === 'admin' ? 'Administrator' : 'User'}
{/* Stats Strip */}
{statConfig.map((stat) => (
{isLoading ? '\u2013' : stats[stat.key as StatKey]}
{stat.label}
))}
{/* Goodreads Shelves */} {/* Watched Series */} {/* Watched Authors */} {/* Active Downloads */} {activeDownloads.length > 0 && (

Active Downloads

View All
{activeDownloads.map((request: any) => ( ))}
)} {/* Recent Requests */}

Recent Requests

{requests.length > 0 && ( View All )}
{isLoading ? (
{[1, 2, 3].map((i) => (
))}
) : recentRequests.length > 0 ? (
{recentRequests.map((request: any) => ( ))}
) : (

No requests yet

Search for audiobooks to get started

Search Audiobooks
)}
{/* API Tokens */}
); }