# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Overview

Church website for the Heilige Drei Könige Catholic parish, built on **Next.js 15 (App Router) + React 19 + Payload CMS v3 + PostgreSQL/PostGIS**. The frontend is fully server-rendered via Next; content is authored in the Payload admin and rendered from typed collection blocks. The repo is a **multi-tenant template**: one codebase serves multiple parish sites, selected at build/run time via `NEXT_PUBLIC_SITE_ID`.

## Common commands

```bash
npm run dev            # Next dev server (http://localhost:3000, admin at /admin)
npm run devsafe        # Same but wipes .next first (use after Payload schema changes)
npm run build          # Production build (output: 'standalone' for Docker)
npm start              # Serve production build
npm run lint           # Next/ESLint. Note: eslint.ignoreDuringBuilds is true in next.config
npm test               # Vitest (run a single test: npx vitest run path/to/file.test.ts)
npm run storybook      # Storybook on :6006 (see Storybook section for caveats)

# Payload
npm run payload migrate:create --name <name>   # create a migration from current schema
npm run payload migrate                    # apply pending migrations
npm run generate:types                     # regenerate src/payload-types.ts
npm run payload jobs:run --cron "* * * * *" --queue default   # run queued jobs locally
```

Node 22+ is required (`engines`). The `predev`/`prebuild` hooks run `scripts/copy-favicon.mjs` to copy the active site's `icon.ico` into `src/app/(home)/`.

## Architecture

### Multi-site configuration
- `sites/<siteId>/config.ts` exports a `SiteConfig` (name, colors, fonts, contact info). Current sites: `dreikoenige`, `chemnitz`.
- `src/config/site.ts` picks one based on `NEXT_PUBLIC_SITE_ID` and exposes `siteConfig`. Unknown IDs throw at module load.
- Each site also provides its own `Logo.tsx`, `logoSvg.ts`, and `icon.ico`. When adding theming or site-specific assets, go through `siteConfig` — don't hardcode.

