Added opaque join-token support across backend and frontend: new migration/model/service/endpoints, guest controllers now resolve tokens, and the demo seeder seeds a token. Tenant event details list/manage tokens with copy/revoke actions, and the guest PWA uses tokens end-to-end (routing, storage, uploads, achievements, etc.). Docs TODO updated to reflect completed steps.

This commit is contained in:
Codex Agent
2025-10-12 10:32:37 +02:00
parent d04e234ca0
commit 9394c3171e
73 changed files with 3277 additions and 911 deletions

View File

@@ -0,0 +1,21 @@
{
"app": {
"brand": "Fotospiel Tenant Admin",
"languageSwitch": "Sprache"
},
"navigation": {
"dashboard": "Dashboard",
"events": "Events",
"tasks": "Aufgaben",
"billing": "Abrechnung",
"settings": "Einstellungen"
},
"language": {
"de": "Deutsch",
"en": "Englisch"
},
"actions": {
"open": "Öffnen",
"viewAll": "Alle anzeigen"
}
}

View File

@@ -0,0 +1,72 @@
{
"actions": {
"newEvent": "Neues Event",
"allEvents": "Alle Events",
"guidedSetup": "Guided Setup"
},
"welcome": {
"fallbackName": "Tenant-Admin",
"greeting": "Hallo {{name}}!",
"subtitle": "Behalte deine Events, Credits und Aufgaben im Blick."
},
"errors": {
"loadFailed": "Dashboard konnte nicht geladen werden."
},
"alerts": {
"errorTitle": "Fehler"
},
"welcomeCard": {
"title": "Starte mit der Welcome Journey",
"summary": "Lerne die Storytelling-Elemente kennen, wähle dein Paket und erstelle dein erstes Event mit geführten Schritten.",
"body1": "Wir begleiten dich durch Pakete, Aufgaben und Galerie-Konfiguration, damit dein Event glänzt.",
"body2": "Du kannst jederzeit zur Welcome Journey zurückkehren, auch wenn bereits Events laufen.",
"cta": "Jetzt starten"
},
"overview": {
"title": "Kurzer Überblick",
"description": "Wichtigste Kennzahlen deines Tenants auf einen Blick.",
"noPackage": "Kein aktives Paket",
"stats": {
"activeEvents": "Aktive Events",
"publishedHint": "{{count}} veröffentlicht",
"newPhotos": "Neue Fotos (7 Tage)",
"taskProgress": "Task-Fortschritt",
"credits": "Credits",
"lowCredits": "Auffüllen empfohlen"
}
},
"quickActions": {
"title": "Schnellaktionen",
"description": "Starte durch mit den wichtigsten Aktionen.",
"createEvent": {
"label": "Event erstellen",
"description": "Plane dein nächstes Highlight."
},
"moderatePhotos": {
"label": "Fotos moderieren",
"description": "Prüfe neue Uploads."
},
"organiseTasks": {
"label": "Tasks organisieren",
"description": "Sorge für klare Verantwortungen."
},
"manageCredits": {
"label": "Credits verwalten",
"description": "Sieh dir Balance & Ledger an."
}
},
"upcoming": {
"title": "Kommende Events",
"description": "Die nächsten Termine inklusive Status & Zugriff.",
"settings": "Einstellungen öffnen",
"empty": {
"message": "Noch keine Termine geplant. Lege dein erstes Event an!",
"cta": "Event planen"
},
"status": {
"live": "Live",
"planning": "In Planung",
"noDate": "Kein Datum"
}
}
}

View File

