From 62346e66e0b22bdfc49f8129cbf0b20c00833571 Mon Sep 17 00:00:00 2001 From: undead2146 Date: Tue, 22 Apr 2025 16:26:59 +0200 Subject: [PATCH 01/30] Update password reset temp link --- .../Auth/ForgotPasswordController.php | 82 ++++++++---------- .../Auth/ResetPasswordController.php | 86 +++++++++++++------ app/Mail/PasswordResetMail.php | 53 +++--------- app/Models/User.php | 60 ++++++++++++- .../ResetPasswordNotification.php | 73 ++++++++++++++++ app/Providers/AuthServiceProvider.php | 60 +++++++++++++ .../views/auth/passwords/email.blade.php | 4 +- .../views/auth/passwords/reset.blade.php | 14 +-- .../views/emails/password-reset.blade.php | 37 ++++++-- .../vendor/notifications/email.blade.php | 58 +++++++++++++ 10 files changed, 393 insertions(+), 134 deletions(-) create mode 100644 app/Notifications/ResetPasswordNotification.php create mode 100644 app/Providers/AuthServiceProvider.php create mode 100644 resources/views/vendor/notifications/email.blade.php diff --git a/app/Http/Controllers/Auth/ForgotPasswordController.php b/app/Http/Controllers/Auth/ForgotPasswordController.php index 0008c932..83b1e8a0 100644 --- a/app/Http/Controllers/Auth/ForgotPasswordController.php +++ b/app/Http/Controllers/Auth/ForgotPasswordController.php @@ -3,31 +3,17 @@ namespace App\Http\Controllers\Auth; use App\Http\Controllers\Controller; -use App\Mail\PasswordResetMail; -use App\Models\Traveller; use App\Models\User; +use App\Models\Traveller; use Illuminate\Foundation\Auth\SendsPasswordResetEmails; use Illuminate\Http\Request; +use Illuminate\Support\Facades\Password; use Illuminate\Support\Facades\DB; -use Illuminate\Support\Facades\Hash; -use Illuminate\Support\Facades\Mail; -use Illuminate\Support\Facades\Schema; +use Illuminate\Support\Facades\Log; use Illuminate\Support\Str; -use Illuminate\Validation\ValidationException; class ForgotPasswordController extends Controller { - /* - |-------------------------------------------------------------------------- - | Password Reset Controller - |-------------------------------------------------------------------------- - | - | This controller is responsible for handling password reset emails and - | includes a trait which assists in sending these notifications from - | your application to your users. Feel free to explore this trait. - | - */ - use SendsPasswordResetEmails; /** @@ -51,56 +37,56 @@ public function sendResetLinkEmail(Request $request) $this->validateEmail($request); try { - // Find the user by their student number/login from users table + // Find the user by their student number/login $user = User::where('login', $request->login)->first(); - \Log::info('Looking for user with login: ' . $request->login); - if (!$user) { - \Log::info('No user found with login: ' . $request->login); + Log::notice('Password reset request for non-existent user: ' . $request->login); return back()->withErrors([ 'login' => ['We kunnen geen gebruiker vinden met dit studentnummer.'], ]); } - \Log::info('Found user: ' . $user->id); - // Find the traveller record for this user to get their email $traveller = Traveller::where('user_id', $user->id)->first(); - $email = null; - - if ($traveller && !empty($traveller->email)) { - $email = $traveller->email; - \Log::info('Using email from traveller record: ' . $email); - } else { - \Log::info('No traveller email found for user: ' . $user->id); + + if (!$traveller || empty($traveller->email)) { + Log::notice('Password reset request for user without email: ' . $user->id); return back()->withErrors([ 'login' => ['We kunnen geen e-mailadres vinden dat gekoppeld is aan dit studentnummer.'], ]); } - - // Generate a new password - $newPassword = Str::random(10); - - // Update the user's password - $user->password = Hash::make($newPassword); - $user->save(); - + + $email = $traveller->email; + + // Generate a token manually + $token = Str::random(64); + + // Store the token in the password_reset_tokens table + DB::table('password_reset_tokens')->updateOrInsert( + ['email' => $email], + ['email' => $email, 'token' => $token, 'created_at' => now()] + ); + + // Create the reset URL + $resetUrl = url(route('password.reset', [ + 'token' => $token, + 'email' => $email, + ], false)); + + // Send custom reset email with URL + \Mail::to($email)->send(new \App\Mail\PasswordResetMail($user, null, $resetUrl)); + // Create masked email for display $maskedEmail = $this->maskEmail($email); - - // Send an email with the new password - \Log::info("Password reset for user {$user->login}: New password is {$newPassword}"); - Mail::to($email)->send(new PasswordResetMail($user, $newPassword)); - - // Redirect to login with success message + + Log::info('Password reset link sent to user: ' . $user->login); return redirect()->route('login') - ->with('status', "We hebben een nieuw wachtwoord verstuurd naar {$maskedEmail}. Controleer uw e-mail."); + ->with('status', "We hebben een link om uw wachtwoord te herstellen verstuurd naar {$maskedEmail}. Controleer uw e-mail."); } catch (\Exception $e) { - \Log::error('Exception in password reset: ' . $e->getMessage()); - \Log::error($e->getTraceAsString()); - + Log::error('Exception in password reset: ' . $e->getMessage()); + return back()->withErrors([ 'login' => ['Er is een fout opgetreden bij het herstellen van uw wachtwoord. Probeer het later opnieuw.'], ]); diff --git a/app/Http/Controllers/Auth/ResetPasswordController.php b/app/Http/Controllers/Auth/ResetPasswordController.php index 7b189d96..35b674ce 100644 --- a/app/Http/Controllers/Auth/ResetPasswordController.php +++ b/app/Http/Controllers/Auth/ResetPasswordController.php @@ -9,9 +9,11 @@ use Illuminate\Auth\Events\PasswordReset; use Illuminate\Foundation\Auth\ResetsPasswords; use Illuminate\Http\Request; +use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Password; use Illuminate\Support\Str; +use Illuminate\Support\Facades\Log; class ResetPasswordController extends Controller { @@ -28,12 +30,68 @@ class ResetPasswordController extends Controller use ResetsPasswords; + protected $redirectTo = '/'; + /** - * Where to redirect users after resetting their password. + * Reset the user's password. * - * @var string + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\JsonResponse */ - protected $redirectTo = RouteServiceProvider::HOME; + public function reset(Request $request) + { + $request->validate($this->rules(), $this->validationErrorMessages()); + + // Here we will check if the token and email match in the password_reset_tokens table + $resetRecord = DB::table('password_reset_tokens') + ->where('email', $request->email) + ->first(); + + if (!$resetRecord) { + Log::notice('Password reset attempt with invalid token or email: ' . $request->email); + return $this->sendResetFailedResponse($request, Password::INVALID_TOKEN); + } + + if ($request->token !== $resetRecord->token) { + Log::notice('Password reset attempt with mismatched token for: ' . $request->email); + return $this->sendResetFailedResponse($request, Password::INVALID_TOKEN); + } + + // Find the traveller with this email + $traveller = Traveller::where('email', $request->email)->first(); + if (!$traveller) { + return $this->sendResetFailedResponse($request, 'We kunnen geen gebruiker vinden met dit e-mailadres.'); + } + + // Get the associated user + $user = User::find($traveller->user_id); + if (!$user) { + return $this->sendResetFailedResponse($request, 'We kunnen geen gebruiker vinden voor dit account.'); + } + + try { + // Reset the password + $user->password = Hash::make($request->password); + $user->setRememberToken(Str::random(60)); + $user->save(); + + // Delete the token + DB::table('password_reset_tokens')->where('email', $request->email)->delete(); + + // Log the user in + auth()->login($user); + + // Trigger the PasswordReset event + event(new PasswordReset($user)); + + Log::info('Password reset successful for user: ' . $user->login); + return $this->sendResetResponse($request, Password::PASSWORD_RESET); + + } catch (\Exception $e) { + Log::error('Exception during password reset: ' . $e->getMessage()); + return $this->sendResetFailedResponse($request, 'Er is een fout opgetreden bij het herstellen van uw wachtwoord.'); + } + } /** * Get the password reset validation rules. @@ -49,26 +107,6 @@ protected function rules() ]; } - /** - * Reset the given user's password. - * - * @param \Illuminate\Contracts\Auth\CanResetPassword $user - * @param string $password - * @return void - */ - protected function resetPassword($user, $password) - { - $this->setUserPassword($user, $password); - - $user->setRememberToken(Str::random(60)); - - $user->save(); - - event(new PasswordReset($user)); - - $this->guard()->login($user); - } - /** * Get the response for a successful password reset. * @@ -95,4 +133,4 @@ protected function sendResetFailedResponse(Request $request, $response) ->withInput($request->only('email')) ->withErrors(['email' => trans($response)]); } -} \ No newline at end of file +} diff --git a/app/Mail/PasswordResetMail.php b/app/Mail/PasswordResetMail.php index d333ffa6..861342e3 100644 --- a/app/Mail/PasswordResetMail.php +++ b/app/Mail/PasswordResetMail.php @@ -2,68 +2,41 @@ namespace App\Mail; -use App\Models\User; use Illuminate\Bus\Queueable; -use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Mail\Mailable; -use Illuminate\Mail\Mailables\Content; -use Illuminate\Mail\Mailables\Envelope; use Illuminate\Queue\SerializesModels; class PasswordResetMail extends Mailable { use Queueable, SerializesModels; - /** - * The user instance. - * - * @var \App\Models\User - */ public $user; - - /** - * The new password. - * - * @var string - */ public $password; + public $resetUrl; /** * Create a new message instance. + * + * @param \App\Models\User $user + * @param string|null $password + * @param string|null $resetUrl + * @return void */ - public function __construct(User $user, string $password) + public function __construct($user, $password = null, $resetUrl = null) { $this->user = $user; $this->password = $password; + $this->resetUrl = $resetUrl; } /** - * Get the message envelope. - */ - public function envelope(): Envelope - { - return new Envelope( - subject: 'Uw wachtwoord voor TechReizen is gereset', - ); - } - - /** - * Get the message content definition. - */ - public function content(): Content - { - return new Content( - view: 'emails.password-reset', - ); - } - - /** - * Get the attachments for the message. + * Build the message. * - * @return array + * @return $this */ - public function attachments(): array + public function build() { - return []; + return $this->subject('TechReizen - Wachtwoord Herstel') + ->view('emails.password-reset'); } } diff --git a/app/Models/User.php b/app/Models/User.php index 7991f189..54b92dce 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -2,15 +2,19 @@ namespace App\Models; -// use Illuminate\Contracts\Auth\MustVerifyEmail; +use App\Models\Traveller; +use Illuminate\Contracts\Auth\MustVerifyEmail; +use Illuminate\Contracts\Auth\CanResetPassword; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; +use Laravel\Sanctum\HasApiTokens; +use App\Notifications\ResetPasswordNotification; +use Illuminate\Support\Facades\Hash; -class User extends Authenticatable +class User extends Authenticatable implements CanResetPassword { - /** @use HasFactory<\Database\Factories\UserFactory> */ - use HasFactory, Notifiable; + use HasFactory, Notifiable; /** * The attributes that are mass assignable. @@ -74,4 +78,52 @@ public function traveller() { return $this->hasOne(Traveller::class); } + + /** + * Get the email address used for password resets. + * + * @return string + */ + public function getEmailForPasswordReset() + { + $traveller = $this->traveller()->first(); + return $traveller ? $traveller->email : null; + } + + /** + * Route notifications for the mail channel. + * + * @return string + */ + public function routeNotificationForMail() + { + return $this->getEmailForPasswordReset(); + } + + /** + * Send the password reset notification. + * + * @param string $token + * @return void + */ + public function sendPasswordResetNotification($token) + { + $this->notify(new ResetPasswordNotification($token)); + } + + /** + * Explicitly set the password attribute with hashing. + * + * @param string $value + * @return void + */ + public function setPasswordAttribute($value) + { + // Only hash the password if it's not already hashed + if ($value && !preg_match('/^\$2y\$/', $value)) { + $this->attributes['password'] = Hash::make($value); + } else { + $this->attributes['password'] = $value; + } + } } diff --git a/app/Notifications/ResetPasswordNotification.php b/app/Notifications/ResetPasswordNotification.php new file mode 100644 index 00000000..8e834b92 --- /dev/null +++ b/app/Notifications/ResetPasswordNotification.php @@ -0,0 +1,73 @@ +token = $token; + } + + /** + * Get the notification's delivery channels. + * + * @param mixed $notifiable + * @return array + */ + public function via($notifiable) + { + return ['mail']; + } + + /** + * Get the mail representation of the notification. + * + * @param mixed $notifiable + * @return \Illuminate\Notifications\Messages\MailMessage|null + */ + public function toMail($notifiable) + { + $url = url(route('password.reset', [ + 'token' => $this->token, + 'email' => $notifiable->getEmailForPasswordReset(), + ], false)); + + // Generate a temporary password for the link + $temporaryPassword = Str::random(10); + + // Send email using your existing PasswordResetMail class + Mail::to($notifiable->getEmailForPasswordReset()) + ->send(new PasswordResetMail( + $notifiable, + $temporaryPassword, + $url + )); + + // This notification doesn't use Laravel's default notification email + return null; + } +} diff --git a/app/Providers/AuthServiceProvider.php b/app/Providers/AuthServiceProvider.php new file mode 100644 index 00000000..14a62b73 --- /dev/null +++ b/app/Providers/AuthServiceProvider.php @@ -0,0 +1,60 @@ + + */ + protected $policies = [ + // + ]; + + /** + * Register any authentication / authorization services. + */ + public function boot(): void + { + $this->registerPolicies(); + + // Set a custom URL for password reset links + ResetPassword::createUrlUsing(function ($user, string $token) { + return url(route('password.reset', [ + 'token' => $token, + 'email' => $user->getEmailForPasswordReset(), + ], false)); + }); + + // Register custom user provider that can look up users by traveller email + Auth::provider('traveller_email', function ($app, array $config) { + return new class($app['hash'], $config['model']) extends EloquentUserProvider { + public function retrieveByCredentials(array $credentials) + { + if (isset($credentials['email'])) { + // Find traveller by email + $traveller = Traveller::where('email', $credentials['email'])->first(); + + if ($traveller) { + // Then find the user + return User::find($traveller->user_id); + } + return null; + } + + // Otherwise use normal retrieval + return parent::retrieveByCredentials($credentials); + } + }; + }); + } +} diff --git a/resources/views/auth/passwords/email.blade.php b/resources/views/auth/passwords/email.blade.php index 96c34948..8ebc06fa 100644 --- a/resources/views/auth/passwords/email.blade.php +++ b/resources/views/auth/passwords/email.blade.php @@ -14,7 +14,7 @@ @endif -

Vul uw studentnummer in, en wij sturen een wachtwoord herstelmail naar het e-mailadres dat aan uw account is gekoppeld.

+

Vul uw studentnummer in, en wij sturen een wachtwoord herstellink naar het e-mailadres dat aan uw account is gekoppeld.

@csrf @@ -37,7 +37,7 @@
diff --git a/resources/views/auth/passwords/reset.blade.php b/resources/views/auth/passwords/reset.blade.php index dccf6c65..00e18da8 100644 --- a/resources/views/auth/passwords/reset.blade.php +++ b/resources/views/auth/passwords/reset.blade.php @@ -5,7 +5,7 @@
-
{{ __('Reset Password') }}
+
{{ __('Wachtwoord Herstellen') }}
@@ -14,10 +14,10 @@
- +
- + @error('email') @@ -28,10 +28,10 @@
- +
- + @error('password') @@ -42,7 +42,7 @@
- +
@@ -52,7 +52,7 @@
diff --git a/resources/views/emails/password-reset.blade.php b/resources/views/emails/password-reset.blade.php index dbc8d299..53d56e80 100644 --- a/resources/views/emails/password-reset.blade.php +++ b/resources/views/emails/password-reset.blade.php @@ -47,18 +47,37 @@
-

Uw wachtwoord is gereset

+

Wachtwoord Herstel

Beste {{ $user->login }},

-

Uw wachtwoord voor TechReizen is gereset volgens uw verzoek. Hieronder vindt u uw nieuwe inloggegevens:

- -

Gebruikersnaam: {{ $user->login }}

-

Nieuw wachtwoord: {{ $password }}

- -

U kunt inloggen met deze gegevens via onze website.

- -

Wij raden u aan om dit wachtwoord zo snel mogelijk te wijzigen na het inloggen.

+ @if(isset($resetUrl)) +

U heeft een verzoek ingediend om uw wachtwoord te herstellen. Klik op de onderstaande link om een nieuw wachtwoord in te stellen:

+ +

+ + Wachtwoord Herstellen + +

+ +

U kunt deze link ook kopiƫren en in uw browser plakken:

+

+ {{ $resetUrl }} +

+ +

Deze link is 60 minuten geldig.

+ +

Als u geen wachtwoord herstel heeft aangevraagd, kunt u deze e-mail negeren en blijft uw huidige wachtwoord geldig.

+ @else +

Uw wachtwoord voor TechReizen is gereset volgens uw verzoek. Hieronder vindt u uw nieuwe inloggegevens:

+ +

Gebruikersnaam: {{ $user->login }}

+

Nieuw wachtwoord: {{ $password }}

+ +

U kunt inloggen met deze gegevens via onze website.

+ +

Wij raden u aan om dit wachtwoord zo snel mogelijk te wijzigen na het inloggen.

+ @endif

Als u dit verzoek niet heeft gedaan, neem dan direct contact op met onze klantenservice.

diff --git a/resources/views/vendor/notifications/email.blade.php b/resources/views/vendor/notifications/email.blade.php new file mode 100644 index 00000000..c44e9ea7 --- /dev/null +++ b/resources/views/vendor/notifications/email.blade.php @@ -0,0 +1,58 @@ +@component('mail::message') +{{-- Greeting --}} +@if (! empty($greeting)) +# {{ $greeting }} +@else +@if ($level === 'error') +# {{ __('Oeps!') }} +@else +# {{ __('Hallo!') }} +@endif +@endif + +{{-- Intro Lines --}} +@foreach ($introLines as $line) +{{ $line }} + +@endforeach + +{{-- Action Button --}} +@isset($actionText) + $level, + default => 'primary', + }; +?> +@component('mail::button', ['url' => $actionUrl, 'color' => $color]) +{{ $actionText }} +@endcomponent +@endisset + +{{-- Outro Lines --}} +@foreach ($outroLines as $line) +{{ $line }} + +@endforeach + +{{-- Salutation --}} +@if (! empty($salutation)) +{{ $salutation }} +@else +{{ __('Met vriendelijke groet,') }} + +{{ config('app.name') }} +@endif + +{{-- Subcopy --}} +@isset($actionText) +@slot('subcopy') +@lang( + "Als u problemen heeft met het klikken op de knop \":actionText\", kopieer en plak dan de onderstaande URL in uw webbrowser:", + [ + 'actionText' => $actionText, + ] +) [{{ $displayableActionUrl }}]({{ $actionUrl }}) +@endslot +@endisset +@endcomponent From b442944693735141e9714d6defec76ad29934753 Mon Sep 17 00:00:00 2001 From: IDavidG <145495942+IDavidGI@users.noreply.github.com> Date: Mon, 28 Apr 2025 09:14:44 +0200 Subject: [PATCH 02/30] small fixes for password reset --- app/Http/Controllers/Auth/ResetPasswordController.php | 2 +- resources/views/auth/login.blade.php | 2 +- resources/views/traveller/home.blade.php | 10 +++++----- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/Http/Controllers/Auth/ResetPasswordController.php b/app/Http/Controllers/Auth/ResetPasswordController.php index 35b674ce..9ba17c39 100644 --- a/app/Http/Controllers/Auth/ResetPasswordController.php +++ b/app/Http/Controllers/Auth/ResetPasswordController.php @@ -116,7 +116,7 @@ protected function rules() */ protected function sendResetResponse(Request $request, $response) { - return redirect($this->redirectPath()) + return redirect(route('traveller.home')) ->with('status', 'Uw wachtwoord is succesvol gewijzigd!'); } diff --git a/resources/views/auth/login.blade.php b/resources/views/auth/login.blade.php index 91b52f84..d8653b5c 100644 --- a/resources/views/auth/login.blade.php +++ b/resources/views/auth/login.blade.php @@ -112,4 +112,4 @@ }); @endif -@endsection +@endsection \ No newline at end of file diff --git a/resources/views/traveller/home.blade.php b/resources/views/traveller/home.blade.php index 5321c008..d6a81875 100644 --- a/resources/views/traveller/home.blade.php +++ b/resources/views/traveller/home.blade.php @@ -4,15 +4,15 @@
+ @if (session('status')) + + @endif
{{ __('Dashboard') }}
- @if (session('status')) - - @endif

{{ __('You are a traveller!') }}

From bcecb0604fcb55155ddabe028efe7ca9438223c3 Mon Sep 17 00:00:00 2001 From: IDavidG <145495942+IDavidGI@users.noreply.github.com> Date: Mon, 28 Apr 2025 10:19:44 +0200 Subject: [PATCH 03/30] added foreign key for cities in traveller --- ...ies_table.php => 2025_03_18_140001_create_cities_table.php} | 3 ++- database/migrations/2025_03_18_140038_create_travellers.php | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) rename database/migrations/{2025_04_03_072111_create_cities_table.php => 2025_03_18_140001_create_cities_table.php} (89%) diff --git a/database/migrations/2025_04_03_072111_create_cities_table.php b/database/migrations/2025_03_18_140001_create_cities_table.php similarity index 89% rename from database/migrations/2025_04_03_072111_create_cities_table.php rename to database/migrations/2025_03_18_140001_create_cities_table.php index 7627e956..03e90221 100644 --- a/database/migrations/2025_04_03_072111_create_cities_table.php +++ b/database/migrations/2025_03_18_140001_create_cities_table.php @@ -11,7 +11,8 @@ public function up(): void { Schema::create('cities', function (Blueprint $table) { - $table->integer('postcode'); + $table->id(); + $table->string('postcode'); $table->string('plaatsnaam'); $table->string('provincie'); $table->timestamps(); diff --git a/database/migrations/2025_03_18_140038_create_travellers.php b/database/migrations/2025_03_18_140038_create_travellers.php index 173baaba..7612a869 100644 --- a/database/migrations/2025_03_18_140038_create_travellers.php +++ b/database/migrations/2025_03_18_140038_create_travellers.php @@ -17,7 +17,8 @@ public function up(): void $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); $table->unsignedBigInteger('trip_id'); // New field for trip_id $table->foreign('trip_id')->references('id')->on('trips')->onDelete('cascade'); // Foreign key constraint - $table->integer('zip_id')->length(10); + $table->unsignedBigInteger('zip_id')->length(10); + $table->foreign('zip_id')->references('id')->on('cities')->onDelete('cascade'); $table->integer('major_id')->length(10); $table->string('first_name'); $table->string('last_name'); From a171dacf5692c4659ded60b5b74b43afcb81aa76 Mon Sep 17 00:00:00 2001 From: undead2146 Date: Mon, 28 Apr 2025 10:36:27 +0200 Subject: [PATCH 04/30] Added custom City-selector component to pick/add a city --- app/Http/Controllers/Api/CityController.php | 96 +++ app/Http/Controllers/Auth/LoginController.php | 17 +- .../GuestRegistrationController.php | 74 +- app/View/Components/CitySelector.php | 54 ++ .../views/components/city-selector.blade.php | 777 ++++++++++++++++++ .../registration/personal-info.blade.php | 108 ++- resources/views/layouts/app.blade.php | 11 + routes/api.php | 8 + routes/web.php | 8 + 9 files changed, 1106 insertions(+), 47 deletions(-) create mode 100644 app/Http/Controllers/Api/CityController.php create mode 100644 app/View/Components/CitySelector.php create mode 100644 resources/views/components/city-selector.blade.php create mode 100644 routes/api.php diff --git a/app/Http/Controllers/Api/CityController.php b/app/Http/Controllers/Api/CityController.php new file mode 100644 index 00000000..dedd61af --- /dev/null +++ b/app/Http/Controllers/Api/CityController.php @@ -0,0 +1,96 @@ +input('query', '')); + if (strlen($query) < 2) { + return response()->json([]); + } + $queryLower = mb_strtolower($query); + + // Search for cities + $cities = Cities::query() + ->where(function ($q) use ($queryLower) { + $q->whereRaw('LOWER(plaatsnaam) LIKE ?', ["%{$queryLower}%"]) + ->orWhere('postcode', 'LIKE', "%{$queryLower}%"); + }) + ->orderByRaw("CASE WHEN LOWER(plaatsnaam) = ? THEN 1 + WHEN LOWER(plaatsnaam) LIKE ? THEN 2 + WHEN postcode = ? THEN 3 + WHEN postcode LIKE ? THEN 4 + ELSE 5 END", + [$queryLower, "{$queryLower}%", $query, "{$query}%"]) + ->limit(20) + ->get(['provincie', 'plaatsnaam', 'postcode']); + + return response()->json($cities); + } catch (\Exception $e) { + Log::error('City search error: ' . $e->getMessage()); + return response()->json(['error' => 'Er is een fout opgetreden'], 500); + } + } + + /** + * Store a newly created city in storage. + */ + public function store(Request $request) + { + try { + $validator = Validator::make($request->all(), [ + 'plaatsnaam' => 'required|string|min:2|max:255', + 'postcode' => 'required|string|max:10', + 'provincie' => 'nullable|string|max:100', + ]); + + if ($validator->fails()) { + return response()->json(['errors' => $validator->errors()], 422); + } + + $plaatsnaam = trim($request->input('plaatsnaam')); + $postcode = trim($request->input('postcode')); + $provincie = trim($request->input('provincie') ?? ''); + $provincie = $provincie === '' ? null : $provincie; + + // Check if city already exists + $existingCity = Cities::where(function($query) use ($plaatsnaam) { + $query->whereRaw('LOWER(plaatsnaam) = ?', [mb_strtolower($plaatsnaam)]); + }) + ->where('postcode', $postcode) + ->first(); + + if ($existingCity) { + // Return HTTP 200 and existing city (instead of 201) to indicate it wasn't newly created + return response()->json($existingCity, 200); + } + + // Create the city + $city = new Cities(); + $city->plaatsnaam = $plaatsnaam; + $city->postcode = $postcode; + $city->provincie = $provincie; + $city->save(); + + // Return HTTP 201 to indicate a new resource was created + return response()->json($city, 201); + } catch (\Exception $e) { + Log::error('City creation error: ' . $e->getMessage()); + return response()->json([ + 'error' => 'Er is een fout opgetreden bij het toevoegen van de gemeente' + ], 500); + } + } +} diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php index aabadc52..d69f4311 100644 --- a/app/Http/Controllers/Auth/LoginController.php +++ b/app/Http/Controllers/Auth/LoginController.php @@ -6,6 +6,9 @@ use Illuminate\Foundation\Auth\AuthenticatesUsers; use Illuminate\Http\Request; use Illuminate\Http\RedirectResponse; +use Illuminate\Support\Facades\Log; +use App\Models\User; + class LoginController extends Controller { /* @@ -74,8 +77,18 @@ public function login(Request $request): RedirectResponse 'login' => 'required', 'password' => 'required', ]); - + + // Check if user exists + $user = User::where('login', $input['login'])->first(); + if (!$user) { + Log::notice('Failed login attempt: User not found: ' . $input['login']); + return redirect()->route('login') + ->with('error', 'Studentnummer of wachtwoord is onjuist.'); + } + if (auth()->attempt(array('login' => $input['login'], 'password' => $input['password']))) { + Log::info('Login successful for user: ' . $input['login']); + if (auth()->user()->role == 'traveller') { return redirect()->route('traveller.home'); } else if (auth()->user()->role == 'guide') { @@ -88,9 +101,9 @@ public function login(Request $request): RedirectResponse return redirect()->route('home'); } } else { + Log::notice('Failed login attempt: Invalid password for user: ' . $input['login']); return redirect()->route('login') ->with('error', 'Studentnummer of wachtwoord is onjuist.'); } - } } diff --git a/app/Http/Controllers/GuestRegistrationController.php b/app/Http/Controllers/GuestRegistrationController.php index b043f37a..b3cc923a 100644 --- a/app/Http/Controllers/GuestRegistrationController.php +++ b/app/Http/Controllers/GuestRegistrationController.php @@ -4,7 +4,7 @@ use App\Models\Education; use App\Models\Major; -use App\Models\Cities; +use App\Models\City; use App\Models\Trip; use App\Models\User; use App\Models\Traveller; @@ -16,6 +16,7 @@ use Illuminate\View\View; use Illuminate\Support\Str; use App\Mail\RegistrationConfirmationMail; +use App\Models\Cities; use Illuminate\Support\Facades\Mail; class GuestRegistrationController extends Controller @@ -95,17 +96,15 @@ public function submitBasicInfo(Request $request) } // Show Personal Info Form - public function showPersonalInfoForm(Request $request) + public function showPersonalInfoForm() { - $registration = $request->session()->get(self::SESSION_KEY, ['step' => 1]); - - if ($registration['step'] < 2) { - return redirect()->route('guest.registration.basic-info'); - } - - $cities = Cities::all(); - - return view('guest.registration.personal-info', ['registration' => (object) $registration, 'cities' => $cities]); + // Get registration data from session + $registration = session()->get('registration', new \stdClass()); + + // Load all cities for the dropdown + $cities = \App\Models\Cities::orderBy('plaatsnaam')->get(); + + return view('guest.registration.personal-info', compact('registration', 'cities')); } // Submit Personal Info Form @@ -120,9 +119,17 @@ public function submitPersonalInfo(Request $request) ->withInput(); } - // Get current session data and update it with validated data + // Get current session data $registration = $request->session()->get(self::SESSION_KEY, []); - $registration = array_merge($registration, $validation['validated'], ['step' => 3]); + + // Explicitly ensure postcode is included + $validated = $validation['validated']; + if ($request->has('postcode')) { + $validated['postcode'] = $request->input('postcode'); + } + + // Merge with existing data and set next step + $registration = array_merge($registration, $validated, ['step' => 3]); // Save updated data back to session $request->session()->put(self::SESSION_KEY, $registration); @@ -196,14 +203,10 @@ public function submitConfirmation(Request $request) $existingUser = User::where('login', $registration['student_number'])->first(); if ($existingUser) { - \Log::info("User with login {$registration['student_number']} already exists. Skipping user creation."); - // Check if the user already has a traveller record $existingTraveller = Traveller::where('user_id', $existingUser->id)->first(); if ($existingTraveller) { - \Log::info("Traveller record for {$registration['student_number']} already exists. Registration complete."); - // Clear the registration data from session $request->session()->forget(self::SESSION_KEY); @@ -230,22 +233,47 @@ public function submitConfirmation(Request $request) DB::statement("ALTER TABLE users AUTO_INCREMENT = 1"); // Create the user record $user = User::create($userData); - \Log::info("Created new user with login: {$registration['student_number']}"); // We'll need to send the password email after traveller creation $shouldSendEmail = true; } + // Find city record using city name and postal code + $cityName = $registration['city'] ?? null; + $postalCode = $registration['postcode'] ?? null; + + if (!$cityName) { + throw new \Exception("Missing city name in registration data"); + } + + // Try to find the city record + $city = null; + + // First, try exact match on both city name and postal code + if ($postalCode) { + $city = Cities::where('plaatsnaam', $cityName) + ->where('postcode', $postalCode) + ->first(); + } + + // If not found, try by name only + if (!$city) { + $city = Cities::where('plaatsnaam', $cityName)->first(); + } + + + // Get the major ID based on name and education $major = Major::where('name', $registration['major']) ->where('education_id', $registration['education']) ->first(); $majorId = $major ? $major->id : 1; // Default to 1 if not found + // Create the traveller record matching the database schema $travellerData = [ 'user_id' => $user->id, - 'trip_id' => Trip::where('name', $registration['trip'])->value('id'), // set trip_id based on trip name - 'zip_id' => 3000, // Default value + 'trip_id' => Trip::where('name', $registration['trip'])->value('id'), + 'zip_id' => $city->id, 'major_id' => $majorId, 'first_name' => $registration['first_name'], 'last_name' => $registration['last_name'], @@ -264,8 +292,7 @@ public function submitConfirmation(Request $request) 'medical_issue' => $registration['medical_info'] === 'yes' ? 1 : 0, 'medical_info' => $registration['medical_info'] === 'yes' ? $registration['medical_details'] : '', 'created_at' => now(), - 'updated_at' => now(), - // from basic-info form + 'updated_at' => now(), ]; //this is for when u delete a user in the database, the id will be reset to 1 @@ -273,14 +300,12 @@ public function submitConfirmation(Request $request) DB::statement("ALTER TABLE travellers AUTO_INCREMENT = 1"); // Use direct DB insertion to avoid model validation issues DB::table('travellers')->insert($travellerData); - \Log::info("Created traveller record for: {$registration['first_name']} {$registration['last_name']}"); // Send the confirmation email with login credentials if this is a new user if (isset($shouldSendEmail)) { Mail::to($registration['email'])->send(new RegistrationConfirmationMail( (object) array_merge($registration, ['password' => $password]) )); - \Log::info("Sent registration confirmation email to: {$registration['email']}"); } // Clear the registration data from session @@ -301,7 +326,6 @@ public function submitConfirmation(Request $request) catch (\Exception $e) { // Log the error and show a generic message \Log::error("Registration error: " . $e->getMessage()); - \Log::error("Stack trace: " . $e->getTraceAsString()); return redirect()->route('login') ->with('error', 'Er is een fout opgetreden bij uw registratie. Neem contact op met de beheerder.'); diff --git a/app/View/Components/CitySelector.php b/app/View/Components/CitySelector.php new file mode 100644 index 00000000..5881f1d0 --- /dev/null +++ b/app/View/Components/CitySelector.php @@ -0,0 +1,54 @@ +selectedCity = $selectedCity; + $this->selectedPostalCode = $selectedPostalCode; + $this->hasError = $hasError; + } + + /** + * Get the view / contents that represent the component. + * + * @return \Illuminate\Contracts\View\View|\Closure|string + */ + public function render() + { + return view('components.city-selector'); + } +} diff --git a/resources/views/components/city-selector.blade.php b/resources/views/components/city-selector.blade.php new file mode 100644 index 00000000..44c60564 --- /dev/null +++ b/resources/views/components/city-selector.blade.php @@ -0,0 +1,777 @@ +@props(['selectedCity' => null, 'selectedPostalCode' => null, 'hasError' => false]) + +
+
+
+
+ + + + + + + +
+
+ + + + +
+
+ + + +
+
+ + +
+ + +
    + +
+
+
+ + +
+
+
+ + +
+
+
+ + +
+
+ + + Bijv. 3000 +
+
+ + +
+
+
+ +
+
+
+ + + + diff --git a/resources/views/guest/registration/personal-info.blade.php b/resources/views/guest/registration/personal-info.blade.php index 825ea761..2d10ab3c 100644 --- a/resources/views/guest/registration/personal-info.blade.php +++ b/resources/views/guest/registration/personal-info.blade.php @@ -1,4 +1,3 @@ - @extends('layouts.app') @section('content') @@ -132,32 +131,26 @@ class="form-control @error('address') is-invalid @enderror" name="address"
- +
+ +
+
Zoek een gemeente of voeg er een toe
+ @error('city') - + {{ $message }} @enderror
- @push('scripts') - - @endpush + +
@@ -189,4 +182,79 @@ class="form-control @error('country') is-invalid @enderror" name="country"
+ + + @endsection + +@push('scripts') + + + +@endpush diff --git a/resources/views/layouts/app.blade.php b/resources/views/layouts/app.blade.php index 2a28e462..a98b20d4 100644 --- a/resources/views/layouts/app.blade.php +++ b/resources/views/layouts/app.blade.php @@ -27,6 +27,14 @@ @endif + + + + + @@ -105,6 +113,9 @@ @yield('content')
+ + + @stack('scripts') diff --git a/routes/api.php b/routes/api.php new file mode 100644 index 00000000..da28c460 --- /dev/null +++ b/routes/api.php @@ -0,0 +1,8 @@ +name('home'); @@ -47,6 +48,7 @@ ->name('guest.registration.confirmation'); Route::post('/confirmation', [GuestRegistrationController::class, 'submitConfirmation']) ->name('guest.registration.confirmation.submit'); + }); Route::get('/register', function () { @@ -54,6 +56,12 @@ })->name('register'); }); +// API Routes for city selection - moved outside auth middleware to be accessible +Route::prefix('api')->group(function () { + Route::get('/cities/search', [App\Http\Controllers\Api\CityController::class, 'search'])->name('api.cities.search'); + Route::post('/cities', [App\Http\Controllers\Api\CityController::class, 'store'])->name('api.cities.store'); +}); + /*------------------------------------------ -------------------------------------------- All traveller Routes List From f6699a24972c4e7ab0a2d395e13ef6b815a23a40 Mon Sep 17 00:00:00 2001 From: undead2146 Date: Mon, 28 Apr 2025 10:41:17 +0200 Subject: [PATCH 05/30] fix redirect resetpassword controller --- app/Http/Controllers/Auth/ResetPasswordController.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Http/Controllers/Auth/ResetPasswordController.php b/app/Http/Controllers/Auth/ResetPasswordController.php index 9ba17c39..9ac7579b 100644 --- a/app/Http/Controllers/Auth/ResetPasswordController.php +++ b/app/Http/Controllers/Auth/ResetPasswordController.php @@ -30,7 +30,7 @@ class ResetPasswordController extends Controller use ResetsPasswords; - protected $redirectTo = '/'; + protected $redirectTo = '/traveller/home'; /** * Reset the user's password. @@ -116,7 +116,7 @@ protected function rules() */ protected function sendResetResponse(Request $request, $response) { - return redirect(route('traveller.home')) + return redirect($this->redirectPath()) ->with('status', 'Uw wachtwoord is succesvol gewijzigd!'); } From 2d20c9930aa64c2661f0469bc193f1be742e0de8 Mon Sep 17 00:00:00 2001 From: undead2146 Date: Mon, 28 Apr 2025 11:05:04 +0200 Subject: [PATCH 06/30] Fix citySelector validation & adding new city --- .../GuestRegistrationController.php | 6 ++--- .../views/components/city-selector.blade.php | 9 ++++--- .../registration/personal-info.blade.php | 27 +++++++++++++++++++ 3 files changed, 36 insertions(+), 6 deletions(-) diff --git a/app/Http/Controllers/GuestRegistrationController.php b/app/Http/Controllers/GuestRegistrationController.php index b3cc923a..239888c5 100644 --- a/app/Http/Controllers/GuestRegistrationController.php +++ b/app/Http/Controllers/GuestRegistrationController.php @@ -98,13 +98,13 @@ public function submitBasicInfo(Request $request) // Show Personal Info Form public function showPersonalInfoForm() { - // Get registration data from session - $registration = session()->get('registration', new \stdClass()); + // Get registration data from session using the correct session key + $registration = session()->get(self::SESSION_KEY, []); // Load all cities for the dropdown $cities = \App\Models\Cities::orderBy('plaatsnaam')->get(); - return view('guest.registration.personal-info', compact('registration', 'cities')); + return view('guest.registration.personal-info', ['registration' => (object) $registration, 'cities' => $cities]); } // Submit Personal Info Form diff --git a/resources/views/components/city-selector.blade.php b/resources/views/components/city-selector.blade.php index 44c60564..c4cb5419 100644 --- a/resources/views/components/city-selector.blade.php +++ b/resources/views/components/city-selector.blade.php @@ -71,7 +71,7 @@ class="search-results-dropdown"

