2025-12-20 00:17:58 +07:00
|
|
|
import { useEffect } from 'react'
|
2025-12-14 02:38:35 +07:00
|
|
|
import { Routes, Route, Navigate } from 'react-router-dom'
|
|
|
|
|
import { useAuthStore } from '@/store/auth'
|
2025-12-16 01:50:40 +07:00
|
|
|
import { ToastContainer, ConfirmModal } from '@/components/ui'
|
2025-12-19 02:07:25 +07:00
|
|
|
import { BannedScreen } from '@/components/BannedScreen'
|
2025-12-14 02:38:35 +07:00
|
|
|
|
|
|
|
|
// Layout
|
|
|
|
|
import { Layout } from '@/components/layout/Layout'
|
|
|
|
|
|
|
|
|
|
// Pages
|
|
|
|
|
import { HomePage } from '@/pages/HomePage'
|
|
|
|
|
import { LoginPage } from '@/pages/LoginPage'
|
|
|
|
|
import { RegisterPage } from '@/pages/RegisterPage'
|
|
|
|
|
import { MarathonsPage } from '@/pages/MarathonsPage'
|
|
|
|
|
import { CreateMarathonPage } from '@/pages/CreateMarathonPage'
|
|
|
|
|
import { MarathonPage } from '@/pages/MarathonPage'
|
|
|
|
|
import { LobbyPage } from '@/pages/LobbyPage'
|
|
|
|
|
import { PlayPage } from '@/pages/PlayPage'
|
|
|
|
|
import { LeaderboardPage } from '@/pages/LeaderboardPage'
|
2025-12-14 20:39:26 +07:00
|
|
|
import { InvitePage } from '@/pages/InvitePage'
|
2025-12-16 00:33:50 +07:00
|
|
|
import { AssignmentDetailPage } from '@/pages/AssignmentDetailPage'
|
2025-12-16 22:12:12 +07:00
|
|
|
import { ProfilePage } from '@/pages/ProfilePage'
|
|
|
|
|
import { UserProfilePage } from '@/pages/UserProfilePage'
|
2025-12-20 02:01:51 +07:00
|
|
|
import { StaticContentPage } from '@/pages/StaticContentPage'
|
2025-12-16 22:12:12 +07:00
|
|
|
import { NotFoundPage } from '@/pages/NotFoundPage'
|
2025-12-17 20:59:47 +07:00
|
|
|
import { TeapotPage } from '@/pages/TeapotPage'
|
2025-12-17 21:50:10 +07:00
|
|
|
import { ServerErrorPage } from '@/pages/ServerErrorPage'
|
2025-12-14 02:38:35 +07:00
|
|
|
|
2025-12-19 02:07:25 +07:00
|
|
|
// Admin Pages
|
|
|
|
|
import {
|
|
|
|
|
AdminLayout,
|
|
|
|
|
AdminDashboardPage,
|
|
|
|
|
AdminUsersPage,
|
|
|
|
|
AdminMarathonsPage,
|
|
|
|
|
AdminLogsPage,
|
|
|
|
|
AdminBroadcastPage,
|
|
|
|
|
AdminContentPage,
|
|
|
|
|
} from '@/pages/admin'
|
|
|
|
|
|
2025-12-14 02:38:35 +07:00
|
|
|
// Protected route wrapper
|
|
|
|
|
function ProtectedRoute({ children }: { children: React.ReactNode }) {
|
|
|
|
|
const isAuthenticated = useAuthStore((state) => state.isAuthenticated)
|
|
|
|
|
|
|
|
|
|
if (!isAuthenticated) {
|
|
|
|
|
return <Navigate to="/login" replace />
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return <>{children}</>
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Public route wrapper (redirect if authenticated)
|
|
|
|
|
function PublicRoute({ children }: { children: React.ReactNode }) {
|
|
|
|
|
const isAuthenticated = useAuthStore((state) => state.isAuthenticated)
|
|
|
|
|
|
|
|
|
|
if (isAuthenticated) {
|
|
|
|
|
return <Navigate to="/marathons" replace />
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return <>{children}</>
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function App() {
|
2025-12-19 02:07:25 +07:00
|
|
|
const banInfo = useAuthStore((state) => state.banInfo)
|
|
|
|
|
const isAuthenticated = useAuthStore((state) => state.isAuthenticated)
|
2025-12-20 00:17:58 +07:00
|
|
|
const syncUser = useAuthStore((state) => state.syncUser)
|
|
|
|
|
|
|
|
|
|
// Sync user data with server on app load
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
syncUser()
|
|
|
|
|
}, [syncUser])
|
2025-12-19 02:07:25 +07:00
|
|
|
|
|
|
|
|
// Show banned screen if user is authenticated and banned
|
|
|
|
|
if (isAuthenticated && banInfo) {
|
|
|
|
|
return (
|
|
|
|
|
<>
|
|
|
|
|
<ToastContainer />
|
|
|
|
|
<BannedScreen banInfo={banInfo} />
|
|
|
|
|
</>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-14 02:38:35 +07:00
|
|
|
return (
|
2025-12-16 01:50:40 +07:00
|
|
|
<>
|
|
|
|
|
<ToastContainer />
|
|
|
|
|
<ConfirmModal />
|
2025-12-14 02:38:35 +07:00
|
|
|
<Routes>
|
|
|
|
|
<Route path="/" element={<Layout />}>
|
|
|
|
|
<Route index element={<HomePage />} />
|
|
|
|
|
|
2025-12-14 20:39:26 +07:00
|
|
|
{/* Public invite page */}
|
|
|
|
|
<Route path="invite/:code" element={<InvitePage />} />
|
|
|
|
|
|
2025-12-20 02:01:51 +07:00
|
|
|
{/* Public static content pages */}
|
|
|
|
|
<Route path="terms" element={<StaticContentPage />} />
|
|
|
|
|
<Route path="privacy" element={<StaticContentPage />} />
|
|
|
|
|
<Route path="page/:key" element={<StaticContentPage />} />
|
|
|
|
|
|
2025-12-14 02:38:35 +07:00
|
|
|
<Route
|
|
|
|
|
path="login"
|
|
|
|
|
element={
|
|
|
|
|
<PublicRoute>
|
|
|
|
|
<LoginPage />
|
|
|
|
|
</PublicRoute>
|
|
|
|
|
}
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
<Route
|
|
|
|
|
path="register"
|
|
|
|
|
element={
|
|
|
|
|
<PublicRoute>
|
|
|
|
|
<RegisterPage />
|
|
|
|
|
</PublicRoute>
|
|
|
|
|
}
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
<Route
|
|
|
|
|
path="marathons"
|
|
|
|
|
element={
|
|
|
|
|
<ProtectedRoute>
|
|
|
|
|
<MarathonsPage />
|
|
|
|
|
</ProtectedRoute>
|
|
|
|
|
}
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
<Route
|
|
|
|
|
path="marathons/create"
|
|
|
|
|
element={
|
|
|
|
|
<ProtectedRoute>
|
|
|
|
|
<CreateMarathonPage />
|
|
|
|
|
</ProtectedRoute>
|
|
|
|
|
}
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
<Route
|
|
|
|
|
path="marathons/:id"
|
|
|
|
|
element={
|
|
|
|
|
<ProtectedRoute>
|
|
|
|
|
<MarathonPage />
|
|
|
|
|
</ProtectedRoute>
|
|
|
|
|
}
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
<Route
|
|
|
|
|
path="marathons/:id/lobby"
|
|
|
|
|
element={
|
|
|
|
|
<ProtectedRoute>
|
|
|
|
|
<LobbyPage />
|
|
|
|
|
</ProtectedRoute>
|
|
|
|
|
}
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
<Route
|
|
|
|
|
path="marathons/:id/play"
|
|
|
|
|
element={
|
|
|
|
|
<ProtectedRoute>
|
|
|
|
|
<PlayPage />
|
|
|
|
|
</ProtectedRoute>
|
|
|
|
|
}
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
<Route
|
|
|
|
|
path="marathons/:id/leaderboard"
|
|
|
|
|
element={
|
|
|
|
|
<ProtectedRoute>
|
|
|
|
|
<LeaderboardPage />
|
|
|
|
|
</ProtectedRoute>
|
|
|
|
|
}
|
|
|
|
|
/>
|
2025-12-16 00:33:50 +07:00
|
|
|
|
|
|
|
|
<Route
|
|
|
|
|
path="assignments/:id"
|
|
|
|
|
element={
|
|
|
|
|
<ProtectedRoute>
|
|
|
|
|
<AssignmentDetailPage />
|
|
|
|
|
</ProtectedRoute>
|
|
|
|
|
}
|
|
|
|
|
/>
|
2025-12-16 22:12:12 +07:00
|
|
|
|
|
|
|
|
{/* Profile routes */}
|
|
|
|
|
<Route
|
|
|
|
|
path="profile"
|
|
|
|
|
element={
|
|
|
|
|
<ProtectedRoute>
|
|
|
|
|
<ProfilePage />
|
|
|
|
|
</ProtectedRoute>
|
|
|
|
|
}
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
<Route path="users/:id" element={<UserProfilePage />} />
|
|
|
|
|
|
2025-12-17 20:59:47 +07:00
|
|
|
{/* Easter egg - 418 I'm a teapot */}
|
|
|
|
|
<Route path="418" element={<TeapotPage />} />
|
|
|
|
|
<Route path="teapot" element={<TeapotPage />} />
|
|
|
|
|
<Route path="tea" element={<TeapotPage />} />
|
|
|
|
|
|
2025-12-17 21:50:10 +07:00
|
|
|
{/* Server error page */}
|
|
|
|
|
<Route path="500" element={<ServerErrorPage />} />
|
|
|
|
|
<Route path="error" element={<ServerErrorPage />} />
|
|
|
|
|
|
2025-12-19 02:07:25 +07:00
|
|
|
{/* Admin routes */}
|
|
|
|
|
<Route
|
|
|
|
|
path="admin"
|
|
|
|
|
element={
|
|
|
|
|
<ProtectedRoute>
|
|
|
|
|
<AdminLayout />
|
|
|
|
|
</ProtectedRoute>
|
|
|
|
|
}
|
|
|
|
|
>
|
|
|
|
|
<Route index element={<AdminDashboardPage />} />
|
|
|
|
|
<Route path="users" element={<AdminUsersPage />} />
|
|
|
|
|
<Route path="marathons" element={<AdminMarathonsPage />} />
|
|
|
|
|
<Route path="logs" element={<AdminLogsPage />} />
|
|
|
|
|
<Route path="broadcast" element={<AdminBroadcastPage />} />
|
|
|
|
|
<Route path="content" element={<AdminContentPage />} />
|
|
|
|
|
</Route>
|
|
|
|
|
|
2025-12-16 22:12:12 +07:00
|
|
|
{/* 404 - must be last */}
|
|
|
|
|
<Route path="*" element={<NotFoundPage />} />
|
2025-12-14 02:38:35 +07:00
|
|
|
</Route>
|
|
|
|
|
</Routes>
|
2025-12-16 01:50:40 +07:00
|
|
|
</>
|
2025-12-14 02:38:35 +07:00
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default App
|