import React from 'react'; import { useNavigate, useParams } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { Image as ImageIcon, RefreshCcw, Search, Filter } from 'lucide-react'; import { YStack, XStack } from '@tamagui/stacks'; import { SizableText as Text } from '@tamagui/text'; import { Pressable } from '@tamagui/react-native-web-lite'; import { MobileScaffold } from './components/Scaffold'; import { MobileCard, PillBadge, CTAButton } from './components/Primitives'; import { BottomNav } from './components/BottomNav'; import { getEventPhotos, updatePhotoVisibility, featurePhoto, unfeaturePhoto, TenantPhoto } from '../api'; import toast from 'react-hot-toast'; import { isAuthError } from '../auth/tokens'; import { getApiErrorMessage } from '../lib/apiError'; import { useMobileNav } from './hooks/useMobileNav'; import { MobileSheet } from './components/Sheet'; type FilterKey = 'all' | 'featured' | 'hidden'; export default function MobileEventPhotosPage() { const { slug: slugParam } = useParams<{ slug?: string }>(); const slug = slugParam ?? null; const navigate = useNavigate(); const { t } = useTranslation('management'); const [photos, setPhotos] = React.useState([]); const [filter, setFilter] = React.useState('all'); const [page, setPage] = React.useState(1); const [loading, setLoading] = React.useState(true); const [error, setError] = React.useState(null); const [busyId, setBusyId] = React.useState(null); const [totalCount, setTotalCount] = React.useState(0); const [hasMore, setHasMore] = React.useState(false); const { go } = useMobileNav(slug); const [search, setSearch] = React.useState(''); const [showFilters, setShowFilters] = React.useState(false); const [uploaderFilter, setUploaderFilter] = React.useState(''); const [onlyFeatured, setOnlyFeatured] = React.useState(false); const [onlyHidden, setOnlyHidden] = React.useState(false); const [lightbox, setLightbox] = React.useState(null); const load = React.useCallback(async () => { if (!slug) return; setLoading(true); setError(null); try { const result = await getEventPhotos(slug, { page, perPage: 20, sort: 'desc', featured: filter === 'featured' || onlyFeatured, status: filter === 'hidden' || onlyHidden ? 'hidden' : undefined, search: search || undefined, uploader: uploaderFilter || undefined, }); setPhotos((prev) => (page === 1 ? result.photos : [...prev, ...result.photos])); setTotalCount(result.meta?.total ?? result.photos.length); const lastPage = result.meta?.last_page ?? 1; setHasMore(page < lastPage); } catch (err) { if (!isAuthError(err)) { setError(getApiErrorMessage(err, t('events.errors.loadFailed', 'Fotos konnten nicht geladen werden.'))); } } finally { setLoading(false); } }, [slug, filter, t, page]); React.useEffect(() => { void load(); }, [load]); React.useEffect(() => { setPage(1); }, [filter, slug]); async function toggleVisibility(photo: TenantPhoto) { if (!slug) return; setBusyId(photo.id); try { const updated = await updatePhotoVisibility(slug, photo.id, photo.status === 'hidden'); setPhotos((prev) => prev.map((p) => (p.id === photo.id ? updated : p))); toast.success( updated.status === 'hidden' ? t('events.photos.hideSuccess', 'Foto versteckt') : t('events.photos.showSuccess', 'Foto eingeblendet'), ); } catch (err) { if (!isAuthError(err)) { setError(getApiErrorMessage(err, t('events.errors.saveFailed', 'Sichtbarkeit konnte nicht geändert werden.'))); toast.error(t('events.photos.hideFailed', 'Sichtbarkeit konnte nicht geändert werden.')); } } finally { setBusyId(null); } } async function toggleFeature(photo: TenantPhoto) { if (!slug) return; setBusyId(photo.id); try { const updated = photo.is_featured ? await unfeaturePhoto(slug, photo.id) : await featurePhoto(slug, photo.id); setPhotos((prev) => prev.map((p) => (p.id === photo.id ? updated : p))); toast.success( updated.is_featured ? t('events.photos.featureSuccess', 'Als Highlight markiert') : t('events.photos.unfeatureSuccess', 'Highlight entfernt'), ); } catch (err) { if (!isAuthError(err)) { setError(getApiErrorMessage(err, t('events.errors.saveFailed', 'Feature konnte nicht geändert werden.'))); toast.error(t('events.photos.featureFailed', 'Feature konnte nicht geändert werden.')); } } finally { setBusyId(null); } } return ( navigate(-1)} rightSlot={ setShowFilters(true)}> load()}> } footer={ } > {error ? ( {error} ) : null} { setSearch(e.target.value); setPage(1); }} placeholder={t('events.photos.search', 'Search photos')} style={{ width: '100%', height: 38, borderRadius: 10, border: '1px solid #e5e7eb', padding: '0 12px', fontSize: 13, background: 'white', marginBottom: 12, }} /> {(['all', 'featured', 'hidden'] as FilterKey[]).map((key) => ( setFilter(key)} style={{ flex: 1 }}> {key === 'all' ? t('common.all', 'All') : key === 'featured' ? t('events.photos.featured', 'Featured') : t('events.photos.hidden', 'Hidden')} ))} {loading ? ( {Array.from({ length: 4 }).map((_, idx) => ( ))} ) : photos.length === 0 ? ( {t('events.photos.empty', 'Keine Fotos gefunden.')} ) : ( {t('events.photos.count', '{{count}} Fotos', { count: totalCount })}
{photos.map((photo) => ( setLightbox(photo)}> {photo.caption {photo.is_featured ? {t('events.photos.featured', 'Featured')} : null} {photo.status === 'hidden' ? {t('events.photos.hidden', 'Hidden')} : null} ))}
{hasMore ? ( setPage((prev) => prev + 1)} /> ) : null}
)} {lightbox ? (
{lightbox.caption {lightbox.uploader_name || t('events.photos.guest', 'Gast')} ❤️ {lightbox.likes_count ?? 0} toggleFeature(lightbox)} /> toggleVisibility(lightbox)} /> setLightbox(null)} />
) : null} setShowFilters(false)} title={t('events.photos.filters', 'Filter')} footer={ { setPage(1); setShowFilters(false); void load(); }} /> } > setUploaderFilter(e.target.value)} placeholder={t('events.photos.uploaderPlaceholder', 'Name or email')} style={inputStyle} /> { setUploaderFilter(''); setOnlyFeatured(false); setOnlyHidden(false); }} />
); }