import React from 'react'; import { useNavigate } from 'react-router-dom'; import { XStack, YStack } from '@tamagui/stacks'; import { SizableText as Text } from '@tamagui/text'; import { Button } from '@tamagui/button'; import { Camera, Sparkles, Image as ImageIcon, Trophy, Star } from 'lucide-react'; import AppShell from '../components/AppShell'; import PhotoFrameTile from '../components/PhotoFrameTile'; import { useEventData } from '../context/EventDataContext'; import { buildEventPath } from '../lib/routes'; import { useStaggeredReveal } from '../lib/useStaggeredReveal'; import { usePollStats } from '../hooks/usePollStats'; import { fetchGallery } from '../services/photosApi'; import { useUploadQueue } from '../services/uploadApi'; import { useTranslation } from '@/guest/i18n/useTranslation'; import { useAppearance } from '@/hooks/use-appearance'; type ActionRingProps = { label: string; icon: React.ReactNode; onPress: () => void; }; type GalleryPreview = { id: number; imageUrl: string; }; function ActionRing({ label, icon, onPress, isDark, }: ActionRingProps & { isDark: boolean }) { return ( ); } function QuickStats({ reveal, stats, queueCount, isDark, }: { reveal: number; stats: { onlineGuests: number; tasksSolved: number }; queueCount: number; isDark: boolean; }) { const { t } = useTranslation(); const cardBorder = isDark ? 'rgba(255, 255, 255, 0.12)' : 'rgba(15, 23, 42, 0.12)'; const cardShadow = isDark ? '0 16px 30px rgba(2, 6, 23, 0.35)' : '0 14px 24px rgba(15, 23, 42, 0.12)'; return ( = 3 ? 1 : 0} y={reveal >= 3 ? 0 : 12} > {stats.onlineGuests} {t('home.stats.online', 'Guests online')} {queueCount} {t('homeV2.stats.uploadsQueued', 'Uploads queued')} ); } function normalizeImageUrl(src?: string | null) { if (!src) { return ''; } if (/^https?:/i.test(src)) { return src; } let cleanPath = src.replace(/^\/+/g, '').replace(/\/+/g, '/'); if (!cleanPath.startsWith('storage/')) { cleanPath = `storage/${cleanPath}`; } return `/${cleanPath}`.replace(/\/+/g, '/'); } export default function HomeScreen() { const { tasksEnabled, token } = useEventData(); const navigate = useNavigate(); const revealStage = useStaggeredReveal({ steps: 4, intervalMs: 140, delayMs: 120 }); const { stats } = usePollStats(token ?? null); const { items } = useUploadQueue(); const [preview, setPreview] = React.useState([]); const [previewLoading, setPreviewLoading] = React.useState(false); const { t } = useTranslation(); const { resolved } = useAppearance(); const isDark = resolved === 'dark'; const cardBorder = isDark ? 'rgba(255, 255, 255, 0.12)' : 'rgba(15, 23, 42, 0.12)'; const mutedButton = isDark ? 'rgba(255, 255, 255, 0.08)' : 'rgba(15, 23, 42, 0.06)'; const mutedButtonBorder = isDark ? 'rgba(255, 255, 255, 0.2)' : 'rgba(15, 23, 42, 0.12)'; const cardShadow = isDark ? '0 18px 40px rgba(2, 6, 23, 0.4)' : '0 16px 32px rgba(15, 23, 42, 0.12)'; const goTo = (path: string) => () => navigate(buildEventPath(token, path)); const rings = [ tasksEnabled ? { label: t('home.actions.items.tasks.label', 'Draw a task card'), icon: , path: '/tasks', } : { label: t('home.actions.items.upload.label', 'Upload photo'), icon: , path: '/upload', }, { label: t('homeV2.rings.newUploads', 'New uploads'), icon: , path: '/gallery', }, { label: t('homeV2.rings.topMoments', 'Top moments'), icon: , path: '/gallery', }, { label: t('navigation.achievements', 'Achievements'), icon: , path: '/achievements', }, ]; React.useEffect(() => { if (!token) { setPreview([]); return; } let active = true; setPreviewLoading(true); fetchGallery(token, { limit: 3 }) .then((response) => { if (!active) return; const photos = Array.isArray(response.data) ? response.data : []; const mapped = photos .map((photo) => { const record = photo as Record; const id = Number(record.id ?? 0); const imageUrl = normalizeImageUrl( (record.thumbnail_url as string | null | undefined) ?? (record.thumbnail_path as string | null | undefined) ?? (record.file_path as string | null | undefined) ?? (record.full_url as string | null | undefined) ?? (record.url as string | null | undefined) ?? (record.image_url as string | null | undefined) ); return { id, imageUrl }; }) .filter((item) => item.id && item.imageUrl); setPreview(mapped); }) .catch((error) => { console.error('Failed to load gallery preview', error); if (active) { setPreview([]); } }) .finally(() => { if (active) { setPreviewLoading(false); } }); return () => { active = false; }; }, [token]); const queueCount = items.filter((item) => item.status !== 'done').length; return ( = 1 ? 1 : 0} y={revealStage >= 1 ? 0 : 12} > {rings.map((ring) => ( ))} {tasksEnabled ? ( = 2 ? 0 : 16} style={{ backgroundImage: isDark ? 'linear-gradient(135deg, rgba(255, 79, 216, 0.25), rgba(79, 209, 255, 0.12))' : 'linear-gradient(135deg, color-mix(in oklab, var(--guest-primary, #FF5A5F) 18%, white), color-mix(in oklab, var(--guest-secondary, #F43F5E) 10%, white))', boxShadow: cardShadow, }} > {t('homeV2.promptQuest.label', 'Prompt quest')} {t('homeV2.promptQuest.title', 'Capture the happiest laugh')} {t('homeV2.promptQuest.subtitle', 'Earn points and keep the gallery lively.')} ) : ( = 2 ? 0 : 16} style={{ backgroundImage: isDark ? 'linear-gradient(135deg, rgba(79, 209, 255, 0.18), rgba(255, 79, 216, 0.12))' : 'linear-gradient(135deg, color-mix(in oklab, var(--guest-secondary, #F43F5E) 10%, white), color-mix(in oklab, var(--guest-primary, #FF5A5F) 10%, white))', boxShadow: cardShadow, }} > {t('homeV2.captureReady.label', 'Capture ready')} {t('homeV2.captureReady.title', 'Add a photo to the shared gallery')} {t('homeV2.captureReady.subtitle', 'Quick add from your camera or device.')} )} = 4 ? 1 : 0} y={revealStage >= 4 ? 0 : 16} style={{ boxShadow: cardShadow, }} > {t('homeV2.galleryPreview.title', 'Gallery preview')} {(previewLoading || preview.length === 0 ? [1, 2, 3, 4] : preview).map((tile, index) => { if (typeof tile === 'number') { return ( ); } return ( ); })} ); }