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

@@ -1,6 +1,8 @@
import React from "react";
import React from "react";
import { useNavigate } from "react-router-dom";
import { useTranslation } from "react-i18next";
import { Sparkles, Users, Camera, CalendarDays, ChevronRight } from "lucide-react";
import {
TenantWelcomeLayout,
WelcomeHero,
@@ -13,6 +15,7 @@ import { ADMIN_WELCOME_PACKAGES_PATH, ADMIN_EVENTS_PATH } from "../../constants"
export default function WelcomeLandingPage() {
const navigate = useNavigate();
const { markStep } = useOnboardingProgress();
const { t } = useTranslation("onboarding");
React.useEffect(() => {
markStep({ welcomeSeen: true, lastStep: "landing" });
@@ -20,38 +23,36 @@ export default function WelcomeLandingPage() {
return (
<TenantWelcomeLayout
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."
eyebrow={t("layout.eyebrow")}
title={t("layout.title")}
subtitle={t("layout.subtitle")}
footer={
<>
<span className="text-brand-navy/80">Schon vertraut mit Fotospiel?</span>
<span className="text-brand-navy/80">{t("layout.alreadyFamiliar")}</span>
<button
type="button"
className="inline-flex items-center gap-1 text-sm font-semibold text-brand-rose hover:text-[var(--brand-rose-strong)]"
onClick={() => navigate(ADMIN_EVENTS_PATH)}
>
Direkt zum Dashboard
{t("layout.jumpToDashboard")}
<ChevronRight className="size-4" />
</button>
</>
}
>
<WelcomeHero
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."
eyebrow={t("hero.eyebrow")}
title={t("hero.title")}
scriptTitle={t("hero.scriptTitle")}
description={t("hero.description")}
actions={[
{
label: "Pakete entdecken",
buttonLabel: "Pakete entdecken",
label: t("hero.primary.label"),
onClick: () => navigate(ADMIN_WELCOME_PACKAGES_PATH),
icon: Sparkles,
},
{
label: "Events anzeigen",
buttonLabel: "Bestehende Events anzeigen",
label: t("hero.secondary.label"),
onClick: () => navigate(ADMIN_EVENTS_PATH),
icon: CalendarDays,
variant: "outline",
@@ -64,24 +65,21 @@ export default function WelcomeLandingPage() {
{
id: "gallery",
icon: Camera,
title: "Premium Gästegalerie",
description:
"Kuratiere Fotos in Echtzeit, markiere Highlights und teile QR-Codes mit einem Tap.",
badge: "Neu",
title: t("highlights.gallery.title"),
description: t("highlights.gallery.description"),
badge: t("highlights.gallery.badge"),
},
{
id: "team",
icon: Users,
title: "Flexibles Team-Onboarding",
description:
"Lade Co-Hosts ein, weise Rollen zu und behalte den Überblick über Moderation und Aufgaben.",
title: t("highlights.team.title"),
description: t("highlights.team.description"),
},
{
id: "sparkles",
id: "story",
icon: Sparkles,
title: "Storytelling in Etappen",
description:
"Geführte Aufgaben und Emotionskarten machen jedes Event zu einer erinnerungswürdigen Reise.",
title: t("highlights.story.title"),
description: t("highlights.story.description"),
},
]}
/>
@@ -90,21 +88,19 @@ export default function WelcomeLandingPage() {
actions={[
{
id: "choose-package",
label: "Dein Eventpaket auswählen",
description:
"Reserviere Credits oder Abos, um sofort Events zu aktivieren. Flexible Optionen für jede Eventgröße.",
label: t("ctaList.choosePackage.label"),
description: t("ctaList.choosePackage.description"),
onClick: () => navigate(ADMIN_WELCOME_PACKAGES_PATH),
icon: Sparkles,
buttonLabel: "Weiter zu Paketen",
buttonLabel: t("ctaList.choosePackage.button"),
},
{
id: "create-event",
label: "Event vorbereiten",
description:
"Sammle Eventdetails, plane Aufgaben und sorge für einen reibungslosen Ablauf noch vor dem Tag des Events.",
label: t("ctaList.createEvent.label"),
description: t("ctaList.createEvent.description"),
onClick: () => navigate(ADMIN_EVENTS_PATH),
icon: CalendarDays,
buttonLabel: "Zum Event-Manager",
buttonLabel: t("ctaList.createEvent.button"),
variant: "secondary",
},
]}