Update guest v2 home and tasks experience
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-02-03 18:59:30 +01:00
parent 298a8375b6
commit 6062b4201b
31 changed files with 753 additions and 259 deletions

View File

@@ -792,6 +792,17 @@
"fontSizeSmall": "S",
"fontSizeMedium": "M",
"fontSizeLarge": "L",
"welcomeMessage": {
"title": "Willkommensnachricht",
"label": "Nachricht",
"placeholder": "Willkommen bei {eventName}!",
"hint": "Nutze {eventName} oder {name} als Platzhalter."
},
"welcomeMessageFallback": "Willkommen bei {eventName}.",
"welcomeGuestFallback": "Gast",
"previewWelcome": "Willkommen!",
"previewGuests": "Gäste",
"previewLikes": "Likes",
"logo": "Logo",
"logoAlt": "Logo",
"logoModeUpload": "Upload",

View File

@@ -788,6 +788,17 @@
"fontSizeSmall": "S",
"fontSizeMedium": "M",
"fontSizeLarge": "L",
"welcomeMessage": {
"title": "Welcome message",
"label": "Message",
"placeholder": "Welcome to {eventName}!",
"hint": "Use {eventName} or {name} as placeholders."
},
"welcomeMessageFallback": "Welcome to {eventName}.",
"welcomeGuestFallback": "Guest",
"previewWelcome": "Welcome!",
"previewGuests": "Guests",
"previewLikes": "Likes",
"logo": "Logo",
"logoAlt": "Logo",
"logoModeUpload": "Upload",

View File

