Rewrite the calendar frontend in Next.js
Replaces the Angular 18 app with Next.js 16 / React 19 / Tailwind 4 / shadcn, the same stack and design system as nachklang-feedback, planned in docs/rewrite-plan.md. - Admin: all calendars in one mobile-friendly list (month groups, calendar chips, Kommend/Vergangen/Alle, search, sort, filters in the URL), an edit sheet for create/edit/duplicate/delete, publish confirmation, and an "Abonnieren" dialog with subscription links for every calendar. - Public landing page: subscribe links from env instead of hardcoded, plus the next public events, server-rendered and revalidated every 5 minutes. - All times handled explicitly as Europe/Berlin; whole-day events stored as 00:00-23:59. Fixes carried over from the Angular app: the move dialog leaking a cancelled calendar change, saves that looked saved when they failed, in-place Date mutation, and duplicated calendar-id mappings. - Same shared-session auth as before (proxy.ts gate, /admin/me, 401 vs 403). - 112 Vitest tests over lib/; docs in docs/ (architecture, features, gotchas, API wish list, deployment). Needs the API branch feature/calendar-ical-escaping-subscriptions deployed first (GET /calendar/events/subscriptions). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
138 files changed
+15942
-24010
No files matched your search
@@ -2,24 +2,56 @@
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
@AGENTS.md
|
||||
|
||||
## Commands
|
||||
|
||||
```bash
|
||||
npm start # Dev server at http://localhost:4200/ (live reload)
|
||||
npm run build # Production build
|
||||
npm run watch # Build with watch mode (development config)
|
||||
npm test # Run unit tests (Jasmine/Karma in Chrome)
|
||||
npm run dev # Dev server at http://localhost:4200/ - the port is required, see Authentication
|
||||
npm run build # Production build (output: standalone; postbuild copies .next/static in)
|
||||
npm run lint # ESLint (eslint-config-next)
|
||||
npm test # Vitest, pure logic in lib/ (runs with TZ=America/New_York on purpose)
|
||||
```
|
||||
|
||||
No linter is configured in this project.
|
||||
`NEXT_PUBLIC_API_BASE_URL` and `NEXT_PUBLIC_ADMIN_APP_URL` must be set (`.env.local`, from
|
||||
`.env.local.example`); `lib/env.ts` fails the build without them.
|
||||
|
||||
## Docs
|
||||
|
||||
`docs/` is kept current as part of every change - features in `features.md`, surprises in
|
||||
`gotchas.md`, API requests in `api-wishlist.md`.
|
||||
|
||||
- [docs/architecture.md](docs/architecture.md) - routes, auth, data flow, time handling
|
||||
- [docs/features.md](docs/features.md) - what the app does
|
||||
- [docs/gotchas.md](docs/gotchas.md) - read before assuming how the API, Next or Base UI behaves
|
||||
- [docs/api-wishlist.md](docs/api-wishlist.md) - API changes this app would like
|
||||
- [docs/deployment.md](docs/deployment.md) - Plesk build/run, switchover, rollback
|
||||
- [docs/rewrite-plan.md](docs/rewrite-plan.md) - the Angular → Next rewrite: decisions and progress log
|
||||
|
||||
## Architecture
|
||||
|
||||
Angular 18 single-page app for managing calendar events for the "Nachklang" organization. Uses Angular Material for UI, RxJS for async data, and reactive forms. No NgRx — state lives in component local variables.
|
||||
Next.js 16 (App Router) + React 19 + TypeScript + Tailwind 4 + shadcn (Base UI primitives), the same
|
||||
stack and design system as `nachklang-feedback`. Rewritten from Angular in September 2026. German UI.
|
||||
|
||||
**Environments** (`src/environments/`): `apiUrl` and `adminAppUrl`.
|
||||
- Dev: `http://localhost:3000` (expects backend running locally) / `http://localhost:3002`
|
||||
- Prod: `https://api.nachklang.art` / `https://admin.nachklang.art`
|
||||
- `app/page.tsx` - public landing page (server component, `revalidate = 300`).
|
||||
- `app/admin/` - the Verwaltung: `layout.tsx` is the session gate, `page.tsx` the event list,
|
||||
`login/page.tsx` a forwarder to the admin app.
|
||||
- `components/admin/` - list, toolbar, chips, `EventSheet` (create/edit/duplicate/delete) and
|
||||
`EventForm`, `PublishDialog`, `SubscriptionsDialog`, hooks `useEvents` / `useListState`.
|
||||
- `lib/` - all logic that isn't rendering, and all of it is unit-tested: `api.ts`, `dates.ts`
|
||||
(Berlin time), `event-form.ts` (form rules, validation, payloads), `filter.ts`, `url-state.ts`,
|
||||
`calendars.ts` (the one calendar list), `events.ts` (parsing the API's raw rows).
|
||||
- `proxy.ts` - cookie-presence gate on `/admin/*`.
|
||||
|
||||
Rules worth knowing before editing (details in `docs/`):
|
||||
|
||||
- **Every wall-clock time is Europe/Berlin**, whatever the browser's zone. Forms hold strings, the API
|
||||
gets UTC instants. Whole-day events are 00:00-23:59 Berlin, end inclusive.
|
||||
- **Always send the whole event.** The API's PUT defaults missing fields (`status` → `PUBLIC`,
|
||||
`repeatFrequency` → `''`).
|
||||
- **Calendars live in `lib/calendars.ts`** and must match `calendarNames` in the API.
|
||||
- **After `npx shadcn add`**, point `cn` imports back at `@/lib/utils` and drop the `cn` /
|
||||
`next-themes` packages it adds (see `docs/gotchas.md`).
|
||||
|
||||
## Authentication
|
||||
|
||||
@@ -29,44 +61,28 @@ feedback and admin apps: accounts live in the admin app, and the session is an h
|
||||
cookie on `.nachklang.art` that the *API's* host sets. Consequences that cannot be designed
|
||||
around:
|
||||
|
||||
- `withCredentials: true` is mandatory on every call (`api.service.ts` sets it once). Without
|
||||
it the browser sends no cookie and the API answers 401.
|
||||
- `credentials: "include"` is mandatory on every call (`lib/api.ts` sets it once). Without it
|
||||
the browser sends no cookie and the API answers 401.
|
||||
- This app can never read or verify the session. It calls `GET /admin/me` and believes the
|
||||
answer; the API's `requireAppAccess('calendar')` is the actual gate.
|
||||
answer; the API's `requireAppAccess('calendar')` is the actual gate. `/admin/me` answers 200
|
||||
for an account without the calendar permission - check `apps`.
|
||||
- 401 and 403 mean different things and must not be collapsed. 401 means "nobody is signed
|
||||
in" and is the only one worth redirecting to the login page - redirecting on 403 produces a
|
||||
loop where signing in succeeds and lands straight back on the refusal. See `failure` in
|
||||
`admin.component.ts`.
|
||||
loop where signing in succeeds and lands straight back on the refusal. See `app/admin/layout.tsx`.
|
||||
- Sign-out ends the session for *all four* apps; there is only one.
|
||||
|
||||
The dev server must be reachable at a port the API trusts. `ng serve` defaults to 4200, which
|
||||
The dev server must be reachable at a port the API trusts. `npm run dev` pins 4200 (`next dev -p 4200`), which
|
||||
is in better-auth's dev `localhostOrigins` and in the admin app's
|
||||
`NEXT_PUBLIC_ALLOWED_REDIRECT_ORIGINS`; another port fails sign-out and the return redirect,
|
||||
not the sign-in.
|
||||
|
||||
**The public calendar stays anonymous.** `GET /calendar/events/public/json` needs no session
|
||||
at all, because nachklang.art reads it to show the next upcoming event.
|
||||
at all, because nachklang.art reads it to show the next upcoming event - and so does this app's
|
||||
landing page, server-side.
|
||||
|
||||
**Routing** (`app.routing.ts`):
|
||||
- `/` → `LandingpageComponent`
|
||||
- `/admin` → `AdminComponent` (main calendar management page)
|
||||
- `**` → `NotfoundComponent`
|
||||
## Local testing
|
||||
|
||||
**Service layer** (`src/app/services/`):
|
||||
- `api.service.ts` — all HTTP calls to the backend REST API; carries the session cookie via `withCredentials`, holds no credential itself
|
||||
- `admin-auth.service.ts` — where signing in happens: the admin app's login URL (with a `?redirect=` back here) and sign-out
|
||||
- `utils.service.ts` — caches the signed-in user's display name for unsaved draft rows. Cosmetic only; the server takes the author from the session
|
||||
|
||||
**Data models** (`src/app/models/`): `Event`, `User`, `Session`
|
||||
|
||||
**Admin page** (`src/app/pages/admin/`) is the core of the app. It owns events state and passes it down to `EventsTableComponent` via `@Input()`. Key features: multi-calendar support (public, members, choir, management, birthdays), event filtering (future/past/all), sorting, and an event-move dialog (`EventMovePopupComponent` via Angular Material Dialog).
|
||||
|
||||
**Calendar-specific behavior:** Birthday calendar auto-sets recurrence to YEARLY. Events have a `status` field (`DRAFT` / `DELETED`).
|
||||
|
||||
**Session lifecycle:** `AdminComponent` calls `me()` on init. 401 redirects to the admin app's
|
||||
login carrying this URL as the return target; 403 (or a signed-in account without the
|
||||
`calendar` permission) shows a refusal with Reload/Sign out; anything else shows "cannot be
|
||||
reached". None of those three redirect, on purpose.
|
||||
|
||||
**Dead since the cutover:** the unrouted `LoginComponent`. `src/app/models/session.ts` is
|
||||
gone; there is no session type here any more, because this app never handles one.
|
||||
Use a throwaway database, never shared infrastructure: the API repo's `docker/init` scripts seed a
|
||||
MariaDB container (podman works) including the `dev@nachklang.art` account; start the API from a
|
||||
scratch directory with its own `.env`. The exact commands are in the progress log of
|
||||
`docs/rewrite-plan.md` ("Local test environment").
|
||||
Reference in new issue
Block a user