Give each call its own page, and a form to answer it

A call now has its address — description, what the volunteer gets, the
commitment added up, level, closing date, project, technologies — and
the application form right there. The landing's call card leads to it
instead of scrolling to the general form, and shows the level in the
visitor's language rather than the API's `mid` or `any`.

The form is the landing's, with a `callSlug` that hides the kind
selector and posts to the new /api/calls/{slug}/apply handler: the API
refuses a `kind` on an application, which is always an offer of time.
Both handlers share one check, core/submission.ts, now tested on its
own.

A call that is filled or closed keeps its page — a link to it may
still be out there — but shows why there is no form, and where to
write instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
pigui
2026-09-29 15:02:06 +02:00
parent 439f951f2e
commit b5681a12ac
15 changed files with 625 additions and 92 deletions

View File

@@ -161,12 +161,17 @@ landing/src/
(thin wrappers over `@pi/ui`), each with its `.scss`
server/
routes/api/contact.post.ts forwards the contact form to the API
routes/api/calls/[slug]/apply.post.ts forwards an application to one call
middleware/not-found-status.ts
styles.scss Material theme, `@pi/ui` sheets, shared primitives
```
**Nothing renders from the browser.** All reads happen in the page's `load`
function, and the one write goes through this app's own `/api/contact` handler.
function, and the two writes go through this app's own handlers —
`/api/contact` for the general form, `/api/calls/{slug}/apply` for a call's —
which share one check (`core/submission.ts`) before spending a round trip. An
application to a call sends no `kind`: the API refuses one there, because
answering a call is always an offer of time.
That is what the API expects: it throttles submissions per e-mail address
rather than per IP *because* an SSR frontend posts on the visitor's behalf, and
keeping the API host out of the browser means CORS never comes up.
@@ -217,6 +222,7 @@ named in that language:
/sobre-nosotros /en/about-us /ca/qui-som
/proyectos/catalogo /en/projects/catalogo /ca/projectes/catalogo
/equipo/ada /en/team/ada /ca/equip/ada
/colabora/frontend /en/contribute/frontend /ca/col-labora/frontend
```
`core/routes.ts#resolveRoute` is that rule read backwards, and its `SECTIONS`