diff --git a/resources/js/admin/context/EventContext.tsx b/resources/js/admin/context/EventContext.tsx index 287b8de..62dbd22 100644 --- a/resources/js/admin/context/EventContext.tsx +++ b/resources/js/admin/context/EventContext.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { useQuery } from '@tanstack/react-query'; import { getEvents, type TenantEvent } from '../api'; +import { useAuth } from '../auth/context'; const STORAGE_KEY = 'tenant-admin.active-event'; @@ -15,6 +16,7 @@ export interface EventContextValue { const EventContext = React.createContext(undefined); export function EventProvider({ children }: { children: React.ReactNode }) { + const { status } = useAuth(); const [storedSlug, setStoredSlug] = React.useState(() => { if (typeof window === 'undefined') { return null; @@ -22,20 +24,29 @@ export function EventProvider({ children }: { children: React.ReactNode }) { return window.localStorage.getItem(STORAGE_KEY); }); - const { data: events = [], isLoading } = useQuery({ + const authReady = status === 'authenticated'; + + const { + data: fetchedEvents = [], + isLoading: queryLoading, + } = useQuery({ queryKey: ['tenant-events'], queryFn: async () => { try { return await getEvents(); } catch (error) { console.warn('[EventContext] Failed to fetch events', error); - return []; + throw error; } }, staleTime: 60 * 1000, cacheTime: 5 * 60 * 1000, + enabled: authReady, }); + const events = authReady ? fetchedEvents : []; + const isLoading = authReady ? queryLoading : status === 'loading'; + React.useEffect(() => { if (!storedSlug && events.length === 1 && events[0]?.slug && typeof window !== 'undefined') { setStoredSlug(events[0].slug); diff --git a/resources/js/admin/i18n/locales/de/auth.json b/resources/js/admin/i18n/locales/de/auth.json index 6bc266a..9e84420 100644 --- a/resources/js/admin/i18n/locales/de/auth.json +++ b/resources/js/admin/i18n/locales/de/auth.json @@ -11,8 +11,8 @@ "Steuere Aufgaben, Emotionen und Slideshows direkt vom Event aus." ], "lead": "Du meldest dich über unser gesichertes Fotospiel-Login an und landest direkt im Event-Dashboard.", - "panel_title": "Melde dich an", - "panel_copy": "Logge dich mit deinem Fotospiel-Adminzugang ein. Wir schützen dein Konto mit persönlichen Zugriffstokens und klaren Rollenrechten.", + "panel_title": "Team Login für Fotospiel", + "panel_copy": "Melde dich an, um Events zu planen, Fotos zu moderieren und Aufgaben im Fotospiel-Team zu koordinieren.", "actions_title": "Wähle deine Anmeldemethode", "actions_copy": "Greife sicher mit deinem Fotospiel-Login oder deinem Google-Konto auf das Tenant-Dashboard zu.", "cta": "Mit Fotospiel-Login fortfahren", diff --git a/resources/js/admin/pages/LoginPage.tsx b/resources/js/admin/pages/LoginPage.tsx index a509461..dd2a531 100644 --- a/resources/js/admin/pages/LoginPage.tsx +++ b/resources/js/admin/pages/LoginPage.tsx @@ -3,7 +3,6 @@ import { useTranslation } from 'react-i18next'; import { useLocation, useNavigate } from 'react-router-dom'; import { Loader2 } from 'lucide-react'; -import AppLogoIcon from '@/components/app-logo-icon'; import { Alert, AlertDescription } from '@/components/ui/alert'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; @@ -119,14 +118,22 @@ export default function LoginPage(): JSX.Element {
- - + + {t('login.brand_alt',

- {t('login.panel_title', t('login.title', 'Event Admin Login'))} + {t('login.panel_title', t('login.title', 'Team Login für Fotospiel'))}

- {t('login.panel_copy', 'Sign in with your Fotospiel admin credentials to manage your events and galleries.')} + {t( + 'login.panel_copy', + 'Melde dich an, um Events zu planen, Fotos zu moderieren und Aufgaben im Fotospiel-Team zu koordinieren.' + )}

@@ -195,7 +202,7 @@ export default function LoginPage(): JSX.Element {
- {t('login.support', "Brauchen Sie Hilfe? Schreiben Sie uns an support@fotospiel.de")} + {t('login.support', 'Fragen? Schreib uns an support@fotospiel.de oder antworte direkt auf deine Einladung.')}
diff --git a/resources/js/guest/i18n/messages.ts b/resources/js/guest/i18n/messages.ts index 0e6fb56..89bdea3 100644 --- a/resources/js/guest/i18n/messages.ts +++ b/resources/js/guest/i18n/messages.ts @@ -106,9 +106,9 @@ export const messages: Record = { }, }, landing: { - pageTitle: 'Willkommen bei der Fotobox!', - headline: 'Willkommen bei der Fotobox!', - subheadline: 'Dein Schlüssel zu unvergesslichen Momenten.', + pageTitle: 'Willkommen bei der Fotospiel.App!', + headline: 'Elegante Erinnerungen, live erzählt.', + subheadline: 'Hier beginnt euer Fotoabenteuer – gemeinsam, intuitiv und live.', join: { title: 'Event beitreten', description: 'Scanne den QR-Code oder gib den Code manuell ein.', @@ -534,9 +534,9 @@ export const messages: Record = { }, }, landing: { - pageTitle: 'Welcome to the photo booth!', - headline: 'Welcome to the photo booth!', - subheadline: 'Your key to unforgettable moments.', + pageTitle: 'Welcome to the Fotospiel.App!', + headline: 'An elegant way to tell memories live.', + subheadline: 'Start your collaborative photo story—intuitive, fast, live.', join: { title: 'Join the event', description: 'Scan the QR code or enter the code manually.', diff --git a/resources/js/guest/pages/LandingPage.tsx b/resources/js/guest/pages/LandingPage.tsx index e8f48b1..c561bff 100644 --- a/resources/js/guest/pages/LandingPage.tsx +++ b/resources/js/guest/pages/LandingPage.tsx @@ -1,10 +1,10 @@ import React, { useEffect, useState } from 'react'; -import { Page } from './_util'; 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'; @@ -131,66 +131,219 @@ export default function LandingPage() { } }, [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.headline')}

-

{t('landing.subheadline')}

-
+
+
+
+
+
+ {errorMessage && ( + + {errorMessage} + + )} - - - {t('landing.join.title')} - {t('landing.join.description')} - - -
-
- - - +
+
+

{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.')} +

-
+ +
+

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

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

{label}

+
+ ))} +
+
+ +
+ {highlightCards.map((feature) => ( +
- {loading ? t('landing.join.buttonLoading') : t('landing.join.button')} - +
+ + + +

{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')} + +
- - +
+
- +
); }