M0: scaffold SPA + API monorepo

Backend (Django 6.1 + DRF):
- Token auth with a custom User model (register/login/logout/me)
- Library models (MediaItem, MediaLocation) and REST endpoints
- File list/detail with search, rating filter, sorting, pagination
- Multipart upload with optional rating/tags/notes
- Range-aware media serving (video seeking) and ffmpeg thumbnails
- scan_files management command for the watched folder

Frontend (React 19 + Vite + TypeScript):
- Catppuccin Mocha design tokens from the design docs
- App shell, token persistence, protected routes
- Library grid with filters, file detail with custom data editor
- Upload page with per-file progress via XHR
- Dev proxy to the Django API
This commit is contained in:
2026-09-17 07:56:47 -05:00
commit 555c25d77b
65 changed files with 5237 additions and 0 deletions
+53
View File
@@ -0,0 +1,53 @@
import { useEffect, type ReactNode } from "react";
import { Navigate, Route, Routes, useLocation } from "react-router-dom";
import { AppShell } from "@/components/AppShell";
import { FullPageSpinner } from "@/components/ui";
import LoginPage from "@/features/auth/LoginPage";
import RegisterPage from "@/features/auth/RegisterPage";
import FileDetailPage from "@/features/library/FileDetailPage";
import LibraryPage from "@/features/library/LibraryPage";
import UploadPage from "@/features/upload/UploadPage";
import { useAuth } from "@/store/auth";
function RequireAuth({ children }: { children: ReactNode }) {
const user = useAuth((state) => state.user);
const location = useLocation();
if (!user) {
return <Navigate to="/login" state={{ from: location.pathname }} replace />;
}
return <>{children}</>;
}
export default function App() {
const ready = useAuth((state) => state.ready);
const loadUser = useAuth((state) => state.loadUser);
useEffect(() => {
void loadUser();
}, [loadUser]);
if (!ready) {
return <FullPageSpinner />;
}
return (
<Routes>
<Route element={<AppShell />}>
<Route path="/" element={<LibraryPage />} />
<Route path="/view/:md5" element={<FileDetailPage />} />
<Route
path="/upload"
element={
<RequireAuth>
<UploadPage />
</RequireAuth>
}
/>
<Route path="*" element={<Navigate to="/" replace />} />
</Route>
<Route path="/login" element={<LoginPage />} />
<Route path="/register" element={<RegisterPage />} />
</Routes>
);
}