Préambule

L'objectif principal de ce TP est d'implémenter une pseudo boutique en ligne, afin de mettre en oeuvre de façon plus poussée les principes de vuex et de vue-router.  Le scénario à implémenter est le suivant :

  • Quand l'utilisateur se trouve sur la page principale de la boutique il voit apparaître :
    • en haut, une barre de navigation avec un seul bouton : "Login" 
    • au centre, un texte du genre "bienvenue à la boutique"

NB : pour simplifier l'exercice et notamment la gestion du panier, on part du principe que des boutons ne vont apparaître que si l'utilisateur est authentifié via la page de login.

 

  • Quand on clique sur "Login", le centre est remplacé par une page où il est possible de fournir un login/mot de passe pour la boutique.
  • S'ils correspondent à un utilisateur valide, alors on considère que l'on récupère les informations liées à l'utilisateur, que l'on stocke dans le store, comme dans les TPs précédents. De plus :
    • le texte du bouton doit changer pour indiquer "Logout".
    • les boutons "Acheter", "Payer", et "Mes commandes" doivent apparaître.

 

  • Quand on clique sur "Acheter", le centre est remplacé par une page en 2 parties :
    • A gauche, une liste apparaît avec chaque nom de virus suivi d'un champ de saisie numérique permettant d'indiquer le nombre d'exemplaires voulus, et un bouton permettant de mettre le virus dans le panier. Il est également possible de sélectionner plusieurs virus et de tous les mettre dans le panier en une seule fois, grâce à un clic sur un bouton en bas de la liste. Dans les deux cas, les articles choisis et leur nombre sont stockés dans le store, dans les informations de l'utilisateur.
    • A droite, une liste apparaît à droite récapitulant les virus choisis se trouvant dans le panier, ainsi que le montant total (déductions de promotions comprises). En bas du panier,,il est possible de cliquer sur un bouton "Acheter" pour créer une commande, dans l'état "waiting_payment". Une boite de dialogue apparaît faisant apparaître l'uuid de la commande.

 

  • Quand on clique sur "Payer", le centre est remplacé par une page où il est possible de saisir deux uuids : celui de la commande et celui d'une transaction bancaire. Quand on clique sur un bouton "Vérifier", on vérifie :
    • s'il existe une transaction bancaire avec cet uuid,
    • si le montant correspond au montant de la commande,
    • si le destinataire est bien le n° de compte de la boutique.
  • Si tout est ok, la commande passe en état "finalized".

 

  • Quand on clique sur "Mes commandes", le centre est remplacé par une page où il est possible de voir l'historique des commandes passées/en cours. Pour celles qui sont en état "waiting_payment", un bouton permet de l'annuler, et un autre permet de la payer. Un clic sur ce dernier redirige vers la page affichée lorsque l'on clique sur "Payer" dans la barre de navigation, mais avec le champ id de commande déjà rempli.

 

0°/ Mise en place

La structuration générale reste la même que pour les TPs précédents. De plus, certains composants vont être réutilisés. La mise en place initiale la plus simple consiste donc à :

  • créer un nouveau projet avec vue-cli,
  • remplacer le répertoire src de ce nouveau projet, par celui du TP 3.

En revanche, pour suivre et tester ces les scénarios proposés, le fichier source de données utilisés dans les TPs précédents (c.a.d. datasource/data.js) a été modifié. Vous devez télécharger la nouvelle version [ ici ] et l'utiliser pour ce TP.

 

1°/ Modularisation du store

Comme l'application DrMad aura à terme des fonctionnalités bien séparées, il est important de modulariser le store. Pour cela :

  • Dans le répertoire store, créer deux fichiers : shop.js et bank.js, en reprenant l'exemple vu en TD sur les modules vuex
  • Dans shop.js copier/coller depuis index.js tout ce qui concerne la gestion de la boutique,
  • Dans bank.js copier/coller depuis index.js tout ce qui concerne la gestion des comptes bancaires
  • Modifier index.js pour importer shop.js et bank.js et créer le store à partir de ceux 2 modules.

Pour les exercices suivants, il va falloir modifier le module shop afin d'implémenter les fonctionnalités demandées.

 

2°/ Le composant racine de la boutique

  • Dans le répertoire views, créer un composant ShopView.vue, avec un template du style :
<template>
  <div>
    <h1>Boutique</h1>
    <router-view name="shopmain"></router-view>
  </div>
