import React from 'react'; import { useNavigate, useParams } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { RefreshCcw, PlugZap, RefreshCw, ShieldCheck, Copy, Power, Clock3 } from 'lucide-react'; import { YStack, XStack } from '@tamagui/stacks'; import { SizableText as Text } from '@tamagui/text'; import { Pressable } from '@tamagui/react-native-web-lite'; import { useTheme } from '@tamagui/core'; import { MobileShell } from './components/MobileShell'; import { MobileCard, CTAButton, PillBadge } from './components/Primitives'; import { getEvent, getEventPhotoboothStatus, enableEventPhotobooth, disableEventPhotobooth, rotateEventPhotobooth, PhotoboothStatus, TenantEvent, } from '../api'; import { isAuthError } from '../auth/tokens'; import { getApiErrorMessage } from '../lib/apiError'; import { formatEventDate, resolveEventDisplayName } from '../lib/events'; import toast from 'react-hot-toast'; export default function MobileEventPhotoboothPage() { const { slug: slugParam } = useParams<{ slug?: string }>(); const slug = slugParam ?? null; const navigate = useNavigate(); const { t, i18n } = useTranslation('management'); const theme = useTheme(); const text = String(theme.color?.val ?? '#111827'); const muted = String(theme.gray?.val ?? '#4b5563'); const border = String(theme.borderColor?.val ?? '#e5e7eb'); const surface = String(theme.surface?.val ?? '#ffffff'); const [event, setEvent] = React.useState(null); const [status, setStatus] = React.useState(null); const [loading, setLoading] = React.useState(true); const [updating, setUpdating] = React.useState(false); const [error, setError] = React.useState(null); const locale = i18n.language?.startsWith('en') ? 'en-GB' : 'de-DE'; const load = React.useCallback(async () => { if (!slug) return; setLoading(true); setError(null); try { const [eventData, statusData] = await Promise.all([getEvent(slug), getEventPhotoboothStatus(slug)]); setEvent(eventData); setStatus(statusData); } catch (err) { if (!isAuthError(err)) { setError(getApiErrorMessage(err, t('management.photobooth.errors.loadFailed', 'Photobooth-Link konnte nicht geladen werden.'))); } } finally { setLoading(false); } }, [slug, t]); React.useEffect(() => { void load(); }, [load]); const handleEnable = async (mode?: 'ftp' | 'sparkbooth') => { if (!slug) return; setUpdating(true); try { const result = await enableEventPhotobooth(slug, { mode: mode ?? status?.mode ?? 'ftp' }); setStatus(result); toast.success(t('management.photobooth.actions.enable', 'Zugang aktiviert')); } catch (err) { if (!isAuthError(err)) { toast.error(getApiErrorMessage(err, t('management.photobooth.errors.enableFailed', 'Zugang konnte nicht aktiviert werden.'))); } } finally { setUpdating(false); } }; const handleDisable = async () => { if (!slug) return; setUpdating(true); try { const result = await disableEventPhotobooth(slug, { mode: status?.mode ?? 'ftp' }); setStatus(result); toast.success(t('management.photobooth.actions.disable', 'Zugang deaktiviert')); } catch (err) { if (!isAuthError(err)) { toast.error(getApiErrorMessage(err, t('management.photobooth.errors.disableFailed', 'Zugang konnte nicht deaktiviert werden.'))); } } finally { setUpdating(false); } }; const handleRotate = async () => { if (!slug) return; setUpdating(true); try { const result = await rotateEventPhotobooth(slug, { mode: status?.mode ?? 'ftp' }); setStatus(result); toast.success(t('management.photobooth.presets.actions.rotate', 'Zugang zurückgesetzt')); } catch (err) { if (!isAuthError(err)) { toast.error(getApiErrorMessage(err, t('management.photobooth.errors.rotateFailed', 'Zugangsdaten konnten nicht neu generiert werden.'))); } } finally { setUpdating(false); } }; const modeLabel = status?.mode === 'sparkbooth' ? t('photobooth.credentials.sparkboothTitle', 'Sparkbooth / HTTP') : t('photobooth.credentials.heading', 'FTP (Classic)'); const isActive = Boolean(status?.enabled); const title = event ? resolveEventDisplayName(event) : t('management.header.appName', 'Event Admin'); const subtitle = event?.event_date ? formatEventDate(event.event_date, locale) : t('header.selectEvent', 'Select an event to continue'); return ( navigate(-1)} headerActions={ load()}> } > {error ? ( {error} ) : null} {loading ? ( {Array.from({ length: 3 }).map((_, idx) => ( ))} ) : ( {t('photobooth.title', 'Photobooth')} {t('photobooth.credentials.description', 'Share these credentials with your photobooth software.')} {t('photobooth.mode.active', 'Current: {{mode}}', { mode: modeLabel })} {isActive ? t('photobooth.status.badgeActive', 'ACTIVE') : t('photobooth.status.badgeInactive', 'INACTIVE')} handleEnable('ftp')} disabled={updating} style={{ width: '100%', paddingHorizontal: 10, paddingVertical: 10 }} /> handleEnable('sparkbooth')} disabled={updating} style={{ width: '100%', paddingHorizontal: 10, paddingVertical: 10 }} /> {t('photobooth.credentials.heading', 'FTP credentials')} {status?.upload_url ? : null} handleRotate()} iconLeft={} style={{ width: '100%', paddingHorizontal: 10, paddingVertical: 10 }} /> (isActive ? handleDisable() : handleEnable())} tone={isActive ? 'ghost' : 'primary'} iconLeft={isActive ? : } disabled={updating} style={{ width: '100%', paddingHorizontal: 10, paddingVertical: 10 }} /> {t('photobooth.status.heading', 'Status')} } label={t('photobooth.status.mode', 'Mode')} value={modeLabel} /> } label={t('photobooth.status.heading', 'Status')} value={isActive ? t('common.enabled', 'Enabled') : t('common.disabled', 'Disabled')} /> {status?.metrics?.uploads_last_hour != null ? ( } label={t('photobooth.rateLimit.usage', 'Uploads last hour')} value={String(status.metrics.uploads_last_hour)} /> ) : null} {status?.metrics?.last_upload_at ? ( } label={t('photobooth.stats.lastUpload', 'Letzter Upload')} value={formatEventDate(status.metrics.last_upload_at, locale) ?? '—'} /> ) : null} )} ); } function CredentialRow({ label, value, border, masked }: { label: string; value: string; border: string; masked?: boolean }) { const { t } = useTranslation('management'); return ( {label} {masked ? '••••••••' : value} { try { await navigator.clipboard.writeText(value); toast.success(t('common.copied', 'Kopiert')); } catch { toast.error(t('common.copyFailed', 'Kopieren fehlgeschlagen')); } }} > ); } function StatusRow({ icon, label, value }: { icon: React.ReactNode; label: string; value: string }) { return ( {icon} {label} {value} ); }