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:
2026-04-13 10:19:14 +02:00
parent 838bee4d60
commit 59ad09fad2
17 changed files with 1145 additions and 254 deletions

View File

@@ -12,6 +12,10 @@ use App\Models\Event;
use App\Models\Person;
use App\Models\TimeSlot;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\Rules\Password;
final class PortalMeController extends Controller
{
@@ -77,4 +81,56 @@ final class PortalMeController extends Controller
return $this->success($data);
}
public function updateProfile(Request $request): JsonResponse
{
$validated = $request->validate([
'event_id' => ['required', 'ulid'],
'first_name' => ['sometimes', 'string', 'max:255'],
'last_name' => ['sometimes', 'string', 'max:255'],
'phone' => ['sometimes', 'nullable', 'string', 'max:50'],
'date_of_birth' => ['sometimes', 'nullable', 'date', 'before:today'],
'remarks' => ['sometimes', 'nullable', 'string', 'max:5000'],
]);
$user = $request->user();
$event = Event::findOrFail($validated['event_id']);
if ($event->isSubEvent()) {
$event = $event->parent;
}
$person = Person::where('user_id', $user->id)
->where('event_id', $event->id)
->firstOrFail();
// Update user record (name fields)
$userFields = Arr::only($validated, ['first_name', 'last_name']);
if (!empty($userFields)) {
$user->update($userFields);
}
// Update person record (phone, date_of_birth, remarks)
$personFields = Arr::only($validated, ['first_name', 'last_name', 'phone', 'date_of_birth', 'remarks']);
if (!empty($personFields)) {
$person->update($personFields);
}
return $this->success(['message' => 'Profiel bijgewerkt.']);
}
public function updatePassword(Request $request): JsonResponse
{
$validated = $request->validate([
'current_password' => ['required', 'string', 'current_password'],
'password' => ['required', 'string', Password::min(8), 'confirmed'],
]);
$request->user()->update([
'password' => Hash::make($validated['password']),
]);
return $this->success(['message' => 'Wachtwoord gewijzigd.']);
}
}