</template>
  • Grâce à <router-view>, le composant va pouvoir afficher les différents composants pour gérer la boutique, grâce à une route à 2 niveaux. 
  • Il faut donc modifiez router/index.js en conséquence.
  • la route /shop est la route racine. Elle doit afficher le composant ShopView (NB : grâce a <router-view> se trouvant dans App.vue) et elle a 5 enfants, qui vont tous s'afficher dans l'emplacement shopmain.
  • la route /shop/home doit permettre d'afficher ShopHome.vue. Elle a comme alias /shop (cf. https://router.vuejs.org/guide/essentials/redirect-and-alias.html pour voir des exemples d'alias)
  • la route /shop/buy doit permettre d'afficher ShopBuy.vue
  • la route /shop/pay/:idcmd doit permettre d'afficher ShopPay.vue. Le paramètre de cette route idcmd doit être transformé en un props pour ShopPay. (cf. section 6°)
  • la route /shop/login doit permettre d'afficher ShopLogin.vue.
  • la route /shop/orders doit permettre d'afficher ShopOrders.vue

 

3°/ L'accueil

  • Dans le répertoire views, créer un composant ShopHome.vue.
  • L'objectif de ce composant est juste d'afficher un texte de bienvenue à la boutique.

 

4°/ Acheter des items

L'objectif est de permettre de sélectionner des virus et de les ajouter à un "panier d'achat", en plus ou moins grand nombre. (NB : cela impose de modifier les composant CheckedList.vue créé dans le TP précédent.)

4.1°/ Le composant racine des achats

  • Dans le répertoire views, créer un composant ShopBuy.vue.
  • Ce composant se contente d'afficher à gauche un composant ItemsList.vue et à droite BasketList.vue

4.2°/ Les items à acheter

  • Modifier le composant CheckedList.vue afin que :
    • il ait un props supplémentaire nommée itemAmount de type booléen.
    • un champ de saisie numérique soit ajouté devant chaque bouton se trouvant après un item (cf. <input> type number), si itemAmount vaut true.
    • quand on clique sur ce bouton, l'événement envoyé doit contenir l'indice de l'item ET la valeur du champ numérique si celui-ci est visible
    • quand on clique sur le bouton se trouvant après la liste des items, l'événement envoyé doit contenir un tableau avec des couples indice/valeur champ numérique (si le champ est visible), pour chaque item sélectionné. Cela doit désélectionner les items.

 

  • Dans le répertoire components, créer un composant ItemsList.vue.
  • Ce composant doit faire la même chose que VirusesView.vue des TPs précédents, mais en utilisant la version modifié de CheckedList.vue pour :
    • que chaque item fasse apparaître le nom du virus, son prix, les promotions, le champ numérique et le bouton pour mettre au panier.
    • quand l'événement clic sur le bouton d'item est reçu, ajouter cet item ainsi que sa quantité dans le panier de l'utilisateur courant stocké dans le store (NB : dans shopUser)
    • quand l'événement clic sur le bouton de fin de liste est reçu, ajouter tous les items et leur quantité dans le panier de l'utilisateur.

 

  • Modifier le module shop du store, afin de pouvoir gérer un panier pour l'utilisateur courant
  • Il existe plusieurs solutions pour faire ces modifications, la plus "logique" étant d'ajouter un objet dans le state représentant le panier.
  • Cependant, on peut également prendre en compte le côté back-end, afin de gérer les objets de la même façon côté front-end.
  • Pour ce TP, c'est la solution qui est choisie.
  • Pour ce faire, on part du fait que une fois l'utilisateur authentifié, l'API renvoie un objet avec la structure suivante (en syntaxe mongoose) :
{
  name: {type: String, required: true},
  login: {type: String, required: true},
  email: {type: String, required: true},
  session: {type: String}, 
  basket: BasketSchema,
}
  • basket a lui-même la structure suivante :
{
  items: [{  
    item: {type: Schema.Types.ObjectId, required: true, ref: 'Item'},
    amount: {type: Number, required: true, min:0}, 
  }]
}
  • La solution à suivre est donc d'ajouter des mutations/actions dans le store permettant de mettre à jour directement l'objet basket de l'utilisateur courant (avec la structure ci-dessus), plutôt que d'ajouter une variable dans le state.
  • NB : normalement, à chaque modification du panier, celles-ci doivent être enregistrée dans la BdD. Dans le cas présent, on ne peut pas modifier le fichier source de données. On se contentera donc de créer une action appelant un service qui simule la requête de mise à jour mais qui se contente d'afficher les informations à envoyer (c.a.d. l'id de l'utilisateur + l'objet basket)

 

