upgrade to tamagui v2 and guest pwa overhaul
This commit is contained in:
@@ -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)'),
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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'}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)}
|
||||
|
||||
@@ -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}>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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 ? (
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user