Comment créer une TodoApp avec ReactJS et Firebase

Bonjour à tous, bienvenue dans ce tutoriel. Avant de commencer, vous devez vous familiariser avec les concepts de base de ReactJS. Si ce n'est pas le cas, je vous recommande de parcourir la documentation ReactJS.

Nous utiliserons les composants suivants dans cette application:

  1. ReactJS
  2. Matériel UI
  3. Firebase
  4. ExpressJS
  5. Facteur

À quoi ressemblera notre application:

Architecture d'application:

Comprendre nos composants:

Vous vous demandez peut-être pourquoi nous utilisons Firebase dans cette application. Eh bien, il fournit une authentification sécurisée , une base de données en temps réel , un composant sans serveur et un compartiment de stockage .

Nous utilisons Express ici pour ne pas avoir à gérer les exceptions HTTP. Nous allons utiliser tous les packages firebase dans notre composant functions. En effet, nous ne voulons pas rendre notre application cliente trop volumineuse, ce qui a tendance à ralentir le processus de chargement de l'interface utilisateur.

Remarque: je vais diviser ce didacticiel en quatre sections distinctes. Au début de chaque section, vous trouverez un commit git dont le code est développé dans cette section. Aussi, si vous voulez voir le code complet, il est disponible dans ce référentiel.

Section 1: Développement des API Todo

Dans cesection , nous allons développer ces éléments:

  1. Configurez les fonctions de Firebase.
  2. Installez le framework Express et créez des API Todo.
  3. Configuration de Firestore comme base de données.

Le code de l'API Todo implémenté dans cette section se trouve à ce commit.

Configurez les fonctions Firebase:

Accédez à la console Firebase.

Sélectionnez l' option Ajouter un projet . Après cela, suivez le gif ci-dessous étape par étape pour configurer le projet Firebase.

Allez dans l'onglet Fonctions et cliquez sur le bouton Commencer :

Vous verrez une boîte de dialogue contenant des instructions sur la façon de configurer les fonctions Firebase . Accédez à votre environnement local. Ouvrez un outil de ligne de commande. Pour installer les outils Firebase sur votre machine, utilisez la commande ci-dessous:

 npm install -g firebase-tools

Une fois que cela est fait, utilisez la commande firebase initpour configurer les fonctions de firebase dans votre environnement local. Sélectionnez les options suivantes lors de l'initialisation de la fonction Firebase dans l'environnement local:

  1. Quelles fonctionnalités Firebase CLI souhaitez-vous configurer pour ce dossier? Appuyez sur Espace pour sélectionner les fonctionnalités, puis sur Entrée pour confirmer vos choix => Fonctions: Configurer et déployer les Fonctions Cloud
  2. Tout d'abord, associons ce répertoire de projet à un projet Firebase…. => Utiliser un projet existant
  3. Sélectionnez un projet Firebase par défaut pour ce répertoire => nom_application
  4. Quelle langue souhaitez-vous utiliser pour écrire des fonctions Cloud? => JavaScript
  5. Voulez-vous utiliser ESLint pour détecter les bogues probables et appliquer le style? => N
  6. Voulez-vous installer des dépendances avec npm maintenant? (O / n) => Y

Une fois la configuration terminée, vous obtiendrez le message suivant:

✔ Firebase initialization complete!

Ce sera notre structure de répertoire une fois l'initialisation terminée:

+-- firebase.json +-- functions | +-- index.js | +-- node_modules | +-- package-lock.json | +-- package.json

Ouvrez maintenant le index.jsrépertoire under functions et copiez-collez le code suivant:

const functions = require('firebase-functions'); exports.helloWorld = functions.https.onRequest((request, response) => { response.send("Hello from Firebase!"); });

Déployez le code sur les fonctions Firebase à l'aide de la commande suivante:

firebase deploy

Une fois le déploiement terminé, vous obtiendrez la ligne de connexion suivante à la fin de votre ligne de commande:

> ✔ Deploy complete! > Project Console: //console.firebase.google.com/project/todoapp-/overview

Allez dans la console de projet> Fonctions et vous y trouverez l'URL de l'API. L'URL ressemblera à ceci:

//-todoapp-.cloudfunctions.net/helloWorld

Copiez cette URL et collez-la dans le navigateur. Vous obtiendrez la réponse suivante:

Hello from Firebase!

Cela confirme que notre fonction Firebase a été correctement configurée.

Installez le Framework Express:

Maintenant, installons le Expressframework dans notre projet à l'aide de la commande suivante:

npm i express

Créons maintenant un répertoire API dans le répertoire des fonctions . Dans ce répertoire, nous allons créer un fichier nommé todos.js. Supprimez tout de la index.js, puis copiez-collez le code suivant:

//index.js const functions = require('firebase-functions'); const app = require('express')(); const { getAllTodos } = require('./APIs/todos') app.get('/todos', getAllTodos); exports.api = functions.https.onRequest(app);

Nous avons attribué la fonction getAllTodos à la route / todos . Ainsi, tous les appels d'API sur cette route s'exécuteront via la fonction getAllTodos. Maintenant, allez dans le todos.jsfichier sous le répertoire API et ici nous allons écrire la fonction getAllTodos.

//todos.js exports.getAllTodos = (request, response) => { todos = [ { 'id': '1', 'title': 'greeting', 'body': 'Hello world from sharvin shah' }, { 'id': '2', 'title': 'greeting2', 'body': 'Hello2 world2 from sharvin shah' } ] return response.json(todos); }

Ici, nous avons déclaré un exemple d'objet JSON. Plus tard, nous tirerons cela du Firestore. Mais pour le moment, nous y reviendrons. Déployez maintenant ceci dans votre fonction Firebase à l'aide de la commande firebase deploy. Il demanderapour obtenir la permission de supprimer le module helloworld - entrez simplement y .

The following functions are found in your project but do not exist in your local source code: helloWorld Would you like to proceed with deletion? Selecting no will continue the rest of the deployments. (y/N) y

Une fois que cela est fait, allez dans la console de projet> Fonctions et vous y trouverez l'URL de l'API. L'API ressemblera à ceci:

//-todoapp-.cloudfunctions.net/api

Maintenant, allez dans le navigateur et copiez-collez l'URL et ajoutez / todos à la fin de cette URL. Vous obtiendrez la sortie suivante:

[ { 'id': '1', 'title': 'greeting', 'body': 'Hello world from sharvin shah' }, { 'id': '2', 'title': 'greeting2', 'body': 'Hello2 world2 from sharvin shah' } ]

Firebase Firestore:

Nous utiliserons un firestore Firebase comme base de données en temps réel pour notre application. Accédez maintenant à Console> Base de données dans Firebase Console. Pour configurer Firestore suivez le gif ci-dessous:

Une fois la configuration terminée, cliquez sur le bouton Démarrer la collecte et définissez l' ID de la collection comme todos . Cliquez sur Suivant et vous obtiendrez la fenêtre contextuelle suivante:

Ignorez la clé DocumentID. Pour le champ, le type et la valeur , reportez-vous au JSON ci-dessous. Mettez à jour la valeur en conséquence:

{ Field: title, Type: String, Value: Hello World }, { Field: body, Type: String, Value: Hello folks I hope you are staying home... }, { Field: createtAt, type: timestamp, value: Add the current date and time here }

Appuyez sur le bouton Enregistrer. Vous verrez que la collection et le document sont créés. Revenez à l'environnement local. Nous devons installer firebase-adminle package Firestore dont nous avons besoin. Utilisez cette commande pour l'installer:

npm i firebase-admin

Créez un répertoire nommé util sous le répertoire des fonctions .Accédez à ce répertoire et créez un nom de fichier admin.js. Dans ce fichier, nous importerons le package d'administration de Firebase et initialiserons l'objet de base de données Firestore. Nous allons exporter ceci pour que d'autres modules puissent l'utiliser.

//admin.js const admin = require('firebase-admin'); admin.initializeApp(); const db = admin.firestore(); module.exports = { admin, db };

Écrivons maintenant une API pour récupérer ces données. Accédez au todos.jssous le répertoire des fonctions> API . Supprimez l'ancien code et copiez-collez le code ci-dessous:

//todos.js const { db } = require('../util/admin'); exports.getAllTodos = (request, response) => { db .collection('todos') .orderBy('createdAt', 'desc') .get() .then((data) => { let todos = []; data.forEach((doc) => { todos.push({ todoId: doc.id, title: doc.data().title, body: doc.data().body, createdAt: doc.data().createdAt, }); }); return response.json(todos); }) .catch((err) => { console.error(err); return response.status(500).json({ error: err.code}); }); };

