Commit Graph

13 Commits

Author SHA1 Message Date
dda60ed5e4 refactor(form-schema): extract schema types and schema-driven behaviors to shared package
Moves formBuilder types, formValidation, useConditionalLogic, useFormSteps,
and formatFieldValue from apps/portal/src to packages/form-schema/src.
Adds @form-schema path alias to both apps/portal and apps/app.
Vue field components remain per-app to allow independent visual evolution.
Behavior-neutral: all 35 Vitest tests green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 23:57:39 +02:00
e95f9a75f6 fix(portal): review display, hover overlay, and drag ghost for complex field types
- Extract formatFieldValue helper for shared use between review step
  and confirmation page — one source of truth for TAG_PICKER,
  AVAILABILITY_PICKER, and SECTION_PRIORITY display, so the raw-ID
  and [object Object] leaks from two parallel stringifiers can't
  regress on either side.
- TAG_PICKER: lookup via field.available_tags (server-inlined).
- AVAILABILITY_PICKER: lookup via usePublicFormTimeSlots, strip
  seconds. "Laden…" while the cache warms.
- SECTION_PRIORITY: defensive shape-guard prevents [object Object]
  leaks, sorted priority-prefixed rendering ("1. Bar, 2. Hospitality").
- Subtle primary-tinted hover (4% primary, primary border) replacing
  the near-black Vuetify default overlay on unranked section cards.
- Explicit ghost-class / drag-class / chosen-class on vuedraggable
  with solid drag-clone + elevation shadow and a 30%-opacity silhouette
  at the origin, so mid-drag text no longer overlaps.
- 17 new formatFieldValue unit assertions + 2 new FieldSectionPriority
  assertions locking in the draggable classes and the disabled-card
  toggle at max.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 22:12:47 +02:00
9256c05db0 feat(portal): implement TAG_PICKER, AVAILABILITY_PICKER, SECTION_PRIORITY field types
- FieldTagPicker: VAutocomplete multiple with grouped category slots,
  empty/null category normalised to "Overig", empty-state info alert
  when the server delivers no tags.
- FieldAvailabilityPicker: date-grouped checkbox list, festival-aware
  via usePublicFormTimeSlots. Event-name subheaders only surface when
  the time-slots span multiple events. Time format strips seconds.
- FieldSectionPriority: tap-to-rank + drag-to-reorder via vuedraggable
  for desktop; mobile tap-only. Renumbers priorities on every mutation.
  Self-heals malformed modelValue. UI soft cap via
  validation_rules.max_priorities clamped to the backend hard cap of 5.
- FieldRenderer: three new types removed from isStubbed.
- publicFormInjection: page-level provide/inject for the public token.
- IdentityMatchBanner: prefers backend-provided Dutch copy with
  frontend defaults as defensive fallback.
- FormConfirmation wires the banner inline.
- usePublicFormTimeSlots and usePublicFormSections TanStack composables.
- 40 new Vitest assertions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 20:00:40 +02:00
4074dce402 feat(portal): public-form component architecture
Replace monolithic register/[eventSlug].vue with composable field
renderer, conditional-logic engine, stepper, and per-field components
driven by Form Builder schema. Adds flatpickr for date fields.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 17:20:59 +02:00
3ecd4daee1 feat(portal): persist submitter details through draft lifecycle
Adds submitterName/submitterEmail state and setters to useFormDraft and
wires them through start/saveDraft/submit. Previously the Contactgegevens
name/email were held in a local page ref and never made it into any
request body, so submissions landed in the DB with NULL submitter fields
and a mid-form reload wiped whatever the user had typed.

- useFormDraft: internal submitterName/submitterEmail refs with setters
  that mark the draft dirty (same debounced-PUT path as field values),
  sessionStorage resume via draft_submitter:{token}, and a
  MISSING_SUBMITTER guard in submitForm so empty fields surface as
  submitError without hitting the endpoint.
- register/[public_token].vue: deletes the local submitter refs and
  reads/writes through the composable; onSubmit pre-validates and
  bounces the user back to the Contactgegevens step with a snackbar
  when fields are missing.
- SaveDraftBody / SubmitBody: optional public_submitter_name and
  public_submitter_email per the documented backend contract.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 14:08:13 +02:00
c4a23b6763 feat: passwordless registration — defer account creation to approval
Removes password from the volunteer registration form. Account
creation is now deferred to the approval step:

