Gifty #2 - Rendons les choses présentables

Dans cet article

Cet article est la suite de la série "10h pour créer un business en ligne". Je recommande évidemment d'avoir lu les articles précédents pour continuer. Pour rappel, ce défi consiste en la création d'une version alpha (ou POC - Proof Of Concept) d'un outil de gestion de liste-cadeaux en ligne, du type liste de mariage, de naissance ou de Noël et autres.

Je repars pour une seconde heure de création sur ce défi, il est temps de faire évoluer les choses.

00:00:00

Une journée est passée, et me voilà seul devant mon PC pour démarrer cette nouvelle heure sur ce défi. Je démarre mon chronomètre en ligne, et comme la première fois, Spotify m'accompagnera avec ce coup-ci une playlist de The Hue à commencer part Wolf Totem <3 !

Il est tard et je suis fatigué. Ma petite puce commence à s'agiter un peu dans son lit, j'ai bien peur de ne pas avoir énormément de temps pour cette session. Il faudrait que je commence à taper dans le fonctionnel mais j'ai tellement peur de ne pas réussir à rester concentré assez longtemps que je préfère me lancer dans quelque chose de plus détente. En plus, j'avais laissé la dernière heure sur un screenshot vraiment pauvre de notre site, j'ai bien envie de rendre les choses un peu plus jolies.

C'est pas super malin dans le cadre du développement d'un POC (preuve de concept). Je veux dire que si l'objectif est de montrer à un client une maquette peu chère d'un service et démontrer ma capacité à le faire, alors ce genre de chose est vraiment superflue. Pourtant, je sais que séparer le fonctionnel de sa présentation est un exercice mental pas toujours simple et - sans aller jusqu'à dire que c'est systématique - la réaction d'un client face à un fonctionnel très complexe est souvent "ouaip mais là la police n'est pas ouf" ou "et niveau des couleurs on pourrait changer ?". En fait, plus c'est complexe, plus il semble que l'on s'attache à des détails annexes, comme si le cerveau cherchait à se raccrocher à ce qu'il maîtrise.

C'est la même raison pour laquelle nous voyons des formes dans les nuages : le cerveau humain analyse ce qu'il ne connait pas en cherchant des patterns connus à réappliquer. En sortir, voir l'ensemble d'une situation inconnue et s'y projeter demande un effort conscient et est souvent un vrai apprentissage. C'est ce qui fait qu'un décorateur d'intérieur peut voir le potentiel d'un appartement : il arrive à se projeter dans les couleurs et le mobilier sans s'attacher aux détails actuels, y compris aussi gros qu'une cloison qui sépare encore deux pièces. Les clients n'étant pas des ingénieurs en développement, c'est à nous de les aider à travers nos présentations à se projeter sur l'état final et le fonctionnel possible ou non. Et parfois c'est pas facile :p

Exit le fonctionnel donc, je pars sur une heure de design et de mise en place d'une page d'accueil qui aide à se projeter un peu dans la suite. Ce n'est pas ainsi qu'il faudrait gérer les choses je pense, mais si de toute façon je me retrouve dans 20 minutes à changer la couche de ma fille, autant que je ne sois pas coupé en trop grande concentration. "My business, my rules" comme dirais Ashe dans Overwatch !

Par contre, partons ici sur la règle du 80/20 ! J'investis 20% de temps, dans ce qui donnera 80% de résultat. Il s'agit donc d'avoir une page correcte, relativement similaire à mon griffonnage de l'heure d'avant, mais sans peaufinage. Le site sera donc un minimum responsif, mais il y aura forcément des problèmes d'ajustements, des détails de marges de ceci ou d'alignement de cela. C'est très souvent ces détails qui font la différence entre un produit de démo et une version en production, et aussi qui coûtent le plus cher en temps et en effort, et donc en budget pour le client puisque ces derniers 20% de résultats prennent souvent 80% du temps de réalisation.

Design papier approximatif pour une frontpage
Une maquette griffonnée à la va-vite: simple !

0 à 15 minutes

Histoire d'être propre et tout ce qu'il y a de plus classique, partons sur un design général à trois colonnes, le tout contenu dans une limite de largeur de 1440px. Cette dimension est fréquemment choisie car correspondant à un écran de taille moyenne : c'est la résolution en pixel CSS d'un MacBook Pro de 15 pouces (2880x1800px réels).

