Add photobooth connect code UI
This commit is contained in:
@@ -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';
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
|
||||
Reference in New Issue
Block a user