mirror of
https://github.com/Dvorinka/MyClubServer.git
synced 2026-06-03 18:22:57 +00:00
574 lines
22 KiB
TypeScript
574 lines
22 KiB
TypeScript
import React, { useEffect, useState } from 'react';
|
|
import { ChakraProvider, extendTheme } from '@chakra-ui/react';
|
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
import { BrowserRouter as Router, Routes, Route, Navigate, Outlet, useLocation } from 'react-router-dom';
|
|
import './styles/custom-scrollbar.css';
|
|
import { AuthProvider, useAuth } from './contexts/AuthContext';
|
|
import AuthPage from './pages/AuthPage';
|
|
import RegisterPage from './pages/RegisterPage';
|
|
import DashboardPage from './pages/DashboardPage';
|
|
import ArticlesListPage from './pages/ArticlesListPage';
|
|
import HomePage from './pages/HomePage';
|
|
import BlogPage from './pages/BlogPage';
|
|
import ArticleDetailPage from './pages/ArticleDetailPage';
|
|
import ActivityDetailPage from './pages/ActivityDetailPage';
|
|
import MatchDetailPage from './pages/MatchDetailPage';
|
|
import ClubPage from './pages/ClubPage';
|
|
import CalendarPage from './pages/CalendarPage';
|
|
import TablesPage from './pages/TablesPage';
|
|
import MatchesPage from './pages/MatchesPage';
|
|
import PlayersPage from './pages/PlayersPage';
|
|
import PlayerDetailPage from './pages/PlayerDetailPage';
|
|
import SponsorsPage from './pages/SponsorsPage';
|
|
import ContactPage from './pages/ContactPage';
|
|
import GalleryPage from './pages/GalleryPage';
|
|
import AlbumDetailPage from './pages/AlbumDetailPage';
|
|
import ForgotPasswordPage from './pages/ForgotPasswordPage';
|
|
import ResetPasswordPage from './pages/ResetPasswordPage';
|
|
import ActivitiesCalendarPage from './pages/ActivitiesCalendarPage';
|
|
import AdminDashboardPage from './pages/admin/AdminDashboardPage';
|
|
import ArticlesAdminPage from './pages/admin/ArticlesAdminPage';
|
|
import SponsorsAdminPage from './pages/admin/SponsorsAdminPage';
|
|
import CategoriesAdminPage from './pages/admin/CategoriesAdminPage';
|
|
import MediaAdminPage from './pages/admin/MediaAdminPage';
|
|
import MatchesAdminPage from './pages/admin/MatchesAdminPage';
|
|
import PlayersAdminPage from './pages/admin/PlayersAdminPage';
|
|
import TeamsAdminPage from './pages/admin/TeamsAdminPage';
|
|
import BannersAdminPage from './pages/admin/BannersAdminPage';
|
|
import MessagesAdminPage from './pages/admin/MessagesAdminPage';
|
|
import SettingsAdminPage from './pages/admin/SettingsAdminPage';
|
|
import UsersAdminPage from './pages/admin/UsersAdminPage';
|
|
import NewsletterAdminPage from './pages/admin/NewsletterAdminPage';
|
|
import CompetitionAliasesAdminPage from './pages/admin/CompetitionAliasesAdminPage';
|
|
import PrefetchAdminPage from './pages/admin/PrefetchAdminPage';
|
|
import AdminVideosPage from './pages/admin/AdminVideosPage';
|
|
import GalleryAdminPage from './pages/admin/GalleryAdminPage';
|
|
import AdminActivitiesPage from './pages/admin/AdminActivitiesPage';
|
|
import AdminMerchPage from './pages/admin/AdminMerchPage';
|
|
import AdminResetPasswordPage from './pages/admin/AdminResetPasswordPage';
|
|
import AboutAdminPage from './pages/admin/AboutAdminPage';
|
|
import AnalyticsAdminPage from './pages/admin/AnalyticsAdminPage';
|
|
import FilesAdminPage from './pages/admin/FilesAdminPage';
|
|
import ContactsAdminPage from './pages/admin/ContactsAdminPage';
|
|
import NavigationAdminPage from './pages/admin/NavigationAdminPage';
|
|
import ShortlinksAdminPage from './pages/admin/ShortlinksAdminPage';
|
|
import CommentsAdminPage from './pages/admin/CommentsAdminPage';
|
|
import EngagementAdminPage from './pages/admin/EngagementAdminPage';
|
|
import SweepstakesAdminPage from './pages/admin/SweepstakesAdminPage';
|
|
import SweepstakeVisualPage from './pages/admin/SweepstakeVisualPage';
|
|
import SemiAdminPage from './pages/SemiAdminPage';
|
|
import PollsAdminPage from './pages/admin/PollsAdminPage';
|
|
// Admin pages render their own AdminLayout internally
|
|
import SetupPage from './pages/SetupPage';
|
|
import StylePreviewPage from './pages/StylePreviewPage';
|
|
import AboutPage from './pages/AboutPage';
|
|
import AdminDocsPage from './pages/admin/AdminDocsPage';
|
|
import ScoreboardAdminPage from './pages/admin/ScoreboardAdminPage';
|
|
import MobileScoreboardControlPage from './pages/admin/MobileScoreboardControlPage';
|
|
import { getSetupStatus } from './services/setup';
|
|
import NewsletterUnsubscribePage from './pages/NewsletterUnsubscribePage';
|
|
import NewsletterPreferencesPage from './pages/NewsletterPreferencesPage';
|
|
import { ClubThemeProvider } from './contexts/ClubThemeContext';
|
|
import CookiePolicyPage from './pages/legal/CookiePolicyPage';
|
|
import OverlayScoreboardPage from './pages/OverlayScoreboardPage';
|
|
import OverlaySponsorsPage from './pages/OverlaySponsorsPage';
|
|
import CookieBanner from './components/CookieBanner';
|
|
import DefaultSEO from './components/seo/DefaultSEO';
|
|
import ProtectedRoute from './components/ProtectedRoute';
|
|
import TermsPage from './pages/legal/TermsPage';
|
|
import PrivacyPolicyPage from './pages/legal/PrivacyPolicyPage';
|
|
import ForbiddenPage from './pages/ForbiddenPage';
|
|
import NotFoundPage from './pages/NotFoundPage';
|
|
import VideosPage from './pages/VideosPage';
|
|
import SearchPage from './pages/SearchPage';
|
|
import ShortRedirectPage from './pages/ShortRedirectPage';
|
|
import ClothingPage from './pages/ClothingPage';
|
|
import PollsPage from './pages/PollsPage';
|
|
import { useUmami } from './hooks/useUmami';
|
|
import { checkin } from './services/engagement';
|
|
import { useFontLoader } from './hooks/useFontLoader';
|
|
|
|
// Create a client with better cache configuration
|
|
const queryClient = new QueryClient({
|
|
defaultOptions: {
|
|
queries: {
|
|
staleTime: 5 * 60 * 1000, // 5 minutes
|
|
cacheTime: 10 * 60 * 1000, // 10 minutes
|
|
refetchOnWindowFocus: false,
|
|
refetchOnMount: false,
|
|
retry: 1,
|
|
},
|
|
},
|
|
});
|
|
|
|
// Theme configuration drawing colors from ClubTheme CSS variables for personalization
|
|
export const theme = extendTheme({
|
|
config: {
|
|
initialColorMode: 'light',
|
|
useSystemColorMode: false,
|
|
},
|
|
// Provide a brand color scale so colorScheme="brand" components style correctly
|
|
colors: {
|
|
brand: {
|
|
50: '#e6f7ff',
|
|
100: '#b3e0ff',
|
|
200: '#80caff',
|
|
300: '#4db3ff',
|
|
400: '#1a9cff',
|
|
500: 'var(--club-primary, #0b5cff)',
|
|
600: '#0066cc',
|
|
700: '#004d99',
|
|
800: '#003366',
|
|
900: '#001a33',
|
|
},
|
|
},
|
|
// Semantic tokens allow live updates when ClubThemeContext changes CSS variables
|
|
semanticTokens: {
|
|
colors: {
|
|
'brand.primary': {
|
|
default: 'var(--club-primary, #0b5cff)',
|
|
},
|
|
'brand.secondary': {
|
|
default: 'var(--club-secondary, #ffd200)',
|
|
},
|
|
'brand.accent': {
|
|
default: 'var(--club-accent, #141414)',
|
|
},
|
|
'text.onPrimary': {
|
|
default: 'var(--club-text-on-primary, #ffffff)',
|
|
},
|
|
'bg.app': {
|
|
default: '#f8f9fb',
|
|
_dark: '#0f1115',
|
|
},
|
|
'text.app': {
|
|
default: '#1a1a1a',
|
|
_dark: '#e8eaf0',
|
|
},
|
|
// Backdrop/outline shades
|
|
'border.subtle': {
|
|
default: 'rgba(0,0,0,0.06)',
|
|
_dark: 'rgba(255,255,255,0.12)',
|
|
},
|
|
'bg.card': {
|
|
default: '#ffffff',
|
|
_dark: '#1a1d29',
|
|
},
|
|
'bg.elevated': {
|
|
default: '#ffffff',
|
|
_dark: '#242831',
|
|
},
|
|
},
|
|
},
|
|
styles: {
|
|
global: {
|
|
'html, body, #root': {
|
|
height: '100%',
|
|
fontFamily: 'var(--font-body, Inter, ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial)',
|
|
},
|
|
body: {
|
|
bg: 'bg.app',
|
|
color: 'text.app',
|
|
lineHeight: 1.5,
|
|
fontFamily: 'var(--font-body, Inter, ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial)',
|
|
},
|
|
'h1, h2, h3, h4, h5, h6': {
|
|
fontFamily: 'var(--font-heading, Inter, ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial)',
|
|
},
|
|
a: {
|
|
transition: 'color 0.2s ease',
|
|
},
|
|
'::selection': {
|
|
background: 'brand.accent',
|
|
color: 'black',
|
|
},
|
|
},
|
|
},
|
|
components: {
|
|
Container: {
|
|
baseStyle: {
|
|
px: { base: 4, md: 6 },
|
|
},
|
|
sizes: {
|
|
'7xl': '88rem',
|
|
},
|
|
},
|
|
Button: {
|
|
baseStyle: {
|
|
fontWeight: '700',
|
|
borderRadius: 'md',
|
|
letterSpacing: '0.4px',
|
|
_hover: { transform: 'translateY(-1px)', boxShadow: 'md' },
|
|
_active: { transform: 'translateY(0)' },
|
|
},
|
|
variants: {
|
|
solid: {
|
|
bg: 'brand.primary',
|
|
color: 'text.onPrimary',
|
|
_hover: { filter: 'brightness(0.95)' },
|
|
},
|
|
outline: {
|
|
border: '2px solid',
|
|
borderColor: 'brand.primary',
|
|
color: 'brand.primary',
|
|
_hover: { bg: 'rgba(0,0,0,0.02)' },
|
|
},
|
|
ghost: {
|
|
color: 'brand.secondary',
|
|
_hover: { bg: 'rgba(0,0,0,0.04)' },
|
|
},
|
|
},
|
|
},
|
|
Card: {
|
|
baseStyle: {
|
|
container: {
|
|
borderRadius: 'lg',
|
|
boxShadow: 'sm',
|
|
overflow: 'hidden',
|
|
transition: 'all 0.2s',
|
|
borderWidth: '1px',
|
|
borderColor: 'border.subtle',
|
|
_hover: { transform: 'translateY(-4px)', boxShadow: 'lg' },
|
|
},
|
|
},
|
|
},
|
|
Divider: {
|
|
baseStyle: {
|
|
borderColor: 'border.subtle',
|
|
},
|
|
},
|
|
Heading: {
|
|
baseStyle: {
|
|
fontFamily: 'var(--font-heading, Inter, ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial)',
|
|
},
|
|
},
|
|
Text: {
|
|
baseStyle: {
|
|
fontFamily: 'var(--font-body, Inter, ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial)',
|
|
},
|
|
},
|
|
},
|
|
fonts: {
|
|
heading: 'var(--font-heading, Inter, ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial)',
|
|
body: 'var(--font-body, Inter, ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial)',
|
|
},
|
|
});
|
|
|
|
// Component to initialize analytics inside Router context
|
|
const AnalyticsInitializer: React.FC = () => {
|
|
useUmami();
|
|
return null;
|
|
};
|
|
|
|
// Component to load and apply club fonts
|
|
const FontLoader: React.FC = () => {
|
|
useFontLoader();
|
|
return null;
|
|
};
|
|
|
|
// Component to trigger daily check-in for authenticated users (once per day per device)
|
|
const CheckinInitializer: React.FC = () => {
|
|
const { isAuthenticated } = useAuth();
|
|
useEffect(() => {
|
|
if (!isAuthenticated) return;
|
|
let cancelled = false;
|
|
const todayKey = (() => {
|
|
const d = new Date();
|
|
const y = d.getFullYear();
|
|
const m = String(d.getMonth() + 1).padStart(2, '0');
|
|
const day = String(d.getDate()).padStart(2, '0');
|
|
return `fc_checkin_${y}-${m}-${day}`;
|
|
})();
|
|
try {
|
|
if (localStorage.getItem(todayKey) === '1') return;
|
|
} catch {}
|
|
// Fire and forget; backend caps ensure idempotence server-side
|
|
(async () => {
|
|
try { await checkin(); if (!cancelled) { try { localStorage.setItem(todayKey, '1'); } catch {} } } catch {}
|
|
})();
|
|
return () => { cancelled = true; };
|
|
}, [isAuthenticated]);
|
|
return null;
|
|
};
|
|
|
|
// Redirect /news -> /blog while preserving query parameters
|
|
const NewsRedirect: React.FC = () => {
|
|
const loc = useLocation();
|
|
return <Navigate to={`/blog${loc.search || ''}`} replace />;
|
|
};
|
|
|
|
const App: React.FC = () => {
|
|
// Uses shared ProtectedRoute component for auth guard
|
|
|
|
// Public Route component - redirects to admin if already authenticated
|
|
const PublicRoute = ({ children }: { children: React.ReactNode }) => {
|
|
const { isAuthenticated, isLoading, user } = useAuth();
|
|
const [checkingSetup, setCheckingSetup] = useState(true);
|
|
const [requiresSetup, setRequiresSetup] = useState<boolean>(false);
|
|
|
|
useEffect(() => {
|
|
let mounted = true;
|
|
(async () => {
|
|
try {
|
|
const s = await getSetupStatus();
|
|
if (mounted) setRequiresSetup(!!s.requires_setup);
|
|
} catch (_) {
|
|
if (mounted) setRequiresSetup(false);
|
|
} finally {
|
|
if (mounted) setCheckingSetup(false);
|
|
}
|
|
})();
|
|
return () => { mounted = false; };
|
|
}, []);
|
|
|
|
if (isLoading || checkingSetup) {
|
|
return <div>Načítání…</div>;
|
|
}
|
|
|
|
if (isAuthenticated) {
|
|
const role = String(user?.role || '').toLowerCase();
|
|
if (role === 'admin') {
|
|
return <Navigate to="/admin" replace />;
|
|
}
|
|
if (role === 'editor') {
|
|
return <Navigate to="/admin" replace />;
|
|
}
|
|
if (role === 'fan') {
|
|
return <Navigate to="/semiadmin" replace />;
|
|
}
|
|
// Default: regular users to frontpage
|
|
return <Navigate to="/" replace />;
|
|
}
|
|
|
|
// If setup is required, redirect to setup wizard unless already on setup
|
|
const currentPath = window.location.pathname;
|
|
if (requiresSetup && currentPath !== '/setup') {
|
|
return <Navigate to="/setup" replace />;
|
|
}
|
|
|
|
return <>{children}</>;
|
|
};
|
|
|
|
// Admin routes group wrapper (no layout here; pages render their own AdminLayout)
|
|
const AdminRoutesWrapper = () => {
|
|
return <Outlet />;
|
|
};
|
|
|
|
return (
|
|
<ChakraProvider theme={theme}>
|
|
<QueryClientProvider client={queryClient}>
|
|
<Router>
|
|
<AuthProvider>
|
|
<ClubThemeProvider>
|
|
<AnalyticsInitializer />
|
|
<FontLoader />
|
|
<CheckinInitializer />
|
|
<DefaultSEO />
|
|
<Routes>
|
|
{/* Public routes */}
|
|
<Route path="/" element={<HomePage />} />
|
|
<Route path="/hledat" element={<SearchPage />} />
|
|
<Route path="/search" element={<SearchPage />} />
|
|
<Route path="/overlay/scoreboard" element={<OverlayScoreboardPage />} />
|
|
<Route path="/overlay/sponsors" element={<OverlaySponsorsPage />} />
|
|
<Route path="/blog" element={<BlogPage />} />
|
|
<Route path="/klub" element={<ClubPage />} />
|
|
<Route path="/o-klubu" element={<AboutPage />} />
|
|
<Route path="/kalendar" element={<CalendarPage />} />
|
|
<Route path="/aktivity" element={<ActivitiesCalendarPage />} />
|
|
<Route path="/tabulky" element={<TablesPage />} />
|
|
<Route path="/zapasy" element={<MatchesPage />} />
|
|
<Route path="/players" element={<PlayersPage />} />
|
|
<Route path="/hraci" element={<PlayersPage />} />
|
|
<Route path="/players/:id" element={<PlayerDetailPage />} />
|
|
<Route path="/hraci/:id" element={<PlayerDetailPage />} />
|
|
<Route path="/sponzori" element={<SponsorsPage />} />
|
|
<Route path="/kontakt" element={<ContactPage />} />
|
|
<Route path="/ankety" element={<PollsPage />} />
|
|
<Route path="/galerie" element={<GalleryPage />} />
|
|
<Route path="/galerie/album/:id" element={<AlbumDetailPage />} />
|
|
<Route path="/videa" element={<VideosPage />} />
|
|
<Route path="/obleceni" element={<ClothingPage />} />
|
|
{/* Legal pages */}
|
|
<Route path="/pravidla-cookies" element={<CookiePolicyPage />} />
|
|
<Route path="/obchodni-podminky" element={<TermsPage />} />
|
|
<Route path="/zasady-ochrany-osobnich-udaju" element={<PrivacyPolicyPage />} />
|
|
{/* Short links - forward to backend origin if frontend captured it */}
|
|
<Route path="/s/:code" element={<ShortRedirectPage />} />
|
|
<Route path="/news" element={<NewsRedirect />} />
|
|
{/* Slug routes must precede id route to avoid conflicts */}
|
|
<Route path="/news/:slug" element={<ArticleDetailPage />} />
|
|
<Route path="/articles/slug/:slug" element={<ArticleDetailPage />} />
|
|
<Route path="/articles/:id" element={<ArticleDetailPage />} />
|
|
{/* Internal match detail */}
|
|
<Route path="/zapas/:id" element={<MatchDetailPage />} />
|
|
<Route path="/aktivita/:id" element={<ActivityDetailPage />} />
|
|
{/* Legacy redirects */}
|
|
<Route path="/clanky" element={<Navigate to="/blog" replace />} />
|
|
<Route path="/aktuality" element={<Navigate to="/blog" replace />} />
|
|
<Route
|
|
path="/setup"
|
|
element={
|
|
<PublicRoute>
|
|
<SetupPage />
|
|
</PublicRoute>
|
|
}
|
|
/>
|
|
<Route
|
|
path="/setup/styl"
|
|
element={
|
|
<PublicRoute>
|
|
<StylePreviewPage />
|
|
</PublicRoute>
|
|
}
|
|
/>
|
|
<Route
|
|
path="/login"
|
|
element={
|
|
<PublicRoute>
|
|
<AuthPage />
|
|
</PublicRoute>
|
|
}
|
|
/>
|
|
<Route
|
|
path="/register"
|
|
element={
|
|
<PublicRoute>
|
|
<RegisterPage />
|
|
</PublicRoute>
|
|
}
|
|
/>
|
|
<Route path="/forgot-password" element={<ForgotPasswordPage />} />
|
|
<Route path="/reset-password" element={<ResetPasswordPage />} />
|
|
<Route path="/newsletter/unsubscribe/:email" element={<NewsletterUnsubscribePage />} />
|
|
<Route path="/newsletter/preferences" element={<NewsletterPreferencesPage />} />
|
|
<Route
|
|
path="/semiadmin"
|
|
element={
|
|
<ProtectedRoute>
|
|
<SemiAdminPage />
|
|
</ProtectedRoute>
|
|
}
|
|
/>
|
|
<Route path="/403" element={<ForbiddenPage />} />
|
|
|
|
{/* Admin area (pages include AdminLayout themselves) */}
|
|
<Route element={
|
|
<ProtectedRoute requiredRole="admin">
|
|
<AdminRoutesWrapper />
|
|
</ProtectedRoute>
|
|
}>
|
|
<Route path="/admin" element={<AdminDashboardPage />} />
|
|
<Route path="/admin/docs" element={<AdminDocsPage />} />
|
|
{/* moved to editor-accessible routes below */}
|
|
<Route path="/admin/o-klubu" element={<AboutAdminPage />} />
|
|
<Route path="/admin/videa" element={<AdminVideosPage />} />
|
|
<Route path="/admin/galerie" element={<GalleryAdminPage />} />
|
|
<Route path="/admin/obleceni" element={<AdminMerchPage />} />
|
|
<Route path="/admin/sponzori" element={<SponsorsAdminPage />} />
|
|
<Route path="/admin/kategorie" element={<CategoriesAdminPage />} />
|
|
{/* moved to editor-accessible routes below */}
|
|
<Route path="/admin/zapasy" element={<MatchesAdminPage />} />
|
|
<Route path="/admin/hraci" element={<PlayersAdminPage />} />
|
|
<Route path="/admin/tymy" element={<TeamsAdminPage />} />
|
|
<Route path="/admin/uzivatele" element={<UsersAdminPage />} />
|
|
<Route path="/admin/bannery" element={<BannersAdminPage />} />
|
|
<Route path="/admin/zpravy" element={<MessagesAdminPage />} />
|
|
<Route path="/admin/nastaveni" element={<SettingsAdminPage />} />
|
|
<Route path="/admin/newsletter" element={<NewsletterAdminPage />} />
|
|
<Route path="/admin/ankety" element={<PollsAdminPage />} />
|
|
<Route path="/admin/aliasy-soutezi" element={<CompetitionAliasesAdminPage />} />
|
|
<Route path="/admin/prefetch" element={<PrefetchAdminPage />} />
|
|
<Route path="/admin/users/send-reset" element={<AdminResetPasswordPage />} />
|
|
<Route path="/admin/scoreboard" element={<ScoreboardAdminPage />} />
|
|
<Route path="/admin/scoreboard/remote" element={<MobileScoreboardControlPage />} />
|
|
<Route path="/admin/analytika" element={<AnalyticsAdminPage />} />
|
|
<Route path="/admin/shortlinks" element={<ShortlinksAdminPage />} />
|
|
<Route path="/admin/soubory" element={<FilesAdminPage />} />
|
|
<Route path="/admin/kontakty" element={<ContactsAdminPage />} />
|
|
<Route path="/admin/navigace" element={<NavigationAdminPage />} />
|
|
<Route path="/admin/komentare" element={<CommentsAdminPage />} />
|
|
<Route path="/admin/engagement" element={<EngagementAdminPage />} />
|
|
<Route path="/admin/sweepstakes" element={<SweepstakesAdminPage />} />
|
|
<Route path="/admin/sweepstakes/:id/visual" element={<SweepstakeVisualPage />} />
|
|
</Route>
|
|
|
|
{/* Remaining protected routes that don't use AdminLayout */}
|
|
<Route
|
|
path="/dashboard"
|
|
element={<Navigate to="/admin" replace />}
|
|
/>
|
|
<Route
|
|
path="/admin/sponsors"
|
|
element={
|
|
<ProtectedRoute requiredRole="admin">
|
|
<SponsorsAdminPage />
|
|
</ProtectedRoute>
|
|
}
|
|
/>
|
|
<Route
|
|
path="/admin/banners"
|
|
element={
|
|
<ProtectedRoute requiredRole="admin">
|
|
<BannersAdminPage />
|
|
</ProtectedRoute>
|
|
}
|
|
/>
|
|
<Route
|
|
path="/admin/messages"
|
|
element={
|
|
<ProtectedRoute requiredRole="admin">
|
|
<MessagesAdminPage />
|
|
</ProtectedRoute>
|
|
}
|
|
/>
|
|
<Route
|
|
path="/admin/settings"
|
|
element={
|
|
<ProtectedRoute requiredRole="admin">
|
|
<SettingsAdminPage />
|
|
</ProtectedRoute>
|
|
}
|
|
/>
|
|
|
|
{/* Editor-accessible content pages (also allow admin) */}
|
|
<Route
|
|
path="/admin/clanky"
|
|
element={
|
|
<ProtectedRoute requiredRole="editor">
|
|
<ArticlesAdminPage />
|
|
</ProtectedRoute>
|
|
}
|
|
/>
|
|
<Route
|
|
path="/admin/aktivity"
|
|
element={
|
|
<ProtectedRoute requiredRole="editor">
|
|
<AdminActivitiesPage />
|
|
</ProtectedRoute>
|
|
}
|
|
/>
|
|
<Route
|
|
path="/admin/media"
|
|
element={
|
|
<ProtectedRoute requiredRole="editor">
|
|
<MediaAdminPage />
|
|
</ProtectedRoute>
|
|
}
|
|
/>
|
|
|
|
{/* Not found route */}
|
|
<Route path="*" element={<NotFoundPage />} />
|
|
</Routes>
|
|
{/* Cookie consent banner shown across the whole site */}
|
|
<CookieBanner />
|
|
</ClubThemeProvider>
|
|
</AuthProvider>
|
|
</Router>
|
|
</QueryClientProvider>
|
|
</ChakraProvider>
|
|
);
|
|
};
|
|
|
|
export default App;
|