Ici, nous récupérons toutes les tâches de la base de données et les transmettons au client dans une liste.

Vous pouvez également exécuter l'application localement à l'aide de la firebase servecommande au lieu de la déployer à chaque fois. Lorsque vous exécutez cette commande, vous pouvez obtenir une erreur concernant les informations d'identification. Pour résoudre ce problème, suivez les étapes mentionnées ci-dessous:

  1. Accédez aux paramètres du projet (icône Paramètres en haut à gauche)
  2. Accédez à l' onglet des comptes de service  
  3. Là-bas, il y aura l'option de générer une nouvelle clé . Cliquez sur cette option et il téléchargera un fichier avec une extension JSON.
  4. Nous devons exporter ces informations d'identification vers notre session de ligne de commande. Utilisez la commande ci-dessous pour ce faire:
export GOOGLE_APPLICATION_CREDENTIALS="/home/user/Downloads/[FILE_NAME].json"

Après cela, exécutez la commande firebase serve. Si vous obtenez toujours l'erreur puis utilisez la commande suivante: firebase login --reauth. Cela ouvrira la page de connexion Google dans un navigateur. Une fois la connexion terminée, cela fonctionnera sans aucune erreur.

Vous trouverez une URL dans les journaux de votre outil de ligne de commande lorsque vous exécutez une commande firebase serve. Ouvrez cette URL dans le navigateur et ajoutez-la /todosaprès.

