import React, { useEffect, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { Input } from '@/components/ui/input'; import { Button } from '@/components/ui/button'; import { Alert, AlertDescription } from '@/components/ui/alert'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { Sparkles, Camera, ShieldCheck, QrCode, PartyPopper, Smartphone } from 'lucide-react'; import { Html5Qrcode } from 'html5-qrcode'; import { readGuestName } from '../context/GuestIdentityContext'; import { useTranslation } from '../i18n/useTranslation'; type LandingErrorKey = 'eventClosed' | 'network' | 'camera'; export default function LandingPage() { const nav = useNavigate(); const { t } = useTranslation(); const [eventCode, setEventCode] = useState(''); const [loading, setLoading] = useState(false); const [errorKey, setErrorKey] = useState(null); const [isScanning, setIsScanning] = useState(false); const [scanner, setScanner] = useState(null); const errorMessage = errorKey ? t(`landing.errors.${errorKey}`) : null; function extractEventKey(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; } async function join(input?: string) { const provided = input ?? eventCode; const normalized = extractEventKey(provided); if (!normalized) return; setLoading(true); setErrorKey(null); try { const res = await fetch(`/api/v1/events/${encodeURIComponent(normalized)}`); if (!res.ok) { setErrorKey('eventClosed'); return; } const data = await res.json(); const targetKey = data.join_token ?? ''; if (!targetKey) { setErrorKey('eventClosed'); return; } const storedName = readGuestName(targetKey); if (!storedName) { nav(`/setup/${encodeURIComponent(targetKey)}`); } else { nav(`/e/${encodeURIComponent(targetKey)}`); } } catch (e) { console.error('Join request failed', e); setErrorKey('network'); } finally { setLoading(false); } } const qrConfig = { fps: 10, qrbox: { width: 250, height: 250 } } as const; async function startScanner() { if (scanner) { try { await scanner.start({ facingMode: 'environment' }, qrConfig, onScanSuccess, () => undefined); setIsScanning(true); } catch (err) { console.error('Scanner start failed', err); setErrorKey('camera'); } return; } try { const newScanner = new Html5Qrcode('qr-reader'); setScanner(newScanner); await newScanner.start({ facingMode: 'environment' }, qrConfig, onScanSuccess, () => undefined); setIsScanning(true); } catch (err) { console.error('Scanner initialisation failed', err); setErrorKey('camera'); } } function stopScanner() { if (!scanner) { setIsScanning(false); return; } scanner .stop() .then(() => { setIsScanning(false); }) .catch((err) => console.error('Scanner stop failed', err)); } async function onScanSuccess(decodedText: string) { const value = decodedText.trim(); if (!value) return; await join(value); stopScanner(); } useEffect(() => () => { if (scanner) { scanner.stop().catch(() => undefined); } }, [scanner]); const heroFeatures = [ { icon: Sparkles, title: t('landing.features.momentsTitle', 'Momente mit Wow-Effekt'), description: t('landing.features.momentsCopy', 'Moderierte Fotoaufgaben motivieren dein Team und halten die Stimmung hoch.'), }, { icon: Camera, title: t('landing.features.uploadTitle', 'Uploads ohne App-Stress'), description: t('landing.features.uploadCopy', 'Scan & Shoot: Gäste landen direkt im Event und teilen ihre Highlights live.'), }, { icon: ShieldCheck, title: t('landing.features.trustTitle', 'Sicher & DSGVO-konform'), description: t('landing.features.trustCopy', 'Nur eingeladene Gäste erhalten Zugriff – mit Tokens, Rollenrechten und deutschem Hosting.'), }, ]; const highlightCards = [ { icon: Sparkles, title: t('landing.highlight.story', 'Storytelling statt Sammelalbum'), description: t('landing.highlight.storyCopy', 'Fotospiel verbindet Aufgaben, Emotionen und Uploads zu einer spannenden Timeline.'), }, { icon: Camera, title: t('landing.highlight.mobile', 'Optimiert für jedes Smartphone'), description: t('landing.highlight.mobileCopy', 'Keine App-Installation nötig – einfach Link öffnen oder QR-Code scannen.'), }, { icon: ShieldCheck, title: t('landing.highlight.privacy', 'Transparente Freigaben'), description: t('landing.highlight.privacyCopy', 'Admin- und Gästerollen sorgen dafür, dass nur autorisierte Personen Inhalte sehen.'), }, { icon: PartyPopper, title: t('landing.highlight.live', 'Live auf Screens & Slideshows'), description: t('landing.highlight.liveCopy', 'Uploads können sofort auf Displays, Projektoren oder dem großen Screen erscheinen.'), }, ]; const steps = [ { icon: QrCode, label: t('landing.steps.scan', 'QR-Code vom Event scannen oder Link öffnen.') }, { icon: Smartphone, label: t('landing.steps.profile', 'Kurz vorstellen: Name eintragen und loslegen.') }, { icon: PartyPopper, label: t('landing.steps.upload', 'Fotos aufnehmen, Aufgaben lösen, Erinnerungen teilen.') }, ]; return (
{errorMessage && ( {errorMessage} )}

{t('landing.pageTitle')}

{t('landing.headline', 'Die Event-Landing, die zum Marketing passt.')}

{t('landing.subheadline', 'Fotospiel begrüßt deine Gäste mit einem warmen Erlebnis, noch bevor die erste Aufnahme entsteht.')}

    {heroFeatures.map((feature) => (
  • {feature.title}

    {feature.description}

  • ))}
{t('landing.tags.private', 'Nur für eingeladene Gäste')} {t('landing.tags.instant', 'Live-Uploads & Aufgaben')} {t('landing.tags.deHosted', 'Gehostet in Deutschland')}
{t('landing.join.title')} {t('landing.join.description')}
{t('landing.join.subline', 'QR · Code · Link')}

{t('landing.scan.headline', 'QR-Code scannen')}

{t('landing.scan.subline', 'Nutze die Kamera deines Smartphones oder Tablets')}

{t('landing.scan.manualDivider')}
setEventCode(event.target.value)} placeholder={t('landing.input.placeholder')} disabled={loading} className="h-12 rounded-2xl border-slate-200 bg-white px-4 text-base" />

{t('landing.hint.support', 'Du hast einen Link erhalten? Füge ihn direkt oben ein – wir erkennen den Event-Code automatisch.')}

{t('landing.steps.title', 'So funktioniert Fotospiel')}

{steps.map(({ icon: Icon, label }) => (

{label}

))}
{highlightCards.map((feature) => (

{feature.title}

{feature.description}

))}

{t('landing.support.title', 'Support & Fragen')}

{t('landing.support.copy', 'Frag dein Event-Team oder melde dich bei uns – wir helfen sofort weiter.')}

{t('landing.support.email', 'support@fotospiel.de')} {t('landing.support.reply', 'Direkt auf die Einladung antworten')}
); }