feat(web): анонимный вход для школьников — только школа и класс
Школьники не должны придумывать пароль, чтобы пройти тест. Бэкенд уже
поддерживал это: POST /polls/{poll_id}/responses принимает user_id как
Optional, а Response.group_id/organization_id — самостоятельные nullable
снимки школы/класса, не завязанные на аккаунт. Не хватало только
фронтенда — все роуты были жёстко завёрнуты в ProtectedRoute с
обязательным JWT.
- AuthContext: параллельная "анонимная идентичность" (org+group в
localStorage, без токена и без запроса к API)
- ProtectedRoute пускает по токену ИЛИ анонимной сессии; новый
AccountRoute — строго по токену, для /my-tests, /profile,
/response/:id (у анонимной сессии нет истории — её негде хранить)
- Новая страница /anonymous — выбор школы+класса тем же
SearchableDropdown, что и в регистрации
- PollPage при отправке ответа берёт org/group из анонимной сессии,
если пользователь не залогинен
- HomePage скрывает "Мои тесты" и профиль для анонимов, добавлена
кнопка выхода из анонимного режима
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,9 +1,10 @@
|
||||
import { BrowserRouter, Navigate, Route, Routes } from "react-router-dom";
|
||||
import { AuthProvider } from "./auth/AuthContext";
|
||||
import { ThemeProvider } from "./theme/ThemeContext";
|
||||
import { ProtectedRoute } from "./components/ProtectedRoute";
|
||||
import { ProtectedRoute, AccountRoute } from "./components/ProtectedRoute";
|
||||
import { LoginPage } from "./pages/LoginPage";
|
||||
import { RegisterPage } from "./pages/RegisterPage";
|
||||
import { AnonymousEntryPage } from "./pages/AnonymousEntryPage";
|
||||
import { HomePage } from "./pages/HomePage";
|
||||
import { TestListPage } from "./pages/TestListPage";
|
||||
import { PollPage } from "./pages/PollPage";
|
||||
@@ -11,10 +12,16 @@ import { MyTestsPage } from "./pages/MyTestsPage";
|
||||
import { ResponsePage } from "./pages/ResponsePage";
|
||||
import { ProfilePage } from "./pages/ProfilePage";
|
||||
|
||||
// Доступно и по токену, и анонимно (школа+класс без аккаунта).
|
||||
function protect(el: React.ReactNode) {
|
||||
return <ProtectedRoute>{el}</ProtectedRoute>;
|
||||
}
|
||||
|
||||
// Требует настоящий аккаунт — история и профиль анонимусам недоступны.
|
||||
function account(el: React.ReactNode) {
|
||||
return <AccountRoute>{el}</AccountRoute>;
|
||||
}
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<ThemeProvider>
|
||||
@@ -23,12 +30,13 @@ export default function App() {
|
||||
<Routes>
|
||||
<Route path="/login" element={<LoginPage />} />
|
||||
<Route path="/register" element={<RegisterPage />} />
|
||||
<Route path="/anonymous" element={<AnonymousEntryPage />} />
|
||||
<Route path="/" element={protect(<HomePage />)} />
|
||||
<Route path="/tests" element={protect(<TestListPage />)} />
|
||||
<Route path="/poll/:pollId" element={protect(<PollPage />)} />
|
||||
<Route path="/my-tests" element={protect(<MyTestsPage />)} />
|
||||
<Route path="/response/:responseId" element={protect(<ResponsePage />)} />
|
||||
<Route path="/profile" element={protect(<ProfilePage />)} />
|
||||
<Route path="/my-tests" element={account(<MyTestsPage />)} />
|
||||
<Route path="/response/:responseId" element={account(<ResponsePage />)} />
|
||||
<Route path="/profile" element={account(<ProfilePage />)} />
|
||||
<Route path="*" element={<Navigate to="/" replace />} />
|
||||
</Routes>
|
||||
</BrowserRouter>
|
||||
|
||||
Reference in New Issue
Block a user