import React from 'react'; import { useNavigate } from 'react-router-dom'; import { YStack, XStack } from '@tamagui/stacks'; import { SizableText as Text } from '@tamagui/text'; import { Button } from '@tamagui/button'; import { Input } from '@tamagui/input'; import { Card } from '@tamagui/card'; import { Html5Qrcode } from 'html5-qrcode'; import { QrCode, ArrowRight } from 'lucide-react'; import { useTranslation } from '@/shared/guest/i18n/useTranslation'; import { fetchEvent } from '../services/eventApi'; import { readGuestName } from '../context/GuestIdentityContext'; import EventLogo from '../components/EventLogo'; import { useGuestThemeVariant } from '../lib/guestTheme'; const qrConfig = { fps: 10, qrbox: { width: 240, height: 240 } } as const; type LandingErrorKey = 'eventClosed' | 'network' | 'camera'; export default function LandingScreen() { const navigate = useNavigate(); const { t } = useTranslation(); const [eventCode, setEventCode] = React.useState(''); const [loading, setLoading] = React.useState(false); const [errorKey, setErrorKey] = React.useState(null); const [isScanning, setIsScanning] = React.useState(false); const scannerRef = React.useRef(null); const { isDark } = useGuestThemeVariant(); const cardBackground = isDark ? 'rgba(15, 23, 42, 0.7)' : 'rgba(255, 255, 255, 0.9)'; const cardBorder = isDark ? 'rgba(148, 163, 184, 0.2)' : 'rgba(15, 23, 42, 0.12)'; const primaryText = isDark ? '#F8FAFF' : '#0F172A'; const mutedText = isDark ? 'rgba(226, 232, 240, 0.78)' : 'rgba(15, 23, 42, 0.6)'; const mutedButton = isDark ? 'rgba(248, 250, 255, 0.1)' : 'rgba(15, 23, 42, 0.06)'; const mutedButtonBorder = isDark ? 'rgba(248, 250, 255, 0.2)' : 'rgba(15, 23, 42, 0.12)'; const brandName = 'Fotospiel'; const errorMessage = errorKey ? t(`landing.errors.${errorKey}`) : null; const extractEventKey = React.useCallback((raw: string): string => { const trimmed = raw.trim(); if (!trimmed) { return ''; } try { const url = new URL(trimmed); const inviteParam = url.searchParams.get('invite') ?? url.searchParams.get('token'); if (inviteParam) { return inviteParam; } const segments = url.pathname.split('/').filter(Boolean); const eventIndex = segments.findIndex((segment) => segment === 'e'); if (eventIndex >= 0 && segments.length > eventIndex + 1) { return decodeURIComponent(segments[eventIndex + 1]); } if (segments.length > 0) { return decodeURIComponent(segments[segments.length - 1]); } } catch { // Not a URL, treat as raw code. } return trimmed; }, []); const join = React.useCallback( async (input?: string) => { const provided = input ?? eventCode; const normalized = extractEventKey(provided); if (!normalized) return; setLoading(true); setErrorKey(null); try { const event = await fetchEvent(normalized); const targetKey = event.join_token ?? normalized; if (!targetKey) { setErrorKey('eventClosed'); return; } const storedName = readGuestName(targetKey); if (!storedName) { navigate(`/setup/${encodeURIComponent(targetKey)}`); } else { navigate(`/e/${encodeURIComponent(targetKey)}`); } } catch (error) { console.error('Join request failed', error); setErrorKey('network'); } finally { setLoading(false); } }, [eventCode, extractEventKey, navigate] ); const onScanSuccess = React.useCallback( async (decodedText: string) => { const value = decodedText.trim(); if (!value) return; await join(value); if (scannerRef.current) { try { await scannerRef.current.stop(); } catch (error) { console.warn('Scanner stop failed', error); } } setIsScanning(false); }, [join] ); const startScanner = React.useCallback(async () => { if (scannerRef.current) { try { await scannerRef.current.start({ facingMode: 'environment' }, qrConfig, onScanSuccess, () => undefined); setIsScanning(true); } catch (error) { console.error('Scanner start failed', error); setErrorKey('camera'); } return; } try { const scanner = new Html5Qrcode('qr-reader'); scannerRef.current = scanner; await scanner.start({ facingMode: 'environment' }, qrConfig, onScanSuccess, () => undefined); setIsScanning(true); } catch (error) { console.error('Scanner init failed', error); setErrorKey('camera'); } }, [onScanSuccess]); const stopScanner = React.useCallback(async () => { if (!scannerRef.current) { setIsScanning(false); return; } try { await scannerRef.current.stop(); } catch (error) { console.warn('Scanner stop failed', error); } finally { setIsScanning(false); } }, []); React.useEffect(() => () => { if (scannerRef.current) { scannerRef.current.stop().catch(() => undefined); } }, []); return ( {brandName} {t('landing.pageTitle')} {t('landing.subheadline')} {errorMessage ? ( {errorMessage} ) : null} {t('landing.join.title')} {t('landing.join.description')} {t('landing.scan.manualDivider')} ); }