feat: improved error handling and redirect on invalid session

This commit is contained in:
Björn Fromme
2025-12-08 16:00:02 +01:00
parent 390ee81675
commit cd2765bab5
7 changed files with 48 additions and 12 deletions
@@ -138,7 +138,8 @@ class IndexController extends AbstractController
return $this->redirectToRoute('app_account'); 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'); return $this->render('booking/modal_cancel.html.twig');
@@ -108,6 +108,7 @@ class Step2Controller extends AbstractController
'bookingDto' => $bookingCreateDto, 'bookingDto' => $bookingCreateDto,
'cardsData' => $cardsData, 'cardsData' => $cardsData,
'summaryData' => $summaryData, 'summaryData' => $summaryData,
'isSubmitted' => $form->isSubmitted(),
]; ];
return $this->render('booking/create/step_2.html.twig', $templateData); 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 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 // Validate participant index
if (false === isset($bookingDto->participants[$index])) { if (false === isset($bookingDto->participants[$index])) {
@@ -178,7 +183,11 @@ class Step2Controller extends AbstractController
)] )]
public function refreshParticipantForm(int $index, Request $request): Response 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 // Validate participant index
if (false === isset($bookingDto->participants[$index])) { if (false === isset($bookingDto->participants[$index])) {
@@ -34,7 +34,7 @@ trait BookingExceptionHandlerTrait
try { try {
return $bookingService->getOrCreateBookingCreateDto($request); return $bookingService->getOrCreateBookingCreateDto($request);
} catch (BookingSessionNotFoundException $e) { } 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'); return $this->redirectToRoute('app_booking_create_error');
} catch (TravelNotFoundException $e) { } catch (TravelNotFoundException $e) {
@@ -8,6 +8,7 @@ use App\Form\BookingParticipantType;
use App\Form\Model\BookingDto; use App\Form\Model\BookingDto;
use App\Form\Model\ParticipantEditDto; use App\Form\Model\ParticipantEditDto;
use Symfony\Component\Form\FormInterface; use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
@@ -20,16 +21,18 @@ use Symfony\Component\HttpFoundation\Response;
trait ParticipantCardFlowTrait 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); $bookingDto = $this->bookingService->getBookingDto($request, $mode);
if (null === $bookingDto) { 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; return $bookingDto;
@@ -183,4 +186,6 @@ trait ParticipantCardFlowTrait
abstract private function render(string $view, array $parameters = [], ?Response $response = null): Response; abstract private function render(string $view, array $parameters = [], ?Response $response = null): Response;
abstract protected function addFlash(string $type, mixed $message): void; abstract protected function addFlash(string $type, mixed $message): void;
abstract protected function redirectToRoute(string $route, array $parameters = [], int $status = 302): RedirectResponse;
} }
+21 -1
View File
@@ -4,10 +4,11 @@ declare(strict_types=1);
namespace App\Htmx; namespace App\Htmx;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
/** /**
* Trait providing HTMX Out-of-Band swap functionality for controllers. * Trait providing HTMX functionality for controllers.
*/ */
trait HxTrait trait HxTrait
{ {
@@ -46,4 +47,23 @@ trait HxTrait
return $response; 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);
}
} }
+2 -1
View File
@@ -12,7 +12,8 @@
messages: errorMessages messages: errorMessages
} %} } %}
<a href="{{ returnUrl }}" class="button button--secondary"> {# hx-boost disabled for cross-origin #}
<a href="{{ returnUrl }}" hx-boost="false" class="button button--secondary">
Zurück Zurück
</a> </a>
</div> </div>
+2 -2
View File
@@ -47,8 +47,8 @@
</li> </li>
</ul> </ul>
{% else %} {% else %}
{# Guest user - show simple homepage link #} {# Guest user - show simple homepage link (hx-boost disabled for cross-origin) #}
<a href="{{ returnUrl }}" class="button button--primary"> <a href="{{ returnUrl }}" hx-boost="false" class="button button--primary">
Zurück Zurück
</a> </a>
{% endif %} {% endif %}