import React, { useState, useEffect } from 'react'; import { useParams, useLocation, useNavigate } from 'react-router-dom'; import { Dialog, DialogContent } from '@/components/ui/dialog'; import { Button } from '@/components/ui/button'; import { Heart, ChevronLeft, ChevronRight, X, Share2, MessageSquare, Copy } from 'lucide-react'; import { likePhoto, createPhotoShareLink } from '../services/photosApi'; import { useTranslation } from '../i18n/useTranslation'; import { useToast } from '../components/ToastHost'; type Photo = { id: number; file_path?: string; thumbnail_path?: string; likes_count?: number; created_at?: string; task_id?: number; task_title?: string; }; type Task = { id: number; title: string }; interface Props { photos?: Photo[]; currentIndex?: number; onClose?: () => void; onIndexChange?: (index: number) => void; token?: string; } export default function PhotoLightbox({ photos, currentIndex, onClose, onIndexChange, token }: Props) { const params = useParams<{ token?: string; photoId?: string }>(); const location = useLocation(); const navigate = useNavigate(); const photoId = params.photoId; const eventToken = params.token || token; const { t, locale } = useTranslation(); const toast = useToast(); const [standalonePhoto, setStandalonePhoto] = useState(null); const [task, setTask] = useState(null); const [taskLoading, setTaskLoading] = useState(false); const [likes, setLikes] = useState(0); const [liked, setLiked] = useState(false); const [shareSheet, setShareSheet] = useState<{ url: string | null; loading: boolean }>({ url: null, loading: false, }); // Determine mode and photo const isStandalone = !photos || photos.length === 0; const currentPhotos = isStandalone ? (standalonePhoto ? [standalonePhoto] : []) : photos || []; const currentIndexVal = isStandalone ? 0 : (currentIndex || 0); const photo = currentPhotos[currentIndexVal]; // Fallback onClose for standalone const handleClose = onClose || (() => navigate(-1)); // Fetch single photo for standalone mode useEffect(() => { if (isStandalone && photoId && !standalonePhoto && eventToken) { const fetchPhoto = async () => { try { const res = await fetch(`/api/v1/photos/${photoId}?locale=${encodeURIComponent(locale)}`, { headers: { Accept: 'application/json', 'X-Locale': locale, }, }); if (res.ok) { const fetchedPhoto: Photo = await res.json(); setStandalonePhoto(fetchedPhoto); // Check state for initial photo if (location.state?.photo) { setStandalonePhoto(location.state.photo); } } else { toast.push({ text: t('lightbox.errors.notFound'), type: 'error' }); } } catch (err) { console.warn('Standalone photo load failed', err); toast.push({ text: t('lightbox.errors.loadFailed'), type: 'error' }); } }; fetchPhoto(); } }, [isStandalone, photoId, eventToken, standalonePhoto, location.state, t, locale, toast]); // Update likes when photo changes React.useEffect(() => { if (photo) { setLikes(photo.likes_count ?? 0); // Check if liked from localStorage try { const raw = localStorage.getItem('liked-photo-ids'); const likedIds = raw ? JSON.parse(raw) : []; setLiked(likedIds.includes(photo.id)); } catch { setLiked(false); } } }, [photo]); const touchRef = React.useRef(null); const startX = React.useRef(0); const currentX = React.useRef(0); const handleTouchStart = (e: React.TouchEvent) => { startX.current = e.touches[0].clientX; }; const handleTouchMove = (e: React.TouchEvent) => { if (!touchRef.current) return; currentX.current = e.touches[0].clientX; const deltaX = currentX.current - startX.current; touchRef.current.style.transform = `translateX(${deltaX}px)`; }; const handleTouchEnd = () => { if (!touchRef.current) return; const deltaX = currentX.current - startX.current; const threshold = 50; // pixels touchRef.current.style.transform = 'translateX(0)'; if (Math.abs(deltaX) > threshold) { if (deltaX > 0 && currentIndexVal > 0) { // Swipe right - previous onIndexChange?.(currentIndexVal - 1); } else if (deltaX < 0 && currentIndexVal < currentPhotos.length - 1) { // Swipe left - next onIndexChange?.(currentIndexVal + 1); } } }; // Load task info if photo has task_id and event key is available React.useEffect(() => { if (!photo?.task_id || !eventToken) { setTask(null); setTaskLoading(false); return; } const taskId = photo.task_id; (async () => { setTaskLoading(true); try { const res = await fetch( `/api/v1/events/${encodeURIComponent(eventToken)}/tasks?locale=${encodeURIComponent(locale)}`, { headers: { Accept: 'application/json', 'X-Locale': locale, }, } ); if (res.ok) { const tasks = (await res.json()) as Task[]; const foundTask = tasks.find((t) => t.id === taskId); if (foundTask) { setTask({ id: foundTask.id, title: foundTask.title || t('lightbox.fallbackTitle').replace('{id}', `${taskId}`) }); } else { setTask({ id: taskId, title: t('lightbox.unknownTitle').replace('{id}', `${taskId}`) }); } } else { setTask({ id: taskId, title: t('lightbox.unknownTitle').replace('{id}', `${taskId}`) }); } } catch (error) { console.error('Failed to load task:', error); setTask({ id: taskId, title: t('lightbox.unknownTitle').replace('{id}', `${taskId}`) }); } finally { setTaskLoading(false); } })(); }, [photo?.task_id, eventToken, t, locale]); async function onLike() { if (liked || !photo) return; setLiked(true); try { const count = await likePhoto(photo.id); setLikes(count); // Update localStorage try { const raw = localStorage.getItem('liked-photo-ids'); const arr: number[] = raw ? JSON.parse(raw) : []; if (!arr.includes(photo.id)) { localStorage.setItem('liked-photo-ids', JSON.stringify([...arr, photo.id])); } } catch (storageError) { console.warn('Failed to persist liked photo IDs', storageError); } } catch (error) { console.error('Like failed:', error); setLiked(false); } } const shareTitle = photo?.task_title ?? task?.title ?? t('share.title', 'Geteiltes Foto'); const shareText = t('share.shareText', { event: shareTitle || 'Fotospiel' }); const WhatsAppIcon = (props: React.SVGProps) => ( ); async function openShareSheet() { if (!photo || !eventToken) return; setShareSheet({ url: null, loading: true }); try { const payload = await createPhotoShareLink(eventToken, photo.id); setShareSheet({ url: payload.url, loading: false }); } catch (error) { console.error('share failed', error); toast.push({ text: t('share.error', 'Teilen fehlgeschlagen'), type: 'error' }); setShareSheet({ url: null, loading: false }); } } function shareWhatsApp(url?: string | null) { if (!url) return; const waUrl = `https://wa.me/?text=${encodeURIComponent(`${shareText} ${url}`)}`; window.open(waUrl, '_blank', 'noopener'); setShareSheet({ url: null, loading: false }); } function shareMessages(url?: string | null) { if (!url) return; const smsUrl = `sms:?&body=${encodeURIComponent(`${shareText} ${url}`)}`; window.open(smsUrl, '_blank', 'noopener'); setShareSheet({ url: null, loading: false }); } async function copyLink(url?: string | null) { if (!url) return; try { await navigator.clipboard?.writeText(url); toast.push({ text: t('share.copySuccess', 'Link kopiert!') }); } catch { toast.push({ text: t('share.copyError', 'Link konnte nicht kopiert werden.'), type: 'error' }); } finally { setShareSheet({ url: null, loading: false }); } } function closeShareSheet() { setShareSheet({ url: null, loading: false }); } function onOpenChange(open: boolean) { if (!open) handleClose(); } return ( {/* Header with controls */}
{currentIndexVal > 0 && ( )} {currentIndexVal < currentPhotos.length - 1 && ( )}
{/* Task Info Overlay */} {task && (
{t('lightbox.taskLabel')}: {task.title}
{taskLoading && (
{t('lightbox.loadingTask')}
)}
)} {/* Photo Display */}
{t('lightbox.photoAlt') { console.error('Image load error:', e); (e.target as HTMLImageElement).style.display = 'none'; }} />
{/* Loading state for task */} {taskLoading && !task && (
{t('lightbox.loadingTask')}
)} {(shareSheet.url !== null || shareSheet.loading) && (

{t('share.title', 'Geteiltes Foto')}

#{photo?.id}

)}
); }