Geen gemeenten gevonden voor ""

-
@@ -745,13 +745,16 @@ class="btn-save" this.newPostalCode = ''; this.newProvince = ''; - // Only show success message if it was actually created (status 201), not found (status 200) + // Dispatch appropriate event based on status code const isNewlyCreated = response.status === 201; if (isNewlyCreated) { - // Dispatch event for success notification window.dispatchEvent(new CustomEvent('city-added', { detail: { city: newCity } })); + } else { + window.dispatchEvent(new CustomEvent('city-found', { + detail: { city: newCity } + })); } } else { const errorData = await response.json(); diff --git a/resources/views/guest/registration/personal-info.blade.php b/resources/views/guest/registration/personal-info.blade.php index 2d10ab3c..eea28b06 100644 --- a/resources/views/guest/registration/personal-info.blade.php +++ b/resources/views/guest/registration/personal-info.blade.php @@ -200,6 +200,24 @@ class="toast position-fixed bottom-0 end-0 m-3" Gemeente succesvol toegevoegd!
+ + + @endsection @push('scripts') @@ -214,6 +232,15 @@ class="toast position-fixed bottom-0 end-0 m-3" } }); + // Listen for city-found event to show info toast + window.addEventListener('city-found', event => { + const toastEl = document.getElementById('cityFoundToast'); + if (toastEl) { + const toast = new bootstrap.Toast(toastEl); + toast.show(); + } + }); + // Fix for form submission with city selector const form = document.querySelector('form[action="{{ route('guest.registration.personal-info.submit') }}"]'); if (form) { From f46befd1a2c9fcbe319f2071f21eabfa2d85a42d Mon Sep 17 00:00:00 2001 From: undead2146 Date: Mon, 28 Apr 2025 11:28:25 +0200 Subject: [PATCH 07/30] Fix: Optimize seeder to use chunks to speed up seeding --- database/seeders/CreateCitiesSeeder.php | 26 +++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/database/seeders/CreateCitiesSeeder.php b/database/seeders/CreateCitiesSeeder.php index 661481a7..a42968be 100644 --- a/database/seeders/CreateCitiesSeeder.php +++ b/database/seeders/CreateCitiesSeeder.php @@ -4,6 +4,7 @@ use App\Models\cities; use Illuminate\Database\Seeder; +use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Log; class CreateCitiesSeeder extends Seeder @@ -13,6 +14,8 @@ class CreateCitiesSeeder extends Seeder */ public function run(): void { + + Log::info('Starting the seeding process for cities.'); $filePath = base_path('database/Data/zipcodes.csv'); @@ -25,21 +28,28 @@ public function run(): void $formattedCities = array_map(function ($city) { return [ - 'Plaatsnaam' => $city['Plaatsnaam'], - 'Postcode' => $city['Postcode'], - 'Provincie' => $city['Provincie'], + 'plaatsnaam' => $city['Plaatsnaam'], + 'postcode' => $city['Postcode'], + 'provincie' => $city['Provincie'], ]; }, $cities); - Log::info('Inserting data into the cities table.', ['count' => count($formattedCities)]); - //DB::table('cities')->insert($formattedCities); - foreach ($formattedCities as $cities) { - Cities::create($cities); - } + + // Use batch insert with chunks of 500 records + $chunks = array_chunk($formattedCities, 500); + + DB::transaction(function() use ($chunks) { + foreach ($chunks as $index => $chunk) { + DB::table('cities')->insert($chunk); + Log::info("Inserted chunk " . ($index + 1) . " of " . count($chunks)); + } + }); + Log::info('Data inserted successfully.'); } else { Log::warning('No data found in the CSV file or the file could not be read.'); } + } /** From 8bdfebded560d4b55815d613dc1c41e3c6b9e0db Mon Sep 17 00:00:00 2001 From: IDavidG <145495942+IDavidGI@users.noreply.github.com> Date: Mon, 28 Apr 2025 11:29:18 +0200 Subject: [PATCH 08/30] made provincie field nullable --- database/migrations/2025_03_18_140001_create_cities_table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/database/migrations/2025_03_18_140001_create_cities_table.php b/database/migrations/2025_03_18_140001_create_cities_table.php index 03e90221..4478671c 100644 --- a/database/migrations/2025_03_18_140001_create_cities_table.php +++ b/database/migrations/2025_03_18_140001_create_cities_table.php @@ -14,7 +14,7 @@ public function up(): void $table->id(); $table->string('postcode'); $table->string('plaatsnaam'); - $table->string('provincie'); + $table->string('provincie')->nullable(); $table->timestamps(); }); } From ff6acc44a9d109f5301f167449c95ff442b3cc45 Mon Sep 17 00:00:00 2001 From: lucasvsw Date: Mon, 28 Apr 2025 11:52:45 +0200 Subject: [PATCH 09/30] Navbar 'mijn reis' dropdown Groepen --- .../GuestRegistrationController.php | 2 +- resources/views/layouts/app.blade.php | 25 +++++++++++++------ 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/app/Http/Controllers/GuestRegistrationController.php b/app/Http/Controllers/GuestRegistrationController.php index 239888c5..3c9e1bd1 100644 --- a/app/Http/Controllers/GuestRegistrationController.php +++ b/app/Http/Controllers/GuestRegistrationController.php @@ -102,7 +102,7 @@ public function showPersonalInfoForm() $registration = session()->get(self::SESSION_KEY, []); // Load all cities for the dropdown - $cities = \App\Models\Cities::orderBy('plaatsnaam')->get(); + $cities = Cities::orderBy('plaatsnaam')->get(); return view('guest.registration.personal-info', ['registration' => (object) $registration, 'cities' => $cities]); } diff --git a/resources/views/layouts/app.blade.php b/resources/views/layouts/app.blade.php index a98b20d4..5a7a1628 100644 --- a/resources/views/layouts/app.blade.php +++ b/resources/views/layouts/app.blade.php @@ -16,7 +16,7 @@ @php - $viteManifestExists = file_exists(public_path('build/manifest.json')); +$viteManifestExists = file_exists(public_path('build/manifest.json')); @endphp @if($viteManifestExists) @@ -79,21 +79,30 @@ @endif @else - @if (Auth::user()->role === 'guest' && Route::has('register')) - @endif + @endif diff --git a/routes/web.php b/routes/web.php index 83af9084..211307d5 100644 --- a/routes/web.php +++ b/routes/web.php @@ -70,6 +70,9 @@ Route::middleware(['auth', 'user-access:traveller'])->group(function () { Route::get('/traveller/home', [HomeController::class, 'travellerHome'])->name('traveller.home'); + Route::get('/traveller/groups', function () { + return view('auth.traveller.groups'); + })->name('traveller.groups'); }); /*------------------------------------------ From 33a073c20e2bb450acee5321dbf2fdb25be18c44 Mon Sep 17 00:00:00 2001 From: IDavidG <145495942+IDavidGI@users.noreply.github.com> Date: Wed, 7 May 2025 19:53:36 +0200 Subject: [PATCH 14/30] added max_members to group table --- app/Models/Group.php | 1 + .../migrations/2025_03_18_130000_create_groups_table.php | 3 ++- database/seeders/CreateGroupsSeeder.php | 6 +++--- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/app/Models/Group.php b/app/Models/Group.php index 2ccbd18c..3d3192d5 100644 --- a/app/Models/Group.php +++ b/app/Models/Group.php @@ -13,5 +13,6 @@ class Group extends Model protected $fillable = [ 'name', + 'max_members', ]; } diff --git a/database/migrations/2025_03_18_130000_create_groups_table.php b/database/migrations/2025_03_18_130000_create_groups_table.php index a5e06e2d..c8f9cd0e 100644 --- a/database/migrations/2025_03_18_130000_create_groups_table.php +++ b/database/migrations/2025_03_18_130000_create_groups_table.php @@ -13,8 +13,9 @@ public function up(): void { Schema::create('groups', function (Blueprint $table) { $table->id(); - $table->string('name')->unique(); $table->foreign('id')->references('id')->on('trips')->onDelete('cascade'); + $table->string('name')->unique(); + $table->integer('max_members')->length(10); $table->timestamps(); }); } diff --git a/database/seeders/CreateGroupsSeeder.php b/database/seeders/CreateGroupsSeeder.php index 0d131c3e..c43043d0 100644 --- a/database/seeders/CreateGroupsSeeder.php +++ b/database/seeders/CreateGroupsSeeder.php @@ -15,9 +15,9 @@ class CreateGroupsSeeder extends Seeder public function run(): void { $groups = [ - ['name' => 'Group A'], - ['name' => 'Group B'], - ['name' => 'Group C'], + ['name' => 'Group A', 'max_members' => 10], + ['name' => 'Group B', 'max_members' => 15], + ['name' => 'Group C', 'max_members' => 20], ]; foreach ($groups as $group) { From 7b6b4ea1ebf7079e8f2862c38139b7bd108288fb Mon Sep 17 00:00:00 2001 From: undead2146 Date: Wed, 7 May 2025 19:56:36 +0200 Subject: [PATCH 15/30] Update GuestRegistrationController to handle Guide Registrations --- .../GuestRegistrationController.php | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/app/Http/Controllers/GuestRegistrationController.php b/app/Http/Controllers/GuestRegistrationController.php index 3c9e1bd1..6c97981a 100644 --- a/app/Http/Controllers/GuestRegistrationController.php +++ b/app/Http/Controllers/GuestRegistrationController.php @@ -222,11 +222,15 @@ public function submitConfirmation(Request $request) // Generate a secure random password $password = Str::random(10); + // Determine role based on student number prefix (U = guide, others = traveller) + $studentNumber = $registration['student_number']; + $role = (Str::startsWith(strtoupper($studentNumber), 'U')) ? 'guide' : 'traveller'; + // Create user with the fields that exist in the users table $userData = [ - 'login' => $registration['student_number'], + 'login' => $studentNumber, 'password' => Hash::make($password), - 'role' => 'traveller', + 'role' => $role // Set role based on the student number prefix ]; // Reset AUTO_INCREMENT value for users table to avoid id gaps @@ -261,8 +265,6 @@ public function submitConfirmation(Request $request) $city = Cities::where('plaatsnaam', $cityName)->first(); } - - // Get the major ID based on name and education $major = Major::where('name', $registration['major']) ->where('education_id', $registration['education']) @@ -303,8 +305,14 @@ public function submitConfirmation(Request $request) // Send the confirmation email with login credentials if this is a new user if (isset($shouldSendEmail)) { + $roleDescription = $user->role === 'guide' ? 'begeleider' : 'reiziger'; + Mail::to($registration['email'])->send(new RegistrationConfirmationMail( - (object) array_merge($registration, ['password' => $password]) + (object) array_merge($registration, [ + 'password' => $password, + 'role' => $user->role, + 'role_description' => $roleDescription + ]) )); } @@ -317,10 +325,14 @@ public function submitConfirmation(Request $request) $request->session()->regenerateToken(); // Redirect to login page with success message and pre-filled data + $roleMessage = $user->role === 'guide' + ? 'U bent geregistreerd als begeleider.' + : 'U bent geregistreerd als reiziger.'; + return redirect()->route('login') ->with('registration_complete', true) ->with('login', $registration['student_number']) - ->with('success', 'Uw registratie is succesvol verwerkt! Een e-mail met uw inloggegevens is verzonden naar ' . $registration['email'] . '. Controleer uw inbox om uw account te activeren.'); + ->with('success', 'Uw registratie is succesvol verwerkt! ' . $roleMessage . ' Een e-mail met uw inloggegevens is verzonden naar ' . $registration['email'] . '. Controleer uw inbox om uw account te activeren.'); } catch (\Exception $e) { From b9e7d29d8efe48b28b49e9db7f926a48cd730b97 Mon Sep 17 00:00:00 2001 From: IDavidG <145495942+IDavidGI@users.noreply.github.com> Date: Wed, 7 May 2025 20:40:04 +0200 Subject: [PATCH 16/30] added group_id field to traveller record in guestregistrationcontroller --- app/Http/Controllers/GuestRegistrationController.php | 2 ++ database/migrations/2025_03_18_140038_create_travellers.php | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app/Http/Controllers/GuestRegistrationController.php b/app/Http/Controllers/GuestRegistrationController.php index 6c97981a..931975f3 100644 --- a/app/Http/Controllers/GuestRegistrationController.php +++ b/app/Http/Controllers/GuestRegistrationController.php @@ -277,6 +277,8 @@ public function submitConfirmation(Request $request) 'trip_id' => Trip::where('name', $registration['trip'])->value('id'), 'zip_id' => $city->id, 'major_id' => $majorId, + // group_id is not set in the form, so we can leave it empty + 'group_id' => null, 'first_name' => $registration['first_name'], 'last_name' => $registration['last_name'], 'email' => $registration['email'], diff --git a/database/migrations/2025_03_18_140038_create_travellers.php b/database/migrations/2025_03_18_140038_create_travellers.php index ab072599..e973f2ae 100644 --- a/database/migrations/2025_03_18_140038_create_travellers.php +++ b/database/migrations/2025_03_18_140038_create_travellers.php @@ -19,7 +19,7 @@ public function up(): void $table->foreign('trip_id')->references('id')->on('trips')->onDelete('cascade'); // Foreign key constraint $table->unsignedBigInteger('zip_id')->length(10); $table->foreign('zip_id')->references('id')->on('cities')->onDelete('cascade'); - $table->unsignedBigInteger('group_id')->length(10); + $table->unsignedBigInteger('group_id')->length(10)->nullable(); $table->foreign('group_id')->references('id')->on('groups')->onDelete('cascade'); $table->integer('major_id')->length(10); $table->string('first_name'); From accadb801f347b22ba3a1df17157ec1dd30c7a75 Mon Sep 17 00:00:00 2001 From: undead2146 Date: Wed, 7 May 2025 22:16:41 +0200 Subject: [PATCH 17/30] Create Groups view/Controllers --- app/Http/Controllers/GroupController.php | 288 ++++++++++++++++ app/Models/Group.php | 59 +++- app/Models/Traveller.php | 99 +++--- app/Models/Trip.php | 51 ++- app/Models/User.php | 66 +++- ...1_01_000002_create_group_members_table.php | 44 +++ .../2025_03_17_120000_create_trips_table.php | 4 + .../2025_03_18_130000_create_groups_table.php | 28 +- .../2025_03_18_140038_create_travellers.php | 8 +- database/seeders/CreateGroupMembersSeeder.php | 67 ++++ database/seeders/CreateGroupsSeeder.php | 71 +++- database/seeders/CreateTravellersSeeder.php | 291 ++++++++++++++++ database/seeders/CreateTripsSeeder.php | 33 +- database/seeders/CreateUsersSeeder.php | 36 +- database/seeders/DatabaseSeeder.php | 5 +- package.json | 4 +- resources/views/groups/create.blade.php | 60 ++++ resources/views/groups/edit.blade.php | 57 ++++ resources/views/groups/index.blade.php | 150 +++++++++ resources/views/groups/show.blade.php | 88 +++++ resources/views/guide/groups/index.blade.php | 150 +++++++++ resources/views/guide/groups/show.blade.php | 262 +++++++++++++++ resources/views/layouts/app.blade.php | 315 ++++++++++++------ routes/web.php | 26 +- vite.config.js | 19 +- 25 files changed, 2074 insertions(+), 207 deletions(-) create mode 100644 app/Http/Controllers/GroupController.php create mode 100644 database/migrations/2023_01_01_000002_create_group_members_table.php create mode 100644 database/seeders/CreateGroupMembersSeeder.php create mode 100644 database/seeders/CreateTravellersSeeder.php create mode 100644 resources/views/groups/create.blade.php create mode 100644 resources/views/groups/edit.blade.php create mode 100644 resources/views/groups/index.blade.php create mode 100644 resources/views/groups/show.blade.php create mode 100644 resources/views/guide/groups/index.blade.php create mode 100644 resources/views/guide/groups/show.blade.php diff --git a/app/Http/Controllers/GroupController.php b/app/Http/Controllers/GroupController.php new file mode 100644 index 00000000..f7b1dfdb --- /dev/null +++ b/app/Http/Controllers/GroupController.php @@ -0,0 +1,288 @@ +role === 'guide' || $user->role === 'admin'; + + // Get the trip ID + $tripId = $user->getTripId(); + + // Check which columns exist in the groups table + $groupColumns = Schema::getColumnListing('groups'); + $hasIsOpen = in_array('is_open', $groupColumns); + $hasCurrentMembers = in_array('current_members', $groupColumns); + + if ($isGuide) { + // Guides see all groups for the current trip + $groups = Group::where('trip_id', $tripId)->get(); + + // Add virtual properties if columns don't exist + if (!$hasIsOpen || !$hasCurrentMembers) { + foreach ($groups as $group) { + if (!$hasIsOpen) { + $group->is_open = true; // Default all groups to open + } + if (!$hasCurrentMembers) { + // Count members directly from relationship + $group->current_members = $group->members()->count(); + } + } + } + + return view('groups.index', [ + 'groups' => $groups, + 'isGuide' => true + ]); + } else { + // Travelers see their groups and available groups + $traveller = $user->traveller; + $joinedGroups = $traveller ? $traveller->groups : collect([]); + + // Get all groups without filtering by missing columns + $availableGroups = Group::where('trip_id', $tripId)->get(); + + // Filter manually if columns don't exist + if (!$hasIsOpen || !$hasCurrentMembers) { + $availableGroups = $availableGroups->filter(function ($group) use ($hasIsOpen, $hasCurrentMembers) { + // Count members if column doesn't exist + $memberCount = $hasCurrentMembers ? $group->current_members : $group->members()->count(); + + // Consider all groups open if column doesn't exist + $isOpen = $hasIsOpen ? $group->is_open : true; + + return $isOpen && $memberCount < $group->max_members; + }); + } + + return view('groups.index', [ + 'joinedGroups' => $joinedGroups, + 'availableGroups' => $availableGroups, + 'isGuide' => false + ]); + } + } + + /** + * Show the form for creating a new group. + * Only accessible by guides. + */ + public function create() + { + // Make sure only guides can access this + if (!Auth::user()->canManageGroups()) { + return redirect()->route('groups.index') + ->with('error', 'Je hebt geen toestemming om groepen aan te maken.'); + } + + return view('groups.create'); + } + + /** + * Store a newly created group in storage. + */ + public function store(Request $request) + { + // Ensure only guides can create groups + if (!Auth::user()->canManageGroups()) { + return redirect()->route('groups.index') + ->with('error', 'Je hebt geen toestemming om groepen aan te maken.'); + } + + $validated = $request->validate([ + 'name' => 'required|string|max:255', + 'description' => 'nullable|string', + 'max_members' => 'required|integer|min:1|max:50', + ]); + + $group = new Group(); + $group->name = $validated['name']; + $group->description = $validated['description'] ?? ''; + $group->max_members = $validated['max_members']; + $group->trip_id = Auth::user()->getTripId(); + $group->created_by = Auth::id(); + + // Check if columns exist before setting them + if (Schema::hasColumn('groups', 'is_open')) { + $group->is_open = $request->has('is_open'); + } + + if (Schema::hasColumn('groups', 'current_members')) { + $group->current_members = 0; + } + + $group->save(); + + return redirect()->route('guide.groups.index') + ->with('success', 'Groep succesvol aangemaakt!'); + } + + /** + * Display the specified group. + * Works for both guides and travelers. + */ + public function show(Group $group) + { + $user = Auth::user(); + $isGuide = $user->role === 'guide' || $user->role === 'admin'; + + // Check if the group belongs to the user's trip + if ($group->trip_id != $user->getTripId()) { + return redirect()->route('groups.index') + ->with('error', 'Deze groep behoort niet tot jouw reis.'); + } + + // Load members relationship + $group->load('members'); + + return view('groups.show', [ + 'group' => $group, + 'isGuide' => $isGuide, + 'isMember' => $user->traveller ? $user->traveller->groups->contains($group->id) : false, + ]); + } + + /** + * Show the form for editing the specified group. + */ + public function edit(Group $group) + { + if (!Auth::user()->canManageGroups()) { + return redirect()->route('groups.index')->with('error', 'You do not have permission to edit groups.'); + } + + return view('groups.edit', compact('group')); + } + + /** + * Update the specified group in storage. + */ + public function update(Request $request, Group $group) + { + if (!Auth::user()->canManageGroups()) { + return redirect()->route('groups.index')->with('error', 'You do not have permission to update groups.'); + } + + $validated = $request->validate([ + 'name' => 'required|string|max:255', + 'description' => 'nullable|string', + 'max_members' => 'nullable|integer|min:' . $group->getMemberCount(), + ]); + + $group->update([ + 'name' => $validated['name'], + 'description' => $validated['description'] ?? $group->description, + 'max_members' => $validated['max_members'] ?? $group->max_members, + ]); + + return redirect()->route('guide.groups.show', $group)->with('success', 'Group updated successfully!'); + } + + /** + * Remove the specified group from storage. + */ + public function destroy(Group $group) + { + if (!Auth::user()->canManageGroups()) { + return redirect()->route('groups.index')->with('error', 'You do not have permission to delete groups.'); + } + + // Remove all travellers from this group first + Traveller::where('group_id', $group->id)->update(['group_id' => null]); + + // Now delete the group + $group->delete(); + + return redirect()->route('guide.groups.index')->with('success', 'Group deleted successfully!'); + } + + /** + * Join a group + */ + public function join(Group $group) + { + $user = Auth::user(); + $traveller = $user->traveller; + + if (!$traveller) { + return redirect()->route('home')->with('error', 'Traveller profile not found.'); + } + + // Check if traveller is already in a group + if ($traveller->hasGroup()) { + return redirect()->route('groups.show', $group)->with('info', 'You are already a member of a group. Leave your current group first.'); + } + + // Check if group is full + if ($group->isFull()) { + return redirect()->route('groups.index')->with('error', 'This group is full.'); + } + + // Add traveller to group + if ($traveller->joinGroup($group)) { + return redirect()->route('groups.show', $group)->with('success', 'You have joined the group successfully!'); + } + + return redirect()->route('groups.index')->with('error', 'Could not join the group.'); + } + + /** + * Leave a group + */ + public function leave(Group $group) + { + $user = Auth::user(); + $traveller = $user->traveller; + + if (!$traveller) { + return redirect()->route('home')->with('error', 'Traveller profile not found.'); + } + + // Check if traveller is in this group + if ($traveller->group_id !== $group->id) { + return redirect()->route('groups.index')->with('error', 'You are not a member of this group.'); + } + + // Remove traveller from group + if ($traveller->leaveGroup()) { + return redirect()->route('groups.index')->with('success', 'You have left the group.'); + } + + return redirect()->route('groups.show', $group)->with('error', 'Could not leave the group.'); + } + + /** + * Remove a traveller from a group (admin/guide only) + */ + public function removeTraveller(Group $group, Traveller $traveller) + { + if (!Auth::user()->canManageGroups()) { + return redirect()->route('groups.index')->with('error', 'You do not have permission to manage group members.'); + } + + // Check if traveller is in this group + if ($traveller->group_id !== $group->id) { + return redirect()->route('guide.groups.show', $group)->with('error', 'This traveller is not a member of this group.'); + } + + // Remove traveller from group + $traveller->leaveGroup(); + + return redirect()->route('guide.groups.show', $group)->with('success', 'Member removed from group successfully.'); + } +} diff --git a/app/Models/Group.php b/app/Models/Group.php index 3d3192d5..ea5aedc7 100644 --- a/app/Models/Group.php +++ b/app/Models/Group.php @@ -2,17 +2,68 @@ namespace App\Models; -use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Factories\HasFactory; +use Illuminate\Database\Eloquent\Model; class Group extends Model { use HasFactory; - protected $table = 'groups'; // <-- Zorg ervoor dat dit er staat! - protected $fillable = [ 'name', - 'max_members', + 'description', + 'trip_id', + 'created_by', + 'max_members' ]; + + /** + * Get the trip that the group belongs to + */ + public function trip() + { + return $this->belongsTo(Trip::class); + } + + /** + * Get the creator of this group + */ + public function creator() + { + return $this->belongsTo(User::class, 'created_by'); + } + + /** + * Get all travelers in this group + */ + public function travellers() + { + return $this->hasMany(Traveller::class); + } + + /** + * Get the travellers (members) of this group through the group_members pivot + */ + public function members() + { + return $this->belongsToMany(Traveller::class, 'group_members', 'group_id', 'traveller_id') + ->withTimestamps() + ->withPivot('joined_at'); + } + + /** + * Check if group is full + */ + public function isFull() + { + return $this->travellers()->count() >= $this->max_members; + } + + /** + * Get the current member count + */ + public function getMemberCount() + { + return $this->travellers()->count(); + } } diff --git a/app/Models/Traveller.php b/app/Models/Traveller.php index a2091cc4..1b456c38 100644 --- a/app/Models/Traveller.php +++ b/app/Models/Traveller.php @@ -4,30 +4,17 @@ use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; -use Illuminate\Notifications\Notifiable; class Traveller extends Model { - use HasFactory, Notifiable; + use HasFactory; - /** - * The table associated with the model. - * - * @var string - */ - protected $table = 'travellers'; - - /** - * The attributes that are mass assignable. - * - * @var array - */ protected $fillable = [ 'user_id', 'trip_id', 'zip_id', - 'major_id', 'group_id', + 'major_id', 'first_name', 'last_name', 'email', @@ -44,64 +31,67 @@ class Traveller extends Model 'bic', 'medical_issue', 'medical_info', + 'remember_token', ]; - + /** - * Default values for attributes + * The attributes that should be cast. * - * @var array + * @var array */ - protected $attributes = [ - 'iban' => 'BE00000000000000', - 'bic' => 'GEBABEBB', - 'medical_issue' => 0, - 'medical_info' => '', + protected $casts = [ + 'birth_date' => 'date', ]; /** - * Get the attributes that should be cast. - * - * @return array - */ - protected function casts(): array - { - return [ - 'birthdate' => 'date', - 'medical_issue' => 'boolean', - ]; - } - - /** - * Get the user associated with this traveller + * Get the user that owns the traveller profile. */ public function user() { return $this->belongsTo(User::class); } - + /** - * Get the trip this traveller belongs to + * Get the trip that the traveller is registered for. */ public function trip() { return $this->belongsTo(Trip::class); } - + /** - * Get the groups this traveller is a member of + * Get the education that the traveller is in. */ - public function groups() + public function education() { - return $this->belongsToMany(Group::class, 'group_members', 'traveller_id', 'group_id')->withTimestamps(); + return $this->belongsTo(Education::class); } - + + /** + * Get the major that the traveller is in. + */ + public function major() + { + return $this->belongsTo(Major::class); + } + /** - * Get the group this traveller belongs to + * Get the group that the traveller belongs to. */ public function group() { return $this->belongsTo(Group::class); } + + /** + * Get the groups the traveller is a member of through group_members + */ + public function groups() + { + return $this->belongsToMany(Group::class, 'group_members', 'traveller_id', 'group_id') + ->withTimestamps() + ->withPivot('joined_at'); + } /** * Check if traveller can manage groups (has a user with guide or admin role) @@ -126,6 +116,12 @@ public function joinGroup(Group $group) { if (!$group->isFull()) { $this->group_id = $group->id; + + // Also add to the group_members pivot table + if (!$this->groups->contains($group->id)) { + $this->groups()->attach($group->id, ['joined_at' => now()]); + } + return $this->save(); } return false; @@ -136,7 +132,16 @@ public function joinGroup(Group $group) */ public function leaveGroup() { - $this->group_id = null; - return $this->save(); + if ($this->group_id) { + $groupId = $this->group_id; + + // Remove from pivot table + $this->groups()->detach($groupId); + + // Remove direct group reference + $this->group_id = null; + return $this->save(); + } + return true; // Already not in a group } } diff --git a/app/Models/Trip.php b/app/Models/Trip.php index f49b1a0d..778242de 100644 --- a/app/Models/Trip.php +++ b/app/Models/Trip.php @@ -9,10 +9,59 @@ class Trip extends Model { use HasFactory; - protected $table = 'trips'; // <-- Zorg ervoor dat dit er staat! + protected $table = 'trips'; protected $fillable = [ 'name', 'contact_email', + 'description', + 'start_date', + 'end_date', ]; + + /** + * The attributes that should be cast. + * + * @var array + */ + protected $casts = [ + 'start_date' => 'date', + 'end_date' => 'date', + ]; + + /** + * Get all travellers for this trip + */ + public function travellers() + { + return $this->hasMany(Traveller::class); + } + + /** + * Get all groups for this trip + */ + public function groups() + { + return $this->hasMany(Group::class); + } + + /** + * Get remaining places on this trip + */ + public function getRemainingPlaces() + { + // Can be implemented if there's a max_travellers field + return null; + } + + /** + * Get duration in days + */ + public function getDurationInDays() + { + if ($this->start_date && $this->end_date) { + return $this->start_date->diffInDays($this->end_date) + 1; + } + return null; + } } diff --git a/app/Models/User.php b/app/Models/User.php index 04d9dfa8..6f33cfb9 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -2,24 +2,21 @@ namespace App\Models; -use App\Models\Traveller; -use Illuminate\Contracts\Auth\MustVerifyEmail; -use Illuminate\Contracts\Auth\CanResetPassword; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; -use Laravel\Sanctum\HasApiTokens; use App\Notifications\ResetPasswordNotification; use Illuminate\Support\Facades\Hash; +use Illuminate\Contracts\Auth\CanResetPassword; class User extends Authenticatable implements CanResetPassword { - use HasFactory, Notifiable; + use HasFactory, Notifiable; /** * The attributes that are mass assignable. * - * @var list + * @var array */ protected $fillable = [ 'login', @@ -30,7 +27,7 @@ class User extends Authenticatable implements CanResetPassword /** * The attributes that should be hidden for serialization. * - * @var list + * @var array */ protected $hidden = [ 'password', @@ -38,17 +35,14 @@ class User extends Authenticatable implements CanResetPassword ]; /** - * Get the attributes that should be cast. + * The attributes that should be cast. * - * @return array + * @var array */ - protected function casts(): array - { - return [ - 'email_verified_at' => 'datetime', - 'password' => 'hashed', - ]; - } + protected $casts = [ + 'email_verified_at' => 'datetime', + 'password' => 'hashed', + ]; /** * Get the traveller associated with the user. @@ -58,6 +52,46 @@ public function traveller() return $this->hasOne(Traveller::class); } + /** + * Get groups created by this user + */ + public function createdGroups() + { + return $this->hasMany(Group::class, 'created_by'); + } + + /** + * Get the group associated with this user's traveller profile + */ + public function getGroup() + { + return $this->traveller ? $this->traveller->group : null; + } + + /** + * Check if user can manage groups (is a guide or admin) + */ + public function canManageGroups() + { + return in_array($this->role, ['guide', 'admin']); + } + + /** + * Get the trip associated with this user through traveller + */ + public function getTrip() + { + return $this->traveller ? $this->traveller->trip : null; + } + + /** + * Get trip ID associated with this user + */ + public function getTripId() + { + return $this->traveller ? $this->traveller->trip_id : null; + } + /** * Get the email address used for password resets. * diff --git a/database/migrations/2023_01_01_000002_create_group_members_table.php b/database/migrations/2023_01_01_000002_create_group_members_table.php new file mode 100644 index 00000000..f08ed337 --- /dev/null +++ b/database/migrations/2023_01_01_000002_create_group_members_table.php @@ -0,0 +1,44 @@ +id(); + $table->unsignedBigInteger('group_id'); + $table->unsignedBigInteger('traveller_id'); // Changed from user_id to traveller_id + $table->timestamp('joined_at')->useCurrent(); + $table->timestamps(); + + $table->unique(['group_id', 'traveller_id']); + + // Add foreign key constraints if tables exist + if (Schema::hasTable('groups')) { + $table->foreign('group_id')->references('id')->on('groups')->onDelete('cascade'); + } + + if (Schema::hasTable('travellers')) { + $table->foreign('traveller_id')->references('id')->on('travellers')->onDelete('cascade'); + } + }); + } + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('group_members'); + } +}; diff --git a/database/migrations/2025_03_17_120000_create_trips_table.php b/database/migrations/2025_03_17_120000_create_trips_table.php index ba0d9f50..a3d0e336 100644 --- a/database/migrations/2025_03_17_120000_create_trips_table.php +++ b/database/migrations/2025_03_17_120000_create_trips_table.php @@ -14,7 +14,11 @@ public function up(): void Schema::create('trips', function (Blueprint $table) { $table->id(); $table->string('name'); + $table->text('description')->nullable(); $table->string('contact_email'); + $table->string('contact_phone')->nullable(); + $table->date('start_date'); + $table->date('end_date'); $table->timestamps(); }); } diff --git a/database/migrations/2025_03_18_130000_create_groups_table.php b/database/migrations/2025_03_18_130000_create_groups_table.php index c8f9cd0e..b5f07f9c 100644 --- a/database/migrations/2025_03_18_130000_create_groups_table.php +++ b/database/migrations/2025_03_18_130000_create_groups_table.php @@ -11,13 +11,27 @@ */ public function up(): void { - Schema::create('groups', function (Blueprint $table) { - $table->id(); - $table->foreign('id')->references('id')->on('trips')->onDelete('cascade'); - $table->string('name')->unique(); - $table->integer('max_members')->length(10); - $table->timestamps(); - }); + // Check if the table already exists + if (!Schema::hasTable('groups')) { + Schema::create('groups', function (Blueprint $table) { + $table->id(); + $table->string('name'); + $table->text('description')->nullable(); + $table->unsignedBigInteger('trip_id'); // Changed from foreignId to avoid immediate constraints + $table->unsignedBigInteger('created_by'); // Changed from foreignId + $table->integer('max_members')->default(10); + $table->timestamps(); + + // Add foreign key constraints after table creation + if (Schema::hasTable('trips')) { + $table->foreign('trip_id')->references('id')->on('trips')->onDelete('cascade'); + } + + if (Schema::hasTable('users')) { + $table->foreign('created_by')->references('id')->on('users')->onDelete('cascade'); + } + }); + } } /** diff --git a/database/migrations/2025_03_18_140038_create_travellers.php b/database/migrations/2025_03_18_140038_create_travellers.php index e973f2ae..a468ca5d 100644 --- a/database/migrations/2025_03_18_140038_create_travellers.php +++ b/database/migrations/2025_03_18_140038_create_travellers.php @@ -21,7 +21,7 @@ public function up(): void $table->foreign('zip_id')->references('id')->on('cities')->onDelete('cascade'); $table->unsignedBigInteger('group_id')->length(10)->nullable(); $table->foreign('group_id')->references('id')->on('groups')->onDelete('cascade'); - $table->integer('major_id')->length(10); + $table->integer('major_id')->length(10)->nullable(); $table->string('first_name'); $table->string('last_name'); $table->string('email'); @@ -30,14 +30,14 @@ public function up(): void $table->string('gender'); $table->string('phone'); $table->string('emergency_phone_1'); - $table->string('emergency_phone_2'); + $table->string('emergency_phone_2')->nullable(); $table->string('nationality'); $table->date('birthdate'); $table->string('birthplace'); $table->string('iban'); $table->string('bic'); - $table->tinyInteger('medical_issue'); - $table->string('medical_info'); + $table->tinyInteger('medical_issue')->nullable(); + $table->string('medical_info')->nullable(); $table->string('remember_token', 100)->nullable(); $table->timestamps(); }); diff --git a/database/seeders/CreateGroupMembersSeeder.php b/database/seeders/CreateGroupMembersSeeder.php new file mode 100644 index 00000000..8c349a19 --- /dev/null +++ b/database/seeders/CreateGroupMembersSeeder.php @@ -0,0 +1,67 @@ +isEmpty()) { + $this->command->info('No groups found, skipping member assignments'); + return; + } + + // Get all travellers + $allTravellers = Traveller::whereHas('user')->get(); + + if ($allTravellers->isEmpty()) { + $this->command->info('No travellers found, skipping member assignments'); + return; + } + + $assignedCount = 0; + + foreach ($groups as $group) { + // Get traveller users for this trip who can join the group + $travellers = $allTravellers->where('trip_id', $group->trip_id); + + if ($travellers->isEmpty()) { + continue; + } + + // Add 3-6 random travelers to each group, respecting the max_members limit + $memberCount = min(count($travellers), rand(3, min(6, $group->max_members))); + $randomTravellers = $travellers->random($memberCount); + + foreach ($randomTravellers as $traveller) { + // Insert directly into group_members table + DB::table('group_members')->insertOrIgnore([ + 'group_id' => $group->id, + 'traveller_id' => $traveller->id, + 'joined_at' => now(), + 'created_at' => now(), + 'updated_at' => now() + ]); + + // Also update the traveller's group_id for direct relationship + $traveller->update(['group_id' => $group->id]); + + $assignedCount++; + } + } + + $this->command->info("Assigned $assignedCount travellers to groups successfully"); + } +} diff --git a/database/seeders/CreateGroupsSeeder.php b/database/seeders/CreateGroupsSeeder.php index c43043d0..5577f66d 100644 --- a/database/seeders/CreateGroupsSeeder.php +++ b/database/seeders/CreateGroupsSeeder.php @@ -2,10 +2,11 @@ namespace Database\Seeders; -use App\Models\Group; -use Illuminate\Database\Console\Seeds\WithoutModelEvents; use Illuminate\Database\Seeder; - +use App\Models\Group; +use App\Models\User; +use App\Models\Trip; +use App\Models\Traveller; class CreateGroupsSeeder extends Seeder { @@ -14,14 +15,64 @@ class CreateGroupsSeeder extends Seeder */ public function run(): void { - $groups = [ - ['name' => 'Group A', 'max_members' => 10], - ['name' => 'Group B', 'max_members' => 15], - ['name' => 'Group C', 'max_members' => 20], - ]; + // Get all trips to create groups for each + $trips = Trip::all(); + + if ($trips->isEmpty()) { + $this->command->info('No trips found, skipping group creation'); + return; + } + + // Get guide/admin users who can create groups + $guides = User::whereIn('role', ['guide', 'admin'])->get(); + + if ($guides->isEmpty()) { + $this->command->info('No guides found, skipping group creation'); + return; + } - foreach ($groups as $group) { - Group::create($group); + // Create groups for each trip + foreach ($trips as $trip) { + // Randomly assign a guide for each trip's groups + $creator = $guides->random(); + + // Create 2-4 groups per trip with different configurations + $groupCount = rand(2, 4); + + for ($i = 1; $i <= $groupCount; $i++) { + $groupTheme = $this->getRandomGroupTheme(); + + Group::create([ + 'name' => "{$groupTheme} - {$trip->name}", + 'description' => "Een groep gefocust op {$groupTheme} tijdens de reis naar {$trip->name}.", + 'trip_id' => $trip->id, + 'created_by' => $creator->id, + 'max_members' => rand(5, 15) + ]); + } } + + $this->command->info('Created ' . ($groupCount * $trips->count()) . ' groups successfully'); + } + + /** + * Get a random interesting group theme + */ + private function getRandomGroupTheme() + { + $themes = [ + 'Cultuur & Geschiedenis', + 'Technologie & Innovatie', + 'Architectuur', + 'Duurzaamheid', + 'Lokale Industrie', + 'Smart Cities', + 'Gastronomie & Culinair', + 'Urban Engineering', + 'Moderne Kunst', + 'Transport & Mobiliteit' + ]; + + return $themes[array_rand($themes)]; } } diff --git a/database/seeders/CreateTravellersSeeder.php b/database/seeders/CreateTravellersSeeder.php new file mode 100644 index 00000000..6d6158b2 --- /dev/null +++ b/database/seeders/CreateTravellersSeeder.php @@ -0,0 +1,291 @@ +get(); + + if ($travellerUsers->isEmpty()) { + $this->command->info('No traveller users found, skipping traveller creation'); + // return; // Keep processing for guides + } + + // Get all trips + $trips = Trip::all(); + + if ($trips->isEmpty()) { + $this->command->info('No trips found, skipping traveller creation'); + return; + } + + // Get available city IDs from the database + $cityIds = Cities::pluck('id')->toArray(); + + if (empty($cityIds)) { + $this->command->info('No cities found in the database. Make sure to run the Cities seeder first.'); + return; + } + + // Get all majors for academic info + $majors = Major::all(); + $educations = Education::all(); + + if ($majors->isEmpty() || $educations->isEmpty()) { + $this->command->info('Missing majors or educations, skipping academic info for some travellers.'); + } + + // Create traveller profiles for each user + $createdCount = 0; + + foreach ($travellerUsers as $user) { + // Check if traveller already exists for this user + if (Traveller::where('user_id', $user->id)->exists()) { + continue; + } + + // Random first/last name generation + $firstName = $this->getRandomFirstName(); + $lastName = $this->getRandomLastName(); + + // Assign to random trip + $trip = $trips->random(); + + // Create traveller record + $traveller = new Traveller(); + $traveller->user_id = $user->id; + $traveller->trip_id = $trip->id; + // group_id can be null initially + $traveller->first_name = $firstName; + $traveller->last_name = $lastName; + $traveller->email = strtolower(str_replace(' ', '.', $firstName) . '.' . str_replace(' ', '', $lastName) . '@student.ucll.be'); + $traveller->country = $this->getRandomCountry(); + $traveller->address = $this->getRandomStreet() . ' ' . rand(1, 150); + + // Select a random city ID from actual existing cities + $traveller->zip_id = $cityIds[array_rand($cityIds)]; + + $traveller->gender = ['M', 'V', 'X'][rand(0, 2)]; + $traveller->phone = '04' . rand(10, 99) . rand(100000, 999999); + $traveller->emergency_phone_1 = '04' . rand(10, 99) . rand(100000, 999999); + $traveller->emergency_phone_2 = (rand(0, 1) ? '04' . rand(10, 99) . rand(100000, 999999) : null); + $traveller->nationality = $this->getRandomNationality(); + $traveller->birthdate = now()->subYears(rand(18, 25))->subDays(rand(0, 365)); + $traveller->birthplace = $this->getRandomBelgianCity(); + $traveller->iban = $this->generateFakeIban(); + $traveller->bic = $this->generateFakeBic(); + $traveller->medical_issue = (bool)rand(0, 1); + $traveller->medical_info = $traveller->medical_issue ? 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.' : null; + // remember_token is usually handled by Laravel auth, often nullable + + $education = $educations->random(); + // Ensure major belongs to the selected education if possible + $majorForEducation = $majors->where('education_id', $education->id); + $major = $majorForEducation->isNotEmpty() ? $majorForEducation->random() : $majors->random(); + $traveller->major_id = $major->id; + + $traveller->save(); + $createdCount++; + } + + // Also create travellers for guide users (they may need to be part of a group too) + $guideUsers = User::where('role', 'guide')->get(); + foreach ($guideUsers as $user) { + // Check if traveller already exists for this user + if (Traveller::where('user_id', $user->id)->exists()) { + continue; + } + + $trip = $trips->random(); + + $traveller = new Traveller(); + $traveller->user_id = $user->id; + $traveller->trip_id = $trip->id; + // group_id can be null initially + $traveller->first_name = 'Guide'; + $traveller->last_name = explode('@', $user->login)[0]; // Assuming login is email-like + $traveller->email = 'guide.' . $traveller->last_name . '@ucll.be'; + $traveller->country = 'Belgium'; + $traveller->address = $this->getRandomStreet() . ' ' . rand(1, 50); + + // Select a random city ID from actual existing cities + $traveller->zip_id = $cityIds[array_rand($cityIds)]; + + $traveller->gender = ['M', 'V', 'X'][rand(0, 2)]; + $traveller->phone = '04' . rand(10, 99) . rand(100000, 999999); + $traveller->emergency_phone_1 = '04' . rand(10, 99) . rand(100000, 999999); + // emergency_phone_2 can be null + $traveller->nationality = 'Belgian'; + $traveller->birthdate = now()->subYears(rand(25, 45))->subDays(rand(0, 365)); + $traveller->birthplace = $this->getRandomBelgianCity(); + $traveller->iban = $this->generateFakeIban(); + $traveller->bic = $this->generateFakeBic(); + $traveller->medical_issue = false; + // medical_info can be null + $traveller->major_id = null; // Set major_id to null for guides + + $traveller->save(); + $createdCount++; + } + + $this->command->info('Created ' . $createdCount . ' traveller profiles'); + } + + /** + * Get a random Belgian first name + */ + private function getRandomFirstName() { + $firstNames = [ + 'Emma', + 'Liam', + 'Olivia', + 'Noah', + 'Lucas', + 'Mila', + 'Louis', + 'Ella', + 'Adam', + 'Louise', + 'Lars', + 'Marie', + 'Jules', + 'Nora', + 'Leon', + 'Sofia', + 'Victor', + 'Camille', + 'Arthur', + 'Elena', + 'Thomas', + 'Lukas', + 'Anna', + 'Mathis', + 'Alice', + 'Finn', + 'Zoe', + 'Oscar', + 'Charlotte', + 'Felix', + 'Elise', + 'Samuel', + 'Laura', + 'Gabriel', + 'Luna', + 'Alexander', + 'Julia', + 'Milan', + 'Lily' + ]; + return $firstNames[array_rand($firstNames)]; + } + + /** + * Get a random Belgian last name + */ + private function getRandomLastName() { + $lastNames = [ + 'Peeters', + 'Janssens', + 'Maes', + 'Jacobs', + 'Mertens', + 'Willems', + 'Claes', + 'Goossens', + 'Wouters', + 'De Smet', + 'Dubois', + 'Lambert', + 'Dupont', + 'Martin', + 'Hendrickx', + 'Verhoeven', + 'Hermans', + 'Vermeulen', + 'Declercq', + 'Vandenberghe', + 'Verbeke', + 'Smets', + 'Verheyen', + 'Jansen', + 'Leroy', + 'Devos', + 'Desmet', + 'Lemmens', + 'Martens', + 'Verschueren', + 'De Backer', + 'Van Damme', + 'Michiels' + ]; + return $lastNames[array_rand($lastNames)]; + } + + /** + * Get a random Belgian street name + */ + private function getRandomStreet() { + $streets = [ + 'Kerkstraat', + 'Schoolstraat', + 'Molenstraat', + 'Dorpsstraat', + 'Stationstraat', + 'Nieuwstraat', + 'Hoogstraat', + 'Lindenlaan', + 'Berkenlaan', + 'Eikenstraat', + 'Veldstraat', + 'Boslaan', + 'Beekstraat', + 'Kasteelstraat', + 'Markt', + 'Rozenlaan', + 'Bergstraat', + 'Vijverstraat', + 'Akkerstraat', + 'Weidestraat' + ]; + return $streets[array_rand($streets)]; + } + + private function getRandomCountry() { + $countries = ['Belgium', 'Netherlands', 'France', 'Germany', 'Luxembourg']; + return $countries[array_rand($countries)]; + } + + private function getRandomNationality() { + $nationalities = ['Belgian', 'Dutch', 'French', 'German', 'Luxembourgish']; + return $nationalities[array_rand($nationalities)]; + } + + private function getRandomBelgianCity() { + $cities = ['Brussels', 'Antwerp', 'Ghent', 'Charleroi', 'Liège', 'Bruges', 'Namur', 'Leuven', 'Mons', 'Aalst']; + return $cities[array_rand($cities)]; + } + + private function generateFakeIban() { + // Basic fake IBAN generator (BE specific for simplicity) + return 'BE' . rand(10, 99) . ' ' . rand(1000, 9999) . ' ' . rand(1000, 9999) . ' ' . rand(1000, 9999); + } + + private function generateFakeBic() { + // Basic fake BIC generator + $banks = ['KRED', 'GEBA', 'BBRU', 'AXAB', 'HBKA']; + return $banks[array_rand($banks)] . 'BE' . rand(10, 99); + } +} diff --git a/database/seeders/CreateTripsSeeder.php b/database/seeders/CreateTripsSeeder.php index 22fabd7c..84ae7b2c 100644 --- a/database/seeders/CreateTripsSeeder.php +++ b/database/seeders/CreateTripsSeeder.php @@ -13,13 +13,40 @@ class CreateTripsSeeder extends Seeder public function run(): void { $trips = [ - ['name' => 'Spanje', 'contact_email' => 'techreizen@gmail.com'], - ['name' => 'Zwitserland', 'contact_email' => 'techreizen@gmail.com',], - ['name' => 'Frankrijk', 'contact_email' => 'techreizen@gmail.com',], + [ + 'name' => 'Barcelona Tech Tour', + 'contact_email' => 'barcelona@techreizen.edu', + 'description' => 'Een technologische reis naar Barcelona, met focus op smart city innovaties en mobile tech bedrijven.', + 'start_date' => '2025-04-15', + 'end_date' => '2025-04-22', + ], + [ + 'name' => 'Zürich Innovation Hub', + 'contact_email' => 'zurich@techreizen.edu', + 'description' => 'Verken de innovatieve technologische hub van Zürich, met bezoeken aan onderzoekscentra en startups.', + 'start_date' => '2025-05-10', + 'end_date' => '2025-05-17', + ], + [ + 'name' => 'Parijs Digital Campus', + 'contact_email' => 'paris@techreizen.edu', + 'description' => 'Ontdek de digitale campus van Parijs, met focus op AI ontwikkeling en digital media.', + 'start_date' => '2025-06-05', + 'end_date' => '2025-06-12', + ], + [ + 'name' => 'Berlin Tech Ecosystem', + 'contact_email' => 'berlin@techreizen.edu', + 'description' => 'Duik in het Berlijnse tech ecosysteem, met bezoeken aan innovatieve startups en tech hubs.', + 'start_date' => '2025-09-20', + 'end_date' => '2025-09-27', + ] ]; foreach ($trips as $trip) { Trip::create($trip); } + + $this->command->info('Created ' . count($trips) . ' trips successfully'); } } diff --git a/database/seeders/CreateUsersSeeder.php b/database/seeders/CreateUsersSeeder.php index b5537c72..f301a0e3 100644 --- a/database/seeders/CreateUsersSeeder.php +++ b/database/seeders/CreateUsersSeeder.php @@ -2,7 +2,6 @@ namespace Database\Seeders; -use Illuminate\Database\Console\Seeds\WithoutModelEvents; use Illuminate\Database\Seeder; use App\Models\User; @@ -13,15 +12,36 @@ class CreateUsersSeeder extends Seeder */ public function run(): void { - $users = [ - ['login' => 'Admin', 'role' => 'admin', 'password' => bcrypt('admin'),], - ['login' => 'Traveller', 'role' => 'traveller', 'password' => bcrypt('traveller'),], - ['login' => 'Guide', 'role' => 'guide', 'password' => bcrypt('guide'),], - ['login' => 'Guest', 'role' => 'guest', 'password' => bcrypt('guest'),], + // Admin and standard users + $baseUsers = [ + ['login' => 'admin', 'role' => 'admin', 'password' => bcrypt('admin')], + ['login' => 'guest', 'role' => 'guest', 'password' => bcrypt('guest')], ]; - - foreach ($users as $user) { + + // Create multiple guide users + $guideUsers = [ + ['login' => 'guide1', 'role' => 'guide', 'password' => bcrypt('guide')], + ['login' => 'guide2', 'role' => 'guide', 'password' => bcrypt('guide')], + ['login' => 'guide3', 'role' => 'guide', 'password' => bcrypt('guide')], + ]; + + // Create multiple traveller users + $travellerUsers = []; + for ($i = 1; $i <= 10; $i++) { + $travellerUsers[] = [ + 'login' => "traveller{$i}", + 'role' => 'traveller', + 'password' => bcrypt('traveller') + ]; + } + + // Combine all users and create them + $allUsers = array_merge($baseUsers, $guideUsers, $travellerUsers); + + foreach ($allUsers as $user) { User::create($user); } + + $this->command->info('Created ' . count($allUsers) . ' test users'); } } diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index 030ae48b..baf295ca 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -3,8 +3,8 @@ namespace Database\Seeders; use App\Models\User; -// use Illuminate\Database\Console\Seeds\WithoutModelEvents; use Illuminate\Database\Seeder; +use Illuminate\Support\Facades\Artisan; class DatabaseSeeder extends Seeder { @@ -20,8 +20,9 @@ public function run(): void CreateEducationsSeeder::class, CreateMajorsSeeder::class, CreateCitiesSeeder::class, + CreateTravellersSeeder::class, CreateGroupsSeeder::class, - // Add other seeders here if needed + CreateGroupMembersSeeder::class, ]); // User::factory(10)->create(); diff --git a/package.json b/package.json index 575138fd..9de787d2 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,9 @@ "type": "module", "scripts": { "build": "vite build", - "dev": "vite" + "dev": "vite", + "tailwind:build": "npx tailwindcss -i ./resources/css/app.css -o ./public/css/app.css --minify", + "build:css": "npx tailwindcss -i ./resources/css/app.css -o ./public/css/app.css --minify" }, "devDependencies": { "@popperjs/core": "^2.11.6", diff --git a/resources/views/groups/create.blade.php b/resources/views/groups/create.blade.php new file mode 100644 index 00000000..b1e8d97c --- /dev/null +++ b/resources/views/groups/create.blade.php @@ -0,0 +1,60 @@ +@extends('layouts.app') + +@section('content') +
+ + +
+
+

