import React from 'react';
import { Button } from '@/components/ui/button';
import { createBrowserRouter, Outlet, useParams, Link } from 'react-router-dom';
import Header from './components/Header';
import BottomNav from './components/BottomNav';
import { useEventData } from './hooks/useEventData';
import { AlertTriangle, Loader2 } from 'lucide-react';
import { EventStatsProvider } from './context/EventStatsContext';
import { GuestIdentityProvider } from './context/GuestIdentityContext';
import { EventBrandingProvider } from './context/EventBrandingContext';
import { LocaleProvider } from './i18n/LocaleContext';
import { DEFAULT_LOCALE, isLocaleCode } from './i18n/messages';
import { useTranslation, type TranslateFn } from './i18n/useTranslation';
import type { EventBranding } from './types/event-branding';
import type { EventBrandingPayload, FetchEventErrorCode } from './services/eventApi';
const LandingPage = React.lazy(() => import('./pages/LandingPage'));
const ProfileSetupPage = React.lazy(() => import('./pages/ProfileSetupPage'));
const HomePage = React.lazy(() => import('./pages/HomePage'));
const TaskPickerPage = React.lazy(() => import('./pages/TaskPickerPage'));
const TaskDetailPage = React.lazy(() => import('./pages/TaskDetailPage'));
const UploadPage = React.lazy(() => import('./pages/UploadPage'));
const UploadQueuePage = React.lazy(() => import('./pages/UploadQueuePage'));
const GalleryPage = React.lazy(() => import('./pages/GalleryPage'));
const PhotoLightbox = React.lazy(() => import('./pages/PhotoLightbox'));
const AchievementsPage = React.lazy(() => import('./pages/AchievementsPage'));
const SlideshowPage = React.lazy(() => import('./pages/SlideshowPage'));
const SettingsPage = React.lazy(() => import('./pages/SettingsPage'));
const LegalPage = React.lazy(() => import('./pages/LegalPage'));
const PublicGalleryPage = React.lazy(() => import('./pages/PublicGalleryPage'));
const NotFoundPage = React.lazy(() => import('./pages/NotFoundPage'));
function HomeLayout() {
const { token } = useParams();
if (!token) {
return (
);
}
return (
);
}
export const router = createBrowserRouter([
{ path: '/event', element: },
{
path: '/setup/:token',
element: ,
children: [
{ index: true, element: },
],
},
{ path: '/g/:token', element: },
{
path: '/e/:token',
element: ,
children: [
{ index: true, element: },
{ path: 'tasks', element: },
{ path: 'tasks/:taskId', element: },
{ path: 'upload', element: },
{ path: 'queue', element: },
{ path: 'gallery', element: },
{ path: 'photo/:photoId', element: },
{ path: 'achievements', element: },
{ path: 'slideshow', element: },
],
},
{ path: '/settings', element: },
{ path: '/legal/:page', element: },
{ path: '*', element: },
]);
function EventBoundary({ token }: { token: string }) {
const { event, status, error, errorCode } = useEventData();
if (status === 'loading') {
return ;
}
if (status === 'error' || !event) {
return ;
}
const eventLocale = isLocaleCode(event.default_locale) ? event.default_locale : DEFAULT_LOCALE;
const localeStorageKey = `guestLocale_event_${event.id ?? token}`;
const branding = mapEventBranding(event.branding);
return (
);
}
function SetupLayout() {
const { token } = useParams<{ token: string }>();
const { event } = useEventData();
if (!token) return null;
const eventLocale = event && isLocaleCode(event.default_locale) ? event.default_locale : DEFAULT_LOCALE;
const localeStorageKey = event ? `guestLocale_event_${event.id}` : `guestLocale_event_${token}`;
const branding = event ? mapEventBranding(event.branding) : null;
return (
);
}
function EventLoadingView() {
const { t } = useTranslation();
return (
{t('eventAccess.loading.title')}
{t('eventAccess.loading.subtitle')}
);
}
function mapEventBranding(raw?: EventBrandingPayload | null): EventBranding | null {
if (!raw) {
return null;
}
return {
primaryColor: raw.primary_color ?? '',
secondaryColor: raw.secondary_color ?? '',
backgroundColor: raw.background_color ?? '',
fontFamily: raw.font_family ?? null,
logoUrl: raw.logo_url ?? null,
};
}
interface EventErrorViewProps {
code: FetchEventErrorCode | null;
message: string | null;
}
function EventErrorView({ code, message }: EventErrorViewProps) {
const { t } = useTranslation();
const content = getErrorContent(t, code, message);
return (
{content.title}
{content.description}
{content.hint && (
{content.hint}
)}
{content.ctaHref && content.ctaLabel && (
)}
);
}
function getErrorContent(
t: TranslateFn,
code: FetchEventErrorCode | null,
message: string | null,
) {
const build = (key: string, options?: { ctaHref?: string }) => {
const ctaLabel = t(`eventAccess.error.${key}.ctaLabel`, '');
const hint = t(`eventAccess.error.${key}.hint`, '');
return {
title: t(`eventAccess.error.${key}.title`),
description: message ?? t(`eventAccess.error.${key}.description`),
ctaLabel: ctaLabel.trim().length > 0 ? ctaLabel : undefined,
ctaHref: options?.ctaHref,
hint: hint.trim().length > 0 ? hint : null,
};
};
switch (code) {
case 'invalid_token':
return build('invalid_token', { ctaHref: '/event' });
case 'token_revoked':
return build('token_revoked', { ctaHref: '/event' });
case 'token_expired':
return build('token_expired', { ctaHref: '/event' });
case 'token_rate_limited':
return build('token_rate_limited');
case 'access_rate_limited':
return build('access_rate_limited');
case 'event_not_public':
return build('event_not_public');
case 'gallery_expired':
return build('gallery_expired', { ctaHref: '/event' });
case 'network_error':
return build('network_error');
case 'server_error':
return build('server_error');
default:
return build('default', { ctaHref: '/event' });
}
}
function SimpleLayout({ title, children }: { title: string; children: React.ReactNode }) {
return (
);
}