feat(frontend): router restructure /admin → /lessons with redirects
This commit is contained in:
@@ -114,7 +114,7 @@ function TreeRow({ n, depth }: { n: LessonTreeNode; depth: number }) {
|
|||||||
>⋮⋮</span>
|
>⋮⋮</span>
|
||||||
)}
|
)}
|
||||||
<span className={`h-2 w-2 rounded-full ${depth === 0 ? 'bg-brand-500' : 'bg-brand-300'}`} />
|
<span className={`h-2 w-2 rounded-full ${depth === 0 ? 'bg-brand-500' : 'bg-brand-300'}`} />
|
||||||
<Link to={`/admin/lessons/${n.id}`} className="flex-1 truncate font-medium text-slate-800 dark:text-slate-100">
|
<Link to={`/lessons/${n.id}`} className="flex-1 truncate font-medium text-slate-800 dark:text-slate-100">
|
||||||
{n.name}
|
{n.name}
|
||||||
<span className="ml-2 rounded-full bg-brand-100 px-2 py-0.5 text-xs font-semibold text-brand-700 dark:bg-brand-900/30 dark:text-brand-200">
|
<span className="ml-2 rounded-full bg-brand-100 px-2 py-0.5 text-xs font-semibold text-brand-700 dark:bg-brand-900/30 dark:text-brand-200">
|
||||||
{n.cardCount}
|
{n.cardCount}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ export function MarketplacePage() {
|
|||||||
async function fork(id: number) {
|
async function fork(id: number) {
|
||||||
try {
|
try {
|
||||||
const f = await lessonsApi.fork(id);
|
const f = await lessonsApi.fork(id);
|
||||||
navigate(`/admin/lessons/${f.id}`);
|
navigate(`/lessons/${f.id}`);
|
||||||
}
|
}
|
||||||
catch (e) { alert(e instanceof ApiClientError ? e.message : 'Forken mislukt'); }
|
catch (e) { alert(e instanceof ApiClientError ? e.message : 'Forken mislukt'); }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import { lazy, Suspense, type ComponentType } from 'react';
|
import { lazy, Suspense, type ComponentType } from 'react';
|
||||||
import { createBrowserRouter, Navigate } from 'react-router-dom';
|
import { createBrowserRouter, Navigate, useParams } from 'react-router-dom';
|
||||||
import { Layout } from './components/Layout.js';
|
import { Layout } from './components/Layout.js';
|
||||||
import { AuthBoundary } from './components/AuthBoundary.js';
|
import { AuthBoundary } from './components/AuthBoundary.js';
|
||||||
import { RoleGuard } from './components/RoleGuard.js';
|
import { RoleGuard } from './components/RoleGuard.js';
|
||||||
|
|
||||||
// Tiny loading placeholder reused for every lazy route boundary.
|
|
||||||
function PageFallback() {
|
function PageFallback() {
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full items-center justify-center p-12">
|
<div className="flex h-full items-center justify-center p-12">
|
||||||
@@ -13,8 +12,6 @@ function PageFallback() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// `React.lazy` requires a default export; our pages use named exports, so we
|
|
||||||
// adapt with a small helper that picks the named export from the module.
|
|
||||||
function lazyPage<K extends string>(
|
function lazyPage<K extends string>(
|
||||||
loader: () => Promise<Record<K, ComponentType>>,
|
loader: () => Promise<Record<K, ComponentType>>,
|
||||||
name: K,
|
name: K,
|
||||||
@@ -33,8 +30,8 @@ function lazyPage<K extends string>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const Dashboard = lazyPage(() => import('./pages/Dashboard.js'), 'DashboardPage');
|
const Dashboard = lazyPage(() => import('./pages/Dashboard.js'), 'DashboardPage');
|
||||||
const Admin = lazyPage(() => import('./pages/Admin.js'), 'AdminPage');
|
const Lessons = lazyPage(() => import('./pages/Lessons.js'), 'LessonsPage');
|
||||||
const AdminLesson = lazyPage(() => import('./pages/AdminLesson.js'), 'AdminLessonPage');
|
const LessonDetail = lazyPage(() => import('./pages/LessonDetail.js'), 'LessonDetailPage');
|
||||||
const PracticeSetup = lazyPage(() => import('./pages/PracticeSetup.js'), 'PracticeSetupPage');
|
const PracticeSetup = lazyPage(() => import('./pages/PracticeSetup.js'), 'PracticeSetupPage');
|
||||||
const Practice = lazyPage(() => import('./pages/Practice.js'), 'PracticePage');
|
const Practice = lazyPage(() => import('./pages/Practice.js'), 'PracticePage');
|
||||||
const PracticeDone = lazyPage(() => import('./pages/PracticeDone.js'), 'PracticeDonePage');
|
const PracticeDone = lazyPage(() => import('./pages/PracticeDone.js'), 'PracticeDonePage');
|
||||||
@@ -53,12 +50,17 @@ const ForgotPassword = lazyPage(() => import('./pages/auth/ForgotPassword.js'),
|
|||||||
const ResetPassword = lazyPage(() => import('./pages/auth/ResetPassword.js'), 'ResetPasswordPage');
|
const ResetPassword = lazyPage(() => import('./pages/auth/ResetPassword.js'), 'ResetPasswordPage');
|
||||||
const AcceptInvite = lazyPage(() => import('./pages/auth/AcceptInvite.js'), 'AcceptInvitePage');
|
const AcceptInvite = lazyPage(() => import('./pages/auth/AcceptInvite.js'), 'AcceptInvitePage');
|
||||||
|
|
||||||
|
function AdminToLessons() { return <Navigate to="/lessons" replace />; }
|
||||||
|
function AdminLessonRedirect() {
|
||||||
|
const { id } = useParams();
|
||||||
|
return <Navigate to={`/lessons/${id ?? ''}`} replace />;
|
||||||
|
}
|
||||||
|
|
||||||
export const router = createBrowserRouter([
|
export const router = createBrowserRouter([
|
||||||
{
|
{
|
||||||
path: '/',
|
path: '/',
|
||||||
element: <Layout />,
|
element: <Layout />,
|
||||||
children: [
|
children: [
|
||||||
// Public auth routes
|
|
||||||
{ path: 'login', element: <Login /> },
|
{ path: 'login', element: <Login /> },
|
||||||
{ path: 'register', element: <Register /> },
|
{ path: 'register', element: <Register /> },
|
||||||
{ path: 'verify-email', element: <VerifyEmail /> },
|
{ path: 'verify-email', element: <VerifyEmail /> },
|
||||||
@@ -66,13 +68,16 @@ export const router = createBrowserRouter([
|
|||||||
{ path: 'reset-password', element: <ResetPassword /> },
|
{ path: 'reset-password', element: <ResetPassword /> },
|
||||||
{ path: 'accept-invite', element: <AcceptInvite /> },
|
{ path: 'accept-invite', element: <AcceptInvite /> },
|
||||||
|
|
||||||
// Authenticated routes
|
|
||||||
{
|
{
|
||||||
element: <AuthBoundary />,
|
element: <AuthBoundary />,
|
||||||
children: [
|
children: [
|
||||||
{ index: true, element: <Dashboard /> },
|
{ index: true, element: <Dashboard /> },
|
||||||
{ path: 'admin', element: <Admin /> },
|
{ path: 'lessons', element: <Lessons /> },
|
||||||
{ path: 'admin/lessons/:id', element: <AdminLesson /> },
|
{ path: 'lessons/:id', element: <LessonDetail /> },
|
||||||
|
|
||||||
|
{ path: 'admin', element: <AdminToLessons /> },
|
||||||
|
{ path: 'admin/lessons/:id', element: <AdminLessonRedirect /> },
|
||||||
|
|
||||||
{ path: 'practice/:lessonId/setup', element: <PracticeSetup /> },
|
{ path: 'practice/:lessonId/setup', element: <PracticeSetup /> },
|
||||||
{ path: 'practice/:lessonId', element: <Practice /> },
|
{ path: 'practice/:lessonId', element: <Practice /> },
|
||||||
{ path: 'practice/:lessonId/done', element: <PracticeDone /> },
|
{ path: 'practice/:lessonId/done', element: <PracticeDone /> },
|
||||||
|
|||||||
Reference in New Issue
Block a user