@@ -0,0 +1,150 @@
{
"billing": {
"title": "Billing und Credits",
"subtitle": "Verwalte Guthaben, Pakete und Abrechnungen.",
"actions": {
"refresh": "Aktualisieren",
"exportCsv": "Export als CSV"
},
"errors": {
"load": "Billing-Daten konnten nicht geladen werden.",
"more": "Weitere Ledger-Einträge konnten nicht geladen werden."
},
"sections": {
"overview": {
"title": "Credits und Status",
"description": "Dein aktuelles Guthaben und das aktive Reseller-Paket.",
"cards": {
"balance": {
"label": "Verfügbare Credits"
},
"used": {
"label": "Genutzte Events",
"helper": "Verfügbar: {{count}}"
},
"price": {
"label": "Preis (netto)"
},
"expires": {
"label": "Ablauf",
"helper": "Automatisch verlängern, falls aktiv"
}
}
},
"packages": {
"title": "Paket-Historie",
"description": "Übersicht über aktive und vergangene Reseller-Pakete.",
"empty": "Noch keine Pakete gebucht.",
"card": {
"statusActive": "Aktiv",
"statusInactive": "Inaktiv",
"used": "Genutzte Events",
"available": "Verfügbar",
"expires": "Ablauf"
}
},
"ledger": {
"title": "Credit Ledger",
"description": "Alle Zu- und Abbuchungen deines Credits-Kontos.",
"empty": "Noch keine Ledger-Einträge vorhanden.",
"loadMore": "Mehr laden",
"reasons": {
"purchase": "Credit-Kauf",
"usage": "Verbrauch",
"manual": "Manuelle Anpassung"
}
}
}
},
"members": {
"title": "Event-Mitglieder",
"subtitle": "Verwalte Moderatoren, Admins und Helfer für dieses Event.",
"actions": {
"back": "Zurück zur Übersicht"
},
"errors": {
"missingSlug": "Kein Event-Slug angegeben.",
"load": "Mitglieder konnten nicht geladen werden.",
"emailRequired": "Bitte gib eine E-Mail-Adresse ein.",
"invite": "Einladung konnte nicht verschickt werden.",
"remove": "Mitglied konnte nicht entfernt werden."
},
"alerts": {
"notFoundTitle": "Event nicht gefunden",
"notFoundDescription": "Bitte kehre zur Eventliste zurück.",
"lockedTitle": "Feature noch nicht aktiviert",
"lockedDescription": "Die Mitgliederverwaltung ist für dieses Event noch nicht verfügbar. Bitte kontaktiere den Support, um das Feature freizuschalten."
},
"sections": {
"list": {
"title": "Mitglieder",
"empty": "Noch keine Mitglieder eingeladen."
},
"invite": {
"title": "Neues Mitglied einladen"
}
},
"labels": {
"status": "Status: {{status}}",
"joined": "Beigetreten: {{date}}"
},
"form": {
"emailLabel": "E-Mail",
"emailPlaceholder": "person@example.com",
"nameLabel": "Name (optional)",
"namePlaceholder": "Name",
"roleLabel": "Rolle",
"rolePlaceholder": "Rolle wählen",
"submit": "Einladung senden"
},
"roles": {
"tenantAdmin": "Tenant-Admin",
"member": "Mitglied",
"guest": "Gast"
},
"statuses": {
"published": "Veröffentlicht",
"draft": "Entwurf",
"active": "Aktiv"
},
"eventStatus": "Status: {{status}}",
"events": {
"untitled": "Unbenanntes Event"
}
},
"tasks": {
"title": "Event-Tasks",
"subtitle": "Verwalte Aufgaben, die diesem Event zugeordnet sind.",
"actions": {
"back": "Zurück zur Übersicht",
"assign": "Ausgewählte Tasks zuweisen"
},
"errors": {
"missingSlug": "Kein Event-Slug angegeben.",
"load": "Event-Tasks konnten nicht geladen werden.",
"assign": "Tasks konnten nicht zugewiesen werden."
},
"alerts": {
"notFoundTitle": "Event nicht gefunden",
"notFoundDescription": "Bitte kehre zur Eventliste zurück."
},
"eventStatus": "Status: {{status}}",
"sections": {
"assigned": {
"title": "Zugeordnete Tasks",
"empty": "Noch keine Tasks zugewiesen."
},
"library": {
"title": "Tasks aus Bibliothek hinzufügen",
"empty": "Keine Tasks in der Bibliothek gefunden."
}
},
"priorities": {
"low": "Niedrig",
"medium": "Mittel",
"high": "Hoch",
"urgent": "Dringend"
}
}
}