✔ functions[api]: http function initialized (//localhost:5000/todoapp-//api).

Vous obtiendrez la sortie JSON suivante dans votre navigateur:

[ { "todoId":"W67t1kSMO0lqvjCIGiuI", "title":"Hello World", "body":"Hello folks I hope you are staying home...", "createdAt":{"_seconds":1585420200,"_nanoseconds":0 } } ]

Écriture d'autres API:

Il est temps d'écrire toutes les autres API todo dont nous aurons besoin pour notre application.

  1. Créer un élément Todo: allez dans le index.jssous le répertoire des fonctions. Importez la méthode postOneTodo sous le getAllTodos existant. Attribuez également la route POST à ​​cette méthode.
//index.js const { .., postOneTodo } = require('./APIs/todos') app.post('/todo', postOneTodo);

Accédez au todos.jsrépertoire des fonctions et ajoutez une nouvelle méthode postOneTodosous la getAllTodosméthode existante .

//todos.js exports.postOneTodo = (request, response) => { if (request.body.body.trim() === '') { return response.status(400).json({ body: 'Must not be empty' }); } if(request.body.title.trim() === '') { return response.status(400).json({ title: 'Must not be empty' }); } const newTodoItem = { title: request.body.title, body: request.body.body, createdAt: new Date().toISOString() } db .collection('todos') .add(newTodoItem) .then((doc)=>{ const responseTodoItem = newTodoItem; responseTodoItem.id = doc.id; return response.json(responseTodoItem); }) .catch((err) => { response.status(500).json({ error: 'Something went wrong' }); console.error(err); }); };

Dans cette méthode, nous ajoutons un nouveau Todo à notre base de données. Si les éléments de notre corps sont vides, nous retournerons une réponse de 400 ou bien nous ajouterons les données.

Exécutez la commande firebase serve et ouvrez l'application Postman. Créez une nouvelle demande et sélectionnez le type de méthode comme POST . Ajoutez l'URL et un corps de type JSON.

URL: //localhost:5000/todoapp-//api/todo METHOD: POST Body: { "title":"Hello World", "body": "We are writing this awesome API" }

Appuyez sur le bouton d'envoi et vous obtiendrez la réponse suivante:

{ "title": "Hello World", "body": "We are writing this awesome API", "createdAt": "2020-03-29T12:30:48.809Z", "id": "nh41IgARCj8LPWBYzjU0" }

2. Supprimer l'élément Todo: Allez dans le index.jssous le répertoire des fonctions. Importez la méthode deleteTodo sous le postOneTodo existant. Attribuez également la route DELETE à cette méthode.

//index.js const { .., deleteTodo } = require('./APIs/todos') app.delete('/todo/:todoId', deleteTodo);

Accédez au todos.jset ajoutez une nouvelle méthode deleteTodosous la postOneTodométhode existante .

//todos.js exports.deleteTodo = (request, response) => { const document = db.doc(`/todos/${request.params.todoId}`); document .get() .then((doc) => { if (!doc.exists) { return response.status(404).json({ error: 'Todo not found' }) } return document.delete(); }) .then(() => { response.json({ message: 'Delete successfull' }); }) .catch((err) => { console.error(err); return response.status(500).json({ error: err.code }); }); };

Dans cette méthode, nous supprimons un Todo de notre base de données. Exécutez la commande firebase serve et accédez au facteur. Créez une nouvelle demande, sélectionnez le type de méthode comme DELETE et ajoutez l'URL.

URL: //localhost:5000/todoapp-//api/todo/ METHOD: DELETE

Appuyez sur le bouton d'envoi et vous obtiendrez la réponse suivante:

{ "message": "Delete successfull" }

3. Modifier l'élément Todo: Allez dans le index.jssous le répertoire des fonctions. Importez la méthode editTodo sous le deleteTodo existant. Attribuez également la route PUT à cette méthode.

//index.js const { .., editTodo } = require('./APIs/todos') app.put('/todo/:todoId', editTodo);

Accédez au todos.jset ajoutez une nouvelle méthode editTodosous la deleteTodométhode existante .

//todos.js exports.editTodo = ( request, response ) => { if(request.body.todoId || request.body.createdAt){ response.status(403).json({message: 'Not allowed to edit'}); } let document = db.collection('todos').doc(`${request.params.todoId}`); document.update(request.body) .then(()=> { response.json({message: 'Updated successfully'}); }) .catch((err) => { console.error(err); return response.status(500).json({ error: err.code }); }); };

Dans cette méthode, nous éditons un Todo à partir de notre base de données. N'oubliez pas ici que nous n'autorisons pas l'utilisateur à modifier les champs todoId ou createdAt. Exécutez la commande firebase serve et accédez au facteur. Créez une nouvelle demande, sélectionnez le type de méthode PUT et ajoutez l'URL.

URL: //localhost:5000/todoapp-//api/todo/ METHOD: PUT

Appuyez sur le bouton d'envoi et vous obtiendrez la réponse suivante:

{ "message": "Updated successfully" }

Structure du répertoire jusqu'à présent:

+-- firebase.json +-- functions | +-- API | +-- +-- todos.js | +-- util | +-- +-- admin.js | +-- index.js | +-- node_modules | +-- package-lock.json | +-- package.json | +-- .gitignore

Avec cela, nous avons terminé la première section de l'application. Vous pouvez aller prendre un café, faire une pause, puis nous travaillerons au développement des API utilisateur.

Section 2: Développement des API utilisateur

Dans cesection , nous allons développer ces composants:

  1. API d'authentification utilisateur (connexion et inscription).
  2. GET et mettre à jour l'API des détails de l'utilisateur.
  3. Mettez à jour l'API d'image de profil utilisateur.
  4. Sécurisation de l'API Todo existante.

Le code de l'API utilisateur implémenté dans cette section se trouve à ce commit.

Commençons donc à créer l'API d'authentification utilisateur. Accédez à la console Firebase> Authentification.

Cliquez sur le bouton Configurer la méthode de connexion . Nous utiliserons l'email et le mot de passe pour la validation de l'utilisateur. Activez l' option Email / Mot de passe .

Pour le moment, nous allons créer manuellement notre utilisateur. Tout d'abord, nous allons créer l'API de connexion. Après cela, nous créerons l'API d'inscription.

Allez dans l'onglet Utilisateurs sous Authentification, remplissez les détails de l'utilisateur et cliquez sur le bouton Ajouter un utilisateur .

1. API de connexion utilisateur:

Tout d'abord, nous devons installer le firebasepackage, qui se compose de la bibliothèque d'authentification Firebase, à l'aide de la commande suivante:

npm i firebase

Une fois l'installation terminée, allez dans le répertoire des fonctions> API . Ici, nous allons créer un users.jsfichier. Maintenant, à l'intérieur, index.jsnous importons une méthode loginUser et lui attribuons la route POST.

//index.js const { loginUser } = require('./APIs/users') // Users app.post('/login', loginUser);

Allez dans Paramètres du projet> Général et vous y trouverez la carte suivante:

Sélectionnez l'icône Web, puis suivez le gif ci-dessous:

Sélectionnez l' option Continuer vers la console . Une fois que cela est fait, vous verrez un JSON avec la configuration de firebase. Allez dans le répertoire functions> util et créez un   config.jsfichier. Copiez-collez le code suivant dans ce fichier:

// config.js module.exports = { apiKey: "............", authDomain: "........", databaseURL: "........", projectId: ".......", storageBucket: ".......", messagingSenderId: "........", appId: "..........", measurementId: "......." };

Remplacez ............par les valeurs que vous obtenez sous Console Firebase> Paramètres du projet> Général> vos applications> Extrait de code Firebase SD> config .

Copiez-collez le code suivant dans le users.jsfichier:

// users.js const { admin, db } = require('../util/admin'); const config = require('../util/config'); const firebase = require('firebase'); firebase.initializeApp(config); const { validateLoginData, validateSignUpData } = require('../util/validators'); // Login exports.loginUser = (request, response) => { const user = { email: request.body.email, password: request.body.password } const { valid, errors } = validateLoginData(user); if (!valid) return response.status(400).json(errors); firebase .auth() .signInWithEmailAndPassword(user.email, user.password) .then((data) => { return data.user.getIdToken(); }) .then((token) => { return response.json({ token }); }) .catch((error) => { console.error(error); return response.status(403).json({ general: 'wrong credentials, please try again'}); }) };

Ici, nous utilisons un module Firebase signInWithEmailAndPassword pour vérifier si les informations d'identification soumises par l'utilisateur sont correctes. S'ils ont raison, nous envoyons le jeton de cet utilisateur ou bien un statut 403 avec un message "fausses informations d'identification".

Créons maintenant validators.jssous le répertoire functions> util . Copiez-collez le code suivant dans ce fichier:

// validators.js const isEmpty = (string) => { if (string.trim() === '') return true; else return false; }; exports.validateLoginData = (data) => { let errors = {}; if (isEmpty(data.email)) errors.email = 'Must not be empty'; if (isEmpty(data.password)) errors.password = 'Must not be empty'; return { errors, valid: Object.keys(errors).length === 0 ? true : false }; };

Avec cela, notre LoginAPI est terminé. Exécutez la firebase servecommande et allez voir le facteur. Créez une nouvelle demande, sélectionnez le type de méthode comme POST et ajoutez l'URL et le corps.

URL: //localhost:5000/todoapp-//api/login METHOD: POST Body: { "email":"Add email that is assigned for user in console", "password": "Add password that is assigned for user in console" }

Appuyez sur le bouton Envoyer la demande dans Postman et vous obtiendrez la sortie suivante:

{ "token": ".........." }

Nous utiliserons ce jeton dans une prochaine partie pour obtenir les détails de l'utilisateur . N'oubliez pas que ce jeton expire dans 60 minutes . Pour générer un nouveau jeton, utilisez à nouveau cette API.

2. API d'inscription des utilisateurs:

Le mécanisme d'authentification par défaut de Firebase vous permet uniquement de stocker des informations telles que le courrier électronique, le mot de passe, etc. Mais nous avons besoin de plus d'informations pour identifier si cet utilisateur possède cette tâche afin qu'il puisse effectuer des opérations de lecture, de mise à jour et de suppression sur celui-ci.

Pour atteindre cet objectif, nous allons créer une nouvelle collection appelée utilisateurs . Sous cette collection, nous stockerons les données de l'utilisateur qui seront mappées au todo en fonction du nom d'utilisateur. Chaque nom d'utilisateur sera unique pour tous les utilisateurs de la plateforme.

Accédez au index.js. Nous importons une méthode signUpUser et lui attribuons la route POST.

//index.js const { .., signUpUser } = require('./APIs/users') app.post('/signup', signUpUser);

Accédez maintenant au validators.jset ajoutez le code suivant sous la validateLoginDataméthode.

// validators.js const isEmail = (email) => { const emailRegEx = /^(([^()\[\]\\.,;:\[email protected]"]+(\.[^()\[\]\\.,;:\[email protected]"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; if (email.match(emailRegEx)) return true; else return false; }; exports.validateSignUpData = (data) => { let errors = {}; if (isEmpty(data.email)) { errors.email = 'Must not be empty'; } else if (!isEmail(data.email)) { errors.email = 'Must be valid email address'; } if (isEmpty(data.firstName)) errors.firstName = 'Must not be empty'; if (isEmpty(data.lastName)) errors.lastName = 'Must not be empty'; if (isEmpty(data.phoneNumber)) errors.phoneNumber = 'Must not be empty'; if (isEmpty(data.country)) errors.country = 'Must not be empty'; if (isEmpty(data.password)) errors.password = 'Must not be empty'; if (data.password !== data.confirmPassword) errors.confirmPassword = 'Passowrds must be the same'; if (isEmpty(data.username)) errors.username = 'Must not be empty'; return { errors, valid: Object.keys(errors).length === 0 ? true : false }; };

Maintenant, allez dans le users.jset ajoutez le code suivant sous le loginUsermodule.

// users.js exports.signUpUser = (request, response) => { const newUser = { firstName: request.body.firstName, lastName: request.body.lastName, email: request.body.email, phoneNumber: request.body.phoneNumber, country: request.body.country, password: request.body.password, confirmPassword: request.body.confirmPassword, username: request.body.username }; const { valid, errors } = validateSignUpData(newUser); if (!valid) return response.status(400).json(errors); let token, userId; db .doc(`/users/${newUser.username}`) .get() .then((doc) => { if (doc.exists) { return response.status(400).json({ username: 'this username is already taken' }); } else { return firebase .auth() .createUserWithEmailAndPassword( newUser.email, newUser.password ); } }) .then((data) => { userId = data.user.uid; return data.user.getIdToken(); }) .then((idtoken) => { token = idtoken; const userCredentials = { firstName: newUser.firstName, lastName: newUser.lastName, username: newUser.username, phoneNumber: newUser.phoneNumber, country: newUser.country, email: newUser.email, createdAt: new Date().toISOString(), userId }; return db .doc(`/users/${newUser.username}`) .set(userCredentials); }) .then(()=>{ return response.status(201).json({ token }); }) .catch((err) => { console.error(err); if (err.code === 'auth/email-already-in-use') { return response.status(400).json({ email: 'Email already in use' }); } else { return response.status(500).json({ general: 'Something went wrong, please try again' }); } }); }

Nous validons nos données utilisateur, et ensuite nous envoyons un email et un mot de passe au module firebase createUserWithEmailAndPassword pour créer l'utilisateur. Une fois que l'utilisateur est créé avec succès, nous enregistrons les informations d'identification de l'utilisateur dans la base de données.

Avec cela, notre API SignUp est terminée. Exécutez la firebase servecommande et allez voir le facteur. Créez une nouvelle demande, sélectionnez le type de méthode comme POST . Ajoutez l'URL et le corps.

URL: //localhost:5000/todoapp-//api/signup METHOD: POST Body: { "firstName": "Add a firstName here", "lastName": "Add a lastName here", "email":"Add a email here", "phoneNumber": "Add a phone number here", "country": "Add a country here", "password": "Add a password here", "confirmPassword": "Add same password here", "username": "Add unique username here" }

Appuyez sur le bouton d'envoi de la demande dans le facteur et vous obtiendrez la sortie suivante:

{ "token": ".........." }

Maintenant, allez dans la console Firebase> Base de données et vous verrez la sortie suivante:

Comme vous pouvez le voir, la collection de nos utilisateurs est créée avec succès avec un seul document.

3. Téléchargez l'image du profil utilisateur:

Nos utilisateurs pourront télécharger leur photo de profil. Pour y parvenir, nous utiliserons le seau de stockage. Accédez à la console Firebase> Stockage et cliquez sur le Get a commencé bouton. Suivez le GIF ci-dessous pour la configuration:

Maintenant, allez dans l' onglet Règles sous Stockage et mettez à jour l'autorisation d'accès au bucket comme illustré ci-dessous:

Pour télécharger la photo de profil, nous utiliserons le package nommé busboy. Pour installer ce package, utilisez la commande suivante:

npm i busboy

Allez à index.js. Importez la méthode uploadProfilePhoto sous la méthode signUpUser existante. Attribuez également la route POST à ​​cette méthode.

//index.js const auth = require('./util/auth'); const { .., uploadProfilePhoto } = require('./APIs/users') app.post('/user/image', auth, uploadProfilePhoto);

Ici, nous avons ajouté une couche d'authentification afin que seul un utilisateur associé à ce compte puisse télécharger l'image. Créez maintenant un fichier nommé auth.jsdans le répertoire functions> utils . Copiez-collez le code suivant dans ce fichier:

// auth.js const { admin, db } = require('./admin'); module.exports = (request, response, next) => { let idToken; if (request.headers.authorization && request.headers.authorization.startsWith('Bearer ')) { idToken = request.headers.authorization.split('Bearer ')[1]; } else { console.error('No token found'); return response.status(403).json({ error: 'Unauthorized' }); } admin .auth() .verifyIdToken(idToken) .then((decodedToken) => { request.user = decodedToken; return db.collection('users').where('userId', '==', request.user.uid).limit(1).get(); }) .then((data) => { request.user.username = data.docs[0].data().username; request.user.imageUrl = data.docs[0].data().imageUrl; return next(); }) .catch((err) => { console.error('Error while verifying token', err); return response.status(403).json(err); }); };

Ici, nous utilisons le module firebase verifyIdToken pour vérifier le jeton. Après cela, nous décodons les détails de l'utilisateur et les transmettons dans la demande existante.

Accédez au users.jset ajoutez le code suivant sous la signupméthode:

// users.js deleteImage = (imageName) => { const bucket = admin.storage().bucket(); const path = `${imageName}` return bucket.file(path).delete() .then(() => { return }) .catch((error) => { return }) } // Upload profile picture exports.uploadProfilePhoto = (request, response) => { const BusBoy = require('busboy'); const path = require('path'); const os = require('os'); const fs = require('fs'); const busboy = new BusBoy({ headers: request.headers }); let imageFileName; let imageToBeUploaded = {}; busboy.on('file', (fieldname, file, filename, encoding, mimetype) => { if (mimetype !== 'image/png' && mimetype !== 'image/jpeg') { return response.status(400).json({ error: 'Wrong file type submited' }); } const imageExtension = filename.split('.')[filename.split('.').length - 1]; imageFileName = `${request.user.username}.${imageExtension}`; const filePath = path.join(os.tmpdir(), imageFileName); imageToBeUploaded = { filePath, mimetype }; file.pipe(fs.createWriteStream(filePath)); }); deleteImage(imageFileName); busboy.on('finish', () => { admin .storage() .bucket() .upload(imageToBeUploaded.filePath, { resumable: false, metadata: { metadata: { contentType: imageToBeUploaded.mimetype } } }) .then(() => { const imageUrl = `//firebasestorage.googleapis.com/v0/b/${config.storageBucket}/o/${imageFileName}?alt=media`; return db.doc(`/users/${request.user.username}`).update({ imageUrl }); }) .then(() => { return response.json({ message: 'Image uploaded successfully' }); }) .catch((error) => { console.error(error); return response.status(500).json({ error: error.code }); }); }); busboy.end(request.rawBody); };

Avec cela, notre API de téléchargement d'image de profil est terminée. Exécutez la firebase servecommande et allez voir le facteur. Créez une nouvelle demande, sélectionnez le type de méthode comme POST , ajoutez l'URL et, dans la section corps, sélectionnez le type comme données de formulaire.

La demande est protégée, vous devrez donc également envoyer le jeton de support . Pour envoyer le jeton du porteur, connectez-vous à nouveau si le jeton a expiré. Après cela, dans Postman App> onglet Autorisation> Type> Bearer Token et dans la section token, collez le token.

URL: //localhost:5000/todoapp-//api/user/image METHOD: GET Body: { REFER THE IMAGE down below }

Appuyez sur le bouton d'envoi de la demande dans le facteur et vous obtiendrez la sortie suivante:

{ "message": "Image uploaded successfully" }

4. Obtenez les détails de l'utilisateur:

Ici, nous récupérons les données de notre utilisateur à partir de la base de données. Accédez à index.jset importez la méthode getUserDetail et attribuez-lui une route GET.

// index.js const { .., getUserDetail } = require('./APIs/users') app.get('/user', auth, getUserDetail);

Maintenant, allez dans le users.jset ajoutez le code suivant après le uploadProfilePhotomodule:

// users.js exports.getUserDetail = (request, response) => { let userData = {}; db .doc(`/users/${request.user.username}`) .get() .then((doc) => { if (doc.exists) { userData.userCredentials = doc.data(); return response.json(userData); } }) .catch((error) => { console.error(error); return response.status(500).json({ error: error.code }); }); }

Nous utilisons le module firebase doc (). Get () pour dériver les détails de l'utilisateur. Avec cela, notre API GET User Details est terminée. Exécutez la firebase servecommande et allez voir le facteur. Créez une nouvelle demande, sélectionnez le type de méthode: GET et ajoutez l'URL et le corps.

La demande est protégée, vous devrez donc également envoyer le jeton de support . Pour envoyer le jeton du porteur, connectez-vous à nouveau si le jeton a expiré.

URL: //localhost:5000/todoapp-//api/user METHOD: GET

Appuyez sur le bouton d'envoi de la demande dans le facteur et vous obtiendrez la sortie suivante:

{ "userCredentials": { "phoneNumber": "........", "email": "........", "country": "........", "userId": "........", "username": "........", "createdAt": "........", "lastName": "........", "firstName": "........" } }

5. Mettez à jour les détails de l'utilisateur:

Ajoutons maintenant la fonctionnalité pour mettre à jour les détails de l'utilisateur. Accédez au index.jset copiez-collez le code suivant:

// index.js const { .., updateUserDetails } = require('./APIs/users') app.post('/user', auth, updateUserDetails);

Maintenant, allez dans le users.jset ajoutez le updateUserDetailsmodule sous l'existant getUserDetails:

// users.js exports.updateUserDetails = (request, response) => { let document = db.collection('users').doc(`${request.user.username}`); document.update(request.body) .then(()=> { response.json({message: 'Updated successfully'}); }) .catch((error) => { console.error(error); return response.status(500).json({ message: "Cannot Update the value" }); }); }

Ici, nous utilisons la méthode de mise à jour de firebase . Avec cela, notre API de mise à jour des détails de l'utilisateur est terminée. Suivez la même procédure pour une demande que pour l'API Get User Details ci-dessus avec une modification. Ajoutez le corps dans la requête ici et la méthode comme POST.

URL: //localhost:5000/todoapp-//api/user METHOD: POST Body : { // You can edit First Name, last Name and country // We will disable other Form Tags from our UI }

Appuyez sur le bouton d'envoi de la demande dans le facteur et vous obtiendrez la sortie suivante:

{ "message": "Updated successfully" }

6. Sécurisation des API Todo:

Pour sécuriser l'API Todo afin que seul l'utilisateur choisi puisse y accéder, nous apporterons quelques modifications à notre code existant. Tout d'abord, nous mettrons à jour notre index.jscomme suit:

// index.js // Todos app.get('/todos', auth, getAllTodos); app.get('/todo/:todoId', auth, getOneTodo); app.post('/todo',auth, postOneTodo); app.delete('/todo/:todoId',auth, deleteTodo); app.put('/todo/:todoId',auth, editTodo);

Nous avons mis à jour toutes les routes Todo en ajoutant de authsorte que tous les appels d'API nécessitent un jeton et ne sont accessibles que par l'utilisateur particulier.

Après cela, allez todos.jsdans le répertoire des fonctions> API .

  1. Créer l'API Todo: ouvrez todos.jset sous la méthode postOneTodo , ajoutez la clé de nom d'utilisateur comme suit:
const newTodoItem = { .., username: request.user.username, .. }

2. GET All Todos API: ouvrez le todos.jset sous la méthode getAllTodos ajoutez la clause where comme suit:

db .collection('todos') .where('username', '==', request.user.username) .orderBy('createdAt', 'desc')

Exécutez le service Firebase et testez notre API GET. N'oubliez pas d'envoyer le jeton porteur. Ici, vous obtiendrez une erreur de réponse comme suit:

{ "error": 9 }

Accédez à la ligne de commande et vous verrez les lignes suivantes enregistrées:

i functions: Beginning execution of "api"> Error: 9 FAILED_PRECONDITION: The query requires an index. You can create it here: > at callErrorFromStatus

Ouvre Ceci dans le navigateur et cliquez sur créer un index.

Une fois que l'index est construit, envoyez à nouveau la demande et vous obtiendrez le résultat suivant:

[ { "todoId": "......", "title": "......", "username": "......", "body": "......", "createdAt": "2020-03-30T13:01:58.478Z" } ]

3.   Supprimer l'API Todo: ouvrez le todos.jset sous la méthode deleteTodo ajoutez la condition suivante. Ajoutez cette condition dans la requête document.get (). Then () sous la condition ! Doc.exists .

.. if(doc.data().username !== request.user.username){ return response.status(403).json({error:"UnAuthorized"}) }

Structure des répertoires jusqu'à présent:

+-- firebase.json +-- functions | +-- API | +-- +-- todos.js | +-- +-- users.js | +-- util | +-- +-- admin.js | +-- +-- auth.js | +-- +-- validators.js | +-- index.js | +-- node_modules | +-- package-lock.json | +-- package.json | +-- .gitignore

Avec cela, nous avons terminé notre backend API. Faites une pause, prenez un café et après cela, nous commencerons à créer le front-end de notre application

Section 3: Tableau de bord utilisateur

Dans cesection , nous allons développer ces composants:

  1. Configurez ReactJS et Material UI.
  2. Créer un formulaire de connexion et d'inscription.
  3. Section des comptes de construction.

Le code du tableau de bord utilisateur implémenté dans cette section se trouve à ce commit.

1. Configurez ReactJS et Material UI:

Nous utiliserons le modèle create-react-app. Cela nous donne une structure fondamentale pour développer l'application. Pour l'installer, utilisez la commande suivante:

npm install -g create-react-app

Accédez au dossier racine du projet où se trouve le répertoire des fonctions. Initialisez notre application frontale à l'aide de la commande suivante:

create-react-app view

N'oubliez pas d'utiliser la version v16.13.1 dela bibliothèque ReactJS .

Une fois l'installation terminée, vous verrez ce qui suit dans vos journaux de ligne de commande:

cd view npm start Happy hacking!

Avec cela, nous avons configuré notre application React. Vous obtiendrez la structure de répertoires suivante:

+-- firebase.json +-- functions { This Directory consists our API logic } +-- view { This Directory consists our FrontEnd Compoenents } +-- .firebaserc +-- .gitignore

Exécutez maintenant l'application à l'aide de la commande npm start. Accédez au navigateur //localhost:3000/et vous verrez la sortie suivante:

Nous allons maintenant supprimer tous les composants inutiles. Accédez au répertoire de visualisation, puis supprimez tous les fichiersqui ont [Remove] devant eux. Pour cela, reportez-vous à l'arborescence de répertoires ci-dessous.

+-- README.md [ Remove ] +-- package-lock.json +-- package.json +-- node_modules +-- .gitignore +-- public | +-- favicon.ico [ Remove ] | +-- index.html | +-- logo192.png [ Remove ] | +-- logo512.png [ Remove ] | +-- manifest.json | +-- robots.txt +-- src | +-- App.css | +-- App.test.js | +-- index.js | +-- serviceWorker.js | +-- App.js | +-- index.css [ Remove ] | +-- logo.svg [ Remove ] | +-- setupTests.js

Allez index.htmlsous le répertoire public et supprimez les lignes suivantes:

Maintenant, allez dans le App.jssous le répertoire src et remplacez l'ancien code par le code suivant:

import React from 'react'; function App() { return ( ); } export default App;

Accédez à index.jset supprimez l'importation suivante:

import './index.css'

Je n'ai pas supprimé le App.cssni je ne l'utilise dans cette application. Mais si vous souhaitez le supprimer ou l'utiliser, vous êtes libre de le faire.

Accédez au navigateur //localhost:3000/et vous obtiendrez une sortie d'écran vide.

Pour installer Material UI, allez dans le répertoire view et copiez-collez cette commande dans le terminal:

npm install @material-ui/core

N'oubliez pas d'utiliser la version v4.9.8 de la bibliothèque Material UI.

2. Formulaire de connexion:

Pour développer le formulaire de connexion, accédez à App.js. En haut de App.jsajouter les importations suivantes:

import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'; import login from './pages/login';

Nous utilisons Switch et Route pour attribuer des itinéraires à notre TodoApp. Pour le moment, nous ajouterons uniquement la route / login et lui attribuerons un composant de connexion.

// App.js 

Créez un répertoire de pages sous le répertoire de vues existant et un fichier nommé login.jssous le répertoire de pages .

Nous importerons les composants Material UI et le package Axios dans login.js:

// login.js // Material UI components import React, { Component } from 'react'; import Avatar from '@material-ui/core/Avatar'; import Button from '@material-ui/core/Button'; import CssBaseline from '@material-ui/core/CssBaseline'; import TextField from '@material-ui/core/TextField'; import Link from '@material-ui/core/Link'; import Grid from '@material-ui/core/Grid'; import LockOutlinedIcon from '@material-ui/icons/LockOutlined'; import Typography from '@material-ui/core/Typography'; import withStyles from '@material-ui/core/styles/withStyles'; import Container from '@material-ui/core/Container'; import CircularProgress from '@material-ui/core/CircularProgress'; import axios from 'axios';

Nous ajouterons les styles suivants à notre page de connexion:

// login.js const styles = (theme) => ({ paper: { marginTop: theme.spacing(8), display: 'flex', flexDirection: 'column', alignItems: 'center' }, avatar: { margin: theme.spacing(1), backgroundColor: theme.palette.secondary.main }, form: { width: '100%', marginTop: theme.spacing(1) }, submit: { margin: theme.spacing(3, 0, 2) }, customError: { color: 'red', fontSize: '0.8rem', marginTop: 10 }, progess: { position: 'absolute' } });

Nous allons créer une classe nommée login qui a un formulaire et soumettre un gestionnaire à l'intérieur.

// login.js class login extends Component { constructor(props) { super(props); this.state = { email: '', password: '', errors: [], loading: false }; } componentWillReceiveProps(nextProps) { if (nextProps.UI.errors) { this.setState({ errors: nextProps.UI.errors }); } } handleChange = (event) => { this.setState({ [event.target.name]: event.target.value }); }; handleSubmit = (event) => { event.preventDefault(); this.setState({ loading: true }); const userData = { email: this.state.email, password: this.state.password }; axios .post('/login', userData) .then((response) => { localStorage.setItem('AuthToken', `Bearer ${response.data.token}`); this.setState({ loading: false, }); this.props.history.push('/'); }) .catch((error) => { this.setState({ errors: error.response.data, loading: false }); }); }; render() { const { classes } = this.props; const { errors, loading } = this.state; return ( Login      Sign In {loading && }     {"Don't have an account? Sign Up"}    {errors.general && (  {errors.general}  )} ); } }

À la fin de ce fichier, ajoutez l'export suivant:

export default withStyles(styles)(login); 

Ajoutez notre URL de fonctions Firebase pour afficher> package.json comme suit:

N'oubliez pas: ajoutez une clé nommée proxy sous l'objet JSON de la liste de navigation existante
"proxy": "//-todoapp-.cloudfunctions.net/api"

Installez le package d' icônes Axios et matériau à l'aide des commandes suivantes:

// Axios command: npm i axios // Material Icons: npm install @material-ui/icons

Nous avons ajouté une route de connexion dans App.js. Dans le, login.jsnous avons créé un composant de classe qui gère l'état, envoie la demande de publication à l'API de connexion à l'aide du package Axios. Si la demande aboutit, nous stockons le jeton. Si nous obtenons des erreurs dans la réponse, nous les rendons simplement sur l'interface utilisateur.

Accédez au navigateur à //localhost:3000/loginet vous verrez l'interface utilisateur de connexion suivante.

Essayez de remplir de mauvaises informations d'identification ou d'envoyer une demande vide et vous obtiendrez les erreurs. Envoyez une demande valide. Accédez à la console développeur> Application . Vous verrez que le jeton des utilisateurs est stocké dans le stockage local. Une fois la connexion réussie, nous serons redirigés vers la page d'accueil.

3. Formulaire d'inscription:

Pour développer le formulaire d'inscription, accédez à App.jset mettez à jour le Routecomposant existant avec la ligne ci-dessous:

// App.js 

N'oubliez pas d'importer:

// App.js import signup from './pages/signup';

Créez un fichier nommé signup.jssous le répertoire des pages .

Dans le signup.js, nous importerons le package Material UI et Axios:

// signup.js import React, { Component } from 'react'; import Avatar from '@material-ui/core/Avatar'; import Button from '@material-ui/core/Button'; import CssBaseline from '@material-ui/core/CssBaseline'; import TextField from '@material-ui/core/TextField'; import Link from '@material-ui/core/Link'; import Grid from '@material-ui/core/Grid'; import LockOutlinedIcon from '@material-ui/icons/LockOutlined'; import Typography from '@material-ui/core/Typography'; import Container from '@material-ui/core/Container'; import withStyles from '@material-ui/core/styles/withStyles'; import CircularProgress from '@material-ui/core/CircularProgress'; import axios from 'axios';

Nous ajouterons les styles suivants à notre page d'inscription:

// signup.js const styles = (theme) => ({ paper: { marginTop: theme.spacing(8), display: 'flex', flexDirection: 'column', alignItems: 'center' }, avatar: { margin: theme.spacing(1), backgroundColor: theme.palette.secondary.main }, form: { width: '100%', // Fix IE 11 issue. marginTop: theme.spacing(3) }, submit: { margin: theme.spacing(3, 0, 2) }, progess: { position: 'absolute' } }); 

Nous allons créer une classe nommée signup qui a un formulaire et soumettre un gestionnaire à l'intérieur.

// signup.js class signup extends Component { constructor(props) { super(props); this.state = { firstName: '', lastName: '', phoneNumber: '', country: '', username: '', email: '', password: '', confirmPassword: '', errors: [], loading: false }; } componentWillReceiveProps(nextProps) { if (nextProps.UI.errors) { this.setState({ errors: nextProps.UI.errors }); } } handleChange = (event) => { this.setState({ [event.target.name]: event.target.value }); }; handleSubmit = (event) => { event.preventDefault(); this.setState({ loading: true }); const newUserData = { firstName: this.state.firstName, lastName: this.state.lastName, phoneNumber: this.state.phoneNumber, country: this.state.country, username: this.state.username, email: this.state.email, password: this.state.password, confirmPassword: this.state.confirmPassword }; axios .post('/signup', newUserData) .then((response) => { localStorage.setItem('AuthToken', `${response.data.token}`); this.setState({ loading: false, }); this.props.history.push('/'); }) .catch((error) => { this.setState({ errors: error.response.data, loading: false }); }); }; render() { const { classes } = this.props; const { errors, loading } = this.state; return ( Sign up                              Sign Up {loading && }     Already have an account? Sign in ); } }

À la fin de ce fichier, ajoutez l'export suivant:

export default withStyles(styles)(signup); 

La logique du composant d'inscription est la même que celle du composant de connexion. Accédez au navigateur à //localhost:3000/signupet vous verrez l'interface d'inscription suivante. Une fois l'inscription réussie, nous serons redirigés vers la page d'accueil.

Essayez de remplir de mauvaises informations d'identification ou d'envoyer une demande vide et vous obtiendrez les erreurs. Envoyez une demande valide. Accédez à la console développeur> Application . Vous verrez que le jeton des utilisateurs est stocké dans le stockage local.

4. Section des comptes:

Pour créer la page du compte, nous devrons d'abord créer notre page d' accueil à partir de laquelle nous chargerons la section du compte . Accédez à App.jset mettez à jour l'itinéraire suivant:

// App.js 

N'oubliez pas l'importation:

// App.js import home from './pages/home';

Créez un nouveau fichier nommé home.js. Ce fichier sera l'index de notre application. Les sections Compte et Todo se chargent toutes deux sur cette page en fonction du clic sur le bouton.

Importez les packages Material UI, le package Axios, notre compte personnalisé, les composants todo et le middleware d'authentification.

// home.js import React, { Component } from 'react'; import axios from 'axios'; import Account from '../components/account'; import Todo from '../components/todo'; import Drawer from '@material-ui/core/Drawer'; import AppBar from '@material-ui/core/AppBar'; import CssBaseline from '@material-ui/core/CssBaseline'; import Toolbar from '@material-ui/core/Toolbar'; import List from '@material-ui/core/List'; import Typography from '@material-ui/core/Typography'; import Divider from '@material-ui/core/Divider'; import ListItem from '@material-ui/core/ListItem'; import ListItemIcon from '@material-ui/core/ListItemIcon'; import ListItemText from '@material-ui/core/ListItemText'; import withStyles from '@material-ui/core/styles/withStyles'; import AccountBoxIcon from '@material-ui/icons/AccountBox'; import NotesIcon from '@material-ui/icons/Notes'; import Avatar from '@material-ui/core/avatar'; import ExitToAppIcon from '@material-ui/icons/ExitToApp'; import CircularProgress from '@material-ui/core/CircularProgress'; import { authMiddleWare } from '../util/auth'

Nous allons définir notre tiroirWidth comme suit:

const drawerWidth = 240;

Nous ajouterons le style suivant à notre page d'accueil:

const styles = (theme) => ({ root: { display: 'flex' }, appBar: { zIndex: theme.zIndex.drawer + 1 }, drawer: { width: drawerWidth, flexShrink: 0 }, drawerPaper: { width: drawerWidth }, content: { flexGrow: 1, padding: theme.spacing(3) }, avatar: { height: 110, width: 100, flexShrink: 0, flexGrow: 0, marginTop: 20 }, uiProgess: { position: 'fixed', zIndex: '1000', height: '31px', width: '31px', left: '50%', top: '35%' }, toolbar: theme.mixins.toolbar });

Nous allons créer une classe nommée home. Cette classe aura un appel API pour obtenir la photo de profil, le prénom et le nom de l'utilisateur. En outre, il aura une logique pour choisir le composant à afficher, soit Todo ou Account:

class home extends Component { state = { render: false }; loadAccountPage = (event) => { this.setState({ render: true }); }; loadTodoPage = (event) => { this.setState({ render: false }); }; logoutHandler = (event) => { localStorage.removeItem('AuthToken'); this.props.history.push('/login'); }; constructor(props) { super(props); this.state = { firstName: '', lastName: '', profilePicture: '', uiLoading: true, imageLoading: false }; } componentWillMount = () => { authMiddleWare(this.props.history); const authToken = localStorage.getItem('AuthToken'); axios.defaults.headers.common = { Authorization: `${authToken}` }; axios .get('/user') .then((response) => { console.log(response.data); this.setState({ firstName: response.data.userCredentials.firstName, lastName: response.data.userCredentials.lastName, email: response.data.userCredentials.email, phoneNumber: response.data.userCredentials.phoneNumber, country: response.data.userCredentials.country, username: response.data.userCredentials.username, uiLoading: false, profilePicture: response.data.userCredentials.imageUrl }); }) .catch((error) => { if(error.response.status === 403) { this.props.history.push('/login') } console.log(error); this.setState({ errorMsg: 'Error in retrieving the data' }); }); }; render() { const { classes } = this.props; if (this.state.uiLoading === true) { return ( {this.state.uiLoading && } ); } else { return ( TodoApp 

{' '} {this.state.firstName} {this.state.lastName}

{' '} {' '} {' '} {' '} {' '} {' '} {this.state.render ? : } ); } } }

Ici, dans le code, vous verrez qui authMiddleWare(this.props.history);est utilisé. Ce middleware vérifie si authToken est nul. Si oui, cela repoussera l'utilisateur vers le login.js. Ceci est ajouté afin que notre utilisateur ne puisse pas accéder à la /route sans inscription ni connexion. À la fin de ce fichier, ajoutez l'exportation suivante:

export default withStyles(styles)(home); 

Vous vous demandez maintenant ce que fait ce code home.js?

 {this.state.render ?  : } 

Il vérifie l'état de rendu que nous définissons au clic du bouton. Créons le répertoire des composants et, sous ce répertoire, créons deux fichiers: account.jset todo.js.

Créons un répertoire nommé util et un fichier nommé auth.jssous ce répertoire. Copiez-collez le code suivant sous auth.js:

export const authMiddleWare = (history) => { const authToken = localStorage.getItem('AuthToken'); if(authToken === null){ history.push('/login') } }

Pour le temps être à l'intérieur du todo.jsfile nous allons juste écrire une classe qui restitue le texte Bonjour je suis todo . Nous travaillerons sur nos todos dans la section suivante:

import React, { Component } from 'react' import withStyles from '@material-ui/core/styles/withStyles'; import Typography from '@material-ui/core/Typography'; const styles = ((theme) => ({ content: { flexGrow: 1, padding: theme.spacing(3), }, toolbar: theme.mixins.toolbar, }) ); class todo extends Component { render() { const { classes } = this.props; return ( Hello I am todo   ) } } export default (withStyles(styles)(todo));

Il est maintenant temps pour la section compte. Importez les utilitaires Material UI, clsx, axios et authmiddleWare dans notre account.js.

// account.js import React, { Component } from 'react'; import withStyles from '@material-ui/core/styles/withStyles'; import Typography from '@material-ui/core/Typography'; import CircularProgress from '@material-ui/core/CircularProgress'; import CloudUploadIcon from '@material-ui/icons/CloudUpload'; import { Card, CardActions, CardContent, Divider, Button, Grid, TextField } from '@material-ui/core'; import clsx from 'clsx'; import axios from 'axios'; import { authMiddleWare } from '../util/auth';

Nous ajouterons le style suivant à notre page de compte:

// account.js const styles = (theme) => ({ content: { flexGrow: 1, padding: theme.spacing(3) }, toolbar: theme.mixins.toolbar, root: {}, details: { display: 'flex' }, avatar: { height: 110, width: 100, flexShrink: 0, flexGrow: 0 }, locationText: { paddingLeft: '15px' }, buttonProperty: { position: 'absolute', top: '50%' }, uiProgess: { position: 'fixed', zIndex: '1000', height: '31px', width: '31px', left: '50%', top: '35%' }, progess: { position: 'absolute' }, uploadButton: { marginLeft: '8px', margin: theme.spacing(1) }, customError: { color: 'red', fontSize: '0.8rem', marginTop: 10 }, submitButton: { marginTop: '10px' } });

Nous allons créer un composant de classe nommé account. Pour le moment, copiez-collez simplement le code suivant:

// account.js class account extends Component { constructor(props) { super(props); this.state = { firstName: '', lastName: '', email: '', phoneNumber: '', username: '', country: '', profilePicture: '', uiLoading: true, buttonLoading: false, imageError: '' }; } componentWillMount = () => { authMiddleWare(this.props.history); const authToken = localStorage.getItem('AuthToken'); axios.defaults.headers.common = { Authorization: `${authToken}` }; axios .get('/user') .then((response) => { console.log(response.data); this.setState({ firstName: response.data.userCredentials.firstName, lastName: response.data.userCredentials.lastName, email: response.data.userCredentials.email, phoneNumber: response.data.userCredentials.phoneNumber, country: response.data.userCredentials.country, username: response.data.userCredentials.username, uiLoading: false }); }) .catch((error) => { if (error.response.status === 403) { this.props.history.push('/login'); } console.log(error); this.setState({ errorMsg: 'Error in retrieving the data' }); }); }; handleChange = (event) => { this.setState({ [event.target.name]: event.target.value }); }; handleImageChange = (event) => { this.setState({ image: event.target.files[0] }); }; profilePictureHandler = (event) => { event.preventDefault(); this.setState({ uiLoading: true }); authMiddleWare(this.props.history); const authToken = localStorage.getItem('AuthToken'); let form_data = new FormData(); form_data.append('image', this.state.image); form_data.append('content', this.state.content); axios.defaults.headers.common = { Authorization: `${authToken}` }; axios .post('/user/image', form_data, { headers: { 'content-type': 'multipart/form-data' } }) .then(() => { window.location.reload(); }) .catch((error) => { if (error.response.status === 403) { this.props.history.push('/login'); } console.log(error); this.setState({ uiLoading: false, imageError: 'Error in posting the data' }); }); }; updateFormValues = (event) => { event.preventDefault(); this.setState({ buttonLoading: true }); authMiddleWare(this.props.history); const authToken = localStorage.getItem('AuthToken'); axios.defaults.headers.common = { Authorization: `${authToken}` }; const formRequest = { firstName: this.state.firstName, lastName: this.state.lastName, country: this.state.country }; axios .post('/user', formRequest) .then(() => { this.setState({ buttonLoading: false }); }) .catch((error) => { if (error.response.status === 403) { this.props.history.push('/login'); } console.log(error); this.setState({ buttonLoading: false }); }); }; render() { const { classes, ...rest } = this.props; if (this.state.uiLoading === true) { return ( {this.state.uiLoading && }  ); } else { return ( {this.state.firstName} {this.state.lastName}  

À la fin de ce fichier, ajoutez l'export suivant:

export default withStyles(styles)(account); 

Il account.jsy a beaucoup de composants utilisés. Voyons d'abord à quoi ressemble notre application. Après cela, j'expliquerai tous les composants utilisés et pourquoi ils sont utilisés.

Accédez au navigateur et si votre jeton a expiré, il vous redirigera vers la   loginpage. Ajoutez vos coordonnées et connectez-vous à nouveau. Une fois que vous avez fait cela, allez dans l'onglet Compte et vous trouverez l'interface utilisateur suivante:

Il y a 3 gestionnaires dans la section Compte:

  1. componentWillMount : Il s'agit de la méthode de cycle de vie intégrée de React. Nous l'utilisons pour charger les données avant le cycle de vie du rendu et mettre à jour nos valeurs d'état.
  2. ProfilePictureUpdate: Il s'agit de notre gestionnaire personnalisé que nous utilisons pour que, lorsque notre utilisateur clique sur le bouton Télécharger une photo, il envoie les données à un serveur et recharge la page pour afficher la nouvelle image de profil de l'utilisateur.
  3. updateFormValues: Il s'agit également de notre gestionnaire personnalisé pour mettre à jour les détails de l'utilisateur. Ici, l'utilisateur peut mettre à jour son prénom, son nom et son pays. Nous n'autorisons pas les mises à jour des e-mails et du nom d'utilisateur car notre logique backend dépend de ces clés.

En dehors de ces 3 gestionnaires, il s'agit d'une page de formulaire avec un style au-dessus. Voici la structure des répertoires jusqu'à ce point dans le dossier de vue:

+-- public +-- src | +-- components | +-- +-- todo.js | +-- +-- account.js | +-- pages | +-- +-- home.js | +-- +-- login.js | +-- +-- signup.js | +-- util | +-- +-- auth.js | +-- README.md | +-- package-lock.json | +-- package.json | +-- .gitignore

Avec cela, nous avons complété notre tableau de bord de compte. Maintenant, allez prendre un café, faites une pause et dans la section suivante, nous allons créer le tableau de bord Todo.

Section 4: Tableau de bord Todo

Dans cesection , nous allons développer l'interface utilisateur pour ces fonctionnalités du tableau de bord Todos:

  1. Ajouter un Todo:
  2. Obtenez tous les todos:
  3. Supprimer une tâche
  4. Modifier une tâche
  5. Obtenez un todo
  6. Application du thème

Le code Todo Dashboard implémenté dans cette section peut être trouvé à ce commit.

Allez todos.jssous le répertoire des composants . Ajoutez les importations suivantes aux importations existantes:

import Button from '@material-ui/core/Button'; import Dialog from '@material-ui/core/Dialog'; import AddCircleIcon from '@material-ui/icons/AddCircle'; import AppBar from '@material-ui/core/AppBar'; import Toolbar from '@material-ui/core/Toolbar'; import IconButton from '@material-ui/core/IconButton'; import CloseIcon from '@material-ui/icons/Close'; import Slide from '@material-ui/core/Slide'; import TextField from '@material-ui/core/TextField'; import Grid from '@material-ui/core/Grid'; import Card from '@material-ui/core/Card'; import CardActions from '@material-ui/core/CardActions'; import CircularProgress from '@material-ui/core/CircularProgress'; import CardContent from '@material-ui/core/CardContent'; import MuiDialogTitle from '@material-ui/core/DialogTitle'; import MuiDialogContent from '@material-ui/core/DialogContent'; import axios from 'axios'; import dayjs from 'dayjs'; import relativeTime from 'dayjs/plugin/relativeTime'; import { authMiddleWare } from '../util/auth';

Nous devons également ajouter les éléments CSS suivants dans les composants de style existants:

const styles = (theme) => ({ .., // Existing CSS elements title: { marginLeft: theme.spacing(2), flex: 1 }, submitButton: { display: 'block', color: 'white', textAlign: 'center', position: 'absolute', top: 14, right: 10 }, floatingButton: { position: 'fixed', bottom: 0, right: 0 }, form: { width: '98%', marginLeft: 13, marginTop: theme.spacing(3) }, toolbar: theme.mixins.toolbar, root: { minWidth: 470 }, bullet: { display: 'inline-block', margin: '0 2px', transform: 'scale(0.8)' }, pos: { marginBottom: 12 }, uiProgess: { position: 'fixed', zIndex: '1000', height: '31px', width: '31px', left: '50%', top: '35%' }, dialogeStyle: { maxWidth: '50%' }, viewRoot: { margin: 0, padding: theme.spacing(2) }, closeButton: { position: 'absolute', right: theme.spacing(1), top: theme.spacing(1), color: theme.palette.grey[500] } });

Nous ajouterons la transition pour la boîte de dialogue contextuelle:

const Transition = React.forwardRef(function Transition(props, ref) { return ; });

Supprimez la classe todo existante et copiez-collez la classe suivante:

class todo extends Component { constructor(props) { super(props); this.state = { todos: '', title: '', body: '', todoId: '', errors: [], open: false, uiLoading: true, buttonType: '', viewOpen: false }; this.deleteTodoHandler = this.deleteTodoHandler.bind(this); this.handleEditClickOpen = this.handleEditClickOpen.bind(this); this.handleViewOpen = this.handleViewOpen.bind(this); } handleChange = (event) => { this.setState({ [event.target.name]: event.target.value }); }; componentWillMount = () => { authMiddleWare(this.props.history); const authToken = localStorage.getItem('AuthToken'); axios.defaults.headers.common = { Authorization: `${authToken}` }; axios .get('/todos') .then((response) => { this.setState({ todos: response.data, uiLoading: false }); }) .catch((err) => { console.log(err); }); }; deleteTodoHandler(data) { authMiddleWare(this.props.history); const authToken = localStorage.getItem('AuthToken'); axios.defaults.headers.common = { Authorization: `${authToken}` }; let todoId = data.todo.todoId; axios .delete(`todo/${todoId}`) .then(() => { window.location.reload(); }) .catch((err) => { console.log(err); }); } handleEditClickOpen(data) { this.setState({ title: data.todo.title, body: data.todo.body, todoId: data.todo.todoId, buttonType: 'Edit', open: true }); } handleViewOpen(data) { this.setState({ title: data.todo.title, body: data.todo.body, viewOpen: true }); } render() { const DialogTitle = withStyles(styles)((props) => { const { children, classes, onClose, ...other } = props; return (  {children} {onClose ? (    ) : null}  ); }); const DialogContent = withStyles((theme) => ({ viewRoot: { padding: theme.spacing(2) } }))(MuiDialogContent); dayjs.extend(relativeTime); const { classes } = this.props; const { open, errors, viewOpen } = this.state; const handleClickOpen = () => { this.setState({ todoId: '', title: '', body: '', buttonType: '', open: true }); }; const handleSubmit = (event) => { authMiddleWare(this.props.history); event.preventDefault(); const userTodo = { title: this.state.title, body: this.state.body }; let options = {}; if (this.state.buttonType === 'Edit') { options = { url: `/todo/${this.state.todoId}`, method: 'put', data: userTodo }; } else { options = { url: '/todo', method: 'post', data: userTodo }; } const authToken = localStorage.getItem('AuthToken'); axios.defaults.headers.common = { Authorization: `${authToken}` }; axios(options) .then(() => { this.setState({ open: false }); window.location.reload(); }) .catch((error) => { this.setState({ open: true, errors: error.response.data }); console.log(error); }); }; const handleViewClose = () => { this.setState({ viewOpen: false }); }; const handleClose = (event) => { this.setState({ open: false }); }; if (this.state.uiLoading === true) { return ( {this.state.uiLoading && }  ); } else { return ( {this.state.buttonType === 'Edit' ? 'Edit Todo' : 'Create a new Todo'}   {this.state.buttonType === 'Edit' ? 'Save' : 'Submit'}                {this.state.todos.map((todo) => (     {todo.title}   {dayjs(todo.createdAt).fromNow()}   {`${todo.body.substring(0, 65)}`}     this.handleViewOpen({ todo })}> {' '} View{' '}   this.handleEditClickOpen({ todo })}> Edit   this.deleteTodoHandler({ todo })}> Delete     ))}    {this.state.title}       ); } } }

