From cd2765bab5f202a54bfc87d009b2697fc10f2afe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Fromme?= Date: Mon, 8 Dec 2025 16:00:02 +0100 Subject: [PATCH] feat: improved error handling and redirect on invalid session --- .../Booking/Create/IndexController.php | 3 ++- .../Booking/Create/Step2Controller.php | 13 +++++++++-- .../Traits/BookingExceptionHandlerTrait.php | 2 +- .../Traits/ParticipantCardFlowTrait.php | 13 +++++++---- src/Htmx/HxTrait.php | 22 ++++++++++++++++++- templates/booking/create/error.html.twig | 3 ++- templates/booking/create/success.html.twig | 4 ++-- 7 files changed, 48 insertions(+), 12 deletions(-) diff --git a/src/Controller/Booking/Create/IndexController.php b/src/Controller/Booking/Create/IndexController.php index f62d596..fef1626 100644 --- a/src/Controller/Booking/Create/IndexController.php +++ b/src/Controller/Booking/Create/IndexController.php @@ -138,7 +138,8 @@ class IndexController extends AbstractController return $this->redirectToRoute('app_account'); } - return $this->redirect($returnUrl); + // Use htmxRedirect for cross-origin safety (returnUrl may be external) + return $this->htmxRedirect($request, $returnUrl); } return $this->render('booking/modal_cancel.html.twig'); diff --git a/src/Controller/Booking/Create/Step2Controller.php b/src/Controller/Booking/Create/Step2Controller.php index e155796..b31c178 100644 --- a/src/Controller/Booking/Create/Step2Controller.php +++ b/src/Controller/Booking/Create/Step2Controller.php @@ -108,6 +108,7 @@ class Step2Controller extends AbstractController 'bookingDto' => $bookingCreateDto, 'cardsData' => $cardsData, 'summaryData' => $summaryData, + 'isSubmitted' => $form->isSubmitted(), ]; return $this->render('booking/create/step_2.html.twig', $templateData); @@ -123,7 +124,11 @@ class Step2Controller extends AbstractController )] public function editParticipant(int $index, Request $request): Response { - $bookingDto = $this->loadBookingDtoOrFail($request, BookingDto::MODE_CREATE); + $result = $this->loadBookingDtoOrRedirect($request, BookingDto::MODE_CREATE); + if ($result instanceof Response) { + return $result; + } + $bookingDto = $result; // Validate participant index if (false === isset($bookingDto->participants[$index])) { @@ -178,7 +183,11 @@ class Step2Controller extends AbstractController )] public function refreshParticipantForm(int $index, Request $request): Response { - $bookingDto = $this->loadBookingDtoOrFail($request, BookingDto::MODE_CREATE); + $result = $this->loadBookingDtoOrRedirect($request, BookingDto::MODE_CREATE); + if ($result instanceof Response) { + return $result; + } + $bookingDto = $result; // Validate participant index if (false === isset($bookingDto->participants[$index])) { diff --git a/src/Controller/Booking/Traits/BookingExceptionHandlerTrait.php b/src/Controller/Booking/Traits/BookingExceptionHandlerTrait.php index 15f369b..40f3e0a 100644 --- a/src/Controller/Booking/Traits/BookingExceptionHandlerTrait.php +++ b/src/Controller/Booking/Traits/BookingExceptionHandlerTrait.php @@ -34,7 +34,7 @@ trait BookingExceptionHandlerTrait try { return $bookingService->getOrCreateBookingCreateDto($request); } catch (BookingSessionNotFoundException $e) { - $this->addFlash('error', 'Ihre Buchungssitzung ist abgelaufen. Bitte starten Sie eine neue Buchung.'); + $this->addFlash('error', 'Deine Buchungssitzung ist abgelaufen. Bitte starte eine neue Buchung.'); return $this->redirectToRoute('app_booking_create_error'); } catch (TravelNotFoundException $e) { diff --git a/src/Controller/Booking/Traits/ParticipantCardFlowTrait.php b/src/Controller/Booking/Traits/ParticipantCardFlowTrait.php index 97b43be..403a1a5 100644 --- a/src/Controller/Booking/Traits/ParticipantCardFlowTrait.php +++ b/src/Controller/Booking/Traits/ParticipantCardFlowTrait.php @@ -8,6 +8,7 @@ use App\Form\BookingParticipantType; use App\Form\Model\BookingDto; use App\Form\Model\ParticipantEditDto; use Symfony\Component\Form\FormInterface; +use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; @@ -20,16 +21,18 @@ use Symfony\Component\HttpFoundation\Response; trait ParticipantCardFlowTrait { /** - * Load BookingDto from session or throw exception. + * Load BookingDto from session or return redirect to login. * - * @throws \RuntimeException When booking data not found in session + * Returns RedirectResponse when session data is missing (e.g., after cancel or session expiry). */ - private function loadBookingDtoOrFail(Request $request, string $mode): BookingDto + private function loadBookingDtoOrRedirect(Request $request, string $mode): BookingDto|RedirectResponse { $bookingDto = $this->bookingService->getBookingDto($request, $mode); if (null === $bookingDto) { - throw new \RuntimeException(sprintf('Booking data not found in session for mode: %s', $mode)); + $this->addFlash('info', 'Deine Sitzung ist abgelaufen. Bitte starte eine neue Buchung.'); + + return $this->redirectToRoute('app_login'); } return $bookingDto; @@ -183,4 +186,6 @@ trait ParticipantCardFlowTrait abstract private function render(string $view, array $parameters = [], ?Response $response = null): Response; abstract protected function addFlash(string $type, mixed $message): void; + + abstract protected function redirectToRoute(string $route, array $parameters = [], int $status = 302): RedirectResponse; } diff --git a/src/Htmx/HxTrait.php b/src/Htmx/HxTrait.php index 0e3fcf5..616a372 100644 --- a/src/Htmx/HxTrait.php +++ b/src/Htmx/HxTrait.php @@ -4,10 +4,11 @@ declare(strict_types=1); namespace App\Htmx; +use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; /** - * Trait providing HTMX Out-of-Band swap functionality for controllers. + * Trait providing HTMX functionality for controllers. */ trait HxTrait { @@ -46,4 +47,23 @@ trait HxTrait return $response; } + + /** + * Creates a redirect response that works correctly with HTMX requests. + * + * For HTMX requests, returns a 200 response with HX-Redirect header, + * which triggers a full page navigation (avoiding CORS issues with cross-origin redirects). + * For regular requests, returns a standard HTTP redirect. + */ + protected function htmxRedirect(Request $request, string $url): Response + { + if ($request->headers->has('HX-Request')) { + $response = new Response('', Response::HTTP_OK); + $response->headers->set('HX-Redirect', $url); + + return $response; + } + + return $this->redirect($url); + } } diff --git a/templates/booking/create/error.html.twig b/templates/booking/create/error.html.twig index 7b5b9b4..540cf6a 100644 --- a/templates/booking/create/error.html.twig +++ b/templates/booking/create/error.html.twig @@ -12,7 +12,8 @@ messages: errorMessages } %} - + {# hx-boost disabled for cross-origin #} + Zurück diff --git a/templates/booking/create/success.html.twig b/templates/booking/create/success.html.twig index 85f03a2..664d679 100644 --- a/templates/booking/create/success.html.twig +++ b/templates/booking/create/success.html.twig @@ -47,8 +47,8 @@ {% else %} - {# Guest user - show simple homepage link #} - + {# Guest user - show simple homepage link (hx-boost disabled for cross-origin) #} + Zurück {% endif %}