Backend:
- Registration creates Person without User (user_id=null)
- On approval, system finds or creates User by person.email
- New accounts get a "set password" email with activation link
- Existing accounts get a portal link email
- Added registration_source column to persons (self/organizer)
- Fuzzy name matching skipped for self-registered persons
- person.email is always source of truth for account linking

Frontend:
- Registration form no longer collects password
- Email check shows info alert with login suggestion
- New wachtwoord-instellen.vue page for account activation
- PasswordRequirements.vue component (reused on reset page)
- Success page updated with activation messaging

Tests: 837 passed (all updated for new flow)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 03:27:47 +02:00
d5fb15e5fe feat: set preferred MFA method from account settings
Adds the ability for users to change their preferred/primary MFA method
when both TOTP and email are available.

Backend:
- Add PUT /auth/mfa/preferred-method endpoint with validation
  (method must be totp/email, MFA must be enabled, TOTP must be
  configured if selecting totp)
- Add totp_configured and email_configured fields to MFA status
  endpoint (totp = has secret + enabled, email = always when enabled)
- Fix setupEmail() to preserve mfa_secret so TOTP config survives
  when email is set up as a second method

Frontend (organizer + portal):
- Add useSetPreferredMethod() composable to useMfa.ts
- Add totp_configured/email_configured to MfaStatus type
- SecurityTab method cards now show "Primaire methode" chip on the
  preferred method and "Als primair instellen" button on the other
- Portal security section shows per-method rows with status chips
  and primary switching

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 22:47:34 +02:00
0be2956ea4 feat: MFA frontend with auth page restyling, challenge screen, and setup wizard
- Restyle organizer auth pages: Dutch text, remove placeholder social login
- Restyle portal auth pages to Vuexy v1 centered card pattern with decorative shapes
- MFA challenge card component with VOtpInput, method tabs, backup code input,
  trusted device checkbox, and session countdown timer
- Login pages handle mfa_required response with device fingerprint header
- Security settings page with TOTP setup (QR code), email setup, disable MFA,
  backup codes regeneration, and trusted devices management
- Portal profile page includes MFA security section
- Admin user detail page shows MFA status with reset button
- MFA enforcement route guard redirects to security settings when required
- Device fingerprint utility for trusted device identification
- MFA types, composables with TanStack Query for both apps

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 21:32:17 +02:00
53100d4f6d feat: portal cross-event my-shifts endpoint and dashboard page
GET /portal/my-shifts aggregates shift assignments across all events
the logged-in user is linked to via Person records. Groups by event
then date, showing only active assignments (approved/pending_approval)
for approved/pending persons.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 15:07:08 +02:00
d4004c798c feat: show identity match hint on registration success page
When a pending identity match is detected after volunteer registration,
the API now returns has_existing_account in the response. The success
page shows a login suggestion card so the volunteer can link their
registration to their existing account.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 08:56:25 +02:00
59ad09fad2 feat(portal): auth persistence, shift visibility, profile page, and UI polish
- Fix session persistence: add loading state to App.vue, hydrate portal store
  in router guards so page refresh preserves auth + event context
- Fix shift visibility for festivals: query child event time slots so shifts
  on sub-events appear in the portal
- Add profile page with editable personal info and password change
- Add backend endpoints: PUT /portal/profile and PUT /portal/password
- Fix registration form: make first_name/last_name editable for logged-in users
- Restyle login page: remove Vuexy illustration, center form with Crewli branding
- Improve dashboard StatusCard with action cards, icons, and upcoming shift count
- Enhance shift cards with status border colors and availability progress bars

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 10:19:14 +02:00
5173f7297f feat(portal): shift claiming and my-shifts for volunteer portal
Backend: PortalShiftController with 4 endpoints (available-shifts,
my-shifts, claim, cancel) delegating to ShiftAssignmentService.
24 PHPUnit tests covering happy paths, auth, conflicts, and edge cases.

Frontend: claim-shifts and my-shifts pages with TanStack Query
composable, conflict detection, confirmation dialogs, and cancel flow.
Navigation and dashboard cards wired up for approved volunteers.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 08:47:12 +02:00
3400e4cc7e feat(portal): multi-step volunteer registration form with public event endpoint
- Add GET /api/v1/public/events/{slug}/registration-data endpoint for fetching
  event sections and time slots without auth
- Create 5-step registration form: personal info, details, motivation, section
  preferences, availability
- VeeValidate + Zod validation per step with Dutch error messages
- Auth-aware: pre-fills name/email for authenticated users
- Mobile responsive with custom chip-based step indicator
- Success page with contextual actions (dashboard vs login)
- Types, composable (TanStack Query), and Zod schemas

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 18:41:20 +02:00