import { Box, AspectRatio, Text, useColorModeValue, SimpleGrid, Heading, HStack, Badge, Button, Link, Modal, ModalOverlay, ModalContent, ModalBody, ModalCloseButton, useDisclosure, Icon, VStack } from '@chakra-ui/react'; import { Link as RouterLink } from 'react-router-dom'; import { FaYoutube, FaPlay } from 'react-icons/fa'; import HorizontalScroller from '../ui/HorizontalScroller'; import { useClubTheme } from '../../contexts/ClubThemeContext'; import { usePublicSettings } from '../../hooks/usePublicSettings'; import { getCachedYouTube, YouTubeVideo } from '../../services/youtube'; import React, { useEffect, useMemo, useState } from 'react'; import CommentsSection from '../comments/CommentsSection'; type Props = { videos?: string[]; variant?: 'grid' | 'carousel'; }; type RenderItem = { key: string; title: string; embedUrl: string; thumbnail?: string; date?: string; // YYYY-MM-DD videoId?: string; }; const toEmbed = (idOrUrl: string): string => { // If a full URL is passed, try to extract the id; otherwise assume it's already an id // supports https://www.youtube.com/watch?v=ID or youtu.be/ID try { if (idOrUrl.includes('youtube.com') || idOrUrl.includes('youtu.be')) { const u = new URL(idOrUrl); if (u.hostname.includes('youtu.be')) { const id = u.pathname.replace('/', ''); return `https://www.youtube.com/embed/${id}`; } const id = u.searchParams.get('v'); if (id) return `https://www.youtube.com/embed/${id}`; } } catch {} // otherwise treat as id return `https://www.youtube.com/embed/${idOrUrl}`; }; const VideosSection: React.FC = ({ videos, variant }) => { const cardBg = useColorModeValue('white', 'gray.800'); const theme = useClubTheme(); const { data: settings } = usePublicSettings(); const [yt, setYt] = useState([]); const { isOpen, onOpen, onClose } = useDisclosure(); const [selectedVideo, setSelectedVideo] = useState(null); const titleOverrides: Record = (settings as any)?.videos_title_overrides || {}; // If admin explicitly disabled, respect it. Otherwise default to ON when there are manual videos configured // or when a YouTube URL is present for auto mode. const hasManualConfigured = Boolean((settings as any)?.videos_items?.length || (settings as any)?.videos?.length); const hasAutoConfigured = Boolean((settings as any)?.youtube_url || (settings as any)?.social_youtube); // Default enablement: if not explicitly set, enable when manual items exist or when a YouTube URL is configured (auto mode). // This avoids flicker caused by toggling visibility while data is loading. const enabled = (typeof (settings as any)?.videos_module_enabled === 'boolean') ? Boolean((settings as any)?.videos_module_enabled) : (hasManualConfigured || ((settings?.videos_source || 'auto') === 'auto' && hasAutoConfigured)); const style = (() => { if (variant === 'carousel') return 'slider'; if (variant === 'grid') return 'grid'; return settings?.videos_style || 'slider'; })(); const source = settings?.videos_source || 'auto'; // Default to 6 items on homepage unless overridden by settings (max 12) const limit = Math.max(1, Math.min(12, settings?.videos_limit ?? 6)); const youtubeUrl = (settings as any)?.youtube_url || (settings as any)?.social_youtube || null; useEffect(() => { try { if (isOpen) onClose(); setSelectedVideo(null); } catch {} }, [style]); useEffect(() => { let canceled = false; const run = async () => { if (source !== 'auto') return; const payload = await getCachedYouTube(); if (!payload) return; // Sort by published_date descending (safety; service should already do this) const vids = (payload.videos || []).slice().sort((a, b) => (Date.parse(b.published_date || '') || 0) - (Date.parse(a.published_date || '') || 0)); if (!canceled) setYt(vids); }; run(); return () => { canceled = true; }; }, [source]); const extractVideoId = (embedUrl: string): string | undefined => { if (embedUrl?.includes('/embed/')) { return embedUrl.split('/embed/')[1]?.split('?')[0]; } return undefined; }; const items: RenderItem[] = useMemo(() => { if (source === 'auto') { return (yt || []).slice(0, limit).map(v => ({ key: v.video_id, title: (titleOverrides?.[v.video_id]?.trim()) || v.title, embedUrl: toEmbed(v.video_id), thumbnail: v.thumbnail_url, date: v.published_date, videoId: v.video_id, })); } // manual fallback from settings or prop const manual = (settings?.videos_items || []).map((it, i) => { const embedUrl = toEmbed(it.url); return { key: `${i}-${it.url}`, title: it.title || `Video ${i+1}`, embedUrl, thumbnail: it.thumbnail_url, date: it.uploaded_at, videoId: extractVideoId(embedUrl), }; }); const legacy = (videos || settings?.videos || []).map((url, i) => { const embedUrl = toEmbed(url as any); return { key: `${i}-${url}`, title: `Video ${i+1}`, embedUrl, videoId: extractVideoId(embedUrl), }; }); return (manual.length ? manual : legacy).slice(0, limit); }, [source, yt, settings?.videos_items, settings?.videos, videos, limit, titleOverrides]); if (!enabled || items.length === 0) return null; const handlePlayClick = (it: RenderItem) => { setSelectedVideo(it); onOpen(); }; const Card: React.FC<{ it: RenderItem; idx: number }> = ({ it, idx }) => { const thumb = it.thumbnail || (it.videoId ? `https://i.ytimg.com/vi/${it.videoId}/hqdefault.jpg` : undefined); const borderColor = useColorModeValue('gray.200', 'gray.600'); const placeholderBg = useColorModeValue('gray.100', 'gray.700'); const placeholderIcon = useColorModeValue('gray.400', 'gray.500'); const videoPrimaryColor = theme.primary; return ( div': { transform: 'scale(1.05)', }, }} > { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); handlePlayClick(it); } }} onClick={() => handlePlayClick(it)} > {/* Thumbnail */} {thumb ? ( ) : ( )} {/* Play overlay */} Přehrát {it.title} {it.date && ( {new Date(it.date).toLocaleDateString('cs-CZ')} )} {it.videoId && ( e.stopPropagation()}> )} ); }; if (style === 'slider') { return ( Videa {items.map((it, idx) => ( ))} {/* Video Modal */} {selectedVideo && (