Files
Spanglish/frontend/src/app/(public)/dashboard/page.tsx
T
MichilisandCursor 75e317d73e Replace spinners with skeleton loading states for faster perceived UX.
Add reusable Skeleton components and route-level loading files across public pages and admin lists so layouts stay stable while data loads.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-12 23:14:11 +00:00

133 lines
4.4 KiB
TypeScript

'use client';
import { useState, useEffect } from 'react';
import { useRouter } from 'next/navigation';
import { useLanguage } from '@/context/LanguageContext';
import { useAuth } from '@/context/AuthContext';
import {
dashboardApi,
NextEventInfo,
UserTicket,
UserPayment,
} from '@/lib/api';
import toast from 'react-hot-toast';
import { CardListSkeleton } from '@/components/ui/Skeleton';
import OverviewTab from './components/OverviewTab';
import TicketsTab from './components/TicketsTab';
import PaymentsTab from './components/PaymentsTab';
import AccountTab from './components/AccountTab';
type Tab = 'overview' | 'tickets' | 'payments' | 'account';
export default function DashboardPage() {
const router = useRouter();
const { locale } = useLanguage();
const { user, isLoading: authLoading, token } = useAuth();
const [activeTab, setActiveTab] = useState<Tab>('overview');
const [nextEvent, setNextEvent] = useState<NextEventInfo | null>(null);
const [tickets, setTickets] = useState<UserTicket[]>([]);
const [payments, setPayments] = useState<UserPayment[]>([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
if (!authLoading && !user) {
router.push('/login');
return;
}
if (user && token) {
loadDashboardData();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [user, authLoading, token]);
const loadDashboardData = async () => {
setLoading(true);
try {
const [nextEventRes, ticketsRes, paymentsRes] = await Promise.all([
dashboardApi.getNextEvent(),
dashboardApi.getTickets(),
dashboardApi.getPayments(),
]);
setNextEvent(nextEventRes.nextEvent);
setTickets(ticketsRes.tickets);
setPayments(paymentsRes.payments);
} catch (error) {
console.error('Failed to load dashboard:', error);
toast.error(locale === 'es' ? 'Error al cargar el panel' : 'Failed to load dashboard data');
} finally {
setLoading(false);
}
};
const tabs: { id: Tab; label: { en: string; es: string } }[] = [
{ id: 'overview', label: { en: 'Overview', es: 'Resumen' } },
{ id: 'tickets', label: { en: 'Tickets', es: 'Entradas' } },
{ id: 'payments', label: { en: 'Payments', es: 'Pagos' } },
{ id: 'account', label: { en: 'Account', es: 'Cuenta' } },
];
if (authLoading || !user) {
return (
<div className="section-padding flex min-h-[70vh] items-center justify-center">
<div className="h-12 w-12 animate-spin rounded-full border-b-2 border-primary-yellow" />
</div>
);
}
return (
<div className="section-padding min-h-[70vh]">
<div className="container-page">
{/* Welcome header */}
<h1 className="mb-6 text-3xl font-bold">
{locale === 'es' ? `¡Hola, ${user.name}!` : `Welcome, ${user.name}!`}
</h1>
{/* Tab navigation */}
<div className="mb-6 border-b border-secondary-light-gray">
<nav className="-mb-px flex gap-6 overflow-x-auto">
{tabs.map((tab) => (
<button
key={tab.id}
onClick={() => setActiveTab(tab.id)}
className={`whitespace-nowrap border-b-2 px-1 pb-3 text-sm font-semibold transition-colors ${
activeTab === tab.id
? 'border-primary-yellow text-primary-dark'
: 'border-transparent text-gray-500 hover:text-gray-700'
}`}
>
{locale === 'es' ? tab.label.es : tab.label.en}
</button>
))}
</nav>
</div>
{/* Tab content */}
{loading ? (
<CardListSkeleton count={3} />
) : (
<>
{activeTab === 'overview' && (
<OverviewTab
nextEvent={nextEvent}
tickets={tickets}
locale={locale}
userName={user.name}
onChange={loadDashboardData}
/>
)}
{activeTab === 'tickets' && (
<TicketsTab tickets={tickets} language={locale} onChange={loadDashboardData} />
)}
{activeTab === 'payments' && (
<PaymentsTab payments={payments} language={locale} onChange={loadDashboardData} />
)}
{activeTab === 'account' && <AccountTab onUpdate={loadDashboardData} />}
</>
)}
</div>
</div>
);
}