View File

@@ -0,0 +1,268 @@
{
"layout": {
"eyebrow": "Fotospiel Tenant Admin",
"title": "Willkommen im Event-Erlebnisstudio",
"subtitle": "Starte mit einer inspirierten Einführung, sichere dir dein Event-Paket und kreiere die perfekte Gästegalerie alles optimiert für mobile Hosts.",
"alreadyFamiliar": "Schon vertraut mit Fotospiel?",
"jumpToDashboard": "Direkt zum Dashboard"
},
"hero": {
"eyebrow": "Dein Event, deine Bühne",
"title": "Gestalte das nächste Fotospiel Erlebnis",
"scriptTitle": "Einmalig für Gäste, mühelos für dich.",
"description": "Mit nur wenigen Schritten führst du deine Gäste durch ein magisches Fotoabenteuer inklusive Storytelling, Aufgaben und moderierter Galerie.",
"primary": {
"label": "Pakete entdecken",
"button": "Pakete entdecken"
},
"secondary": {
"label": "Events anzeigen",
"button": "Bestehende Events anzeigen"
}
},
"highlights": {
"gallery": {
"title": "Premium Gästegalerie",
"description": "Kuratiere Fotos in Echtzeit, markiere Highlights und teile QR-Codes mit einem Tap.",
"badge": "Neu"
},
"team": {
"title": "Flexibles Team-Onboarding",
"description": "Lade Co-Hosts ein, weise Rollen zu und behalte den Überblick über Moderation und Aufgaben."
},
"story": {
"title": "Storytelling in Etappen",
"description": "Geführte Aufgaben und Emotionskarten machen jedes Event zu einer erinnerungswürdigen Reise."
}
},
"ctaList": {
"choosePackage": {
"label": "Dein Eventpaket auswählen",
"description": "Reserviere Credits oder Abos, um sofort Events zu aktivieren. Flexible Optionen für jede Eventgröße.",
"button": "Weiter zu Paketen"
},
"createEvent": {
"label": "Event vorbereiten",
"description": "Sammle Eventdetails, plane Aufgaben und sorge für einen reibungslosen Ablauf noch vor dem Tag des Events.",
"button": "Zum Event-Manager"
}
},
"packages": {
"layout": {
"eyebrow": "Schritt 2",
"title": "Wähle dein Eventpaket",
"subtitle": "Fotospiel bietet flexible Preismodelle: einmalige Credits oder Abos, die mehrere Events abdecken."
},
"step": {
"title": "Aktiviere die passenden Credits",
"description": "Sichere dir Kapazität für dein nächstes Event. Du kannst jederzeit upgraden bezahle nur, was du brauchst."
},
"state": {
"loading": "Pakete werden geladen …",
"errorTitle": "Fehler beim Laden",
"errorDescription": "Bitte versuche es erneut oder kontaktiere den Support.",
"emptyTitle": "Der Katalog ist leer",
"emptyDescription": "Aktuell sind keine Pakete verfügbar. Wende dich an den Support, um neue Angebote freizuschalten."
},
"card": {
"subscription": "Abo",
"creditPack": "Credit-Paket",
"description": "Sofort einsatzbereit für dein nächstes Event.",
"descriptionWithPhotos": "Bis zu {{count}} Fotos inklusive perfekt für lebendige Reportagen.",
"active": "Aktives Paket",
"select": "Paket wählen",
"onRequest": "Auf Anfrage",
"purchased": "Bereits gekauft am {{date}}",
"purchasedUnknown": "unbekanntem Datum",
"badges": {
"guests": "{{count}} Gäste",
"days": "{{count}} Tage Galerie",
"photos": "{{count}} Fotos"
}
},
"features": {
"subscription": "Abo-Verlängerung",
"priority_support": "Priorisierter Support",
"custom_domain": "Eigene Domain",
"analytics": "Analytics",
"team_management": "Teamverwaltung",
"moderation_tools": "Moderationstools",
"prints": "Print-Uploads"
},
"cta": {
"billing": {
"label": "Direkt zum Billing",
"description": "Falls du schon weißt, welches Paket du brauchst, gelangst du hier zum bekannten Abrechnungsbereich.",
"button": "Billing öffnen"
},
"summary": {
"label": "Bestellübersicht anzeigen",
"description": "Prüfe Paketdetails und entscheide, ob du direkt zahlen oder später fortfahren möchtest.",
"button": "Weiter zur Übersicht"
}
}
},
"summary": {
"layout": {
"eyebrow": "Schritt 3",
"title": "Bestellübersicht",
"subtitle": "Prüfe Paket, Preis und Abrechnung bevor du zum Event-Setup wechselst."
},
"footer": {
"back": "Zurück zur Paketauswahl"
},
"step": {
"title": "Deine Auswahl im Überblick",
"description": "Du kannst sofort abrechnen oder das Setup fortsetzen und später bezahlen."
},
"state": {
"loading": "Wir prüfen verfügbare Pakete …",
"errorTitle": "Paketdaten derzeit nicht verfügbar",
"errorDescription": "Bitte versuche es erneut oder kontaktiere den Support.",
"missingTitle": "Keine Paketauswahl gefunden",
"missingDescription": "Bitte wähle zuerst ein Paket aus oder aktualisiere die Seite, falls sich Daten geändert haben."
},
"details": {
"subscription": "Abo",
"creditPack": "Credit-Paket",
"photos": "Bis zu {{count}} Fotos",
"galleryDays": "Galerie {{count}} Tage",
"guests": "{{count}} Gäste",
"infinity": "∞",
"features": {
"subscription": "Abo",
"priority_support": "Priorisierter Support",
"custom_domain": "Eigene Domain",
"analytics": "Analytics",
"team_management": "Teamverwaltung",
"moderation_tools": "Moderationstools",
"prints": "Print-Uploads"
},
"section": {
"photosTitle": "Fotos & Galerie",
"photosValue": "Bis zu {{count}} Fotos, Galerie {{days}} Tage",
"photosUnlimited": "Unbegrenzte Fotos, flexible Galerie",
"guestsTitle": "Gäste & Team",
"guestsValue": "{{count}} Gäste inklusive, Co-Hosts frei planbar",
"guestsUnlimited": "Unbegrenzte Gästeliste",
"featuresTitle": "Highlights",
"featuresNone": "Standard",
"statusTitle": "Status",
"statusActive": "Bereits gebucht",
"statusInactive": "Noch nicht gebucht"
}
},
"status": {
"pendingTitle": "Abrechnung steht noch aus",
"pendingDescription": "Du kannst das Event bereits vorbereiten. Spätestens zur Veröffentlichung benötigst du ein aktives Paket."
},
"free": {
"description": "Dieses Paket ist kostenlos. Du kannst es sofort deinem Tenant zuweisen und direkt mit dem Setup weitermachen.",
"activate": "Gratis-Paket aktivieren",
"progress": "Aktivierung läuft …",
"successTitle": "Gratis-Paket aktiviert",
"successDescription": "Deine Credits wurden hinzugefügt. Weiter geht's mit dem Event-Setup.",
"failureTitle": "Aktivierung fehlgeschlagen",
"errorMessage": "Kostenloses Paket konnte nicht aktiviert werden.",
},
"stripe": {
"sectionTitle": "Kartenzahlung (Stripe)",
"heading": "Kartenzahlung",
"notReady": "Zahlungsmodul noch nicht bereit. Bitte lade die Seite neu.",
"genericError": "Zahlung fehlgeschlagen. Bitte erneut versuchen.",
"missingPaymentId": "Zahlung konnte nicht bestätigt werden (fehlende Zahlungs-ID).",
"completionFailed": "Der Kauf wurde bei uns noch nicht verbucht. Bitte kontaktiere den Support mit deiner Zahlungsbestätigung.",
"errorTitle": "Zahlung fehlgeschlagen",
"submitting": "Zahlung wird bestätigt …",
"submit": "Jetzt bezahlen",
"hint": "Sicherer Checkout über Stripe. Du erhältst eine Bestätigung, sobald der Kauf verbucht wurde.",
"loading": "Zahlungsdetails werden geladen …",
"unavailableTitle": "Stripe nicht verfügbar",
"unavailableDescription": "Stripe konnte nicht initialisiert werden.",
"missingKey": "Stripe Publishable Key fehlt. Bitte konfiguriere VITE_STRIPE_PUBLISHABLE_KEY.",
"intentFailed": "Stripe-Zahlung konnte nicht vorbereitet werden."
},
"paypal": {
"sectionTitle": "PayPal",
"heading": "PayPal",
"createFailed": "PayPal-Bestellung konnte nicht erstellt werden. Bitte versuche es erneut.",
"captureFailed": "PayPal-Zahlung konnte nicht abgeschlossen werden. Bitte kontaktiere den Support, falls der Betrag bereits abgebucht wurde.",
"errorTitle": "PayPal-Fehler",
"genericError": "PayPal hat ein Problem gemeldet. Bitte versuche es später erneut.",
"missingOrderId": "PayPal hat keine Order-ID geliefert.",
"cancelled": "PayPal-Zahlung wurde abgebrochen.",
"hint": "PayPal leitet dich ggf. weiter, um die Zahlung zu bestätigen. Anschließend kommst du automatisch zurück.",
"notConfiguredTitle": "PayPal nicht konfiguriert",
"notConfiguredDescription": "Hinterlege VITE_PAYPAL_CLIENT_ID, damit Gastgeber optional mit PayPal bezahlen können."
},
"nextStepsTitle": "Nächste Schritte",
"nextSteps": [
"Optional: Abrechnung abschließen (Stripe/PayPal) im Billing-Bereich.",
"Event-Setup durchlaufen und Aufgaben, Team & Galerie konfigurieren.",
"Vor dem Go-Live Credits prüfen und Gäste-Link teilen."
],
"cta": {
"billing": {
"label": "Abrechnung starten",
"description": "Öffnet den Billing-Bereich mit allen Kaufoptionen (Stripe, PayPal, Credits).",
"button": "Zu Billing & Zahlung"
},
"setup": {
"label": "Mit Event-Setup fortfahren",
"description": "Du kannst später jederzeit zur Abrechnung zurückkehren.",
"button": "Weiter zum Setup"
}
}
},
"eventSetup": {
"layout": {
"eyebrow": "Schritt 4",
"title": "Bereite dein erstes Event vor",
"subtitle": "Fülle wenige Details aus, lade Co-Hosts ein und öffne deine Gästegalerie für das große Ereignis."
},
"step": {
"title": "Event-Setup in Minuten",
"description": "Wir führen dich durch Name, Datum, Mood und Aufgaben. Danach kannst du Fotos moderieren und Gäste live begleiten."
},
"tiles": {
"story": {
"title": "Story & Stimmung",
"copy": "Wähle Bildsprache, Farben und Emotionskarten für dein Event."
},
"team": {
"title": "Team organisieren",
"copy": "Lade Moderator*innen oder Fotograf*innen ein und teile Rollen zu."
},
"launch": {
"title": "Go-Live vorbereiten",
"copy": "Erstelle QR-Codes, teste die Gästegalerie und kommuniziere den Ablauf."
}
},
"cta": {
"heading": "Bereit für dein erstes Event?",
"description": "Du wechselst jetzt in den Event-Manager. Dort kannst du Tasks zuweisen, Mitglieder einladen und die Gästegalerie testen. Keine Sorge: Du kannst jederzeit zur Welcome Journey zurückkehren.",
"button": "Event erstellen"
},
"actions": {
"back": {
"label": "Noch einmal Pakete prüfen",
"description": "Vergleiche Preise oder aktualisiere dein derzeitiges Paket.",
"button": "Zu Paketen"
},
"dashboard": {
"label": "Zum Dashboard",
"description": "Springe ins Management, um bestehende Events zu bearbeiten.",
"button": "Dashboard öffnen"
},
"events": {
"label": "Eventübersicht",
"description": "Behalte den Überblick über aktive und archivierte Events.",
"button": "Eventliste"
}
}
}
}