Remove sparkbooth option from photobooth UI

This commit is contained in:
Codex Agent
2026-01-12 19:50:30 +01:00
parent c76081de05
commit 27dff67b69
3 changed files with 88 additions and 128 deletions

View File

@@ -1168,15 +1168,20 @@
"mode": "Modus" "mode": "Modus"
}, },
"mode": { "mode": {
"title": "Photobooth-Typ auswählen", "title": "Uploader-Verbindung",
"description": "Wähle zwischen klassischem FTP und Sparkbooth HTTP-Upload. Umschalten generiert neue Zugangsdaten.", "description": "Nutze die Fotospiel-Uploader-App für HTTP-Uploads. Beim Zurücksetzen werden neue Zugangsdaten generiert.",
"active": "Aktuell: {{mode}}" "active": "Aktuell: {{mode}}",
"uploader": "Uploader-App (HTTP)"
},
"selector": {
"title": "Verbindung",
"description": "Nutze die Fotospiel-Uploader-App für HTTP-Uploads."
}, },
"credentials": { "credentials": {
"heading": "FTP-Zugangsdaten", "heading": "Zugangsdaten für die Uploader-App",
"description": "Teile die Zugangsdaten mit eurer Photobooth-Software.", "description": "Teile die Zugangsdaten mit der Fotospiel-Uploader-App.",
"sparkboothTitle": "Uploader-App (HTTP)", "uploaderTitle": "Uploader-App (HTTP)",
"sparkboothDescription": "Trage URL, Benutzername und Passwort in die Fotospiel-Uploader-App ein. Antworten sind JSON (optional XML).", "uploaderDescription": "Trage URL, Benutzername und Passwort in die Fotospiel-Uploader-App ein. Antworten sind JSON (optional XML).",
"host": "Host", "host": "Host",
"port": "Port", "port": "Port",
"username": "Benutzername", "username": "Benutzername",
@@ -1197,6 +1202,10 @@
"failed": "Verbindungscode konnte nicht erstellt werden." "failed": "Verbindungscode konnte nicht erstellt werden."
} }
}, },
"uploader": {
"format": "Antwort-Format",
"hint": "POST mit Mediendatei oder base64-Feld \"media\"; die App nutzt diese Zugangsdaten."
},
"actions": { "actions": {
"enable": "Photobooth aktivieren", "enable": "Photobooth aktivieren",
"disable": "Deaktivieren", "disable": "Deaktivieren",
@@ -1214,9 +1223,9 @@
"title": "Setup-Checkliste", "title": "Setup-Checkliste",
"description": "Durchlaufe die Schritte, bevor du Gästen Zugang gibst.", "description": "Durchlaufe die Schritte, bevor du Gästen Zugang gibst.",
"enable": "Zugang aktivieren", "enable": "Zugang aktivieren",
"enableCopy": "Aktiviere den FTP-Account für eure Photobooth-Software.", "enableCopy": "Aktiviere die Verbindung für die Uploader-App.",
"share": "Zugang teilen", "share": "Zugang teilen",
"shareCopy": "Übergib Host, Benutzer & Passwort an den Betreiber.", "shareCopy": "Übergib URL, Benutzername & Passwort an den Betreiber.",
"monitor": "Uploads beobachten", "monitor": "Uploads beobachten",
"monitorCopy": "Verfolge Uploads & Limits direkt im Dashboard." "monitorCopy": "Verfolge Uploads & Limits direkt im Dashboard."
}, },
@@ -1443,7 +1452,7 @@
"photobooth": { "photobooth": {
"title": "Fotobox-Uploads", "title": "Fotobox-Uploads",
"titleForEvent": "Fotobox-Uploads verwalten", "titleForEvent": "Fotobox-Uploads verwalten",
"subtitle": "Erstelle FTP-Zugänge für Photobooth-Software und behalte Limits im Blick.", "subtitle": "Erstelle Zugang für die Uploader-App und behalte Limits im Blick.",
"actions": { "actions": {
"backToEvent": "Zur Detailansicht", "backToEvent": "Zur Detailansicht",
"allEvents": "Zur Eventliste" "allEvents": "Zur Eventliste"

View File

@@ -881,15 +881,20 @@
"mode": "Mode" "mode": "Mode"
}, },
"mode": { "mode": {
"title": "Choose your photobooth type", "title": "Uploader connection",
"description": "Pick classic FTP or Sparkbooth HTTP upload. Switching regenerates credentials.", "description": "Use the Fotospiel uploader app for live HTTP uploads. Rotating access regenerates credentials.",
"active": "Current: {{mode}}" "active": "Current: {{mode}}",
"uploader": "Uploader App (HTTP)"
},
"selector": {
"title": "Connection",
"description": "Use the Fotospiel uploader app for HTTP uploads."
}, },
"credentials": { "credentials": {
"heading": "FTP credentials", "heading": "Uploader app credentials",
"description": "Share these credentials with your photobooth software.", "description": "Share these credentials with the Fotospiel uploader app.",
"sparkboothTitle": "Uploader App (HTTP)", "uploaderTitle": "Uploader App (HTTP)",
"sparkboothDescription": "Enter URL, username and password in the Fotospiel uploader app. Responses default to JSON (XML optional).", "uploaderDescription": "Enter URL, username and password in the Fotospiel uploader app. Responses default to JSON (XML optional).",
"host": "Host", "host": "Host",
"port": "Port", "port": "Port",
"username": "Username", "username": "Username",
@@ -910,6 +915,10 @@
"failed": "Connect code could not be created." "failed": "Connect code could not be created."
} }
}, },
"uploader": {
"format": "Response format",
"hint": "POST with media file or base64 \"media\" field; app uses these credentials."
},
"actions": { "actions": {
"enable": "Activate photobooth", "enable": "Activate photobooth",
"disable": "Disable", "disable": "Disable",
@@ -927,9 +936,9 @@
"title": "Setup checklist", "title": "Setup checklist",
"description": "Complete each step before guests upload.", "description": "Complete each step before guests upload.",
"enable": "Activate access", "enable": "Activate access",
"enableCopy": "Enable the FTP account in your photobooth software.", "enableCopy": "Enable the uploader app connection for this event.",
"share": "Share credentials", "share": "Share credentials",
"shareCopy": "Hand over host, user, and password to the operator.", "shareCopy": "Share URL, username, and password with the operator.",
"monitor": "Monitor uploads", "monitor": "Monitor uploads",
"monitorCopy": "Watch uploads & limits in the dashboard." "monitorCopy": "Watch uploads & limits in the dashboard."
}, },
@@ -1436,11 +1445,11 @@
"submit": "Save emotion" "submit": "Save emotion"
} }
}, },
"management": { "management": {
"photobooth": { "photobooth": {
"title": "Photobooth uploads", "title": "Photobooth uploads",
"titleForEvent": "Manage photobooth uploads", "titleForEvent": "Manage photobooth uploads",
"subtitle": "Create FTP access for photobooth software and keep limits in sight.", "subtitle": "Create uploader access for photobooth apps and keep limits in sight.",
"actions": { "actions": {
"backToEvent": "Back to detail view", "backToEvent": "Back to detail view",
"allEvents": "Back to event list" "allEvents": "Back to event list"

View File

@@ -35,7 +35,6 @@ export default function MobileEventPhotoboothPage() {
const [event, setEvent] = React.useState<TenantEvent | null>(null); const [event, setEvent] = React.useState<TenantEvent | null>(null);
const [status, setStatus] = React.useState<PhotoboothStatus | null>(null); const [status, setStatus] = React.useState<PhotoboothStatus | null>(null);
const [selectedMode, setSelectedMode] = React.useState<'ftp' | 'sparkbooth'>('ftp');
const [loading, setLoading] = React.useState(true); const [loading, setLoading] = React.useState(true);
const [updating, setUpdating] = React.useState(false); const [updating, setUpdating] = React.useState(false);
const [error, setError] = React.useState<string | null>(null); const [error, setError] = React.useState<string | null>(null);
@@ -54,7 +53,6 @@ export default function MobileEventPhotoboothPage() {
const [eventData, statusData] = await Promise.all([getEvent(slug), getEventPhotoboothStatus(slug)]); const [eventData, statusData] = await Promise.all([getEvent(slug), getEventPhotoboothStatus(slug)]);
setEvent(eventData); setEvent(eventData);
setStatus(statusData); setStatus(statusData);
setSelectedMode(statusData.mode ?? 'ftp');
} catch (err) { } catch (err) {
if (!isAuthError(err)) { if (!isAuthError(err)) {
setError(getApiErrorMessage(err, t('management.photobooth.errors.loadFailed', 'Photobooth-Link konnte nicht geladen werden.'))); setError(getApiErrorMessage(err, t('management.photobooth.errors.loadFailed', 'Photobooth-Link konnte nicht geladen werden.')));
@@ -68,20 +66,14 @@ export default function MobileEventPhotoboothPage() {
void load(); void load();
}, [load]); }, [load]);
React.useEffect(() => {
if (status?.mode) {
setSelectedMode(status.mode);
}
}, [status?.mode]);
const handleEnable = async (mode?: 'ftp' | 'sparkbooth') => { const handleEnable = async () => {
if (!slug) return; if (!slug) return;
const nextMode = mode ?? selectedMode ?? status?.mode ?? 'ftp'; const nextMode = 'sparkbooth';
setUpdating(true); setUpdating(true);
try { try {
const result = await enableEventPhotobooth(slug, { mode: nextMode }); const result = await enableEventPhotobooth(slug, { mode: nextMode });
setStatus(result); setStatus(result);
setSelectedMode(result.mode ?? nextMode);
toast.success(t('management.photobooth.actions.enable', 'Zugang aktiviert')); toast.success(t('management.photobooth.actions.enable', 'Zugang aktiviert'));
} catch (err) { } catch (err) {
if (!isAuthError(err)) { if (!isAuthError(err)) {
@@ -94,12 +86,11 @@ export default function MobileEventPhotoboothPage() {
const handleDisable = async () => { const handleDisable = async () => {
if (!slug) return; if (!slug) return;
const mode = status?.mode ?? selectedMode ?? 'ftp'; const mode = 'sparkbooth';
setUpdating(true); setUpdating(true);
try { try {
const result = await disableEventPhotobooth(slug, { mode }); const result = await disableEventPhotobooth(slug, { mode });
setStatus(result); setStatus(result);
setSelectedMode(result.mode ?? mode);
toast.success(t('management.photobooth.actions.disable', 'Zugang deaktiviert')); toast.success(t('management.photobooth.actions.disable', 'Zugang deaktiviert'));
} catch (err) { } catch (err) {
if (!isAuthError(err)) { if (!isAuthError(err)) {
@@ -112,12 +103,11 @@ export default function MobileEventPhotoboothPage() {
const handleRotate = async () => { const handleRotate = async () => {
if (!slug) return; if (!slug) return;
const mode = selectedMode ?? status?.mode ?? 'ftp'; const mode = 'sparkbooth';
setUpdating(true); setUpdating(true);
try { try {
const result = await rotateEventPhotobooth(slug, { mode }); const result = await rotateEventPhotobooth(slug, { mode });
setStatus(result); setStatus(result);
setSelectedMode(result.mode ?? mode);
toast.success(t('management.photobooth.presets.actions.rotate', 'Zugang zurückgesetzt')); toast.success(t('management.photobooth.presets.actions.rotate', 'Zugang zurückgesetzt'));
} catch (err) { } catch (err) {
if (!isAuthError(err)) { if (!isAuthError(err)) {
@@ -147,26 +137,18 @@ export default function MobileEventPhotoboothPage() {
} }
}; };
const activeMode = selectedMode ?? status?.mode ?? 'ftp';
const isSpark = activeMode === 'sparkbooth';
const spark = status?.sparkbooth ?? null; const spark = status?.sparkbooth ?? null;
const ftp = status?.ftp ?? null; const metrics = spark?.metrics ?? null;
const metrics = isSpark ? spark?.metrics ?? null : status?.metrics ?? null; const expiresAt = spark?.expires_at ?? status?.expires_at;
const expiresAt = isSpark ? spark?.expires_at ?? status?.expires_at : status?.expires_at ?? spark?.expires_at;
const lastUploadAt = metrics?.last_upload_at; const lastUploadAt = metrics?.last_upload_at;
const uploads24h = metrics?.uploads_24h ?? metrics?.uploads_today; const uploads24h = metrics?.uploads_24h ?? metrics?.uploads_today;
const uploadsTotal = metrics?.uploads_total; const uploadsTotal = metrics?.uploads_total;
const connectionPath = status?.path ?? '—'; const uploadUrl = spark?.upload_url ?? status?.upload_url;
const ftpUrl = status?.ftp_url ?? '—';
const uploadUrl = isSpark ? spark?.upload_url ?? status?.upload_url : null;
const responseFormat = spark?.response_format ?? 'json'; const responseFormat = spark?.response_format ?? 'json';
const username = isSpark ? spark?.username ?? status?.username : status?.username ?? spark?.username ?? null; const username = spark?.username ?? status?.username ?? null;
const password = isSpark ? spark?.password ?? status?.password : status?.password ?? spark?.password ?? null; const password = spark?.password ?? status?.password ?? null;
const modeLabel = const modeLabel = t('photobooth.mode.uploader', 'Uploader App (HTTP)');
activeMode === 'sparkbooth'
? t('photobooth.credentials.sparkboothTitle', 'Uploader App (HTTP)')
: t('photobooth.credentials.heading', 'FTP (Classic)');
const isActive = Boolean(status?.enabled); const isActive = Boolean(status?.enabled);
const title = t('photobooth.title', 'Photobooth'); const title = t('photobooth.title', 'Photobooth');
@@ -174,7 +156,7 @@ export default function MobileEventPhotoboothPage() {
const handleToggle = (checked: boolean) => { const handleToggle = (checked: boolean) => {
if (!slug || updating) return; if (!slug || updating) return;
if (checked) { if (checked) {
void handleEnable(status?.mode ?? 'ftp'); void handleEnable();
} else { } else {
void handleDisable(); void handleDisable();
} }
@@ -262,93 +244,53 @@ export default function MobileEventPhotoboothPage() {
<MobileCard space="$2"> <MobileCard space="$2">
<Text fontSize="$sm" fontWeight="700" color={text}> <Text fontSize="$sm" fontWeight="700" color={text}>
{t('photobooth.selector.title', 'Choose adapter')} {t('photobooth.selector.title', 'Connection')}
</Text> </Text>
<Text fontSize="$xs" color={muted}> <Text fontSize="$xs" color={muted}>
{t( {t('photobooth.selector.description', 'Use the Fotospiel uploader app for HTTP uploads.')}
'photobooth.selector.description',
'FTP (Classic) works with most booths. The Uploader App uses HTTP POST without FTP.'
)}
</Text> </Text>
<XStack space="$2" marginTop="$2" flexWrap="nowrap">
<XStack flex={1} minWidth={0}>
<CTAButton
label={t('photobooth.mode.ftp', 'FTP (Classic)')}
tone={activeMode === 'ftp' ? 'primary' : 'ghost'}
onPress={() => setSelectedMode('ftp')}
disabled={updating}
style={{ width: '100%', paddingHorizontal: 10, paddingVertical: 10 }}
/>
</XStack>
<XStack flex={1} minWidth={0}>
<CTAButton
label={t('photobooth.mode.sparkbooth', 'Uploader App (HTTP)')}
tone={activeMode === 'sparkbooth' ? 'primary' : 'ghost'}
onPress={() => setSelectedMode('sparkbooth')}
disabled={updating}
style={{ width: '100%', paddingHorizontal: 10, paddingVertical: 10 }}
/>
</XStack>
</XStack>
</MobileCard> </MobileCard>
<MobileCard space="$2"> <MobileCard space="$2">
<XStack alignItems="center" justifyContent="space-between"> <XStack alignItems="center" justifyContent="space-between">
<Text fontSize="$sm" fontWeight="700" color={text}> <Text fontSize="$sm" fontWeight="700" color={text}>
{isSpark ? t('photobooth.credentials.sparkboothTitle', 'Uploader App (HTTP)') : t('photobooth.credentials.heading', 'FTP credentials')} {t('photobooth.credentials.uploaderTitle', 'Uploader App (HTTP)')}
</Text> </Text>
{!isSpark && ftp?.require_ftps ? <PillBadge tone="warning">{t('photobooth.credentials.ftps', 'FTPS required')}</PillBadge> : null}
</XStack> </XStack>
<YStack space="$1"> <YStack space="$1">
{isSpark ? ( <CredentialRow label={t('photobooth.credentials.postUrl', 'Upload URL')} value={uploadUrl ?? '—'} border={border} />
<> <CredentialRow label={t('photobooth.credentials.username', 'Username')} value={username ?? '—'} border={border} />
<CredentialRow label={t('photobooth.credentials.postUrl', 'Upload URL')} value={uploadUrl ?? '—'} border={border} /> <CredentialRow label={t('photobooth.credentials.password', 'Password')} value={password ?? '—'} border={border} masked />
<CredentialRow label={t('photobooth.credentials.username', 'Username')} value={username ?? '—'} border={border} /> <CredentialRow label={t('photobooth.uploader.format', 'Response format')} value={responseFormat.toUpperCase()} border={border} />
<CredentialRow label={t('photobooth.credentials.password', 'Password')} value={password ?? '—'} border={border} masked /> <Text fontSize="$xs" color={muted}>
<CredentialRow label={t('photobooth.sparkbooth.format', 'Response format')} value={responseFormat.toUpperCase()} border={border} /> {t('photobooth.uploader.hint', 'POST with media file or base64 "media" field; app uses these credentials.')}
</Text>
<YStack space="$2" marginTop="$2">
<Text fontSize="$xs" color={muted}>
{t('photobooth.connectCode.description', 'Create a 6-digit code for the uploader app.')}
</Text>
<CTAButton
label={
connectLoading
? t('common.processing', '...')
: t('photobooth.connectCode.actions.generate', 'Generate connect code')
}
onPress={handleGenerateConnectCode}
iconLeft={<PlugZap size={14} color={surface} />}
disabled={!isActive || updating || connectLoading}
style={{ width: '100%', paddingHorizontal: 10, paddingVertical: 10 }}
/>
{connectCode ? (
<CredentialRow label={t('photobooth.connectCode.label', 'Connect code')} value={connectCode} border={border} />
) : null}
{connectExpiresAt ? (
<Text fontSize="$xs" color={muted}> <Text fontSize="$xs" color={muted}>
{t('photobooth.sparkbooth.hint', 'POST with media file or base64 "media" field; app uses these credentials.')} {t('photobooth.connectCode.expires', 'Expires: {{date}}', {
date: formatEventDate(connectExpiresAt, locale),
})}
</Text> </Text>
<YStack space="$2" marginTop="$2"> ) : null}
<Text fontSize="$xs" color={muted}> </YStack>
{t('photobooth.connectCode.description', 'Create a 6-digit code for the uploader app.')}
</Text>
<CTAButton
label={
connectLoading
? t('common.processing', '...')
: t('photobooth.connectCode.actions.generate', 'Generate connect code')
}
onPress={handleGenerateConnectCode}
iconLeft={<PlugZap size={14} color={surface} />}
disabled={!isActive || updating || connectLoading}
style={{ width: '100%', paddingHorizontal: 10, paddingVertical: 10 }}
/>
{connectCode ? (
<CredentialRow label={t('photobooth.connectCode.label', 'Connect code')} value={connectCode} border={border} />
) : null}
{connectExpiresAt ? (
<Text fontSize="$xs" color={muted}>
{t('photobooth.connectCode.expires', 'Expires: {{date}}', {
date: formatEventDate(connectExpiresAt, locale),
})}
</Text>
) : null}
</YStack>
</>
) : (
<>
<CredentialRow label={t('photobooth.credentials.host', 'Host')} value={ftp?.host ?? '—'} border={border} />
<CredentialRow label={t('photobooth.credentials.port', 'Port')} value={String(ftp?.port ?? '—')} border={border} />
<CredentialRow label={t('photobooth.credentials.path', 'Target folder')} value={connectionPath} border={border} />
<CredentialRow label={t('photobooth.credentials.postUrl', 'FTP URL')} value={ftpUrl} border={border} />
<CredentialRow label={t('photobooth.credentials.username', 'Username')} value={username ?? '—'} border={border} />
<CredentialRow label={t('photobooth.credentials.password', 'Password')} value={password ?? '—'} border={border} masked />
<Text fontSize="$xs" color={muted}>
{t('photobooth.credentials.ftpsHint', 'Use FTPS if required; uploads go into the target folder for this event.')}
</Text>
</>
)}
</YStack> </YStack>
<XStack space="$2" marginTop="$2" flexWrap="wrap"> <XStack space="$2" marginTop="$2" flexWrap="wrap">
<XStack flex={1} minWidth={0}> <XStack flex={1} minWidth={0}>
@@ -363,7 +305,7 @@ export default function MobileEventPhotoboothPage() {
<XStack flex={1} minWidth={0}> <XStack flex={1} minWidth={0}>
<CTAButton <CTAButton
label={isActive ? t('photobooth.actions.disable', 'Disable uploads') : t('photobooth.actions.enable', 'Enable uploads')} label={isActive ? t('photobooth.actions.disable', 'Disable uploads') : t('photobooth.actions.enable', 'Enable uploads')}
onPress={() => (isActive ? handleDisable() : handleEnable(selectedMode))} onPress={() => (isActive ? handleDisable() : handleEnable())}
tone={isActive ? 'ghost' : 'primary'} tone={isActive ? 'ghost' : 'primary'}
iconLeft={isActive ? <Power size={14} color={text} /> : <PlugZap size={14} color={surface} />} iconLeft={isActive ? <Power size={14} color={text} /> : <PlugZap size={14} color={surface} />}
disabled={updating} disabled={updating}