Story 1.1: initialisation projet

This commit is contained in:
2026-02-04 02:14:17 +01:00
parent c9f95d39a0
commit e3f062249b
38 changed files with 10300 additions and 0 deletions

View File

@@ -0,0 +1,326 @@
# 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
1. `/contact` affiche le formulaire avec les champs : Nom (requis), Prénom (requis), Email (requis), Entreprise (optionnel), Catégorie (dropdown requis), Objet (requis), Message (textarea requis)
2. Le champ email utilise `type="email"` pour validation native
3. Le dropdown Catégorie propose : "Je souhaite parler de mon projet", "Je souhaite vous proposer un poste", "Autre"
4. Les champs requis sont marqués visuellement (astérisque ou indication)
5. La validation HTML5 native est activée (required, type="email", maxlength)
6. Les labels sont explicites et associés aux champs (accessibilité)
7. 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 `/contact` déjà configurée (Story 3.2)
- [] **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
- [] **Task 3 : Configurer les attributs HTML5** (AC: 2, 5)
- [] `type="email"` sur le champ email
- [] `required` sur les champs obligatoires
- [] `maxlength` appropriés (100, 255, 200, 5000)
- [] `placeholder` pour guider la saisie
- [] `autocomplete` pour 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
<?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 |
| email | email | Oui | 255 | email |
| 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) |