Files
crewli/apps/portal
bert.hausmans dd7dfe9c0b feat(portal): migrate option consumers to relational rich shape
Aligns the portal form renderer with the post-WS-5d snapshot + resource
contract. FieldRadio, FieldSelect, FieldMultiselect, and FieldCheckboxList
now consume options as arrays of {value, label, sort_order, translations?}
objects instead of the legacy string | {label, description, value?} union.

Locale resolution: option-level translations[locale] is preferred over
the default label when the active form locale is non-default. Pages
provide the locale via providePublicFormLocale (new helper in
publicFormInjection, mirrors providePublicFormToken). Field components
inject via usePublicFormLocale, which falls back to 'nl' when no
provider is on the tree — keeps standalone component tests light.
[public_token].vue now provides schemaQuery.data.locale ?? 'nl' to all
option-bearing renderers.

TypeScript types updated: PublicFormField.options is now OptionSpec[] |
null in @form-schema/types/formBuilder. The legacy `FieldOption` union
type is gone — passing strings or {label, description} would now fail
type-check. resolveOptionLabel(option, locale) helper exported from the
same module is the single source of truth for label resolution.

The legacy per-option `description` field is dropped as part of the
type narrowing — ARCH §5.1's option-bearing field types
(RADIO/SELECT/MULTISELECT/CHECKBOX_LIST) don't model descriptions; the
parallel RegistrationFieldTemplate domain in apps/app keeps its own
description support which is orthogonal and out of WS-5d scope. The 4
migrated components no longer render the description subtitle/paragraph
(both Vuetify item slots and the radio/checkbox custom #label slots
removed).

apps/app is NOT touched in this commit — its only options-reading
components (RegistrationField*.vue) consume the legacy
registration_field_templates / registration_form_fields domain and are
out of WS-5d scope. The commit-3 secondary filter-registry scan
returned zero portal+app consumers as predicted, so commit 4 stays
portal-only.

Vitest: 102 → 111 passed (+9 new tests in FieldOptionsLocale.spec.ts
covering preference of translations[locale] over label, fallback on
missing translation, and default-locale-no-provider fall-through, for
each of the four migrated components plus a no-provider sanity test).
The 2 pre-existing failures in FieldSectionPriority.spec.ts (stale
post-WS-5b max_priorities → max_selected references) are out of WS-5d
scope; the failure baseline is unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 02:50:33 +02:00
..

Crewli — Portal SPA

External-facing app for volunteers (login) and artists/suppliers (token links). Stripped Vuexy layout; same stack as other apps.

Setup

  1. Install dependencies:
pnpm install
  1. Create .env.local:
VITE_API_URL=http://localhost:8000/api/v1
VITE_APP_NAME="Crewli Portal"
  1. Dev server uses port 5175 (see vite.config.ts or run from repo root: make portal).
pnpm dev --port 5175

Port

Runs on http://localhost:5175

Production: e.g. VITE_API_URL=https://api.crewli.app/api/v1 and host the SPA at https://portal.crewli.app (portal links in emails use this host; see api/.env.example).