Nieuwe Groep Aanmaken

+

Maak een nieuwe groep voor deze reis

+
+ +
+ @if ($errors->any()) +
+

Er zijn fouten gevonden:

+
    + @foreach ($errors->all() as $error) +
  • {{ $error }}
  • + @endforeach +
+
+ @endif + + + @csrf + +
+ + +
+ +
+ + +

Optioneel. Geef een korte beschrijving van de groep.

+
+ +
+ + +
+ +
+ +
+ +
+
+
+@endsection diff --git a/resources/views/groups/edit.blade.php b/resources/views/groups/edit.blade.php new file mode 100644 index 00000000..57be9fd0 --- /dev/null +++ b/resources/views/groups/edit.blade.php @@ -0,0 +1,57 @@ +@extends('layouts.app') + +@section('content') +
+
+
+
+

Groep Bewerken

+

{{ $group->name }}

+
+ +
+ @if ($errors->any()) + + @endif + +
+ @csrf + @method('PUT') + +
+ + +
+ +
+ + +
+ +
+ + +

Kan alleen verhoogd worden als er al leden in deze groep zitten.

+
+ +
+ + Annuleren + + +
+
+
+
+
+
+@endsection diff --git a/resources/views/groups/index.blade.php b/resources/views/groups/index.blade.php new file mode 100644 index 00000000..64f883f3 --- /dev/null +++ b/resources/views/groups/index.blade.php @@ -0,0 +1,150 @@ +@extends('layouts.app') + +@section('content') +
+ +
+
+
+

