feat: htmx powered requests, improved loading indicator

feat: loading indicator and htmx form submit
This commit is contained in:
Björn Fromme
2026-03-16 11:59:10 +01:00
parent 023ecdebc9
commit 83589ee677
20 changed files with 658 additions and 197 deletions
+48 -42
View File
@@ -17,56 +17,62 @@
</div>
{% endif %}
<div class="grid grid-cols-3 gap-8">
<div class="col-span-2">
<div id="form-wrapper" class="col-span-2">
<h2 class="pb-4">
Unterkunft
</h2>
{{ form_start(form) }}
{{ form_errors(form) }}
{% if groupedRooms.by_room is not empty %}
<h3>
Zimmer
</h3>
{% for roomId, room in groupedRooms.by_room %}
{{ form_row(form.roomSelections[roomId], {
'attr': {
'hx-post': path('app_booking_create_step_1_room_summary'),
'hx-target': '#booking-summary',
'hx-swap': 'innerHTML',
'hx-trigger': 'change'
}
}) }}
{% endfor %}
{% endif %}
{% if groupedRooms.by_pax is not empty %}
<h3>
Betten
</h3>
{% for roomId, room in groupedRooms.by_pax %}
{{ form_row(form.roomSelections[roomId], {
'attr': {
'hx-post': path('app_booking_create_step_1_room_summary'),
'hx-target': '#booking-summary',
'hx-swap': 'innerHTML',
'hx-trigger': 'change'
}
}) }}
{% endfor %}
{% endif %}
{% do form.roomSelections.setRendered %}
{# This block contains the room selection form fields #}
{% block room_selection_form %}
<div id="room-selection-form"{% if htmx_oob_swap is defined and htmx_oob_swap %} hx-swap-oob="true"{% endif %}>
{{ form_errors(form) }}
{% if groupedRooms.by_room is not empty %}
<h3>
Zimmer
</h3>
{% for roomId, room in groupedRooms.by_room %}
{{ form_row(form.roomSelections[roomId], {
'attr': {
'hx-post': path('app_booking_create_step_1_refresh'),
'hx-swap': 'none',
'hx-trigger': 'change'
}
}) }}
{% endfor %}
{% endif %}
{% if groupedRooms.by_pax is not empty %}
<h3>
Betten
</h3>
{% for roomId, room in groupedRooms.by_pax %}
{{ form_row(form.roomSelections[roomId], {
'attr': {
'hx-post': path('app_booking_create_step_1_refresh'),
'hx-swap': 'none',
'hx-trigger': 'change'
}
}) }}
{% endfor %}
{% endif %}
</div>
{% endblock %}
<div class="flex justify-end pt-4">
<button type="submit" class="button bg-button bg-button--secondary">Weiter</button>
<button type="submit" class="button bg-button bg-button--secondary" {{ stimulus_action('loading', 'toggle') }}>Weiter</button>
</div>
{{ form_rest(form) }}
{{ form_end(form) }}
</div>
<div id="booking-summary">
{% include 'booking/_summary.html.twig' with {
'bookingCreateDto': bookingCreateDto,
'participantCount': participantCount,
'pricingData': pricingData,
'groupedSelectedRooms': groupedSelectedRooms,
'assignmentCounts': []
} %}
</div>
{% block booking_summary %}
<div id="booking-summary"{% if htmx_oob_swap is defined and htmx_oob_swap %} hx-swap-oob="true"{% endif %}>
{% include 'booking/_summary.html.twig' with {
'bookingCreateDto': bookingCreateDto,
'participantCount': participantCount,
'pricingData': pricingData,
'groupedSelectedRooms': groupedSelectedRooms,
'assignmentCounts': []
} %}
</div>
{% endblock %}
</div>
{% endblock %}
+2 -2
View File
@@ -55,7 +55,7 @@
<div {{ stimulus_controller('toast') }}></div>
<h1>Neue Buchung</h1>
<div class="grid grid-cols-3 gap-8">
<div class="col-span-2">
<div id="form-wrapper" class="col-span-2">
<h2>Teilnehmer</h2>
{{ form_start(form) }}
{% do form.participants.setRendered %}
@@ -318,7 +318,7 @@
{% endblock %}
<div class="flex justify-between">
<a href="{{ path('app_booking_create_step_1') }}" class="button bg-button bg-button--secondary">Zurück</a>
<button type="submit" class="button bg-button bg-button--secondary">Weiter</button>
<button type="submit" class="button bg-button bg-button--secondary" {{ stimulus_action('loading', 'toggle') }}>Weiter</button>
</div>
{{ form_rest(form) }}
{{ form_end(form) }}
+11 -3
View File
@@ -27,10 +27,18 @@
<h1>Neue Buchung</h1>
<div class="grid grid-cols-3 gap-8">
<div class="col-span-2">
<div id="form-wrapper" class="col-span-2">
<h2 class="mb-6">Zahlungsart</h2>
{{ form_start(form, {'attr': {'novalidate': 'novalidate'}}) }}
{{ form_start(form, {
'attr': {
'novalidate': 'novalidate',
'hx-post': path('app_booking_create_step_3'),
'hx-target': '#form-wrapper',
'hx-select': '#form-wrapper',
'hx-swap': 'outerHTML'
}
}) }}
{# Payment method selection with HTMX #}
<div id="form-payment"
@@ -63,7 +71,7 @@
<div class="flex justify-between pt-6">
<a href="{{ path('app_booking_create_step_2') }}" class="button bg-button bg-button--secondary">Zurück</a>
<button type="submit" class="button bg-button bg-button--secondary">Weiter</button>
<button type="submit" class="button bg-button bg-button--secondary" {{ stimulus_action('loading', 'toggle') }}>Weiter</button>
</div>
{{ form_end(form) }}
+11 -3
View File
@@ -21,7 +21,7 @@
</div>
{% endif %}
<div>
<div id="form-wrapper">
<h2 class="mb-6">Buchung bestätigen</h2>
{# Travel Summary #}
@@ -290,7 +290,15 @@
</div>
{# Confirmation Form #}
{{ form_start(form, {'attr': {'novalidate': 'novalidate'}}) }}
{{ form_start(form, {
'attr': {
'novalidate': 'novalidate',
'hx-post': path('app_booking_create_step_4'),
'hx-target': '#form-wrapper',
'hx-select': '#form-wrapper',
'hx-swap': 'outerHTML'
}
}) }}
<div class="mb-8 p-6 bg-blue-50 border border-blue-200 rounded-lg">
{{ form_row(form.confirmationAccepted, {
@@ -300,7 +308,7 @@
<div class="flex justify-between pt-6">
<a href="{{ path('app_booking_create_step_3') }}" class="button bg-button bg-button--secondary">Zurück</a>
<button type="submit" class="button bg-button bg-button--primary">Verbindlich buchen</button>
<button type="submit" class="button bg-button bg-button--primary" {{ stimulus_action('loading', 'toggle') }}>Verbindlich buchen</button>
</div>
{{ form_end(form) }}
+16 -4
View File
@@ -53,11 +53,23 @@
{% block content %}
{% include '_partials/_flashes.html.twig' %}
<div {{ stimulus_controller('toast') }}></div>
<h1 class="text-3xl font-semibold pb-8">Buchung bearbeiten</h1>
{# Header with reload button #}
<div class="flex justify-between items-center pb-8">
<h1 class="text-3xl font-semibold">Buchung bearbeiten</h1>
<button type="button"
hx-post="{{ path('app_booking_edit_reload', {id: bookingData.id}) }}"
hx-confirm="Alle nicht gespeicherten Änderungen gehen verloren. Fortfahren?"
{{ stimulus_action('loading', 'toggle') }}
class="px-4 py-2 bg-gray-200 hover:bg-gray-300 text-gray-800 rounded transition-colors">
🔄 Änderungen verwerfen
</button>
</div>
{# Grid layout with 2/3 form + 1/3 summary #}
<div class="grid grid-cols-3 gap-8">
<div class="col-span-2">
<div id="form-wrapper" class="col-span-2">
<h2>Teilnehmer</h2>
{{ form_start(form) }}
{% do form.participants.setRendered %}
@@ -362,7 +374,7 @@
{% endblock %}
<div class="flex justify-between">
<a href="{{ path('app_bookings') }}" class="button bg-button bg-button--secondary">Zurück</a>
<button type="submit" class="button bg-button bg-button--secondary">Aktualisieren</button>
<button type="submit" class="button bg-button bg-button--secondary" {{ stimulus_action('loading', 'toggle') }}>Aktualisieren</button>
</div>
{{ form_rest(form) }}
{{ form_end(form) }}
@@ -379,4 +391,4 @@
</div>
{% endblock %}
</div>
{% endblock %}
{% endblock %}