Files
ludikevent_crm/templates/revervation/flow.twig
Serreau Jovann 0be752c145 ```
 feat(revervation): [Ajoute la création de session de réservation et le flow]
🐛 fix(PurgeCommandTest): [Utilise addCommand au lieu de add pour les commandes]
📝 chore(deps): [Mise à jour des dépendances Composer et corrections]
🐛 fix(KeycloakAuthenticator): [Corrige le type nullable de l'exception start]
 feat(Customer): [Ajoute les sessions de commandes aux entités Customer]
♻️ refactor(AppLogger): [Refactorise l'AppLogger pour obtenir l'UserAgent]
 feat(FlowReserve): [Ajoute une action de validation du panier]
```
2026-01-31 13:49:25 +01:00

299 lines
24 KiB
Twig

{% extends 'revervation/base.twig' %}
{% block title %}Finalisation de votre demande{% endblock %}
{% block body %}
<div class="max-w-7xl mx-auto px-4 py-12">
<div class="bg-white rounded-3xl shadow-xl p-8 border border-gray-100">
<h1 class="text-3xl font-black text-slate-900 uppercase italic mb-8 text-center">Récapitulatif de votre demande</h1>
{% if session.customer is null %}
{# --- LOGIN FORM --- #}
<div class="sm:mx-auto sm:w-full sm:max-w-md">
<div class="text-center mb-6">
<h2 class="text-xl font-bold text-gray-800">Connectez-vous pour continuer</h2>
<p class="mt-2 text-sm text-gray-600">
Ou <a href="{{ path('reservation_register') }}" class="font-medium text-blue-600 hover:text-blue-500">créez un compte</a>.
</p>
</div>
{% if error is defined and error %}
<div class="mb-4 p-4 rounded-2xl bg-red-50 border border-red-100 text-red-700 text-sm">
{{ error.messageKey|trans(error.messageData, 'security') }}
</div>
{% endif %}
<form action="{{ path('reservation_flow', {sessionId: session.uuid}) }}" method="post" class="space-y-6">
<div>
<label for="username" class="block text-sm font-semibold text-gray-700">Adresse Email</label>
<div class="mt-1">
<input type="email" id="username" name="_username" value="{{ last_username|default('') }}" required autofocus
class="appearance-none block w-full px-4 py-3 border border-gray-200 rounded-2xl shadow-sm placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all">
</div>
</div>
<div>
<label for="password" class="block text-sm font-semibold text-gray-700">Mot de passe</label>
<div class="mt-1">
<input type="password" id="password" name="_password" required
class="appearance-none block w-full px-4 py-3 border border-gray-200 rounded-2xl shadow-sm placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all">
</div>
</div>
<input type="hidden" name="_csrf_token" value="{{ csrf_token('authenticate_flow') }}">
<div>
<button type="submit"
class="w-full flex justify-center py-4 px-4 border border-transparent rounded-2xl shadow-lg text-sm font-bold text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-all">
Se connecter
</button>
</div>
</form>
</div>
{% else %}
{# --- LOGGED IN STATE --- #}
<div class="max-w-3xl mx-auto space-y-8">
{# --- CART DETAILS --- #}
{% if cart is defined %}
<div class="bg-slate-50 rounded-2xl p-6 border border-slate-100">
<div class="flex items-center gap-4 mb-6">
<div class="bg-indigo-100 p-3 rounded-full">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-indigo-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4zm-8 2a2 2 0 11-4 0 2 2 0 014 0z" />
</svg>
</div>
<div>
<h3 class="text-lg font-bold text-slate-900">Détails de la réservation</h3>
{% if cart.startDate and cart.endDate %}
<p class="text-sm text-slate-600">Du <span class="font-medium text-slate-900">{{ cart.startDate|date('d/m/Y') }}</span> au <span class="font-medium text-slate-900">{{ cart.endDate|date('d/m/Y') }}</span> ({{ cart.duration }} jours)</p>
{% endif %}
</div>
</div>
<div class="space-y-4">
{% for item in cart.items %}
<div class="flex items-center bg-white p-4 rounded-xl border border-slate-200 shadow-sm">
{% if item.image %}
<img src="{{ item.image }}" alt="{{ item.product.name }}" class="h-16 w-16 object-cover rounded-lg mr-4 bg-slate-100">
{% else %}
<div class="h-16 w-16 bg-slate-100 rounded-lg mr-4 flex items-center justify-center text-slate-400">
<svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
</div>
{% endif %}
<div class="flex-1">
<h4 class="font-bold text-slate-800">{{ item.product.name }}</h4>
<p class="text-xs text-slate-500 line-clamp-1 mb-2">{{ item.product.description }}</p>
<div class="text-xs text-slate-600 bg-slate-50 p-2 rounded-lg border border-slate-100 inline-block">
<div class="flex flex-wrap gap-x-3 gap-y-1">
<span>1er jour : <strong class="text-slate-800">{{ item.price1Day|number_format(2, ',', ' ') }} €</strong></span>
{% if cart.duration > 1 %}
<span class="text-slate-300">|</span>
<span>Jours supp. : <strong class="text-slate-800">{{ item.priceSup|number_format(2, ',', ' ') }} €</strong> <span class="text-slate-400">x {{ cart.duration - 1 }}</span></span>
{% endif %}
</div>
</div>
</div>
<div class="text-right">
<p class="font-bold text-slate-900">{{ item.totalPriceHT|number_format(2, ',', ' ') }} € HT</p>
{% if cart.tvaEnabled %}
<p class="text-xs text-slate-500">{{ item.totalPriceTTC|number_format(2, ',', ' ') }} € TTC</p>
{% endif %}
</div>
</div>
{% else %}
<p class="text-center text-slate-500 py-4">Aucun produit sélectionné.</p>
{% endfor %}
</div>
<div class="mt-6 border-t border-slate-200 pt-4 space-y-2">
<div class="flex justify-between text-sm text-slate-600">
<span>Total HT</span>
<span class="font-medium">{{ cart.totalHT|number_format(2, ',', ' ') }} €</span>
</div>
{% if cart.tvaEnabled %}
<div class="flex justify-between text-sm text-slate-600">
<span>TVA (20%)</span>
<span class="font-medium">{{ cart.totalTva|number_format(2, ',', ' ') }} €</span>
</div>
<div class="flex justify-between text-lg font-black text-slate-900 pt-2 border-t border-slate-200 mt-2">
<span>Total TTC</span>
<span>{{ cart.totalTTC|number_format(2, ',', ' ') }} €</span>
</div>
{% endif %}
</div>
</div>
{% endif %}
<div class="bg-slate-50 rounded-2xl p-6 border border-slate-100 flex flex-col md:flex-row items-center md:items-start gap-6">
<div class="bg-blue-100 p-4 rounded-full flex-shrink-0">
<svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8 text-blue-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
</svg>
</div>
<div class="flex-1 w-full text-center md:text-left">
<h3 class="text-lg font-bold text-slate-900 mb-4">Informations Client</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="bg-white p-3 rounded-xl border border-slate-200 shadow-sm">
<span class="block text-xs text-slate-500 uppercase font-semibold tracking-wider">Nom complet</span>
<span class="block text-slate-800 font-medium mt-1">{{ session.customer.name }} {{ session.customer.surname }}</span>
</div>
<div class="bg-white p-3 rounded-xl border border-slate-200 shadow-sm">
<span class="block text-xs text-slate-500 uppercase font-semibold tracking-wider">Téléphone</span>
<span class="block text-slate-800 font-medium mt-1">{{ session.customer.phone }}</span>
</div>
<div class="bg-white p-3 rounded-xl border border-slate-200 shadow-sm md:col-span-2">
<span class="block text-xs text-slate-500 uppercase font-semibold tracking-wider">Email</span>
<span class="block text-slate-800 font-medium mt-1">{{ session.customer.email }}</span>
</div>
</div>
</div>
</div>
{# --- EVENT & BILLING FORM --- #}
<div class="bg-white rounded-2xl p-6 border border-slate-200 shadow-sm">
<h3 class="text-xl font-bold text-slate-900 mb-6 flex items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-blue-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4" />
</svg>
Informations de l'événement
</h3>
<form action="{{ path('reservation_flow_update', {sessionId: session.uuid}) }}" method="post" class="space-y-10">
{# Billing Address #}
<div>
<div class="flex items-center gap-3 mb-6 pb-2 border-b border-slate-100">
<span class="bg-blue-100 text-blue-600 p-2 rounded-lg">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 10h18M7 15h1m4 0h1m-7 4h12a3 3 0 003-3V8a3 3 0 00-3-3H6a3 3 0 00-3 3v8a3 3 0 003 3z" /></svg>
</span>
<h4 class="font-bold text-lg text-slate-900">Adresse de facturation</h4>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div class="md:col-span-2">
<label class="block text-xs font-bold text-slate-500 uppercase tracking-wide mb-2">Adresse complète</label>
<input type="text" name="billingAddress" value="{{ session.billingAddress }}" required
class="block w-full rounded-2xl border-slate-200 shadow-sm focus:border-blue-500 focus:ring-blue-500 py-3 px-4 bg-slate-50 focus:bg-white transition-all placeholder-slate-400"
placeholder="123 Rue de la Paix">
</div>
<div>
<label class="block text-xs font-bold text-slate-500 uppercase tracking-wide mb-2">Code Postal</label>
<input type="text" name="billingZipCode" value="{{ session.billingZipCode }}" required
class="block w-full rounded-2xl border-slate-200 shadow-sm focus:border-blue-500 focus:ring-blue-500 py-3 px-4 bg-slate-50 focus:bg-white transition-all placeholder-slate-400"
placeholder="75000">
</div>
<div>
<label class="block text-xs font-bold text-slate-500 uppercase tracking-wide mb-2">Ville</label>
<input type="text" name="billingTown" value="{{ session.billingTown }}" required
class="block w-full rounded-2xl border-slate-200 shadow-sm focus:border-blue-500 focus:ring-blue-500 py-3 px-4 bg-slate-50 focus:bg-white transition-all placeholder-slate-400"
placeholder="Paris">
</div>
</div>
</div>
{# Event Address #}
<div>
<div class="flex items-center gap-3 mb-6 pb-2 border-b border-slate-100">
<span class="bg-blue-100 text-blue-600 p-2 rounded-lg">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z" /><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 11a3 3 0 11-6 0 3 3 0 016 0z" /></svg>
</span>
<h4 class="font-bold text-lg text-slate-900">Lieu de l'événement</h4>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div class="md:col-span-2">
<label class="block text-xs font-bold text-slate-500 uppercase tracking-wide mb-2">Adresse de l'événement</label>
<input type="text" name="adressEvent" value="{{ session.adressEvent }}" required
class="block w-full rounded-2xl border-slate-200 shadow-sm focus:border-blue-500 focus:ring-blue-500 py-3 px-4 bg-slate-50 focus:bg-white transition-all placeholder-slate-400">
</div>
<div class="md:col-span-2">
<label class="block text-xs font-bold text-slate-500 uppercase tracking-wide mb-2">Complément d'adresse <span class="text-slate-300 font-normal normal-case">(Optionnel)</span></label>
<input type="text" name="adress2Event" value="{{ session.adress2Event }}"
class="block w-full rounded-2xl border-slate-200 shadow-sm focus:border-blue-500 focus:ring-blue-500 py-3 px-4 bg-slate-50 focus:bg-white transition-all placeholder-slate-400">
</div>
<div>
<label class="block text-xs font-bold text-slate-500 uppercase tracking-wide mb-2">Code Postal</label>
<input type="text" name="zipCodeEvent" value="{{ session.zipCodeEvent }}" required
class="block w-full rounded-2xl border-slate-200 shadow-sm focus:border-blue-500 focus:ring-blue-500 py-3 px-4 bg-slate-50 focus:bg-white transition-all placeholder-slate-400">
</div>
<div>
<label class="block text-xs font-bold text-slate-500 uppercase tracking-wide mb-2">Ville</label>
<input type="text" name="townEvent" value="{{ session.townEvent }}" required
class="block w-full rounded-2xl border-slate-200 shadow-sm focus:border-blue-500 focus:ring-blue-500 py-3 px-4 bg-slate-50 focus:bg-white transition-all placeholder-slate-400">
</div>
</div>
</div>
{# Event Details #}
<div>
<div class="flex items-center gap-3 mb-6 pb-2 border-b border-slate-100">
<span class="bg-blue-100 text-blue-600 p-2 rounded-lg">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" /></svg>
</span>
<h4 class="font-bold text-lg text-slate-900">Détails techniques</h4>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div class="md:col-span-2">
<label class="block text-xs font-bold text-slate-500 uppercase tracking-wide mb-2">Type d'événement</label>
<select name="type" required
class="block w-full rounded-2xl border-slate-200 shadow-sm focus:border-blue-500 focus:ring-blue-500 py-3 px-4 bg-slate-50 focus:bg-white transition-all">
<option value="">Sélectionnez le type...</option>
{% for type in ['Anniversaire enfant', 'Kermesse scolaire', 'Fête communale', 'Événement d\'entreprise', 'Mariage', 'Autre'] %}
<option value="{{ type }}" {% if session.type == type %}selected{% endif %}>{{ type }}</option>
{% endfor %}
</select>
</div>
<div class="md:col-span-2">
<label class="block text-xs font-bold text-slate-500 uppercase tracking-wide mb-2">Détails supplémentaires</label>
<textarea name="details" rows="3"
class="block w-full rounded-2xl border-slate-200 shadow-sm focus:border-blue-500 focus:ring-blue-500 py-3 px-4 bg-slate-50 focus:bg-white transition-all placeholder-slate-400"
placeholder="Précisions utiles pour l'installation...">{{ session.details }}</textarea>
</div>
<div>
<label class="block text-xs font-bold text-slate-500 uppercase tracking-wide mb-2">Type de sol</label>
<input type="text" name="typeSol" value="{{ session.typeSol }}"
class="block w-full rounded-2xl border-slate-200 shadow-sm focus:border-blue-500 focus:ring-blue-500 py-3 px-4 bg-slate-50 focus:bg-white transition-all placeholder-slate-400"
placeholder="Ex: Herbe, Bitume...">
</div>
<div>
<label class="block text-xs font-bold text-slate-500 uppercase tracking-wide mb-2">Pente</label>
<input type="text" name="pente" value="{{ session.pente }}"
class="block w-full rounded-2xl border-slate-200 shadow-sm focus:border-blue-500 focus:ring-blue-500 py-3 px-4 bg-slate-50 focus:bg-white transition-all placeholder-slate-400"
placeholder="Ex: Terrain plat">
</div>
<div class="md:col-span-2">
<label class="block text-xs font-bold text-slate-500 uppercase tracking-wide mb-2">Accès (largeur portail, escaliers...)</label>
<textarea name="access" rows="2"
class="block w-full rounded-2xl border-slate-200 shadow-sm focus:border-blue-500 focus:ring-blue-500 py-3 px-4 bg-slate-50 focus:bg-white transition-all placeholder-slate-400"
placeholder="Informations d'accès pour le véhicule...">{{ session.access }}</textarea>
</div>
<div>
<label class="block text-xs font-bold text-slate-500 uppercase tracking-wide mb-2">Distance prise électrique (m)</label>
<input type="number" step="0.1" name="distancePower" value="{{ session.distancePower }}"
class="block w-full rounded-2xl border-slate-200 shadow-sm focus:border-blue-500 focus:ring-blue-500 py-3 px-4 bg-slate-50 focus:bg-white transition-all placeholder-slate-400"
placeholder="0">
</div>
</div>
</div>
<div class="pt-6 border-t border-slate-100">
<button type="submit" class="w-full md:w-auto px-8 py-4 bg-gradient-to-r from-blue-600 to-blue-700 text-white font-bold rounded-2xl shadow-lg shadow-blue-200 hover:shadow-xl hover:scale-[1.02] transition-all flex items-center justify-center gap-2 text-lg mx-auto md:mx-0">
Valider ma demande
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 5l7 7m0 0l-7 7m7-7H3" />
</svg>
</button>
</div>
</form>
</div>
</div>
{% endif %}
</div>
</div>
{% endblock %}