Fix guest demo UX and enforce guest limits
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-21 21:35:40 +01:00
parent 50cc4e76df
commit 2f9a700e00
28 changed files with 812 additions and 118 deletions

View File

@@ -17,25 +17,26 @@ import { MobileSheet } from './components/Sheet';
import toast from 'react-hot-toast';
import { adminPath } from '../constants';
import { useBackNavigation } from './hooks/useBackNavigation';
import { ADMIN_COLORS, ADMIN_GRADIENTS, useAdminTheme } from './theme';
import { ADMIN_GRADIENTS, useAdminTheme } from './theme';
import { extractBrandingForm, type BrandingFormValues } from '../lib/brandingForm';
import { getContrastingTextColor } from '@/guest/lib/color';
import { DEFAULT_EVENT_BRANDING } from '@/guest/context/EventBrandingContext';
import { getContrastingTextColor, relativeLuminance } from '@/guest/lib/color';
const BRANDING_FORM_DEFAULTS = {
primary: ADMIN_COLORS.primary,
accent: ADMIN_COLORS.accent,
background: '#ffffff',
surface: '#ffffff',
mode: 'auto' as const,
buttonStyle: 'filled' as const,
buttonRadius: 12,
buttonPrimary: ADMIN_COLORS.primary,
buttonSecondary: ADMIN_COLORS.accent,
linkColor: ADMIN_COLORS.accent,
fontSize: 'm' as const,
logoMode: 'upload' as const,
logoPosition: 'left' as const,
logoSize: 'm' as const,
primary: DEFAULT_EVENT_BRANDING.primaryColor,
accent: DEFAULT_EVENT_BRANDING.secondaryColor,
background: DEFAULT_EVENT_BRANDING.backgroundColor,
surface: DEFAULT_EVENT_BRANDING.palette?.surface ?? DEFAULT_EVENT_BRANDING.backgroundColor,
mode: DEFAULT_EVENT_BRANDING.mode ?? 'auto',
buttonStyle: DEFAULT_EVENT_BRANDING.buttons?.style ?? 'filled',
buttonRadius: DEFAULT_EVENT_BRANDING.buttons?.radius ?? 12,
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,
fontSize: DEFAULT_EVENT_BRANDING.typography?.sizePreset ?? 'm',
logoMode: DEFAULT_EVENT_BRANDING.logo?.mode ?? 'emoticon',
logoPosition: DEFAULT_EVENT_BRANDING.logo?.position ?? 'left',
logoSize: DEFAULT_EVENT_BRANDING.logo?.size ?? 'm',
};
const BRANDING_FORM_BASE: BrandingFormValues = {
@@ -176,10 +177,22 @@ export default function MobileBrandingPage() {
}, [tenantBrandingLoaded]);
const previewForm = form.useDefaultBranding && tenantBranding ? tenantBranding : form;
const previewBackground = previewForm.background;
const previewSurfaceCandidate = previewForm.surface || previewBackground;
const backgroundLuminance = relativeLuminance(previewBackground);
const surfaceLuminance = relativeLuminance(previewSurfaceCandidate);
const previewSurface = Math.abs(surfaceLuminance - backgroundLuminance) < 0.06
? backgroundLuminance >= 0.6
? '#ffffff'
: '#0f172a'
: previewSurfaceCandidate;
const previewForeground = backgroundLuminance >= 0.6 ? '#1f2937' : '#f8fafc';
const previewMutedForeground = backgroundLuminance >= 0.6 ? '#6b7280' : '#cbd5e1';
const previewBorder = backgroundLuminance >= 0.6 ? '#e6d9d6' : '#334155';
const previewTitle = event ? renderName(event.name) : t('events.placeholders.untitled', 'Unbenanntes Event');
const previewHeadingFont = previewForm.headingFont || 'Archivo Black';
const previewBodyFont = previewForm.bodyFont || 'Manrope';
const previewSurfaceText = getContrastingTextColor(previewForm.surface, '#ffffff', '#0f172a');
const previewHeadingFont = previewForm.headingFont || DEFAULT_EVENT_BRANDING.typography?.heading || DEFAULT_EVENT_BRANDING.fontFamily;
const previewBodyFont = previewForm.bodyFont || DEFAULT_EVENT_BRANDING.typography?.body || DEFAULT_EVENT_BRANDING.fontFamily;
const previewSurfaceText = previewForeground;
const previewScale = FONT_SIZE_SCALE[previewForm.fontSize] ?? 1;
const previewButtonColor = previewForm.buttonPrimary || previewForm.primary;
const previewButtonText = getContrastingTextColor(previewButtonColor, '#ffffff', '#0f172a');
@@ -560,8 +573,8 @@ export default function MobileBrandingPage() {
<Text fontSize="$sm" fontWeight="700" color={textStrong}>
{t('events.branding.previewTitle', 'Guest App Preview')}
</Text>
<YStack borderRadius={16} borderWidth={1} borderColor={border} backgroundColor={previewForm.background} padding="$3" space="$2" alignItems="center">
<YStack width="100%" borderRadius={12} backgroundColor={previewForm.surface} borderWidth={1} borderColor={border} overflow="hidden">
<YStack borderRadius={16} borderWidth={1} borderColor={previewBorder} backgroundColor={previewBackground} padding="$3" space="$2" alignItems="center">
<YStack width="100%" borderRadius={12} backgroundColor={previewSurface} borderWidth={1} borderColor={previewBorder} overflow="hidden">
<YStack
height={64}
style={{ background: `linear-gradient(135deg, ${previewForm.primary}, ${previewForm.accent})` }}
@@ -602,18 +615,18 @@ export default function MobileBrandingPage() {
{previewTitle}
</Text>
<Text
color={previewSurfaceText}
style={{ fontFamily: previewBodyFont, opacity: 0.7, fontSize: 13 * previewScale }}
color={previewMutedForeground}
style={{ fontFamily: previewBodyFont, fontSize: 13 * previewScale }}
>
{t('events.branding.previewSubtitle', 'Aktuelle Farben & Schriften')}
</Text>
</YStack>
</XStack>
<XStack space="$2" marginTop="$1">
<ColorSwatch color={previewForm.primary} label={t('events.branding.primary', 'Primary')} />
<ColorSwatch color={previewForm.accent} label={t('events.branding.accent', 'Accent')} />
<ColorSwatch color={previewForm.background} label={t('events.branding.background', 'Background')} />
<ColorSwatch color={previewForm.surface} label={t('events.branding.surface', 'Surface')} />
<ColorSwatch color={previewForm.primary} label={t('events.branding.primary', 'Primary')} borderColor={previewBorder} />
<ColorSwatch color={previewForm.accent} label={t('events.branding.accent', 'Accent')} borderColor={previewBorder} />
<ColorSwatch color={previewBackground} label={t('events.branding.background', 'Background')} borderColor={previewBorder} />
<ColorSwatch color={previewSurface} label={t('events.branding.surface', 'Surface')} borderColor={previewBorder} />
</XStack>
<XStack marginTop="$2">
<div
@@ -1220,11 +1233,11 @@ function ColorField({
);
}
function ColorSwatch({ color, label }: { color: string; label: string }) {
function ColorSwatch({ color, label, borderColor }: { color: string; label: string; borderColor?: string }) {
const { border, muted } = useAdminTheme();
return (
<YStack alignItems="center" space="$1">
<YStack width={40} height={40} borderRadius={12} borderWidth={1} borderColor={border} backgroundColor={color} />
<YStack width={40} height={40} borderRadius={12} borderWidth={1} borderColor={borderColor ?? border} backgroundColor={color} />
<Text fontSize="$xs" color={muted}>
{label}
</Text>