diff --git a/src/BusProNet/Model/Address.php b/src/BusProNet/Model/Address.php index a6a409d..85bb5d3 100644 --- a/src/BusProNet/Model/Address.php +++ b/src/BusProNet/Model/Address.php @@ -4,6 +4,8 @@ declare(strict_types=1); namespace App\BusProNet\Model; +use Symfony\Component\Validator\Constraints as Assert; + /** * Represents a physical address with street, postal code, city, and country information. * @@ -13,14 +15,18 @@ namespace App\BusProNet\Model; */ class Address { + #[Assert\NotBlank(message: 'Bitte angeben', groups: ['personal_data'])] public ?string $street = null; + #[Assert\NotBlank(message: 'Bitte angeben', groups: ['personal_data'])] public ?string $postCode = null; + #[Assert\NotBlank(message: 'Bitte angeben', groups: ['personal_data'])] public ?string $city = null; public ?string $district = null; + #[Assert\NotBlank(message: 'Bitte angeben', groups: ['personal_data'])] public ?string $country = null; /** diff --git a/src/BusProNet/Model/PersonalData.php b/src/BusProNet/Model/PersonalData.php index 881fdcb..57458de 100644 --- a/src/BusProNet/Model/PersonalData.php +++ b/src/BusProNet/Model/PersonalData.php @@ -31,11 +31,18 @@ class PersonalData public ?string $salutation = null; public ?string $title = null; + + #[Assert\NotBlank(message: 'Bitte angeben', groups: ['personal_data'])] + #[Assert\Choice(choices: ['M', 'W', 'D'], message: 'Bitte gib einen gültigen Wert an', groups: ['personal_data'])] public ?string $gender = null; + + #[Assert\NotBlank(message: 'Bitte angeben', groups: ['personal_data'])] public ?string $nationality = null; public ?string $height = null; public ?string $shoeSize = null; public ?string $weight = null; + + #[Assert\NotBlank(message: 'Bitte angeben', groups: ['personal_data'])] public ?\DateTimeImmutable $dateOfBirth = null; public ?string $remarks = null; diff --git a/src/Controller/Account/PersonalDataController.php b/src/Controller/Account/PersonalDataController.php index 9bcdc08..0b3108d 100644 --- a/src/Controller/Account/PersonalDataController.php +++ b/src/Controller/Account/PersonalDataController.php @@ -1,5 +1,7 @@ logger->info('Updated personal data', [ 'email' => $user->getEmail(), ]); + + // Handle profile completion redirect + $session = $request->getSession(); + $redirectUrl = $session->get(ProfileCompletionSubscriber::SESSION_REDIRECT_KEY); + + if (null !== $redirectUrl && true === $this->completenessChecker->isComplete($personalData)) { + $session->remove(ProfileCompletionSubscriber::SESSION_REDIRECT_KEY); + + return $this->redirect($redirectUrl); + } } catch (ApiClientException $e) { $this->addFlash('error', $e->getMessage()); } diff --git a/src/EventSubscriber/ProfileCompletionSubscriber.php b/src/EventSubscriber/ProfileCompletionSubscriber.php new file mode 100644 index 0000000..514fcec --- /dev/null +++ b/src/EventSubscriber/ProfileCompletionSubscriber.php @@ -0,0 +1,105 @@ + ['onLoginSuccess', -10], + ]; + } + + public function onLoginSuccess(LoginSuccessEvent $event): void + { + $user = $event->getUser(); + + if (false === $user instanceof User) { + return; + } + + $email = $user->getEmail(); + $password = $this->crypt->decrypt($user->getPassword()); + + try { + $personalData = $this->apiClient->getPersonalData($email, $password); + } catch (ApiClientException $e) { + $this->logger->warning('Failed to fetch personal data for profile completeness check', [ + 'email' => $email, + 'error' => $e->getMessage(), + ]); + + return; + } + + if (false === $personalData instanceof PersonalData) { + $this->logger->warning('Invalid response when fetching personal data for profile completeness check', [ + 'email' => $email, + ]); + + return; + } + + if (true === $this->completenessChecker->isComplete($personalData)) { + return; + } + + $this->logger->info('Incomplete profile detected, redirecting to profile completion', [ + 'email' => $email, + ]); + + $request = $event->getRequest(); + $session = $request->getSession(); + + // Store the original target URL (from authenticator's response or target path) + $originalResponse = $event->getResponse(); + $targetUrl = null; + + if ($originalResponse instanceof RedirectResponse) { + $targetUrl = $originalResponse->getTargetUrl(); + } + + // Don't redirect back to the personal data page itself + $personalDataUrl = $this->urlGenerator->generate('app_personal_data'); + if (null !== $targetUrl && $targetUrl !== $personalDataUrl) { + $session->set(self::SESSION_REDIRECT_KEY, $targetUrl); + } + + // Override the response to redirect to personal data page + $event->setResponse(new RedirectResponse($personalDataUrl)); + } +} diff --git a/src/Form/PersonalDataType.php b/src/Form/PersonalDataType.php index 8afd71f..315a86f 100644 --- a/src/Form/PersonalDataType.php +++ b/src/Form/PersonalDataType.php @@ -1,9 +1,13 @@ add('gender', ChoiceType::class, [ + 'label' => 'Gender', + 'placeholder' => false, + 'choices' => [ + 'männlich' => 'M', + 'weiblich' => 'W', + 'divers' => 'D', + ], + 'invalid_message' => 'Bitte gib einen gültigen Wert an', + ]) + ->add('dateOfBirth', BirthdayType::class, [ + 'label' => 'Geburtsdatum', + 'widget' => 'text', + 'input' => 'datetime_immutable', + 'html5' => false, + 'invalid_message' => 'Bitte gib ein gültiges Datum ein', + ]) ->add('street', TextType::class, [ 'label' => 'Straße', 'property_path' => 'address.street', diff --git a/src/Service/ProfileCompletenessChecker.php b/src/Service/ProfileCompletenessChecker.php new file mode 100644 index 0000000..5abe652 --- /dev/null +++ b/src/Service/ProfileCompletenessChecker.php @@ -0,0 +1,47 @@ +validator->validate($personalData, null, [self::VALIDATION_GROUP]); + + return 0 === $violations->count(); + } +} diff --git a/templates/account/personal_data.html.twig b/templates/account/personal_data.html.twig index 9cb1357..56b6200 100644 --- a/templates/account/personal_data.html.twig +++ b/templates/account/personal_data.html.twig @@ -1,20 +1,27 @@ {% extends 'layout.html.twig' %} {% block content %} + {% include '_partials/_flashes.html.twig' %}

Meine
Daten

+ {% if app.session.get('_profile_completion_redirect') %} +
+ {% include '_partials/_alert.html.twig' with { + level: 'info', + messages: ['Bitte vervollständige erst deine Daten, um fortzufahren.'] + } %} +
+ {% endif %}
- Name: {{ personalData.fullName }} -
- Gender: {{ personalData.gender|map_gender }} -
- Geburtsdatum: {{ personalData.dateOfBirth|date('d.m.Y') }} +

+ {{ personalData.fullName }} +

@@ -32,24 +39,31 @@

-

- Kontaktdaten -

- {% include '_partials/_flashes.html.twig' %} {{ form_start(personalDataForm) }}
+

+ Persönliches +

+ {{ form_row(personalDataForm.gender, { 'label_attr': { 'class': 'text-white' } }) }} + {{ form_row(personalDataForm.dateOfBirth, { 'label_attr': { 'class': 'text-white' } }) }} + {{ form_row(personalDataForm.nationality, { 'label_attr': { 'class': 'text-white' } }) }} +

+ Kontakt +

+ {{ form_row(personalDataForm.email, { 'label_attr': { 'class': 'text-white' } }) }} + {{ form_row(personalDataForm.mobile, { 'label_attr': { 'class': 'text-white' } }) }} + {{ form_row(personalDataForm.phone, { 'label_attr': { 'class': 'text-white' } }) }} +
+
+

+ Anschrift +

{{ form_row(personalDataForm.street, { 'label_attr': { 'class': 'text-white' } }) }} {{ form_row(personalDataForm.postCode, { 'label_attr': { 'class': 'text-white' } }) }} {{ form_row(personalDataForm.city, { 'label_attr': { 'class': 'text-white' } }) }} {{ form_row(personalDataForm.country, { 'label_attr': { 'class': 'text-white' } }) }}
-
- {{ form_row(personalDataForm.nationality, { 'label_attr': { 'class': 'text-white' } }) }} - {{ form_row(personalDataForm.email, { 'label_attr': { 'class': 'text-white' } }) }} - {{ form_row(personalDataForm.phone, { 'label_attr': { 'class': 'text-white' } }) }} - {{ form_row(personalDataForm.mobile, { 'label_attr': { 'class': 'text-white' } }) }} -