À la fin de ce fichier, ajoutez l'export suivant:

export default withStyles(styles)(todo); 

Nous allons d'abord comprendre comment fonctionne notre interface utilisateur et après cela, nous comprendrons le code. Accédez au navigateur et vous obtiendrez l'interface utilisateur suivante:

Cliquez sur le bouton Ajouter dans le coin inférieur droit et vous obtiendrez l'écran suivant:

Ajoutez le titre et les détails de Todo et appuyez sur le bouton Soumettre. Vous obtiendrez l'écran suivant:

Après cela, cliquez sur le bouton Afficher et vous pourrez voir tous les détails du Todo:

Cliquez sur le bouton Modifier et vous pourrez modifier la tâche:

Cliquez sur le bouton Supprimer et vous pourrez supprimer le Todo. Maintenant que nous savons comment fonctionne Dashboard, nous allons comprendre les composants qui y sont utilisés.

1. Add Todo: Pour implémenter le add todo, nous utiliserons le composant Dialogue de Material UI. Ce composant implémente une fonctionnalité de hook. Nous utilisons les classes donc nous supprimerons cette fonctionnalité.

// This sets the state to open and buttonType flag to add: const handleClickOpen = () => { this.setState({ todoId: '', title: '', body: '', buttonType: '', open: true }); }; // This sets the state to close: const handleClose = (event) => { this.setState({ open: false }); };

