events werden nun erfolgreich gespeichert, branding wird nun erfolgreich gespeichert, emotionen können nun angelegt werden. Task Ansicht im Event admin verbessert, Buttons in FAB umgewandelt und vereinheitlicht. Teilen-Link Guest PWA schicker gemacht, SynGoogleFonts ausgebaut (mit Einzel-Family-Download).

This commit is contained in:
Codex Agent
2025-11-27 16:08:08 +01:00
parent bfa15cc48e
commit 96f8c5d63c
39 changed files with 1970 additions and 640 deletions

View File

@@ -208,7 +208,7 @@ export default function EventDetailPage() {
const toolkitData = toolkit.data;
const eventName = event ? resolveName(event.name) : t('events.placeholders.untitled', 'Unbenanntes Event');
const subtitle = t('events.workspace.detailSubtitle', 'Behalte Status, Aufgaben und Einladungen deines Events im Blick.');
const subtitle = t('events.workspace.detailSubtitle', 'Behalte Status, Aufgaben und QR-Codes deines Events im Blick.');
const currentTabKey = 'overview';
const eventTabs = React.useMemo(() => {
@@ -221,6 +221,11 @@ export default function EventDetailPage() {
return buildEventTabs(event, translateMenu, counts);
}, [event, stats?.uploads_total, toolkitData?.tasks?.summary?.total, t]);
const brandingAllowed = React.useMemo(() => {
const settings = (event?.settings ?? {}) as Record<string, unknown>;
return Boolean(settings.branding_allowed ?? true);
}, [event]);
const limitWarnings = React.useMemo(
() => (event?.limits ? buildLimitWarnings(event.limits, (key, options) => tCommon(`limits.${key}`, options)) : []),
[event?.limits, tCommon],
@@ -449,7 +454,7 @@ const shownWarningToasts = React.useRef<Set<string>>(new Set());
</p>
<h1 className="text-xl font-semibold text-slate-900 dark:text-white">{resolveName(event.name)}</h1>
<p className="text-sm text-slate-600 dark:text-slate-300">
{t('events.workspace.hero.description', 'Konzentriere dich auf Aufgaben, Moderation und Einladungen für dieses Event.')}
{t('events.workspace.hero.description', 'Konzentriere dich auf Aufgaben, Moderation und QR-Code für dieses Event.')}
</p>
<div className="flex flex-wrap gap-2 text-xs">
<Badge variant="secondary" className="gap-1 rounded-full bg-emerald-100 text-emerald-800 dark:bg-emerald-500/20 dark:text-emerald-100">
@@ -503,6 +508,7 @@ const shownWarningToasts = React.useRef<Set<string>>(new Set());
navigateToInvites={() => navigate(`${ADMIN_EVENT_INVITES_PATH(event.slug)}?tab=layout`)}
/>
</div>
{brandingAllowed ? (
<BrandingMissionCard
event={event}
invites={toolkitData?.invites}
@@ -512,6 +518,7 @@ const shownWarningToasts = React.useRef<Set<string>>(new Set());
onOpenTasks={() => navigate(ADMIN_EVENT_TASKS_PATH(event.slug))}
onOpenEmotions={() => navigate(buildEngagementTabPath('emotions'))}
/>
) : null}
{event.addons?.length ? (
<SectionCard>
<SectionHeader
@@ -758,9 +765,9 @@ function InviteSummary({ invites, navigateToInvites }: { invites: EventToolkit['
return (
<SectionCard className="space-y-3">
<SectionHeader
eyebrow={t('events.invites.badge', 'Einladungen')}
title={t('events.invites.title', 'QR-Einladungen')}
description={t('events.invites.subtitle', 'Behält aktive Einladungen und Layouts im Blick.')}
eyebrow={t('events.invites.badge', 'QR-Codes')}
title={t('events.invites.title', 'QR-Codes & Layouts')}
description={t('events.invites.subtitle', 'Behält aktive QR-Codes und Layouts im Blick.')}
/>
<div className="space-y-3 text-sm text-slate-700 dark:text-slate-300">
<div className="flex gap-2 text-sm text-slate-900">
@@ -782,11 +789,11 @@ function InviteSummary({ invites, navigateToInvites }: { invites: EventToolkit['
))}
</ul>
) : (
<p className="text-xs text-slate-500">{t('events.invites.empty', 'Noch keine Einladungen erstellt.')}</p>
<p className="text-xs text-slate-500">{t('events.invites.empty', 'Noch keine QR-Codes erstellt.')}</p>
)}
<Button variant="outline" onClick={navigateToInvites} className="border-amber-200 text-amber-700 hover:bg-amber-50">
<QrCode className="mr-2 h-4 w-4" /> {t('events.invites.manage', 'Layouts & Einladungen verwalten')}
<QrCode className="mr-2 h-4 w-4" /> {t('events.invites.manage', 'QR-Codes & Layouts verwalten')}
</Button>
</div>
</SectionCard>
@@ -999,10 +1006,10 @@ function GalleryShareCard({
<SectionHeader
eyebrow={t('events.galleryShare.badge', 'Galerie')}
title={t('events.galleryShare.title', 'Galerie teilen')}
description={t('events.galleryShare.emptyDescription', 'Erstelle einen Einladungslink, um Fotos zu teilen.')}
description={t('events.galleryShare.emptyDescription', 'Erstelle einen QR-Codeslink, um Fotos zu teilen.')}
/>
<Button onClick={onManageInvites} className="w-full rounded-full bg-brand-rose text-white shadow-md shadow-rose-300/40">
{t('events.galleryShare.createInvite', 'Einladung erstellen')}
{t('events.galleryShare.createInvite', 'QR-Code erstellen')}
</Button>
</SectionCard>
);