4.3°/ Le panier

  • Dans le répertoire components, créer un composant BasketList.vue.
  • Ce composant doit utiliser la version modifié de CheckedList.vue pour :
    • afficher chaque item du panier avec un bouton supprimer après,
    • afficher un bouton "vider le panier" après cette liste,
    • quand l'événement clic sur le bouton d'item est reçu, supprimer cet item du panier de l'utilisateur.
    • quand l'événement clic sur le bouton après la liste est reçu, supprimer le contenu du panier.
  • De plus, le composant doit afficher un bouton "Acheter" permettant de valider la commande. Quand on clique sur ce bouton, il faut normalement envoyer une requête à l'API (avec l'id utilisateur comme donnée) et recevoir en retour un objet représentant la commande. Dans la BdD cette commande est représentée par le schéma OrderSchema suivant  :
{
  items: [{    
    item: {
      name: {type: String, required: true},
      description: {type: String},
      price: {type: Number, required: true, min: 0}, 
      promotion: [{
        discount: {type: Number, required: true, min: 0}, 
        amount: {type: Number, required: true, min: 0}, 
      }],
      object: {type: String, required: true}
    },
    amount: {type: Number, required: true, min:0}, 
  }],
  date: {type: Date, default: Date.now},
  total: {type: Number, required: true, min:0}, 
  status: {type: String, require: true, enum: ['waiting_payment', 'finalized', 'cancelled']},
  uuid: {type: String, required: true }, // value MUST BE an unique uuid v4
}
  • Dans ce TP, la solution la plus simple consiste à créer une fonction de service qui va créer un objet avec cette structure à partir de ce qu'il y a stocké dans le panier.
  • Cette fonction est appelée par une action du store, qui stocke l'objet dans le champ orders de l'utilisateur courant (ce qui implique également de créer les mutations pour ce faire). En effet, dans la BdD, le schéma d'un utilisateur contient un champ orders contenant toutes ses commandes. Il est donc logique de suivre le même système plutôt que d'utiliser une nouvelle variable dans le state.

Problème : les fonctions de service utilisant le fichier source de données se trouvent dans controller.js, donc "hors" de vuejs. Il faut donc trouver un moyen d'accéder au store dans controller.js afin de pouvoir récupérer le panier courant.

 

5°/ Payer une commande

  • Dans le répertoire views, créer un composant ShopPay.vue.
  • Ce composant prend en props l'id d'une commande et affiche :
    • un champ texte pour saisir l'id d'une commande. Si la props mentionnée ci-dessus est définie, alors ce champ de saisie est initialisé avec la valeur de la props.
    • un bouton "Payer". Quand on clique sur celui-ci, on appelle une action du store qui va :
      • en premier lieu, vérifier si l'id de la commande correspond à une commande existante dans le champ orders de l'utilisateur courant, et dans ce cas, récupérer l'objet représentant la commande,
      • sinon, appeler une fonction de service de shop.service.js, qui vérifie que l'id de la commande correspond bien à une commande existante pour l'utilisateur courant, en cherchant dans le fichier de source de données, et dans ce cas, récupérer l'objet représentant la commande,
      • si l'id est invalide, l'action du store s'arrête et le composant doit afficher un dialogue d'erreur
      • sinon, on passe la commande à l'état "finalized", considérant qu'elle est effectivement payée.

NB : dans le TP suivant, la gestion d'un compte bancaire sera ajoutée, ce qui permettra de vérifier si le montant de la commande correspond effectivement à une transaction bancaire de paiement.

 

6°/ Authentification

Cette partie a déjà été implémentée dans les TPs précédents. Il suffit donc de reprendre le composant déjà écrit., et en faisant quelques modifications

La première concerne le fichier datasource/controller.js. En effet, dans la solution proposée, la fonction qui recherche un utilisateur dans le fichier source de données data.js se contente :

  • de vérifier le login mais pas le mot de passe : il faut également faire cette vérification.
  • de renvoyer TOUT l'objet utilisateur : il faut retourner uniquement les champs qui seraient renvoyés par l'API (cf. schéma mongoose en 4.2)

La deuxième consiste à afficher un dialogue d'erreur si le login/mot de passe n'est pas valide.

Enfin, si l’authentification est correcte, on doit suivre la route /shop/buy.

 

7°/ Commandes passées

  • Dans le répertoire views, créer un composant ShopOrders.vue.
  • Ce composant utilise CheckedList.vue pour afficher (sans case à cocher) la liste des commandes de l'utilisateur courant, donc se trouvant dans le champ orders (cf. ci-dessus)
  • Chaque élément de la liste affiche son montant, son état (c.a.d. champ status), et est suivi par des boutons "Payer" et "Annuler" si la commande est dans l'état "waiting_payment".
  • Quand on clique sur "Payer", on suit la route /shop/pay/:idcmd, en mettant comme valeur d'idcmd l'id de la commande que l'on veut payer.
  • Quand on clique sur "Annuler", la commande doit passer à l'état "cancelled".

Pour réaliser ces opérations, il faut écrire des action/mutation dans le store, notamment :

  • une action qui récupère les commandes passées de l'utilisateur courant, grâce à une fonction de service dans shop.service.js (à écrire), qui elle même fait appel à une fonction de datasource/controller.js (à écrire).
  • une mutation qui met à jour le champ orders de l'utilisateur courant avec une liste de commande,
  • une mutation qui met à jour l'état d'une commande de l'utilisateur courant.

Afin d'aller chercher une première fois les commandes passées, il est possible d'appeler l'action du store dans la fonction mounted() du composant.