Idée générale de la répartition des régions sur le site
Croquis de la répartition générale des régions sur le site

Pour ma page d'accueil, il me suffira de créer une page affectée à la région Content et d'utiliser la Sidebar Right avec une largeur surchargée pour des blocs de formulaire. Gain de temps et de code :)

D'un point de vu HTML, nos trois régions content, sidebar left et sidebar right se suivent dans cet ordre ci. L'ordre à l'affichage est géré via la propriété CSS order permettant de modifier l'ordre d'affichage des éléments.

Dans notre page.html.twig 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<main id="main" role="main">
  <a id="main-content" tabindex="-1"></a>{# link is in html.html.twig #}
 
  <div class="layout-content">
    {{ page.content }}
  </div>{# /.layout-content #}
 
  {% if page.sidebar_left %}
    <aside class="layout-sidebar layout-sidebar-left" role="complementary">
      {{ page.sidebar_left }}
    </aside>
  {% endif %}
 
  {% if page.sidebar_right %}
    <aside class="layout-sidebar layout-sidebar-right" role="complementary">
      {{ page.sidebar_right }}
    </aside>
  {% endif %}
</main>

Le SCSS correspondant

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/* Content + sidebar layout */
@include breakpoint-up(medium) {
  main {
    display: flex;
 
    .layout-content {
      order: 2;
      flex: 1;
    }
 
    .layout-sidebar {
      width: 300px;
 
      &-left {
        order: 1;
        margin-right: 2rem;
      }
 
      &-right {
        order: 3;
        margin-left: 2rem;
      }
    }
  }
}

Remarquez également comme le header et le footer prennent l'intégralité de la largeur de la fenêtre dans l'apparence, mais se restreignent à 1440px dans le contenu ? J'introduis un wrapper interne pour cela :

1
2
3
4
5
6
7
8
9
10
11
12
.inner-wrapper {
  margin: 0 2rem;
}
@include breakpoint-up(large) {
  .inner-wrapper {
    max-width: $page_width;
    margin: 0 auto;
    .inner-wrapper {
      max-width: none;
    }
  }
}
J'annule les effets d'un inner-wrapper qui serait dans un autre, en prévision… Je travaille beaucoup avec des briques de mise en forme via Layout Paragraphs et comme vous pouvez le voir en exemple sur la page d'accueil de mon site, ils peuvent vite se retrouver les uns dans les autres pour des mises en forme complexes.

Un petit peu de CSS et quelques clics plus loin, on se retrouve très très vite avec quelque chose de propre : le haut de page est un simple cadre flex avec une bordure du bas et une ombre tandis que seule une couleur de fond matérialise le pied de page. Il ne reste plus qu'à placer les menus et ajuster la taille des éléments du header. Je pense que le tout se passe de commentaire.

Pour mes menus, j'applique un mixin qui me permet de les afficher en ligne, optionnellement avec un élément entre chaque menu: rien en haut, un pipe en pied de page. J'ai à cet effet créé une page "mentions légales" en lorem ipsum complètement bidon, juste pour tester l'affichage en ligne des menus : on l'affecte au footer.

Drupal s'installe par défaut avec un formulaire de contact tout prêt. Je récupère donc un fichier form.scss déjà fait pour styliser rapidement les formulaires : celui de ce site perso.

Avez-vous remarquer l'effet au survol des boutons ? C'est un effet créé en jouant avec les pseudo-éléments ::before et ::after. Mais ceux-ci ne fonctionnent pas sur l'élément input. Il me faut donc convertir l'intégralité des input[type="submit"] du site en button et pour cela je dois éditer leur template.

Pour faire fonctionner cet effet au survol des boutons de formulaire que j'aime tant (cf le formulaire de contact en page d'accueil de ce site), il me faut transformer les input[type="submit"] du site en button. Pour cela, deux modifs ultra-rapides sont nécessaires.

1- Ajouter un preprocess dans le fichier gifty_theme.theme

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
 * Implements template_preprocess_input().
 *
 * Preprocess here is used to change input[type='submit'] to button.
 *
 * @see templates/form/input--submit.html.twig
 */
function gifty_theme_preprocess_input(&$variables) {
  if ($variables['attributes']['type'] == 'submit' &&
      isset($variables['element']['#button_type']) && $variables['element']['#button_type'] == 'primary') {
    $variables['value'] = $variables['attributes']['value'];
    unset($variables['attributes']['value']);
  }
}

2- Surcharger le template des input de type submit en créant un fichier form/input--submit.html.twig dans mon dossier templates

1
2
3
4
<button{{ attributes }}>
  {{ value }}
  {{ children }}
</button>

C'est tout simple, pourquoi s'en priver ?!

Bon, faisons le point: déjà 15 minutes de passées, mais nous avons une structure de page et du CSS pour assurer un design assez générique pour nos quelques pages actuellement existantes : la page d'accueil, une page de mentions légales bidon, et une page de formulaire de contact fournie par Drupal et les pages de connexion / création de compte, là aussi fournie par Drupal.

J'ai suivi la règle des 80/20 à savoir je suis allé à l'essentiel pour un maximum d'efficacité en un minimum de temps. A ce stade, je n'ai pas testé le site sur les navigateurs les plus anciens type IE mais j'ai rapidement vérifié que ce soit correct en mobile / tablette / desktop. Ce n'est pas pour autant vraiment une validation "production ready", mais ça suffira pour ce site. Une source immense de simplification est la navigation, pour l'instant prévue pour être une simple icône "profil" en haut du header, inutile donc de prévoir un menu burger responsif. Je suppose qu'à un moment ou un autre, j'aurais besoin d'un menu type dropdown, mais nous verrons cela si nécessaire.

  • Gifty - page d'accueil en cours d'avancement
  • Gifty - page d'accueil en cours d'avancement, vue de l'admin
  • Gifty - page du formulaire de contact
  • Gifty - page des mentions légales
Règle des 80/20 : une base simple qui fait 80% du boulot pour 20% d'efforts

15 à 30 minutes

Vous l'aurez certainement remarqué sur le croquis présenté dans l'article précédent, notre page d'accueil présente deux zones distinctes, avec une couleur de fond de la zone de gauche qui prend la totalité de l'écran. Cette organisation de page s'inspire de la page d'accueil du service dropbox.

Pour ce faire, notre contenu sera présenté dans la zone Content sur la page de gauche, et nos blocs de connexion et création de compte sur la Sidebar Right. Pour cette page et cette page seulement, nous allons devoir modifier le template afin de ne pas utiliser notre .inner-wrapper à cet endroit. Nous allons alors jouer en CSS sur les padding des deux zones pour recréer cette impression de centrage.

Première chose à faire, je duplique donc mon template de page et renomme cette nouvelle version page--front.html.twig. Je peux alors y enlever le wrapper dont nous avons parlé.

En haut du fichier, juste au-dessus de la première ligne d'HTML, je rajoute cette ligne :

{{ attach_library('gifty_theme/frontpage') }}

Cela a pour effet de charger la libraire CSS/JS nommée frontpage et déclarée dans mon thème pour cette page uniquement. Nous allons en effet avoir besoin de CSS et JS en plus pour cette page spécifiquement. Cette librairie se déclare dans le fichier gifty_theme.libraries.yml à la suite des autres.

1
2
3
4
5
6
7
8
9
10
frontpage:
  css:
    theme:
      css/pages/frontpage.css: {}
  js:
    js/frontpage.js: {}
  dependencies:
    - core/jquery
    - core/drupal
    - core/jquery.once

Le CSS est donc généré depuis un fichier gifty_theme/src/sass/pages/frontpage.scss selon la convention mise en place précédemment. Nous reviendrons sur la raison du JS un peu plus loin dans cet article.

Dans ce CSS, je surcharge la largeur de la colonne de droite pour qu'elle prenne 480px plus le padding souhaité à droite. Idem pour mon contenu, il doit avoir un padding à gauche correspondant pour permettre visuellement de retrouver nos 1440px de conteneur.

Gifty - Layout général de la page d'accueil
Contrairement au reste du site, le layout de la page d'accueil est fullsize avec un contenu restreint par des padding internes

Plus concrètement, notre CSS ressemble donc à cela: 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@include breakpoint-up(medium) {
  .layout-sidebar {
    width: calc(480px + ((100vw - 1440px) / 2));
    padding-right: 3rem;
  }
}
@include breakpoint-up(large) {
  .layout-content {
    padding-left: calc((100% - 1440px) / 2);
  }
  .layout-sidebar-right {
    padding-right: calc((100% - 1440px) / 2);
  }
}

On remarque enfin que le contenu prend la totalité de la hauteur de l'écran moins le header et le footer ce qui est réalisé par une simple mise en place flex. L'astuce intéressante là-dedans vient surtout de la gestion de l'affichage de la toolbar d'administration qui obstrue une partie de l'écran et qui est a prendre en compte.

Il est maintenant temps de créer le contenu de cette page d'accueil. Pour cela, je crée une "page de base" que j'affecte à la page d'accueil via la configuration /admin/config/system/site-information.

Si vous avez bien suivi, c'est donc la seconde page que l'on crée sur le site et donc le node/2. Ma frontpage a donc pour ID 2 et non 1. Cela ne change strictement rien, mais prouve ma non planification de la situation. Jamais mes petits tocs n'auraient toléré un tel désordre sur un projet réel !

Vous vous rappelez de l'image du cadeau que j'avais gardé de côté parce que je l'aimais bien ? Et bien je m'en sers ici au sein de mon contenu, et je rajoute une petite phrase catchy un peu bidon, plus du CSS et le tour est joué pour la partie de gauche.

Pour la partie de droite, je souhaite affecter un formulaire de connexion et un formulaire de création de compte à ma sidebar. Manque de bol, Drupal ne définit pas de bloc pour le formulaire de création de compte: c'est une page en soit. Dans un vrai projet, j'aurais sûrement écrit un petit bout de code custom pour exposer mon besoin dans un bloc, mais pourquoi réinventer la roue ? J'installe donc ici le module Form Block qui fait exactement ce que dit son nom: il expose n'importe quel formulaire Drupal dans un bloc affectable à une région. Je me retrouve donc avec mes blocs disponibles et via la page d'administration des blocs sur /admin/structure/contact. Je les affecte à ma Sidebar Right et restreint à la page <front> : le tour est joué !

Gifty - La page d'accueil commence à prendre forme
La page d'accueil commence à prendre forme, avec nos deux formulaires dans la barre latérale et notre contenu type "page" à gauche

30 à 45 minutes

Perdons un peu de temps ! 

Avouons que dans le cadre d'un POC, notre page d'accueil suffit très largement. Seulement, quelques trucs me chagrinent avec les formulaires. Tout d'abord, j'adore l'effet type page d'accueil de dropbox où sous le titre "connectez-vous", on aperçoit en petit "ou créez un compte" avec un lien qui, sur la même page, fait disparaître le formulaire de connexion pour faire apparaître celui d'inscription. C'est super simple à faire, et je trouve ça assez cool. Nous allons donc le faire ici !

Vous vous rappelez tout à l'heure notre fichier frontpage.js ? Et bien c'est pour cela. Nous allons créer un simple petit script jQuery. Dès qu'un élément aura un attribut data-clickshow ou data-clickhide, au clic sur cet élément, nous afficherons ou cacherons tout élément qui matchera le sélecteur de cet attribut data. Nous pourrons ainsi afficher/cacher n'importe quel(s) élément(s) de la page hyper facilement. Que ce soit le formulaire, mais aussi modifier le lien de menu en header, pourquoi pas l'image et le texte dans la partie de gauche, bref à notre guise. Voici le code de ce fichier JS, encore une fois, on fait au plus simple et au plus efficace.

Le contenu de frontpage.js : 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
 * Custom behavior for gifty theme frontpage.
 */
 
(function ($, Drupal) {
  'use strict';
 
  Drupal.behaviors.gifty_theme_frontpage = {
    attach: function attach(context) {
      // Binds "on click" method to elements with data attributes clickshow / clickhide and acts accordingly.
      $('*[data-clickshow], *[data-clickhide]', context).once('clickshowhide').on('click', function(e) {
        e.preventDefault();
        var that = $(this);
        $(that.data('clickhide')).hide();
        $(that.data('clickshow')).show();
      });
    }
  };
})(jQuery, Drupal);

Il ne reste plus qu'à ajouter ces deux fameux attributs dans nos blocs. Pour cela, nous inspectons le code source de la page, dans lequel nous avions activé le debug twig.

Le debug twig affiche les suggestions de templates dans le code source
Le debug twig permet de connaître le template utilisé ainsi que des suggestions de templates alternatifs

Grâce au debug twig, nous savons que nous allons copier le template block.html.twig issu du thème Classy et nous le renommerons block--user-login-block.html.twig pour le personnaliser dans notre propre thème. De même pour le formulaire d'inscription qui sera renommé en block--formblock-user-register.html.twig

Au sein de ces fichiers twig, j'ajoute une sorte de sous-titre sous l'affichage du titre, via l'ajout de la troisième ligne dans la portion de code suivante : 

1
2
3
4
{% if label %}
  <h2{{ title_attributes }}>{{ label }}</h2>
  <div class="subtitle">{{ 'or @action'|t({'@action': action }) }}</div>
{% endif %}

Vous l'avez deviné, la dernière chose à faire est donc d'injecter cette fameuse variable action qui sera le lien souhaité. Je suis certain qu'on aurait pu faire plus malin, mais là encore pour cet exercice-là, la vitesse prime, je rajoute donc le preprocess suivant dans mon thème :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/**
 * Implements hook_preprocess_block().
 */
function gifty_theme_preprocess_block(&$variables) {
  $block_id = $variables['elements']['#id'];
  if ($block_id == 'connexionutilisateur') {
    $url = \Drupal\Core\Url::fromUserInput('#', [
      'attributes' => [
        'class' => ['action'],
        'data-clickshow' => '#block-formulairedinscriptiondesutilisateurs',
        'data-clickhide' => '#block-connexionutilisateur',
      ],
    ]);
    $variables['action'] = Drupal\Core\Link::fromTextAndUrl(t('Create new account'), $url)->toString();
  }
 
  if ($block_id == 'formulairedinscriptiondesutilisateurs') {
    $url = \Drupal\Core\Url::fromUserInput('#', [
      'attributes' => [
        'class' => ['action'],
        'data-clickshow' => '#block-connexionutilisateur',
        'data-clickhide' => '#block-formulairedinscriptiondesutilisateurs',
      ],
    ]);
    $variables['action'] = \Drupal\Core\Link::fromTextAndUrl(t('Log in'), $url)->toString();
  }
}

45 à 60 minutes

Quelques dernières petites choses me chagrinent. Je ne vais pas m'étendre sur chacune d'elles, mais rapidement j'installe et configure un certain nombre de modules pour répondre à mes désirs suivants:

  • Je veux que mes utilisateurs puissent se connecter soit via leur adresse email, soit via leur nom d'utilisateur. Pour cela, j'utilise simplement le module Login Email or Username
  • Je veux que mes utilisateurs puissent définir leur mot de passe dès l'inscription, avant la validation de leur email (qui ne sera valide que 24h avant que le compte ne soit purgé). Pour cela, j'utilise le module User registration password
  • Je veux une case "Se souvenir de moi" sur le formulaire de connexion. Pour cela, j'utilise le module Persistent Login
  • Je veux une case "J'accepte les conditions d'utilisations" sur le formulaire d'inscription. Je veux aussi garder trace de qui a accepté, et quand, la charte d'utilisation du site, ainsi que de redemander automatiquement validation des conditions aux utilisateurs lors de changement significatifs de cette charte. Pour cela, j'utilise le module Legal
  • Je veux compacter mes formulaires, et pour cela, automatiquement transformer mes titres de champs en placeholder HTML5. Pour cela, je rajoute une méthode #process aux éléments de formulaire via hook_element_info_alter() pour convertir #title en attribut placeholder.
  • Je veux supprimer les user_links du block de formulaire de login. Pour cela je le preprocess avec le code suivant:
    unset($variables['elements']['content']['user_links']);
  • Je veux ajouter un lien "Mot de passe oublié ?" sur mon formulaire de login. Pour cela j'ajoute un #markup en preprocessant ce formulaire
  • Je veux supprimer toutes les descriptions qui me paraissent inutiles sur les champs du formulaire d'enregistrement. Je preprocess donc l'ensemble en conséquence via le code
    1
    2
    3
    
    foreach (\Drupal\Core\Render\Element::children($form['account']) as $field) {
      unset($form['account'][$field]['#description']);
    }
  • Je veux que les messages d'erreur du formulaire et autres s'affichent dans la sidebar. Je configure donc les blocs en conséquence.

Quart d'heure de rab'

Une heure, ça passe très très vite ! En écrivant cet article, et en réalisant les copies d'écran, je me suis aperçu de quelques détails supplémentaires à régler. Des choses qui ne prennent pas longtemps, mais qui me chagrinent. Je décide donc de m'accorder un petit quart d'heure supplémentaire sur le sujet pour:

  • Lorsque le formulaire de connexion est en erreur, la page se réinitialise et nous retombons sur la page d'accueil. J'ajoute donc ces lignes vraiment pas optimales, mais fonctionnelles dans mon frontpage.js :
    1
    2
    3
    4
    5
    6
    
    // If an error is found in a form on the page,
    // the appropriate block is shown while others are hidden.
    if ($('form .form-item--error', context).length) {
      $('.region-sidebar-right .block').hide();
      $('form .form-item--error', context).parents('.block').show();
    }
  • Je me suis demandé si l'on pouvait supprimer proprement le résumé d'erreur généré par le module Inline Entity Form. Il se trouve qu'il y a un patch pour ça, que j'ai donc appliqué à mon projet via composer.
  • Je voudrais ajouter les liens "S'inscrire" et "Se connecter" dans le menu utilisateur que j'ai affecté au header, mais qu'ils ne soient disponibles que pour les utilisateurs anonymes. Il vaudrait mieux créer des liens customs pour gérer correctement les conditions d'affichage et de cache, mais pour aller plus vite, je modifie ceux fournis par Drupal. Pour cela, je preprocess mon menu de la manière suivante:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    
    /**
     * Implements hook_preprocess_menu().
     */
    function gifty_theme_preprocess_menu(&$variables) {
      if ($variables['menu_name'] == 'account') {
        $current_user = \Drupal::currentUser();
        $items = $variables['items'];
        if ($current_user->isAuthenticated()) {
          foreach ($items as $key => &$item) {
            if ($item['url']->getRouteName() == 'user.register' || $item['url']->getRouteName() == 'user.login') {
              unset($variables['items'][$key]);
            }
          }
        }
      }
    }
  • Je veux mettre en place mes attributs data-clickshow et data-clickhide sur ces nouveaux liens du menu également pour qu'ils contrôlent eux-aussi l'affichage des formulaires. Pour cela, j'installe module Menu Link Attributes.

Bilan

Et bah on est pas mal !

Le résultat est assez propre et j'en suis satisfait à ce stade. Bien entendu, mettre en place si vite une page d'accueil complète soulève forcément des problèmes non traités ici. Je n'ai pas testé l'application en détail sur toutes les tailles d'écrans, ni testé sur les différents navigateurs, ou encore vérifié la cohérence des messages d'erreurs ou du parcours utilisateur pour la création de compte. Je n'ai pas testé non plus l'accessibilité de mon système permettant de basculer d'un formulaire à l'autre. Bref, autant dire qu'à cette vitesse, je n'ai rien testé proprement.

Je dois aussi avouer que je m'en veux un peu. D'une part d'avoir dépensé une heure de temps sur du design non essentiel pour ce POC. D'autres part d'avoir même pris du rab' de temps pour des pinailleries. Mais cela fait partie de ma personnalité et mes travers : quand je vois un problème rapidement résolvable, j'ai beaucoup de mal à lâcher prise et passer à la suite, même s'il le faut.

L'heure prochaine, on attaque le fonctionnel sérieux. Mais avant, quelques copies d'écran du résultat !

  • Gifty - Connexion en page d'accueil
  • Gifty - Erreur de connexion en page d'accueil
  • Gifty - Inscription en page d'accueil
  • Gifty - Erreur d'inscription en page d'accueil
En peu de temps, nous avons mis en place une page d'accueil simple et propre.

Ajouter un commentaire

Votre nom sera affiché publiquement avec votre commentaire.
Votre email restera privé et n'est utilisé que pour vous notifier de l'approbation de ce commentaire.
Sur internet, vous pouvez être qui vous voulez. Soyez quelqu'un de bien :)