feat: auth by default, theme toggle, CoderPad startup, review fixes
- Require a session on every route; public routes opt out with @allow_anonymous - Split password hashing and pepper loading into passwords.py - Add a system/light/dark theme toggle backed by light-dark() colors - Ignore stale 401s from an earlier session, PATCH only changed book fields, and block overlapping journal-entry saves - Add bin/start and CoderPad Vite server settings for the pad's start/restart - Rewrite README as a mise onboarding guide; expand .gitignore - Include review-round fixes and tests
This commit is contained in:
+43
-13
@@ -1,4 +1,4 @@
|
||||
import { useState } from "react";
|
||||
import { Fragment, type ReactNode, useState } from "react";
|
||||
import {
|
||||
BrowserRouter,
|
||||
Link,
|
||||
@@ -6,15 +6,18 @@ import {
|
||||
Outlet,
|
||||
Route,
|
||||
Routes,
|
||||
useParams,
|
||||
} from "react-router";
|
||||
import { errorMessage } from "./api";
|
||||
import { AuthProvider, useAuth } from "./auth/AuthContext";
|
||||
import { LoginForm } from "./auth/LoginForm";
|
||||
import { RequireAuth } from "./auth/RequireAuth";
|
||||
import { BookList } from "./books/BookList";
|
||||
import { BookDetail } from "./books/BookDetail";
|
||||
import { BookForm } from "./books/BookForm";
|
||||
import { secondaryButton } from "./ui";
|
||||
import { BookList } from "./books/BookList";
|
||||
import { AlertIcon, BookIcon } from "./icons";
|
||||
import { ThemeToggle } from "./ThemeToggle";
|
||||
import { errorText, secondaryButton } from "./ui";
|
||||
|
||||
export function App() {
|
||||
return (
|
||||
@@ -34,8 +37,22 @@ export function AppRoutes() {
|
||||
<Route element={<Shell />}>
|
||||
<Route path="/books" element={<BookList />} />
|
||||
<Route path="/books/new" element={<BookForm />} />
|
||||
<Route path="/books/:id/edit" element={<BookForm />} />
|
||||
<Route path="/books/:id" element={<BookDetail />} />
|
||||
<Route
|
||||
path="/books/:id/edit"
|
||||
element={
|
||||
<KeyedByBookId>
|
||||
<BookForm />
|
||||
</KeyedByBookId>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/books/:id"
|
||||
element={
|
||||
<KeyedByBookId>
|
||||
<BookDetail />
|
||||
</KeyedByBookId>
|
||||
}
|
||||
/>
|
||||
</Route>
|
||||
</Route>
|
||||
<Route path="*" element={<Navigate to="/books" replace />} />
|
||||
@@ -43,18 +60,30 @@ export function AppRoutes() {
|
||||
);
|
||||
}
|
||||
|
||||
function KeyedByBookId({ children }: { children: ReactNode }) {
|
||||
const { id } = useParams();
|
||||
return <Fragment key={id}>{children}</Fragment>;
|
||||
}
|
||||
|
||||
function Shell() {
|
||||
const { user, logout } = useAuth();
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
return (
|
||||
<div className="min-h-screen bg-slate-50 text-slate-900">
|
||||
<header className="border-b border-slate-200 bg-white">
|
||||
<div className="mx-auto flex max-w-4xl items-center justify-between p-4">
|
||||
<Link to="/books" className="text-lg font-semibold">
|
||||
<div className="min-h-screen">
|
||||
<header className="border-b border-line">
|
||||
<div className="mx-auto flex max-w-4xl flex-wrap items-center justify-between gap-3 px-6 py-3.5">
|
||||
<Link
|
||||
to="/books"
|
||||
className="flex items-center gap-2.5 rounded-lg font-serif text-2xl font-semibold focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-accent"
|
||||
>
|
||||
<span className="text-accent">
|
||||
<BookIcon />
|
||||
</span>
|
||||
My Library
|
||||
</Link>
|
||||
<div className="flex items-center gap-3 text-sm">
|
||||
<span>{user?.username}</span>
|
||||
<div className="flex items-center gap-3">
|
||||
<ThemeToggle />
|
||||
<span className="text-muted">{user?.username}</span>
|
||||
<button
|
||||
type="button"
|
||||
className={secondaryButton}
|
||||
@@ -69,13 +98,14 @@ function Shell() {
|
||||
{error && (
|
||||
<p
|
||||
role="alert"
|
||||
className="mx-auto max-w-4xl px-4 pb-2 text-sm text-red-700"
|
||||
className={`mx-auto max-w-4xl px-6 pb-3 ${errorText}`}
|
||||
>
|
||||
<AlertIcon />
|
||||
{error}
|
||||
</p>
|
||||
)}
|
||||
</header>
|
||||
<main className="mx-auto max-w-4xl p-4">
|
||||
<main className="mx-auto max-w-4xl px-6 pt-11 pb-18">
|
||||
<Outlet />
|
||||
</main>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user