pdf-quiz-generator/frontend/src/App.jsx
Daniel 699cbabfcb Add OIDC/SSO login, reminder fixes, user notification settings
- 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>
2026-04-10 04:10:15 +02:00

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>
)
}