From e32b1fa45a0ccca73499d3ec3e14182eed891854 Mon Sep 17 00:00:00 2001 From: Codex Agent Date: Mon, 12 Jan 2026 17:59:35 +0100 Subject: [PATCH] Add photobooth connect code UI --- resources/js/admin/api.ts | 27 ++++++++++ .../js/admin/i18n/locales/de/management.json | 12 +++++ .../js/admin/i18n/locales/en/management.json | 12 +++++ .../js/admin/mobile/EventPhotoboothPage.tsx | 49 +++++++++++++++++++ 4 files changed, 100 insertions(+) diff --git a/resources/js/admin/api.ts b/resources/js/admin/api.ts index f827c10..e7090de 100644 --- a/resources/js/admin/api.ts +++ b/resources/js/admin/api.ts @@ -218,6 +218,11 @@ export type PhotoboothStatus = { metrics?: PhotoboothStatusMetrics | null; }; +export type PhotoboothConnectCode = { + code: string; + expires_at: string | null; +}; + export type EventAddonCheckout = { addon_key: string; quantity?: number; @@ -2041,6 +2046,28 @@ export async function disableEventPhotobooth(slug: string, options?: { mode?: 'f ); } +export async function createEventPhotoboothConnectCode( + slug: string, + options?: { expires_in_minutes?: number } +): Promise { + const body = options ? JSON.stringify(options) : undefined; + const headers = body ? { 'Content-Type': 'application/json' } : undefined; + + const response = await authorizedFetch(`${photoboothEndpoint(slug)}/connect-codes`, { + method: 'POST', + body, + headers, + }); + + const data = await jsonOrThrow<{ data?: JsonValue }>(response, 'Failed to create photobooth connect code'); + const record = (data.data ?? {}) as Record; + + return { + code: typeof record.code === 'string' ? record.code : '', + expires_at: typeof record.expires_at === 'string' ? record.expires_at : null, + }; +} + export async function submitTenantFeedback(payload: { category: string; sentiment?: 'positive' | 'neutral' | 'negative'; diff --git a/resources/js/admin/i18n/locales/de/management.json b/resources/js/admin/i18n/locales/de/management.json index 7399551..2deb285 100644 --- a/resources/js/admin/i18n/locales/de/management.json +++ b/resources/js/admin/i18n/locales/de/management.json @@ -1185,6 +1185,18 @@ "postUrl": "Upload-URL", "responseFormat": "Antwort-Format" }, + "connectCode": { + "label": "Verbindungscode", + "description": "Erstelle einen 6-stelligen Code für die Uploader-App.", + "expires": "Läuft ab: {{date}}", + "actions": { + "generate": "Verbindungscode erstellen", + "generated": "Verbindungscode erstellt" + }, + "errors": { + "failed": "Verbindungscode konnte nicht erstellt werden." + } + }, "actions": { "enable": "Photobooth aktivieren", "disable": "Deaktivieren", diff --git a/resources/js/admin/i18n/locales/en/management.json b/resources/js/admin/i18n/locales/en/management.json index ada41d1..58bfefb 100644 --- a/resources/js/admin/i18n/locales/en/management.json +++ b/resources/js/admin/i18n/locales/en/management.json @@ -898,6 +898,18 @@ "postUrl": "Upload URL", "responseFormat": "Response format" }, + "connectCode": { + "label": "Connect code", + "description": "Create a 6-digit code for the uploader app.", + "expires": "Expires: {{date}}", + "actions": { + "generate": "Generate connect code", + "generated": "Connect code created" + }, + "errors": { + "failed": "Connect code could not be created." + } + }, "actions": { "enable": "Activate photobooth", "disable": "Disable", diff --git a/resources/js/admin/mobile/EventPhotoboothPage.tsx b/resources/js/admin/mobile/EventPhotoboothPage.tsx index 79c8124..81530b1 100644 --- a/resources/js/admin/mobile/EventPhotoboothPage.tsx +++ b/resources/js/admin/mobile/EventPhotoboothPage.tsx @@ -14,6 +14,7 @@ import { enableEventPhotobooth, disableEventPhotobooth, rotateEventPhotobooth, + createEventPhotoboothConnectCode, PhotoboothStatus, TenantEvent, } from '../api'; @@ -38,6 +39,9 @@ export default function MobileEventPhotoboothPage() { const [loading, setLoading] = React.useState(true); const [updating, setUpdating] = React.useState(false); const [error, setError] = React.useState(null); + const [connectCode, setConnectCode] = React.useState(null); + const [connectExpiresAt, setConnectExpiresAt] = React.useState(null); + const [connectLoading, setConnectLoading] = React.useState(false); const back = useBackNavigation(slug ? adminPath(`/mobile/events/${slug}`) : adminPath('/mobile/events')); const locale = i18n.language?.startsWith('en') ? 'en-GB' : 'de-DE'; @@ -124,6 +128,25 @@ export default function MobileEventPhotoboothPage() { } }; + const handleGenerateConnectCode = async () => { + if (!slug) return; + setConnectLoading(true); + try { + const result = await createEventPhotoboothConnectCode(slug); + setConnectCode(result.code || null); + setConnectExpiresAt(result.expires_at ?? null); + toast.success(t('photobooth.connectCode.actions.generated', 'Verbindungscode erstellt')); + } catch (err) { + if (!isAuthError(err)) { + toast.error( + getApiErrorMessage(err, t('photobooth.connectCode.errors.failed', 'Verbindungscode konnte nicht erstellt werden.')) + ); + } + } finally { + setConnectLoading(false); + } + }; + const activeMode = selectedMode ?? status?.mode ?? 'ftp'; const isSpark = activeMode === 'sparkbooth'; const spark = status?.sparkbooth ?? null; @@ -286,6 +309,32 @@ export default function MobileEventPhotoboothPage() { {t('photobooth.sparkbooth.hint', 'POST with media file or base64 "media" field; username/password required.')} + + + {t('photobooth.connectCode.description', 'Create a 6-digit code for the uploader app.')} + + } + disabled={!isActive || updating || connectLoading} + style={{ width: '100%', paddingHorizontal: 10, paddingVertical: 10 }} + /> + {connectCode ? ( + + ) : null} + {connectExpiresAt ? ( + + {t('photobooth.connectCode.expires', 'Expires: {{date}}', { + date: formatEventDate(connectExpiresAt, locale), + })} + + ) : null} + ) : ( <>