From 21600dc2d6ea941d0801aea5f46b93bfc772be1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Fromme?= Date: Sun, 7 Dec 2025 14:11:50 +0100 Subject: [PATCH] feat: implement hx-boost for reliable loading indication --- assets/app.js | 4 +- assets/controllers/loading_controller.js | 37 +++---- .../Booking/Create/IndexController.php | 12 ++- .../Booking/Create/Step1Controller.php | 8 +- .../Booking/Create/Step2Controller.php | 39 +------- .../Booking/Create/Step3Controller.php | 4 +- .../Booking/Create/Step4Controller.php | 2 +- .../Booking/Edit/IndexController.php | 96 +++++++------------ .../Traits/ParticipantCardFlowTrait.php | 2 - src/Form/Model/BookingDto.php | 6 -- .../ParticipantInsuranceFieldHandler.php | 5 + src/Service/BookingEditDataLoaderService.php | 70 ++------------ src/Service/BookingService.php | 3 - templates/account/index.html.twig | 4 +- templates/base.html.twig | 2 +- templates/booking/_form_theme.html.twig | 1 + templates/booking/_pagination.html.twig | 2 +- templates/booking/_participant_card.html.twig | 3 +- templates/booking/_participant_form.html.twig | 2 +- .../booking/create/authenticate.html.twig | 2 +- templates/booking/create/step_1.html.twig | 7 +- templates/booking/create/step_2.html.twig | 7 +- .../create/step_2_participant.html.twig | 10 +- templates/booking/create/step_3.html.twig | 8 +- templates/booking/create/step_4.html.twig | 8 +- templates/booking/create/success.html.twig | 4 +- templates/booking/edit/index.html.twig | 31 +++--- templates/booking/edit/modal_cancel.html.twig | 22 +++++ templates/booking/edit/modal_reload.html.twig | 22 +++++ templates/booking/edit/participant.html.twig | 8 +- templates/booking/index.html.twig | 1 - templates/security/login.html.twig | 2 +- 32 files changed, 163 insertions(+), 271 deletions(-) create mode 100644 templates/booking/edit/modal_cancel.html.twig create mode 100644 templates/booking/edit/modal_reload.html.twig diff --git a/assets/app.js b/assets/app.js index 873ed6d..c949347 100644 --- a/assets/app.js +++ b/assets/app.js @@ -5,8 +5,8 @@ import htmx from 'htmx.org' window.htmx = htmx htmx.config.includeIndicatorStyles = false -htmx.config.historyEnabled = false -htmx.config.historyCacheSize = 0 +htmx.config.historyEnabled = true +htmx.config.historyCacheSize = 10 htmx.config.allowScriptTags = false htmx.config.withCredentials = true htmx.config.selfRequestsOnly = false diff --git a/assets/controllers/loading_controller.js b/assets/controllers/loading_controller.js index d7d1776..d990500 100644 --- a/assets/controllers/loading_controller.js +++ b/assets/controllers/loading_controller.js @@ -4,9 +4,14 @@ export default class extends Controller { static targets = ['indicator'] static classes = ['hidden'] + static values = { + visible: { + type: Boolean, + default: false + } + } connect() { - this.isVisible = false this.debounceTimeout = null // Bind event handlers to preserve context @@ -40,9 +45,9 @@ export default class extends Controller { } } - handleBeforeRequest(event) { + handleBeforeRequest() { // Don't start a new debounce if already visible - if (this.isVisible) { + if (true === this.visibleValue) { return } @@ -52,31 +57,17 @@ export default class extends Controller { }, 200) } - handleAfterRequest(event) { + handleAfterRequest() { // Clear debounce timer if request completes before 200ms if (this.debounceTimeout) { clearTimeout(this.debounceTimeout) this.debounceTimeout = null } - // Check if response contains HX-Redirect header - const xhr = event.detail.xhr - const hxRedirect = xhr.getResponseHeader('HX-Redirect') - - // Keep loading indicator visible if redirecting - if (hxRedirect) { - // Ensure indicator is visible during redirect - if (!this.isVisible) { - this.show() - } - return - } - - // Hide indicator if it's visible this.hide() } - handleTimeout(event) { + handleTimeout() { // Hide loading indicator this.hide() @@ -86,23 +77,23 @@ export default class extends Controller { handlePageShow(event) { // event.persisted is true when page is restored from bfcache (back/forward navigation) - if (event.persisted) { + if (true === event.persisted) { this.hide() } } - handleHistoryRestore(event) { + handleHistoryRestore() { // HTMX history restore - hide loading indicator this.hide() } show() { - this.isVisible = true + this.visibleValue = true this.indicatorTarget.classList.remove(this.hiddenClass) } hide() { - this.isVisible = false + this.visibleValue = false this.indicatorTarget.classList.add(this.hiddenClass) } } diff --git a/src/Controller/Booking/Create/IndexController.php b/src/Controller/Booking/Create/IndexController.php index 88af424..286ffff 100644 --- a/src/Controller/Booking/Create/IndexController.php +++ b/src/Controller/Booking/Create/IndexController.php @@ -107,11 +107,11 @@ class IndexController extends AbstractController } /** - * Cancels the active booking session and returns to the login page. + * Cancels the active booking session and returns to the appropriate page. * * This endpoint allows users to exit the booking flow at any time by - * clearing the booking session data and redirecting them back to the - * regular login screen. + * clearing the booking session data and redirecting them to the account + * dashboard (if logged in) or login page (if guest). */ #[Route('/bookings/cancel', name: 'app_booking_cancel')] public function cancel(Request $request): Response @@ -127,8 +127,10 @@ class IndexController extends AbstractController // Add a flash message to inform the user $this->addFlash('info', 'Buchung abgebrochen.'); - // Redirect to login page - return $this->hxRedirect($request, $this->generateUrl('app_login')); + // Redirect to account dashboard if logged in, otherwise to login page + $targetRoute = null !== $this->getUser() ? 'app_account' : 'app_login'; + + return $this->redirectToRoute($targetRoute); } return $this->render('booking/modal_cancel.html.twig'); diff --git a/src/Controller/Booking/Create/Step1Controller.php b/src/Controller/Booking/Create/Step1Controller.php index 6757f1e..4a7d1fe 100644 --- a/src/Controller/Booking/Create/Step1Controller.php +++ b/src/Controller/Booking/Create/Step1Controller.php @@ -55,12 +55,6 @@ class Step1Controller extends AbstractController } $form = $this->createForm(BookingCreateStep1Type::class, $bookingCreateDto, [ - 'attr' => [ - 'hx-post' => $this->generateUrl('app_booking_create_step_1'), - 'hx-target' => '#form-wrapper', - 'hx-select' => '#form-wrapper', - 'hx-swap' => 'outerHTML', - ], 'validation_groups' => ['booking_create_step_1'], ]); @@ -80,7 +74,7 @@ class Step1Controller extends AbstractController // Clear baseline snapshot when moving to step 2 $this->bookingService->clearBaselineSnapshot($request); - return $this->hxRedirect($request, $this->generateUrl('app_booking_create_step_2')); + return $this->redirectToRoute('app_booking_create_step_2'); } // Get complete summary data (pricing, rooms, CMS data) diff --git a/src/Controller/Booking/Create/Step2Controller.php b/src/Controller/Booking/Create/Step2Controller.php index 1c68011..7b2f9df 100644 --- a/src/Controller/Booking/Create/Step2Controller.php +++ b/src/Controller/Booking/Create/Step2Controller.php @@ -94,7 +94,7 @@ class Step2Controller extends AbstractController $this->bookingService->saveBookingDto($request, $bookingCreateDto, BookingDto::MODE_CREATE); // Proceed to Step 3 - return $this->hxRedirect($request, $this->generateUrl('app_booking_create_step_3')); + return $this->redirectToRoute('app_booking_create_step_3'); } // Generate cards data with validation state if form was submitted and failed @@ -112,17 +112,6 @@ class Step2Controller extends AbstractController 'summaryData' => $summaryData, ]; - // HTMX request: render blocks only - if ($this->isHxRequest($request)) { - return $this->htmxOobResponse( - 'booking/create/step_2.html.twig', - ['participant_cards', 'booking_summary'], - $templateData, - $this->generateUrl('app_booking_create_step_2') - ); - } - - // Regular request: render full template return $this->render('booking/create/step_2.html.twig', $templateData); } @@ -162,7 +151,7 @@ class Step2Controller extends AbstractController $this->addNotificationsAsFlashMessages($notifications); // HTMX redirect to cards view - return $this->hxRedirect($request, $this->generateUrl('app_booking_create_step_2')); + return $this->redirectToRoute('app_booking_create_step_2'); } // Get complete summary data (pricing, rooms, CMS data) @@ -174,29 +163,8 @@ class Step2Controller extends AbstractController 'bookingDto' => $bookingDto, 'summaryData' => $summaryData, 'refreshRouteName' => 'app_booking_create_step_2_participant_refresh', - 'submitRouteName' => 'app_booking_create_step_2_participant', ]; - // HTMX request: render blocks only with OOB swap - if ($this->isHxRequest($request)) { - $response = $this->htmxOobResponse( - 'booking/_participant_form.html.twig', - ['participant_form', 'booking_summary'], - $templateData, - $this->generateUrl('app_booking_create_step_2_participant', ['index' => $index]) - ); - - // Add notifications to render response if present - if (false === empty($notifications)) { - $response->headers->set('HX-Trigger', json_encode([ - 'showNotifications' => $notifications, - ])); - } - - return $response; - } - - // Regular request: render full template return $this->render('booking/create/step_2_participant.html.twig', $templateData); } @@ -226,8 +194,7 @@ class Step2Controller extends AbstractController $request, $bookingDto, $index, - 'app_booking_create_step_2_participant_refresh', - 'app_booking_create_step_2_participant' + 'app_booking_create_step_2_participant_refresh' ); } } diff --git a/src/Controller/Booking/Create/Step3Controller.php b/src/Controller/Booking/Create/Step3Controller.php index fb4c1a8..6d71db1 100644 --- a/src/Controller/Booking/Create/Step3Controller.php +++ b/src/Controller/Booking/Create/Step3Controller.php @@ -103,7 +103,7 @@ class Step3Controller extends AbstractController $this->addFlash('info', $message); - return $this->hxRedirect($request, $this->generateUrl('app_booking_create_step_4')); + return $this->redirectToRoute('app_booking_create_step_4'); } // Not a fallback scenario - show validation error @@ -153,7 +153,7 @@ class Step3Controller extends AbstractController $this->addFlash('info', $inquiryResponse->message); } - return $this->hxRedirect($request, $this->generateUrl('app_booking_create_step_4')); + return $this->redirectToRoute('app_booking_create_step_4'); } catch (TimeoutException $e) { return $this->handleApiError( 'Booking inquiry timeout', diff --git a/src/Controller/Booking/Create/Step4Controller.php b/src/Controller/Booking/Create/Step4Controller.php index bda4038..bf528fa 100644 --- a/src/Controller/Booking/Create/Step4Controller.php +++ b/src/Controller/Booking/Create/Step4Controller.php @@ -104,7 +104,7 @@ class Step4Controller extends AbstractController $this->clearTravelDataCache($bookingCreateDto); $this->bookingService->clearBookingDto($request, BookingDto::MODE_CREATE); - return $this->hxRedirect($request, $this->generateUrl('app_booking_create_success')); + return $this->redirectToRoute('app_booking_create_success'); } catch (TimeoutException $e) { return $this->handleApiError( 'Booking creation timeout', diff --git a/src/Controller/Booking/Edit/IndexController.php b/src/Controller/Booking/Edit/IndexController.php index 50c0e6e..ee58a41 100644 --- a/src/Controller/Booking/Edit/IndexController.php +++ b/src/Controller/Booking/Edit/IndexController.php @@ -71,8 +71,7 @@ class IndexController extends AbstractController $password = $this->crypt->decrypt($user->getPassword()); // Load form data from session (or API on first load) - $loadResult = $this->dataLoader->loadFormData($request, $id, $email, $password); - $bookingDto = $loadResult['bookingDto']; + $bookingDto = $this->dataLoader->loadFormData($request, $id, $email, $password); if (null === $bookingDto) { $this->addFlash('error', 'Buchungsdaten nicht (mehr) verfügbar'); @@ -80,16 +79,6 @@ class IndexController extends AbstractController return $this->redirectToRoute('app_bookings'); } - // Show staleness warning if applicable - if (null !== $loadResult['stalenessWarning']) { - $this->addFlash('info', $loadResult['stalenessWarning']); - } - - // Reset staleness timer when first loading the cards view (not HTMX requests) - if (false === $this->isHxRequest($request)) { - $this->dataLoader->resetStalenessTimer($request, $bookingDto); - } - // Fetch booking data for display (surcharges, canceled status, etc.) $bookingData = $this->dataLoader->fetchBookingData($email, $password, $id); if (null === $bookingData || $bookingData instanceof Notification) { @@ -129,17 +118,6 @@ class IndexController extends AbstractController 'hasValidationErrors' => $form->isSubmitted() && false === $form->isValid(), ]; - // If HTMX request, render only blocks to avoid layout duplication - if ($this->isHxRequest($request)) { - return $this->htmxOobResponse( - 'booking/edit/index.html.twig', - ['participant_cards', 'booking_summary'], - $templateData, - $this->generateUrl('app_booking_edit', ['id' => $id]) - ); - } - - // Regular request: render full template return $this->render('booking/edit/index.html.twig', $templateData); } @@ -215,7 +193,7 @@ class IndexController extends AbstractController $this->addNotificationsAsFlashMessages($notifications); // Redirect back to cards - return $this->hxRedirect($request, $this->generateUrl('app_booking_edit', ['id' => $id])); + return $this->redirectToRoute('app_booking_edit', ['id' => $id]); } // Get complete summary data (pricing, rooms, CMS data) @@ -233,32 +211,10 @@ class IndexController extends AbstractController 'summaryData' => $summaryData, 'refreshRouteName' => 'app_booking_edit_participant_refresh', 'refreshRouteParams' => ['id' => $id, 'index' => $index], - 'submitRouteName' => 'app_booking_edit_participant', - 'submitRouteParams' => ['id' => $id, 'index' => $index], 'cancelRouteName' => 'app_booking_edit', 'cancelRouteParams' => ['id' => $id], ]; - // HTMX request: render blocks only with OOB swap - if ($this->isHxRequest($request)) { - $response = $this->htmxOobResponse( - 'booking/_participant_form.html.twig', - ['participant_form', 'booking_summary'], - $templateData, - $this->generateUrl('app_booking_edit_participant', ['id' => $id, 'index' => $index]) - ); - - // Add notifications to render response if present - if (false === empty($notifications)) { - $response->headers->set('HX-Trigger', json_encode([ - 'showNotifications' => $notifications, - ])); - } - - return $response; - } - - // Regular request: render full template return $this->render('booking/edit/participant.html.twig', $templateData); } @@ -331,8 +287,6 @@ class IndexController extends AbstractController 'summaryData' => $summaryData, 'refreshRouteName' => 'app_booking_edit_participant_refresh', 'refreshRouteParams' => ['id' => $id, 'index' => $index], - 'submitRouteName' => 'app_booking_edit_participant', - 'submitRouteParams' => ['id' => $id, 'index' => $index], 'cancelRouteName' => 'app_booking_edit', 'cancelRouteParams' => ['id' => $id], ] @@ -350,40 +304,56 @@ class IndexController extends AbstractController /** * Reloads booking data from API, discarding all session changes. + * + * GET: Returns modal HTML for confirmation + * POST: Clears session and redirects to reload the booking */ #[Route( path: '/bookings/{id}/edit/reload', name: 'app_booking_edit_reload', - requirements: ['id' => '\d+'], - methods: ['POST'] + requirements: ['id' => '\d+'] )] #[IsGranted('ROLE_USER')] public function reloadFromApi(int $id, Request $request): Response { - // Clear session to discard all changes - $this->bookingService->clearBookingDto($request, BookingDto::MODE_EDIT); + if (Request::METHOD_POST === $request->getMethod()) { + // Clear session to discard all changes + $this->bookingService->clearBookingDto($request, BookingDto::MODE_EDIT); - $this->addFlash('success', 'Änderungen verworfen, Daten neu geladen'); + $this->addFlash('success', 'Änderungen verworfen, Daten neu geladen'); - return $this->hxRedirect($request, $this->generateUrl('app_booking_edit', ['id' => $id])); + return $this->redirectToRoute('app_booking_edit', ['id' => $id]); + } + + return $this->render('booking/edit/modal_reload.html.twig', [ + 'bookingId' => $id, + ]); } /** - * Handles "Zurück" button - clears session and returns to bookings list. + * Handles "Zurück" button - shows confirmation modal or clears session. + * + * GET: Returns modal HTML for confirmation + * POST: Clears session and redirects to bookings list */ #[Route( path: '/bookings/{id}/edit/cancel', name: 'app_booking_edit_cancel', - requirements: ['id' => '\d+'], - methods: ['POST'] + requirements: ['id' => '\d+'] )] #[IsGranted('ROLE_USER')] - public function cancelEdit(Request $request): Response + public function cancelEdit(int $id, Request $request): Response { - // Clear session to discard dirty state - $this->bookingService->clearBookingDto($request, BookingDto::MODE_EDIT); + if (Request::METHOD_POST === $request->getMethod()) { + // Clear session to discard dirty state + $this->bookingService->clearBookingDto($request, BookingDto::MODE_EDIT); - return $this->hxRedirect($request, $this->generateUrl('app_bookings')); + return $this->redirectToRoute('app_bookings'); + } + + return $this->render('booking/edit/modal_cancel.html.twig', [ + 'bookingId' => $id, + ]); } /** @@ -420,7 +390,7 @@ class IndexController extends AbstractController 'booking_id' => $id, ]); - return $this->hxRedirect($request, $this->generateUrl('app_booking_edit', ['id' => $id])); + return $this->redirectToRoute('app_booking_edit', ['id' => $id]); } } catch (TimeoutException $e) { $this->addFlash('error', 'Die Anfrage hat zu lange gedauert. Bitte versuchen Sie es erneut.'); @@ -433,6 +403,6 @@ class IndexController extends AbstractController $this->addFlash('error', 'Es ist ein Fehler in der Kommunikation mit dem Buchungssystem aufgetreten'); } - return $this->hxRedirect($request, $this->generateUrl('app_booking_edit', ['id' => $id])); + return $this->redirectToRoute('app_booking_edit', ['id' => $id]); } } diff --git a/src/Controller/Booking/Traits/ParticipantCardFlowTrait.php b/src/Controller/Booking/Traits/ParticipantCardFlowTrait.php index 4506f48..97b43be 100644 --- a/src/Controller/Booking/Traits/ParticipantCardFlowTrait.php +++ b/src/Controller/Booking/Traits/ParticipantCardFlowTrait.php @@ -114,7 +114,6 @@ trait ParticipantCardFlowTrait BookingDto $bookingDto, int $index, string $refreshRouteName, - string $submitRouteName, ): Response { // Create form with validation disabled $form = $this->createParticipantForm($bookingDto, $index, [ @@ -143,7 +142,6 @@ trait ParticipantCardFlowTrait 'bookingDto' => $bookingDto, 'summaryData' => $summaryData, 'refreshRouteName' => $refreshRouteName, - 'submitRouteName' => $submitRouteName, ] ); diff --git a/src/Form/Model/BookingDto.php b/src/Form/Model/BookingDto.php index 5f2b18d..a0f844e 100644 --- a/src/Form/Model/BookingDto.php +++ b/src/Form/Model/BookingDto.php @@ -60,12 +60,6 @@ class BookingDto */ public ?Booking $booking = null; - /** - * Timestamp of last session update (for staleness detection). - * Updated automatically by BookingService::saveBookingDto(). - */ - public ?\DateTimeImmutable $lastSessionUpdate = null; - /** * Fingerprint of the booking state when loaded from API (edit mode only). * This property stores the original state and is never updated after initial load. diff --git a/src/Form/Service/ParticipantInsuranceFieldHandler.php b/src/Form/Service/ParticipantInsuranceFieldHandler.php index c92c99f..f6ed3c8 100644 --- a/src/Form/Service/ParticipantInsuranceFieldHandler.php +++ b/src/Form/Service/ParticipantInsuranceFieldHandler.php @@ -193,6 +193,11 @@ class ParticipantInsuranceFieldHandler extends AbstractParticipantFieldHandler // Handle form resubmission with existing insurance (automatic reassignment check) if (null !== $currentInsurance && null !== $selectedInsuranceId) { + // Preserve "no insurance" selection during resubmission + if ($currentInsurance->isNoInsurance()) { + return; + } + // Check if current insurance is still eligible with updated participant data $eligibleInsurances = $this->insuranceService->getEligibleInsurances($selectableInsurances, $participant, $bookingDto, $travelPrice); $isCurrentInsuranceStillEligible = $this->isInsuranceInList($currentInsurance, $eligibleInsurances); diff --git a/src/Service/BookingEditDataLoaderService.php b/src/Service/BookingEditDataLoaderService.php index feec70d..de4e465 100644 --- a/src/Service/BookingEditDataLoaderService.php +++ b/src/Service/BookingEditDataLoaderService.php @@ -17,13 +17,11 @@ use Symfony\Contracts\Cache\ItemInterface; /** * Handles loading and initializing booking data for edit mode. * - * Encapsulates the logic for loading booking data from session or API, - * refreshing availability data, and detecting session staleness. + * Encapsulates the logic for loading booking data from session or API + * and refreshing availability data. */ class BookingEditDataLoaderService { - private const STALENESS_THRESHOLD_SECONDS = 300; // 5 minutes - public function __construct( private readonly ApiClient $apiClient, private readonly BookingDataProcessor $bookingDataProcessor, @@ -36,23 +34,19 @@ class BookingEditDataLoaderService /** * Loads booking data from session or initializes from API on first load. - * - * @return array{bookingDto: BookingDto|null, stalenessWarning: string|null} */ - public function loadFormData(Request $request, int $bookingId, string $email, string $password): array + public function loadFormData(Request $request, int $bookingId, string $email, string $password): ?BookingDto { $formData = $this->bookingService->getBookingDto($request, BookingDto::MODE_EDIT); if (null === $formData) { - $bookingDto = $this->initializeFromApi($request, $bookingId, $email, $password); - - return [ - 'bookingDto' => $bookingDto, - 'stalenessWarning' => null, - ]; + return $this->initializeFromApi($request, $bookingId, $email, $password); } - return $this->refreshFromSession($formData); + // Refresh availability data + $this->travelDataService->enrichWithFreshAvailabilities($formData->travel); + + return $formData; } /** @@ -91,25 +85,6 @@ class BookingEditDataLoaderService return $formData; } - /** - * Refreshes booking data loaded from session with latest availability. - * - * @return array{bookingDto: BookingDto, stalenessWarning: string|null} - */ - public function refreshFromSession(BookingDto $formData): array - { - // Refresh availability data - $this->travelDataService->enrichWithFreshAvailabilities($formData->travel); - - // Check for staleness - $stalenessWarning = $this->getStalenessWarning($formData); - - return [ - 'bookingDto' => $formData, - 'stalenessWarning' => $stalenessWarning, - ]; - } - /** * Fetches booking data from API with caching. */ @@ -139,33 +114,4 @@ class BookingEditDataLoaderService // Ignore cache deletion errors } } - - /** - * Resets the staleness timer on the booking DTO. - */ - public function resetStalenessTimer(Request $request, BookingDto $bookingDto): void - { - $bookingDto->lastSessionUpdate = new \DateTimeImmutable(); - $this->bookingService->saveBookingDto($request, $bookingDto, BookingDto::MODE_EDIT); - } - - /** - * Generates a staleness warning message if session is older than threshold. - */ - private function getStalenessWarning(BookingDto $formData): ?string - { - if (null === $formData->lastSessionUpdate) { - return null; - } - - $ageInSeconds = (new \DateTimeImmutable())->getTimestamp() - $formData->lastSessionUpdate->getTimestamp(); - - if ($ageInSeconds <= self::STALENESS_THRESHOLD_SECONDS) { - return null; - } - - $minutes = (int) ceil($ageInSeconds / 60); - - return sprintf('Sie bearbeiten diese Buchung seit %d Minuten. Die Daten könnten veraltet sein.', $minutes); - } } diff --git a/src/Service/BookingService.php b/src/Service/BookingService.php index 308a92b..b3ce9f3 100644 --- a/src/Service/BookingService.php +++ b/src/Service/BookingService.php @@ -93,9 +93,6 @@ class BookingService */ public function saveBookingDto(Request $request, BookingDto $bookingDto, string $mode): void { - // Track last session update for staleness detection - $bookingDto->lastSessionUpdate = new \DateTimeImmutable(); - $sessionKey = $this->getSessionKey($mode); $request->getSession()->set($sessionKey, $bookingDto); } diff --git a/templates/account/index.html.twig b/templates/account/index.html.twig index b3359ed..ffb6987 100644 --- a/templates/account/index.html.twig +++ b/templates/account/index.html.twig @@ -10,13 +10,13 @@