+ @if($isGuide) + Groepen Beheren + @else + Mijn Groepen + @endif +

+

+ @if($isGuide) + Bekijk en beheer alle groepen voor deze reis + @else + Bekijk je groepen en vind nieuwe groepen om aan deel te nemen + @endif +

+
+ + @if($isGuide) + + @endif +
+
+ + @if (session('success')) + + @endif + + @if (session('error')) + + @endif + + @if($isGuide) + +
+ + + + + + + + + + + @forelse ($groups as $group) + + + + + + + @empty + + + + @endforelse + +
NaamBeschrijvingMax LedenActies
+
{{ $group->name }}
+
+
{{ Str::limit($group->description, 50) }}
+
+
{{ $group->max_members }}
+
+ Bekijken +
+ Er zijn nog geen groepen aangemaakt voor deze reis. +
+
+ @else + +
+

Mijn Groepen

+ + @if(count($joinedGroups) > 0) +
+ @foreach($joinedGroups as $group) +
+
+

{{ $group->name }}

+
+
+

{{ Str::limit($group->description, 100) }}

+
+ + {{ $group->max_members }} maximale leden + + + Details + +
+
+
+ @endforeach +
+ @else +
+

Je bent nog geen lid van een groep.

+
+ @endif +
+ +
+

Beschikbare Groepen