En dehors de cela, nous modifierons également l'emplacement du bouton Ajouter Todo.

// Position our button floatingButton: { position: 'fixed', bottom: 0, right: 0 }, 

Nous allons maintenant remplacer la balise de liste par un formulaire à l'intérieur de ce dialogue. Cela nous aidera à ajouter le nouveau todo.

// Show Edit or Save depending on buttonType state {this.state.buttonType === 'Edit' ? 'Save' : 'Submit'} // Our Form to add a todo    // TextField here   // TextField here   

lehandleSubmitse compose de logique pour lire l' buttonTypeétat. Si l'état est une chaîne vide, (“”)il sera publié sur l'API Add Todo. Si l'état est Editalors dans ce scénario, il mettra à jour le Todo Edit.

2. Get Todos: Pour afficher les todos, nous allons utiliser le Grid containeret à l'intérieur, nous plaçons le Grid item. À l'intérieur de cela, nous utiliserons un Cardcomposant pour afficher les données.

 {this.state.todos.map((todo) => (    // Here will show Todo with view, edit and delete button   ))} 

Nous utilisons la carte pour afficher l'élément todo lorsque l'API les envoie dans une liste. Nous utiliserons le cycle de vie componentWillMount pour obtenir et définir l'état avant l'exécution du rendu. Il y a 3 boutons ( afficher, modifier et supprimer ) donc nous aurons besoin de 3 gestionnaires pour gérer l'opération lorsque le bouton est cliqué. Nous en apprendrons davantage sur ces boutons dans leurs sous-sections respectives.

3. Edit Todo: Pour l'édition todo, nous réutilisons le code contextuel de dialogue qui est utilisé dans add todo. Pour différencier les clics de bouton, nous utilisons un buttonTypeétat. Pour Add Todo, l'   buttonTypeétat est (“”)tandis que pour modifier todo c'est Edit.

handleEditClickOpen(data) { this.setState({ .., buttonType: 'Edit', .. }); }

Dans la handleSubmitméthode, nous lisons l' buttonTypeétat, puis envoyons la demande en conséquence.

4. Supprimer Todo: Lorsque ce bouton est cliqué, nous envoyons l'objet todo à notre deleteTodoHandler, puis il envoie la demande plus loin au backend.

 this.deleteTodoHandler({ todo })}>Delete

5. Afficher Todo: Lors de l'affichage des données, nous les avons tronquées afin que l'utilisateur puisse avoir un aperçu de ce que le todo est sur. Mais si un utilisateur souhaite en savoir plus à ce sujet, il doit cliquer sur le bouton Afficher.

Pour cela, nous utiliserons le dialogue Personnalisé. À l'intérieur de cela, nous utilisons DialogTitle et DialogContent. Il affiche notre titre et notre contenu. Dans DialougeContent, nous utiliserons le formulaire pour afficher le contenu que l'utilisateur a publié. (C'est une solution que j'ai trouvée, il y en a beaucoup et vous êtes libre d'en essayer d'autres.)

// This is used to remove the underline of the Form InputProps={{ disableUnderline: true }} // This is used so that user cannot edit the data readonly

6. Application du thème: Ceci est la dernière étape de notre application. Nous appliquerons un thème sur notre application. Pour cela, nous utilisons createMuiThemeet à ThemeProviderpartir de l'interface utilisateur matérielle. Copiez-collez le code suivant dans App.js:

import { ThemeProvider as MuiThemeProvider } from '@material-ui/core/styles'; import createMuiTheme from '@material-ui/core/styles/createMuiTheme'; const theme = createMuiTheme({ palette: { primary: { light: '#33c9dc', main: '#FF5722', dark: '#d50000', contrastText: '#fff' } } }); function App() { return (  // Router and switch will be here.  ); }

Nous avons manqué d'appliquer un thème à notre bouton todo.jsdans le CardActions. Ajoutez la balise de couleur pour le bouton Afficher, Modifier et Supprimer.

Allez dans le navigateur et vous constaterez que tout est pareil sauf que l'application est d'une couleur différente.

Et nous avons terminé! Nous avons construit une TodoApp en utilisant ReactJS et Firebase. Si vous l'avez construit jusqu'à présent, alors une très grande félicitations à vous pour cette réalisation.

N'hésitez pas à me contacter sur Twitter et Github.