Projet créé par Kevin Labtani dans le cadre de mon stage chez Hike-Up de début avril à fin juillet 2020.
Le but du projet est de développer la partie front-end utilisateur d'une application d'enquêtes en ligne équivalent à Typeform mais répondant aux besoins spécifiques de Hike-Up. L'outil de création d'enquêtes et le back-end sont gérés de manière séparée à cette application.
Un exemple de l'application consistant en un questionnaire démontrant chaque question type est hébergé en ligne à survey-wizard-test.netlify.app. Cet exemple utilise les données hardcodées dans l'initialState
et n'est pas connecté à un back-end.
nb: J'ai l'autorisation de partager le code à des fins non commerciales.
15 questions type sont implémentés:
- MultipleChoice pour les questions à choix multiples.
- SingleChoice pour les questions à choix unique.
- YesNo pour les questions de type oui-non ou vrai-faux.
- Legal pour les questions oui-non de type légal.
- Rating pour les questions de type évaluation.
- ShortText pour les questions de type textes courts.
- OpinionScale pour les questions de type opinion.
- Email pour les questions de type email.
- Number pour les questions de type nombre.
- PhoneNumber pour les questions de type numéro de téléphone.
- LongText pour les questions de type textes longs.
- FileUpload pour les questions de type upload de fichier.
- Welcome pour la première page du questionnaire (à usage unique).
- ThankYou pour la dernière page du questionnaire (à usage unique).
- Recap pour présenter une récapitulation des réponses et soumettre le questionnaire (à usage unique).
Le temps (en secondes) passé par l'utilisateur sur chaque question est enregistré et envoyé au back-end lors de la soumission des réponses.
Les données suivantes sont extraites du browser de l'utilisateur et envoyées au back-end lors de la soumission des réponses: nom du browser, useragent du browser, langue préférée de l'utilisateur, plateforme de l'utilisateur (eg. windows), referrer, et dans le cas où l'utilisateur est sur un mobile: type d'appareil mobile (gsm ou tablette) et OS du mobile.
Un mode de soumission partielle est implémenté, si l'utilisateur quitte le questionnaire sans soumettre ses réponses, un appel vers le back-end est effectué pour enregistrer l'id de la dernière question répondue, l'id du questionnaire et l'id assigné à l'utilisateur.
De plus, les données nécessaires à la reprise en cours du questionnaire sont enregistrées en localStorage
, l'utilisateur a donc la possibilité lors d'une session ultérieure de reprendre le questionnaire là où il en était si il le souhaite, cette option lui sera automatiquement présentée lors qu'il rechargera le questionnaire.
Le support multi-langues est implémenté, toute les strings statiques non dépendantes des questions (eg. messages d'erreur, placeholders) sont récupérées du back-end par la route pour les options de configuration.
Veuillez vous référer à la variable initialState
du fichier QuestionsState.js
pour une exemple d'implémentation de chaque question type. Une description des fonctionnalités notables suit.
Toutes les questions type sauf les questions type Welcome, ThankYou & Recap:
- peuvent être rendues obligatoires ou optionnelles par la clé
question_required
; les questions obligatoires sont marquées par une*
après le titre de la question. - ont la possibilité d'intégrer une image dans la bulle question, en dessous du titre de la question, par la clé
question_picture
. (Exemple visuel) - ont la possibilité d'avoir un
avatar_answer
et unavatar_question
différent par question, par les clés du même nom. - ont une validation implémentée spécifiquement question type par question type (eg. un email valide doit être fourni pour la question type email où un message d'erreur sera affiché à l'utilisateur).
Les questions présentant un choix unique (YesNo, Legal, SingleChoice, OpinionScale) ont la possibilité d'implémenter une navigation conditionnelle dans le questionnaire, sur base du choix effectué, en passant une question_id
valide à la clé next_id_if_selected
(qui va passer outre la clé par défaut default_next_id
) de chaque choix (box_values
) pour les quatre questions sus-citées.
Les questions type SingleChoice & MultipleChoice:
- acceptent un paramètre optionnel
other
permettant une réponse libre de l'utilisateur. (Exemple visuel) - acceptent un paramètre optionnel
picture
permettant l'affichage d'une image pour chaque choix, avec ou sanslabel
, pour autant qu'une clépicture
soit présente pour chaque choix (box_values
).
npm install
pour installer les dépendances
npm start
pour lancer l'application en mode développement
Cette application étant créée par create-react-app
, veuillez vous référer à leur documentation pour plus de détails.
Considérez remplacer la commande (nécessaire pour l'intégration continue avec netlify) "build": "CI= react-scripts build",
par "build": "react-scripts build",
dans le fichier package.json
Considérez remplacer le MemoryRouter
par un BrowserRouter
dans le fichier App.js
, veuillez consulter la documentation de react-router-dom
pour voir la différence.
Cette application est dépendante d'une API REST fournissant les routes suivantes:
- Route GET pour récupérer les questions au niveau de la fonction
getQuestions
du fichierQuestionState.js
. Une implémentation de chaque question type est fournie dans l'initialState
de ce même fichier. - Route GET pour récupérer les options de configuration au niveau de la fonction
getConfig
du fichierConfigState.js
Une implémentation type est fournie dans l'initialState
de ce même fichier. - Route POST pour envoyer les réponses au niveau de la fonction
sendAnswer
du fichierForm.js
. La réponse est construite par la fonctionconstructAnswer
; alternativement, vous pouvez décommenter leconsole.log(data);
dans la fonctionsendAnswer
et soumettre un questionnaire rempli (soumission par la question type Recap), ce qui vous permettra de voir dans la console l'objet envoyé en réponse à l'API. - Route POST pour gérer une soumission partielle du questionnaire au niveau de la fonction
window.onbeforeunload
du fichierForm.js
. Lorsqu'un utilisateur quitte prématurément le questionnaire, l'application envoie par la méthodesendBeacon
de l'API WebNavigator
une requête vers la routehttps://url/to/api/${responseUuid}/false/${lastLocation}/${assessmentId}
pour informer le back-end du fait qu'un utilisateur a quitté le questionnaire sans le soumettre avec l'id de la dernière réponse atteinte par l'utilisateur (lastLocation), l'id du questionnaire (assessmentId) et l'uuiid assigné à cet utilisateur (responseUuid). - Route POST (Content-Type: multipart/form-data) pour envoyer des fichiers (eg. images ou pdf) au niveau de la fonction
uploadHandler
du fichierFileUpload.js
pour la question type FileUpload.
-
src/components
contient- un sous-dossier pour les Composants React de layout.
- un sous-dossier pour les Composants React implémentant les 15 questions type.
- le Composant React ErrorPage qui sera rendu à l'écran si l'une des deux requêtes GET (pour récupérer les questions et les options de configurations) rencontre un problème.
- le Composant React principal
Form
. L'état relatif aux réponses et aux timings de réponse est géré dans ce Composant.
-
src/context
contient trois sous-dossiers pour les trois Contextes React implémentés, un pour les questions, un pour les alertes et un pour la configuration. Les implémentations sont basées sur le Flux Pattern
J'ai choisi d'utiliser bootstrap en tant que module npm plutôt que d'utiliser une librairie de Composants React tel que React Bootstrap ou reactstrap dû au degré de personnalisation nécessaire (eg. les boutons pour les questions type à choix unique ou multiple sont en fait des labels stylisés pour ressembler à des boutons), je pense qu'utiliser une librairie de Composants aurait rendu la tâche plus compliquée. De plus je n'utilise que la partie css de bootstrap, je n'importe pas les dépendances js dans l'application.
Je devais assurer la rétrocompatibilité avec les systèmes existant au niveau du back-end (en PHP) et de la database, ce qui explique le format parfois particulier dans lequel je récupère (/et renvoie) les données depuis (/vers) l'API.
Pour raison de rétrocompatibilité, les Composants implémentant les questions type Legal et YesNo sont fonctionnellement identiques.
J'ai implémenté un système d'alertes pour les erreurs, et effectue la validation "à la main" plutôt que d'utiliser formik
ou react-hook-form
car j'avais confiance en ma capacité à délivrer une application fonctionnelle en codant cette partie par moi-même alors que je n'étais pas sûr de pouvoir adapter formik
à mon cas d'utilisation particulier.
Cela dit, la création d'un custom hook et l'utilisation de useReducer
plutôt que du useState
pour gérer les réponses et les timings m'aurait permis d'extraire une partie de la logique hors du Composant Form
et aurait rendu celui-ci plus lisible.
Certains des Composants implémentant les questions type sont relativement lourds, il devrait être possible d'extraire un Composant Input
paramétrable qui permettrait de réduire la duplication de code d'un Composant à l'autre.
- React par
create-react-app
, avec les packagesreact-router-dom
pour le routing etframer-motion
pour les animations. - Axios pour les requêtes http.
- Bootstrap + Sass.
- Font Awesome pour les icônes.
code formaté par prettier
et linté par eslint
.
Projet arrêté le 17 juillet 2020 suite à la fin de mon stage.
- Christophe Masse, Sébastien Archambeau & Dominique Pellegrino de chez Hike-Up pour l'opportunité de prester mon stage chez eux.