+ + @if(count($availableGroups) > 0) +
+ @foreach($availableGroups as $group) +
+
+

{{ $group->name }}

+
+
+

{{ Str::limit($group->description, 100) }}

+
+ + {{ $group->max_members }} maximale leden + + + Details + +
+
+
+ @endforeach +
+ @else +
+

Er zijn geen beschikbare groepen om lid van te worden.

+
+ @endif +
+ @endif +
+@endsection diff --git a/resources/views/groups/show.blade.php b/resources/views/groups/show.blade.php new file mode 100644 index 00000000..6bf6f202 --- /dev/null +++ b/resources/views/groups/show.blade.php @@ -0,0 +1,88 @@ +@extends('layouts.app') + +@section('content') +
+ + +
+
+

{{ $group->name }}

+

Max. {{ $group->max_members }} leden

+
+ +
+
+

Beschrijving

+

{{ $group->description ?: 'Geen beschrijving beschikbaar.' }}

+
+ +
+ +
+

Groepsleden

+ + @if(isset($group->members) && count($group->members) > 0) +
+ @foreach($group->members as $member) +
+
+ +
+
+

{{ $member->first_name }} {{ $member->last_name }}

+ @if($isGuide) +

{{ $member->email }}

+ @endif +
+
+ @endforeach +
+ @else +

