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 (
);
})}
);
}