fix: resolve SonarQube accessibility and test issues across templates
- Add for/id attributes to all form labels for accessibility compliance - Add <title> tags to PDF templates (rgpd_access, rgpd_no_data, rgpd_deletion, contrat_revendeur) - Add role="presentation" to email layout tables - Remove deprecated cellpadding/cellspacing attributes from all templates - Fix PHPUnit notices by replacing createMock with createStub where no expectations are set Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -23,30 +23,30 @@
|
||||
<h2 class="text-sm font-black uppercase tracking-widest mb-4">Identite</h2>
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div>
|
||||
<label class="block text-xs font-black uppercase tracking-widest mb-2">Prenom *</label>
|
||||
<input type="text" name="firstName" required placeholder="Prenom"
|
||||
<label for="firstName" class="block text-xs font-black uppercase tracking-widest mb-2">Prenom *</label>
|
||||
<input type="text" id="firstName" name="firstName" required placeholder="Prenom"
|
||||
class="w-full px-4 py-3 border-2 border-gray-900 bg-white text-sm font-bold focus:outline-none focus:border-indigo-600 focus:shadow-[4px_4px_0px_rgba(79,70,229,0.3)]">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-black uppercase tracking-widest mb-2">Nom *</label>
|
||||
<input type="text" name="lastName" required placeholder="Nom"
|
||||
<label for="lastName" class="block text-xs font-black uppercase tracking-widest mb-2">Nom *</label>
|
||||
<input type="text" id="lastName" name="lastName" required placeholder="Nom"
|
||||
class="w-full px-4 py-3 border-2 border-gray-900 bg-white text-sm font-bold focus:outline-none focus:border-indigo-600 focus:shadow-[4px_4px_0px_rgba(79,70,229,0.3)]">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-black uppercase tracking-widest mb-2">Email *</label>
|
||||
<input type="email" name="email" required placeholder="email@exemple.fr"
|
||||
<label for="email" class="block text-xs font-black uppercase tracking-widest mb-2">Email *</label>
|
||||
<input type="email" id="email" name="email" required placeholder="email@exemple.fr"
|
||||
class="w-full px-4 py-3 border-2 border-gray-900 bg-white text-sm font-bold focus:outline-none focus:border-indigo-600 focus:shadow-[4px_4px_0px_rgba(79,70,229,0.3)]">
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mt-4">
|
||||
<div>
|
||||
<label class="block text-xs font-black uppercase tracking-widest mb-2">Telephone</label>
|
||||
<input type="tel" name="phone" placeholder="06 12 34 56 78"
|
||||
<label for="phone" class="block text-xs font-black uppercase tracking-widest mb-2">Telephone</label>
|
||||
<input type="tel" id="phone" name="phone" placeholder="06 12 34 56 78"
|
||||
class="w-full px-4 py-3 border-2 border-gray-900 bg-white text-sm font-bold focus:outline-none focus:border-indigo-600 focus:shadow-[4px_4px_0px_rgba(79,70,229,0.3)]">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-black uppercase tracking-widest mb-2">Type</label>
|
||||
<select name="typeCompany" class="w-full px-4 py-3 border-2 border-gray-900 bg-white text-sm font-bold">
|
||||
<label for="typeCompany" class="block text-xs font-black uppercase tracking-widest mb-2">Type</label>
|
||||
<select id="typeCompany" name="typeCompany" class="w-full px-4 py-3 border-2 border-gray-900 bg-white text-sm font-bold">
|
||||
<option value="">— Selectionner —</option>
|
||||
<option value="particulier">Particulier</option>
|
||||
<option value="association">Association</option>
|
||||
@@ -65,23 +65,23 @@
|
||||
<h2 class="text-sm font-black uppercase tracking-widest mb-4">Entreprise</h2>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label class="block text-xs font-black uppercase tracking-widest mb-2">Raison sociale</label>
|
||||
<input type="text" name="raisonSociale" placeholder="Nom de l'entreprise"
|
||||
<label for="raisonSociale" class="block text-xs font-black uppercase tracking-widest mb-2">Raison sociale</label>
|
||||
<input type="text" id="raisonSociale" name="raisonSociale" placeholder="Nom de l'entreprise"
|
||||
class="w-full px-4 py-3 border-2 border-gray-900 bg-white text-sm font-bold focus:outline-none focus:border-indigo-600 focus:shadow-[4px_4px_0px_rgba(79,70,229,0.3)]">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-black uppercase tracking-widest mb-2">SIRET</label>
|
||||
<input type="text" name="siret" maxlength="14" placeholder="12345678901234"
|
||||
<label for="siret" class="block text-xs font-black uppercase tracking-widest mb-2">SIRET</label>
|
||||
<input type="text" id="siret" name="siret" maxlength="14" placeholder="12345678901234"
|
||||
class="w-full px-4 py-3 border-2 border-gray-900 bg-white text-sm font-bold focus:outline-none focus:border-indigo-600 focus:shadow-[4px_4px_0px_rgba(79,70,229,0.3)]">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-black uppercase tracking-widest mb-2">RCS</label>
|
||||
<input type="text" name="rcs" placeholder="RCS Paris 123 456 789"
|
||||
<label for="rcs" class="block text-xs font-black uppercase tracking-widest mb-2">RCS</label>
|
||||
<input type="text" id="rcs" name="rcs" placeholder="RCS Paris 123 456 789"
|
||||
class="w-full px-4 py-3 border-2 border-gray-900 bg-white text-sm font-bold focus:outline-none focus:border-indigo-600 focus:shadow-[4px_4px_0px_rgba(79,70,229,0.3)]">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-black uppercase tracking-widest mb-2">N° TVA</label>
|
||||
<input type="text" name="numTva" placeholder="FR12345678901"
|
||||
<label for="numTva" class="block text-xs font-black uppercase tracking-widest mb-2">N° TVA</label>
|
||||
<input type="text" id="numTva" name="numTva" placeholder="FR12345678901"
|
||||
class="w-full px-4 py-3 border-2 border-gray-900 bg-white text-sm font-bold focus:outline-none focus:border-indigo-600 focus:shadow-[4px_4px_0px_rgba(79,70,229,0.3)]">
|
||||
</div>
|
||||
</div>
|
||||
@@ -91,24 +91,24 @@
|
||||
<h2 class="text-sm font-black uppercase tracking-widest mb-4">Adresse</h2>
|
||||
<div class="flex flex-col gap-4">
|
||||
<div>
|
||||
<label class="block text-xs font-black uppercase tracking-widest mb-2">Adresse</label>
|
||||
<input type="text" name="address" placeholder="Numero et rue"
|
||||
<label for="address" class="block text-xs font-black uppercase tracking-widest mb-2">Adresse</label>
|
||||
<input type="text" id="address" name="address" placeholder="Numero et rue"
|
||||
class="w-full px-4 py-3 border-2 border-gray-900 bg-white text-sm font-bold focus:outline-none focus:border-indigo-600 focus:shadow-[4px_4px_0px_rgba(79,70,229,0.3)]">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-black uppercase tracking-widest mb-2">Complement d'adresse</label>
|
||||
<input type="text" name="address2" placeholder="Batiment, etage, etc."
|
||||
<label for="address2" class="block text-xs font-black uppercase tracking-widest mb-2">Complement d'adresse</label>
|
||||
<input type="text" id="address2" name="address2" placeholder="Batiment, etage, etc."
|
||||
class="w-full px-4 py-3 border-2 border-gray-900 bg-white text-sm font-bold focus:outline-none focus:border-indigo-600 focus:shadow-[4px_4px_0px_rgba(79,70,229,0.3)]">
|
||||
</div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label class="block text-xs font-black uppercase tracking-widest mb-2">Code postal</label>
|
||||
<input type="text" name="zipCode" maxlength="10" placeholder="02800"
|
||||
<label for="zipCode" class="block text-xs font-black uppercase tracking-widest mb-2">Code postal</label>
|
||||
<input type="text" id="zipCode" name="zipCode" maxlength="10" placeholder="02800"
|
||||
class="w-full px-4 py-3 border-2 border-gray-900 bg-white text-sm font-bold focus:outline-none focus:border-indigo-600 focus:shadow-[4px_4px_0px_rgba(79,70,229,0.3)]">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-black uppercase tracking-widest mb-2">Ville</label>
|
||||
<input type="text" name="city" placeholder="Beautor"
|
||||
<label for="city" class="block text-xs font-black uppercase tracking-widest mb-2">Ville</label>
|
||||
<input type="text" id="city" name="city" placeholder="Beautor"
|
||||
class="w-full px-4 py-3 border-2 border-gray-900 bg-white text-sm font-bold focus:outline-none focus:border-indigo-600 focus:shadow-[4px_4px_0px_rgba(79,70,229,0.3)]">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -41,10 +41,10 @@
|
||||
placeholder="email@exemple.fr">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-black uppercase tracking-widest mb-2">Groupes d'acces</label>
|
||||
<label for="groups-member" class="block text-xs font-black uppercase tracking-widest mb-2">Groupes d'acces</label>
|
||||
<div class="grid grid-cols-2 md:grid-cols-4 gap-3">
|
||||
<label class="flex items-center gap-2 px-3 py-2 border-2 border-indigo-200 hover:border-indigo-600 cursor-pointer transition-colors bg-indigo-50">
|
||||
<input type="checkbox" name="groups[]" value="gp_member" class="accent-indigo-600" checked>
|
||||
<input type="checkbox" id="groups-member" name="groups[]" value="gp_member" class="accent-indigo-600" checked>
|
||||
<span class="text-xs font-bold text-indigo-800">Membre</span>
|
||||
</label>
|
||||
<label class="flex items-center gap-2 px-3 py-2 border-2 border-red-200 hover:border-red-600 cursor-pointer transition-colors bg-red-50">
|
||||
|
||||
@@ -91,8 +91,8 @@
|
||||
<div class="flex flex-col gap-3">
|
||||
<form method="post" action="{{ path('app_admin_profil_avatar') }}" enctype="multipart/form-data" class="flex items-end gap-3">
|
||||
<div>
|
||||
<label class="block text-[9px] font-black uppercase tracking-widest text-gray-400 mb-1">Image (JPG, PNG, WebP — max 2Mo)</label>
|
||||
<input type="file" name="avatar" accept="image/jpeg,image/png,image/webp" required
|
||||
<label for="avatar" class="block text-[9px] font-black uppercase tracking-widest text-gray-400 mb-1">Image (JPG, PNG, WebP — max 2Mo)</label>
|
||||
<input type="file" id="avatar" name="avatar" accept="image/jpeg,image/png,image/webp" required
|
||||
class="text-xs file:mr-3 file:py-2 file:px-4 file:border-2 file:border-gray-900 file:bg-[#fabf04] file:text-gray-900 file:font-black file:uppercase file:text-[10px] file:tracking-widest file:cursor-pointer">
|
||||
</div>
|
||||
<button type="submit" class="px-4 py-2 border-2 border-gray-900 bg-[#fabf04] font-black uppercase text-[10px] tracking-widest hover:bg-yellow-500 transition-all">
|
||||
@@ -119,19 +119,19 @@
|
||||
<form method="post" action="{{ path('app_admin_profil_update') }}" class="flex flex-col gap-4">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label class="block text-xs font-black uppercase tracking-widest mb-2">Prenom</label>
|
||||
<input type="text" name="firstName" value="{{ app.user.firstName }}" required
|
||||
<label for="firstName" class="block text-xs font-black uppercase tracking-widest mb-2">Prenom</label>
|
||||
<input type="text" id="firstName" name="firstName" value="{{ app.user.firstName }}" required
|
||||
class="w-full px-4 py-3 border-2 border-gray-900 bg-white text-sm font-bold focus:outline-none focus:border-indigo-600 focus:shadow-[4px_4px_0px_rgba(79,70,229,0.3)]">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-black uppercase tracking-widest mb-2">Nom</label>
|
||||
<input type="text" name="lastName" value="{{ app.user.lastName }}" required
|
||||
<label for="lastName" class="block text-xs font-black uppercase tracking-widest mb-2">Nom</label>
|
||||
<input type="text" id="lastName" name="lastName" value="{{ app.user.lastName }}" required
|
||||
class="w-full px-4 py-3 border-2 border-gray-900 bg-white text-sm font-bold focus:outline-none focus:border-indigo-600 focus:shadow-[4px_4px_0px_rgba(79,70,229,0.3)]">
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-black uppercase tracking-widest mb-2">Email</label>
|
||||
<input type="email" name="email" value="{{ app.user.email }}" required
|
||||
<label for="email" class="block text-xs font-black uppercase tracking-widest mb-2">Email</label>
|
||||
<input type="email" id="email" name="email" value="{{ app.user.email }}" required
|
||||
class="w-full px-4 py-3 border-2 border-gray-900 bg-white text-sm font-bold focus:outline-none focus:border-indigo-600 focus:shadow-[4px_4px_0px_rgba(79,70,229,0.3)]">
|
||||
</div>
|
||||
{% if app.user.keycloakId %}
|
||||
@@ -152,21 +152,21 @@
|
||||
<div class="border-2 border-gray-900 bg-white p-6 shadow-[4px_4px_0px_rgba(0,0,0,1)]">
|
||||
<form method="post" action="{{ path('app_admin_profil_password') }}" class="flex flex-col gap-4">
|
||||
<div>
|
||||
<label class="block text-xs font-black uppercase tracking-widest mb-2">Mot de passe actuel</label>
|
||||
<input type="password" name="current_password" required
|
||||
<label for="current_password" class="block text-xs font-black uppercase tracking-widest mb-2">Mot de passe actuel</label>
|
||||
<input type="password" id="current_password" name="current_password" required
|
||||
class="w-full px-4 py-3 border-2 border-gray-900 bg-white text-sm font-bold focus:outline-none focus:border-indigo-600 focus:shadow-[4px_4px_0px_rgba(79,70,229,0.3)]"
|
||||
placeholder="Votre mot de passe actuel">
|
||||
</div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label class="block text-xs font-black uppercase tracking-widest mb-2">Nouveau mot de passe</label>
|
||||
<input type="password" name="new_password" required minlength="8"
|
||||
<label for="new_password" class="block text-xs font-black uppercase tracking-widest mb-2">Nouveau mot de passe</label>
|
||||
<input type="password" id="new_password" name="new_password" required minlength="8"
|
||||
class="w-full px-4 py-3 border-2 border-gray-900 bg-white text-sm font-bold focus:outline-none focus:border-indigo-600 focus:shadow-[4px_4px_0px_rgba(79,70,229,0.3)]"
|
||||
placeholder="Minimum 8 caracteres">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-black uppercase tracking-widest mb-2">Confirmer</label>
|
||||
<input type="password" name="confirm_password" required minlength="8"
|
||||
<label for="confirm_password" class="block text-xs font-black uppercase tracking-widest mb-2">Confirmer</label>
|
||||
<input type="password" id="confirm_password" name="confirm_password" required minlength="8"
|
||||
class="w-full px-4 py-3 border-2 border-gray-900 bg-white text-sm font-bold focus:outline-none focus:border-indigo-600 focus:shadow-[4px_4px_0px_rgba(79,70,229,0.3)]"
|
||||
placeholder="Retapez le mot de passe">
|
||||
</div>
|
||||
|
||||
@@ -23,18 +23,18 @@
|
||||
<h2 class="text-sm font-black uppercase tracking-widest mb-4">Identite</h2>
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div>
|
||||
<label class="block text-xs font-black uppercase tracking-widest mb-2">Prenom *</label>
|
||||
<input type="text" name="firstName" required placeholder="Prenom"
|
||||
<label for="firstName" class="block text-xs font-black uppercase tracking-widest mb-2">Prenom *</label>
|
||||
<input type="text" id="firstName" name="firstName" required placeholder="Prenom"
|
||||
class="w-full px-4 py-3 border-2 border-gray-900 bg-white text-sm font-bold focus:outline-none focus:border-indigo-600 focus:shadow-[4px_4px_0px_rgba(79,70,229,0.3)]">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-black uppercase tracking-widest mb-2">Nom *</label>
|
||||
<input type="text" name="lastName" required placeholder="Nom"
|
||||
<label for="lastName" class="block text-xs font-black uppercase tracking-widest mb-2">Nom *</label>
|
||||
<input type="text" id="lastName" name="lastName" required placeholder="Nom"
|
||||
class="w-full px-4 py-3 border-2 border-gray-900 bg-white text-sm font-bold focus:outline-none focus:border-indigo-600 focus:shadow-[4px_4px_0px_rgba(79,70,229,0.3)]">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-black uppercase tracking-widest mb-2">Email *</label>
|
||||
<input type="email" name="email" required placeholder="email@exemple.fr"
|
||||
<label for="email" class="block text-xs font-black uppercase tracking-widest mb-2">Email *</label>
|
||||
<input type="email" id="email" name="email" required placeholder="email@exemple.fr"
|
||||
class="w-full px-4 py-3 border-2 border-gray-900 bg-white text-sm font-bold focus:outline-none focus:border-indigo-600 focus:shadow-[4px_4px_0px_rgba(79,70,229,0.3)]">
|
||||
</div>
|
||||
</div>
|
||||
@@ -44,18 +44,18 @@
|
||||
<h2 class="text-sm font-black uppercase tracking-widest mb-4">Entreprise</h2>
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div>
|
||||
<label class="block text-xs font-black uppercase tracking-widest mb-2">Raison sociale</label>
|
||||
<input type="text" name="raisonSociale" placeholder="Nom de l'entreprise"
|
||||
<label for="raisonSociale" class="block text-xs font-black uppercase tracking-widest mb-2">Raison sociale</label>
|
||||
<input type="text" id="raisonSociale" name="raisonSociale" placeholder="Nom de l'entreprise"
|
||||
class="w-full px-4 py-3 border-2 border-gray-900 bg-white text-sm font-bold focus:outline-none focus:border-indigo-600 focus:shadow-[4px_4px_0px_rgba(79,70,229,0.3)]">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-black uppercase tracking-widest mb-2">SIRET</label>
|
||||
<input type="text" name="siret" maxlength="14" placeholder="12345678901234"
|
||||
<label for="siret" class="block text-xs font-black uppercase tracking-widest mb-2">SIRET</label>
|
||||
<input type="text" id="siret" name="siret" maxlength="14" placeholder="12345678901234"
|
||||
class="w-full px-4 py-3 border-2 border-gray-900 bg-white text-sm font-bold focus:outline-none focus:border-indigo-600 focus:shadow-[4px_4px_0px_rgba(79,70,229,0.3)]">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-black uppercase tracking-widest mb-2">Telephone</label>
|
||||
<input type="tel" name="phone" placeholder="06 12 34 56 78"
|
||||
<label for="phone" class="block text-xs font-black uppercase tracking-widest mb-2">Telephone</label>
|
||||
<input type="tel" id="phone" name="phone" placeholder="06 12 34 56 78"
|
||||
class="w-full px-4 py-3 border-2 border-gray-900 bg-white text-sm font-bold focus:outline-none focus:border-indigo-600 focus:shadow-[4px_4px_0px_rgba(79,70,229,0.3)]">
|
||||
</div>
|
||||
</div>
|
||||
@@ -65,19 +65,19 @@
|
||||
<h2 class="text-sm font-black uppercase tracking-widest mb-4">Adresse</h2>
|
||||
<div class="flex flex-col gap-4">
|
||||
<div>
|
||||
<label class="block text-xs font-black uppercase tracking-widest mb-2">Adresse</label>
|
||||
<input type="text" name="address" placeholder="Numero et rue"
|
||||
<label for="address" class="block text-xs font-black uppercase tracking-widest mb-2">Adresse</label>
|
||||
<input type="text" id="address" name="address" placeholder="Numero et rue"
|
||||
class="w-full px-4 py-3 border-2 border-gray-900 bg-white text-sm font-bold focus:outline-none focus:border-indigo-600 focus:shadow-[4px_4px_0px_rgba(79,70,229,0.3)]">
|
||||
</div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label class="block text-xs font-black uppercase tracking-widest mb-2">Code postal</label>
|
||||
<input type="text" name="zipCode" maxlength="10" placeholder="02800"
|
||||
<label for="zipCode" class="block text-xs font-black uppercase tracking-widest mb-2">Code postal</label>
|
||||
<input type="text" id="zipCode" name="zipCode" maxlength="10" placeholder="02800"
|
||||
class="w-full px-4 py-3 border-2 border-gray-900 bg-white text-sm font-bold focus:outline-none focus:border-indigo-600 focus:shadow-[4px_4px_0px_rgba(79,70,229,0.3)]">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-black uppercase tracking-widest mb-2">Ville</label>
|
||||
<input type="text" name="city" placeholder="Beautor"
|
||||
<label for="city" class="block text-xs font-black uppercase tracking-widest mb-2">Ville</label>
|
||||
<input type="text" id="city" name="city" placeholder="Beautor"
|
||||
class="w-full px-4 py-3 border-2 border-gray-900 bg-white text-sm font-bold focus:outline-none focus:border-indigo-600 focus:shadow-[4px_4px_0px_rgba(79,70,229,0.3)]">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -25,24 +25,24 @@
|
||||
<h2 class="text-sm font-black uppercase tracking-widest mb-4">Entreprise</h2>
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div>
|
||||
<label class="block text-xs font-black uppercase tracking-widest mb-2">Raison sociale</label>
|
||||
<input type="text" name="raisonSociale" value="{{ revendeur.raisonSociale }}" placeholder="Nom de l'entreprise"
|
||||
<label for="raisonSociale" class="block text-xs font-black uppercase tracking-widest mb-2">Raison sociale</label>
|
||||
<input type="text" id="raisonSociale" name="raisonSociale" value="{{ revendeur.raisonSociale }}" placeholder="Nom de l'entreprise"
|
||||
class="w-full px-4 py-3 border-2 border-gray-900 bg-white text-sm font-bold focus:outline-none focus:border-indigo-600 focus:shadow-[4px_4px_0px_rgba(79,70,229,0.3)]">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-black uppercase tracking-widest mb-2">SIRET</label>
|
||||
<input type="text" name="siret" value="{{ revendeur.siret }}" maxlength="14" placeholder="12345678901234"
|
||||
<label for="siret" class="block text-xs font-black uppercase tracking-widest mb-2">SIRET</label>
|
||||
<input type="text" id="siret" name="siret" value="{{ revendeur.siret }}" maxlength="14" placeholder="12345678901234"
|
||||
class="w-full px-4 py-3 border-2 border-gray-900 bg-white text-sm font-bold focus:outline-none focus:border-indigo-600 focus:shadow-[4px_4px_0px_rgba(79,70,229,0.3)]">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-black uppercase tracking-widest mb-2">Telephone</label>
|
||||
<input type="tel" name="phone" value="{{ revendeur.phone }}" placeholder="06 12 34 56 78"
|
||||
<label for="phone" class="block text-xs font-black uppercase tracking-widest mb-2">Telephone</label>
|
||||
<input type="tel" id="phone" name="phone" value="{{ revendeur.phone }}" placeholder="06 12 34 56 78"
|
||||
class="w-full px-4 py-3 border-2 border-gray-900 bg-white text-sm font-bold focus:outline-none focus:border-indigo-600 focus:shadow-[4px_4px_0px_rgba(79,70,229,0.3)]">
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4">
|
||||
<label class="block text-xs font-black uppercase tracking-widest mb-2">Email</label>
|
||||
<input type="email" name="email" value="{{ revendeur.email }}" placeholder="email@exemple.fr"
|
||||
<label for="email" class="block text-xs font-black uppercase tracking-widest mb-2">Email</label>
|
||||
<input type="email" id="email" name="email" value="{{ revendeur.email }}" placeholder="email@exemple.fr"
|
||||
class="w-full px-4 py-3 border-2 border-gray-900 bg-white text-sm font-bold focus:outline-none focus:border-indigo-600 focus:shadow-[4px_4px_0px_rgba(79,70,229,0.3)]">
|
||||
</div>
|
||||
</section>
|
||||
@@ -51,19 +51,19 @@
|
||||
<h2 class="text-sm font-black uppercase tracking-widest mb-4">Adresse</h2>
|
||||
<div class="flex flex-col gap-4">
|
||||
<div>
|
||||
<label class="block text-xs font-black uppercase tracking-widest mb-2">Adresse</label>
|
||||
<input type="text" name="address" value="{{ revendeur.address }}" placeholder="Numero et rue"
|
||||
<label for="address" class="block text-xs font-black uppercase tracking-widest mb-2">Adresse</label>
|
||||
<input type="text" id="address" name="address" value="{{ revendeur.address }}" placeholder="Numero et rue"
|
||||
class="w-full px-4 py-3 border-2 border-gray-900 bg-white text-sm font-bold focus:outline-none focus:border-indigo-600 focus:shadow-[4px_4px_0px_rgba(79,70,229,0.3)]">
|
||||
</div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label class="block text-xs font-black uppercase tracking-widest mb-2">Code postal</label>
|
||||
<input type="text" name="zipCode" value="{{ revendeur.zipCode }}" maxlength="10" placeholder="02800"
|
||||
<label for="zipCode" class="block text-xs font-black uppercase tracking-widest mb-2">Code postal</label>
|
||||
<input type="text" id="zipCode" name="zipCode" value="{{ revendeur.zipCode }}" maxlength="10" placeholder="02800"
|
||||
class="w-full px-4 py-3 border-2 border-gray-900 bg-white text-sm font-bold focus:outline-none focus:border-indigo-600 focus:shadow-[4px_4px_0px_rgba(79,70,229,0.3)]">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-black uppercase tracking-widest mb-2">Ville</label>
|
||||
<input type="text" name="city" value="{{ revendeur.city }}" placeholder="Beautor"
|
||||
<label for="city" class="block text-xs font-black uppercase tracking-widest mb-2">Ville</label>
|
||||
<input type="text" id="city" name="city" value="{{ revendeur.city }}" placeholder="Beautor"
|
||||
class="w-full px-4 py-3 border-2 border-gray-900 bg-white text-sm font-bold focus:outline-none focus:border-indigo-600 focus:shadow-[4px_4px_0px_rgba(79,70,229,0.3)]">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<form method="get" action="{{ path('app_admin_stats_index') }}" class="mb-6 border-2 border-gray-900 bg-white p-4 shadow-[4px_4px_0px_rgba(0,0,0,1)]">
|
||||
<div class="flex flex-wrap items-end gap-3">
|
||||
<div>
|
||||
<label class="block text-[9px] font-black uppercase tracking-widest text-gray-400 mb-1">Periode</label>
|
||||
<label for="stats-period-select" class="block text-[9px] font-black uppercase tracking-widest text-gray-400 mb-1">Periode</label>
|
||||
<select name="period" id="stats-period-select" class="px-3 py-2 border-2 border-gray-900 text-xs font-bold bg-white">
|
||||
<option value="current" {{ period == 'current' ? 'selected' }}>Mois en cours</option>
|
||||
<option value="1" {{ period == '1' ? 'selected' }}>-1 mois</option>
|
||||
@@ -25,12 +25,12 @@
|
||||
</div>
|
||||
<div id="stats-custom-range" class="{{ period == 'custom' ? '' : 'hidden' }} flex items-end gap-3">
|
||||
<div>
|
||||
<label class="block text-[9px] font-black uppercase tracking-widest text-gray-400 mb-1">Du</label>
|
||||
<input type="date" name="from" value="{{ dateFrom }}" class="px-3 py-2 border-2 border-gray-900 text-xs font-bold bg-white">
|
||||
<label for="from" class="block text-[9px] font-black uppercase tracking-widest text-gray-400 mb-1">Du</label>
|
||||
<input type="date" id="from" name="from" value="{{ dateFrom }}" class="px-3 py-2 border-2 border-gray-900 text-xs font-bold bg-white">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-[9px] font-black uppercase tracking-widest text-gray-400 mb-1">Au</label>
|
||||
<input type="date" name="to" value="{{ dateTo }}" class="px-3 py-2 border-2 border-gray-900 text-xs font-bold bg-white">
|
||||
<label for="to" class="block text-[9px] font-black uppercase tracking-widest text-gray-400 mb-1">Au</label>
|
||||
<input type="date" id="to" name="to" value="{{ dateTo }}" class="px-3 py-2 border-2 border-gray-900 text-xs font-bold bg-white">
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="px-4 py-2 border-2 border-gray-900 bg-[#fabf04] font-black uppercase text-[10px] tracking-widest hover:bg-yellow-500 transition-all">
|
||||
|
||||
@@ -54,29 +54,29 @@
|
||||
<h2 class="text-sm font-black uppercase tracking-widest mb-3">Publier un message</h2>
|
||||
<form method="post" action="{{ path('app_admin_status_message_create') }}" class="flex flex-wrap items-end gap-3">
|
||||
<div class="w-40">
|
||||
<label class="block text-[9px] font-black uppercase tracking-widest text-gray-400 mb-1">Service</label>
|
||||
<select name="service_id" required class="w-full px-3 py-2 border-2 border-gray-900 text-xs font-bold bg-white">
|
||||
<label for="service_id" class="block text-[9px] font-black uppercase tracking-widest text-gray-400 mb-1">Service</label>
|
||||
<select id="service_id" name="service_id" required class="w-full px-3 py-2 border-2 border-gray-900 text-xs font-bold bg-white">
|
||||
{% for service in allServices %}
|
||||
<option value="{{ service.id }}">{{ service.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="w-24">
|
||||
<label class="block text-[9px] font-black uppercase tracking-widest text-gray-400 mb-1">Severite</label>
|
||||
<select name="severity" class="w-full px-3 py-2 border-2 border-gray-900 text-xs font-bold bg-white">
|
||||
<label for="severity" class="block text-[9px] font-black uppercase tracking-widest text-gray-400 mb-1">Severite</label>
|
||||
<select id="severity" name="severity" class="w-full px-3 py-2 border-2 border-gray-900 text-xs font-bold bg-white">
|
||||
<option value="info">Info</option>
|
||||
<option value="warning">Warning</option>
|
||||
<option value="critical">Critical</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="flex-1 min-w-[120px]">
|
||||
<label class="block text-[9px] font-black uppercase tracking-widest text-gray-400 mb-1">Titre</label>
|
||||
<input type="text" name="title" required placeholder="Ex: Maintenance prevue..."
|
||||
<label for="title" class="block text-[9px] font-black uppercase tracking-widest text-gray-400 mb-1">Titre</label>
|
||||
<input id="title" type="text" name="title" required placeholder="Ex: Maintenance prevue..."
|
||||
class="w-full px-3 py-2 border-2 border-gray-900 text-xs font-bold bg-white">
|
||||
</div>
|
||||
<div class="flex-1 min-w-[150px]">
|
||||
<label class="block text-[9px] font-black uppercase tracking-widest text-gray-400 mb-1">Message</label>
|
||||
<input type="text" name="content" required placeholder="Details..."
|
||||
<label for="content" class="block text-[9px] font-black uppercase tracking-widest text-gray-400 mb-1">Message</label>
|
||||
<input id="content" type="text" name="content" required placeholder="Details..."
|
||||
class="w-full px-3 py-2 border-2 border-gray-900 text-xs font-bold bg-white">
|
||||
</div>
|
||||
<button type="submit" class="px-4 py-2 border-2 border-gray-900 bg-[#fabf04] font-black uppercase text-[10px] tracking-widest hover:bg-yellow-500 transition-all">
|
||||
@@ -155,8 +155,8 @@
|
||||
<div class="px-4 py-3 border-t border-gray-200 bg-gray-50">
|
||||
<form method="post" action="{{ path('app_admin_status_update', {id: service.id}) }}" class="flex flex-wrap items-end gap-3">
|
||||
<div>
|
||||
<label class="block text-[9px] font-black uppercase tracking-widest text-gray-400 mb-1">Statut</label>
|
||||
<select name="status" class="px-3 py-1.5 border-2 border-gray-900 text-xs font-bold bg-white">
|
||||
<label for="status_{{ service.id }}" class="block text-[9px] font-black uppercase tracking-widest text-gray-400 mb-1">Statut</label>
|
||||
<select id="status_{{ service.id }}" name="status" class="px-3 py-1.5 border-2 border-gray-900 text-xs font-bold bg-white">
|
||||
<option value="up" {{ service.status == 'up' ? 'selected' }}>Up</option>
|
||||
<option value="degraded" {{ service.status == 'degraded' ? 'selected' }}>Degrade</option>
|
||||
<option value="down" {{ service.status == 'down' ? 'selected' }}>Down</option>
|
||||
@@ -165,8 +165,8 @@
|
||||
</select>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<label class="block text-[9px] font-black uppercase tracking-widest text-gray-400 mb-1">Message</label>
|
||||
<input type="text" name="message" value="{{ service.message }}" placeholder="Message optionnel..."
|
||||
<label for="message_{{ service.id }}" class="block text-[9px] font-black uppercase tracking-widest text-gray-400 mb-1">Message</label>
|
||||
<input id="message_{{ service.id }}" type="text" name="message" value="{{ service.message }}" placeholder="Message optionnel..."
|
||||
class="w-full px-3 py-1.5 border-2 border-gray-900 text-xs font-bold bg-white">
|
||||
</div>
|
||||
<button type="submit" class="px-4 py-1.5 border-2 border-gray-900 bg-[#fabf04] font-black uppercase text-[10px] tracking-widest hover:bg-yellow-500 transition-all">
|
||||
|
||||
@@ -25,13 +25,13 @@
|
||||
<div class="border-2 border-gray-900 bg-white p-6 shadow-[4px_4px_0px_rgba(0,0,0,1)]">
|
||||
<form method="post" action="{{ path('app_admin_status_category_create') }}" class="flex flex-wrap items-end gap-4">
|
||||
<div class="flex-1 min-w-[200px]">
|
||||
<label class="block text-xs font-black uppercase tracking-widest mb-2">Nom</label>
|
||||
<input type="text" name="name" required placeholder="Ex: Hebergement Web"
|
||||
<label for="category_name" class="block text-xs font-black uppercase tracking-widest mb-2">Nom</label>
|
||||
<input id="category_name" type="text" name="name" required placeholder="Ex: Hebergement Web"
|
||||
class="w-full px-4 py-3 border-2 border-gray-900 bg-white text-sm font-bold focus:outline-none focus:border-indigo-600">
|
||||
</div>
|
||||
<div class="w-24">
|
||||
<label class="block text-xs font-black uppercase tracking-widest mb-2">Position</label>
|
||||
<input type="number" name="position" value="0"
|
||||
<label for="category_position" class="block text-xs font-black uppercase tracking-widest mb-2">Position</label>
|
||||
<input id="category_position" type="number" name="position" value="0"
|
||||
class="w-full px-4 py-3 border-2 border-gray-900 bg-white text-sm font-bold focus:outline-none focus:border-indigo-600">
|
||||
</div>
|
||||
<button type="submit" class="px-6 py-3 border-2 border-gray-900 bg-[#fabf04] font-black uppercase text-sm tracking-widest hover:bg-yellow-500 transition-all shadow-[4px_4px_0px_rgba(0,0,0,1)] hover:shadow-[2px_2px_0px_rgba(0,0,0,1)] active:shadow-none active:translate-x-1 active:translate-y-1">
|
||||
@@ -98,18 +98,18 @@
|
||||
<form method="post" action="{{ path('app_admin_status_service_create') }}" class="flex flex-wrap items-end gap-3">
|
||||
<input type="hidden" name="category_id" value="{{ category.id }}">
|
||||
<div class="flex-1 min-w-[120px]">
|
||||
<label class="block text-[9px] font-black uppercase tracking-widest text-gray-400 mb-1">Nom</label>
|
||||
<input type="text" name="name" required placeholder="Ex: CRM Ecosplay"
|
||||
<label for="service_name_{{ category.id }}" class="block text-[9px] font-black uppercase tracking-widest text-gray-400 mb-1">Nom</label>
|
||||
<input id="service_name_{{ category.id }}" type="text" name="name" required placeholder="Ex: CRM Ecosplay"
|
||||
class="w-full px-3 py-2 border-2 border-gray-900 bg-white text-xs font-bold focus:outline-none focus:border-indigo-600">
|
||||
</div>
|
||||
<div class="flex-1 min-w-[120px]">
|
||||
<label class="block text-[9px] font-black uppercase tracking-widest text-gray-400 mb-1">URL</label>
|
||||
<input type="url" name="url" placeholder="https://..."
|
||||
<label for="service_url_{{ category.id }}" class="block text-[9px] font-black uppercase tracking-widest text-gray-400 mb-1">URL</label>
|
||||
<input id="service_url_{{ category.id }}" type="url" name="url" placeholder="https://..."
|
||||
class="w-full px-3 py-2 border-2 border-gray-900 bg-white text-xs font-bold focus:outline-none focus:border-indigo-600">
|
||||
</div>
|
||||
<div class="w-28">
|
||||
<label class="block text-[9px] font-black uppercase tracking-widest text-gray-400 mb-1">Type</label>
|
||||
<select name="external_type" class="w-full px-3 py-2 border-2 border-gray-900 bg-white text-xs font-bold">
|
||||
<label for="service_external_type_{{ category.id }}" class="block text-[9px] font-black uppercase tracking-widest text-gray-400 mb-1">Type</label>
|
||||
<select id="service_external_type_{{ category.id }}" name="external_type" class="w-full px-3 py-2 border-2 border-gray-900 bg-white text-xs font-bold">
|
||||
<option value="">Interne</option>
|
||||
<option value="stripe">Stripe</option>
|
||||
<option value="docuseal">DocuSeal</option>
|
||||
@@ -118,8 +118,8 @@
|
||||
</select>
|
||||
</div>
|
||||
<div class="w-16">
|
||||
<label class="block text-[9px] font-black uppercase tracking-widest text-gray-400 mb-1">Pos.</label>
|
||||
<input type="number" name="position" value="0"
|
||||
<label for="service_position_{{ category.id }}" class="block text-[9px] font-black uppercase tracking-widest text-gray-400 mb-1">Pos.</label>
|
||||
<input id="service_position_{{ category.id }}" type="number" name="position" value="0"
|
||||
class="w-full px-3 py-2 border-2 border-gray-900 bg-white text-xs font-bold focus:outline-none focus:border-indigo-600">
|
||||
</div>
|
||||
<button type="submit" class="px-4 py-2 border-2 border-gray-900 bg-[#fabf04] font-black uppercase text-[10px] tracking-widest hover:bg-yellow-500 transition-all">
|
||||
|
||||
@@ -6,13 +6,13 @@
|
||||
<title>{% block title %}CRM Ecosplay{% endblock %}</title>
|
||||
</head>
|
||||
<body style="margin: 0; padding: 0; background-color: #fbfbfb; font-family: Arial, Helvetica, sans-serif; color: #111827;">
|
||||
<table width="100%" cellpadding="0" cellspacing="0" style="background-color: #fbfbfb; padding: 40px 0;">
|
||||
<table role="presentation" width="100%" style="background-color: #fbfbfb; padding: 40px 0;">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<p style="font-size: 11px; color: #666; margin: 0 0 16px 0; text-align: center;">
|
||||
Cet email ne s'affiche pas correctement ? <a href="__VIEW_URL__" style="color: #4338ca; text-decoration: underline;">Voir en ligne</a>
|
||||
</p>
|
||||
<table width="600" cellpadding="0" cellspacing="0" style="background-color: #ffffff; border: 4px solid #111827;">
|
||||
<table role="presentation" width="600" style="background-color: #ffffff; border: 4px solid #111827;">
|
||||
<tr>
|
||||
<td style="background-color: #fabf04; border-bottom: 4px solid #111827; padding: 20px; text-align: center;">
|
||||
<img src="https://crm.e-cosplay.fr/logo.jpg" alt="CRM Ecosplay" width="120" style="display: block; margin: 0 auto;">
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
<div style="background: #111827; color: #fff; padding: 20px; margin: 20px 0;">
|
||||
<p style="font-size: 11px; text-transform: uppercase; letter-spacing: 1px; margin: 0 0 12px; opacity: 0.7;">Vos identifiants de connexion</p>
|
||||
<table cellpadding="0" cellspacing="0" style="width: 100%; font-size: 14px;">
|
||||
<table style="width: 100%; font-size: 14px;">
|
||||
<tr>
|
||||
<td style="color: #fabf04; font-weight: bold; padding: 4px 0; width: 120px;">Email</td>
|
||||
<td style="color: #fff; padding: 4px 0;">{{ email }}</td>
|
||||
@@ -45,7 +45,7 @@
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
||||
<table cellpadding="0" cellspacing="0" style="margin: 24px 0;">
|
||||
<table style="margin: 24px 0;">
|
||||
<tr>
|
||||
<td style="background: #fabf04; border: 2px solid #111827; padding: 12px 24px;">
|
||||
<a href="{{ url('app_home') }}" style="color: #111827; font-weight: 900; text-transform: uppercase; font-size: 13px; text-decoration: none; letter-spacing: 1px;">Se connecter</a>
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
<p style="font-size: 14px; line-height: 1.6; margin: 0 0 8px; font-weight: bold;">Pour acceder a votre espace, suivez ces etapes :</p>
|
||||
|
||||
<table cellpadding="0" cellspacing="0" style="width: 100%; margin: 16px 0;">
|
||||
<table style="width: 100%; margin: 16px 0;">
|
||||
<tr>
|
||||
<td style="padding: 12px 16px; border-left: 4px solid #fabf04; background: #f9fafb;">
|
||||
<p style="font-size: 12px; font-weight: 900; text-transform: uppercase; color: #fabf04; margin: 0 0 4px;">Etape 1</p>
|
||||
@@ -36,7 +36,7 @@
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<table cellpadding="0" cellspacing="0" style="margin: 24px 0; width: 100%;">
|
||||
<table style="margin: 24px 0; width: 100%;">
|
||||
<tr>
|
||||
<td style="background: #fabf04; border: 2px solid #111827; padding: 14px 24px; text-align: center;">
|
||||
<a href="{{ setPasswordUrl }}" style="color: #111827; font-weight: 900; text-transform: uppercase; font-size: 14px; text-decoration: none; letter-spacing: 1px;">Definir mon mot de passe</a>
|
||||
@@ -44,7 +44,7 @@
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<table cellpadding="0" cellspacing="0" style="margin: 0 0 24px; width: 100%;">
|
||||
<table style="margin: 0 0 24px; width: 100%;">
|
||||
<tr>
|
||||
<td style="background: #fff; border: 2px solid #111827; padding: 12px 24px; text-align: center;">
|
||||
<a href="{{ url('app_home') }}" style="color: #111827; font-weight: 900; text-transform: uppercase; font-size: 12px; text-decoration: none; letter-spacing: 1px;">Acceder a la page de connexion</a>
|
||||
@@ -54,7 +54,7 @@
|
||||
|
||||
<div style="background: #f9fafb; padding: 16px; border: 1px solid #e5e7eb; margin: 16px 0;">
|
||||
<p style="font-size: 12px; font-weight: bold; margin: 0 0 8px;">Informations de votre compte :</p>
|
||||
<table cellpadding="0" cellspacing="0" style="font-size: 12px; width: 100%;">
|
||||
<table style="font-size: 12px; width: 100%;">
|
||||
<tr>
|
||||
<td style="padding: 2px 0; color: #666; width: 120px;">Code revendeur</td>
|
||||
<td style="padding: 2px 0; font-weight: bold; font-family: monospace;">{{ codeRevendeur }}</td>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<h1 style="font-size: 20px; font-weight: bold; text-transform: uppercase; margin: 0 0 16px 0;">Attestation RGPD signee</h1>
|
||||
<p style="font-size: 14px; line-height: 1.6; margin: 0 0 16px 0;">Bonjour,</p>
|
||||
<p style="font-size: 14px; line-height: 1.6; margin: 0 0 16px 0;">Suite a votre demande, vous trouverez en piece jointe votre <strong>attestation d'{{ typeName }}</strong>, signee electroniquement par l'association E-Cosplay.</p>
|
||||
<table cellpadding="0" cellspacing="0" style="margin: 16px 0; font-size: 13px; width: 100%;">
|
||||
<table style="margin: 16px 0; font-size: 13px; width: 100%;">
|
||||
<tr>
|
||||
<td style="padding: 8px 12px; background: #f9fafb; border-left: 3px solid #fabf04; font-weight: bold;">Reference</td>
|
||||
<td style="padding: 8px 12px; background: #f9fafb;">{{ attestation.reference }}</td>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Contrat Revendeur - E-Cosplay</title>
|
||||
<style>
|
||||
@page { margin: 0; size: A4; }
|
||||
body { font-family: Arial, Helvetica, sans-serif; font-size: 10px; color: #111827; margin: 0; padding: 0; line-height: 1.6; }
|
||||
@@ -39,7 +40,7 @@
|
||||
|
||||
<p style="margin-bottom: 12px; font-weight: 700;">Entre les soussignes :</p>
|
||||
|
||||
<table class="parties" cellpadding="0" cellspacing="0">
|
||||
<table class="parties">
|
||||
<tr>
|
||||
<td class="left">
|
||||
<span class="label">Le Prestataire</span>
|
||||
@@ -183,7 +184,7 @@
|
||||
|
||||
<p style="margin-top: 14px; font-weight: 700;">Fait en deux exemplaires, a Beautor, le {{ date|date('d/m/Y') }}.</p>
|
||||
|
||||
<table class="signatures" cellpadding="0" cellspacing="0">
|
||||
<table class="signatures">
|
||||
<tr>
|
||||
<td>
|
||||
<span class="sig-label">Pour l'Association E-Cosplay</span>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Attestation RGPD - Acces aux donnees</title>
|
||||
<style>
|
||||
@page { margin: 0; size: A4; }
|
||||
body { font-family: Arial, Helvetica, sans-serif; font-size: 10px; color: #111827; margin: 0; padding: 0; }
|
||||
@@ -47,7 +48,7 @@
|
||||
<span class="doc-type">Droit d'acces</span>
|
||||
<h1>Donnees personnelles</h1>
|
||||
<div class="subtitle">RGPD — Article 15</div>
|
||||
<table class="info-grid" cellpadding="0" cellspacing="0">
|
||||
<table class="info-grid">
|
||||
<tr>
|
||||
<td class="info-cell" width="25%"><span class="info-label">Reference</span><span class="info-value">{{ attestation.reference }}</span></td>
|
||||
<td class="info-cell" width="25%"><span class="info-label">Date</span><span class="info-value">{{ date|date('d/m/Y a H:i') }}</span></td>
|
||||
@@ -74,7 +75,7 @@
|
||||
{% endfor %}
|
||||
|
||||
<div class="verify-box">
|
||||
<table cellpadding="0" cellspacing="0">
|
||||
<table>
|
||||
<tr>
|
||||
<td class="verify-qr"><img src="{{ qrcode }}" alt="QR Code"></td>
|
||||
<td class="verify-info">
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Attestation RGPD - Suppression des donnees</title>
|
||||
<style>
|
||||
@page { margin: 0; size: A4; }
|
||||
body { font-family: Arial, Helvetica, sans-serif; font-size: 11px; color: #111827; margin: 0; padding: 0; }
|
||||
@@ -45,7 +46,7 @@
|
||||
<span class="doc-type">Suppression definitive</span>
|
||||
<h1>Attestation de suppression des donnees</h1>
|
||||
<div class="subtitle">RGPD — Article 17</div>
|
||||
<table class="info-grid" cellpadding="0" cellspacing="0">
|
||||
<table class="info-grid">
|
||||
<tr>
|
||||
<td class="info-cell" width="33%"><span class="info-label">Reference</span><span class="info-value">{{ attestation.reference }}</span></td>
|
||||
<td class="info-cell" width="33%"><span class="info-label">Date</span><span class="info-value">{{ date|date('d/m/Y a H:i') }}</span></td>
|
||||
@@ -63,7 +64,7 @@
|
||||
</div>
|
||||
<div class="warning">Cette suppression est irreversible. Aucune donnee relative a cette adresse IP ne subsiste dans nos bases de donnees.</div>
|
||||
<div class="verify-box">
|
||||
<table cellpadding="0" cellspacing="0">
|
||||
<table>
|
||||
<tr>
|
||||
<td class="verify-qr"><img src="{{ qrcode }}" alt="QR Code"></td>
|
||||
<td class="verify-info">
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Attestation RGPD - Absence de donnees</title>
|
||||
<style>
|
||||
@page { margin: 0; size: A4; }
|
||||
body { font-family: Arial, Helvetica, sans-serif; font-size: 11px; color: #111827; margin: 0; padding: 0; }
|
||||
@@ -44,7 +45,7 @@
|
||||
<span class="doc-type">Document officiel</span>
|
||||
<h1>Attestation d'absence de donnees</h1>
|
||||
<div class="subtitle">RGPD — Article 15</div>
|
||||
<table class="info-grid" cellpadding="0" cellspacing="0">
|
||||
<table class="info-grid">
|
||||
<tr>
|
||||
<td class="info-cell" width="33%"><span class="info-label">Reference</span><span class="info-value">{{ attestation.reference }}</span></td>
|
||||
<td class="info-cell" width="33%"><span class="info-label">Date</span><span class="info-value">{{ date|date('d/m/Y a H:i') }}</span></td>
|
||||
@@ -62,7 +63,7 @@
|
||||
<p>Aucune donnee personnelle (identifiants de session, evenements de navigation, informations techniques) n'est stockee dans nos bases de donnees pour cette adresse IP.</p>
|
||||
</div>
|
||||
<div class="verify-box">
|
||||
<table cellpadding="0" cellspacing="0">
|
||||
<table>
|
||||
<tr>
|
||||
<td class="verify-qr"><img src="{{ qrcode }}" alt="QR Code"></td>
|
||||
<td class="verify-info">
|
||||
|
||||
@@ -8,14 +8,17 @@ use App\Security\KeycloakAuthenticator;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use KnpU\OAuth2ClientBundle\Client\ClientRegistry;
|
||||
use KnpU\OAuth2ClientBundle\Client\OAuth2ClientInterface;
|
||||
use League\OAuth2\Client\Provider\ResourceOwnerInterface;
|
||||
use League\OAuth2\Client\Token\AccessToken;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Session\Flash\FlashBagInterface;
|
||||
use Symfony\Component\HttpFoundation\Session\FlashBagAwareSessionInterface;
|
||||
use Symfony\Component\Routing\RouterInterface;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
use Symfony\Component\Security\Core\Exception\AuthenticationException;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;
|
||||
|
||||
class KeycloakAuthenticatorTest extends TestCase
|
||||
@@ -28,16 +31,16 @@ class KeycloakAuthenticatorTest extends TestCase
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->clientRegistry = $this->createMock(ClientRegistry::class);
|
||||
$this->em = $this->createMock(EntityManagerInterface::class);
|
||||
$this->userRepository = $this->createMock(UserRepository::class);
|
||||
$this->router = $this->createMock(RouterInterface::class);
|
||||
$this->clientRegistry = $this->createStub(ClientRegistry::class);
|
||||
$this->em = $this->createStub(EntityManagerInterface::class);
|
||||
$this->userRepository = $this->createStub(UserRepository::class);
|
||||
$this->router = $this->createStub(RouterInterface::class);
|
||||
|
||||
$this->authenticator = new KeycloakAuthenticator(
|
||||
$this->clientRegistry,
|
||||
$this->em,
|
||||
$this->userRepository,
|
||||
$this->router
|
||||
$this->router,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -54,34 +57,44 @@ class KeycloakAuthenticatorTest extends TestCase
|
||||
public function testAuthenticate(): void
|
||||
{
|
||||
$request = new Request();
|
||||
$client = $this->createMock(OAuth2ClientInterface::class);
|
||||
$client = $this->createStub(OAuth2ClientInterface::class);
|
||||
|
||||
$accessToken = new AccessToken(['access_token' => 'fake-token']);
|
||||
|
||||
$this->clientRegistry->method('getClient')->with('keycloak')->willReturn($client);
|
||||
$this->clientRegistry->method('getClient')->willReturn($client);
|
||||
$client->method('getAccessToken')->willReturn($accessToken);
|
||||
|
||||
$passport = $this->authenticator->authenticate($request);
|
||||
$this->assertInstanceOf(SelfValidatingPassport::class, $passport);
|
||||
|
||||
$userBadge = $passport->getBadge(\Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge::class);
|
||||
$userBadge = $passport->getBadge(UserBadge::class);
|
||||
$this->assertNotNull($userBadge);
|
||||
|
||||
// Test the user loader callback
|
||||
$keycloakUser = $this->createMock(\League\OAuth2\Client\Provider\ResourceOwnerInterface::class);
|
||||
$keycloakUser = $this->createStub(ResourceOwnerInterface::class);
|
||||
$keycloakUser->method('toArray')->willReturn([
|
||||
'sub' => '123',
|
||||
'email' => 'test@example.com',
|
||||
'given_name' => 'John',
|
||||
'family_name' => 'Doe',
|
||||
'groups' => ['super_admin_asso']
|
||||
'groups' => ['super_admin_asso'],
|
||||
]);
|
||||
|
||||
$client->method('fetchUserFromToken')->willReturn($keycloakUser);
|
||||
$this->userRepository->method('findOneBy')->willReturn(null);
|
||||
|
||||
$this->em->expects($this->once())->method('persist');
|
||||
$this->em->expects($this->once())->method('flush');
|
||||
$em = $this->createMock(EntityManagerInterface::class);
|
||||
$em->expects($this->once())->method('persist');
|
||||
$em->expects($this->once())->method('flush');
|
||||
|
||||
// Recreate authenticator with mock em for expectations
|
||||
$authenticator = new KeycloakAuthenticator(
|
||||
$this->clientRegistry,
|
||||
$em,
|
||||
$this->userRepository,
|
||||
$this->router,
|
||||
);
|
||||
$passport = $authenticator->authenticate($request);
|
||||
$userBadge = $passport->getBadge(UserBadge::class);
|
||||
|
||||
$user = $userBadge->getUser();
|
||||
$this->assertInstanceOf(User::class, $user);
|
||||
@@ -93,27 +106,28 @@ class KeycloakAuthenticatorTest extends TestCase
|
||||
public function testAuthenticateExistingUserByEmail(): void
|
||||
{
|
||||
$request = new Request();
|
||||
$client = $this->createMock(OAuth2ClientInterface::class);
|
||||
$client = $this->createStub(OAuth2ClientInterface::class);
|
||||
$accessToken = new AccessToken(['access_token' => 'fake-token']);
|
||||
$this->clientRegistry->method('getClient')->willReturn($client);
|
||||
$client->method('getAccessToken')->willReturn($accessToken);
|
||||
|
||||
$passport = $this->authenticator->authenticate($request);
|
||||
$userBadge = $passport->getBadge(\Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge::class);
|
||||
$userBadge = $passport->getBadge(UserBadge::class);
|
||||
|
||||
$keycloakUser = $this->createMock(\League\OAuth2\Client\Provider\ResourceOwnerInterface::class);
|
||||
$keycloakUser = $this->createStub(ResourceOwnerInterface::class);
|
||||
$keycloakUser->method('toArray')->willReturn([
|
||||
'sub' => '123',
|
||||
'email' => 'existing@example.com',
|
||||
'groups' => []
|
||||
'groups' => [],
|
||||
]);
|
||||
$client->method('fetchUserFromToken')->willReturn($keycloakUser);
|
||||
|
||||
$existingUser = new User();
|
||||
$this->userRepository->method('findOneBy')->willReturnCallback(function ($criteria) use ($existingUser) {
|
||||
if (isset($criteria['email']) && $criteria['email'] === 'existing@example.com') {
|
||||
if (isset($criteria['email']) && 'existing@example.com' === $criteria['email']) {
|
||||
return $existingUser;
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
@@ -126,8 +140,8 @@ class KeycloakAuthenticatorTest extends TestCase
|
||||
public function testOnAuthenticationSuccess(): void
|
||||
{
|
||||
$request = new Request();
|
||||
$token = $this->createMock(TokenInterface::class);
|
||||
$this->router->method('generate')->with('app_home')->willReturn('/home');
|
||||
$token = $this->createStub(TokenInterface::class);
|
||||
$this->router->method('generate')->willReturn('/home');
|
||||
|
||||
$response = $this->authenticator->onAuthenticationSuccess($request, $token, 'main');
|
||||
$this->assertInstanceOf(RedirectResponse::class, $response);
|
||||
@@ -136,16 +150,16 @@ class KeycloakAuthenticatorTest extends TestCase
|
||||
|
||||
public function testOnAuthenticationFailure(): void
|
||||
{
|
||||
$request = $this->createMock(Request::class);
|
||||
$request = $this->createStub(Request::class);
|
||||
|
||||
$session = $this->createMock(\Symfony\Component\HttpFoundation\Session\FlashBagAwareSessionInterface::class);
|
||||
$flashBag = $this->createMock(\Symfony\Component\HttpFoundation\Session\Flash\FlashBagInterface::class);
|
||||
|
||||
$request->method('getSession')->willReturn($session);
|
||||
$session->method('getFlashBag')->willReturn($flashBag);
|
||||
$flashBag = $this->createMock(FlashBagInterface::class);
|
||||
$flashBag->expects($this->once())->method('add')->with('error', $this->anything());
|
||||
|
||||
$this->router->method('generate')->with('app_home')->willReturn('/home');
|
||||
$session = $this->createStub(FlashBagAwareSessionInterface::class);
|
||||
$session->method('getFlashBag')->willReturn($flashBag);
|
||||
|
||||
$request->method('getSession')->willReturn($session);
|
||||
$this->router->method('generate')->willReturn('/home');
|
||||
|
||||
$response = $this->authenticator->onAuthenticationFailure($request, new AuthenticationException());
|
||||
$this->assertInstanceOf(RedirectResponse::class, $response);
|
||||
@@ -154,7 +168,7 @@ class KeycloakAuthenticatorTest extends TestCase
|
||||
public function testStart(): void
|
||||
{
|
||||
$request = new Request();
|
||||
$this->router->method('generate')->with('app_home')->willReturn('/home');
|
||||
$this->router->method('generate')->willReturn('/home');
|
||||
|
||||
$response = $this->authenticator->start($request);
|
||||
$this->assertInstanceOf(RedirectResponse::class, $response);
|
||||
|
||||
@@ -18,24 +18,22 @@ class LoginSuccessHandlerTest extends TestCase
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->router = $this->createMock(RouterInterface::class);
|
||||
$this->authorizationChecker = $this->createMock(AuthorizationCheckerInterface::class);
|
||||
$this->router = $this->createStub(RouterInterface::class);
|
||||
$this->authorizationChecker = $this->createStub(AuthorizationCheckerInterface::class);
|
||||
$this->handler = new LoginSuccessHandler($this->router, $this->authorizationChecker);
|
||||
}
|
||||
|
||||
#[DataProvider('provideRolesAndRoutes')]
|
||||
public function testOnAuthenticationSuccess(string $role, string $routeName): void
|
||||
{
|
||||
$request = $this->createMock(Request::class);
|
||||
$token = $this->createMock(TokenInterface::class);
|
||||
$request = new Request();
|
||||
$token = $this->createStub(TokenInterface::class);
|
||||
|
||||
$this->authorizationChecker->method('isGranted')
|
||||
->willReturnCallback(fn ($r) => $r === $role);
|
||||
|
||||
$this->router->expects($this->once())
|
||||
->method('generate')
|
||||
->with($routeName)
|
||||
->willReturn('/' . $routeName);
|
||||
$this->router->method('generate')
|
||||
->willReturnCallback(fn ($route) => '/'.$route);
|
||||
|
||||
$response = $this->handler->onAuthenticationSuccess($request, $token);
|
||||
|
||||
@@ -51,15 +49,13 @@ class LoginSuccessHandlerTest extends TestCase
|
||||
|
||||
public function testOnAuthenticationSuccessDefault(): void
|
||||
{
|
||||
$request = $this->createMock(Request::class);
|
||||
$token = $this->createMock(TokenInterface::class);
|
||||
$request = new Request();
|
||||
$token = $this->createStub(TokenInterface::class);
|
||||
|
||||
$this->authorizationChecker->method('isGranted')->willReturn(false);
|
||||
|
||||
$this->router->expects($this->once())
|
||||
->method('generate')
|
||||
->with('app_home')
|
||||
->willReturn('/');
|
||||
$this->router->method('generate')
|
||||
->willReturnCallback(fn ($route) => '/');
|
||||
|
||||
$response = $this->handler->onAuthenticationSuccess($request, $token);
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
namespace App\Tests\Twig;
|
||||
|
||||
use App\Twig\ViteAssetExtension;
|
||||
use Nelmio\SecurityBundle\EventListener\ContentSecurityPolicyListener;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Psr\Cache\CacheItemInterface;
|
||||
use Psr\Cache\CacheItemPoolInterface;
|
||||
@@ -16,7 +15,7 @@ class ViteAssetExtensionTest extends TestCase
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->manifestPath = sys_get_temp_dir().'/manifest.json';
|
||||
$this->cache = $this->createMock(CacheItemPoolInterface::class);
|
||||
$this->cache = $this->createStub(CacheItemPoolInterface::class);
|
||||
}
|
||||
|
||||
protected function tearDown(): void
|
||||
@@ -49,9 +48,11 @@ class ViteAssetExtensionTest extends TestCase
|
||||
{
|
||||
$_ENV['VITE_LOAD'] = '0';
|
||||
|
||||
// Use a dummy to test nonce logic since we can't mock final CSP listener
|
||||
$extension = new class($this->manifestPath, $this->cache) extends ViteAssetExtension {
|
||||
protected function getNonce(): string { return 'fake-nonce'; }
|
||||
protected function getNonce(): string
|
||||
{
|
||||
return 'fake-nonce';
|
||||
}
|
||||
};
|
||||
|
||||
$html = $extension->asset('app.js');
|
||||
@@ -63,11 +64,11 @@ class ViteAssetExtensionTest extends TestCase
|
||||
public function testAssetProdWithCacheHit(): void
|
||||
{
|
||||
$_ENV['VITE_LOAD'] = '1';
|
||||
$item = $this->createMock(CacheItemInterface::class);
|
||||
$item = $this->createStub(CacheItemInterface::class);
|
||||
$item->method('isHit')->willReturn(true);
|
||||
$item->method('get')->willReturn(['app.js' => ['file' => 'app.123.js', 'css' => ['app.123.css']]]);
|
||||
|
||||
$this->cache->method('getItem')->with(ViteAssetExtension::CACHE_KEY)->willReturn($item);
|
||||
$this->cache->method('getItem')->willReturn($item);
|
||||
|
||||
$extension = new ViteAssetExtension($this->manifestPath, $this->cache);
|
||||
$html = $extension->asset('app.js');
|
||||
@@ -79,7 +80,7 @@ class ViteAssetExtensionTest extends TestCase
|
||||
public function testAssetProdWithCacheMissAndMissingFile(): void
|
||||
{
|
||||
$_ENV['VITE_LOAD'] = '1';
|
||||
$item = $this->createMock(CacheItemInterface::class);
|
||||
$item = $this->createStub(CacheItemInterface::class);
|
||||
$item->method('isHit')->willReturn(false);
|
||||
$this->cache->method('getItem')->willReturn($item);
|
||||
|
||||
@@ -100,12 +101,13 @@ class ViteAssetExtensionTest extends TestCase
|
||||
|
||||
$item = $this->createMock(CacheItemInterface::class);
|
||||
$item->method('isHit')->willReturn(false);
|
||||
$this->cache->method('getItem')->willReturn($item);
|
||||
|
||||
$item->expects($this->once())->method('set');
|
||||
$this->cache->expects($this->once())->method('save');
|
||||
|
||||
$extension = new ViteAssetExtension($this->manifestPath, $this->cache);
|
||||
$cache = $this->createMock(CacheItemPoolInterface::class);
|
||||
$cache->method('getItem')->willReturn($item);
|
||||
$cache->expects($this->once())->method('save');
|
||||
|
||||
$extension = new ViteAssetExtension($this->manifestPath, $cache);
|
||||
$html = $extension->asset('app.js');
|
||||
|
||||
$this->assertStringContainsString('/build/app.456.js', $html);
|
||||
@@ -123,11 +125,11 @@ class ViteAssetExtensionTest extends TestCase
|
||||
public function testFaviconsProd(): void
|
||||
{
|
||||
$_ENV['VITE_LOAD'] = '1';
|
||||
$item = $this->createMock(CacheItemInterface::class);
|
||||
$item = $this->createStub(CacheItemInterface::class);
|
||||
$item->method('isHit')->willReturn(true);
|
||||
$item->method('get')->willReturn([
|
||||
'favicon.png' => ['file' => 'favicon.789.png'],
|
||||
'app.js' => ['file' => 'app.js']
|
||||
'app.js' => ['file' => 'app.js'],
|
||||
]);
|
||||
$this->cache->method('getItem')->willReturn($item);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user