Remove sparkbooth option from photobooth UI
Some checks failed
linter / quality (push) Has been cancelled
tests / ci (push) Has been cancelled
tests / ui (push) Has been cancelled

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

View File

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

View File

@@ -881,15 +881,20 @@
"mode": "Mode"
},
"mode": {
"title": "Choose your photobooth type",
"description": "Pick classic FTP or Sparkbooth HTTP upload. Switching regenerates credentials.",
"active": "Current: {{mode}}"
"title": "Uploader connection",
"description": "Use the Fotospiel uploader app for live HTTP uploads. Rotating access regenerates credentials.",
"active": "Current: {{mode}}",
"uploader": "Uploader App (HTTP)"
},
"selector": {
"title": "Connection",
"description": "Use the Fotospiel uploader app for HTTP uploads."
},
"credentials": {
"heading": "FTP credentials",
"description": "Share these credentials with your photobooth software.",
"sparkboothTitle": "Uploader App (HTTP)",
"sparkboothDescription": "Enter URL, username and password in the Fotospiel uploader app. Responses default to JSON (XML optional).",
"heading": "Uploader app credentials",
"description": "Share these credentials with the Fotospiel uploader app.",
"uploaderTitle": "Uploader App (HTTP)",
"uploaderDescription": "Enter URL, username and password in the Fotospiel uploader app. Responses default to JSON (XML optional).",
"host": "Host",
"port": "Port",
"username": "Username",
@@ -910,6 +915,10 @@
"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": {
"enable": "Activate photobooth",
"disable": "Disable",
@@ -927,9 +936,9 @@
"title": "Setup checklist",
"description": "Complete each step before guests upload.",
"enable": "Activate access",
"enableCopy": "Enable the FTP account in your photobooth software.",
"enableCopy": "Enable the uploader app connection for this event.",
"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",
"monitorCopy": "Watch uploads & limits in the dashboard."
},
@@ -1436,11 +1445,11 @@
"submit": "Save emotion"
}
},
"management": {
"photobooth": {
"title": "Photobooth uploads",
"titleForEvent": "Manage photobooth uploads",
"subtitle": "Create FTP access for photobooth software and keep limits in sight.",
"management": {
"photobooth": {
"title": "Photobooth uploads",
"titleForEvent": "Manage photobooth uploads",
"subtitle": "Create uploader access for photobooth apps and keep limits in sight.",
"actions": {
"backToEvent": "Back to detail view",
"allEvents": "Back to event list"

View File

@@ -35,7 +35,6 @@ export default function MobileEventPhotoboothPage() {
const [event, setEvent] = React.useState<TenantEvent | 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 [updating, setUpdating] = React.useState(false);
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)]);
setEvent(eventData);
setStatus(statusData);
setSelectedMode(statusData.mode ?? 'ftp');
} catch (err) {
if (!isAuthError(err)) {
setError(getApiErrorMessage(err, t('management.photobooth.errors.loadFailed', 'Photobooth-Link konnte nicht geladen werden.')));
@@ -68,20 +66,14 @@ export default function MobileEventPhotoboothPage() {
void load();
}, [load]);
React.useEffect(() => {
if (status?.mode) {
setSelectedMode(status.mode);
}
}, [status?.mode]);
const handleEnable = async (mode?: 'ftp' | 'sparkbooth') => {
const handleEnable = async () => {
if (!slug) return;
const nextMode = mode ?? selectedMode ?? status?.mode ?? 'ftp';
const nextMode = 'sparkbooth';
setUpdating(true);
try {
const result = await enableEventPhotobooth(slug, { mode: nextMode });
setStatus(result);
setSelectedMode(result.mode ?? nextMode);
toast.success(t('management.photobooth.actions.enable', 'Zugang aktiviert'));
} catch (err) {
if (!isAuthError(err)) {
@@ -94,12 +86,11 @@ export default function MobileEventPhotoboothPage() {
const handleDisable = async () => {
if (!slug) return;
const mode = status?.mode ?? selectedMode ?? 'ftp';
const mode = 'sparkbooth';
setUpdating(true);
try {
const result = await disableEventPhotobooth(slug, { mode });
setStatus(result);
setSelectedMode(result.mode ?? mode);
toast.success(t('management.photobooth.actions.disable', 'Zugang deaktiviert'));
} catch (err) {
if (!isAuthError(err)) {
@@ -112,12 +103,11 @@ export default function MobileEventPhotoboothPage() {
const handleRotate = async () => {
if (!slug) return;
const mode = selectedMode ?? status?.mode ?? 'ftp';
const mode = 'sparkbooth';
setUpdating(true);
try {
const result = await rotateEventPhotobooth(slug, { mode });
setStatus(result);
setSelectedMode(result.mode ?? mode);
toast.success(t('management.photobooth.presets.actions.rotate', 'Zugang zurückgesetzt'));
} catch (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 ftp = status?.ftp ?? null;
const metrics = isSpark ? spark?.metrics ?? null : status?.metrics ?? null;
const expiresAt = isSpark ? spark?.expires_at ?? status?.expires_at : status?.expires_at ?? spark?.expires_at;
const metrics = spark?.metrics ?? null;
const expiresAt = spark?.expires_at ?? status?.expires_at;
const lastUploadAt = metrics?.last_upload_at;
const uploads24h = metrics?.uploads_24h ?? metrics?.uploads_today;
const uploadsTotal = metrics?.uploads_total;
const connectionPath = status?.path ?? '—';
const ftpUrl = status?.ftp_url ?? '—';
const uploadUrl = isSpark ? spark?.upload_url ?? status?.upload_url : null;
const uploadUrl = spark?.upload_url ?? status?.upload_url;
const responseFormat = spark?.response_format ?? 'json';
const username = isSpark ? spark?.username ?? status?.username : status?.username ?? spark?.username ?? null;
const password = isSpark ? spark?.password ?? status?.password : status?.password ?? spark?.password ?? null;
const username = spark?.username ?? status?.username ?? null;
const password = spark?.password ?? status?.password ?? null;
const modeLabel =
activeMode === 'sparkbooth'
? t('photobooth.credentials.sparkboothTitle', 'Uploader App (HTTP)')
: t('photobooth.credentials.heading', 'FTP (Classic)');
const modeLabel = t('photobooth.mode.uploader', 'Uploader App (HTTP)');
const isActive = Boolean(status?.enabled);
const title = t('photobooth.title', 'Photobooth');
@@ -174,7 +156,7 @@ export default function MobileEventPhotoboothPage() {
const handleToggle = (checked: boolean) => {
if (!slug || updating) return;
if (checked) {
void handleEnable(status?.mode ?? 'ftp');
void handleEnable();
} else {
void handleDisable();
}
@@ -262,93 +244,53 @@ export default function MobileEventPhotoboothPage() {
<MobileCard space="$2">
<Text fontSize="$sm" fontWeight="700" color={text}>
{t('photobooth.selector.title', 'Choose adapter')}
{t('photobooth.selector.title', 'Connection')}
</Text>
<Text fontSize="$xs" color={muted}>
{t(
'photobooth.selector.description',
'FTP (Classic) works with most booths. The Uploader App uses HTTP POST without FTP.'
)}
{t('photobooth.selector.description', 'Use the Fotospiel uploader app for HTTP uploads.')}
</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 space="$2">
<XStack alignItems="center" justifyContent="space-between">
<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>
{!isSpark && ftp?.require_ftps ? <PillBadge tone="warning">{t('photobooth.credentials.ftps', 'FTPS required')}</PillBadge> : null}
</XStack>
<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.password', 'Password')} value={password ?? '—'} border={border} masked />
<CredentialRow label={t('photobooth.sparkbooth.format', 'Response format')} value={responseFormat.toUpperCase()} border={border} />
<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.password', 'Password')} value={password ?? '—'} border={border} masked />
<CredentialRow label={t('photobooth.uploader.format', 'Response format')} value={responseFormat.toUpperCase()} border={border} />
<Text fontSize="$xs" color={muted}>
{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}>
{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>
<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}>
{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>
</>
)}
) : null}
</YStack>
</YStack>
<XStack space="$2" marginTop="$2" flexWrap="wrap">
<XStack flex={1} minWidth={0}>
@@ -363,7 +305,7 @@ export default function MobileEventPhotoboothPage() {
<XStack flex={1} minWidth={0}>
<CTAButton
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'}
iconLeft={isActive ? <Power size={14} color={text} /> : <PlugZap size={14} color={surface} />}
disabled={updating}