Add photobooth connect code 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 17:59:35 +01:00
parent 6edc890e01
commit e32b1fa45a
4 changed files with 100 additions and 0 deletions

View File

@@ -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<PhotoboothConnectCode> {
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<string, JsonValue>;
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';

View File

@@ -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",

View File

@@ -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",

View File

@@ -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<string | null>(null);
const [connectCode, setConnectCode] = React.useState<string | null>(null);
const [connectExpiresAt, setConnectExpiresAt] = React.useState<string | null>(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() {
<Text fontSize="$xs" color={muted}>
{t('photobooth.sparkbooth.hint', 'POST with media file or base64 "media" field; username/password required.')}
</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>
</>
) : (
<>