Files
mcma-webui/src/routes/index.tsx
T
Senko-san aed0572071 Scaffold global navigation aligned to routes plan
Build out the full web route map from music-selfhost-routes.md as
scaffolding (no functionality on new screens):

- Full route tree: /login, /albums/:id, /artists/:id, /playlists(+detail),
  /discover, /upload, metadata editor (single + batch), /storage/maintenance,
  /queue, nested /settings and /admin, and a 404.
- Sidebar rebuilt to the A1 spec with permission-gated Discover/Upload.
- ProtectedRoute gains requirePermission; Permission exported.
- AppShell wraps Outlet in a Suspense boundary for lazy routes.
- Reusable Placeholder + SubNav; Settings/Admin become nested layouts.
- Settings/Profile: wired language + theme selectors.
- Remove orphaned Home feature (web has no Home; / -> /library) and the
  now-unused house icon + nav.home keys.
- i18n keys (en + ru) and CSS for page-title/sub-nav.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 17:05:21 +03:00

165 lines
5.3 KiB
TypeScript

import { lazy } from 'react';
import { Routes, Route, Navigate } from 'react-router';
import { AppShell } from '../components/layout/AppShell';
import { ProtectedRoute } from './ProtectedRoute';
// Public (outside the shell)
import { ConnectPage } from '../features/connect/ConnectPage';
import { LoginPage } from '../features/auth/LoginPage';
// Core screens (eager — first paint inside the shell)
import { LibraryPage } from '../features/library/LibraryPage';
import { AlbumDetailPage } from '../features/album-detail/AlbumDetailPage';
import { ArtistDetailPage } from '../features/artist-detail/ArtistDetailPage';
import { PlaylistsPage } from '../features/playlists/PlaylistsPage';
import { PlaylistDetailPage } from '../features/playlist-detail/PlaylistDetailPage';
// Settings / Admin layouts + panels (small, eager)
import { SettingsPage } from '../features/settings/SettingsPage';
import {
ProfileSettings,
PlaybackSettings,
ScrobblingSettings,
InstanceSettings,
} from '../features/settings/panels';
import { AdminPage } from '../features/admin/AdminPage';
import {
AdminUsers,
AdminUserDetail,
AdminSources,
AdminInstance,
} from '../features/admin/panels';
import { NotFoundPage } from '../features/not-found/NotFoundPage';
// Secondary screens — lazily loaded (Suspense boundary lives in AppShell)
const SearchDownloadPage = lazy(() =>
import('../features/search-download/SearchDownloadPage').then((m) => ({
default: m.SearchDownloadPage,
})),
);
const DownloadsManagerPage = lazy(() =>
import('../features/downloads-manager/DownloadsManagerPage').then((m) => ({
default: m.DownloadsManagerPage,
})),
);
const UploadPage = lazy(() =>
import('../features/upload/UploadPage').then((m) => ({ default: m.UploadPage })),
);
const MetadataEditorPage = lazy(() =>
import('../features/metadata-editor/MetadataEditorPage').then((m) => ({
default: m.MetadataEditorPage,
})),
);
const StoragePage = lazy(() =>
import('../features/storage/StoragePage').then((m) => ({
default: m.StoragePage,
})),
);
const StorageMaintenancePage = lazy(() =>
import('../features/storage/StorageMaintenancePage').then((m) => ({
default: m.StorageMaintenancePage,
})),
);
const QueuePage = lazy(() =>
import('../features/queue/QueuePage').then((m) => ({ default: m.QueuePage })),
);
export function AppRoutes() {
return (
<Routes>
{/* Public */}
<Route path="/connect" element={<ConnectPage />} />
<Route path="/login" element={<LoginPage />} />
{/* Authenticated shell */}
<Route
element={
<ProtectedRoute>
<AppShell />
</ProtectedRoute>
}
>
<Route index element={<Navigate to="/library" replace />} />
{/* Library */}
<Route path="/library" element={<LibraryPage />} />
<Route path="/albums/:albumId" element={<AlbumDetailPage />} />
<Route path="/artists/:artistId" element={<ArtistDetailPage />} />
{/* Playlists */}
<Route path="/playlists" element={<PlaylistsPage />} />
<Route path="/playlists/:playlistId" element={<PlaylistDetailPage />} />
{/* Discover & downloads (permission-gated) */}
<Route
path="/discover"
element={
<ProtectedRoute requirePermission="download">
<SearchDownloadPage />
</ProtectedRoute>
}
/>
<Route
path="/downloads"
element={
<ProtectedRoute requirePermission="download">
<DownloadsManagerPage />
</ProtectedRoute>
}
/>
{/* Upload & metadata */}
<Route
path="/upload"
element={
<ProtectedRoute requirePermission="upload">
<UploadPage />
</ProtectedRoute>
}
/>
<Route
path="/tracks/:trackId/metadata"
element={<MetadataEditorPage />}
/>
<Route path="/metadata/batch" element={<MetadataEditorPage batch />} />
{/* Storage */}
<Route path="/storage" element={<StoragePage />} />
<Route path="/storage/maintenance" element={<StorageMaintenancePage />} />
{/* Queue (narrow viewports) */}
<Route path="/queue" element={<QueuePage />} />
{/* Settings */}
<Route path="/settings" element={<SettingsPage />}>
<Route index element={<Navigate to="/settings/profile" replace />} />
<Route path="profile" element={<ProfileSettings />} />
<Route path="playback" element={<PlaybackSettings />} />
<Route path="scrobbling" element={<ScrobblingSettings />} />
<Route path="instance" element={<InstanceSettings />} />
</Route>
{/* Admin (admin-gated) */}
<Route
path="/admin"
element={
<ProtectedRoute requireAdmin>
<AdminPage />
</ProtectedRoute>
}
>
<Route index element={<Navigate to="/admin/users" replace />} />
<Route path="users" element={<AdminUsers />} />
<Route path="users/:userId" element={<AdminUserDetail />} />
<Route path="sources" element={<AdminSources />} />
<Route path="instance" element={<AdminInstance />} />
</Route>
{/* 404 */}
<Route path="*" element={<NotFoundPage />} />
</Route>
</Routes>
);
}