Deze groep heeft nog geen leden.

+ @endif +
+ + @if(!$isGuide) +
+ @if(!$isMember) +
+ @csrf + +
+ @else +
+ @csrf + @method('DELETE') + +
+ @endif +
+ @endif + + @if($isGuide) +
+ + Bewerken + + +
+ @csrf + @method('DELETE') + +
+
+ @endif +
+
+
+@endsection diff --git a/resources/views/guide/groups/index.blade.php b/resources/views/guide/groups/index.blade.php new file mode 100644 index 00000000..c63b512b --- /dev/null +++ b/resources/views/guide/groups/index.blade.php @@ -0,0 +1,150 @@ +@extends('layouts.app') + +@section('content') +
+ +
+
+
+

Groepen Beheren

+

Beheer alle groepen voor deze reis

+
+ +
+
+ +
+ + @if (session('success')) + + @endif + + @if (session('error')) + + @endif + + +
+
+

Groepen Overzicht

+
+ + + Beheer en organiseer alle groepen voor deze reis + +
+
+ + @if ($managedGroups->isEmpty()) +
+
+ +
+

Geen groepen beschikbaar

+

+ Er zijn nog geen groepen aangemaakt voor deze reis. +

+ + Nieuwe Groep Aanmaken + +
+ @else +
+
+ + + + + + + + + + + + + @foreach ($managedGroups as $group) + + + + + + + + + @endforeach + +
NaamLedenActies
+
{{ $group->name }}
+
+ {{ Str::limit($group->description, 30) }} +
+
+
+ + {{ $group->getMemberCount() }}/{{ $group->max_members }} + + + @if($group->getMemberCount() >= $group->max_members) + Vol + @endif +
+
+
+ + + Bekijken + + + + Bewerken + +
+ @csrf + @method('DELETE') + +
+
+
+
+
+ @endif +
+
+
+@endsection diff --git a/resources/views/guide/groups/show.blade.php b/resources/views/guide/groups/show.blade.php new file mode 100644 index 00000000..4c5982b4 --- /dev/null +++ b/resources/views/guide/groups/show.blade.php @@ -0,0 +1,262 @@ +@extends('layouts.app') + +@section('content') +
+ +
+
+
+
+

