import React, { useEffect, useState } from 'react'; import { API_URL } from '../../services/api'; import { getZoneramaManifestWithFallbacks } from '../../services/zonerama'; import { Link as RouterLink } from 'react-router-dom'; import { Box, Heading, SimpleGrid, Image, Text, VStack, HStack, Button, Skeleton, Badge, useColorModeValue, } from '@chakra-ui/react'; import { Calendar, Image as ImageIcon, ExternalLink, ArrowRight } from 'lucide-react'; import { useTranslation } from 'react-i18next'; interface Album { id: string; title: string; url: string; date: string; photos_count: number; views_count?: number; photos: Array<{ id: string; page_url: string; image_1500: string; }>; } const resolveBackendUrl = (path: string) => { try { if (/^https?:\/\//i.test(path)) return path; if (path.startsWith('/cache') || path.startsWith('/uploads') || path.startsWith('/api/')) { // Prefer explicit asset base (backend origin) when provided; fallback to API_URL origin let origin = ''; try { const assetBase = (process.env.REACT_APP_ASSET_BASE_URL || '').trim(); if (assetBase) { origin = new URL(assetBase, typeof window !== 'undefined' ? window.location.origin : 'http://localhost:3000').origin; } } catch {} if (!origin) { try { origin = new URL(API_URL, typeof window !== 'undefined' ? window.location.origin : 'http://localhost:3000').origin; } catch {} } const abs = new URL(path, origin || (typeof window !== 'undefined' ? window.location.origin : 'http://localhost:3000')); return abs.toString(); } return path; } catch { return path; } }; const GallerySection: React.FC<{ zoneramaUrl?: string | null }> = ({ zoneramaUrl }) => { const { t } = useTranslation(); const [albums, setAlbums] = useState([]); const [loading, setLoading] = useState(true); const [profileUrl, setProfileUrl] = useState(null); // Dark mode colors const cardBg = useColorModeValue('white', 'gray.800'); const headingColor = useColorModeValue('gray.800', 'gray.100'); const textColor = useColorModeValue('gray.600', 'gray.300'); const infoBg = useColorModeValue('blue.50', 'blue.900'); const infoBorder = useColorModeValue('blue.200', 'blue.700'); const infoText = useColorModeValue('blue.700', 'blue.200'); useEffect(() => { const fetchAlbums = async () => { setLoading(true); try { // Load from both sources and combine const [profileRes, albumsRes] = await Promise.allSettled([ fetch(resolveBackendUrl('/cache/prefetch/zonerama_profile.json'), { cache: 'no-cache' }), fetch(resolveBackendUrl('/cache/prefetch/zonerama_albums.json'), { cache: 'no-cache' }) ]); let combinedAlbums: Album[] = []; // Get profile albums (newest/main source) if (profileRes.status === 'fulfilled' && profileRes.value.ok) { const profileData = await profileRes.value.json(); if (profileData && profileData.input_link) { setProfileUrl(profileData.input_link); } combinedAlbums = [...(profileData.albums || [])]; } // Get blog-related albums (additional source) if (albumsRes.status === 'fulfilled' && albumsRes.value.ok) { const albumsData = await albumsRes.value.json(); const blogAlbums = Array.isArray(albumsData) ? albumsData : []; // Filter out albums with empty/invalid data and avoid duplicates const validBlogAlbums = blogAlbums.filter((album: any) => album.id && album.title && !combinedAlbums.some(existing => existing.id === album.id) ); combinedAlbums = [...combinedAlbums, ...validBlogAlbums]; } // Fallback: synthesize albums from manifest/picks when both sources are empty or invalid if ((!combinedAlbums || combinedAlbums.length === 0)) { try { const items = await getZoneramaManifestWithFallbacks(); if (Array.isArray(items) && items.length > 0) { const byAlbum: Record = {} as any; items.forEach((it) => { const aid = String(it.album_id || 'unknown'); (byAlbum[aid] = byAlbum[aid] || []).push(it); }); const synthesized: Album[] = Object.entries(byAlbum).map(([aid, arr]) => ({ id: aid, title: 'Album', url: (arr[0] as any).page_url || '#', date: '', photos_count: arr.length, photos: arr.slice(0, 12).map((p: any) => ({ id: String(p.id || ''), page_url: String(p.page_url || ''), image_1500: String(p.src || p.local || '') })), })); combinedAlbums = synthesized; } } catch {} } // Sort by date (newest first) combinedAlbums.sort((a, b) => { const parseDate = (dateStr: string) => { if (!dateStr) return new Date(0); const parts = dateStr.split(/[.\s]+/).filter(Boolean); if (parts.length === 3) { const [day, month, year] = parts; return new Date(`${year}-${month.padStart(2, '0')}-${day.padStart(2, '0')}`); } return new Date(dateStr); }; return parseDate(b.date).getTime() - parseDate(a.date).getTime(); }); // Get the 3 most recent albums const recentAlbums = combinedAlbums.slice(0, 3); setAlbums(recentAlbums); } catch (err) { console.error('Error loading albums:', err); } finally { setLoading(false); } }; fetchAlbums(); }, []); if (loading) { return ( Galerie {[1, 2, 3].map((i) => ( ))} ); } if (albums.length === 0) { return null; } return ( {/* Header */} {t('homepage.gallery')} {t('gallery.latest_albums')} {/* Zonerama Attribution (single source of truth) */} © Fotografie z{' '} Zonerama {/* Albums Grid */} {albums.map((album) => { const coverPhoto = album.photos && album.photos.length > 0 ? album.photos[0] : null; return ( {/* Cover Image */} {coverPhoto ? ( {album.title} ) : ( )} {/* Album Info */} {album.title} {album.date && ( {album.date} )} {album.photos_count} foto {album.views_count !== undefined && album.views_count > 0 && ( {album.views_count} zhlédnutí )} ); })} {/* Bottom CTA */} ); }; export default GallerySection;