This commit is contained in:
Tomas Dvorak
2025-10-25 16:33:53 +02:00
parent 857e6f007d
commit 3d621e2187
9 changed files with 238 additions and 121 deletions
@@ -42,7 +42,7 @@ const SponsorsSection: React.FC<SponsorsSectionProps> = ({
const fetchSponsors = async () => { const fetchSponsors = async () => {
try { try {
// Try API first // Try API first
const apiRes = await fetch(`${API_URL}/public/sponsors`); const apiRes = await fetch(`${API_URL}/sponsors`);
if (apiRes.ok) { if (apiRes.ok) {
const data = await apiRes.json(); const data = await apiRes.json();
if (!cancelled && Array.isArray(data)) { if (!cancelled && Array.isArray(data)) {
@@ -44,9 +44,10 @@ const resolveBackendUrl = (path: string) => {
} }
}; };
const GallerySection: React.FC = () => { const GallerySection: React.FC<{ zoneramaUrl?: string | null }> = ({ zoneramaUrl }) => {
const [albums, setAlbums] = useState<Album[]>([]); const [albums, setAlbums] = useState<Album[]>([]);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [profileUrl, setProfileUrl] = useState<string | null>(null);
// Dark mode colors // Dark mode colors
const cardBg = useColorModeValue('white', 'gray.800'); const cardBg = useColorModeValue('white', 'gray.800');
@@ -72,6 +73,9 @@ const GallerySection: React.FC = () => {
// Get profile albums (newest/main source) // Get profile albums (newest/main source)
if (profileRes.status === 'fulfilled' && profileRes.value.ok) { if (profileRes.status === 'fulfilled' && profileRes.value.ok) {
const profileData = await profileRes.value.json(); const profileData = await profileRes.value.json();
if (profileData && profileData.input_link) {
setProfileUrl(profileData.input_link);
}
combinedAlbums = [...(profileData.albums || [])]; combinedAlbums = [...(profileData.albums || [])];
} }
@@ -176,7 +180,7 @@ const GallerySection: React.FC = () => {
📸 Všechny fotografie jsou z platformy{' '} 📸 Všechny fotografie jsou z platformy{' '}
<Text <Text
as="a" as="a"
href="https://zonerama.com" href={zoneramaUrl || profileUrl || 'https://zonerama.com'}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
fontWeight="600" fontWeight="600"
+21 -3
View File
@@ -10,7 +10,6 @@ import {
Button, Button,
HStack, HStack,
VStack, VStack,
Image,
Text, Text,
Badge, Badge,
Link, Link,
@@ -18,6 +17,7 @@ import {
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { useCountdown } from '../../hooks/useCountdown'; import { useCountdown } from '../../hooks/useCountdown';
import { assetUrl, sanitizeClubName } from '../../utils/url'; import { assetUrl, sanitizeClubName } from '../../utils/url';
import { TeamLogo } from '../common/TeamLogo';
export type FacrMatchLike = { export type FacrMatchLike = {
id?: string | number; id?: string | number;
@@ -28,6 +28,8 @@ export type FacrMatchLike = {
away?: string; away?: string;
home_logo_url?: string; home_logo_url?: string;
away_logo_url?: string; away_logo_url?: string;
home_id?: string;
away_id?: string;
competition?: string; competition?: string;
competitionName?: string; competitionName?: string;
venue?: string; venue?: string;
@@ -112,7 +114,15 @@ export const MatchModal: React.FC<MatchModalProps> = ({ isOpen, match, onClose,
role={onTeamClick ? 'button' : undefined} role={onTeamClick ? 'button' : undefined}
tabIndex={onTeamClick ? 0 : undefined} tabIndex={onTeamClick ? 0 : undefined}
> >
<Image src={assetUrl(match.home_logo_url) || '/logo192.png'} alt={match.home || 'Domácí'} boxSize="56px" objectFit="contain" /> <TeamLogo
teamId={match.home_id}
teamName={match.home}
facrLogo={match.home_logo_url}
size="custom"
alt={match.home || 'Domácí'}
boxSize="56px"
borderRadius="full"
/>
<Text fontWeight="semibold" noOfLines={1} textAlign="center">{sanitizeClubName(match.home) || 'Domácí'}</Text> <Text fontWeight="semibold" noOfLines={1} textAlign="center">{sanitizeClubName(match.home) || 'Domácí'}</Text>
</VStack> </VStack>
<VStack spacing={1} minW="120px"> <VStack spacing={1} minW="120px">
@@ -157,7 +167,15 @@ export const MatchModal: React.FC<MatchModalProps> = ({ isOpen, match, onClose,
role={onTeamClick ? 'button' : undefined} role={onTeamClick ? 'button' : undefined}
tabIndex={onTeamClick ? 0 : undefined} tabIndex={onTeamClick ? 0 : undefined}
> >
<Image src={assetUrl(match.away_logo_url) || '/logo192.png'} alt={match.away || 'Hosté'} boxSize="56px" objectFit="contain" /> <TeamLogo
teamId={match.away_id}
teamName={match.away}
facrLogo={match.away_logo_url}
size="custom"
alt={match.away || 'Hosté'}
boxSize="56px"
borderRadius="full"
/>
<Text fontWeight="semibold" noOfLines={1} textAlign="center">{sanitizeClubName(match.away) || 'Hosté'}</Text> <Text fontWeight="semibold" noOfLines={1} textAlign="center">{sanitizeClubName(match.away) || 'Hosté'}</Text>
</VStack> </VStack>
</HStack> </HStack>
@@ -81,7 +81,7 @@ const PhotosSection: React.FC<{ zoneramaUrl?: string | null }> = ({ zoneramaUrl
📸 Fotografie z{' '} 📸 Fotografie z{' '}
<Text <Text
as="a" as="a"
href="https://zonerama.com" href={zoneramaUrl || 'https://zonerama.com'}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
fontWeight="600" fontWeight="600"
+1 -1
View File
@@ -58,7 +58,7 @@ const Footer: React.FC = () => {
} catch {} } catch {}
// Fetch sponsors // Fetch sponsors
try { try {
const sponsorsRes = await fetch(`${API_URL}/public/sponsors`); const sponsorsRes = await fetch(`${API_URL}/sponsors`);
if (sponsorsRes.ok) { if (sponsorsRes.ok) {
const data = await sponsorsRes.json(); const data = await sponsorsRes.json();
if (!cancelled && Array.isArray(data)) { if (!cancelled && Array.isArray(data)) {
@@ -5,6 +5,7 @@ import Navbar from '../Navbar';
import Footer from './Footer'; import Footer from './Footer';
import { useAllPageElementConfigs } from '../../hooks/usePageElementConfig'; import { useAllPageElementConfigs } from '../../hooks/usePageElementConfig';
import SpartaNavbar from '../elements/SpartaNavbar'; import SpartaNavbar from '../elements/SpartaNavbar';
import SponsorsSection from '../common/SponsorsSection';
interface MainLayoutProps { interface MainLayoutProps {
children: ReactNode; children: ReactNode;
@@ -50,6 +51,7 @@ export const MainLayout: React.FC<MainLayoutProps> = ({ children, headerInsideCo
</Box> </Box>
{children} {children}
</Container> </Container>
<SponsorsSection />
<Box as="footer" data-element="footer" style={{ ...getStyles('footer') }}> <Box as="footer" data-element="footer" style={{ ...getStyles('footer') }}>
<Footer /> <Footer />
</Box> </Box>
@@ -66,6 +68,8 @@ export const MainLayout: React.FC<MainLayoutProps> = ({ children, headerInsideCo
<Container maxW="container.xl" py={8}> <Container maxW="container.xl" py={8}>
{children} {children}
</Container> </Container>
{/* Global sponsors section across front-facing pages */}
<SponsorsSection />
<Box as="footer" data-element="footer" style={{ ...getStyles('footer') }}> <Box as="footer" data-element="footer" style={{ ...getStyles('footer') }}>
<Footer /> <Footer />
</Box> </Box>
+149 -91
View File
@@ -31,7 +31,7 @@ import { useForm } from 'react-hook-form';
import { sendContact } from '../services/public'; import { sendContact } from '../services/public';
import { useSettings } from '../hooks/useSettings'; import { useSettings } from '../hooks/useSettings';
import MainLayout from '../components/layout/MainLayout'; import MainLayout from '../components/layout/MainLayout';
import { FiMail, FiPhone } from 'react-icons/fi'; import { FiMail, FiPhone, FiMapPin } from 'react-icons/fi';
import { trackContactSubmit, trackFormSubmit } from '../utils/umami'; import { trackContactSubmit, trackFormSubmit } from '../utils/umami';
import SponsorsSection from '../components/common/SponsorsSection'; import SponsorsSection from '../components/common/SponsorsSection';
import ContactMap from '../components/home/ContactMap'; import ContactMap from '../components/home/ContactMap';
@@ -142,7 +142,13 @@ const ContactPage: React.FC = () => {
const uncategorized = contactsData?.uncategorized || []; const uncategorized = contactsData?.uncategorized || [];
const hasContacts = categories.length > 0 || uncategorized.length > 0; const hasContacts = categories.length > 0 || uncategorized.length > 0;
if (!hasLocation && !hasContacts) return null; const hasContactInfo = !!(
(settings as any)?.contact_address ||
(settings as any)?.contact_phone ||
(settings as any)?.contact_email
);
if (!hasLocation && !hasContacts && !hasContactInfo) return null;
return ( return (
<SimpleGrid columns={{ base: 1, md: 2 }} spacing={8}> <SimpleGrid columns={{ base: 1, md: 2 }} spacing={8}>
@@ -162,97 +168,149 @@ const ContactPage: React.FC = () => {
</Box> </Box>
)} )}
{/* Categories as tabs on the right */} {/* Right column: contact info card (if any) + contacts tabs */}
{hasContacts && ( {(hasContactInfo || hasContacts) && (
<Box bg={bgColor} p={4} borderRadius="lg" borderWidth="1px" borderColor={borderColor} boxShadow="sm"> <VStack align="stretch" spacing={4}>
<Heading size="md" mb={3}>Kontaktní osoby</Heading> {hasContactInfo && (
<Tabs colorScheme="blue" isFitted> <Box bg={bgColor} p={4} borderRadius="lg" borderWidth="1px" borderColor={borderColor} boxShadow="sm">
<TabList> <Heading size="md" mb={3}>Kontaktní údaje</Heading>
{categories.map(([name]) => ( <VStack align="stretch" spacing={3}>
<Tab key={name}>{name}</Tab> {(settings as any)?.contact_address && (
))} <HStack align="start">
{uncategorized.length > 0 && <Tab>Ostatní</Tab>} <Icon as={FiMapPin} boxSize={5} color="blue.500" mt={1} />
</TabList> <VStack align="start" spacing={0}>
<TabPanels> <Text fontWeight="bold">Adresa</Text>
{categories.map(([name, persons]) => ( <Text>{(settings as any)?.contact_address}</Text>
<TabPanel key={name} pt={4}> {(settings as any)?.contact_city && (
<SimpleGrid columns={{ base: 1, sm: 2 }} spacing={4}> <Text>
{persons.map((contact) => ( {(settings as any)?.contact_zip && `${(settings as any)?.contact_zip} `}
<Box key={contact.id} bg={bgColor} p={4} borderRadius="md" borderWidth="1px" borderColor={borderColor}> {(settings as any)?.contact_city}
<VStack align="start" spacing={3}> </Text>
{contact.image_url && ( )}
<Avatar src={contact.image_url} name={contact.name} size="lg" /> {(settings as any)?.contact_country && <Text>{(settings as any)?.contact_country}</Text>}
)} </VStack>
<Box> </HStack>
<Heading size="sm">{contact.name}</Heading> )}
{contact.position && (
<Badge colorScheme="blue" mt={1}>{contact.position}</Badge> {(settings as any)?.contact_phone && (
)} <HStack align="start">
<Icon as={FiPhone} boxSize={5} color="blue.500" mt={1} />
<VStack align="start" spacing={0}>
<Text fontWeight="bold">Telefon</Text>
<Link href={`tel:${(settings as any)?.contact_phone}`} color="blue.500">
{(settings as any)?.contact_phone}
</Link>
</VStack>
</HStack>
)}
{(settings as any)?.contact_email && (
<HStack align="start">
<Icon as={FiMail} boxSize={5} color="blue.500" mt={1} />
<VStack align="start" spacing={0}>
<Text fontWeight="bold">Email</Text>
<Link href={`mailto:${(settings as any)?.contact_email}`} color="blue.500">
{(settings as any)?.contact_email}
</Link>
</VStack>
</HStack>
)}
</VStack>
</Box>
)}
{hasContacts && (
<Box bg={bgColor} p={4} borderRadius="lg" borderWidth="1px" borderColor={borderColor} boxShadow="sm">
<Heading size="md" mb={3}>Kontaktní osoby</Heading>
<Tabs colorScheme="blue" isFitted>
<TabList>
{categories.map(([name]) => (
<Tab key={name}>{name}</Tab>
))}
{uncategorized.length > 0 && <Tab>Ostatní</Tab>}
</TabList>
<TabPanels>
{categories.map(([name, persons]) => (
<TabPanel key={name} pt={4}>
<SimpleGrid columns={{ base: 1, sm: 2 }} spacing={4}>
{persons.map((contact) => (
<Box key={contact.id} bg={bgColor} p={4} borderRadius="md" borderWidth="1px" borderColor={borderColor}>
<VStack align="start" spacing={3}>
{contact.image_url && (
<Avatar src={contact.image_url} name={contact.name} size="lg" />
)}
<Box>
<Heading size="sm">{contact.name}</Heading>
{contact.position && (
<Badge colorScheme="blue" mt={1}>{contact.position}</Badge>
)}
</Box>
{contact.description && (
<Text fontSize="sm" color="gray.600">{contact.description}</Text>
)}
<VStack align="start" spacing={1}>
{contact.email && (
<HStack spacing={2}>
<Icon as={FiMail} color="blue.500" />
<Link href={`mailto:${contact.email}`} color="blue.500" fontSize="sm">{contact.email}</Link>
</HStack>
)}
{contact.phone && (
<HStack spacing={2}>
<Icon as={FiPhone} color="blue.500" />
<Link href={`tel:${contact.phone}`} color="blue.500" fontSize="sm">{contact.phone}</Link>
</HStack>
)}
</VStack>
</VStack>
</Box> </Box>
{contact.description && ( ))}
<Text fontSize="sm" color="gray.600">{contact.description}</Text> </SimpleGrid>
)} </TabPanel>
<VStack align="start" spacing={1}> ))}
{contact.email && ( {uncategorized.length > 0 && (
<HStack spacing={2}> <TabPanel pt={4}>
<Icon as={FiMail} color="blue.500" /> <SimpleGrid columns={{ base: 1, sm: 2 }} spacing={4}>
<Link href={`mailto:${contact.email}`} color="blue.500" fontSize="sm">{contact.email}</Link> {uncategorized.map((contact) => (
</HStack> <Box key={contact.id} bg={bgColor} p={4} borderRadius="md" borderWidth="1px" borderColor={borderColor}>
)} <VStack align="start" spacing={3}>
{contact.phone && ( {contact.image_url && (
<HStack spacing={2}> <Avatar src={contact.image_url} name={contact.name} size="lg" />
<Icon as={FiPhone} color="blue.500" /> )}
<Link href={`tel:${contact.phone}`} color="blue.500" fontSize="sm">{contact.phone}</Link> <Box>
</HStack> <Heading size="sm">{contact.name}</Heading>
)} {contact.position && (
</VStack> <Badge colorScheme="blue" mt={1}>{contact.position}</Badge>
</VStack> )}
</Box> </Box>
))} {contact.description && (
</SimpleGrid> <Text fontSize="sm" color="gray.600">{contact.description}</Text>
</TabPanel> )}
))} <VStack align="start" spacing={1}>
{uncategorized.length > 0 && ( {contact.email && (
<TabPanel pt={4}> <HStack spacing={2}>
<SimpleGrid columns={{ base: 1, sm: 2 }} spacing={4}> <Icon as={FiMail} color="blue.500" />
{uncategorized.map((contact) => ( <Link href={`mailto:${contact.email}`} color="blue.500" fontSize="sm">{contact.email}</Link>
<Box key={contact.id} bg={bgColor} p={4} borderRadius="md" borderWidth="1px" borderColor={borderColor}> </HStack>
<VStack align="start" spacing={3}> )}
{contact.image_url && ( {contact.phone && (
<Avatar src={contact.image_url} name={contact.name} size="lg" /> <HStack spacing={2}>
)} <Icon as={FiPhone} color="blue.500" />
<Box> <Link href={`tel:${contact.phone}`} color="blue.500" fontSize="sm">{contact.phone}</Link>
<Heading size="sm">{contact.name}</Heading> </HStack>
{contact.position && ( )}
<Badge colorScheme="blue" mt={1}>{contact.position}</Badge> </VStack>
)} </VStack>
</Box> </Box>
{contact.description && ( ))}
<Text fontSize="sm" color="gray.600">{contact.description}</Text> </SimpleGrid>
)} </TabPanel>
<VStack align="start" spacing={1}> )}
{contact.email && ( </TabPanels>
<HStack spacing={2}> </Tabs>
<Icon as={FiMail} color="blue.500" /> </Box>
<Link href={`mailto:${contact.email}`} color="blue.500" fontSize="sm">{contact.email}</Link> )}
</HStack> </VStack>
)}
{contact.phone && (
<HStack spacing={2}>
<Icon as={FiPhone} color="blue.500" />
<Link href={`tel:${contact.phone}`} color="blue.500" fontSize="sm">{contact.phone}</Link>
</HStack>
)}
</VStack>
</VStack>
</Box>
))}
</SimpleGrid>
</TabPanel>
)}
</TabPanels>
</Tabs>
</Box>
)} )}
</SimpleGrid> </SimpleGrid>
); );
+52 -8
View File
@@ -24,6 +24,7 @@ import ClubModal from '../components/home/ClubModal';
import MatchModal from '../components/home/MatchModal'; import MatchModal from '../components/home/MatchModal';
import { useAllPageElementConfigs } from '../hooks/usePageElementConfig'; import { useAllPageElementConfigs } from '../hooks/usePageElementConfig';
import { API_URL } from '../services/api'; import { API_URL } from '../services/api';
import { TeamLogo } from '../components/common/TeamLogo';
// Types for real API-driven data // Types for real API-driven data
type NewsItem = { type NewsItem = {
@@ -336,6 +337,8 @@ const HomePage: React.FC = () => {
time, time,
home: m.home, home: m.home,
away: m.away, away: m.away,
home_id: m.home_id,
away_id: m.away_id,
home_logo_url: getOverrideLogo(m.home, m.home_logo_url), home_logo_url: getOverrideLogo(m.home, m.home_logo_url),
away_logo_url: getOverrideLogo(m.away, m.away_logo_url), away_logo_url: getOverrideLogo(m.away, m.away_logo_url),
score: m.score, score: m.score,
@@ -1339,7 +1342,15 @@ const HomePage: React.FC = () => {
<div className="container" data-element="container" style={{ ...getStyles('container') }}> <div className="container" data-element="container" style={{ ...getStyles('container') }}>
{/* Header: logo + club name */} {/* Header: logo + club name */}
<div className="home-header"> <div className="home-header">
<img src={assetUrl(clubLogo) || '/images/club-logo.png'} alt="Klub" /> <TeamLogo
teamId={settings?.club_id}
teamName={clubName}
facrLogo={assetUrl(clubLogo) || undefined}
size="custom"
alt="Klub"
borderRadius="full"
style={{ width: 56, height: 56 }}
/>
<div> <div>
<h1 style={{ margin: 0 }}>{clubName}</h1> <h1 style={{ margin: 0 }}>{clubName}</h1>
<div className="subtitle" style={{ fontSize: '0.95rem' }}>Oficiální web klubu</div> <div className="subtitle" style={{ fontSize: '0.95rem' }}>Oficiální web klubu</div>
@@ -1462,7 +1473,15 @@ const HomePage: React.FC = () => {
<FiChevronLeft size={24} /> <FiChevronLeft size={24} />
</button> </button>
<div className="team"> <div className="team">
<img className="logo" src={assetUrl(show?.home_logo_url) || assetUrl(clubLogo) || '/images/club-logo.png'} alt="Domácí" /> <TeamLogo
className="logo"
teamId={show?.home_id}
teamName={show?.home}
facrLogo={show?.home_logo_url}
size="custom"
alt="Domácí"
borderRadius="full"
/>
<div>{sanitizeClubName(show?.home || matches[0]?.homeTeam || clubName)}</div> <div>{sanitizeClubName(show?.home || matches[0]?.homeTeam || clubName)}</div>
</div> </div>
<div className="countdown"> <div className="countdown">
@@ -1471,7 +1490,15 @@ const HomePage: React.FC = () => {
<div style={{ fontSize: '0.8rem', opacity: 0.85 }}>Začátek zápasu</div> <div style={{ fontSize: '0.8rem', opacity: 0.85 }}>Začátek zápasu</div>
</div> </div>
<div className="team"> <div className="team">
<img className="logo" src={assetUrl(show?.away_logo_url) || '/images/club-opponent.png'} alt="Hosté" /> <TeamLogo
className="logo"
teamId={show?.away_id}
teamName={show?.away}
facrLogo={show?.away_logo_url}
size="custom"
alt="Hosté"
borderRadius="full"
/>
<div>{sanitizeClubName(show?.away || matches[0]?.awayTeam || 'Soupeř')}</div> <div>{sanitizeClubName(show?.away || matches[0]?.awayTeam || 'Soupeř')}</div>
</div> </div>
<button <button
@@ -1535,7 +1562,14 @@ const HomePage: React.FC = () => {
</div> </div>
<div className="teams"> <div className="teams">
<div className="team"> <div className="team">
<img src={assetUrl(m.home_logo_url)} alt={m.home} /> <TeamLogo
teamId={m.home_id}
teamName={m.home}
facrLogo={m.home_logo_url}
size="custom"
alt={m.home}
borderRadius="full"
/>
<div className="name">{sanitizeClubName(m.home)}</div> <div className="name">{sanitizeClubName(m.home)}</div>
</div> </div>
<div className="score"> <div className="score">
@@ -1550,7 +1584,14 @@ const HomePage: React.FC = () => {
)} )}
</div> </div>
<div className="team"> <div className="team">
<img src={assetUrl(m.away_logo_url)} alt={m.away} /> <TeamLogo
teamId={m.away_id}
teamName={m.away}
facrLogo={m.away_logo_url}
size="custom"
alt={m.away}
borderRadius="full"
/>
<div className="name">{sanitizeClubName(m.away)}</div> <div className="name">{sanitizeClubName(m.away)}</div>
</div> </div>
</div> </div>
@@ -1680,8 +1721,11 @@ const HomePage: React.FC = () => {
<td style={{ padding: '10px 8px' }}> <td style={{ padding: '10px 8px' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '8px', minWidth: 0 }}> <div style={{ display: 'flex', alignItems: 'center', gap: '8px', minWidth: 0 }}>
{row.team_logo_url && ( {row.team_logo_url && (
<img <TeamLogo
src={assetUrl(row.team_logo_url)} teamId={row.team_id}
teamName={row.team?.name ?? row.team ?? row.club}
facrLogo={row.team_logo_url}
size="custom"
alt={row.team?.name ?? row.team ?? row.club ?? '-'} alt={row.team?.name ?? row.team ?? row.club ?? '-'}
style={{ width: '24px', height: '24px', borderRadius: '50%', objectFit: 'cover', background: 'var(--bg-soft)', border: '1px solid var(--card-border)', flexShrink: 0 }} style={{ width: '24px', height: '24px', borderRadius: '50%', objectFit: 'cover', background: 'var(--bg-soft)', border: '1px solid var(--card-border)', flexShrink: 0 }}
/> />
@@ -1757,7 +1801,7 @@ const HomePage: React.FC = () => {
{isVisible('gallery', false) && ( {isVisible('gallery', false) && (
<section data-element="gallery" style={{ marginTop: 32, marginBottom: 32, position: 'relative', ...getStyles('gallery') }}> <section data-element="gallery" style={{ marginTop: 32, marginBottom: 32, position: 'relative', ...getStyles('gallery') }}>
<div style={{ maxWidth: 1200, margin: '0 auto', padding: '0 12px' }}> <div style={{ maxWidth: 1200, margin: '0 auto', padding: '0 12px' }}>
<GallerySection /> <GallerySection zoneramaUrl={galleryUrl} />
</div> </div>
</section> </section>
)} )}
+3 -14
View File
@@ -129,20 +129,9 @@ const SponsorsAdminPage: React.FC = () => {
if (!file) return; if (!file) return;
try { try {
const res = await uploadFile(file); const res = await uploadFile(file);
const apiOrigin = new URL(API_URL, window.location.origin).origin; // uploadFile already normalizes to a backend-relative URL like '/uploads/2025/10/...'
// If backend returned an absolute URL pointing to the dev host (same origin as app), rewrite to API origin // Store that relative path to keep it portable across environments
let url = res.url || ''; setEditing((prev) => ({ ...(prev || {}), logo_url: res.url || '' }));
try {
const parsed = new URL(url, window.location.origin);
const appOrigin = window.location.origin;
if (parsed.origin === appOrigin) {
// replace with API origin while keeping path
url = apiOrigin + parsed.pathname + parsed.search + parsed.hash;
}
} catch (e) {
// ignore
}
setEditing((prev) => ({ ...(prev || {}), logo_url: url }));
toast({ title: 'Logo nahráno', status: 'success' }); toast({ title: 'Logo nahráno', status: 'success' });
} catch (err) { } catch (err) {
toast({ title: 'Nahrání loga selhalo', status: 'error' }); toast({ title: 'Nahrání loga selhalo', status: 'error' });