upgrade to tamagui v2 and guest pwa overhaul

This commit is contained in:
Codex Agent
2026-02-02 13:01:20 +01:00
parent 2e78f3ab8d
commit 7c6e14ffe2
168 changed files with 47462 additions and 8914 deletions

View File

@@ -77,7 +77,7 @@ export function BottomNav({ active, onNavigate }: { active: NavKey; onNavigate:
<YStack
alignItems="center"
justifyContent="center"
space="$1"
gap="$1"
minHeight={50}
style={{
transform: isPressed ? 'scale(0.92)' : (activeState ? 'scale(1.05)' : 'scale(1)'),

View File

@@ -27,7 +27,7 @@ export function ContextHelpLink({ slug, label }: ContextHelpLinkProps) {
>
<XStack
alignItems="center"
space="$1.5"
gap="$1.5"
paddingHorizontal="$2.5"
paddingVertical="$1.5"
borderRadius={999}

View File

@@ -41,14 +41,14 @@ export function EventSwitcherSheet({
onClose={onClose}
snapPoints={[65]}
>
<YStack space="$2">
<YStack gap="$2">
{events.map((event) => {
const isActive = event.slug === activeSlug;
return (
<Pressable key={event.slug} onPress={() => handleSelect(event.slug)}>
<XStack
alignItems="center"
space="$3"
gap="$3"
padding="$3"
borderRadius={14}
backgroundColor={isActive ? theme.surfaceMuted : 'transparent'}

View File

@@ -18,7 +18,7 @@ export function MobileField({ label, hint, error, children }: FieldProps) {
const { text, muted, danger } = useAdminTheme();
return (
<YStack space="$1.5">
<YStack gap="$1.5">
{typeof label === 'string' || typeof label === 'number' ? (
<Text fontSize="$sm" fontWeight="800" color={text}>
{label}

View File

@@ -76,7 +76,7 @@ export function LegalConsentSheet({
onClose={onClose}
title={copy?.title ?? t('events.legalConsent.title', 'Before purchase')}
footer={
<YStack space="$2">
<YStack gap="$2">
{error ? (
<Text fontSize="$sm" color={danger}>
{error}
@@ -97,12 +97,12 @@ export function LegalConsentSheet({
</YStack>
}
>
<YStack space="$2">
<YStack gap="$2">
<Text fontSize="$sm" color={text}>
{copy?.description ?? t('events.legalConsent.description', 'Please confirm the legal notes before buying an add-on.')}
</Text>
{requireTerms ? (
<XStack space="$3" alignItems="flex-start">
<XStack gap="$3" alignItems="flex-start">
<Checkbox
id="legal-terms"
size="$4"
@@ -130,7 +130,7 @@ export function LegalConsentSheet({
</XStack>
) : null}
{requireWaiver ? (
<XStack space="$3" alignItems="flex-start">
<XStack gap="$3" alignItems="flex-start">
<Checkbox
id="legal-waiver"
size="$4"

View File

@@ -33,9 +33,9 @@ export function LimitWarnings({
}
return (
<YStack space="$2">
<YStack gap="$2">
{warnings.map((warning) => (
<MobileCard key={warning.id} borderColor={borderColor} space="$2">
<MobileCard key={warning.id} borderColor={borderColor} gap="$2">
<Text fontSize="$sm" color={textColor} fontWeight="700">
{warning.message}
</Text>
@@ -100,7 +100,7 @@ function MobileAddonsPicker({
}
return (
<XStack space="$2" alignItems="center">
<XStack gap="$2" alignItems="center">
<MobileSelect
value={selected}
onChange={(event) => setSelected(event.target.value)}

View File

@@ -33,13 +33,13 @@ export function MobileInstallBanner({
return (
<MobileCard
space={isCompact ? '$1.5' : '$2'}
gap={isCompact ? '$1.5' : '$2'}
borderColor={border}
backgroundColor={surfaceMuted}
padding={isCompact ? '$2' : '$3'}
>
<XStack alignItems="center" justifyContent="space-between" gap="$2">
<XStack alignItems="center" space="$2" flex={1}>
<XStack alignItems="center" gap="$2" flex={1}>
<XStack
width={isCompact ? 32 : 36}
height={isCompact ? 32 : 36}
@@ -50,7 +50,7 @@ export function MobileInstallBanner({
>
{isPrompt ? <Download size={16} color={primary} /> : <Share2 size={16} color={primary} />}
</XStack>
<YStack flex={1} space="$0.5">
<YStack flex={1} gap="$0.5">
<Text fontSize={isCompact ? '$xs' : '$sm'} fontWeight="800" color={textStrong}>
{t('installBanner.title', 'Install Fotospiel Admin')}
</Text>
@@ -61,7 +61,7 @@ export function MobileInstallBanner({
</Text>
</YStack>
</XStack>
<XStack alignItems="center" space="$2">
<XStack alignItems="center" gap="$2">
{isPrompt && onInstall && isCompact ? (
<Pressable onPress={onInstall}>
<Text fontSize={10} fontWeight="700" color={primary}>

View File

@@ -177,7 +177,7 @@ export function MobileShell({ title, subtitle, children, activeTab, onBack, head
paddingVertical="$1.5"
borderRadius={999}
alignItems="center"
space="$1.5"
gap="$1.5"
maxWidth={220}
borderWidth={1}
borderColor="rgba(255, 255, 255, 0.08)"
@@ -201,7 +201,7 @@ export function MobileShell({ title, subtitle, children, activeTab, onBack, head
const headerBackButton = onBack ? (
<HeaderActionButton onPress={onBack} ariaLabel={t('actions.back', 'Back')}>
<XStack alignItems="center" space="$1.5">
<XStack alignItems="center" gap="$1.5">
<ChevronLeft size={28} color="white" strokeWidth={2.5} />
</XStack>
</HeaderActionButton>
@@ -214,7 +214,7 @@ export function MobileShell({ title, subtitle, children, activeTab, onBack, head
);
const headerActionsRow = (
<XStack alignItems="center" space="$2.5">
<XStack alignItems="center" gap="$2.5">
{showQr ? (
<HeaderActionButton
onPress={() => navigate(adminPath(`/mobile/events/${effectiveActive?.slug}/qr`))}
@@ -282,7 +282,7 @@ export function MobileShell({ title, subtitle, children, activeTab, onBack, head
borderWidth={1} borderColor={actionBorder}
>
{user?.avatar_url ? (
<Image source={{ uri: user.avatar_url }} width={36} height={36} resizeMode="cover" />
<Image src={user.avatar_url} width={36} height={36} objectFit="cover" />
) : (
<Text fontSize="$xs" fontWeight="700" color="white">
{user?.name?.charAt(0).toUpperCase() ?? 'U'}
@@ -315,7 +315,7 @@ export function MobileShell({ title, subtitle, children, activeTab, onBack, head
paddingTop: 'calc(env(safe-area-inset-top, 0px) + 16px)',
}}
>
<XStack alignItems="center" justifyContent="space-between" minHeight={48} space="$2">
<XStack alignItems="center" justifyContent="space-between" minHeight={48} gap="$2">
{headerBackButton}
<XStack flex={1} justifyContent="center" alignItems="center">
@@ -332,7 +332,7 @@ export function MobileShell({ title, subtitle, children, activeTab, onBack, head
flex={1}
padding="$4"
paddingBottom="$10"
space="$3"
gap="$3"
width="100%"
maxWidth={800}
style={{ paddingBottom: 'calc(env(safe-area-inset-bottom, 0px) + 96px)' }}
@@ -345,7 +345,7 @@ export function MobileShell({ title, subtitle, children, activeTab, onBack, head
</XStack>
) : null}
{queuedPhotoCount > 0 ? (
<MobileCard space="$2">
<MobileCard gap="$2">
<Text fontSize="$sm" fontWeight="700" color={theme.textStrong}>
{t('status.queueTitle', 'Photo actions pending')}
</Text>
@@ -365,7 +365,7 @@ export function MobileShell({ title, subtitle, children, activeTab, onBack, head
</MobileCard>
) : null}
{subtitle ? (
<YStack space="$1">
<YStack gap="$1">
{title ? (
<Text fontSize="$lg" fontWeight="800" color={theme.textStrong}>
{title}

View File

@@ -45,7 +45,7 @@ export function OnboardingShell({
paddingHorizontal="$5"
paddingTop="$5"
paddingBottom="$6"
space="$4"
gap="$4"
style={{
paddingTop: 'calc(env(safe-area-inset-top, 0px) + 20px)',
paddingBottom: 'calc(env(safe-area-inset-bottom, 0px) + 24px)',
@@ -54,7 +54,7 @@ export function OnboardingShell({
<XStack alignItems="center" justifyContent="space-between">
{onBack ? (
<Pressable onPress={onBack} aria-label={resolvedBackLabel}>
<XStack alignItems="center" space="$1.5">
<XStack alignItems="center" gap="$1.5">
<ChevronLeft size={22} color={text} />
<Text fontSize="$sm" fontWeight="700" color={text}>
{resolvedBackLabel}
@@ -86,7 +86,7 @@ export function OnboardingShell({
shadowOpacity={0.06}
shadowRadius={14}
shadowOffset={{ width: 0, height: 8 }}
space="$2"
gap="$2"
>
{eyebrow ? (
<Text fontSize="$xs" fontWeight="700" color={muted} textTransform="uppercase" letterSpacing={0.6}>
@@ -103,7 +103,7 @@ export function OnboardingShell({
) : null}
</YStack>
<YStack space="$4">{children}</YStack>
<YStack gap="$4">{children}</YStack>
{footer ? <YStack marginTop="$2">{footer}</YStack> : null}
</YStack>
</YStack>

View File

@@ -26,7 +26,7 @@ export function MobileCard({
shadowRadius={16}
shadowOffset={{ width: 0, height: 10 }}
padding="$3.5"
space="$2"
gap="$2"
style={{
...style,
}}
@@ -138,7 +138,7 @@ export function CTAButton({
backgroundColor={backgroundColor}
borderWidth={isPrimary || isDanger ? 0 : 2}
borderColor={borderColor}
space="$2"
gap="$2"
style={primaryStyle}
>
{iconLeft}
@@ -169,7 +169,7 @@ export function KpiTile({
const iconColor = color || primary;
return (
<MobileCard borderRadius={14} padding="$2.5" width="31%" minWidth={100} space="$1.5">
<MobileCard borderRadius={14} padding="$2.5" width="31%" minWidth={100} gap="$1.5">
<XStack
width={28}
height={28}
@@ -181,7 +181,7 @@ export function KpiTile({
<IconCmp size={14} color={iconColor} />
</XStack>
<YStack space="$0">
<YStack gap="$0">
<Text fontSize="$xl" fontWeight="900" color={textStrong} letterSpacing={-0.5} lineHeight="$xl">
{value}
</Text>
@@ -237,7 +237,7 @@ export function KpiStrip({
minWidth={150}
maxWidth={220}
>
<XStack alignItems="center" space="$2">
<XStack alignItems="center" gap="$2">
<Text
fontSize={32}
fontWeight="900"
@@ -248,7 +248,7 @@ export function KpiStrip({
{item.value}
</Text>
<Separator vertical backgroundColor={separatorColor} height={32} marginHorizontal="$1.5" />
<YStack alignItems="center" space="$0.5" paddingLeft="$0.5">
<YStack alignItems="center" gap="$0.5" paddingLeft="$0.5">
<XStack
width={28}
height={28}
@@ -339,7 +339,7 @@ export function ActionTile({
style={tileStyle}
borderRadius={isCluster ? 14 : 16}
padding="$3"
space="$2.5"
gap="$2.5"
backgroundColor={glassSurface ?? backgroundColor}
borderWidth={2}
borderColor={borderColor}
@@ -405,7 +405,7 @@ export function FloatingActionButton({
borderRadius={999}
alignItems="center"
justifyContent="center"
space="$2"
gap="$2"
backgroundColor={primary}
shadowColor={shadow}
shadowOpacity={0.2}

View File

@@ -41,10 +41,10 @@ export function MobileScaffold({ title, onBack, rightSlot, children, footer }: M
WebkitBackdropFilter: 'blur(12px)',
}}
>
<XStack alignItems="center" space="$2">
<XStack alignItems="center" gap="$2">
{onBack ? (
<Pressable onPress={onBack}>
<XStack alignItems="center" space="$1.5">
<XStack alignItems="center" gap="$1.5">
<ChevronLeft size={18} color={primary} />
<Text fontSize="$sm" color={primary} fontWeight="600">
{t('actions.back', 'Back')}
@@ -63,7 +63,7 @@ export function MobileScaffold({ title, onBack, rightSlot, children, footer }: M
</XStack>
</XStack>
<YStack flex={1} padding="$4" space="$3" paddingBottom={footer ? '$14' : '$5'}>
<YStack flex={1} padding="$4" gap="$3" paddingBottom={footer ? '$14' : '$5'}>
{children}
</YStack>

View File

@@ -38,9 +38,9 @@ export function SetupChecklist({
const content = (
<YStack>
<Pressable onPress={() => setCollapsed(!collapsed)}>
<YStack padding="$3" paddingVertical="$2.5" space="$2">
<YStack padding="$3" paddingVertical="$2.5" gap="$2">
<XStack alignItems="center" justifyContent="space-between">
<XStack alignItems="center" space="$2">
<XStack alignItems="center" gap="$2">
<Text fontSize="$xs" fontWeight="700" color={theme.muted} textTransform="uppercase" letterSpacing={1}>
{title}
</Text>
@@ -51,7 +51,7 @@ export function SetupChecklist({
)}
</XStack>
<XStack alignItems="center" space="$2">
<XStack alignItems="center" gap="$2">
<Text fontSize="$xs" color={theme.muted} fontWeight="600">
{completedCount}/{steps.length}
</Text>
@@ -89,7 +89,7 @@ export function SetupChecklist({
backgroundColor={isNext ? theme.surfaceMuted : 'transparent'}
onPress={() => navigate(adminPath(step.targetPath))}
title={
<XStack alignItems="center" space="$2.5">
<XStack alignItems="center" gap="$2.5">
{step.isComplete ? (
<CheckCircle2 size={18} color={theme.successText} />
) : isNext ? (

View File

@@ -77,7 +77,7 @@ export function MobileSheet({
showsVerticalScrollIndicator={false}
{...({ contentContainerStyle: { paddingBottom: 6 } } as any)}
>
<YStack space={contentSpacing}>
<YStack gap={contentSpacing}>
<XStack alignItems="center" justifyContent="space-between">
<Text fontSize="$md" fontWeight="800" color={textStrong}>
{title}

View File

@@ -106,7 +106,7 @@ export function UserMenuSheet({ open, onClose, user, isMember, navigate }: UserM
borderLeftWidth={1}
borderColor={theme.border}
padding="$4"
space="$3"
gap="$3"
style={{
transform: open ? 'translateX(0)' : 'translateX(100%)',
transition: 'transform 220ms ease',
@@ -133,7 +133,7 @@ export function UserMenuSheet({ open, onClose, user, isMember, navigate }: UserM
</Pressable>
</XStack>
<XStack alignItems="center" space="$3">
<XStack alignItems="center" gap="$3">
<XStack
width={48}
height={48}
@@ -158,7 +158,7 @@ export function UserMenuSheet({ open, onClose, user, isMember, navigate }: UserM
<Separator backgroundColor={theme.border} opacity={0.6} />
<YStack space="$2">
<YStack gap="$2">
<Text fontSize="$xs" fontWeight="700" color={theme.muted} textTransform="uppercase" letterSpacing={1}>
{t('mobileProfile.settings', 'Einstellungen')}
</Text>
@@ -172,7 +172,7 @@ export function UserMenuSheet({ open, onClose, user, isMember, navigate }: UserM
paddingHorizontal="$3"
onPress={() => handleNavigate(item.path)}
title={
<XStack alignItems="center" space="$2">
<XStack alignItems="center" gap="$2">
<XStack
width={28}
height={28}
@@ -197,7 +197,7 @@ export function UserMenuSheet({ open, onClose, user, isMember, navigate }: UserM
</YGroup>
</YStack>
<YStack space="$2">
<YStack gap="$2">
<Text fontSize="$xs" fontWeight="700" color={theme.muted} textTransform="uppercase" letterSpacing={1}>
{t('settings.appearance.title', 'Darstellung')}
</Text>
@@ -207,7 +207,7 @@ export function UserMenuSheet({ open, onClose, user, isMember, navigate }: UserM
paddingVertical="$2"
paddingHorizontal="$3"
title={
<XStack space="$2" alignItems="center">
<XStack gap="$2" alignItems="center">
<Text fontSize="$sm" color={theme.textStrong}>
{t('mobileProfile.language', 'Sprache')}
</Text>
@@ -235,7 +235,7 @@ export function UserMenuSheet({ open, onClose, user, isMember, navigate }: UserM
paddingVertical="$2"
paddingHorizontal="$3"
title={
<XStack space="$2" alignItems="center">
<XStack gap="$2" alignItems="center">
<Text fontSize="$sm" color={theme.textStrong}>
{t('mobileProfile.theme', 'Dark Mode')}
</Text>