Restructure photobooth page flow
This commit is contained in:
@@ -1208,6 +1208,19 @@
|
|||||||
"uploader": {
|
"uploader": {
|
||||||
"hint": "POST mit Mediendatei oder base64-Feld \"media\"; die App nutzt diese Zugangsdaten."
|
"hint": "POST mit Mediendatei oder base64-Feld \"media\"; die App nutzt diese Zugangsdaten."
|
||||||
},
|
},
|
||||||
|
"steps": {
|
||||||
|
"activate": {
|
||||||
|
"title": "1. Photobooth aktivieren",
|
||||||
|
"description": "Schalte den Upload-Zugang fuer dieses Event frei."
|
||||||
|
},
|
||||||
|
"download": {
|
||||||
|
"title": "2. Uploader App herunterladen"
|
||||||
|
},
|
||||||
|
"access": {
|
||||||
|
"title": "3. Verbindungscode erstellen",
|
||||||
|
"description": "Der Code verbindet die App sicher mit deinem Event."
|
||||||
|
}
|
||||||
|
},
|
||||||
"uploaderDownload": {
|
"uploaderDownload": {
|
||||||
"title": "Fotospiel Uploader App",
|
"title": "Fotospiel Uploader App",
|
||||||
"description": "Die Fotospiel Uploader App wird benötigt, damit Uploads stabil laufen, die Zugangsdaten geschützt bleiben und keine Dateien verloren gehen.",
|
"description": "Die Fotospiel Uploader App wird benötigt, damit Uploads stabil laufen, die Zugangsdaten geschützt bleiben und keine Dateien verloren gehen.",
|
||||||
|
|||||||
@@ -921,6 +921,19 @@
|
|||||||
"uploader": {
|
"uploader": {
|
||||||
"hint": "POST with media file or base64 \"media\" field; app uses these credentials."
|
"hint": "POST with media file or base64 \"media\" field; app uses these credentials."
|
||||||
},
|
},
|
||||||
|
"steps": {
|
||||||
|
"activate": {
|
||||||
|
"title": "1. Activate photobooth",
|
||||||
|
"description": "Enable upload access for this event."
|
||||||
|
},
|
||||||
|
"download": {
|
||||||
|
"title": "2. Download uploader app"
|
||||||
|
},
|
||||||
|
"access": {
|
||||||
|
"title": "3. Generate connect code",
|
||||||
|
"description": "The code securely pairs the app with your event."
|
||||||
|
}
|
||||||
|
},
|
||||||
"uploaderDownload": {
|
"uploaderDownload": {
|
||||||
"title": "Fotospiel Uploader App",
|
"title": "Fotospiel Uploader App",
|
||||||
"description": "The Fotospiel Uploader App is required so uploads stay stable, credentials remain protected, and no files are lost.",
|
"description": "The Fotospiel Uploader App is required so uploads stay stable, credentials remain protected, and no files are lost.",
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useNavigate, useParams } from 'react-router-dom';
|
import { useNavigate, useParams } from 'react-router-dom';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { RefreshCcw, PlugZap, RefreshCw, ShieldCheck, Copy, Power, Clock3, Mail } from 'lucide-react';
|
import { RefreshCcw, PlugZap, RefreshCw, ShieldCheck, Copy, Power, Clock3, Mail, Download } from 'lucide-react';
|
||||||
import { YStack, XStack } from '@tamagui/stacks';
|
import { YStack, XStack } from '@tamagui/stacks';
|
||||||
import { SizableText as Text } from '@tamagui/text';
|
import { SizableText as Text } from '@tamagui/text';
|
||||||
import { Switch } from '@tamagui/switch';
|
|
||||||
import { Pressable } from '@tamagui/react-native-web-lite';
|
import { Pressable } from '@tamagui/react-native-web-lite';
|
||||||
import { MobileShell, HeaderActionButton } from './components/MobileShell';
|
import { MobileShell, HeaderActionButton } from './components/MobileShell';
|
||||||
import { MobileCard, CTAButton, PillBadge, SkeletonCard } from './components/Primitives';
|
import { MobileCard, CTAButton, PillBadge, SkeletonCard } from './components/Primitives';
|
||||||
@@ -172,15 +171,6 @@ export default function MobileEventPhotoboothPage() {
|
|||||||
const isActive = Boolean(status?.enabled);
|
const isActive = Boolean(status?.enabled);
|
||||||
const title = t('photobooth.title', 'Photobooth');
|
const title = t('photobooth.title', 'Photobooth');
|
||||||
|
|
||||||
const handleToggle = (checked: boolean) => {
|
|
||||||
if (!slug || updating) return;
|
|
||||||
if (checked) {
|
|
||||||
void handleEnable();
|
|
||||||
} else {
|
|
||||||
void handleDisable();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MobileShell
|
<MobileShell
|
||||||
activeTab="home"
|
activeTab="home"
|
||||||
@@ -209,161 +199,122 @@ export default function MobileEventPhotoboothPage() {
|
|||||||
) : (
|
) : (
|
||||||
<YStack space="$2">
|
<YStack space="$2">
|
||||||
<MobileCard space="$3">
|
<MobileCard space="$3">
|
||||||
<XStack justifyContent="space-between" alignItems="center" space="$3" flexWrap="wrap">
|
<YStack space="$1">
|
||||||
<YStack space="$1" flex={1} minWidth={0}>
|
<Text fontSize="$sm" fontWeight="800" color={text}>
|
||||||
<Text fontSize="$md" fontWeight="800" color={text}>
|
{t('photobooth.steps.activate.title', '1. Photobooth aktivieren')}
|
||||||
{t('photobooth.title', 'Photobooth')}
|
</Text>
|
||||||
</Text>
|
<Text fontSize="$xs" color={muted}>
|
||||||
<Text fontSize="$xs" color={muted}>
|
{t('photobooth.steps.activate.description', 'Schalte den Upload-Zugang fuer dieses Event frei.')}
|
||||||
{t('photobooth.credentials.description', 'Share these credentials with your photobooth software.')}
|
</Text>
|
||||||
</Text>
|
|
||||||
<Text fontSize="$xs" color={muted}>
|
|
||||||
{t('photobooth.mode.active', 'Current: {{mode}}', { mode: modeLabel })}
|
|
||||||
</Text>
|
|
||||||
</YStack>
|
|
||||||
<YStack alignItems="flex-end" space="$2">
|
|
||||||
<PillBadge tone={isActive ? 'success' : 'warning'}>
|
|
||||||
{isActive ? t('photobooth.status.badgeActive', 'ACTIVE') : t('photobooth.status.badgeInactive', 'INACTIVE')}
|
|
||||||
</PillBadge>
|
|
||||||
<XStack alignItems="center" space="$2">
|
|
||||||
<Text fontSize="$xs" color={muted}>
|
|
||||||
{isActive ? t('common.enabled', 'Enabled') : t('common.disabled', 'Disabled')}
|
|
||||||
</Text>
|
|
||||||
<Switch
|
|
||||||
size="$4"
|
|
||||||
checked={isActive}
|
|
||||||
disabled={updating}
|
|
||||||
onCheckedChange={handleToggle}
|
|
||||||
aria-label={t('photobooth.actions.toggle', 'Toggle photobooth access')}
|
|
||||||
>
|
|
||||||
<Switch.Thumb />
|
|
||||||
</Switch>
|
|
||||||
</XStack>
|
|
||||||
</YStack>
|
|
||||||
</XStack>
|
|
||||||
<YStack space="$1" marginTop="$2">
|
|
||||||
<XStack justifyContent="space-between" alignItems="center">
|
|
||||||
<Text fontSize="$xs" color={muted}>
|
|
||||||
{t('photobooth.stats.lastUpload', 'Last upload')}
|
|
||||||
</Text>
|
|
||||||
<Text fontSize="$xs" fontWeight="700" color={text}>
|
|
||||||
{lastUploadAt ? formatEventDate(lastUploadAt, locale) : t('photobooth.status.never', 'Never')}
|
|
||||||
</Text>
|
|
||||||
</XStack>
|
|
||||||
<XStack justifyContent="space-between" alignItems="center">
|
|
||||||
<Text fontSize="$xs" color={muted}>
|
|
||||||
{t('photobooth.status.expires', 'Access expires')}
|
|
||||||
</Text>
|
|
||||||
<Text fontSize="$xs" fontWeight="700" color={text}>
|
|
||||||
{expiresAt ? formatEventDate(expiresAt, locale) : '—'}
|
|
||||||
</Text>
|
|
||||||
</XStack>
|
|
||||||
</YStack>
|
</YStack>
|
||||||
</MobileCard>
|
<XStack alignItems="center" justifyContent="space-between" space="$3" flexWrap="wrap">
|
||||||
|
<PillBadge tone={isActive ? 'success' : 'warning'}>
|
||||||
<MobileCard space="$2">
|
{isActive ? t('photobooth.status.badgeActive', 'ACTIVE') : t('photobooth.status.badgeInactive', 'INACTIVE')}
|
||||||
<Text fontSize="$sm" fontWeight="700" color={text}>
|
</PillBadge>
|
||||||
{t('photobooth.selector.title', 'Connection')}
|
<Text fontSize="$xs" color={muted}>
|
||||||
</Text>
|
{t('photobooth.mode.active', 'Current: {{mode}}', { mode: modeLabel })}
|
||||||
<Text fontSize="$xs" color={muted}>
|
|
||||||
{t('photobooth.selector.description', 'Use the Fotospiel uploader app for HTTP uploads.')}
|
|
||||||
</Text>
|
|
||||||
</MobileCard>
|
|
||||||
|
|
||||||
<MobileCard space="$2">
|
|
||||||
<Text fontSize="$sm" fontWeight="700" color={text}>
|
|
||||||
{t('photobooth.uploaderDownload.title', 'Fotospiel Uploader App')}
|
|
||||||
</Text>
|
|
||||||
<Text fontSize="$xs" color={muted}>
|
|
||||||
{t(
|
|
||||||
'photobooth.uploaderDownload.description',
|
|
||||||
'Die Fotospiel Uploader App ist verpflichtend, damit Uploads stabil laufen, die Zugangsdaten geschützt bleiben und keine Dateien verloren gehen.'
|
|
||||||
)}
|
|
||||||
</Text>
|
|
||||||
<CTAButton
|
|
||||||
label={
|
|
||||||
sendingEmail
|
|
||||||
? t('common.processing', '...')
|
|
||||||
: t('photobooth.uploaderDownload.emailAction', 'Download-Links per E-Mail senden')
|
|
||||||
}
|
|
||||||
tone="ghost"
|
|
||||||
onPress={handleSendDownloadEmail}
|
|
||||||
iconLeft={<Mail size={14} color={text} />}
|
|
||||||
disabled={sendingEmail}
|
|
||||||
/>
|
|
||||||
<CTAButton
|
|
||||||
label={t('photobooth.uploaderDownload.actionWindows', 'Uploader herunterladen (Windows)')}
|
|
||||||
onPress={() => {
|
|
||||||
const url = new URL('/downloads/PhotoboothUploader-win-x64.exe', window.location.origin).toString();
|
|
||||||
window.open(url, '_blank', 'noopener,noreferrer');
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<CTAButton
|
|
||||||
label={t('photobooth.uploaderDownload.actionMac', 'Uploader herunterladen (macOS)')}
|
|
||||||
tone="ghost"
|
|
||||||
onPress={() => {
|
|
||||||
const url = new URL('/downloads/PhotoboothUploader-macos-x64', window.location.origin).toString();
|
|
||||||
window.open(url, '_blank', 'noopener,noreferrer');
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<CTAButton
|
|
||||||
label={t('photobooth.uploaderDownload.actionLinux', 'Uploader herunterladen (Linux)')}
|
|
||||||
tone="ghost"
|
|
||||||
onPress={() => {
|
|
||||||
const url = new URL('/downloads/PhotoboothUploader-linux-x64', window.location.origin).toString();
|
|
||||||
window.open(url, '_blank', 'noopener,noreferrer');
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</MobileCard>
|
|
||||||
|
|
||||||
<MobileCard space="$2">
|
|
||||||
<XStack alignItems="center" justifyContent="space-between">
|
|
||||||
<Text fontSize="$sm" fontWeight="700" color={text}>
|
|
||||||
{t('photobooth.credentials.uploaderTitle', 'Uploader App (HTTP)')}
|
|
||||||
</Text>
|
</Text>
|
||||||
</XStack>
|
</XStack>
|
||||||
<YStack space="$2">
|
<XStack space="$2" marginTop="$2">
|
||||||
<CTAButton
|
|
||||||
label={updating ? t('common.processing', '...') : t('photobooth.actions.rotate', 'Regenerate access')}
|
|
||||||
onPress={() => handleRotate()}
|
|
||||||
iconLeft={<RefreshCw size={14} color={surface} />}
|
|
||||||
disabled={updating}
|
|
||||||
style={{ width: '100%', paddingHorizontal: 10, paddingVertical: 10 }}
|
|
||||||
/>
|
|
||||||
<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())}
|
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}
|
||||||
style={{ width: '100%', paddingHorizontal: 10, paddingVertical: 10 }}
|
fullWidth={false}
|
||||||
/>
|
/>
|
||||||
<YStack space="$2">
|
{isActive ? (
|
||||||
<Text fontSize="$xs" color={muted}>
|
|
||||||
{t('photobooth.connectCode.description', 'Create a 6-digit code for the uploader app.')}
|
|
||||||
</Text>
|
|
||||||
<CTAButton
|
<CTAButton
|
||||||
label={
|
label={t('photobooth.actions.rotate', 'Regenerate access')}
|
||||||
connectLoading
|
onPress={() => handleRotate()}
|
||||||
? t('common.processing', '...')
|
tone="ghost"
|
||||||
: t('photobooth.connectCode.actions.generate', 'Generate connect code')
|
iconLeft={<RefreshCw size={14} color={text} />}
|
||||||
}
|
disabled={updating}
|
||||||
onPress={handleGenerateConnectCode}
|
fullWidth={false}
|
||||||
iconLeft={<PlugZap size={14} color={surface} />}
|
|
||||||
disabled={!isActive || updating || connectLoading}
|
|
||||||
style={{ width: '100%', paddingHorizontal: 10, paddingVertical: 10 }}
|
|
||||||
/>
|
/>
|
||||||
{connectCode ? (
|
) : null}
|
||||||
<CredentialRow label={t('photobooth.connectCode.label', 'Connect code')} value={connectCode} border={border} />
|
</XStack>
|
||||||
) : null}
|
</MobileCard>
|
||||||
{connectExpiresAt ? (
|
|
||||||
<Text fontSize="$xs" color={muted}>
|
<MobileCard space="$3">
|
||||||
{t('photobooth.connectCode.expires', 'Expires: {{date}}', {
|
<YStack space="$1">
|
||||||
date: formatEventDateTime(connectExpiresAt, locale),
|
<Text fontSize="$sm" fontWeight="800" color={text}>
|
||||||
})}
|
{t('photobooth.steps.download.title', '2. Uploader App herunterladen')}
|
||||||
</Text>
|
</Text>
|
||||||
) : null}
|
<Text fontSize="$xs" color={muted}>
|
||||||
</YStack>
|
{t(
|
||||||
|
'photobooth.uploaderDownload.description',
|
||||||
|
'Die Fotospiel Uploader App ist verpflichtend, damit Uploads stabil laufen, die Zugangsdaten geschuetzt bleiben und keine Dateien verloren gehen.'
|
||||||
|
)}
|
||||||
|
</Text>
|
||||||
|
</YStack>
|
||||||
|
<XStack space="$2" marginTop="$2" flexWrap="wrap">
|
||||||
|
<CTAButton
|
||||||
|
label={t('photobooth.uploaderDownload.actionWindows', 'Uploader herunterladen (Windows)')}
|
||||||
|
onPress={() => {
|
||||||
|
const url = new URL('/downloads/PhotoboothUploader-win-x64.exe', window.location.origin).toString();
|
||||||
|
window.open(url, '_blank', 'noopener,noreferrer');
|
||||||
|
}}
|
||||||
|
iconLeft={<Download size={14} color={surface} />}
|
||||||
|
fullWidth={false}
|
||||||
|
/>
|
||||||
|
<CTAButton
|
||||||
|
label={t('photobooth.uploaderDownload.actionMac', 'Uploader herunterladen (macOS)')}
|
||||||
|
tone="ghost"
|
||||||
|
onPress={() => {
|
||||||
|
const url = new URL('/downloads/PhotoboothUploader-macos-x64', window.location.origin).toString();
|
||||||
|
window.open(url, '_blank', 'noopener,noreferrer');
|
||||||
|
}}
|
||||||
|
fullWidth={false}
|
||||||
|
/>
|
||||||
|
<CTAButton
|
||||||
|
label={t('photobooth.uploaderDownload.actionLinux', 'Uploader herunterladen (Linux)')}
|
||||||
|
tone="ghost"
|
||||||
|
onPress={() => {
|
||||||
|
const url = new URL('/downloads/PhotoboothUploader-linux-x64', window.location.origin).toString();
|
||||||
|
window.open(url, '_blank', 'noopener,noreferrer');
|
||||||
|
}}
|
||||||
|
fullWidth={false}
|
||||||
|
/>
|
||||||
|
</XStack>
|
||||||
|
<XStack space="$2" marginTop="$2">
|
||||||
|
<CTAButton
|
||||||
|
label={
|
||||||
|
sendingEmail
|
||||||
|
? t('common.processing', '...')
|
||||||
|
: t('photobooth.uploaderDownload.emailAction', 'Download-Links per E-Mail senden')
|
||||||
|
}
|
||||||
|
tone="ghost"
|
||||||
|
onPress={handleSendDownloadEmail}
|
||||||
|
iconLeft={<Mail size={14} color={text} />}
|
||||||
|
disabled={sendingEmail}
|
||||||
|
fullWidth={false}
|
||||||
|
/>
|
||||||
|
</XStack>
|
||||||
|
</MobileCard>
|
||||||
|
|
||||||
|
<MobileCard space="$3">
|
||||||
|
<YStack space="$1">
|
||||||
|
<Text fontSize="$sm" fontWeight="800" color={text}>
|
||||||
|
{t('photobooth.steps.access.title', '3. Verbindungscode erstellen')}
|
||||||
|
</Text>
|
||||||
|
<Text fontSize="$xs" color={muted}>
|
||||||
|
{t('photobooth.steps.access.description', 'Der Code verbindet die App sicher mit deinem Event.')}
|
||||||
|
</Text>
|
||||||
|
</YStack>
|
||||||
|
<XStack space="$2" marginTop="$2">
|
||||||
|
<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}
|
||||||
|
fullWidth={false}
|
||||||
|
/>
|
||||||
<CTAButton
|
<CTAButton
|
||||||
label={
|
label={
|
||||||
showCredentials
|
showCredentials
|
||||||
@@ -372,7 +323,20 @@ export default function MobileEventPhotoboothPage() {
|
|||||||
}
|
}
|
||||||
tone="ghost"
|
tone="ghost"
|
||||||
onPress={() => setShowCredentials((current) => !current)}
|
onPress={() => setShowCredentials((current) => !current)}
|
||||||
|
fullWidth={false}
|
||||||
/>
|
/>
|
||||||
|
</XStack>
|
||||||
|
<YStack space="$2" marginTop="$2">
|
||||||
|
{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: formatEventDateTime(connectExpiresAt, locale),
|
||||||
|
})}
|
||||||
|
</Text>
|
||||||
|
) : null}
|
||||||
{showCredentials ? (
|
{showCredentials ? (
|
||||||
<YStack space="$1">
|
<YStack space="$1">
|
||||||
<CredentialRow label={t('photobooth.credentials.postUrl', 'Upload URL')} value={uploadUrl ?? '—'} border={border} />
|
<CredentialRow label={t('photobooth.credentials.postUrl', 'Upload URL')} value={uploadUrl ?? '—'} border={border} />
|
||||||
|
|||||||
Reference in New Issue
Block a user