feat(form/join): Améliore le formulaire de candidature avec Tailwind et style neubrutaliste.
```
This commit is contained in:
Serreau Jovann
2025-12-26 22:13:35 +01:00
parent 89b318305e
commit 47f1e0e683
6 changed files with 87 additions and 75 deletions

View File

@@ -24,3 +24,7 @@
} }
} }
} }
#join_role {
display: flex;
flex-direction: row;
}

View File

@@ -65,7 +65,7 @@ class JoinController extends AbstractController
'E-Cosplay', 'E-Cosplay',
'[E-Cosplay] - Nouvelle candidature', '[E-Cosplay] - Nouvelle candidature',
'mails/candidat/new.twig', 'mails/candidat/new.twig',
['joint'=>$j], ['join'=>$j],
[new DataPart($content,'candidat.pdf','application/pdf')] [new DataPart($content,'candidat.pdf','application/pdf')]
); );
@@ -74,7 +74,7 @@ class JoinController extends AbstractController
$j->getSurname()." ".$j->getName(), $j->getSurname()." ".$j->getName(),
"[E-Cosplay] - Confirmation de votre candidature", "[E-Cosplay] - Confirmation de votre candidature",
'mails/candidat/confirm.twig', 'mails/candidat/confirm.twig',
['joint'=>$j], ['join'=>$j],
[new DataPart($content,'candidat.pdf','application/pdf')] [new DataPart($content,'candidat.pdf','application/pdf')]
); );
return $this->redirectToRoute('app_recruit_confirmed'); return $this->redirectToRoute('app_recruit_confirmed');

View File

@@ -9,22 +9,27 @@
{%- endblock %} {%- endblock %}
{# ---------- ROW ---------- #} {# ---------- ROW ---------- #}
{# ---------- ROW : Version Esport / Neubrutaliste ---------- #}
{% block form_row %} {% block form_row %}
<div class="mb-5"> <div class="flex flex-col mb-6 last:mb-0 w-full">
{{ form_label(form) }} {# Label avec style forcé #}
<div class="mt-1"> {{ form_label(form, null, {
'label_attr': {'class': 'font-black uppercase italic text-xs tracking-widest text-gray-900 mb-2'}
}) }}
{# Widget (Input, Select, etc.) #}
<div class="relative w-full">
{{ form_widget(form) }} {{ form_widget(form) }}
</div> </div>
{% if not compound and not form.vars.valid %}
{# Affiche l'erreur en bas du champ simple #} {# Erreurs de validation #}
<p class="text-sm text-red-600 mt-1">{{ form_errors(form) }}</p> {% if not form.vars.valid %}
{% else %} <div class="mt-2 self-start bg-red-500 text-white font-black italic uppercase text-[10px] px-2 py-1 border-2 border-black shadow-[3px_3px_0px_#000]">
{# Affiche l'erreur pour les champs composés (si form_errors n'est pas déjà dans le widget) #} {{ form_errors(form) }}
{{ form_errors(form) }} </div>
{% endif %} {% endif %}
</div> </div>
{% endblock %} {% endblock %}
{# ---------- LABEL ---------- #} {# ---------- LABEL ---------- #}
{% block form_label %} {% block form_label %}
@@ -78,47 +83,28 @@
class="form-textarea form-input mt-1 block w-full px-3 py-2 bg-white border border-gray-300 text-gray-900 placeholder-gray-400 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm transition duration-150" class="form-textarea form-input mt-1 block w-full px-3 py-2 bg-white border border-gray-300 text-gray-900 placeholder-gray-400 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm transition duration-150"
>{{ value }}</textarea> >{{ value }}</textarea>
{% endblock %} {% endblock %}
{# ---------- CHECKBOX : Alignée en ligne ---------- #}
{# ---------- SELECT ---------- #}
{% block choice_widget_collapsed %}
<select
{{ block('widget_attributes') }}
class="form-select form-input mt-1 block w-full px-3 py-2 bg-white border border-gray-300 text-gray-900 placeholder-gray-400 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm transition duration-150"
>
{% if placeholder is not none %}
<option value="" {% if required and value is empty %}selected{% endif %}>
{{ placeholder != '' ? (placeholder|trans({}, translation_domain)) : '' }}
</option>
{% endif %}
{% for group_label, choice in choices %}
{% if choice is iterable %}
<optgroup label="{{ group_label|trans({}, translation_domain) }}">
{% for nested_choice in choice %}
<option value="{{ nested_choice.value }}" {% if nested_choice is selectedchoice(value) %}selected{% endif %}>
{{ nested_choice.label|trans({}, translation_domain) }}
</option>
{% endfor %}
</optgroup>
{% else %}
<option value="{{ choice.value }}" {% if choice is selectedchoice(value) %}selected{% endif %}>
{{ choice.label|trans({}, translation_domain) }}
</option>
{% endif %}
{% endfor %}
</select>
{% endblock %}
{# ---------- CHECKBOX ---------- #}
{% block checkbox_widget %} {% block checkbox_widget %}
<div class="flex items-center"> <label class="flex items-center group cursor-pointer select-none w-full">
<input type="checkbox" <input type="checkbox" value="{{ value }}" {{ block('widget_attributes') }} {% if checked %}checked="checked"{% endif %} class="peer hidden">
{{ block('widget_attributes') }}
{% if value not in ['', null] %} value="{{ value }}"{% endif %} {# Le carré de la checkbox #}
{% if checked %}checked="checked"{% endif %} <div class="flex-shrink-0 w-6 h-6 bg-white border-[3px] border-black shadow-[3px_3px_0px_#000] peer-checked:bg-green-400 peer-checked:shadow-none peer-checked:translate-x-[2px] peer-checked:translate-y-[2px] transition-all flex items-center justify-center">
class="form-checkbox h-5 w-5 text-indigo-600 border-gray-300 rounded focus:ring-indigo-500"> <svg class="w-4 h-4 text-black opacity-0 peer-checked:opacity-100 transition-opacity" fill="none" stroke="currentColor" viewBox="0 0 24 24">
</div> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="4" d="M5 13l4 4L19 7"></path>
</svg>
</div>
{# Le texte à côté #}
{% if label is defined and label is not same as(false) %}
<span class="ml-3 font-black uppercase italic text-xs tracking-tight text-gray-900 leading-none">
{{ label|trans }}
</span>
{% endif %}
</label>
{% endblock %} {% endblock %}
{# ---------- RADIO ---------- #} {# ---------- RADIO ---------- #}
{% block radio_widget %} {% block radio_widget %}
<input type="radio" <input type="radio"
@@ -139,3 +125,15 @@
hover:file:bg-indigo-700 hover:file:bg-indigo-700
bg-white border border-gray-300 rounded-md shadow-sm"> bg-white border border-gray-300 rounded-md shadow-sm">
{% endblock %} {% endblock %}
{% block choice_widget_expanded %}
<div {{ block('widget_container_attributes') }} class="flex flex-col gap-3 mt-2">
{% for child in form %}
<div class="flex items-center">
{{ form_widget(child) }}
</div>
{% endfor %}
</div>
{% endblock %}

View File

@@ -221,11 +221,23 @@
<div class="p-6 bg-gray-100 border-4 border-black border-dashed"> <div class="p-6 bg-gray-100 border-4 border-black border-dashed">
<h3 class="font-black uppercase italic mb-4 text-[#E63946]">{{ 'form.section.social'|trans }}</h3> <h3 class="font-black uppercase italic mb-4 text-[#E63946]">{{ 'form.section.social'|trans }}</h3>
<div class="grid md:grid-cols-2 gap-4"> <div class="grid md:grid-cols-2 gap-4">
{{ form_row(form.discordAccount, {'label': 'form.label.discord'|trans, 'attr': {'class': 'border-2 border-black p-2 w-full font-bold'}}) }} <div class="flex flex-col">
{{ form_row(form.instaLink, {'label': 'form.label.insta'|trans, 'attr': {'class': 'border-2 border-black p-2 w-full font-bold'}}) }} {{ form_label(form.discordAccount, 'form.label.discord'|trans, {'label_attr': {'class': 'font-black uppercase text-sm mb-2'}}) }}
{{ form_row(form.tiktokLink, {'label': 'form.label.tiktok'|trans, 'attr': {'class': 'border-2 border-black p-2 w-full font-bold'}}) }} {{ form_widget(form.discordAccount, {'attr': {'class': 'border-4 border-black p-3'}}) }}
{{ form_row(form.facebookLink, {'label': 'form.label.facebook'|trans, 'attr': {'class': 'border-2 border-black p-2 w-full font-bold'}}) }} </div>
</div> <div class="flex flex-col">
{{ form_label(form.instaLink, 'form.label.insta'|trans, {'label_attr': {'class': 'font-black uppercase text-sm mb-2'}}) }}
{{ form_widget(form.instaLink, {'attr': {'class': 'border-4 border-black p-3'}}) }}
</div>
<div class="flex flex-col">
{{ form_label(form.tiktokLink, 'form.label.tiktok'|trans, {'label_attr': {'class': 'font-black uppercase text-sm mb-2'}}) }}
{{ form_widget(form.tiktokLink, {'attr': {'class': 'border-4 border-black p-3'}}) }}
</div>
<div class="flex flex-col">
{{ form_label(form.facebookLink, 'form.label.facebook'|trans, {'label_attr': {'class': 'font-black uppercase text-sm mb-2'}}) }}
{{ form_widget(form.facebookLink, {'attr': {'class': 'border-4 border-black p-3'}}) }}
</div>
</div>
</div> </div>
{# RÔLE & MOTIVATION #} {# RÔLE & MOTIVATION #}
@@ -234,11 +246,9 @@
{{ form_widget(form.who, {'attr': {'class': 'border-4 border-black p-3 min-h-[120px]'}}) }} {{ form_widget(form.who, {'attr': {'class': 'border-4 border-black p-3 min-h-[120px]'}}) }}
</div> </div>
<div class="flex flex-col"> {# Remplace tout ton bloc manuel par cette ligne unique #}
{{ form_label(form.role, 'form.label.role'|trans, {'label_attr': {'class': 'font-black uppercase text-sm mb-2'}}) }} <div class="neubrutal-container">
<div class="grid grid-cols-2 md:grid-cols-4 gap-4 mt-2"> {{ form_row(form.role) }}
{{ form_widget(form.role) }}
</div>
</div> </div>
{# BOUTON ENVOI #} {# BOUTON ENVOI #}

View File

@@ -5,7 +5,7 @@
{% endblock %} {% endblock %}
{% block content %} {% block content %}
BONJOUR {{ join.name|upper }}, BONJOUR {{ datas.join.name|upper }},
C'est confirmé ! Nous avons bien reçu ta candidature pour rejoindre l'association E-Cosplay. C'est confirmé ! Nous avons bien reçu ta candidature pour rejoindre l'association E-Cosplay.
@@ -13,7 +13,7 @@
LA SUITE DES ÉVÉNEMENTS : LA SUITE DES ÉVÉNEMENTS :
------------------------- -------------------------
Nous reviendrons vers toi sous peu (généralement sous quelques jours) pour te donner une réponse ou convenir d'un petit échange, soit par email, soit directement sur Discord ({{ join.discordAccount|default('via le compte fourni') }}). Nous reviendrons vers toi sous peu (généralement sous quelques jours) pour te donner une réponse ou convenir d'un petit échange, soit par email, soit directement sur Discord ({{ datas.join.discordAccount|default('via le compte fourni') }}).
À très bientôt, À très bientôt,

View File

@@ -1,7 +1,7 @@
{% extends 'mails/base.twig' %} {% extends 'mails/base.twig' %}
{% block subject %} {% block subject %}
[NOUVELLE CANDIDATURE] - {{ join.pseudo|default(join.surname|upper) }} ({{ join.civ|upper }}) [NOUVELLE CANDIDATURE] - {{ datas.join.pseudo|default(join.surname|upper) }} ({{ datas.join.civ|upper }})
{% endblock %} {% endblock %}
{% block content %} {% block content %}
@@ -15,27 +15,27 @@
IDENTITÉ COSPLAY : IDENTITÉ COSPLAY :
------------------ ------------------
- Pseudo / Scène : {{ join.pseudo|default('Non renseigné') }} - Pseudo / Scène : {{ datas.join.pseudo|default('Non renseigné') }}
- Nom Complet : ({{ join.civ|upper }}) {{ join.surname|upper }} {{ join.name }} - Nom Complet : ({{ datas.join.civ|upper }}) {{ datas.join.surname|upper }} {{ datas.join.name }}
- Identité : {{ join.crossCosplay|upper }} (Cross) | {{ join.trans|upper }} (Trans) - Identité : {{ datas.join.crossCosplay|upper }} (Cross) | {{ datas.join.trans|upper }} (Trans)
DÉTAILS DU CANDIDAT : DÉTAILS DU CANDIDAT :
--------------------- ---------------------
- Email : {{ join.email }} - Email : {{ datas.join.email }}
- Téléphone : {{ join.phone }} - Téléphone : {{ datas.join.phone }}
- Rôles souhaités : {% if join.role is iterable %}{{ join.role|join(', ') }}{% else %}{{ join.role }}{% endif %} - Rôles souhaités : {% if datas.join.role is iterable %}{{ datas.join.role|join(', ') }}{% else %}{{ datas.join.role }}{% endif %}
- Date de dépôt : {{ join.createAt|date('d/m/Y à H:i') }} - Date de dépôt : {{ datas.join.createAt|date('d/m/Y à H:i') }}
PRÉSENTATION : PRÉSENTATION :
-------------- --------------
{{ join.who|default('Aucune présentation fournie.') }} {{ datas.join.who|default('Aucune présentation fournie.') }}
COORDONNÉES NUMÉRIQUES : COORDONNÉES NUMÉRIQUES :
------------------------ ------------------------
- Discord : {{ join.discordAccount|default('Non renseigné') }} - Discord : {{ datas.join.discordAccount|default('Non renseigné') }}
- Instagram : {{ join.instaLink|default('N/A') }} - Instagram : {{ datas.join.instaLink|default('N/A') }}
- TikTok : {{ join.tiktokLink|default('N/A') }} - TikTok : {{ datas.join.tiktokLink|default('N/A') }}
- Facebook : {{ join.facebookLink|default('N/A') }} - Facebook : {{ datas.join.facebookLink|default('N/A') }}
---------------------------------------------------------- ----------------------------------------------------------
Note : Le dossier complet avec le calcul de l'âge est Note : Le dossier complet avec le calcul de l'âge est