### Next App Router layout
`src/app/` uses two route groups:
- **`(home)`** — public site. Notably:
  - `[[...slug]]/page.tsx` is the catch-all that renders any CMS `Pages` document via the shared `<Blocks>` composition.
  - Dedicated routes exist for domain entities: `gemeinde/` (churches), `gruppe/` (groups), `gottesdienst/` (worship), `veranstaltungen/` (events), `blog/`, `pfarrei/` (parish), `sakramente/`, `spenden/` (donations), `suche/` (search), `gebetsanliegen-des-papstes/` (pope's prayer intentions).
  - `api/draft/` enables Payload draft mode for live preview.
- **`(payload)`** — Payload admin (`/admin`) and Payload's own API routes. Don't edit files under this group by hand; they're scaffolded by `@payloadcms/next`.

### Payload as the content layer
`src/payload.config.ts` is the single source of truth. Collections, globals, the Lexical editor config, the jobs queue, and plugins (GCS storage, search) all live there.

- **Collections** (`src/collections/*.ts`): `Parish`, `Churches`, `Worship`, `Events`, `Blog`, `Groups`, `Pages`, `Announcements`, `LiturgicalCalendar`, `PopesPrayerIntentions`, `Classifieds`, `ContactPerson`, `Locations`, `DonationForms`, `Prayers`, `Magazine`, `Highlight`, `Documents`, `Media`, `Users`.
- **Globals** (`src/globals/`): `Menu`, `Footer`, plus a `ValidateHref` helper.
- **Blocks** (`src/collections/blocks/*.ts`): reusable Lexical-adjacent block schemas referenced by `Pages.content`, `Blog.content`, etc. Each block has a matching renderer branch in `src/compositions/Blocks/Blocks.tsx` — **adding a new block means updating both the schema and `Blocks.tsx`**.
- **Types**: `src/payload-types.ts` is generated — never edit by hand. Run `npm run generate:types` after schema changes. The TS path alias `@payload-config` resolves to the config file and `@/*` resolves to `src/*`.
- **DB**: Postgres adapter with `idType: 'uuid'` and `push: false` (migrations only, no dev-time schema push). PostGIS extension is required because `Locations` uses geo fields.
- **GraphQL is disabled**. Use the REST/local API.
- **Admin UI**: German only (`@payloadcms/translations/languages/de`); logo comes from the active site's `Logo.tsx` via `serverProps`.

### Data fetching pattern
Server components read via helpers in `src/fetch/` (e.g. `fetchPageBySlug`, `fetchBlogBySlug`, `fetchChurchBySlug`) rather than calling Payload directly from page files. These helpers accept a `draft` flag so draft mode works end-to-end. Authenticated previews use `isAuthenticated()` from `src/utils/auth.ts` plus the `<RefreshRouteOnSave>` client component.

### Rendering structure
- **`src/components/<Name>/`** — leaf/primitive UI (`Button`, `Title`, `Container`, …). Each folder usually contains the `.tsx`, a `styles.module.scss`, and a `.stories.tsx`.
- **`src/compositions/<Name>/`** — larger assemblies that combine components and sometimes fetch data (e.g. `Blocks`, `PageHeader`, `Footer`, `ContactSection`).
- **`src/pageComponents/<Entity>/`** — page-level components for specific routes (`Home`, `Parish`, `Event`, `Worship`, `Search`).
- **`src/utils/dto/`** — DTO mappers that normalize Payload shapes (e.g. `transformGallery`, `getPhoto`) before passing data into presentation components. When a block renderer needs data, prefer an existing DTO helper over inlining the reshape.

### Recurring masses / jobs queue
See the README for the full walkthrough. Key points for code changes:
- The task is `src/jobs/generateRecurringMasses.ts`; it's registered in `payload.config.ts` under `jobs.tasks`.
- `autoRun` only fires when `NODE_ENV === 'production'` (via `shouldAutoRun`). In dev, either trigger via the admin "Run now" button or the `npm run payload jobs:run` CLI command.
- Churches' `afterChange` hook queues a regeneration whenever the `recurringSchedule` field changes. The task only appends future occurrences — it will not overwrite existing `Worship` docs.
- `Worship` docs produced by the task are indistinguishable from hand-created ones; editors can still cancel/modify individual occurrences.

### Search
The `@payloadcms/plugin-search` plugin indexes `parish`, `pages`, `blog`, `event`, `group`. Two non-default tweaks in `payload.config.ts` worth knowing about:
1. `beforeSync` maps `originalDoc.name` → `searchDoc.title` because `Parish` and `Groups` don't have a `title` field.
2. `searchOverrides.fields` lifts the `doc.value` relationship's `maxDepth` to 1 so the results page (`/suche`) can read each referenced doc's `slug` to build URLs.

### Storage (GCS)
The `gcsStorage` plugin is registered unconditionally but `enabled: !!process.env.GOOGLE_BUCKET`. `alwaysInsertFields: true` keeps the collection schema (and therefore `payload-types.ts` / `importMap.js`) identical whether or not GCS is active — so you don't get spurious diffs switching between local-only dev and cloud dev. Public URLs are built manually via `generateFileURL` (`https://storage.googleapis.com/<bucket>/<prefix>/<filename>`).

### Styling
- SCSS Modules (`*.module.scss`) colocated with components.
- Root-level `_template.scss` and CSS variables from `siteConfig` drive theming.
- `next.config.mjs` pins `images.remotePatterns` to `storage.googleapis.com/<GOOGLE_BUCKET>/**`; local uploads are served from Next's default route.

### Storybook
`.storybook/main.ts` contains a custom **`mockServerModules` Vite plugin** that stubs out `payload` and every `@payloadcms/*` import with a Proxy-based no-op module. This is intentional — components that incidentally import Payload utilities won't blow up the browser build. If you add a component that genuinely needs server-only behavior in a story, refactor the story to pass data as props rather than trying to unmock.

### Migrations
Payload migrations live in `src/migrations/`. Every schema change should produce a new `<timestamp>_<name>.ts` + `.json` pair via `npm run payload migrate:create`. Production deploys must run `npm run payload migrate` against the prod DB (not yet wired into CD — see README todo).

## Path aliases
- `@/*` → `src/*`
- `@payload-config` → `src/payload.config.ts`

## Things to avoid
- Don't edit `src/payload-types.ts` — regenerate it.
- Don't edit files under `src/app/(payload)/admin/` or `src/app/(payload)/api/` — they're Payload-owned.
- Don't add a new block schema without also adding its renderer branch in `src/compositions/Blocks/Blocks.tsx` — the Pages route will silently drop unknown block types.
- Don't rely on `push: true` dev-mode schema sync; this project uses migrations exclusively.
