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>
This commit is contained in:
@@ -31,10 +31,12 @@ final class PortalShiftController extends Controller
|
||||
return $this->forbidden('Je moet eerst goedgekeurd zijn om diensten te claimen.');
|
||||
}
|
||||
|
||||
$eventIds = $this->resolveEventIds($event);
|
||||
|
||||
$shifts = Shift::query()
|
||||
->where('status', 'open')
|
||||
->where('slots_open_for_claiming', '>', 0)
|
||||
->whereHas('timeSlot', fn ($q) => $q->where('event_id', $event->id)->where('person_type', 'VOLUNTEER'))
|
||||
->whereHas('timeSlot', fn ($q) => $q->whereIn('event_id', $eventIds)->where('person_type', 'VOLUNTEER'))
|
||||
->with(['festivalSection', 'timeSlot', 'location'])
|
||||
->withCount([
|
||||
'shiftAssignments as active_assignments_count' => fn ($q) => $q->whereNotIn('status', [
|
||||
@@ -113,8 +115,10 @@ final class PortalShiftController extends Controller
|
||||
{
|
||||
$person = $this->resolvePerson($event);
|
||||
|
||||
$eventIds = $this->resolveEventIds($event);
|
||||
|
||||
$assignments = ShiftAssignment::where('person_id', $person->id)
|
||||
->whereHas('shift.timeSlot', fn ($q) => $q->where('event_id', $event->id))
|
||||
->whereHas('shift.timeSlot', fn ($q) => $q->whereIn('event_id', $eventIds))
|
||||
->with(['shift.festivalSection', 'shift.timeSlot', 'shift.location'])
|
||||
->get();
|
||||
|
||||
@@ -239,6 +243,25 @@ final class PortalShiftController extends Controller
|
||||
->firstOrFail();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all event IDs relevant for shift queries.
|
||||
* For festivals: includes parent + all child event IDs.
|
||||
* For flat/sub-events: just the event's own ID.
|
||||
*
|
||||
* @return list<string>
|
||||
*/
|
||||
private function resolveEventIds(Event $event): array
|
||||
{
|
||||
$ids = [$event->id];
|
||||
|
||||
if ($event->isFestival()) {
|
||||
$childIds = $event->children()->pluck('id')->all();
|
||||
$ids = array_merge($ids, $childIds);
|
||||
}
|
||||
|
||||
return $ids;
|
||||
}
|
||||
|
||||
private function mapClaimErrorMessage(string $message): string
|
||||
{
|
||||
if (str_contains($message, 'niet open')) {
|
||||
|
||||
Reference in New Issue
Block a user