@@ -17,6 +17,7 @@ export type BrandingFormValues = {
buttonPrimary: string;
buttonSecondary: string;
linkColor: string;
welcomeMessage: string;
};
export type BrandingFormDefaults = Pick<
@@ -33,6 +34,7 @@ export type BrandingFormDefaults = Pick<
| 'buttonPrimary'
| 'buttonSecondary'
| 'linkColor'
| 'welcomeMessage'
| 'fontSize'
| 'logoMode'
| 'logoPosition'
@@ -125,6 +127,11 @@ export function extractBrandingForm(settings: unknown, defaults: BrandingFormDef
const buttonPrimary = readHexColor(buttons.primary, readHexColor(branding.button_primary_color, primary));
const buttonSecondary = readHexColor(buttons.secondary, readHexColor(branding.button_secondary_color, accent));
const linkColor = readHexColor(buttons.link_color ?? buttons.linkColor, readHexColor(branding.link_color, accent));
const welcomeMessage = typeof branding.welcome_message === 'string'
? branding.welcome_message
: typeof branding.welcomeMessage === 'string'
? branding.welcomeMessage
: defaults.welcomeMessage;
return {
primary,
@@ -145,5 +152,6 @@ export function extractBrandingForm(settings: unknown, defaults: BrandingFormDef
buttonPrimary,
buttonSecondary,
linkColor,
welcomeMessage,
};
}

View File

@@ -2,14 +2,14 @@ import React from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { Theme } from '@tamagui/core';
import { Image as ImageIcon, RefreshCcw, UploadCloud, Trash2, ChevronDown, Save, Droplets, Lock } from 'lucide-react';
import { Image as ImageIcon, RefreshCcw, UploadCloud, Trash2, ChevronDown, Save, Droplets, Lock, X } from 'lucide-react';
import { YStack, XStack } from '@tamagui/stacks';
import { SizableText as Text } from '@tamagui/text';
import { Pressable } from '@tamagui/react-native-web-lite';
import { Slider } from 'tamagui';
import { MobileShell, HeaderActionButton } from './components/MobileShell';
import { MobileCard, CTAButton, SkeletonCard } from './components/Primitives';
import { MobileColorInput, MobileField, MobileFileInput, MobileInput, MobileSelect } from './components/FormControls';
import { MobileColorInput, MobileField, MobileFileInput, MobileInput, MobileSelect, MobileTextArea } from './components/FormControls';
import { TenantEvent, getEvent, updateEvent, getTenantFonts, getTenantSettings, TenantFont, WatermarkSettings, trackOnboarding } from '../api';
import { isAuthError } from '../auth/tokens';
import { ApiError, getApiErrorMessage } from '../lib/apiError';
@@ -37,6 +37,7 @@ const BRANDING_FORM_DEFAULTS = {
buttonPrimary: DEFAULT_EVENT_BRANDING.buttons?.primary ?? DEFAULT_EVENT_BRANDING.primaryColor,
buttonSecondary: DEFAULT_EVENT_BRANDING.buttons?.secondary ?? DEFAULT_EVENT_BRANDING.secondaryColor,
linkColor: DEFAULT_EVENT_BRANDING.buttons?.linkColor ?? DEFAULT_EVENT_BRANDING.secondaryColor,
welcomeMessage: '',
fontSize: DEFAULT_EVENT_BRANDING.typography?.sizePreset ?? 'm',
logoMode: DEFAULT_EVENT_BRANDING.logo?.mode ?? 'emoticon',
logoPosition: DEFAULT_EVENT_BRANDING.logo?.position ?? 'left',
@@ -76,6 +77,9 @@ const resolveBrandingDefaults = (tenantBranding: BrandingFormValues | null) => {
const bodyFont = tenantBranding.bodyFont.trim()
? tenantBranding.bodyFont
: BRANDING_FORM_DEFAULTS.bodyFont;
const welcomeMessage = tenantBranding.welcomeMessage?.trim()
? tenantBranding.welcomeMessage
: BRANDING_FORM_DEFAULTS.welcomeMessage;
return {
...BRANDING_FORM_DEFAULTS,
@@ -90,6 +94,7 @@ const resolveBrandingDefaults = (tenantBranding: BrandingFormValues | null) => {
buttonPrimary: primary,
buttonSecondary: accent,
linkColor: accent,
welcomeMessage,
};
};
@@ -241,6 +246,12 @@ export default function MobileBrandingPage() {
const previewLogoUrl = previewForm.logoMode === 'upload' ? previewForm.logoDataUrl : '';
const previewLogoValue = previewForm.logoMode === 'emoticon' ? previewForm.logoValue : '';
const previewInitials = getInitials(previewTitle);
const previewWelcomeTemplate = previewForm.welcomeMessage.trim()
? previewForm.welcomeMessage.trim()
: t('events.branding.welcomeMessageFallback', 'Willkommen bei {eventName}.');
const previewWelcomeMessage = previewWelcomeTemplate
.replace('{eventName}', previewTitle)
.replace('{name}', t('events.branding.welcomeGuestFallback', 'Gast'));
const previewThemeName = previewForm.mode === 'dark' ? 'guestNight' : 'guestLight';
const previewIsDark = previewThemeName === 'guestNight';
const previewVariables = {
@@ -346,6 +357,7 @@ export default function MobileBrandingPage() {
secondary: form.buttonSecondary,
link_color: form.linkColor,
},
welcome_message: form.welcomeMessage.trim() ? form.welcomeMessage.trim() : null,
logo_data_url: form.logoMode === 'upload' && logoIsDataUrl ? logoUploadValue : null,
logo_url: form.logoMode === 'upload' ? (normalizedLogoPath || null) : null,
logo_mode: form.logoMode,
@@ -705,6 +717,48 @@ export default function MobileBrandingPage() {
</XStack>
<YStack gap="$2">
<YStack
position="relative"
padding="$3"
borderRadius={14}
backgroundColor={previewSurface}
borderWidth={1}
borderColor={previewBorder}
style={{ boxShadow: previewSurfaceShadow }}
>
<YStack position="absolute" top={-10} right={-10}>
<YStack
width={28}
height={28}
borderRadius={999}
backgroundColor={previewSurface}
borderWidth={1}
borderColor={previewBorder}
alignItems="center"
justifyContent="center"
style={{
boxShadow: previewIsDark
? '0 6px 0 rgba(0, 0, 0, 0.45)'
: '0 6px 0 rgba(15, 23, 42, 0.2)',
}}
>
<X size={12} color={previewSurfaceText} />
</YStack>
</YStack>
<Text
fontWeight="700"
color={previewSurfaceText}
style={{ fontFamily: previewHeadingFont, fontSize: 14 * previewScale }}
>
{t('events.branding.previewWelcome', 'Willkommen!')}
</Text>
<Text
color={previewMutedForeground}
style={{ fontFamily: previewBodyFont, fontSize: 12 * previewScale }}
>
{previewWelcomeMessage}
</Text>
</YStack>
<YStack
padding="$3"
borderRadius={14}
@@ -744,25 +798,37 @@ export default function MobileBrandingPage() {
flex={1}
>
<Text fontSize="$2" color={previewMutedForeground}>
{t('events.branding.previewStat', 'Online Guests')}
{t('events.branding.previewGuests', 'Guests')}
</Text>
<Text fontWeight="800" color={previewSurfaceText} style={{ fontSize: 14 * previewScale }}>
148
</Text>
</YStack>
<div
style={{
padding: '8px 14px',
borderRadius: previewForm.buttonRadius,
background: previewForm.buttonStyle === 'outline' ? 'transparent' : previewButtonColor,
color: previewForm.buttonStyle === 'outline' ? previewForm.linkColor : previewButtonText,
border: previewForm.buttonStyle === 'outline' ? `1px solid ${previewForm.linkColor}` : 'none',
fontWeight: 700,
fontSize: 13 * previewScale,
}}
<YStack
padding="$2"
borderRadius="$pill"
backgroundColor={previewSurface}
borderWidth={1}
borderColor={previewBorder}
flex={1}
>
{t('events.branding.previewCta', 'Fotos hochladen')}
</div>
<Text fontSize="$2" color={previewMutedForeground}>
{t('events.branding.previewLikes', 'Likes')}
</Text>
<Text fontWeight="800" color={previewSurfaceText} style={{ fontSize: 14 * previewScale }}>
392
</Text>
</YStack>
</XStack>
<XStack gap="$2" alignItems="center">
<YStack width={64} height={48} borderRadius={12} backgroundColor={previewIconSurface} />
<YStack width={64} height={48} borderRadius={12} backgroundColor={previewIconSurface} />
<YStack width={64} height={48} borderRadius={12} backgroundColor={previewIconSurface} />
</XStack>
<XStack gap="$2">
<YStack flex={1} height={44} borderRadius={12} backgroundColor={previewIconSurface} />
<YStack flex={1} height={44} borderRadius={12} backgroundColor={previewIconSurface} />
<YStack flex={1} height={44} borderRadius={12} backgroundColor={previewIconSurface} />
</XStack>
</YStack>
@@ -909,6 +975,23 @@ export default function MobileBrandingPage() {
</XStack>
</MobileCard>
<MobileCard gap="$3">
<Text fontSize="$md" fontWeight="800" color={textStrong}>
{t('events.branding.welcomeMessage.title', 'Welcome message')}
</Text>
<MobileField label={t('events.branding.welcomeMessage.label', 'Message')}>
<MobileTextArea
value={form.welcomeMessage}
placeholder={t('events.branding.welcomeMessage.placeholder', 'Welcome to {eventName}!')}
onChange={(event) => setForm((prev) => ({ ...prev, welcomeMessage: event.target.value }))}
disabled={brandingDisabled}
/>
</MobileField>
<Text fontSize="$xs" color={muted}>
{t('events.branding.welcomeMessage.hint', 'Use {eventName} or {name} as placeholders.')}
</Text>
</MobileCard>
<MobileCard gap="$3">
<Text fontSize="$md" fontWeight="800" color={textStrong}>
{t('events.branding.logo', 'Logo')}

View File

@@ -54,6 +54,7 @@ const TENANT_BRANDING_DEFAULTS = {
buttonPrimary: DEFAULT_EVENT_BRANDING.buttons?.primary ?? DEFAULT_EVENT_BRANDING.primaryColor,
buttonSecondary: DEFAULT_EVENT_BRANDING.buttons?.secondary ?? DEFAULT_EVENT_BRANDING.secondaryColor,
linkColor: DEFAULT_EVENT_BRANDING.buttons?.linkColor ?? DEFAULT_EVENT_BRANDING.secondaryColor,
welcomeMessage: '',
fontSize: DEFAULT_EVENT_BRANDING.typography?.sizePreset ?? 'm',
logoMode: DEFAULT_EVENT_BRANDING.logo?.mode ?? 'emoticon',
logoPosition: DEFAULT_EVENT_BRANDING.logo?.position ?? 'left',
@@ -263,6 +264,7 @@ export default function MobileProfileAccountPage() {
body_font: brandingForm.bodyFont,
font_size: brandingForm.fontSize,
mode: brandingForm.mode,
welcome_message: brandingForm.welcomeMessage.trim() ? brandingForm.welcomeMessage.trim() : null,
typography: {
...(typeof existingBranding.typography === 'object' ? (existingBranding.typography as Record<string, unknown>) : {}),
heading: brandingForm.headingFont,