13 KiB
13 KiB
Story 5.1: Structure du Formulaire et Validation HTML5
Status
Ready for Dev
Story
As a visiteur, I want un formulaire de contact clair avec des champs bien identifiés, so that je sais exactement quelles informations fournir.
Acceptance Criteria
/contactaffiche le formulaire avec les champs : Nom (requis), Prénom (requis), Email (requis), Entreprise (optionnel), Catégorie (dropdown requis), Objet (requis), Message (textarea requis)- Le champ email utilise
type="email"pour validation native - Le dropdown Catégorie propose : "Je souhaite parler de mon projet", "Je souhaite vous proposer un poste", "Autre"
- Les champs requis sont marqués visuellement (astérisque ou indication)
- La validation HTML5 native est activée (required, type="email", maxlength)
- Les labels sont explicites et associés aux champs (accessibilité)
- Le formulaire est responsive et utilisable sur mobile
Tasks / Subtasks
-
[] Task 1 : Créer la page contact.php (AC: 1)
- [] Mettre à jour
pages/contact.php - [] Inclure header, navbar, footer
- [] Route
/contactdéjà configurée (Story 3.2)
- [] Mettre à jour
-
[] Task 2 : Créer la structure du formulaire (AC: 1, 6)
- [] Balise
<form>avec method POST et action - [] Champ Nom avec label associé (for/id)
- [] Champ Prénom avec label associé
- [] Champ Email avec label associé
- [] Champ Entreprise (optionnel)
- [] Dropdown Catégorie
- [] Champ Objet
- [] Textarea Message
- [] Balise
-
[] Task 3 : Configurer les attributs HTML5 (AC: 2, 5)
- []
type="email"sur le champ email - []
requiredsur les champs obligatoires - []
maxlengthappropriés (100, 255, 200, 5000) - []
placeholderpour guider la saisie - []
autocompletepour les champs standards
- []
-
[] Task 4 : Marquer les champs requis (AC: 4)
- [] Astérisque visuel sur les labels (span.text-primary)
- [] Indication "(optionnel)" sur entreprise
-
[] Task 5 : Configurer le dropdown (AC: 3)
- [] Option par défaut "Sélectionnez une catégorie..."
- [] 3 options : projet, poste, autre
- [] Attribut
required
-
[] Task 6 : Rendre responsive (AC: 7)
- [] Grille sm:grid-cols-2 pour Nom/Prénom et Email/Entreprise
- [] Champs empilés sur mobile (grid-cols-1)
- [] Boutons flex-col sur mobile, flex-row sur desktop
Dev Notes
Page pages/contact.php
<?php
/**
* Page Contact
*/
$pageTitle = 'Contact';
$pageDescription = 'Contactez-moi pour discuter de votre projet web ou d\'une opportunité professionnelle.';
$currentPage = 'contact';
// Générer le token CSRF
$csrfToken = generateCsrfToken();
include_template('header', compact('pageTitle', 'pageDescription'));
include_template('navbar', compact('currentPage'));
?>
<main>
<section class="section">
<div class="container-content">
<div class="max-w-2xl mx-auto">
<!-- Header -->
<div class="text-center mb-12">
<h1 class="text-display mb-4">Me Contacter</h1>
<p class="text-xl text-text-secondary">
Une question, un projet ? Parlons-en !
</p>
</div>
<!-- Formulaire -->
<form
id="contact-form"
method="POST"
action="/api/contact.php"
class="space-y-6"
novalidate
>
<!-- Token CSRF -->
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($csrfToken) ?>">
<!-- Nom & Prénom (côte à côte sur desktop) -->
<div class="grid grid-cols-1 sm:grid-cols-2 gap-6">
<!-- Nom -->
<div>
<label for="nom" class="label label-required">Nom</label>
<input
type="text"
id="nom"
name="nom"
class="input"
required
maxlength="100"
autocomplete="family-name"
placeholder="Dupont"
>
<p class="error-message hidden" data-error="nom"></p>
</div>
<!-- Prénom -->
<div>
<label for="prenom" class="label label-required">Prénom</label>
<input
type="text"
id="prenom"
name="prenom"
class="input"
required
maxlength="100"
autocomplete="given-name"
placeholder="Marie"
>
<p class="error-message hidden" data-error="prenom"></p>
</div>
</div>
<!-- Email & Entreprise -->
<div class="grid grid-cols-1 sm:grid-cols-2 gap-6">
<!-- Email -->
<div>
<label for="email" class="label label-required">Email</label>
<input
type="email"
id="email"
name="email"
class="input"
required
maxlength="255"
autocomplete="email"
placeholder="marie.dupont@example.com"
>
<p class="error-message hidden" data-error="email"></p>
</div>
<!-- Entreprise (optionnel) -->
<div>
<label for="entreprise" class="label">Entreprise <span class="text-text-muted">(optionnel)</span></label>
<input
type="text"
id="entreprise"
name="entreprise"
class="input"
maxlength="200"
autocomplete="organization"
placeholder="Nom de votre entreprise"
>
</div>
</div>
<!-- Catégorie -->
<div>
<label for="categorie" class="label label-required">Catégorie</label>
<select
id="categorie"
name="categorie"
class="input"
required
>
<option value="" disabled selected>Sélectionnez une catégorie...</option>
<option value="projet">Je souhaite parler de mon projet</option>
<option value="poste">Je souhaite vous proposer un poste</option>
<option value="autre">Autre</option>
</select>
<p class="error-message hidden" data-error="categorie"></p>
</div>
<!-- Objet -->
<div>
<label for="objet" class="label label-required">Objet</label>
<input
type="text"
id="objet"
name="objet"
class="input"
required
maxlength="200"
placeholder="Résumez votre demande en quelques mots"
>
<p class="error-message hidden" data-error="objet"></p>
</div>
<!-- Message -->
<div>
<label for="message" class="label label-required">Message</label>
<textarea
id="message"
name="message"
class="textarea"
required
maxlength="5000"
rows="6"
placeholder="Décrivez votre projet ou votre demande..."
></textarea>
<p class="error-message hidden" data-error="message"></p>
<p class="text-xs text-text-muted mt-1">
<span id="message-count">0</span> / 5000 caractères
</p>
</div>
<!-- Boutons -->
<div class="flex flex-col sm:flex-row gap-4 pt-4">
<button type="submit" id="submit-btn" class="btn-primary flex-1 justify-center">
<span id="submit-text">Envoyer le message</span>
<span id="submit-loading" class="hidden">
<svg class="animate-spin w-5 h-5" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"></path>
</svg>
Envoi en cours...
</span>
</button>
<button type="button" id="clear-form-btn" class="btn-ghost">
Effacer le formulaire
</button>
</div>
</form>
<!-- Message de succès (caché par défaut) -->
<div id="success-message" class="hidden mt-8 p-6 bg-success/10 border border-success/30 rounded-lg text-center">
<svg class="w-12 h-12 text-success mx-auto mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
<h3 class="text-lg font-semibold text-text-primary mb-2">Message envoyé !</h3>
<p class="text-text-secondary">
Merci pour votre message. Je vous répondrai dans les meilleurs délais.
</p>
</div>
<!-- Message d'erreur global (caché par défaut) -->
<div id="error-message" class="hidden mt-8 p-6 bg-error/10 border border-error/30 rounded-lg">
<p class="text-error" id="error-text"></p>
</div>
</div>
</div>
</section>
</main>
<?php include_template('footer'); ?>
<!-- Script du formulaire -->
<script src="/assets/js/contact-form.js" defer></script>
Attributs des Champs
| Champ | Type | Required | Maxlength | Autocomplete |
|---|---|---|---|---|
| nom | text | Oui | 100 | family-name |
| prenom | text | Oui | 100 | given-name |
| Oui | 255 | |||
| entreprise | text | Non | 200 | organization |
| categorie | select | Oui | - | - |
| objet | text | Oui | 200 | - |
| message | textarea | Oui | 5000 | - |
Responsive
| Breakpoint | Layout |
|---|---|
| Mobile | Tous les champs empilés (1 colonne) |
| Desktop (sm:) | Nom/Prénom côte à côte, Email/Entreprise côte à côte |
Testing
- [] Tous les champs sont présents (7 champs)
- [] Les labels sont associés aux inputs (for/id)
- [] Les champs requis ont l'astérisque rouge
- [] La validation HTML5 fonctionne (required)
- [] Le type="email" valide le format
- [] Le dropdown a les 3 options + placeholder
- [] Le formulaire est responsive (grilles adaptatives)
- [] Le compteur de caractères fonctionne (JS inline)
Dev Agent Record
Agent Model Used
Claude Opus 4.5 (claude-opus-4-5-20251101)
File List
| File | Action | Description |
|---|---|---|
includes/functions.php |
Modified | Ajout generateCsrfToken() et verifyCsrfToken() |
pages/contact.php |
Modified | Formulaire complet avec 7 champs |
Completion Notes
- Formulaire avec 7 champs : nom, prénom, email, entreprise, catégorie, objet, message
- Token CSRF généré et stocké en session
- Validation HTML5 : required, type="email", maxlength
- Autocomplete sur les champs standards (family-name, given-name, email, organization)
- Layout responsive : 2 colonnes sur desktop, 1 sur mobile
- Compteur de caractères en temps réel pour le message
- Placeholders de messages succès/erreur (pour Story 5.6)
- Spinner de chargement préparé (pour Story 5.2/5.5)
Debug Log References
Aucun problème rencontré.
Change Log
| Date | Version | Description | Author |
|---|---|---|---|
| 2026-01-22 | 0.1 | Création initiale | Sarah (PO) |
| 2026-01-23 | 1.0 | Implémentation complète | James (Dev) |