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>
This commit is contained in:
2026-04-10 18:41:20 +02:00
parent 60ea1f0b40
commit 3400e4cc7e
12 changed files with 1077 additions and 8 deletions

View File

@@ -21,6 +21,10 @@ use App\Http\Controllers\Api\V1\ShiftAssignmentController;
use App\Http\Controllers\Api\V1\ShiftController;
use App\Http\Controllers\Api\V1\TimeSlotController;
use App\Http\Controllers\Api\V1\VolunteerAvailabilityController;
use App\Http\Controllers\Api\V1\VolunteerRegistrationController;
use App\Http\Controllers\Api\V1\PublicRegistrationDataController;
use App\Http\Controllers\Api\V1\PortalTokenController;
use App\Http\Controllers\Api\V1\PortalMeController;
use App\Http\Controllers\Api\V1\UserOrganisationTagController;
use App\Models\FestivalSection;
use App\Models\Organisation;
@@ -50,12 +54,20 @@ Route::post('auth/login', LoginController::class);
Route::get('invitations/{token}', [InvitationController::class, 'show']);
Route::post('invitations/{token}/accept', [InvitationController::class, 'accept']);
// Public portal routes
Route::get('public/events/{slug}/registration-data', PublicRegistrationDataController::class);
Route::post('events/{event}/volunteer-register', VolunteerRegistrationController::class);
Route::post('portal/token-auth', [PortalTokenController::class, 'auth']);
// Protected routes
Route::middleware('auth:sanctum')->group(function () {
// Auth
Route::get('auth/me', MeController::class);
Route::post('auth/logout', LogoutController::class);
// Portal (authenticated)
Route::get('portal/me', [PortalMeController::class, 'index']);
// Organisations
Route::apiResource('organisations', OrganisationController::class)
->only(['index', 'show', 'store', 'update']);