mirror of
https://github.com/Dvorinka/MyClubServer.git
synced 2026-06-04 18:52:56 +00:00
dev day #90 🥳
This commit is contained in:
+279
-125
@@ -39,6 +39,7 @@ const MatchesSlider = React.lazy(() => import('../components/pack/MatchesSlider'
|
||||
import ActivitiesList from '../components/pack/ActivitiesList';
|
||||
import { useAuth } from '../contexts/AuthContext';
|
||||
import SweepstakeWidget from '../components/sweepstakes/SweepstakeWidget';
|
||||
import { sortCategoriesWithOrder } from '../utils/categorySort';
|
||||
|
||||
// Types for real API-driven data
|
||||
type NewsItem = {
|
||||
@@ -92,7 +93,7 @@ const HomePage: React.FC = () => {
|
||||
const [edgeRoleIdx, setEdgeRoleIdx] = useState<number>(0);
|
||||
const blogAutoRef = useRef<HTMLDivElement | null>(null);
|
||||
// FACR competitions with matches (for slider)
|
||||
const [facrCompetitions, setFacrCompetitions] = useState<Array<{ name:string; matches:Array<any>; matches_link?:string }>>([]);
|
||||
const [facrCompetitions, setFacrCompetitions] = useState<Array<{ name:string; matches:Array<any>; matches_link?:string; display_order?: number }>>([]);
|
||||
const [matchesTab, setMatchesTab] = useState<number>(0);
|
||||
const [selectedClub, setSelectedClub] = useState<any>(null);
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
@@ -118,10 +119,11 @@ const HomePage: React.FC = () => {
|
||||
const [merchItems, setMerchItems] = useState<UiMerch[]>([]);
|
||||
const [merchEnabled, setMerchEnabled] = useState<boolean>(false);
|
||||
const [upcomingEvents, setUpcomingEvents] = useState<UiEvent[]>([]);
|
||||
const [activitiesLoaded, setActivitiesLoaded] = useState<boolean>(false);
|
||||
const [defer, setDefer] = useState<boolean>(false);
|
||||
// Aliases
|
||||
const [aliases, setAliases] = useState<CompetitionAlias[]>([]);
|
||||
const [aliasMap, setAliasMap] = useState<Record<string, { alias: string; original_name?: string }>>({});
|
||||
const [aliasMap, setAliasMap] = useState<Record<string, { alias: string; original_name?: string; display_order?: number }>>({});
|
||||
const [settings, setSettings] = useState<any>(null);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(true);
|
||||
const [isEditingMode, setIsEditingMode] = useState<boolean>(false);
|
||||
@@ -164,6 +166,33 @@ const HomePage: React.FC = () => {
|
||||
slug: item.slug,
|
||||
})), [featured]);
|
||||
|
||||
const upcomingCompIndices = useMemo(() => {
|
||||
const now = Date.now();
|
||||
try {
|
||||
return (facrCompetitions || [])
|
||||
.map((c, i) => {
|
||||
const items = Array.isArray(c?.matches) ? c.matches : [];
|
||||
const hasUpcoming = items.some((m: any) => {
|
||||
const t = new Date(`${m.date || ''}T${(m.time || '00:00')}:00`).getTime();
|
||||
return !isNaN(t) && t > now;
|
||||
});
|
||||
return hasUpcoming ? i : -1;
|
||||
})
|
||||
.filter((i) => i !== -1);
|
||||
} catch {
|
||||
return [] as number[];
|
||||
}
|
||||
}, [facrCompetitions]);
|
||||
|
||||
useEffect(() => {
|
||||
try {
|
||||
if (!Array.isArray(upcomingCompIndices) || upcomingCompIndices.length === 0) return;
|
||||
if (!upcomingCompIndices.includes(nextCompIdx)) {
|
||||
setNextCompIdx(upcomingCompIndices[0]);
|
||||
}
|
||||
} catch {}
|
||||
}, [upcomingCompIndices, nextCompIdx]);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
|
||||
@@ -262,8 +291,8 @@ const HomePage: React.FC = () => {
|
||||
try {
|
||||
aliasesList = await getCompetitionAliasesPublic();
|
||||
} catch {}
|
||||
const amap: Record<string, { alias: string; original_name?: string }> = {};
|
||||
(aliasesList || []).forEach((a) => { if (a?.code && a?.alias) amap[a.code] = { alias: a.alias, original_name: a.original_name }; });
|
||||
const amap: Record<string, { alias: string; original_name?: string; display_order?: number }> = {};
|
||||
(aliasesList || []).forEach((a) => { if (a?.code && a?.alias) amap[a.code] = { alias: a.alias, original_name: a.original_name, display_order: a.display_order }; });
|
||||
// Try live settings API first
|
||||
let liveSettings: any = null;
|
||||
try {
|
||||
@@ -392,10 +421,12 @@ const HomePage: React.FC = () => {
|
||||
return {
|
||||
name: (amap?.[c?.code]?.alias) || c.name || c.code || 'Soutěž',
|
||||
matches_link: c.matches_link,
|
||||
matches: filtered
|
||||
matches: filtered,
|
||||
display_order: (amap?.[c?.code]?.display_order),
|
||||
};
|
||||
});
|
||||
setFacrCompetitions(comps);
|
||||
const sortedComps = sortCategoriesWithOrder(comps as any);
|
||||
setFacrCompetitions(sortedComps as any);
|
||||
|
||||
// Next match FACR link
|
||||
const first = filteredMatches?.[0];
|
||||
@@ -414,7 +445,7 @@ const HomePage: React.FC = () => {
|
||||
|
||||
// Load players via API (include inactive to show as non-active instead of hiding)
|
||||
try {
|
||||
const apiPlayers: ApiPlayer[] = await apiGetPlayers({ active: false });
|
||||
const apiPlayers: ApiPlayer[] = await apiGetPlayers();
|
||||
const mappedPlayers: UiPlayer[] = (apiPlayers || []).map((p: ApiPlayer) => ({
|
||||
id: p.id,
|
||||
name: [p.first_name, p.last_name].filter(Boolean).join(' '),
|
||||
@@ -481,7 +512,7 @@ const HomePage: React.FC = () => {
|
||||
const top3 = all.slice(0, 3);
|
||||
setFeatured(top3);
|
||||
setNews((prev) => {
|
||||
const featuredKeys = new Set(top3.map((f) => (f.slug ? `s:${f.slug}` : `i:${f.id}`)));
|
||||
const featuredKeys = new Set(all.map((f) => (f.slug ? `s:${f.slug}` : `i:${f.id}`)));
|
||||
return (prev || []).filter((n) => !featuredKeys.has(n.slug ? `s:${n.slug}` : `i:${n.id}`));
|
||||
});
|
||||
} catch {}
|
||||
@@ -531,6 +562,8 @@ const HomePage: React.FC = () => {
|
||||
if (facrTablesJSON?.competitions?.length) {
|
||||
const comps = (facrTablesJSON.competitions || []).map((c: any) => ({
|
||||
name: (amap?.[c?.code]?.alias) || c.name || c.code,
|
||||
display_order: (amap?.[c?.code]?.display_order),
|
||||
code: c.code,
|
||||
table: (c.table?.overall || []).map((r: any, idx: number) => ({
|
||||
position: Number(r.rank || idx + 1),
|
||||
team: r.team || r.team_name || '-',
|
||||
@@ -544,7 +577,8 @@ const HomePage: React.FC = () => {
|
||||
score: r.score || '0:0',
|
||||
})),
|
||||
}));
|
||||
setStandings(comps);
|
||||
const sortedTables = sortCategoriesWithOrder(comps as any);
|
||||
setStandings(sortedTables);
|
||||
}
|
||||
|
||||
// Club name/logo from FACR if not provided by settings
|
||||
@@ -630,6 +664,9 @@ const HomePage: React.FC = () => {
|
||||
}));
|
||||
if (active) setUpcomingEvents(mapped);
|
||||
} catch {}
|
||||
finally {
|
||||
if (active) setActivitiesLoaded(true);
|
||||
}
|
||||
})();
|
||||
return () => { active = false; };
|
||||
}, []);
|
||||
@@ -1402,13 +1439,17 @@ const HomePage: React.FC = () => {
|
||||
</div>
|
||||
</a>
|
||||
) : (
|
||||
<a href="/news" className="hero-card big" style={{ textDecoration: 'none' }}>
|
||||
<div className="bg" style={{ backgroundImage: `url('/images/news/placeholder.jpg')` }} />
|
||||
<div className="overlay">
|
||||
<div style={{ opacity: 0.9, fontSize: '0.8rem', color: '#ffffff' }}>Aktuality</div>
|
||||
<h2 style={{ margin: '4px 0 0 0', color: '#ffffff' }}>Nejnovější titulek</h2>
|
||||
</div>
|
||||
</a>
|
||||
isLoading ? (
|
||||
<div className="hero-card big skeleton" style={{ borderRadius: 16 }} />
|
||||
) : (
|
||||
<a href="/news" className="hero-card big" style={{ textDecoration: 'none' }}>
|
||||
<div className="bg" style={{ backgroundImage: `url('/images/news/placeholder.jpg')` }} />
|
||||
<div className="overlay">
|
||||
<div style={{ opacity: 0.9, fontSize: '0.8rem', color: '#ffffff' }}>Aktuality</div>
|
||||
<h2 style={{ margin: '4px 0 0 0', color: '#ffffff' }}>Nejnovější titulek</h2>
|
||||
</div>
|
||||
</a>
|
||||
)
|
||||
)}
|
||||
<div className="small-col">
|
||||
{featured.slice(1, 3).map((n, idx) => (
|
||||
@@ -1421,13 +1462,17 @@ const HomePage: React.FC = () => {
|
||||
</a>
|
||||
))}
|
||||
{Array.from({ length: Math.max(0, 2 - Math.min(2, Math.max(0, featured.length - 1))) }).map((_, idx) => (
|
||||
<a key={`placeholder-${idx}`} href="/news" className="hero-card small" style={{ textDecoration: 'none' }}>
|
||||
<div className="bg" style={{ backgroundImage: `url('/images/news/placeholder.jpg')`, filter: 'grayscale(50%) brightness(0.7)' }} />
|
||||
<div className="overlay">
|
||||
<div style={{ opacity: 0.8, fontSize: '0.8rem', color: '#fff' }}>Aktuality</div>
|
||||
<h3 style={{ margin: '4px 0 0 0', color: '#fff' }}>Připravujeme...</h3>
|
||||
</div>
|
||||
</a>
|
||||
isLoading ? (
|
||||
<div key={`placeholder-${idx}`} className="hero-card small skeleton" style={{ borderRadius: 16 }} />
|
||||
) : (
|
||||
<a key={`placeholder-${idx}`} href="/news" className="hero-card small" style={{ textDecoration: 'none' }}>
|
||||
<div className="bg" style={{ backgroundImage: `url('/images/news/placeholder.jpg')`, filter: 'grayscale(50%) brightness(0.7)' }} />
|
||||
<div className="overlay">
|
||||
<div style={{ opacity: 0.8, fontSize: '0.8rem', color: '#fff' }}>Aktuality</div>
|
||||
<h3 style={{ margin: '4px 0 0 0', color: '#fff' }}>Připravujeme...</h3>
|
||||
</div>
|
||||
</a>
|
||||
)
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
@@ -1438,7 +1483,7 @@ const HomePage: React.FC = () => {
|
||||
{(banners || []).filter(b => b.placement === 'homepage_middle').map((b) => (
|
||||
<a key={b.id} href={b.url || '#'} target={b.url ? '_blank' : undefined} rel={b.url ? 'noopener noreferrer' : undefined} style={{ display: 'inline-block', margin: 8 }}>
|
||||
{/* eslint-disable-next-line jsx-a11y/alt-text */}
|
||||
<img src={b.image} alt={b.name} style={{ maxWidth: '100%', width: b.width ? `${b.width}px` : undefined, height: b.height ? `${b.height}px` : 'auto' }} />
|
||||
<img loading="lazy" decoding="async" src={b.image} alt={b.name} style={{ maxWidth: '100%', width: b.width ? `${b.width}px` : undefined, height: b.height ? `${b.height}px` : 'auto' }} />
|
||||
</a>
|
||||
))}
|
||||
</section>
|
||||
@@ -1446,34 +1491,37 @@ const HomePage: React.FC = () => {
|
||||
|
||||
{/* Featured articles are now shown in the hero grid above, not here */}
|
||||
|
||||
{/* Sidebar banners (homepage_sidebar) - fixed edge rail, left/right via MyUIbrix variant */}
|
||||
{/* Sidebar banners (homepage_sidebar) - sticky within page container */}
|
||||
{(banners || []).some(b => b.placement === 'homepage_sidebar') && (
|
||||
<section
|
||||
key={`sidebar-${refreshKey}-${getVariant('sidebar', 'right')}`}
|
||||
data-element="sidebar"
|
||||
data-variant={getVariant('sidebar', 'right')}
|
||||
className={`banner banner-sidebar sidebar-${getVariant('sidebar', 'right')}`}
|
||||
style={{
|
||||
// Use configured styles but force fixed rail placement
|
||||
...getStyles('sidebar'),
|
||||
position: 'fixed',
|
||||
top: 112,
|
||||
left: getVariant('sidebar', 'right') === 'left' ? 12 : 'auto',
|
||||
right: getVariant('sidebar', 'right') === 'left' ? 'auto' : 12,
|
||||
width: 320,
|
||||
maxWidth: '100%',
|
||||
zIndex: 50,
|
||||
pointerEvents: 'none',
|
||||
}}
|
||||
style={{ margin: '24px 0', ...getStyles('sidebar') }}
|
||||
>
|
||||
{(banners || []).filter(b => b.placement === 'homepage_sidebar').map((b) => (
|
||||
<div key={b.id} className="card" style={{ display: 'block', marginBottom: 12, pointerEvents: 'auto', padding: 4 }}>
|
||||
<a href={b.url || '#'} target={b.url ? '_blank' : undefined} rel={b.url ? 'noopener noreferrer' : undefined} style={{ display: 'block' }}>
|
||||
{/* eslint-disable-next-line jsx-a11y/alt-text */}
|
||||
<img loading="lazy" src={b.image} alt={b.name} style={{ width: b.width ? `${b.width}px` : '100%', height: b.height ? `${b.height}px` : 'auto', maxWidth: '100%' }} />
|
||||
</a>
|
||||
<div style={{ maxWidth: 1200, margin: '0 auto', padding: '0 12px' }}>
|
||||
<div
|
||||
style={{
|
||||
position: 'sticky',
|
||||
top: 112,
|
||||
width: 320,
|
||||
maxWidth: '100%',
|
||||
marginLeft: getVariant('sidebar', 'right') === 'left' ? 0 : 'auto',
|
||||
marginRight: getVariant('sidebar', 'right') === 'left' ? 'auto' : 0,
|
||||
zIndex: 1,
|
||||
}}
|
||||
>
|
||||
{(banners || []).filter(b => b.placement === 'homepage_sidebar').map((b) => (
|
||||
<div key={b.id} className="card" style={{ display: 'block', marginBottom: 12, padding: 4 }}>
|
||||
<a href={b.url || '#'} target={b.url ? '_blank' : undefined} rel={b.url ? 'noopener noreferrer' : undefined} style={{ display: 'block' }}>
|
||||
{/* eslint-disable-next-line jsx-a11y/alt-text */}
|
||||
<img loading="lazy" decoding="async" src={b.image} alt={b.name} style={{ width: b.width ? `${b.width}px` : '100%', height: b.height ? `${b.height}px` : 'auto', maxWidth: '100%' }} />
|
||||
</a>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
{getVariant('hero', heroStyle) === 'scroller' && isVisible('hero', true) && (
|
||||
@@ -1492,58 +1540,68 @@ const HomePage: React.FC = () => {
|
||||
)}
|
||||
|
||||
{/* Next match: categories (competitions) with left/right navigation - synced with matchesTab */}
|
||||
{facrCompetitions.length > 0 && isVisible('matches', true) ? (
|
||||
(() => {
|
||||
const comp = facrCompetitions[Math.max(0, Math.min(matchesTab, facrCompetitions.length - 1))];
|
||||
const items = Array.isArray(comp?.matches) ? comp.matches : [];
|
||||
const upcoming = items
|
||||
.map((m: any) => ({ m, t: new Date(`${m.date}T${(m.time || '00:00')}:00`).getTime() }))
|
||||
.filter((x: any) => !isNaN(x.t) && x.t > Date.now())
|
||||
.sort((a: any, b: any) => a.t - b.t)[0]?.m;
|
||||
const show = upcoming || items[0] || null;
|
||||
const link = (show && (show.facr_link || show.report_url)) || comp?.matches_link || nextMatchLink;
|
||||
const handleNextMatchClick = () => {
|
||||
if (show) {
|
||||
setSelectedMatch({
|
||||
...show,
|
||||
competition: comp?.name,
|
||||
});
|
||||
setIsMatchModalOpen(true);
|
||||
} else if (link) {
|
||||
window.open(link, '_blank', 'noopener,noreferrer');
|
||||
}
|
||||
};
|
||||
{isVisible('matches', true) ? (
|
||||
facrCompetitions.length > 0 ? (
|
||||
upcomingCompIndices.length > 0 ? (
|
||||
(() => {
|
||||
const safeIndex = Math.max(0, Math.min(nextCompIdx, facrCompetitions.length - 1));
|
||||
const pos = upcomingCompIndices.indexOf(safeIndex);
|
||||
const effectiveIndex = pos >= 0 ? upcomingCompIndices[pos] : upcomingCompIndices[0];
|
||||
const comp = facrCompetitions[effectiveIndex];
|
||||
const items = Array.isArray(comp?.matches) ? comp.matches : [];
|
||||
const upcoming = items
|
||||
.map((m: any) => ({ m, t: new Date(`${m.date}T${(m.time || '00:00')}:00`).getTime() }))
|
||||
.filter((x: any) => !isNaN(x.t) && x.t > Date.now())
|
||||
.sort((a: any, b: any) => a.t - b.t)[0]?.m;
|
||||
const show = upcoming || null;
|
||||
const link = (show && (show.facr_link || show.report_url)) || comp?.matches_link || nextMatchLink;
|
||||
const prevIdx = upcomingCompIndices[(Math.max(0, pos) - 1 + upcomingCompIndices.length) % upcomingCompIndices.length];
|
||||
const nextIdx = upcomingCompIndices[(Math.max(0, pos) + 1) % upcomingCompIndices.length];
|
||||
const handleNextMatchClick = () => {
|
||||
if (show) {
|
||||
setSelectedMatch({
|
||||
...show,
|
||||
competition: comp?.name,
|
||||
});
|
||||
setIsMatchModalOpen(true);
|
||||
} else if (link) {
|
||||
window.open(link, '_blank', 'noopener,noreferrer');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
return (
|
||||
<NextMatch
|
||||
data={show}
|
||||
competitionName={comp?.name}
|
||||
countdown={countdown}
|
||||
onPrev={() => setNextCompIdx(prevIdx)}
|
||||
onNext={() => setNextCompIdx(nextIdx)}
|
||||
onOpen={handleNextMatchClick}
|
||||
elementProps={{
|
||||
'data-element': 'matches' as any,
|
||||
'data-variant': getVariant('matches', 'compact') as any,
|
||||
'aria-live': 'polite' as any,
|
||||
style: { ...getStyles('matches') },
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})()
|
||||
) : null
|
||||
) : (
|
||||
<div className="card">
|
||||
<NextMatch
|
||||
data={show}
|
||||
competitionName={comp?.name}
|
||||
countdown={countdown}
|
||||
onPrev={() => setMatchesTab((i) => (i - 1 + facrCompetitions.length) % facrCompetitions.length)}
|
||||
onNext={() => setMatchesTab((i) => (i + 1) % facrCompetitions.length)}
|
||||
onOpen={handleNextMatchClick}
|
||||
elementProps={{
|
||||
'data-element': 'matches' as any,
|
||||
'data-variant': getVariant('matches', 'compact') as any,
|
||||
style: { ...getStyles('matches') },
|
||||
key={`matches-${refreshKey}-${getVariant('matches', 'compact')}`}
|
||||
data={{
|
||||
home: matches[0]?.homeTeam || clubName,
|
||||
home_logo_url: matches[0]?.homeLogoURL || clubLogo,
|
||||
away: matches[0]?.awayTeam || 'Soupeř',
|
||||
away_logo_url: matches[0]?.awayLogoURL,
|
||||
}}
|
||||
countdown={countdown}
|
||||
elementProps={{ 'data-element': 'matches', 'data-variant': getVariant('matches', 'compact'), 'aria-live': 'polite', style: { position: 'relative', ...getStyles('matches') } }}
|
||||
/>
|
||||
);
|
||||
})()
|
||||
) : isVisible('matches', true) ? (
|
||||
<div className="card">
|
||||
<NextMatch
|
||||
key={`matches-${refreshKey}-${getVariant('matches', 'compact')}`}
|
||||
data={{
|
||||
home: matches[0]?.homeTeam || clubName,
|
||||
home_logo_url: matches[0]?.homeLogoURL || clubLogo,
|
||||
away: matches[0]?.awayTeam || 'Soupeř',
|
||||
away_logo_url: matches[0]?.awayLogoURL,
|
||||
}}
|
||||
countdown={countdown}
|
||||
elementProps={{ 'data-element': 'matches', 'data-variant': getVariant('matches', 'compact'), style: { position: 'relative', ...getStyles('matches') } }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
) : null}
|
||||
|
||||
{/* Sweepstakes / Lottery widget (visible around matches section) */}
|
||||
@@ -1570,6 +1628,20 @@ const HomePage: React.FC = () => {
|
||||
</Suspense>
|
||||
) : null
|
||||
)}
|
||||
|
||||
{facrCompetitions.length === 0 && isLoading && (
|
||||
<section data-element="matches-slider" data-variant={getVariant('matches-slider', 'carousel')} aria-label="Zápasy" style={{ position: 'relative', contentVisibility: 'auto' as any, containIntrinsicSize: '280px', ...getStyles('matches-slider') }}>
|
||||
<div className="section-head" style={{ marginTop: 16, marginBottom: 16 }}>
|
||||
<h3>Zápasy</h3>
|
||||
<a href="/kalendar" className="see-all">Všechny zápasy</a>
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: 18, overflow: 'hidden', padding: '8px 2px 16px 2px' }}>
|
||||
{[1,2,3].map((i) => (
|
||||
<div key={i} className="card skeleton" style={{ minWidth: 340, height: 160, borderRadius: 12 }} />
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* News + Tables: split into two independent sections */}
|
||||
{(() => {
|
||||
@@ -1597,23 +1669,31 @@ const HomePage: React.FC = () => {
|
||||
style={{ marginTop: 32 }}
|
||||
>
|
||||
{showNews && (
|
||||
<section key={`news-${refreshKey}-${newsVariant}`} data-element="news" data-variant={newsVariant} className="news-list" style={{ ...getStyles('news') }}>
|
||||
<section key={`news-${refreshKey}-${newsVariant}`} data-element="news" data-variant={newsVariant} className="news-list" aria-labelledby="home-news-heading" style={{ ...getStyles('news'), contentVisibility: 'auto' as any, containIntrinsicSize: '600px' }}>
|
||||
<div className="section-head" style={{ marginTop: 0 }}>
|
||||
<h3>Další aktuality</h3>
|
||||
<h3 id="home-news-heading">Další aktuality</h3>
|
||||
<a href="/news" className="see-all" style={{ fontSize: '0.85rem' }}>Zobrazit vše <FiArrowRight size={14} /></a>
|
||||
</div>
|
||||
{newsVariant === 'scroller' ? (
|
||||
<BlogCardsScroller />
|
||||
) : (
|
||||
<NewsList items={news as any} />
|
||||
isLoading && (!news || (news as any).length === 0) ? (
|
||||
<div className="blog-list">
|
||||
{[1,2,3,4].map(i => (
|
||||
<div key={i} className="card skeleton" style={{ height: 96, borderRadius: 12 }} />
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<NewsList items={news as any} />
|
||||
)
|
||||
)}
|
||||
</section>
|
||||
)}
|
||||
|
||||
{showTable && (
|
||||
<div key={`table-${refreshKey}-${getVariant('table', 'split_news')}`} data-element="table" data-variant={getVariant('table', 'split_news')} style={{ ...getStyles('table') }}>
|
||||
<div key={`table-${refreshKey}-${getVariant('table', 'split_news')}`} data-element="table" data-variant={getVariant('table', 'split_news')} role="region" aria-labelledby="home-table-heading" style={{ ...getStyles('table'), contentVisibility: 'auto' as any, containIntrinsicSize: '520px' }}>
|
||||
<div className="section-head" style={{ marginTop: 0, marginBottom: 12 }}>
|
||||
<h3>Tabulky</h3>
|
||||
<h3 id="home-table-heading">Tabulky</h3>
|
||||
<a href="/tabulky" className="see-all" style={{ fontSize: '0.85rem' }}>Zobrazit vše <FiArrowRight size={14} /></a>
|
||||
</div>
|
||||
{defer ? (
|
||||
@@ -1639,7 +1719,15 @@ const HomePage: React.FC = () => {
|
||||
}}
|
||||
/>
|
||||
</Suspense>
|
||||
) : null}
|
||||
) : (
|
||||
<div className="table-card">
|
||||
<div className="standings">
|
||||
{[1,2,3,4,5,6,7,8].map(i => (
|
||||
<div key={i} className="standing-row skeleton" style={{ borderRadius: 12 }} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{/* Banners under the table, inside the table column */}
|
||||
{(banners || []).some(b => b.placement === 'homepage_under_table') && (
|
||||
defer ? (
|
||||
@@ -1657,12 +1745,28 @@ const HomePage: React.FC = () => {
|
||||
{/* (Moved) Banner under tables now renders inside the table column above */}
|
||||
|
||||
{/* Competition tables moved into right column below */}
|
||||
|
||||
{upcomingEvents.length > 0 && isVisible('activities', true) && (
|
||||
<section key={`activities-${refreshKey}-${getVariant('activities', 'list')}`} data-element="activities" data-variant={getVariant('activities', 'list')} style={{ marginTop: 32, marginBottom: 16, position: 'relative', ...getStyles('activities') }}>
|
||||
|
||||
{isVisible('activities', true) && !activitiesLoaded && (
|
||||
<section data-element="activities" data-variant={getVariant('activities', 'list')} aria-labelledby="home-activities-heading" style={{ marginTop: 32, marginBottom: 16, position: 'relative', contentVisibility: 'auto' as any, containIntrinsicSize: '600px', ...getStyles('activities') }}>
|
||||
<div style={{ maxWidth: 1200, margin: '0 auto', padding: '0 12px' }}>
|
||||
<div className="section-head" style={{ marginTop: 0 }}>
|
||||
<h3>Aktivity</h3>
|
||||
<h3 id="home-activities-heading">Aktivity</h3>
|
||||
<a href="/aktivity" className="see-all">Zobrazit vše <FiArrowRight /></a>
|
||||
</div>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr', gap: 12 }}>
|
||||
{[1,2,3].map(i => (
|
||||
<div key={i} className="card skeleton" style={{ height: 120, borderRadius: 12 }} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{upcomingEvents.length > 0 && isVisible('activities', true) && (
|
||||
<section key={`activities-${refreshKey}-${getVariant('activities', 'list')}`} data-element="activities" data-variant={getVariant('activities', 'list')} aria-labelledby="home-activities-heading" style={{ marginTop: 32, marginBottom: 16, position: 'relative', contentVisibility: 'auto' as any, containIntrinsicSize: '600px', ...getStyles('activities') }}>
|
||||
<div style={{ maxWidth: 1200, margin: '0 auto', padding: '0 12px' }}>
|
||||
<div className="section-head" style={{ marginTop: 0 }}>
|
||||
<h3 id="home-activities-heading">Aktivity</h3>
|
||||
<a href="/aktivity" className="see-all">Zobrazit vše <FiArrowRight /></a>
|
||||
</div>
|
||||
<ActivitiesList items={upcomingEvents as any} />
|
||||
@@ -1671,10 +1775,25 @@ const HomePage: React.FC = () => {
|
||||
)}
|
||||
|
||||
{/* Players scroller */}
|
||||
{players.length > 0 && isVisible('team', false) && (
|
||||
<section key={`team-${refreshKey}-${getVariant('team', 'grid')}`} data-element="team" data-variant={getVariant('team', 'grid')} className="players-scroller" style={{ marginTop: 32, position: 'relative', ...getStyles('team') }}>
|
||||
|
||||
{isVisible('team', false) && players.length === 0 && isLoading && (
|
||||
<section data-element="team" data-variant={getVariant('team', 'grid')} className="players-scroller" aria-labelledby="home-players-heading" style={{ marginTop: 32, position: 'relative', contentVisibility: 'auto' as any, containIntrinsicSize: '600px', ...getStyles('team') }}>
|
||||
<div className="section-head">
|
||||
<h3>Hráči</h3>
|
||||
<h3 id="home-players-heading">Hráči</h3>
|
||||
<a href="/players" className="see-all">Zobrazit vše <FiArrowRight /></a>
|
||||
</div>
|
||||
<div className="scroll-x">
|
||||
{[1,2,3,4,5,6].map(i => (
|
||||
<div key={i} className="player-card skeleton" style={{ width: 170, height: 210, borderRadius: 14 }} />
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{players.length > 0 && isVisible('team', false) && (
|
||||
<section key={`team-${refreshKey}-${getVariant('team', 'grid')}`} data-element="team" data-variant={getVariant('team', 'grid')} className="players-scroller" aria-labelledby="home-players-heading" style={{ marginTop: 32, position: 'relative', contentVisibility: 'auto' as any, containIntrinsicSize: '600px', ...getStyles('team') }}>
|
||||
<div className="section-head">
|
||||
<h3 id="home-players-heading">Hráči</h3>
|
||||
<a href="/players" className="see-all">Zobrazit vše <FiArrowRight /></a>
|
||||
</div>
|
||||
<div className="scroll-x">
|
||||
@@ -1691,7 +1810,7 @@ const HomePage: React.FC = () => {
|
||||
|
||||
{/* Gallery */}
|
||||
{isVisible('gallery', false) && (
|
||||
<section key={`gallery-${refreshKey}-${getVariant('gallery', 'grid')}`} data-element="gallery" data-variant={getVariant('gallery', 'grid')} style={{ marginTop: 32, marginBottom: 32, position: 'relative', ...getStyles('gallery') }}>
|
||||
<section key={`gallery-${refreshKey}-${getVariant('gallery', 'grid')}`} data-element="gallery" data-variant={getVariant('gallery', 'grid')} aria-labelledby="home-gallery-heading" style={{ marginTop: 32, marginBottom: 32, position: 'relative', contentVisibility: 'auto' as any, containIntrinsicSize: '700px', ...getStyles('gallery') }}>
|
||||
<div style={{ maxWidth: 1200, margin: '0 auto', padding: '0 12px' }}>
|
||||
{defer ? (
|
||||
<Suspense fallback={null}>
|
||||
@@ -1704,7 +1823,7 @@ const HomePage: React.FC = () => {
|
||||
|
||||
{/* Videos */}
|
||||
{isVisible('videos', false) && (
|
||||
<section key={`videos-${refreshKey}-${getVariant('videos', 'carousel')}`} data-element="videos" data-variant={getVariant('videos', 'carousel')} style={{ marginTop: 32, marginBottom: 32, position: 'relative', ...getStyles('videos') }}>
|
||||
<section key={`videos-${refreshKey}-${getVariant('videos', 'carousel')}`} data-element="videos" data-variant={getVariant('videos', 'carousel')} aria-labelledby="home-videos-heading" style={{ marginTop: 32, marginBottom: 32, position: 'relative', contentVisibility: 'auto' as any, containIntrinsicSize: '700px', ...getStyles('videos') }}>
|
||||
<div style={{ maxWidth: 1200, margin: '0 auto', padding: '0 12px' }}>
|
||||
{defer ? (
|
||||
<Suspense fallback={null}>
|
||||
@@ -1713,26 +1832,50 @@ const HomePage: React.FC = () => {
|
||||
variant={(getVariant('videos', 'carousel') as any) as 'grid' | 'carousel'}
|
||||
/>
|
||||
</Suspense>
|
||||
) : null}
|
||||
) : (
|
||||
<>
|
||||
<div className="section-head">
|
||||
<h3 id="home-videos-heading">Videa</h3>
|
||||
<a href="/videa" className="see-all">Více videí</a>
|
||||
</div>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr', gap: 12 }}>
|
||||
{[1,2,3].map((i) => (
|
||||
<div key={i} className="card skeleton" style={{ height: 240, borderRadius: 12 }} />
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{isVisible('merch', true) && (
|
||||
<section key={`merch-${refreshKey}-${getVariant('merch', 'grid')}`} data-element="merch" data-variant={getVariant('merch', 'grid')} style={{ marginTop: 24, marginBottom: 24, position: 'relative', ...getStyles('merch') }}>
|
||||
<section key={`merch-${refreshKey}-${getVariant('merch', 'grid')}`} data-element="merch" data-variant={getVariant('merch', 'grid')} aria-labelledby="home-merch-heading" style={{ marginTop: 24, marginBottom: 24, position: 'relative', contentVisibility: 'auto' as any, containIntrinsicSize: '600px', ...getStyles('merch') }}>
|
||||
<div style={{ maxWidth: 1200, margin: '0 auto', padding: '0 12px' }}>
|
||||
{defer ? (
|
||||
<Suspense fallback={null}>
|
||||
<MerchSection variant={(getVariant('merch', 'grid') as any) as 'grid' | 'carousel' | 'featured' | 'list'} />
|
||||
</Suspense>
|
||||
) : null}
|
||||
) : (
|
||||
<>
|
||||
<div className="section-head">
|
||||
<h3 id="home-merch-heading">Oblečení týmu</h3>
|
||||
<a href="/obleceni" className="see-all">Zobrazit vše</a>
|
||||
</div>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: 12 }}>
|
||||
{[1,2,3,4,5].map((i) => (
|
||||
<div key={i} className="card skeleton" style={{ height: 180, borderRadius: 12 }} />
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* Polls / Voting */}
|
||||
{isVisible('poll', false) && (
|
||||
<section key={`poll-${refreshKey}-${getVariant('poll', 'vertical')}`} data-element="poll" data-variant={getVariant('poll', 'vertical')} style={{ marginTop: 32, marginBottom: 32, position: 'relative', ...getStyles('poll') }}>
|
||||
<section key={`poll-${refreshKey}-${getVariant('poll', 'vertical')}`} data-element="poll" data-variant={getVariant('poll', 'vertical')} aria-label="Anketa" style={{ marginTop: 32, marginBottom: 32, position: 'relative', contentVisibility: 'auto' as any, containIntrinsicSize: '500px', ...getStyles('poll') }}>
|
||||
<div style={{ maxWidth: 1200, margin: '0 auto', padding: '0 12px' }}>
|
||||
{defer ? (
|
||||
<Suspense fallback={null}>
|
||||
@@ -1740,7 +1883,9 @@ const HomePage: React.FC = () => {
|
||||
<PollsWidget featuredOnly={true} maxPolls={1} title="Anketa" />
|
||||
</div>
|
||||
</Suspense>
|
||||
) : null}
|
||||
) : (
|
||||
<div className="card skeleton" style={{ height: 320, borderRadius: 12 }} />
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
@@ -1751,7 +1896,7 @@ const HomePage: React.FC = () => {
|
||||
{(banners || []).filter(b => b.placement === 'homepage_footer').map((b) => (
|
||||
<a key={b.id} href={b.url || '#'} target={b.url ? '_blank' : undefined} rel={b.url ? 'noopener noreferrer' : undefined} style={{ display: 'inline-block', margin: 8 }}>
|
||||
{/* eslint-disable-next-line jsx-a11y/alt-text */}
|
||||
<img src={b.image} alt={b.name} style={{ maxWidth: '100%', width: b.width ? `${b.width}px` : undefined, height: b.height ? `${b.height}px` : 'auto' }} />
|
||||
<img loading="lazy" decoding="async" src={b.image} alt={b.name} style={{ maxWidth: '100%', width: b.width ? `${b.width}px` : undefined, height: b.height ? `${b.height}px` : 'auto' }} />
|
||||
</a>
|
||||
))}
|
||||
</section>
|
||||
@@ -1759,13 +1904,15 @@ const HomePage: React.FC = () => {
|
||||
|
||||
{/* CTA (Newsletter) moved up */}
|
||||
{isVisible('newsletter', false) && (
|
||||
<section key={`newsletter-${refreshKey}-${getVariant('newsletter', 'default')}`} data-element="newsletter" data-variant={getVariant('newsletter', 'default')} className="newsletter-cta" style={{ marginTop: 24, marginBottom: 24, position: 'relative', ...getStyles('newsletter') }}>
|
||||
<section key={`newsletter-${refreshKey}-${getVariant('newsletter', 'default')}`} data-element="newsletter" data-variant={getVariant('newsletter', 'default')} className="newsletter-cta" aria-label="Přihlášení k newsletteru" style={{ marginTop: 24, marginBottom: 24, position: 'relative', contentVisibility: 'auto' as any, containIntrinsicSize: '420px', ...getStyles('newsletter') }}>
|
||||
<div className="card" style={{ maxWidth: 960, margin: '0 auto' }}>
|
||||
{defer ? (
|
||||
<Suspense fallback={null}>
|
||||
<NewsletterSubscribe />
|
||||
</Suspense>
|
||||
) : null}
|
||||
) : (
|
||||
<div className="skeleton" style={{ height: 280, borderRadius: 12 }} />
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
@@ -1830,6 +1977,7 @@ const HomePage: React.FC = () => {
|
||||
data-element="sponsors"
|
||||
data-variant={variant}
|
||||
className={`sponsors ${sponsorsTheme === 'dark' ? 'dark' : ''}`}
|
||||
aria-labelledby="home-sponsors-heading"
|
||||
style={{
|
||||
width: '100vw',
|
||||
position: 'relative',
|
||||
@@ -1839,19 +1987,28 @@ const HomePage: React.FC = () => {
|
||||
paddingLeft: 'max(16px, calc((100vw - 1200px) / 2))',
|
||||
paddingRight: 'max(16px, calc((100vw - 1200px) / 2))',
|
||||
boxSizing: 'border-box',
|
||||
contentVisibility: 'auto' as any,
|
||||
containIntrinsicSize: '520px',
|
||||
...getStyles('sponsors')
|
||||
}}
|
||||
>
|
||||
<div className="section-head">
|
||||
<h3>Sponzoři</h3>
|
||||
<h3 id="home-sponsors-heading">Sponzoři</h3>
|
||||
</div>
|
||||
{isLoading && ordered.length === 0 && (
|
||||
<div className="sponsors-grid">
|
||||
{[1,2,3,4,5,6,7,8].map(i => (
|
||||
<div key={i} className="sponsor-tile skeleton" style={{ minHeight: 90, borderRadius: 12 }} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{variant === 'grid' && (
|
||||
<>
|
||||
{general.length > 0 && (
|
||||
<div className="title-sponsor">
|
||||
{general.map((g) => (
|
||||
<a key={`g-${g.id}`} className="sponsor-tile" href={g.url || '#'} target="_blank" rel="noreferrer noopener">
|
||||
<img src={assetUrl(g.logo) || '/images/sponsors/placeholder.png'} alt={g.name} />
|
||||
<img loading="lazy" decoding="async" src={assetUrl(g.logo) || '/images/sponsors/placeholder.png'} alt={g.name} />
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
@@ -1860,7 +2017,7 @@ const HomePage: React.FC = () => {
|
||||
<div className="sponsors-grid">
|
||||
{(standard.length > 0 ? standard : (general.length === 0 ? ordered : [])).map((s) => (
|
||||
<a key={s.id} className="sponsor-tile" href={s.url || '#'} target="_blank" rel="noreferrer noopener">
|
||||
<img src={assetUrl(s.logo) || '/images/sponsors/placeholder.png'} alt={s.name} />
|
||||
<img loading="lazy" decoding="async" src={assetUrl(s.logo) || '/images/sponsors/placeholder.png'} alt={s.name} />
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
@@ -1872,7 +2029,7 @@ const HomePage: React.FC = () => {
|
||||
<div className="track">
|
||||
{[...ordered, ...ordered].map((s, idx) => (
|
||||
<a key={`${s.id}-${idx}`} className="sponsor-tile" href={s.url || '#'} target="_blank" rel="noreferrer noopener">
|
||||
<img src={assetUrl(s.logo) || '/images/sponsors/placeholder.png'} alt={s.name} />
|
||||
<img loading="lazy" decoding="async" src={assetUrl(s.logo) || '/images/sponsors/placeholder.png'} alt={s.name} />
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
@@ -1883,7 +2040,7 @@ const HomePage: React.FC = () => {
|
||||
<div className="belt">
|
||||
{[...ordered, ...ordered, ...ordered].map((s, idx) => (
|
||||
<a key={`${s.id}-${idx}`} className="sponsor-tile" href={s.url || '#'} target="_blank" rel="noreferrer noopener">
|
||||
<img src={assetUrl(s.logo) || '/images/sponsors/placeholder.png'} alt={s.name} />
|
||||
<img loading="lazy" decoding="async" src={assetUrl(s.logo) || '/images/sponsors/placeholder.png'} alt={s.name} />
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
@@ -1943,11 +2100,8 @@ const HomePage: React.FC = () => {
|
||||
};
|
||||
|
||||
function czYears(n: number): string {
|
||||
const mod100 = n % 100;
|
||||
if (mod100 >= 11 && mod100 <= 14) return 'let';
|
||||
const mod10 = n % 10;
|
||||
if (mod10 === 1) return 'rok';
|
||||
if (mod10 >= 2 && mod10 <= 4) return 'roky';
|
||||
if (n === 1) return 'rok';
|
||||
if (n >= 2 && n <= 4) return 'roky';
|
||||
return 'let';
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user