- Add generic OIDC/SSO support (configurable via env vars) - Admin can enable SSO-only mode (disables password login) - SSO callback auto-creates and verifies users - Login page shows SSO button when configured, hides password form in SSO-only mode - Fix reminders: skip course quizzes and deleted quizzes - Don't create reminders for course quiz attempts - Add user reminder opt-out toggle in Settings > Notifications - Scheduler checks user opt-out before sending emails Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
117 lines
4.8 KiB
JavaScript
117 lines
4.8 KiB
JavaScript
import { BrowserRouter, Routes, Route, Navigate, Outlet } from 'react-router-dom'
|
|
import { AuthProvider, useAuth } from './context/AuthContext'
|
|
import { ThemeProvider } from './context/ThemeContext'
|
|
import Navbar from './components/Navbar'
|
|
import LoginPage from './pages/LoginPage'
|
|
import RegisterPage from './pages/RegisterPage'
|
|
import DashboardPage from './pages/DashboardPage'
|
|
import UploadPage from './pages/UploadPage'
|
|
import DocumentDetailPage from './pages/DocumentDetailPage'
|
|
import QuizPage from './pages/QuizPage'
|
|
import QuizzesPage from './pages/QuizzesPage'
|
|
import ResultsPage from './pages/ResultsPage'
|
|
import AdminPage from './pages/AdminPage'
|
|
import AccountPage from './pages/AccountPage'
|
|
import SettingsPage from './pages/SettingsPage'
|
|
import QuestionBankPage from './pages/QuestionBankPage'
|
|
import JobsPage from './pages/JobsPage'
|
|
import TrashPage from './pages/TrashPage'
|
|
import QuizEditPage from './pages/QuizEditPage'
|
|
import VerifyEmailPage from './pages/VerifyEmailPage'
|
|
import ForgotPasswordPage from './pages/ForgotPasswordPage'
|
|
import ResetPasswordPage from './pages/ResetPasswordPage'
|
|
import NotFoundPage from './pages/NotFoundPage'
|
|
import LandingPage from './pages/LandingPage'
|
|
import FlashcardsPage from './pages/FlashcardsPage'
|
|
import FlashcardStudyPage from './pages/FlashcardStudyPage'
|
|
import CoursesPage from './pages/CoursesPage'
|
|
import CourseDetailPage from './pages/CourseDetailPage'
|
|
import CourseEditorPage from './pages/CourseEditorPage'
|
|
import SsoCallbackPage from './pages/SsoCallbackPage'
|
|
|
|
// Layout wrapper for authenticated app pages (Navbar + container + footer)
|
|
function AppLayout() {
|
|
return (
|
|
<>
|
|
<Navbar />
|
|
<div className="container">
|
|
<Outlet />
|
|
</div>
|
|
<footer className="site-footer">
|
|
<div className="container">© {new Date().getFullYear()} PedsHub</div>
|
|
</footer>
|
|
</>
|
|
)
|
|
}
|
|
|
|
// Guard: redirect to /home if not logged in, or to / if not moderator
|
|
function RequireAuth({ moderator = false }) {
|
|
const { user, loading } = useAuth()
|
|
if (loading) return <div className="loading"><div className="spinner" /></div>
|
|
if (!user) return <Navigate to="/home" replace />
|
|
if (moderator && user.role !== 'admin' && user.role !== 'moderator') return <Navigate to="/" replace />
|
|
return <Outlet />
|
|
}
|
|
|
|
function AppRoutes() {
|
|
const { user, loading } = useAuth()
|
|
if (loading) return <div className="loading"><div className="spinner" /></div>
|
|
|
|
return (
|
|
<Routes>
|
|
{/* Always public */}
|
|
<Route path="/home" element={<LandingPage />} />
|
|
<Route path="/login" element={user ? <Navigate to="/" replace /> : <LoginPage />} />
|
|
<Route path="/register" element={user ? <Navigate to="/" replace /> : <RegisterPage />} />
|
|
<Route path="/verify-email" element={<VerifyEmailPage />} />
|
|
<Route path="/forgot-password" element={<ForgotPasswordPage />} />
|
|
<Route path="/reset-password" element={<ResetPasswordPage />} />
|
|
<Route path="/sso-callback" element={<SsoCallbackPage />} />
|
|
|
|
{/* Authenticated app — wrapped in AppLayout */}
|
|
<Route element={<RequireAuth />}>
|
|
<Route element={<AppLayout />}>
|
|
<Route path="/" element={<DashboardPage />} />
|
|
<Route path="/quizzes" element={<QuizzesPage />} />
|
|
<Route path="/quizzes/:id" element={<QuizPage />} />
|
|
<Route path="/results/:id" element={<ResultsPage />} />
|
|
<Route path="/documents/:id" element={<DocumentDetailPage />} />
|
|
<Route path="/account" element={<AccountPage />} />
|
|
<Route path="/settings" element={<SettingsPage />} />
|
|
<Route path="/question-bank" element={<QuestionBankPage />} />
|
|
<Route path="/flashcards" element={<FlashcardsPage />} />
|
|
<Route path="/flashcards/:deckId/study" element={<FlashcardStudyPage />} />
|
|
<Route path="/courses" element={<CoursesPage />} />
|
|
<Route path="/courses/:courseId" element={<CourseDetailPage />} />
|
|
<Route path="/courses/:courseId/edit" element={<CourseEditorPage />} />
|
|
<Route path="/admin" element={<AdminPage />} />
|
|
</Route>
|
|
</Route>
|
|
|
|
{/* Moderator-only */}
|
|
<Route element={<RequireAuth moderator />}>
|
|
<Route element={<AppLayout />}>
|
|
<Route path="/upload" element={<UploadPage />} />
|
|
<Route path="/quizzes/:id/edit" element={<QuizEditPage />} />
|
|
<Route path="/jobs" element={<JobsPage />} />
|
|
<Route path="/trash" element={<TrashPage />} />
|
|
</Route>
|
|
</Route>
|
|
|
|
{/* Catch-all */}
|
|
<Route path="*" element={user ? <NotFoundPage /> : <Navigate to="/home" replace />} />
|
|
</Routes>
|
|
)
|
|
}
|
|
|
|
export default function App() {
|
|
return (
|
|
<BrowserRouter>
|
|
<ThemeProvider>
|
|
<AuthProvider>
|
|
<AppRoutes />
|
|
</AuthProvider>
|
|
</ThemeProvider>
|
|
</BrowserRouter>
|
|
)
|
|
}
|