photobooth funktionen im event admin verlinkt, gäste pwa zeigt photobooth nur noch an, wenn diese aktiviert ist. kontaktformular optimiert. teilen-link mit iMessage und whatsapp erweitert.

This commit is contained in:
Codex Agent
2025-11-23 22:22:06 +01:00
parent 3d9eaa1194
commit df414a31cd
32 changed files with 809 additions and 280 deletions

View File

@@ -23,6 +23,7 @@ import {
ADMIN_EVENT_TASKS_PATH,
ADMIN_EVENT_TOOLKIT_PATH,
ADMIN_EVENT_VIEW_PATH,
ADMIN_EVENT_PHOTOBOOTH_PATH,
} from '../constants';
import type { TenantEvent } from '../api';
import { cn } from '@/lib/utils';
@@ -62,6 +63,7 @@ function buildEventLinks(slug: string, t: ReturnType<typeof useTranslation>['t']
return [
{ key: 'summary', label: t('eventMenu.summary', 'Übersicht'), href: ADMIN_EVENT_VIEW_PATH(slug) },
{ key: 'photos', label: t('eventMenu.photos', 'Uploads'), href: ADMIN_EVENT_PHOTOS_PATH(slug) },
{ key: 'photobooth', label: t('eventMenu.photobooth', 'Photobooth'), href: ADMIN_EVENT_PHOTOBOOTH_PATH(slug) },
{ key: 'guests', label: t('eventMenu.guests', 'Team & Gäste'), href: ADMIN_EVENT_MEMBERS_PATH(slug) },
{ key: 'tasks', label: t('eventMenu.tasks', 'Aufgaben'), href: ADMIN_EVENT_TASKS_PATH(slug) },
{ key: 'invites', label: t('eventMenu.invites', 'Einladungen'), href: ADMIN_EVENT_INVITES_PATH(slug) },

View File

@@ -13,6 +13,7 @@ import {
MessageSquare,
Printer,
QrCode,
PlugZap,
RefreshCw,
Smile,
Sparkles,
@@ -47,6 +48,7 @@ import {
ADMIN_EVENT_EDIT_PATH,
ADMIN_EVENT_INVITES_PATH,
ADMIN_EVENT_MEMBERS_PATH,
ADMIN_EVENT_PHOTOBOOTH_PATH,
ADMIN_EVENT_PHOTOS_PATH,
ADMIN_EVENT_TASKS_PATH,
} from '../constants';
@@ -590,6 +592,13 @@ function QuickActionsCard({ slug, busy, onToggle, navigate }: { slug: string; bu
description: t('events.quickActions.rolesDesc', 'Verwalte Moderatoren und Co-Leads.'),
onClick: () => navigate(ADMIN_EVENT_MEMBERS_PATH(slug)),
},
{
key: 'photobooth',
icon: <PlugZap className="h-4 w-4" />,
label: t('events.quickActions.photobooth', 'Photobooth anbinden'),
description: t('events.quickActions.photoboothDesc', 'FTP-Link aktivieren und Zugangsdaten kopieren.'),
onClick: () => navigate(ADMIN_EVENT_PHOTOBOOTH_PATH(slug)),
},
{
key: 'print',
icon: <Printer className="h-4 w-4" />,

View File

@@ -4,6 +4,7 @@ import { Camera, Loader2, Sparkles, Trash2, AlertTriangle, ShoppingCart } from '
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import toast from 'react-hot-toast';
import { AddonsPicker } from '../components/Addons/AddonsPicker';
@@ -16,7 +17,7 @@ import { isAuthError } from '../auth/tokens';
import { getApiErrorMessage } from '../lib/apiError';
import { buildLimitWarnings, type EventLimitSummary } from '../lib/limitWarnings';
import { useTranslation } from 'react-i18next';
import { ADMIN_EVENTS_PATH, ADMIN_EVENT_VIEW_PATH } from '../constants';
import { ADMIN_EVENTS_PATH, ADMIN_EVENT_VIEW_PATH, ADMIN_EVENT_PHOTOBOOTH_PATH } from '../constants';
export default function EventPhotosPage() {
const params = useParams<{ slug?: string }>();
@@ -37,6 +38,10 @@ export default function EventPhotosPage() {
const [limits, setLimits] = React.useState<EventLimitSummary | null>(null);
const [addons, setAddons] = React.useState<EventAddonCatalogItem[]>([]);
const [eventAddons, setEventAddons] = React.useState<EventAddonSummary[]>([]);
const photoboothUploads = React.useMemo(
() => photos.filter((photo) => photo.ingest_source === 'photobooth').length,
[photos],
);
const load = React.useCallback(async () => {
if (!slug) {
@@ -165,13 +170,29 @@ export default function EventPhotosPage() {
)}
<Card className="border-0 bg-white/85 shadow-xl shadow-sky-100/60">
<CardHeader>
<CardTitle className="flex items-center gap-2 text-xl text-slate-900">
<Camera className="h-5 w-5 text-sky-500" /> {t('photos.gallery.title', 'Galerie')}
</CardTitle>
<CardDescription className="text-sm text-slate-600">
{t('photos.gallery.description', 'Klick auf ein Foto, um es hervorzuheben oder zu löschen.')}
</CardDescription>
<CardHeader className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
<div>
<CardTitle className="flex items-center gap-2 text-xl text-slate-900">
<Camera className="h-5 w-5 text-sky-500" /> {t('photos.gallery.title', 'Galerie')}
</CardTitle>
<CardDescription className="text-sm text-slate-600">
{t('photos.gallery.description', 'Klick auf ein Foto, um es hervorzuheben oder zu löschen.')}
</CardDescription>
</div>
<div className="flex flex-wrap items-center gap-2">
<Badge variant="outline" className="border-sky-200 text-sky-700">
{t('photos.gallery.photoboothCount', '{{count}} Photobooth-Uploads', { count: photoboothUploads })}
</Badge>
<Button
variant="ghost"
size="sm"
onClick={() => slug && navigate(ADMIN_EVENT_PHOTOBOOTH_PATH(slug))}
disabled={!slug}
className="text-rose-600 hover:bg-rose-50"
>
{t('photos.gallery.photoboothCta', 'Photobooth-Zugang öffnen')}
</Button>
</div>
</CardHeader>
<CardContent>
{loading ? (

View File

@@ -32,6 +32,7 @@ import {
ADMIN_EVENT_TASKS_PATH,
ADMIN_EVENT_INVITES_PATH,
ADMIN_EVENT_TOOLKIT_PATH,
ADMIN_EVENT_PHOTOBOOTH_PATH,
} from '../constants';
import { buildLimitWarnings } from '../lib/limitWarnings';
import { useTranslation } from 'react-i18next';
@@ -368,6 +369,7 @@ function EventCard({
{ key: 'members', label: translate('events.list.actions.members', 'Mitglieder'), to: ADMIN_EVENT_MEMBERS_PATH(slug) },
{ key: 'tasks', label: translate('events.list.actions.tasks', 'Tasks'), to: ADMIN_EVENT_TASKS_PATH(slug) },
{ key: 'invites', label: translate('events.list.actions.invites', 'QR-Einladungen'), to: ADMIN_EVENT_INVITES_PATH(slug) },
{ key: 'photobooth', label: translate('events.list.actions.photobooth', 'Photobooth'), to: ADMIN_EVENT_PHOTOBOOTH_PATH(slug) },
{ key: 'toolkit', label: translate('events.list.actions.toolkit', 'Toolkit'), to: ADMIN_EVENT_TOOLKIT_PATH(slug) },
];