import React from 'react'; import { useNavigate, useParams } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { AlertCircle, ArrowLeft, Loader2, PlugZap, Power, RefreshCw, ShieldCheck, Copy } from 'lucide-react'; import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { AdminLayout } from '../components/AdminLayout'; import { PhotoboothStatus, TenantEvent, disableEventPhotobooth, enableEventPhotobooth, getEvent, getEventPhotoboothStatus, rotateEventPhotobooth, } from '../api'; import { isAuthError } from '../auth/tokens'; import { getApiErrorMessage } from '../lib/apiError'; import { ADMIN_EVENTS_PATH, ADMIN_EVENT_VIEW_PATH } from '../constants'; type State = { event: TenantEvent | null; status: PhotoboothStatus | null; loading: boolean; updating: boolean; error: string | null; }; export default function EventPhotoboothPage() { const { slug } = useParams<{ slug: string }>(); const navigate = useNavigate(); const { t } = useTranslation(['management', 'common']); const [state, setState] = React.useState({ event: null, status: null, loading: true, updating: false, error: null, }); const load = React.useCallback(async () => { if (!slug) { setState((prev) => ({ ...prev, loading: false, error: t('management.photobooth.errors.missingSlug', 'Kein Event ausgewählt.'), })); return; } setState((prev) => ({ ...prev, loading: true, error: null })); try { const [eventData, statusData] = await Promise.all([getEvent(slug), getEventPhotoboothStatus(slug)]); setState({ event: eventData, status: statusData, loading: false, updating: false, error: null, }); } catch (error) { if (!isAuthError(error)) { setState((prev) => ({ ...prev, loading: false, error: getApiErrorMessage(error, t('management.photobooth.errors.loadFailed', 'Photobooth-Link konnte nicht geladen werden.')), })); } else { setState((prev) => ({ ...prev, loading: false })); } } }, [slug, t]); React.useEffect(() => { void load(); }, [load]); async function handleEnable(): Promise { if (!slug) return; setState((prev) => ({ ...prev, updating: true, error: null })); try { const result = await enableEventPhotobooth(slug); setState((prev) => ({ ...prev, status: result, updating: false, })); } catch (error) { if (!isAuthError(error)) { setState((prev) => ({ ...prev, updating: false, error: getApiErrorMessage(error, t('management.photobooth.errors.enableFailed', 'Zugang konnte nicht aktiviert werden.')), })); } else { setState((prev) => ({ ...prev, updating: false })); } } } async function handleRotate(): Promise { if (!slug) return; setState((prev) => ({ ...prev, updating: true, error: null })); try { const result = await rotateEventPhotobooth(slug); setState((prev) => ({ ...prev, status: result, updating: false, })); } catch (error) { if (!isAuthError(error)) { setState((prev) => ({ ...prev, updating: false, error: getApiErrorMessage(error, t('management.photobooth.errors.rotateFailed', 'Zugangsdaten konnten nicht neu generiert werden.')), })); } else { setState((prev) => ({ ...prev, updating: false })); } } } async function handleDisable(): Promise { if (!slug) return; if (!window.confirm(t('management.photobooth.confirm.disable', 'Photobooth-Zugang deaktivieren?'))) { return; } setState((prev) => ({ ...prev, updating: true, error: null })); try { const result = await disableEventPhotobooth(slug); setState((prev) => ({ ...prev, status: result, updating: false, })); } catch (error) { if (!isAuthError(error)) { setState((prev) => ({ ...prev, updating: false, error: getApiErrorMessage(error, t('management.photobooth.errors.disableFailed', 'Zugang konnte nicht deaktiviert werden.')), })); } else { setState((prev) => ({ ...prev, updating: false })); } } } const { event, status, loading, updating, error } = state; const title = event ? t('management.photobooth.titleForEvent', { defaultValue: 'Fotobox-Uploads verwalten', event: resolveEventName(event.name) }) : t('management.photobooth.title', 'Fotobox-Uploads'); const subtitle = t( 'management.photobooth.subtitle', 'Erstelle einen einfachen FTP-Link für Photobooth-Software. Rate-Limit: 20 Fotos/Minute.' ); const actions = (
{slug ? ( ) : null}
); return ( {error ? ( {t('common:messages.error', 'Fehler')} {error} ) : null} {loading ? ( ) : (
)}
); } function resolveEventName(name: TenantEvent['name']): string { if (typeof name === 'string') { return name; } if (name && typeof name === 'object') { return Object.values(name)[0] ?? 'Event'; } return 'Event'; } function PhotoboothSkeleton() { return (
{Array.from({ length: 3 }).map((_, idx) => (
))}
); } function StatusCard({ status }: { status: PhotoboothStatus | null }) { const { t } = useTranslation('management'); const isActive = Boolean(status?.enabled); const badgeColor = isActive ? 'bg-emerald-600 text-white' : 'bg-slate-300 text-slate-800'; const icon = isActive ? : ; return (
{t('photobooth.status.heading', 'Status')} {isActive ? t('photobooth.status.active', 'Photobooth-Link ist aktiv.') : t('photobooth.status.inactive', 'Noch keine Photobooth-Uploads angebunden.')}
{icon} {isActive ? t('photobooth.status.badgeActive', 'AKTIV') : t('photobooth.status.badgeInactive', 'INAKTIV')}
{status?.expires_at ? ( {t('photobooth.status.expiresAt', 'Automatisches Abschalten am {{date}}', { date: new Date(status.expires_at).toLocaleString(), })} ) : null}
); } type CredentialCardProps = { status: PhotoboothStatus | null; updating: boolean; onEnable: () => Promise; onRotate: () => Promise; onDisable: () => Promise; }; function CredentialsCard({ status, updating, onEnable, onRotate, onDisable }: CredentialCardProps) { const { t } = useTranslation('management'); const isActive = Boolean(status?.enabled); return ( {t('photobooth.credentials.heading', 'FTP-Zugangsdaten')} {t( 'photobooth.credentials.description', 'Teile die Zugangsdaten mit der Photobooth-Software. Passwörter werden max. 8 Zeichen lang generiert.' )}
{isActive ? ( <> ) : ( )}
); } function RateLimitCard({ status }: { status: PhotoboothStatus | null }) { const { t } = useTranslation('management'); const rateLimit = status?.rate_limit_per_minute ?? 20; return (
{t('photobooth.rateLimit.heading', 'Sicherheit & Limits')} {t('photobooth.rateLimit.description', 'Uploads werden strikt auf {{count}} Fotos pro Minute begrenzt.', { count: rateLimit, })}

{t( 'photobooth.rateLimit.body', 'Bei Überschreitung wird die Verbindung hart geblockt. Nach 60 Sekunden wird der Zugang automatisch wieder freigegeben.' )}

{t( 'photobooth.rateLimit.hint', 'Ablaufzeit stimmt mit dem Event-Ende überein. Nach Ablauf wird der Account automatisch entfernt.' )}

); } type FieldProps = { label: string; value: string; copyable?: boolean; sensitive?: boolean; className?: string; }; function Field({ label, value, copyable, sensitive, className }: FieldProps) { const [copied, setCopied] = React.useState(false); const showValue = sensitive && value && value !== '—' ? '•'.repeat(Math.min(6, value.length)) : value; async function handleCopy() { if (!copyable || !value || value === '—') return; await navigator.clipboard.writeText(value); setCopied(true); setTimeout(() => setCopied(false), 1500); } return (

{label}

{showValue} {copyable ? ( ) : null}
); }