{{ $group->name }}

+ + {{ $group->trip->name }} + +
+

Groep beheer en ledenlijst

+
+ +
+
+ +
+ + @if (session('success')) + + @endif + + @if (session('error')) + + @endif + + +
+

Groepsinformatie

+ +
+
+
+

Beschrijving

+

{{ $group->description ?: 'Geen beschrijving beschikbaar' }}

+
+
+
+

Leden

+
+
+
+
+ + {{ $group->getMemberCount() }}/{{ $group->max_members }} + + ({{ min(100, ($group->getMemberCount() / $group->max_members) * 100) }}%) + + +
+
+
+

Aangemaakt door

+
+
+ +
+ {{ $group->creator->name ?? 'Onbekend' }} +
+
+
+
+
+
+ + +
+
+

Groepsleden beheren

+
+ +
+
+ + @if ($members->isEmpty()) +
+
+ +
+

Geen groepsleden

+

+ Deze groep heeft nog geen leden. Studenten kunnen zelf lid worden van deze groep. +

+
+ @else +
+ @csrf + @method('DELETE') + +
+
+ + + + + + + + + + + + @foreach ($members as $member) + + + + + + + + @endforeach + +
+ + NaamActies
+ @if (!($member->user && $member->user->canManageGroups())) + + @endif + +
+
+ +
+
+
{{ $member->first_name }} {{ $member->last_name }}
+ @if ($member->user && $member->user->canManageGroups()) +
Begeleider
+ @endif +
{{ $member->email }}
+
+
+
+ @if (!($member->user && $member->user->canManageGroups())) + + @else + + Begeleider + + @endif +
+
+
+ +
+ +
+
+ @endif +
+
+ + + +
+ +@push('scripts') + +@endpush +@endsection diff --git a/resources/views/layouts/app.blade.php b/resources/views/layouts/app.blade.php index fe171568..6d868722 100644 --- a/resources/views/layouts/app.blade.php +++ b/resources/views/layouts/app.blade.php @@ -14,126 +14,247 @@ - - @php -$viteManifestExists = file_exists(public_path('build/manifest.json')); - @endphp + + + + + - @if($viteManifestExists) - @vite(['resources/sass/app.scss', 'resources/js/app.js']) - @else - - - - - @endif - + + + + + + + + -
-
-
logo ucll
-
-
-

TECHNOLOGIE

-
-
-

internationalisering - studiereizen

-
+ +
+
+
+ logo ucll +
+
+

TECHNOLOGIE

+

internationalisering - studiereizen

