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

@@ -179,7 +179,7 @@ function PhotoGridTile({
)}
{badges.length ? (
<XStack position="absolute" top={6} left={6} space="$1.5">
<XStack position="absolute" top={6} left={6} gap="$1.5">
{badges.map((label) => (
<PhotoStatusTag key={`${photo.id}-${label}`} label={label} />
))}
@@ -194,7 +194,7 @@ function PhotoGridTile({
padding="$1"
borderRadius={12}
backgroundColor={overlayBg}
space="$2"
gap="$2"
justifyContent="space-between"
>
{actions.map((action) => (
@@ -1034,7 +1034,7 @@ export default function MobileEventControlRoomPage() {
}, [queuedActions, slug]);
const headerActions = (
<XStack space="$2">
<XStack gap="$2">
<HeaderActionButton
onPress={() => {
if (activeTab === 'moderation') {
@@ -1070,7 +1070,7 @@ export default function MobileEventControlRoomPage() {
value={activeTab}
onValueChange={(val) => setActiveTab(val as 'moderation' | 'live')}
header={(
<YStack space="$2">
<YStack gap="$2">
<XStack justifyContent="flex-end">
<ContextHelpLink slug="control-room-moderation" />
</XStack>
@@ -1094,8 +1094,8 @@ export default function MobileEventControlRoomPage() {
</XStack>
</Accordion.Trigger>
<Accordion.Content {...({ paddingTop: '$2' } as any)}>
<YStack space="$3">
<YStack space="$2">
<YStack gap="$3">
<YStack gap="$2">
<Text fontSize="$sm" fontWeight="800" color={text}>
{t('controlRoom.automation.title', 'Automation')}
</Text>
@@ -1177,7 +1177,7 @@ export default function MobileEventControlRoomPage() {
</MobileField>
</YStack>
<YStack space="$2">
<YStack gap="$2">
<Text fontSize="$sm" fontWeight="800" color={text}>
{t('controlRoom.automation.uploaders.title', 'Uploader overrides')}
</Text>
@@ -1195,7 +1195,7 @@ export default function MobileEventControlRoomPage() {
'Uploads from these devices skip the approval queue.',
)}
>
<YStack space="$2">
<YStack gap="$2">
<MobileSelect
value={trustedUploaderSelection}
onChange={(event) => setTrustedUploaderSelection(event.target.value)}
@@ -1215,7 +1215,7 @@ export default function MobileEventControlRoomPage() {
/>
</YStack>
{trustedUploaders.length ? (
<YStack space="$2">
<YStack gap="$2">
{trustedUploaders.map((rule) => (
<XStack
key={`trusted-${rule.device_id}`}
@@ -1227,7 +1227,7 @@ export default function MobileEventControlRoomPage() {
borderColor={border}
backgroundColor={surfaceMuted}
>
<YStack space="$0.5">
<YStack gap="$0.5">
<Text fontSize="$sm" fontWeight="700" color={text}>
{rule.label ?? t('common.anonymous', 'Anonymous')}
</Text>
@@ -1267,7 +1267,7 @@ export default function MobileEventControlRoomPage() {
'Uploads from these devices always need approval.',
)}
>
<YStack space="$2">
<YStack gap="$2">
<MobileSelect
value={forceReviewSelection}
onChange={(event) => setForceReviewSelection(event.target.value)}
@@ -1287,7 +1287,7 @@ export default function MobileEventControlRoomPage() {
/>
</YStack>
{forceReviewUploaders.length ? (
<YStack space="$2">
<YStack gap="$2">
{forceReviewUploaders.map((rule) => (
<XStack
key={`force-${rule.device_id}`}
@@ -1299,7 +1299,7 @@ export default function MobileEventControlRoomPage() {
borderColor={border}
backgroundColor={surfaceMuted}
>
<YStack space="$0.5">
<YStack gap="$0.5">
<Text fontSize="$sm" fontWeight="700" color={text}>
{rule.label ?? t('common.anonymous', 'Anonymous')}
</Text>
@@ -1344,11 +1344,11 @@ export default function MobileEventControlRoomPage() {
value: 'moderation',
label: t('controlRoom.tabs.moderation', 'Moderation'),
content: (
<YStack space="$2">
<YStack gap="$2">
{queuedEventCount > 0 ? (
<MobileCard>
<XStack alignItems="center" justifyContent="space-between" space="$2">
<YStack space="$1" flex={1}>
<XStack alignItems="center" justifyContent="space-between" gap="$2">
<YStack gap="$1" flex={1}>
<Text fontSize="$sm" fontWeight="700" color={text}>
{t('mobilePhotos.queueTitle', 'Changes waiting to sync')}
</Text>
@@ -1371,7 +1371,7 @@ export default function MobileEventControlRoomPage() {
) : null}
<MobileCard>
<YStack space="$2">
<YStack gap="$2">
<Text fontSize="$xs" fontWeight="800" color={text}>
{t('mobilePhotos.filtersTitle', 'Filter')}
</Text>
@@ -1390,7 +1390,7 @@ export default function MobileEventControlRoomPage() {
value={moderationFilter}
onValueChange={(value: string) => value && setModerationFilter(value as ModerationFilter)}
>
<XStack space="$1.5">
<XStack gap="$1.5">
{MODERATION_FILTERS.map((option) => {
const active = option.value === moderationFilter;
const count = moderationCounts[option.value] ?? 0;
@@ -1407,7 +1407,7 @@ export default function MobileEventControlRoomPage() {
paddingHorizontal="$3"
pressStyle={{ opacity: 0.85 }}
>
<XStack alignItems="center" space="$1.5">
<XStack alignItems="center" gap="$1.5">
<Text fontSize="$xs" fontWeight="700" color={active ? '#fff' : muted}>
{t(option.labelKey, option.fallback)}
</Text>
@@ -1455,13 +1455,13 @@ export default function MobileEventControlRoomPage() {
) : null}
{moderationLoading && moderationPage === 1 ? (
<YStack space="$2">
<YStack gap="$2">
{Array.from({ length: 3 }).map((_, idx) => (
<SkeletonCard key={`moderation-skeleton-${idx}`} height={120} />
))}
</YStack>
) : moderationPhotos.length === 0 ? (
<MobileCard alignItems="center" justifyContent="center" space="$2">
<MobileCard alignItems="center" justifyContent="center" gap="$2">
<ImageIcon size={28} color={muted} />
<Text fontSize="$sm" fontWeight="700" color={text}>
{t('controlRoom.emptyModeration', 'No uploads match this filter.')}
@@ -1543,7 +1543,7 @@ export default function MobileEventControlRoomPage() {
value: 'live',
label: t('controlRoom.tabs.live', 'Live Show'),
content: (
<YStack space="$2">
<YStack gap="$2">
<MobileCard borderColor={border} backgroundColor="transparent">
<Text fontSize="$sm" color={muted}>
{t(
@@ -1559,7 +1559,7 @@ export default function MobileEventControlRoomPage() {
</MobileCard>
<MobileCard>
<YStack space="$2">
<YStack gap="$2">
<Text fontSize="$xs" fontWeight="800" color={text}>
{t('liveShowQueue.filterLabel', 'Live status')}
</Text>
@@ -1578,7 +1578,7 @@ export default function MobileEventControlRoomPage() {
value={liveStatusFilter}
onValueChange={(value: string) => value && setLiveStatusFilter(value as LiveShowQueueStatus)}
>
<XStack space="$1.5">
<XStack gap="$1.5">
{LIVE_STATUS_OPTIONS.map((option) => {
const active = option.value === liveStatusFilter;
const count = liveCounts[option.value] ?? 0;
@@ -1595,7 +1595,7 @@ export default function MobileEventControlRoomPage() {
paddingHorizontal="$3"
pressStyle={{ opacity: 0.85 }}
>
<XStack alignItems="center" space="$1.5">
<XStack alignItems="center" gap="$1.5">
<Text fontSize="$xs" fontWeight="700" color={active ? '#fff' : muted}>
{t(option.labelKey, option.fallback)}
</Text>
@@ -1631,13 +1631,13 @@ export default function MobileEventControlRoomPage() {
) : null}
{liveLoading && livePage === 1 ? (
<YStack space="$2">
<YStack gap="$2">
{Array.from({ length: 3 }).map((_, idx) => (
<SkeletonCard key={`live-skeleton-${idx}`} height={120} />
))}
</YStack>
) : livePhotos.length === 0 ? (
<MobileCard alignItems="center" justifyContent="center" space="$2">
<MobileCard alignItems="center" justifyContent="center" gap="$2">
<Text fontSize="$sm" fontWeight="700" color={text}>
{t('controlRoom.emptyLive', 'No photos waiting for Live Show.')}
</Text>