Refine guest v2 gallery empty states
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 22:16:02 +01:00
parent bcf5f0eb20
commit 4f910b2d2a

View File

@@ -2,7 +2,7 @@ import React from 'react';
import { YStack, XStack } from '@tamagui/stacks';
import { SizableText as Text } from '@tamagui/text';
import { Button } from '@tamagui/button';
import { Image as ImageIcon, Filter } from 'lucide-react';
import { Camera, Image as ImageIcon, Filter } from 'lucide-react';
import AppShell from '../components/AppShell';
import PhotoFrameTile from '../components/PhotoFrameTile';
import { useEventData } from '../context/EventDataContext';
@@ -56,6 +56,7 @@ export default function GalleryScreen() {
const [loading, setLoading] = React.useState(false);
const { data: delta } = usePollGalleryDelta(token ?? null, { locale });
const [filter, setFilter] = React.useState<GalleryFilter>('latest');
const uploadPath = React.useMemo(() => buildEventPath(token ?? null, '/upload'), [token]);
React.useEffect(() => {
if (!token) {
@@ -139,6 +140,8 @@ export default function GalleryScreen() {
const displayPhotos = filteredPhotos;
const leftColumn = displayPhotos.filter((_, index) => index % 2 === 0);
const rightColumn = displayPhotos.filter((_, index) => index % 2 === 1);
const isEmpty = !loading && displayPhotos.length === 0;
const isSingle = !loading && displayPhotos.length === 1;
React.useEffect(() => {
if (filter === 'photobooth' && !photos.some((photo) => photo.ingestSource === 'photobooth')) {
@@ -265,10 +268,88 @@ export default function GalleryScreen() {
</XStack>
</YStack>
<XStack gap="$3">
<YStack flex={1} gap="$3">
{(loading || leftColumn.length === 0 ? Array.from({ length: 5 }, (_, index) => index) : leftColumn).map(
(tile, index) => {
{isEmpty ? (
<YStack
padding="$4"
borderRadius="$card"
backgroundColor="$surface"
borderWidth={1}
borderColor={cardBorder}
gap="$3"
alignItems="center"
style={{
boxShadow: cardShadow,
}}
>
<YStack
width={64}
height={64}
borderRadius={20}
backgroundColor="$primary"
alignItems="center"
justifyContent="center"
style={{ boxShadow: cardShadow }}
>
<Camera size={28} color="#FFFFFF" />
</YStack>
<Text fontSize="$4" fontWeight="$7" textAlign="center">
{t('galleryPage.emptyTitle', 'Noch keine Fotos')}
</Text>
<Text fontSize="$2" color="$color" opacity={0.7} textAlign="center">
{t('galleryPage.emptyDescription', 'Lade das erste Foto hoch und starte die Galerie.')}
</Text>
<Button
size="$3"
backgroundColor="$primary"
borderRadius="$pill"
onPress={() => navigate(uploadPath)}
>
<Text fontSize="$2" fontWeight="$7" color="#FFFFFF">
{t('galleryPage.emptyCta', 'Foto hochladen')}
</Text>
</Button>
</YStack>
) : isSingle ? (
<YStack gap="$3">
<Button unstyled onPress={() => openLightbox(displayPhotos[0].id)}>
<PhotoFrameTile height={360} borderRadius="$card">
<YStack flex={1} width="100%" height="100%" alignItems="center" justifyContent="center">
<img
src={displayPhotos[0].imageUrl}
alt={t('galleryPage.photo.alt', { id: displayPhotos[0].id }, 'Photo {id}')}
style={{ width: '100%', height: '100%', objectFit: 'cover' }}
/>
</YStack>
</PhotoFrameTile>
</Button>
<Button unstyled onPress={() => navigate(uploadPath)}>
<PhotoFrameTile height={160} borderRadius="$card">
<YStack flex={1} alignItems="center" justifyContent="center" gap="$2" padding="$3">
<YStack
width={48}
height={48}
borderRadius={16}
backgroundColor="$primary"
alignItems="center"
justifyContent="center"
style={{ boxShadow: cardShadow }}
>
<Camera size={20} color="#FFFFFF" />
</YStack>
<Text fontSize="$3" fontWeight="$7" textAlign="center">
{t('galleryPage.promptTitle', 'Füge dein nächstes Foto hinzu')}
</Text>
<Text fontSize="$2" color="$color" opacity={0.7} textAlign="center">
{t('galleryPage.promptDescription', 'Teile den Moment mit allen Gästen.')}
</Text>
</YStack>
</PhotoFrameTile>
</Button>
</YStack>
) : (
<XStack gap="$3">
<YStack flex={1} gap="$3">
{(loading ? Array.from({ length: 5 }, (_, index) => index) : leftColumn).map((tile, index) => {
if (typeof tile === 'number') {
return <PhotoFrameTile key={`left-${tile}`} height={140 + (index % 3) * 24} shimmer shimmerDelayMs={200 + index * 120} />;
}
@@ -289,12 +370,10 @@ export default function GalleryScreen() {
</PhotoFrameTile>
</Button>
);
}
)}
</YStack>
<YStack flex={1} gap="$3">
{(loading || rightColumn.length === 0 ? Array.from({ length: 5 }, (_, index) => index) : rightColumn).map(
(tile, index) => {
})}
</YStack>
<YStack flex={1} gap="$3">
{(loading ? Array.from({ length: 5 }, (_, index) => index) : rightColumn).map((tile, index) => {
if (typeof tile === 'number') {
return <PhotoFrameTile key={`right-${tile}`} height={120 + (index % 3) * 28} shimmer shimmerDelayMs={260 + index * 140} />;
}
@@ -315,10 +394,35 @@ export default function GalleryScreen() {
</PhotoFrameTile>
</Button>
);
}
)}
</YStack>
</XStack>
})}
{!loading && rightColumn.length === 0 ? (
<Button unstyled onPress={() => navigate(uploadPath)}>
<PhotoFrameTile height={180}>
<YStack flex={1} alignItems="center" justifyContent="center" gap="$2" padding="$3">
<YStack
width={46}
height={46}
borderRadius={16}
backgroundColor="$primary"
alignItems="center"
justifyContent="center"
style={{ boxShadow: cardShadow }}
>
<Camera size={20} color="#FFFFFF" />
</YStack>
<Text fontSize="$3" fontWeight="$7" textAlign="center">
{t('galleryPage.promptTitle', 'Füge dein nächstes Foto hinzu')}
</Text>
<Text fontSize="$2" color="$color" opacity={0.7} textAlign="center">
{t('galleryPage.promptDescription', 'Teile den Moment mit allen Gästen.')}
</Text>
</YStack>
</PhotoFrameTile>
</Button>
) : null}
</YStack>
</XStack>
)}
<YStack
padding="$4"