-
- +
+ -
- @yield('content') -
-
+
+ @yield('content') +
@stack('scripts') - diff --git a/routes/web.php b/routes/web.php index 211307d5..d9daed2e 100644 --- a/routes/web.php +++ b/routes/web.php @@ -5,6 +5,7 @@ use App\Http\Controllers\HomeController; use App\Http\Controllers\GuestRegistrationController; use App\Http\Controllers\api\CityController; +use App\Http\Controllers\GroupController; Route::get('/', [HomeController::class, 'index'])->name('home'); @@ -68,11 +69,15 @@ -------------------------------------------- --------------------------------------------*/ Route::middleware(['auth', 'user-access:traveller'])->group(function () { - Route::get('/traveller/home', [HomeController::class, 'travellerHome'])->name('traveller.home'); - Route::get('/traveller/groups', function () { - return view('auth.traveller.groups'); - })->name('traveller.groups'); + + // Group routes for travelers + Route::prefix('traveller')->group(function () { + Route::get('/groups', [GroupController::class, 'index'])->name('groups.index'); + Route::get('/groups/{group}', [GroupController::class, 'show'])->name('groups.show'); + Route::post('/groups/{group}/join', [GroupController::class, 'join'])->name('groups.join'); + Route::delete('/groups/{group}/leave', [GroupController::class, 'leave'])->name('groups.leave'); + }); }); /*------------------------------------------ @@ -81,8 +86,19 @@ -------------------------------------------- --------------------------------------------*/ Route::middleware(['auth', 'user-access:guide'])->group(function () { - Route::get('/guide/home', [HomeController::class, 'guideHome'])->name('guide.home'); + + // Guide uses the same groups.index route as travelers for consistency + Route::get('/guide/groups', [GroupController::class, 'index'])->name('guide.groups.index'); + + // Group management routes for guides (create/edit/delete) + Route::get('/guide/groups/create', [GroupController::class, 'create'])->name('groups.create'); + Route::post('/guide/groups', [GroupController::class, 'store'])->name('groups.store'); + Route::get('/guide/groups/{group}', [GroupController::class, 'show'])->name('guide.groups.show'); + Route::get('/guide/groups/{group}/edit', [GroupController::class, 'edit'])->name('groups.edit'); + Route::put('/guide/groups/{group}', [GroupController::class, 'update'])->name('groups.update'); + Route::delete('/guide/groups/{group}', [GroupController::class, 'destroy'])->name('groups.destroy'); + Route::delete('/guide/groups/{group}/traveller/{traveller}', [GroupController::class, 'removeTraveller'])->name('groups.remove-traveller'); }); /*------------------------------------------ diff --git a/vite.config.js b/vite.config.js index dbbf3330..ede29223 100644 --- a/vite.config.js +++ b/vite.config.js @@ -1,14 +1,29 @@ import { defineConfig } from 'vite'; import laravel from 'laravel-vite-plugin'; +import tailwindcss from 'tailwindcss'; +import autoprefixer from 'autoprefixer'; export default defineConfig({ plugins: [ laravel({ input: [ - 'resources/sass/app.scss', - 'resources/js/app.js', + 'resources/css/app.css', + 'resources/js/app.js' ], refresh: true, }), ], + resolve: { + alias: { + '@': '/resources', + }, + }, + css: { + postcss: { + plugins: [ + tailwindcss(), + autoprefixer(), + ], + }, + } }); From da1608c21e31af4a8eb358e6703f4a73a62ae66e Mon Sep 17 00:00:00 2001 From: undead2146 Date: Wed, 7 May 2025 22:22:00 +0200 Subject: [PATCH 18/30] Fix route & view for guide homepage --- resources/views/guide/home.blade.php | 18 ++++++++++++++++++ routes/web.php | 3 +++ 2 files changed, 21 insertions(+) diff --git a/resources/views/guide/home.blade.php b/resources/views/guide/home.blade.php index e6e48fad..b2d837d5 100644 --- a/resources/views/guide/home.blade.php +++ b/resources/views/guide/home.blade.php @@ -4,7 +4,25 @@
+ + +
+ +
{{ __('Dashboard') }}
diff --git a/routes/web.php b/routes/web.php index d9daed2e..aa1aba87 100644 --- a/routes/web.php +++ b/routes/web.php @@ -86,6 +86,9 @@ -------------------------------------------- --------------------------------------------*/ Route::middleware(['auth', 'user-access:guide'])->group(function () { + + Route::get('/', [HomeController::class, 'guideHome'])->name('guide.home'); + Route::get('/guide/home', [HomeController::class, 'guideHome'])->name('guide.home'); // Guide uses the same groups.index route as travelers for consistency From bc38267b62c1e169e93b909d5029765fa232434f Mon Sep 17 00:00:00 2001 From: undead2146 Date: Thu, 8 May 2025 14:23:05 +0200 Subject: [PATCH 19/30] Fix minor bugs --- app/Http/Controllers/HomeController.php | 5 +-- config/app.php | 2 +- routes/web.php | 42 ++++++++++++++----------- 3 files changed, 27 insertions(+), 22 deletions(-) diff --git a/app/Http/Controllers/HomeController.php b/app/Http/Controllers/HomeController.php index 28e24178..24140bf3 100644 --- a/app/Http/Controllers/HomeController.php +++ b/app/Http/Controllers/HomeController.php @@ -13,7 +13,8 @@ class HomeController extends Controller */ public function __construct() { - //$this->middleware('auth'); + $this->middleware('auth')->except(['index']); + } /** @@ -23,7 +24,7 @@ public function __construct() */ public function index() { - return view('home'); + return view('Home'); } /** diff --git a/config/app.php b/config/app.php index f4672673..30c0dcf0 100644 --- a/config/app.php +++ b/config/app.php @@ -13,7 +13,7 @@ | */ - 'name' => env('APP_NAME', 'Laravel'), + 'name' => 'Techreizen', /* |-------------------------------------------------------------------------- diff --git a/routes/web.php b/routes/web.php index aa1aba87..52505dd9 100644 --- a/routes/web.php +++ b/routes/web.php @@ -14,6 +14,18 @@ Auth::routes(); +// API Routes for city selection - moved outside auth middleware to be accessible +Route::prefix('api')->group(function () { + Route::get('/cities/search', [App\Http\Controllers\Api\CityController::class, 'search'])->name('api.cities.search'); + Route::post('/cities', [App\Http\Controllers\Api\CityController::class, 'store'])->name('api.cities.store'); +}); + +// Common group routes accessible to both travelers and guides +Route::middleware(['auth'])->group(function () { + Route::get('/groups', [GroupController::class, 'index'])->name('groups.index'); + Route::get('/groups/{group}', [GroupController::class, 'show'])->name('groups.show'); +}); + /*------------------------------------------ -------------------------------------------- All guest Routes List @@ -24,6 +36,8 @@ Route::get('/disclaimerPage', [HomeController::class, 'disclaimerPage'])->name('guest.disclaimer'); Route::post('/disclaimerPage', [HomeController::class, 'acceptDisclaimer'])->name('guest.disclaimer.accept'); + Route::get('/guest/home', [HomeController::class, 'index'])->name('welcome'); + // Guest Registration Routes Route::prefix('registration')->group(function () { // Step 1: Basic Info @@ -57,12 +71,6 @@ })->name('register'); }); -// API Routes for city selection - moved outside auth middleware to be accessible -Route::prefix('api')->group(function () { - Route::get('/cities/search', [App\Http\Controllers\Api\CityController::class, 'search'])->name('api.cities.search'); - Route::post('/cities', [App\Http\Controllers\Api\CityController::class, 'store'])->name('api.cities.store'); -}); - /*------------------------------------------ -------------------------------------------- All traveller Routes List @@ -71,13 +79,9 @@ Route::middleware(['auth', 'user-access:traveller'])->group(function () { Route::get('/traveller/home', [HomeController::class, 'travellerHome'])->name('traveller.home'); - // Group routes for travelers - Route::prefix('traveller')->group(function () { - Route::get('/groups', [GroupController::class, 'index'])->name('groups.index'); - Route::get('/groups/{group}', [GroupController::class, 'show'])->name('groups.show'); - Route::post('/groups/{group}/join', [GroupController::class, 'join'])->name('groups.join'); - Route::delete('/groups/{group}/leave', [GroupController::class, 'leave'])->name('groups.leave'); - }); + // Traveler-specific group actions + Route::post('/groups/{group}/join', [GroupController::class, 'join'])->name('groups.join'); + Route::delete('/groups/{group}/leave', [GroupController::class, 'leave'])->name('groups.leave'); }); /*------------------------------------------ @@ -87,21 +91,21 @@ --------------------------------------------*/ Route::middleware(['auth', 'user-access:guide'])->group(function () { - Route::get('/', [HomeController::class, 'guideHome'])->name('guide.home'); Route::get('/guide/home', [HomeController::class, 'guideHome'])->name('guide.home'); - // Guide uses the same groups.index route as travelers for consistency - Route::get('/guide/groups', [GroupController::class, 'index'])->name('guide.groups.index'); - - // Group management routes for guides (create/edit/delete) + // Guide-specific group management routes + Route::get('/guide/groups', [GroupController::class, 'guideIndex'])->name('guide.groups.index'); Route::get('/guide/groups/create', [GroupController::class, 'create'])->name('groups.create'); Route::post('/guide/groups', [GroupController::class, 'store'])->name('groups.store'); - Route::get('/guide/groups/{group}', [GroupController::class, 'show'])->name('guide.groups.show'); Route::get('/guide/groups/{group}/edit', [GroupController::class, 'edit'])->name('groups.edit'); Route::put('/guide/groups/{group}', [GroupController::class, 'update'])->name('groups.update'); Route::delete('/guide/groups/{group}', [GroupController::class, 'destroy'])->name('groups.destroy'); + Route::post('/guide/groups/{group}/add-member', [GroupController::class, 'addMember'])->name('groups.add-member'); Route::delete('/guide/groups/{group}/traveller/{traveller}', [GroupController::class, 'removeTraveller'])->name('groups.remove-traveller'); + + // Add toggle lock route + Route::post('/guide/groups/{group}/toggle-lock', [GroupController::class, 'toggleLock'])->name('groups.toggle-lock'); }); /*------------------------------------------ From b5970310822261223b7c1baf6abc269746ddd937 Mon Sep 17 00:00:00 2001 From: IDavidG <145495942+IDavidGI@users.noreply.github.com> Date: Thu, 8 May 2025 14:24:38 +0200 Subject: [PATCH 20/30] Update web.php --- routes/web.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/routes/web.php b/routes/web.php index 52505dd9..2ceaddaa 100644 --- a/routes/web.php +++ b/routes/web.php @@ -95,9 +95,10 @@ Route::get('/guide/home', [HomeController::class, 'guideHome'])->name('guide.home'); // Guide-specific group management routes - Route::get('/guide/groups', [GroupController::class, 'guideIndex'])->name('guide.groups.index'); + Route::get('/guide/groups', [GroupController::class, 'index'])->name('guide.groups.index'); Route::get('/guide/groups/create', [GroupController::class, 'create'])->name('groups.create'); Route::post('/guide/groups', [GroupController::class, 'store'])->name('groups.store'); + Route::get('/guide/groups/{group}', [GroupController::class, 'show'])->name('guide.groups.show'); Route::get('/guide/groups/{group}/edit', [GroupController::class, 'edit'])->name('groups.edit'); Route::put('/guide/groups/{group}', [GroupController::class, 'update'])->name('groups.update'); Route::delete('/guide/groups/{group}', [GroupController::class, 'destroy'])->name('groups.destroy'); From 800fe7b198b0de1a074947a373d19c60793a97bc Mon Sep 17 00:00:00 2001 From: lucasvsw Date: Thu, 8 May 2025 14:35:55 +0200 Subject: [PATCH 21/30] Updates Check op r u of b nummer voor registratie van opleiding Nationaliteit verplicht --- .../GuestRegistrationController.php | 42 +- .../views/auth/passwords/email.blade.php | 4 +- .../views/components/city-selector.blade.php | 5 +- .../guest/registration/basic-info.blade.php | 102 +++-- .../guest/registration/contact-info.blade.php | 269 +++++++------ .../registration/personal-info.blade.php | 366 +++++++++--------- 6 files changed, 413 insertions(+), 375 deletions(-) diff --git a/app/Http/Controllers/GuestRegistrationController.php b/app/Http/Controllers/GuestRegistrationController.php index 931975f3..c35a561a 100644 --- a/app/Http/Controllers/GuestRegistrationController.php +++ b/app/Http/Controllers/GuestRegistrationController.php @@ -76,20 +76,39 @@ public function showBasicInfoForm(Request $request) // Submit Basic Info Form public function submitBasicInfo(Request $request) { - // Use the validation service to validate the form - $validation = $this->formValidator->validate(request: $request, formType: 'basicInfo'); + // Haal het studentnummer op + $studentNumber = $request->input('student_number'); + + // Bepaal of de velden verplicht zijn op basis van het studentnummer + $rules = [ + 'trip' => 'required|string', + 'student_number' => 'required|string|regex:/^[rub]\d{7}$/i', + ]; + + if (Str::startsWith(strtolower($studentNumber), 'r')) { + // Voor studenten zijn opleiding en afstudeerrichting verplicht + $rules['education'] = 'required|exists:educations,id'; + $rules['major'] = 'required|string'; + } else { + // Voor leraren/begeleiders zijn deze velden optioneel + $rules['education'] = 'nullable|exists:educations,id'; + $rules['major'] = 'nullable|string'; + } - if (!$validation['success']) { - return back() - ->withErrors($validation['validator']) - ->withInput(); + // Valideer de invoer + $validation = $request->validate($rules); + + // Voeg standaardwaarden toe voor 'education' en 'major' als het een "u" of "b" nummer is + if (Str::startsWith(strtolower($studentNumber), ['u', 'b'])) { + $validation['education'] = null; + $validation['major'] = null; } - // Get current session data and update it with validated data + // Haal de huidige sessiegegevens op en werk deze bij $registration = $request->session()->get(self::SESSION_KEY, []); - $registration = array_merge($registration, $validation['validated'], ['step' => 2]); + $registration = array_merge($registration, $validation, ['step' => 2]); - // Save updated data back to session + // Sla de bijgewerkte gegevens op in de sessie $request->session()->put(self::SESSION_KEY, $registration); return redirect()->route('guest.registration.personal-info'); @@ -222,9 +241,9 @@ public function submitConfirmation(Request $request) // Generate a secure random password $password = Str::random(10); - // Determine role based on student number prefix (U = guide, others = traveller) + // Determine role based on student number prefix (R, U, B = traveller, others = guide) $studentNumber = $registration['student_number']; - $role = (Str::startsWith(strtoupper($studentNumber), 'U')) ? 'guide' : 'traveller'; + $role = (Str::startsWith(strtoupper($studentNumber), [ 'U', 'B'])) ? 'guide' : 'traveller'; // Create user with the fields that exist in the users table $userData = [ @@ -242,6 +261,7 @@ public function submitConfirmation(Request $request) $shouldSendEmail = true; } + // Find city record using city name and postal code $cityName = $registration['city'] ?? null; $postalCode = $registration['postcode'] ?? null; diff --git a/resources/views/auth/passwords/email.blade.php b/resources/views/auth/passwords/email.blade.php index 8ebc06fa..d63aac6f 100644 --- a/resources/views/auth/passwords/email.blade.php +++ b/resources/views/auth/passwords/email.blade.php @@ -14,13 +14,13 @@
@endif -

Vul uw studentnummer in, en wij sturen een wachtwoord herstellink naar het e-mailadres dat aan uw account is gekoppeld.

+

Vul uw login nummer in, en wij sturen een wachtwoord herstellink naar het e-mailadres dat aan uw account is gekoppeld.

@csrf
- +
diff --git a/resources/views/components/city-selector.blade.php b/resources/views/components/city-selector.blade.php index c4cb5419..05f91105 100644 --- a/resources/views/components/city-selector.blade.php +++ b/resources/views/components/city-selector.blade.php @@ -44,10 +44,7 @@ class="clear-btn"
- - +
diff --git a/resources/views/guest/registration/basic-info.blade.php b/resources/views/guest/registration/basic-info.blade.php index 36ca44ca..c147303e 100644 --- a/resources/views/guest/registration/basic-info.blade.php +++ b/resources/views/guest/registration/basic-info.blade.php @@ -43,10 +43,10 @@
- +
+ name="student_number" value="{{ old('student_number', $registration->student_number) }}" required> @error('student_number') {{ $message }} @@ -57,46 +57,49 @@
-
- -
- - @error('education') - - {{ $message }} - - @enderror + + -
- -
- - @error('major') - - {{ $message }} - - @enderror +
+ +
+ + @error('major') + + {{ $message }} + + @enderror +
- +
+ +
diff --git a/resources/views/guest/registration/contact-info.blade.php b/resources/views/guest/registration/contact-info.blade.php index 2463d75f..06808b3a 100644 --- a/resources/views/guest/registration/contact-info.blade.php +++ b/resources/views/guest/registration/contact-info.blade.php @@ -1,174 +1,169 @@ @extends('layouts.app') @section('content') -
-
-
-
-
-
- {{ __('Registratie - Contactgegevens') }} +
+
+
+
+
+
+ {{ __('Registratie - Contactgegevens') }} +
-
-
- +
+ -
- @csrf + + @csrf -
-
{{ __('Contactgegevens') }}
-
+
+
{{ __('Contactgegevens') }}
+
-
- -
- @if (str_starts_with(session('guest_registration.student_number'), 'b')) - - @else - - @endif - @error('email') - - {{ $message }} - - @enderror +
+ +
+ + @error('email') + + {{ $message }} + + @enderror +
-
- @if (str_starts_with(session('guest_registration.student_number'), 'r')) -
- -
- - @if ($errors->has('secondary_email')) - - {{ $errors->first('secondary_email') }} - - @endif + @if (str_starts_with(session('guest_registration.student_number'), 'r')) +
+ +
+ + @if ($errors->has('secondary_email')) + + {{ $errors->first('secondary_email') }} + + @endif +
-
- @endif - -
- -
- - @error('phone') - - {{ $message }} - - @enderror + @endif + +
+ +
+ + @error('phone') + + {{ $message }} + + @enderror +
-
-
- -
- - @error('emergency_contact') - - {{ $message }} - - @enderror +
+ +
+ + @error('emergency_contact') + + {{ $message }} + + @enderror +
-
-
- -
- - @error('optional_emergency_contact') - - {{ $message }} - - @enderror +
+ +
+ + @error('optional_emergency_contact') + + {{ $message }} + + @enderror +
-
-
-
{{ __('Medische Gegevens') }}
-
+
+
{{ __('Medische Gegevens') }}
+
-
- -
-
- medical_details ?? '') ? '' : 'checked') }}> - -
-
- medical_details ?? '') ? 'checked' : '') }}> - +
+ +
+
+ medical_details ?? '') ? '' : 'checked') }}> + +
+
+ medical_details ?? '') ? 'checked' : '') }}> + +
-
-
- -
- - @error('medical_details') - - {{ $message }} - - @enderror +
+ +
+ + @error('medical_details') + + {{ $message }} + + @enderror +
-
-
-
- - {{ __('Vorige') }} - - +
+
+ + {{ __('Vorige') }} + + +
-
- + +
-
- + medicalInfoYes.addEventListener('change', toggleMedicalDetails); + medicalInfoNo.addEventListener('change', toggleMedicalDetails); + }); + @endsection diff --git a/resources/views/guest/registration/personal-info.blade.php b/resources/views/guest/registration/personal-info.blade.php index eea28b06..59d0d7a7 100644 --- a/resources/views/guest/registration/personal-info.blade.php +++ b/resources/views/guest/registration/personal-info.blade.php @@ -1,223 +1,223 @@ @extends('layouts.app') @section('content') -
-
-
-
-
-
- {{ __('Registratie - Persoonlijke Informatie') }} -
+
+
+
+
+
+
+ {{ __('Registratie - Persoonlijke Informatie') }}
+
-
- +
+ -
- @csrf + + @csrf -
-
{{ __('Persoonlijke Informatie') }}
-
+
+
{{ __('Persoonlijke Informatie') }}
+
-
- -
- - @error('first_name') - - {{ $message }} - - @enderror -
+
+ +
+ + @error('first_name') + + {{ $message }} + + @enderror
+
-
- -
- - @error('last_name') - - {{ $message }} - - @enderror -
+
+ +
+ + @error('last_name') + + {{ $message }} + + @enderror
+
-
- -
- - @error('gender') - - {{ $message }} - - @enderror -
+
+ +
+ + @error('gender') + + {{ $message }} + + @enderror
+
-
- -
- - @error('date_of_birth') - - {{ $message }} - - @enderror -
+
+ +
+ + @error('date_of_birth') + + {{ $message }} + + @enderror
+
-
- -
- - @error('place_of_birth') - - {{ $message }} - - @enderror -
+
+ +
+ + @error('place_of_birth') + + {{ $message }} + + @enderror
+
-
- -
- -
+
+ +
+
-
-
{{ __('Adres Informatie') }}
-
-
- -
- - @error('address') - - {{ $message }} - - @enderror -
+
+
+
{{ __('Adres Informatie') }}
+
+
+ +
+ + @error('address') + + {{ $message }} + + @enderror
+
-
- -
-
- -
-
Zoek een gemeente of voeg er een toe
- - @error('city') - - {{ $message }} - - @enderror +
+ +
+
+
+
Zoek een gemeente of voeg er een toe
+ + @error('city') + + {{ $message }} + + @enderror
+
- -
- -
- - @error('country') - - {{ $message }} - - @enderror -
+ +
+ +
+ + @error('country') + + {{ $message }} + + @enderror
+
-
-
- - {{ __('Vorige') }} - - -
+
+
+ + {{ __('Vorige') }} + +
- -
+
+
+
- -