mirror of
https://github.com/Dvorinka/MyClubServer.git
synced 2026-06-04 18:52:56 +00:00
dev day #69
This commit is contained in:
@@ -0,0 +1,177 @@
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { Link as RouterLink, useLocation } from 'react-router-dom';
|
||||
import '../../styles/sparta-styles.css';
|
||||
import { usePublicSettings } from '../../hooks/usePublicSettings';
|
||||
import { useClubTheme } from '../../contexts/ClubThemeContext';
|
||||
import { getNavigationItems, NavigationItem, seedDefaultNavigation } from '../../services/navigation';
|
||||
import { getCategories, Category } from '../../services/public';
|
||||
|
||||
// Minimal NavLink type used to render items
|
||||
type NavLink = { label: string; to?: string; items?: { label: string; to: string }[]; external?: boolean };
|
||||
|
||||
const SpartaNavbar: React.FC = () => {
|
||||
const { data: settings } = usePublicSettings();
|
||||
const theme = useClubTheme();
|
||||
const location = useLocation();
|
||||
|
||||
const [mobileOpen, setMobileOpen] = useState(false);
|
||||
const [dynamicNavItems, setDynamicNavItems] = useState<NavigationItem[]>([]);
|
||||
const [navLoading, setNavLoading] = useState(true);
|
||||
const [navCategories, setNavCategories] = useState<Category[] | null>(null);
|
||||
|
||||
// Load dynamic navigation
|
||||
useEffect(() => {
|
||||
let active = true;
|
||||
(async () => {
|
||||
try {
|
||||
const items = await getNavigationItems();
|
||||
if (active && Array.isArray(items)) {
|
||||
const publicItems = items.filter(item => !item.requires_admin);
|
||||
if (publicItems.length === 0) {
|
||||
try {
|
||||
await seedDefaultNavigation();
|
||||
const newItems = await getNavigationItems();
|
||||
if (active && Array.isArray(newItems)) {
|
||||
const publicNewItems = newItems.filter(item => !item.requires_admin);
|
||||
setDynamicNavItems(publicNewItems);
|
||||
}
|
||||
} catch {
|
||||
setDynamicNavItems([]);
|
||||
}
|
||||
} else {
|
||||
setDynamicNavItems(publicItems);
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// leave empty, fallback will handle
|
||||
} finally {
|
||||
if (active) setNavLoading(false);
|
||||
}
|
||||
})();
|
||||
return () => { active = false };
|
||||
}, []);
|
||||
|
||||
// Load categories (for Blog dropdown fallback)
|
||||
useEffect(() => {
|
||||
let active = true;
|
||||
(async () => {
|
||||
try {
|
||||
const cats = await getCategories();
|
||||
if (active && Array.isArray(cats) && cats.length > 0) {
|
||||
setNavCategories(cats);
|
||||
} else if (active && Array.isArray(settings?.categories)) {
|
||||
setNavCategories(settings!.categories as any);
|
||||
}
|
||||
} catch {
|
||||
if (active && Array.isArray(settings?.categories)) {
|
||||
setNavCategories(settings!.categories as any);
|
||||
}
|
||||
}
|
||||
})();
|
||||
return () => { active = false };
|
||||
}, [settings?.categories]);
|
||||
|
||||
const isPathActive = (to?: string) => {
|
||||
if (!to) return false;
|
||||
return location.pathname === to || location.pathname.startsWith((to || '') + '/');
|
||||
};
|
||||
|
||||
const convertToNavLink = (item: NavigationItem): NavLink => {
|
||||
const link: NavLink = {
|
||||
label: item.label,
|
||||
to: item.url || '#',
|
||||
external: item.type === 'external',
|
||||
};
|
||||
if (item.type === 'dropdown' && item.children && item.children.length > 0) {
|
||||
link.items = item.children.map(child => ({ label: child.label, to: child.url || '#' }));
|
||||
}
|
||||
return link;
|
||||
};
|
||||
|
||||
const categoryItems = useMemo(() => {
|
||||
const source = Array.isArray(navCategories) && navCategories.length > 0 ? navCategories : [];
|
||||
return source.map((cat: any) => ({ label: cat.name, to: cat.url || (cat.slug ? `/blog?category=${encodeURIComponent(cat.slug)}` : '/blog') }));
|
||||
}, [navCategories]);
|
||||
|
||||
const NAV_LINKS: NavLink[] = useMemo(() => {
|
||||
if (!navLoading && dynamicNavItems.length > 0) {
|
||||
const navLinks = dynamicNavItems.map(convertToNavLink);
|
||||
if (categoryItems.length > 0) {
|
||||
const idx = navLinks.findIndex(l => l.label === 'Články' || l.label === 'Blog' || l.to === '/blog');
|
||||
if (idx !== -1) navLinks[idx] = { ...navLinks[idx], items: categoryItems };
|
||||
}
|
||||
return navLinks;
|
||||
}
|
||||
|
||||
// Fallback minimal menu
|
||||
const links: NavLink[] = [
|
||||
{ label: 'Domů', to: '/' },
|
||||
...(settings?.show_about_in_nav === false ? [] : [{ label: 'O klubu', to: '/o-klubu' } as NavLink]),
|
||||
{ label: 'Kalendář', to: '/kalendar' },
|
||||
{ label: 'Zápasy', to: '/zapasy' },
|
||||
{ label: 'Aktivity', to: '/aktivity' },
|
||||
{ label: 'Hráči', to: '/hraci' },
|
||||
categoryItems.length > 0 ? { label: 'Články', to: '/blog', items: categoryItems } : { label: 'Články', to: '/blog' },
|
||||
{ label: 'Videa', to: '/videa' },
|
||||
{ label: settings?.gallery_label || 'Fotogalerie', to: '/galerie' },
|
||||
...(settings?.shop_url ? [{ label: 'Fanshop', to: settings.shop_url, external: true } as NavLink] : []),
|
||||
{ label: 'Sponzoři', to: '/sponzori' },
|
||||
{ label: 'Kontakt', to: '/kontakt' },
|
||||
];
|
||||
return links;
|
||||
}, [navLoading, dynamicNavItems, settings?.show_about_in_nav, settings?.shop_url, settings?.gallery_label, categoryItems]);
|
||||
|
||||
const logoUrl = settings?.club_logo_url || theme.logoUrl || '/dist/img/logo-club-empty.svg';
|
||||
const clubName = settings?.club_name || theme.name || 'Klub';
|
||||
|
||||
return (
|
||||
<div className="sparta-navbar-container">
|
||||
<div className="sparta-navbar">
|
||||
{/* Burger toggle for mobile */}
|
||||
<button
|
||||
aria-label="Menu"
|
||||
className="sparta-navbar-toggle"
|
||||
onClick={() => setMobileOpen(o => !o)}
|
||||
>
|
||||
<div className="sparta-burger-icon" aria-hidden>
|
||||
<div className="sparta-burger-line" />
|
||||
<div className="sparta-burger-line" />
|
||||
<div className="sparta-burger-line" />
|
||||
</div>
|
||||
</button>
|
||||
|
||||
{/* Brand */}
|
||||
<RouterLink to="/" className="sparta-navbar-brand" onClick={() => setMobileOpen(false)}>
|
||||
<img src={logoUrl} alt={clubName} />
|
||||
</RouterLink>
|
||||
|
||||
{/* Links */}
|
||||
<nav
|
||||
className="sparta-navbar-links"
|
||||
style={{ display: mobileOpen ? 'flex' : undefined, flexWrap: 'wrap' }}
|
||||
>
|
||||
{NAV_LINKS.map((nav) => {
|
||||
const isActive = isPathActive(nav.to);
|
||||
const className = isActive ? 'sparta-button-tertiary' : 'sparta-button-tertiary';
|
||||
|
||||
if (nav.external && nav.to) {
|
||||
return (
|
||||
<a key={nav.label} href={nav.to} target="_blank" rel="noreferrer" className={className} onClick={() => setMobileOpen(false)}>
|
||||
{nav.label}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<RouterLink key={nav.label} to={nav.to || '#'} className={className} onClick={() => setMobileOpen(false)}>
|
||||
{nav.label}
|
||||
</RouterLink>
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SpartaNavbar;
|
||||
Reference in New Issue
Block a user