This commit is contained in:
Tomáš Dvořák
2025-10-16 13:32:05 +02:00
commit 12cba639b9
663 changed files with 168914 additions and 0 deletions
@@ -0,0 +1,153 @@
import React, { useEffect, useState } from 'react';
import { assetUrl } from '../../utils/url';
interface Sponsor {
id: number | string;
name: string;
logo: string;
url?: string;
tier?: string;
}
interface SponsorsSectionProps {
layout?: 'grid' | 'slider' | 'scroller' | 'pyramid';
theme?: 'dark' | 'light';
}
const resolveBackendUrl = (path: string) => {
try {
if (/^https?:\/\//i.test(path)) return path;
if (path.startsWith('/cache') || path.startsWith('/uploads') || path.startsWith('/api/')) {
const base = process.env.REACT_APP_API_BASE_URL || process.env.REACT_APP_API_URL || 'http://localhost:8080/api/v1';
const b = new URL(base);
const abs = new URL(path, `${b.protocol}//${b.host}`);
return abs.toString();
}
return path;
} catch {
return path;
}
};
const SponsorsSection: React.FC<SponsorsSectionProps> = ({
layout = 'grid',
theme = 'light'
}) => {
const [sponsors, setSponsors] = useState<Sponsor[]>([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
let cancelled = false;
const fetchSponsors = async () => {
try {
// Try API first
const apiRes = await fetch(`${process.env.REACT_APP_API_BASE_URL || process.env.REACT_APP_API_URL || 'http://localhost:8080/api/v1'}/public/sponsors`);
if (apiRes.ok) {
const data = await apiRes.json();
if (!cancelled && Array.isArray(data)) {
const mapped = data.map((s: any) => ({
id: s.id,
name: s.name,
logo: assetUrl(s.logo_url) || '/images/sponsors/placeholder.png',
url: s.website_url || undefined,
tier: s.tier,
}));
setSponsors(mapped);
setLoading(false);
return;
}
}
} catch {}
// Fallback to cache
try {
const cacheRes = await fetch(resolveBackendUrl('/cache/prefetch/settings.json'), { cache: 'no-cache' });
if (cacheRes.ok) {
const settings = await cacheRes.json();
if (!cancelled) {
const sponsorsData = settings?.sponsors || settings?.partners || [];
if (Array.isArray(sponsorsData) && sponsorsData.length) {
setSponsors(
sponsorsData.map((s: any, i: number) => ({
id: s.id ?? i + 1,
name: s.name || 'Sponsor',
logo: s.logo_url || s.logoUrl || s.logo || '/images/sponsors/placeholder.png',
url: s.url || s.website || s.link || '#',
tier: s.tier,
}))
);
}
}
}
} catch {}
if (!cancelled) {
setLoading(false);
}
};
fetchSponsors();
return () => { cancelled = true; };
}, []);
if (loading || sponsors.length === 0) {
return null;
}
const title = sponsors.find((s: any) => s.tier === 'title') || sponsors[0];
const others = sponsors.filter((s) => s !== title);
return (
<section
className={`sponsors ${theme === 'dark' ? 'dark' : ''}`}
style={{
width: '100vw',
position: 'relative',
left: '50%',
right: '50%',
transform: 'translateX(-50%)',
paddingLeft: 'max(16px, calc((100vw - 1200px) / 2))',
paddingRight: 'max(16px, calc((100vw - 1200px) / 2))',
boxSizing: 'border-box',
marginTop: '32px',
marginBottom: '32px',
}}
>
<div className="section-head">
<h3>Sponzoři</h3>
</div>
{layout === 'grid' ? (
<>
{title && (
<div className="title-sponsor">
<a className="sponsor-tile" href={title.url || '#'} target="_blank" rel="noreferrer noopener">
<img src={title.logo} alt={title.name} />
</a>
</div>
)}
<div className="divider" aria-hidden />
<div className="sponsors-grid">
{others.map((s) => (
<a key={s.id} className="sponsor-tile" href={s.url || '#'} target="_blank" rel="noreferrer noopener">
<img src={s.logo} alt={s.name} />
</a>
))}
</div>
</>
) : (
<div className="sponsors-slider">
<div className="track">
{[...sponsors, ...sponsors].map((s, idx) => (
<a key={`${s.id}-${idx}`} className="sponsor-tile" href={s.url || '#'} target="_blank" rel="noreferrer noopener">
<img src={s.logo} alt={s.name} />
</a>
))}
</div>
</div>
)}
</section>
);
};
export default SponsorsSection;