mirror of
https://github.com/Dvorinka/MyClubServer.git
synced 2026-06-04 10:42:57 +00:00
292 lines
9.0 KiB
TypeScript
292 lines
9.0 KiB
TypeScript
import React, { useEffect, useState } from 'react';
|
|
import { API_URL } from '../../services/api';
|
|
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';
|
|
|
|
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/')) {
|
|
const origin = new URL(API_URL, typeof window !== 'undefined' ? window.location.origin : 'http://localhost:3000').origin;
|
|
const abs = new URL(path, origin);
|
|
return abs.toString();
|
|
}
|
|
return path;
|
|
} catch {
|
|
return path;
|
|
}
|
|
};
|
|
|
|
const GallerySection: React.FC<{ zoneramaUrl?: string | null }> = ({ zoneramaUrl }) => {
|
|
const [albums, setAlbums] = useState<Album[]>([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const [profileUrl, setProfileUrl] = useState<string | null>(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];
|
|
}
|
|
|
|
// 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 (
|
|
<Box py={12}>
|
|
<VStack spacing={6} align="stretch">
|
|
<Heading size="xl">Galerie</Heading>
|
|
<SimpleGrid columns={{ base: 1, md: 2, lg: 3 }} spacing={6}>
|
|
{[1, 2, 3].map((i) => (
|
|
<Skeleton key={i} height="300px" borderRadius="lg" />
|
|
))}
|
|
</SimpleGrid>
|
|
</VStack>
|
|
</Box>
|
|
);
|
|
}
|
|
|
|
if (albums.length === 0) {
|
|
return null;
|
|
}
|
|
|
|
return (
|
|
<Box py={12}>
|
|
<VStack spacing={6} align="stretch">
|
|
{/* Header */}
|
|
<HStack justify="space-between" align="center" flexWrap="wrap">
|
|
<VStack align="start" spacing={1}>
|
|
<Heading size="xl" color={headingColor}>
|
|
Fotogalerie
|
|
</Heading>
|
|
<Text color={textColor} fontSize="sm">
|
|
Nejnovější alba z našich akcí
|
|
</Text>
|
|
</VStack>
|
|
|
|
<Button
|
|
as={RouterLink}
|
|
to="/galerie"
|
|
rightIcon={<ArrowRight size={18} />}
|
|
colorScheme="blue"
|
|
variant="outline"
|
|
size="md"
|
|
>
|
|
Zobrazit vše
|
|
</Button>
|
|
</HStack>
|
|
|
|
{/* Zonerama Attribution */}
|
|
<Box
|
|
bg={infoBg}
|
|
borderWidth="1px"
|
|
borderColor={infoBorder}
|
|
borderRadius="md"
|
|
px={4}
|
|
py={2}
|
|
>
|
|
<Text fontSize="xs" color={infoText}>
|
|
📸 Všechny fotografie jsou z platformy{' '}
|
|
<Text
|
|
as="a"
|
|
href={zoneramaUrl || profileUrl || 'https://zonerama.com'}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
fontWeight="600"
|
|
color="blue.600"
|
|
_hover={{ textDecoration: 'underline' }}
|
|
>
|
|
Zonerama
|
|
</Text>
|
|
</Text>
|
|
</Box>
|
|
|
|
{/* Albums Grid */}
|
|
<SimpleGrid columns={{ base: 1, md: 2, lg: 3 }} spacing={6}>
|
|
{albums.map((album) => {
|
|
const coverPhoto = album.photos && album.photos.length > 0
|
|
? album.photos[0]
|
|
: null;
|
|
|
|
return (
|
|
<Box
|
|
key={album.id}
|
|
as={RouterLink}
|
|
to={`/galerie/album/${album.id}`}
|
|
bg={cardBg}
|
|
borderRadius="lg"
|
|
overflow="hidden"
|
|
boxShadow="md"
|
|
transition="all 0.3s"
|
|
borderWidth="1px"
|
|
borderColor={useColorModeValue('gray.200', 'gray.700')}
|
|
_hover={{
|
|
transform: 'translateY(-8px)',
|
|
boxShadow: '2xl',
|
|
borderColor: useColorModeValue('gray.300', 'gray.600'),
|
|
}}
|
|
cursor="pointer"
|
|
>
|
|
{/* Cover Image */}
|
|
{coverPhoto ? (
|
|
<Image
|
|
src={coverPhoto.image_1500}
|
|
alt={album.title}
|
|
w="100%"
|
|
h="200px"
|
|
objectFit="cover"
|
|
loading="lazy"
|
|
/>
|
|
) : (
|
|
<Box
|
|
w="100%"
|
|
h="200px"
|
|
bg="gray.200"
|
|
display="flex"
|
|
alignItems="center"
|
|
justifyContent="center"
|
|
>
|
|
<ImageIcon size={48} color="gray" />
|
|
</Box>
|
|
)}
|
|
|
|
{/* Album Info */}
|
|
<VStack align="stretch" p={4} spacing={2}>
|
|
<Heading size="sm" color={headingColor} noOfLines={2} minH="40px">
|
|
{album.title}
|
|
</Heading>
|
|
|
|
<VStack spacing={2} fontSize="xs" color={textColor} align="stretch">
|
|
{album.date && (
|
|
<HStack spacing={1}>
|
|
<Calendar size={14} />
|
|
<Text>{album.date}</Text>
|
|
</HStack>
|
|
)}
|
|
<HStack spacing={1}>
|
|
<ImageIcon size={14} />
|
|
<Text>{album.photos_count} foto</Text>
|
|
</HStack>
|
|
</VStack>
|
|
|
|
{album.views_count !== undefined && album.views_count > 0 && (
|
|
<Badge colorScheme="purple" fontSize="2xs" alignSelf="flex-start">
|
|
{album.views_count} zhlédnutí
|
|
</Badge>
|
|
)}
|
|
</VStack>
|
|
</Box>
|
|
);
|
|
})}
|
|
</SimpleGrid>
|
|
|
|
{/* Bottom CTA */}
|
|
<Box textAlign="center" pt={4}>
|
|
<Button
|
|
as={RouterLink}
|
|
to="/galerie"
|
|
rightIcon={<ArrowRight size={18} />}
|
|
colorScheme="blue"
|
|
size="lg"
|
|
>
|
|
Zobrazit všechna alba
|
|
</Button>
|
|
</Box>
|
|
</VStack>
|
|
</Box>
|
|
);
|
|
};
|
|
|
|
export default GallerySection;
|