mirror of
https://github.com/Dvorinka/MyClubServer.git
synced 2026-06-04 10:42:57 +00:00
upload
This commit is contained in:
@@ -0,0 +1,471 @@
|
||||
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 } from 'react-router-dom';
|
||||
import './styles/custom-scrollbar.css';
|
||||
import { AuthProvider, useAuth } from './contexts/AuthContext';
|
||||
import AuthPage from './pages/AuthPage';
|
||||
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 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 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 ClothingPage from './pages/ClothingPage';
|
||||
import PollsPage from './pages/PollsPage';
|
||||
import { useUmami } from './hooks/useUmami';
|
||||
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;
|
||||
};
|
||||
|
||||
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 } = 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) {
|
||||
return <Navigate to="/admin" 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 />
|
||||
<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="/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 />} />
|
||||
<Route path="/news" element={<Navigate to="/blog" replace />} />
|
||||
{/* 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="/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="/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 />} />
|
||||
<Route path="/admin/clanky" element={<ArticlesAdminPage />} />
|
||||
<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/aktivity" element={<AdminActivitiesPage />} />
|
||||
<Route path="/admin/sponzori" element={<SponsorsAdminPage />} />
|
||||
<Route path="/admin/kategorie" element={<CategoriesAdminPage />} />
|
||||
<Route path="/admin/media" element={<MediaAdminPage />} />
|
||||
<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/soubory" element={<FilesAdminPage />} />
|
||||
<Route path="/admin/kontakty" element={<ContactsAdminPage />} />
|
||||
<Route path="/admin/navigace" element={<NavigationAdminPage />} />
|
||||
</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>
|
||||
}
|
||||
/>
|
||||
|
||||
{/* 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;
|
||||
Reference in New Issue
Block a user