This commit is contained in:
Tomáš Dvořák
2025-10-16 17:10:13 +02:00
parent f5e7be92c7
commit 35d0954afd
84 changed files with 9571 additions and 4668 deletions
+43 -83
View File
@@ -4,7 +4,7 @@ import { FiArrowRight, FiCalendar, FiUsers, FiAward, FiChevronLeft, FiChevronRig
import '../styles/theme.css';
import './styles/UnifiedHome.css';
import { getPublicSettings } from '../services/settings';
import { assetUrl } from '../utils/url';
import { assetUrl, sanitizeClubName } from '../utils/url';
import { getPlayers as apiGetPlayers, Player as ApiPlayer } from '../services/players';
import { getSponsors as apiGetSponsors, Sponsor as ApiSponsor } from '../services/sponsors';
import BlogCardsScroller from '../components/home/BlogCardsScroller';
@@ -17,6 +17,7 @@ import NewsletterSubscribe from '../components/newsletter/NewsletterSubscribe';
import MyUIbrixStyleEditor from '../components/editor/MyUIbrixEditor';
import ClubModal from '../components/home/ClubModal';
import MatchModal from '../components/home/MatchModal';
import { useAllPageElementConfigs } from '../hooks/usePageElementConfig';
// Types for real API-driven data
type NewsItem = {
@@ -101,6 +102,9 @@ const HomePage: React.FC = () => {
const [aliasMap, setAliasMap] = useState<Record<string, { alias: string; original_name?: string }>>({});
const [settings, setSettings] = useState<any>(null);
const [isLoading, setIsLoading] = useState<boolean>(true);
// MyUIbrix element configuration hook for live preview
const { getVariant, isVisible, loading: configLoading } = useAllPageElementConfigs('homepage');
useEffect(() => {
let cancelled = false;
@@ -578,52 +582,8 @@ const HomePage: React.FC = () => {
return () => { disposed = true; };
}, [clubLogo]);
// Listen to MyUIbrix events for live preview
useEffect(() => {
const handleMyUIbrixChange = (e: CustomEvent) => {
const { elementName, variant, visible, previewMode } = e.detail;
if (!previewMode) return; // Only respond to preview mode changes
// For now, log the change - full implementation would update element visibility/variant
console.log(`MyUIbrix: ${elementName} -> ${variant} (visible: ${visible})`);
// You can implement logic here to dynamically show/hide or restyle elements
// For example:
// - Toggle display on data-element sections based on visibility
// - Apply variant-specific classes
};
const handleMyUIbrixStyleChange = (e: CustomEvent) => {
const { elementName, styles, previewMode } = e.detail;
if (!previewMode) return;
// Apply custom styles to elements
const elements = document.querySelectorAll(`[data-element="${elementName}"]`);
elements.forEach((el: any) => {
Object.keys(styles).forEach((key) => {
el.style[key] = styles[key];
});
});
};
const handleMyUIbrixReorder = (e: CustomEvent) => {
const { order, previewMode } = e.detail;
if (!previewMode) return;
// Reorder elements based on the order array
console.log('MyUIbrix: Reorder elements', order);
};
window.addEventListener('myuibrix-change' as any, handleMyUIbrixChange);
window.addEventListener('myuibrix-style-change' as any, handleMyUIbrixStyleChange);
window.addEventListener('myuibrix-reorder' as any, handleMyUIbrixReorder);
return () => {
window.removeEventListener('myuibrix-change' as any, handleMyUIbrixChange);
window.removeEventListener('myuibrix-style-change' as any, handleMyUIbrixStyleChange);
window.removeEventListener('myuibrix-reorder' as any, handleMyUIbrixReorder);
};
}, []);
// MyUIbrix events are handled by useAllPageElementConfigs hook
// It automatically updates getVariant() and isVisible() when changes occur in edit mode
// Countdown to next match (uses selected competition upcoming if available)
useEffect(() => {
@@ -707,9 +667,9 @@ const HomePage: React.FC = () => {
<div className="list">
{(facrCompetitions[matchesTab]?.matches || []).slice(0,4).map((m:any, idx:number) => (
<a key={m.id || idx} className="row" href={m.facr_link || m.report_url || '#'} target="_blank" rel="noopener noreferrer">
<div className="team"><img src={assetUrl(m.home_logo_url)} alt={m.home} /><span>{m.home}</span></div>
<div className="team"><img src={assetUrl(m.home_logo_url)} alt={m.home} /><span>{sanitizeClubName(m.home)}</span></div>
<div className="meta"><span>{new Date(`${m.date}T${(m.time||'00:00')}:00`).toLocaleDateString()}</span><span></span><span>{m.time || ''}</span></div>
<div className="team"><img src={assetUrl(m.away_logo_url)} alt={m.away} /><span>{m.away}</span></div>
<div className="team"><img src={assetUrl(m.away_logo_url)} alt={m.away} /><span>{sanitizeClubName(m.away)}</span></div>
</a>
))}
</div>
@@ -834,12 +794,12 @@ const HomePage: React.FC = () => {
<div className="row teams">
<div className="team">
<img src={assetUrl(m.home_logo_url)} alt={m.home} />
<span>{m.home}</span>
<span>{sanitizeClubName(m.home)}</span>
</div>
<span className="vs">vs</span>
<div className="team">
<img src={assetUrl(m.away_logo_url)} alt={m.away} />
<span>{m.away}</span>
<span>{sanitizeClubName(m.away)}</span>
</div>
</div>
</a>
@@ -1404,8 +1364,8 @@ const HomePage: React.FC = () => {
</div>
</div>
{/* Hero section: variant controlled by settings.hero_style */}
{heroStyle === 'grid' && (
{/* Hero section: variant controlled by MyUIbrix (getVariant) or fallback to settings.hero_style */}
{getVariant('hero', heroStyle) === 'grid' && isVisible('hero', true) && (
<section data-element="hero" className="hero-grid">
{news[0] ? (
<a href={`/news/${news[0].slug || news[0].id}`} className="hero-card big" style={{ textDecoration: 'none' }}>
@@ -1435,10 +1395,11 @@ const HomePage: React.FC = () => {
</a>
))}
{Array.from({ length: Math.max(0, 2 - Math.min(2, Math.max(0, news.length - 1))) }).map((_, idx) => (
<div key={`placeholder-${idx}`} className="hero-card small" style={{ background: 'var(--bg-soft)', pointerEvents: 'none' }}>
<div className="overlay" style={{ background: 'linear-gradient(to bottom, transparent 0%, rgba(0,0,0,0.3) 40%, rgba(0,0,0,0.6) 100%)' }}>
<div style={{ opacity: 0.6, fontSize: '0.8rem', color: 'var(--text-on-primary)' }}>Aktuality</div>
<h3 style={{ margin: '4px 0 0 0', color: 'var(--text-on-primary)', opacity: 0.6 }}>Připravujeme...</h3>
<div key={`placeholder-${idx}`} className="hero-card small" style={{ pointerEvents: '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>
</div>
))}
@@ -1446,7 +1407,7 @@ const HomePage: React.FC = () => {
</section>
)}
{/* Banner: homepage_middle */}
{(banners || []).some(b => b.placement === 'homepage_middle') && (
{(banners || []).some(b => b.placement === 'homepage_middle') && isVisible('banner', true) && (
<section data-element="banner" className="banner banner-middle" style={{ margin: '24px 0', textAlign: 'center' }}>
{(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 }}>
@@ -1458,7 +1419,7 @@ const HomePage: React.FC = () => {
)}
{/* Featured articles grid (uses Articles.featured flag) */}
{featured.length > 0 && (
{featured.length > 0 && isVisible('news', true) && (
<section data-element="news" className="three-cols" style={{ marginTop: 8 }}>
{featured.map((n) => (
<a key={n.id} href={`/news/${n.slug || n.id}`} className="hero-card small" style={{ textDecoration: 'none', height: 220 }}>
@@ -1488,19 +1449,19 @@ const HomePage: React.FC = () => {
</div>
</section>
)}
{heroStyle === 'scroller' && (
{getVariant('hero', heroStyle) === 'scroller' && isVisible('hero', true) && (
<section data-element="hero">
<BlogCardsScroller />
</section>
)}
{(heroStyle === 'swiper' || heroStyle === 'swiper_full') && (
<section data-element="hero" style={heroStyle === 'swiper_full' ? { marginLeft: 'calc(50% - 50vw)', marginRight: 'calc(50% - 50vw)' } : undefined}>
{(getVariant('hero', heroStyle) === 'swiper' || getVariant('hero', heroStyle) === 'swiper_full') && isVisible('hero', true) && (
<section data-element="hero" style={getVariant('hero', heroStyle) === 'swiper_full' ? { marginLeft: 'calc(50% - 50vw)', marginRight: 'calc(50% - 50vw)' } : undefined}>
<BlogSwiper />
</section>
)}
{/* Next match: categories (competitions) with left/right navigation - synced with matchesTab */}
{facrCompetitions.length > 0 ? (
{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 : [];
@@ -1532,7 +1493,7 @@ const HomePage: React.FC = () => {
</button>
<div className="team">
<img className="logo" src={assetUrl(show?.home_logo_url) || assetUrl(clubLogo) || '/images/club-logo.png'} alt="Domácí" />
<div>{show?.home || matches[0]?.homeTeam || clubName}</div>
<div>{sanitizeClubName(show?.home || matches[0]?.homeTeam || clubName)}</div>
</div>
<div className="countdown">
<div style={{ fontSize: '0.8rem', opacity: 0.85, marginBottom: 4 }}>{comp?.name || 'Soutěž'}</div>
@@ -1541,7 +1502,7 @@ const HomePage: React.FC = () => {
</div>
<div className="team">
<img className="logo" src={assetUrl(show?.away_logo_url) || '/images/club-opponent.png'} alt="Hosté" />
<div>{show?.away || matches[0]?.awayTeam || 'Soupeř'}</div>
<div>{sanitizeClubName(show?.away || matches[0]?.awayTeam || 'Soupeř')}</div>
</div>
<button
aria-label="Další soutěž"
@@ -1554,11 +1515,11 @@ const HomePage: React.FC = () => {
</section>
);
})()
) : (
<section className="next-match">
) : isVisible('matches', true) ? (
<section data-element="matches" className="next-match">
<div className="team">
<img className="logo" src={assetUrl(matches[0]?.homeLogoURL) || assetUrl(clubLogo) || '/images/club-logo.png'} alt="Domácí" />
<div>{matches[0]?.homeTeam || clubName}</div>
<div>{sanitizeClubName(matches[0]?.homeTeam || clubName)}</div>
</div>
<div className="countdown">
{countdown || '—'}
@@ -1571,10 +1532,10 @@ const HomePage: React.FC = () => {
</div>
<div className="team">
<img className="logo" src={assetUrl(matches[0]?.awayLogoURL) || '/images/club-opponent.png'} alt="Hosté" />
<div>{matches[0]?.awayTeam || 'Soupeř'}</div>
<div>{sanitizeClubName(matches[0]?.awayTeam || 'Soupeř')}</div>
</div>
</section>
)}
) : null}
{/* Matches slider with scores by competition */}
{facrCompetitions.length > 0 && (
@@ -1625,7 +1586,7 @@ const HomePage: React.FC = () => {
<div className="teams">
<div className="team">
<img src={assetUrl(m.home_logo_url)} alt={m.home} />
<div className="name">{m.home}</div>
<div className="name">{sanitizeClubName(m.home)}</div>
</div>
<div className="score">
{m.score ? (
@@ -1640,7 +1601,7 @@ const HomePage: React.FC = () => {
</div>
<div className="team">
<img src={assetUrl(m.away_logo_url)} alt={m.away} />
<div className="name">{m.away}</div>
<div className="name">{sanitizeClubName(m.away)}</div>
</div>
</div>
</div>
@@ -1658,6 +1619,7 @@ const HomePage: React.FC = () => {
{/* Competition tables moved into right column below */}
{/* Standings: tabs per competition (only FACR), clicking row opens ClubModal */}
{isVisible('table', true) && (
<section data-element="table" className="standings" style={{ marginTop: 32 }}>
<div>
<div className="section-head" style={{ marginTop: 0 }}>
@@ -1690,15 +1652,6 @@ const HomePage: React.FC = () => {
<h3>Tabulky</h3>
<a href="/tabulky" className="see-all" style={{ fontSize: '0.85rem' }}>Zobrazit vše <FiArrowRight size={14} /></a>
</div>
<div className="tabs" style={{ marginBottom: 12 }}>
{standings.length > 0 ? standings.map((s:any, i: number) => (
<button key={`${s.name}-${i}`} className={i===matchesTab ? 'active' : ''} onClick={() => setMatchesTab(i)}>
<span>{s.name || s.competition || 'Soutěž'}</span>
</button>
)) : ['Liga'].map((t: string, i: number) => (
<button key={`${t}-${i}`} className={i===matchesTab ? 'active' : ''} disabled>{t}</button>
))}
</div>
{standings.length > 0 ? (
<div className="standings">
{(standings[matchesTab]?.table || standings[matchesTab]?.rows || []).slice(0,8).map((row: any, idx: number) => {
@@ -1738,9 +1691,10 @@ const HomePage: React.FC = () => {
</div>
</div>
</section>
)}
{/* Players scroller (optional) */}
{players.length > 0 && (
{players.length > 0 && isVisible('team', false) && (
<section data-element="team" className="players-scroller" style={{ marginTop: 32 }}>
<div className="section-head">
<h3>Hráči</h3>
@@ -1775,13 +1729,15 @@ const HomePage: React.FC = () => {
)}
{/* Videos */}
{isVisible('videos', false) && (
<section data-element="videos" style={{ marginTop: 32, marginBottom: 32 }}>
<div style={{ maxWidth: 1200, margin: '0 auto', padding: '0 12px' }}>
<VideosSection />
</div>
</section>
)}
{true && (
{isVisible('merch', true) && (
<section data-element="merch" style={{ marginTop: 24, marginBottom: 24 }}>
<div style={{ maxWidth: 1200, margin: '0 auto', padding: '0 12px' }}>
<MerchSection />
@@ -1790,11 +1746,13 @@ const HomePage: React.FC = () => {
)}
{/* Newsletter subscription CTA */}
{isVisible('newsletter', false) && (
<section data-element="newsletter" className="newsletter-cta" style={{ marginTop: 24, marginBottom: 24 }}>
<div className="card" style={{ maxWidth: 960, margin: '0 auto' }}>
<NewsletterSubscribe />
</div>
</section>
)}
{/* Banner: homepage_top */}
{(banners || []).some(b => b.placement === 'homepage_top') && (
@@ -1821,6 +1779,7 @@ const HomePage: React.FC = () => {
)}
{/* Sponsors: grid or slider (controlled by settings); dark theme supported; full-bleed */}
{isVisible('sponsors', true) && (
<section
data-element="sponsors"
className={`sponsors ${sponsorsTheme === 'dark' ? 'dark' : ''}`}
@@ -1874,6 +1833,7 @@ const HomePage: React.FC = () => {
</div>
)}
</section>
)}
</div>
<ClubModal
isOpen={isModalOpen}