So erstellen Sie eine TodoApp mit ReactJS und Firebase

Hallo Leute, willkommen zu diesem Tutorial. Bevor wir beginnen, sollten Sie mit den grundlegenden ReactJS-Konzepten vertraut sein. Wenn nicht, würde ich empfehlen, dass Sie die ReactJS-Dokumentation durchgehen.

Wir werden die folgenden Komponenten in dieser Anwendung verwenden:

  1. ReactJS
  2. Material-Benutzeroberfläche
  3. Feuerbasis
  4. ExpressJS
  5. Postbote

Wie unsere Anwendung aussehen wird:

Anwendungsarchitektur:

Unsere Komponenten verstehen:

Sie fragen sich vielleicht, warum wir in dieser Anwendung Firebase verwenden. Nun, es bietet eine sichere Authentifizierung , eine Echtzeitdatenbank , eine serverlose Komponente und einen Speicherbereich .

Wir verwenden hier Express, damit wir keine HTTP-Ausnahmen behandeln müssen. Wir werden alle Firebase-Pakete in unserer Funktionskomponente verwenden. Dies liegt daran, dass wir unsere Client-Anwendung nicht zu groß machen möchten, was den Ladevorgang der Benutzeroberfläche verlangsamt.

Hinweis: Ich werde dieses Tutorial in vier separate Abschnitte unterteilen. Am Anfang jedes Abschnitts finden Sie ein Git-Commit, dessen Code in diesem Abschnitt entwickelt wurde. Wenn Sie den vollständigen Code anzeigen möchten, ist er in diesem Repository verfügbar.

Abschnitt 1: Entwicklung von Todo-APIs

In diesemAbschnitt werden wir diese Elemente entwickeln:

  1. Konfigurieren Sie die Firebase-Funktionen.
  2. Installieren Sie das Express-Framework und erstellen Sie Todo-APIs.
  3. Firestore als Datenbank konfigurieren.

Der in diesem Abschnitt implementierte Todo-API-Code finden Sie bei diesem Commit.

Firebase-Funktionen konfigurieren:

Gehen Sie zur Firebase-Konsole.

Wählen Sie die Option Projekt hinzufügen. Folgen Sie anschließend Schritt für Schritt dem unten stehenden GIF, um das Firebase-Projekt zu konfigurieren.

Gehen Sie zur Registerkarte Funktionen und klicken Sie auf die Schaltfläche Erste Schritte :

Sie sehen ein Dialogfeld mit Anweisungen zum Einrichten der Firebase-Funktionen . Gehen Sie zu Ihrer lokalen Umgebung. Öffnen Sie ein Befehlszeilentool. Verwenden Sie den folgenden Befehl, um die Firebase-Tools auf Ihrem Computer zu installieren:

 npm install -g firebase-tools

Verwenden Sie anschließend den Befehl firebase init, um die Firebase-Funktionen in Ihrer lokalen Umgebung zu konfigurieren. Wählen Sie die folgenden Optionen aus, wenn Sie die Firebase-Funktion in der lokalen Umgebung initialisieren:

  1. Welche Firebase CLI-Funktionen möchten Sie für diesen Ordner einrichten? Drücken Sie die Leertaste, um Funktionen auszuwählen, und drücken Sie die Eingabetaste, um Ihre Auswahl zu bestätigen => Funktionen: Konfigurieren und Bereitstellen von Cloud-Funktionen
  2. Lassen Sie uns zunächst dieses Projektverzeichnis einem Firebase-Projekt zuordnen. => Verwenden Sie ein vorhandenes Projekt
  3. Wählen Sie ein Standard-Firebase-Projekt für dieses Verzeichnis => Anwendungsname
  4. Welche Sprache möchten Sie zum Schreiben von Cloud-Funktionen verwenden? => JavaScript
  5. Möchten Sie ESLint verwenden, um mögliche Fehler zu erkennen und den Stil durchzusetzen? => N.
  6. Möchten Sie jetzt Abhängigkeiten mit npm installieren? (J / n) => Y.

Nach Abschluss der Konfiguration wird folgende Meldung angezeigt:

✔ Firebase initialization complete!

Dies ist unsere Verzeichnisstruktur, sobald die Initialisierung abgeschlossen ist:

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

Öffnen Sie nun das index.jsVerzeichnis unter Funktionen und kopieren Sie den folgenden Code:

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

Stellen Sie den Code mit dem folgenden Befehl für Firebase-Funktionen bereit:

firebase deploy

Nach Abschluss der Bereitstellung wird am Ende Ihrer Befehlszeile die folgende Protokollzeile angezeigt:

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

Gehen Sie zu Projektkonsole> Funktionen und dort finden Sie die URL der API. Die URL sieht folgendermaßen aus:

//-todoapp-.cloudfunctions.net/helloWorld

Kopieren Sie diese URL und fügen Sie sie in den Browser ein. Sie erhalten folgende Antwort:

Hello from Firebase!

Dies bestätigt, dass unsere Firebase-Funktion ordnungsgemäß konfiguriert wurde.

Installieren Sie das Express Framework:

Installieren wir nun das ExpressFramework in unserem Projekt mit dem folgenden Befehl:

npm i express

Erstellen wir nun ein API- Verzeichnis innerhalb des Funktionsverzeichnisses . In diesem Verzeichnis erstellen wir eine Datei mit dem Namen todos.js. Entfernen Sie alles aus dem index.jsund kopieren Sie den folgenden Code:

//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);

Wir haben die Funktion getAllTodos der Route / todos zugewiesen . Alle API-Aufrufe auf dieser Route werden also über die Funktion getAllTodos ausgeführt. Gehen Sie nun zu der todos.jsDatei im APIs-Verzeichnis und hier schreiben wir die Funktion 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); }

Hier haben wir ein Beispiel-JSON-Objekt deklariert. Später werden wir das aus dem Firestore ableiten. Aber vorerst werden wir dies zurückgeben. Stellen Sie dies nun mit dem Befehl für Ihre Firebase-Funktion bereit firebase deploy. Es wird fragenFür die Erlaubnis zum Löschen des Moduls helloworld geben Sie einfach y ein .

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

Sobald dies erledigt ist, gehen Sie zu Projektkonsole> Funktionen und dort finden Sie die URL der API. Die API sieht folgendermaßen aus:

//-todoapp-.cloudfunctions.net/api

Gehen Sie nun zum Browser und kopieren Sie die URL und fügen Sie / todos am Ende dieser URL hinzu. Sie erhalten folgende Ausgabe:

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

Firebase Firestore:

Wir werden einen Firebase-Firestore als Echtzeitdatenbank für unsere Anwendung verwenden. Gehen Sie nun in der Firebase-Konsole zu Konsole> Datenbank . Um den Firestore zu konfigurieren, folgen Sie dem folgenden GIF:

Sobald die Konfiguration abgeschlossen ist, klicken Sie auf die Schaltfläche Sammlung starten und legen Sie die Sammlungs-ID als Aufgaben fest . Klicken Sie auf Weiter und Sie erhalten das folgende Popup:

Ignorieren Sie den DocumentID-Schlüssel. Informationen zu Feld, Typ und Wert finden Sie im JSON unten. Aktualisieren Sie den Wert entsprechend:

{ 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 }

Drücken Sie die Speichertaste. Sie werden sehen, dass die Sammlung und das Dokument erstellt werden. Kehren Sie zur lokalen Umgebung zurück. Wir müssen installieren, firebase-adminwelches das Firestore-Paket enthält, das wir benötigen. Verwenden Sie diesen Befehl, um es zu installieren:

npm i firebase-admin

Erstellen Sie ein Verzeichnis mit dem Namen util unter dem Funktionsverzeichnis .Gehen Sie in dieses Verzeichnis und erstellen Sie einen Dateinamen admin.js. In dieser Datei importieren wir das Firebase-Admin-Paket und initialisieren das Firestore-Datenbankobjekt. Wir werden dies exportieren, damit andere Module es verwenden können.

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

Schreiben wir nun eine API, um diese Daten abzurufen. Wechseln Sie in das Verzeichnis todos.jsunter Funktionen> APIs . Entfernen Sie den alten Code und kopieren Sie den folgenden Code:

//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}); }); };

Hier holen wir alle Aufgaben aus der Datenbank und leiten sie in einer Liste an den Client weiter.

Sie können die Anwendung auch lokal mit dem firebase serveBefehl ausführen, anstatt sie jedes Mal bereitzustellen. Wenn Sie diesen Befehl ausführen, wird möglicherweise eine Fehlermeldung bezüglich der Anmeldeinformationen angezeigt. Führen Sie die folgenden Schritte aus, um das Problem zu beheben:

  1. Gehen Sie zu den Projekteinstellungen (Einstellungssymbol oben links)
  2. Wechseln Sie zur Registerkarte Dienstkonten  
  3. Unten besteht die Möglichkeit, einen neuen Schlüssel zu generieren . Klicken Sie auf diese Option, um eine Datei mit der JSON-Erweiterung herunterzuladen.
  4. Wir müssen diese Anmeldeinformationen in unsere Befehlszeilensitzung exportieren. Verwenden Sie dazu den folgenden Befehl:
export GOOGLE_APPLICATION_CREDENTIALS="/home/user/Downloads/[FILE_NAME].json"

Führen Sie danach den Befehl firebase serve aus. Wenn der Fehler weiterhin auftritt, verwenden Sie den folgenden Befehl : firebase login --reauth. Die Google-Anmeldeseite wird in einem Browser geöffnet. Sobald die Anmeldung abgeschlossen ist, funktioniert es ohne Fehler.

Sie finden eine URL in den Protokollen Ihres Befehlszeilentools, wenn Sie einen Firebase-Serve-Befehl ausführen. Öffnen Sie diese URL im Browser und hängen Sie /todossie an.

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

Sie erhalten die folgende JSON-Ausgabe in Ihrem Browser:

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

Andere APIs schreiben:

Es ist Zeit, alle anderen Aufgaben-APIs zu schreiben, die wir für unsere Anwendung benötigen.

  1. Todo-Element erstellen: Gehen Sie zum index.jsunter dem Funktionsverzeichnis. Importieren Sie die postOneTodo-Methode unter den vorhandenen getAllTodos. Weisen Sie dieser Methode auch die POST-Route zu.
//index.js const { .., postOneTodo } = require('./APIs/todos') app.post('/todo', postOneTodo);

Gehen Sie in das todos.jsFunktionsverzeichnis und fügen Sie postOneTodounter der vorhandenen getAllTodosMethode eine neue Methode hinzu.

//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); }); };

Bei dieser Methode fügen wir unserer Datenbank einen neuen Todo hinzu. Wenn die Elemente unseres Körpers leer sind, geben wir eine Antwort von 400 zurück oder fügen die Daten hinzu.

Führen Sie den Befehl firebase serve aus und öffnen Sie die Postman-Anwendung. Erstellen Sie eine neue Anforderung und wählen Sie den Methodentyp als POST aus . Fügen Sie die URL und einen Text vom Typ JSON hinzu.

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

Drücken Sie die Senden-Taste und Sie erhalten die folgende Antwort:

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

2. Todo-Element löschen: Gehen Sie zu index.jsunter das Funktionsverzeichnis. Importieren Sie die deleteTodo-Methode unter dem vorhandenen postOneTodo. Weisen Sie dieser Methode auch die DELETE-Route zu.

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

Gehen Sie zu todos.jsund fügen Sie eine neue Methode deleteTodounter der vorhandenen postOneTodoMethode hinzu.

//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 }); }); };

Bei dieser Methode löschen wir einen Todo aus unserer Datenbank. Führen Sie den Befehl Firebase Serve aus und gehen Sie zum Postboten. Erstellen Sie eine neue Anforderung, wählen Sie den Methodentyp als LÖSCHEN aus und fügen Sie die URL hinzu.

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

Drücken Sie die Senden-Taste und Sie erhalten die folgende Antwort:

{ "message": "Delete successfull" }

3. Todo-Element bearbeiten: Gehen Sie zu index.jsunter das Funktionsverzeichnis. Importieren Sie die editTodo-Methode unter dem vorhandenen deleteTodo. Weisen Sie dieser Methode auch die PUT-Route zu.

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

Gehen Sie zu todos.jsund fügen Sie eine neue Methode editTodounter der vorhandenen deleteTodoMethode hinzu.

//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 }); }); };

Bei dieser Methode bearbeiten wir einen Todo aus unserer Datenbank. Denken Sie daran, dass wir dem Benutzer hier nicht erlauben, die Felder todoId oder createdAt zu bearbeiten. Führen Sie den Befehl Firebase Serve aus und gehen Sie zum Postboten. Erstellen Sie eine neue Anforderung, wählen Sie den Methodentyp als PUT aus und fügen Sie die URL hinzu.

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

Drücken Sie die Senden-Taste und Sie erhalten die folgende Antwort:

{ "message": "Updated successfully" }

Verzeichnisstruktur bis jetzt:

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

Damit haben wir den ersten Abschnitt des Antrags abgeschlossen. Sie können Kaffee trinken, eine Pause einlegen und danach werden wir an der Entwicklung der Benutzer-APIs arbeiten.

Abschnitt 2: Entwickeln von Benutzer-APIs

In diesemAbschnitt werden wir diese Komponenten entwickeln:

  1. API für die Benutzerauthentifizierung (Anmeldung und Anmeldung).
  2. API für Benutzerdetails abrufen und aktualisieren.
  3. Aktualisieren Sie die Benutzerprofilbild-API.
  4. Sichern der vorhandenen Todo-API.

Der in diesem Abschnitt implementierte Benutzer-API-Code befindet sich bei diesem Commit.

Beginnen wir also mit der Erstellung der Benutzerauthentifizierungs-API. Gehen Sie zur Firebase-Konsole> Authentifizierung.

Klicken Sie auf das Set up Sign-in-Methode Taste. Wir werden E-Mail und Passwort für die Benutzerüberprüfung verwenden. Aktivieren Sie die Option E-Mail / Passwort .

Im Moment werden wir unseren Benutzer manuell erstellen. Zuerst erstellen wir die Login-API. Danach erstellen wir die Anmelde-API.

Gehen Sie unter Authentifizierung zur Registerkarte Benutzer, geben Sie die Benutzerdetails ein und klicken Sie auf die Schaltfläche Benutzer hinzufügen.

1. Benutzeranmelde-API:

Zuerst müssen wir das firebasePaket, das aus der Firebase-Authentifizierungsbibliothek besteht, mit dem folgenden Befehl installieren :

npm i firebase

Wechseln Sie nach Abschluss der Installation in das Verzeichnis Funktionen> APIs . Hier erstellen wir eine users.jsDatei. Jetzt index.jsimportieren wir Inside eine loginUser-Methode und weisen ihr die POST-Route zu.

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

Gehen Sie zu Projekteinstellungen> Allgemein und dort finden Sie die folgende Karte:

Wählen Sie das Web-Symbol und folgen Sie dem GIF unten:

Wählen Sie die Option Weiter zur Konsole . Sobald dies erledigt ist, sehen Sie einen JSON mit Firebase-Konfiguration. Gehen Sie zum Verzeichnis functions> util und erstellen Sie eine   config.jsDatei. Kopieren Sie den folgenden Code in diese Datei und fügen Sie ihn ein:

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

Ersetzen Sie diese ............durch die Werte, die Sie unter Firebase-Konsole> Projekteinstellungen> Allgemein> Ihre Apps> Firebase SD-Snippet> Konfiguration erhalten .

Kopieren Sie den folgenden Code und fügen Sie ihn in die users.jsDatei ein:

// 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'}); }) };

Hier verwenden wir ein Firebase- Modul signInWithEmailAndPassword , um zu überprüfen, ob die vom Benutzer übermittelten Anmeldeinformationen richtig sind. Wenn sie richtig sind, senden wir das Token dieses Benutzers oder einen 403-Status mit der Meldung "Falsche Anmeldeinformationen".

Jetzt erstellen wir validators.jsunter dem Verzeichnis functions> util . Kopieren Sie den folgenden Code in diese Datei und fügen Sie ihn ein:

// 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 }; };

Damit ist unsere LoginAPI abgeschlossen. Führen Sie den firebase serveBefehl aus und gehen Sie zum Postboten. Erstellen Sie eine neue Anforderung, wählen Sie den Methodentyp als POST aus und fügen Sie die URL und den Text hinzu.

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" }

Klicken Sie im Postboten auf die Schaltfläche "Anfrage senden" und Sie erhalten die folgende Ausgabe:

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

Wir werden dieses Token in einem kommenden Teil verwenden, um die Benutzerdetails zu erhalten . Denken Sie daran, dass dieser Token in 60 Minuten abläuft . Verwenden Sie diese API erneut, um ein neues Token zu generieren.

2. Benutzeranmelde-API:

Mit dem Standardauthentifizierungsmechanismus von firebase können Sie nur Informationen wie E-Mail, Kennwort usw. speichern. Wir benötigen jedoch weitere Informationen, um festzustellen, ob dieser Benutzer diese Aufgabe besitzt, damit er Lese-, Aktualisierungs- und Löschvorgänge ausführen kann.

Um dieses Ziel zu erreichen, werden wir eine neue Sammlung namens Benutzer erstellen . In dieser Sammlung speichern wir die Daten des Benutzers, die basierend auf dem Benutzernamen der Aufgabe zugeordnet werden. Jeder Benutzername ist für alle Benutzer auf der Plattform eindeutig.

Gehe zum index.js. Wir importieren eine signUpUser-Methode und weisen ihr die POST-Route zu.

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

Gehen Sie nun zu validators.jsund fügen Sie den folgenden Code unter der validateLoginDataMethode hinzu.

// 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 }; };

Gehen Sie nun zu users.jsund fügen Sie den folgenden Code unter dem loginUserModul hinzu.

// 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' }); } }); }

Wir validieren unsere Benutzerdaten und senden anschließend eine E-Mail und ein Kennwort an das Firebase- Modul createUserWithEmailAndPassword , um den Benutzer zu erstellen. Sobald der Benutzer erfolgreich erstellt wurde, speichern wir die Benutzeranmeldeinformationen in der Datenbank.

Damit ist unsere SignUp-API abgeschlossen. Führen Sie den firebase serveBefehl aus und gehen Sie zum Postboten. Erstellen Sie eine neue Anforderung und wählen Sie den Methodentyp als POST aus . Fügen Sie die URL und den Text hinzu.

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" }

Klicken Sie im Postboten auf die Schaltfläche "Anfrage senden" und Sie erhalten die folgende Ausgabe:

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

Gehen Sie nun zur Firebase-Konsole> Datenbank und dort sehen Sie die folgende Ausgabe:

Wie Sie sehen, wurde die Sammlung unseres Benutzers erfolgreich mit einem Dokument erstellt.

3. Benutzerprofil hochladen Bild:

Unsere Benutzer können ihr Profilbild hochladen. Um dies zu erreichen, werden wir einen Lagereimer verwenden. Gehen Sie auf die Firebase Konsole> Speicher und klicken Sie auf die loszulegen Taste. Folgen Sie dem GIF unten für die Konfiguration:

Gehen Sie nun zur Registerkarte Regeln unter Speicher und aktualisieren Sie die Berechtigung für den Bucket-Zugriff gemäß dem folgenden Bild:

Zum Hochladen des Profilbildes verwenden wir das genannte Paket busboy. Verwenden Sie den folgenden Befehl, um dieses Paket zu installieren:

npm i busboy

Gehe zu index.js. Importieren Sie die uploadProfilePhoto-Methode unterhalb der vorhandenen signUpUser-Methode. Weisen Sie dieser Methode auch die POST-Route zu.

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

Hier haben wir eine Authentifizierungsebene hinzugefügt, damit nur ein Benutzer, der diesem Konto zugeordnet ist, das Bild hochladen kann. Erstellen Sie nun eine Datei mit dem Namen auth.jsin Funktionen> utils - Verzeichnis. Kopieren Sie den folgenden Code in diese Datei und fügen Sie ihn ein:

// 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); }); };

Hier verwenden wir das Firebase- Modul verifyIdToken , um das Token zu überprüfen. Danach dekodieren wir die Benutzerdetails und übergeben sie an die vorhandene Anfrage.

Gehen Sie zu users.jsund fügen Sie den folgenden Code unter der signupMethode hinzu:

// 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); };

Damit ist unsere Upload Profile Picture API abgeschlossen. Führen Sie den firebase serveBefehl aus und gehen Sie zum Postboten. Erstellen Sie eine neue Anforderung, wählen Sie den Methodentyp als POST aus , fügen Sie die URL hinzu und wählen Sie im Hauptteil den Typ als Formulardaten aus.

Die Anfrage ist geschützt, daher müssen Sie auch den Inhaber-Token senden . Um das Inhaber-Token zu senden, melden Sie sich erneut an, wenn das Token abgelaufen ist. Danach fügen Sie in Postman App> Registerkarte Autorisierung> Typ> Inhaber-Token und im Token-Abschnitt das Token ein.

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

Klicken Sie im Postboten auf die Schaltfläche "Anfrage senden" und Sie erhalten die folgende Ausgabe:

{ "message": "Image uploaded successfully" }

4. Benutzerdetails abrufen:

Hier holen wir die Daten unseres Benutzers aus der Datenbank. Gehen Sie zu index.jsund importieren Sie die Methode getUserDetail und weisen Sie ihr die Route GET zu.

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

Gehen Sie nun zu users.jsund fügen Sie nach dem uploadProfilePhotoModul den folgenden Code hinzu :

// 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 }); }); }

Wir verwenden das Modul firebase doc (). Get () , um die Benutzerdetails abzuleiten. Damit ist unsere GET User Details API abgeschlossen. Führen Sie den firebase serveBefehl aus und gehen Sie zum Postboten. Erstellen Sie eine neue Anforderung, wählen Sie den Methodentyp: GET aus und fügen Sie die URL und den Text hinzu.

Die Anfrage ist geschützt, daher müssen Sie auch den Inhaber-Token senden . Um das Inhaber-Token zu senden, melden Sie sich erneut an, wenn das Token abgelaufen ist.

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

Klicken Sie im Postboten auf die Schaltfläche "Anfrage senden" und Sie erhalten die folgende Ausgabe:

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

5. Benutzerdetails aktualisieren:

Fügen wir nun die Funktionalität zum Aktualisieren der Benutzerdetails hinzu. Gehen Sie zu index.jsund kopieren Sie den folgenden Code:

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

Gehen Sie nun zu users.jsund fügen Sie das updateUserDetailsModul unter dem vorhandenen hinzu 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" }); }); }

Hier verwenden wir die Firebase- Aktualisierungsmethode . Damit ist unsere Update User Details API abgeschlossen. Befolgen Sie für eine Anforderung das gleiche Verfahren wie mit der obigen API zum Abrufen von Benutzerdetails mit einer Änderung. Fügen Sie hier einen Text in die Anforderung ein und verwenden Sie die Methode als 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 }

Klicken Sie im Postboten auf die Schaltfläche "Anfrage senden" und Sie erhalten die folgende Ausgabe:

{ "message": "Updated successfully" }

6. Sichern von Todo-APIs:

Um die Todo-API so zu sichern, dass nur der ausgewählte Benutzer darauf zugreifen kann, nehmen wir einige Änderungen an unserem vorhandenen Code vor. Zunächst werden wir unsere index.jswie folgt aktualisieren :

// 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);

Wir haben alle Todo-Routen durch Hinzufügen aktualisiert , authsodass für alle API-Aufrufe ein Token erforderlich ist und nur der jeweilige Benutzer darauf zugreifen kann.

Gehen Sie danach in das Verzeichnis todos.jsunter Funktionen> APIs .

  1. Todo-API erstellen: Öffnen Sie die todos.jsund fügen Sie unter der postOneTodo- Methode den Benutzernamen wie folgt hinzu:
const newTodoItem = { .., username: request.user.username, .. }

2. GET All Todos API: Öffnen Sie die todos.jsund fügen Sie unter der Methode getAllTodos die where-Klausel wie folgt hinzu:

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

Führen Sie den Firebase-Serve aus und testen Sie unsere GET-API. Vergessen Sie nicht, den Inhaber-Token zu senden. Hier erhalten Sie einen Antwortfehler wie folgt:

{ "error": 9 }

Gehen Sie zur Befehlszeile und Sie werden die folgenden protokollierten Zeilen sehen:

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

Öffne das Klicken Sie im Browser auf Index erstellen.

Sobald der Index erstellt ist, senden Sie die Anfrage erneut und Sie erhalten die folgende Ausgabe:

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

3.   Todo-API löschen : Öffnen Sie die todos.jsund fügen Sie unter der Methode deleteTodo die folgende Bedingung hinzu. Fügen Sie diese Bedingung in die Abfrage document.get (). Then () unter der Bedingung ! Doc.exists ein.

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

Bisherige Verzeichnisstruktur:

+-- 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

Damit haben wir unser API-Backend fertiggestellt. Machen Sie eine Pause, trinken Sie einen Kaffee und danach beginnen wir mit dem Aufbau des Frontends unserer Anwendung

Abschnitt 3: Benutzer-Dashboard

In diesemAbschnitt werden wir diese Komponenten entwickeln:

  1. Konfigurieren Sie ReactJS und Material UI.
  2. Anmelde- und Anmeldeformular erstellen.
  3. Abschnitt "Gebäudekonto".

Der in diesem Abschnitt implementierte Benutzer-Dashboard-Code befindet sich bei diesem Commit.

1. Konfigurieren Sie ReactJS und Material UI:

Wir werden die Create-React-App-Vorlage verwenden. Es gibt uns eine grundlegende Struktur für die Entwicklung der Anwendung. Verwenden Sie zum Installieren den folgenden Befehl:

npm install -g create-react-app

Wechseln Sie in den Stammordner des Projekts, in dem sich das Funktionsverzeichnis befindet. Initialisieren Sie unsere Front-End-Anwendung mit dem folgenden Befehl:

create-react-app view

Denken Sie daran, die Version v16.13.1 von zu verwendendie ReactJS-Bibliothek .

Nach Abschluss der Installation wird in Ihren Befehlszeilenprotokollen Folgendes angezeigt:

cd view npm start Happy hacking!

Damit haben wir unsere React-Anwendung konfiguriert. Sie erhalten folgende Verzeichnisstruktur:

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

Führen Sie nun die Anwendung mit dem Befehl aus npm start. Gehen Sie zum Browser //localhost:3000/und Sie sehen die folgende Ausgabe:

Jetzt werden wir alle unnötigen Komponenten entfernen. Wechseln Sie in das Ansichtsverzeichnis und entfernen Sie alle Dateiendie haben [Entfernen] vor sich. Informationen hierzu finden Sie in der folgenden Verzeichnisbaumstruktur.

+-- 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

Gehen Sie zu index.htmlunter dem öffentlichen Verzeichnis und entfernen Sie die folgenden Zeilen:

Gehen Sie nun in das App.jsVerzeichnis unter src und ersetzen Sie den alten Code durch den folgenden Code:

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

Gehen Sie zu index.jsund entfernen Sie den folgenden Import:

import './index.css'

Ich habe das nicht gelöscht App.cssund verwende es auch nicht in dieser Anwendung. Wenn Sie es jedoch löschen oder verwenden möchten, können Sie dies tun.

Gehen Sie zum Browser //localhost:3000/und Sie erhalten eine leere Bildschirmausgabe.

Um die Material-Benutzeroberfläche zu installieren, wechseln Sie in das Ansichtsverzeichnis und kopieren Sie diesen Befehl in das Terminal:

npm install @material-ui/core

Denken Sie daran, Version 4.9.8 der Material UI-Bibliothek zu verwenden.

2. Anmeldeformular:

Um das Anmeldeformular zu entwickeln, gehen Sie zu App.js. Fügen Sie oben App.jsdie folgenden Importe hinzu:

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

Wir verwenden Switch und Route , um Routen für unsere TodoApp zuzuweisen. Im Moment werden wir nur die / login- Route hinzufügen und ihr eine Login-Komponente zuweisen.

// App.js 

Erstellen Sie ein Seitenverzeichnis unter dem vorhandenen Ansichtsverzeichnis und eine Datei mit dem Namen login.jsunter dem Seitenverzeichnis .

Wir importieren Material UI-Komponenten und das Axios-Paket in 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';

Wir werden unserer Anmeldeseite die folgenden Stile hinzufügen:

// 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' } });

Wir werden eine Klasse namens login erstellen, die ein Formular und einen Submit-Handler enthält.

// 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}  )} ); } }

Fügen Sie am Ende dieser Datei den folgenden Export hinzu:

export default withStyles(styles)(login); 

Fügen Sie unsere URL für Firebase-Funktionen wie folgt hinzu , um> package.json anzuzeigen :

Denken Sie daran: Fügen Sie unter dem vorhandenen JSON-Objekt der Browserliste einen Schlüssel mit dem Namen Proxy hinzu
"proxy": "//-todoapp-.cloudfunctions.net/api"

Installieren Sie das Axios- und Materialsymbolpaket mit den folgenden Befehlen:

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

Wir haben eine Login-Route hinzugefügt App.js. In der haben login.jswir eine Klassenkomponente erstellt, die den Status behandelt und die Post-Anfrage mit dem Axios-Paket an die Login-API sendet. Wenn die Anfrage erfolgreich ist, speichern wir das Token. Wenn wir Fehler in der Antwort erhalten, rendern wir sie einfach auf der Benutzeroberfläche.

Gehen Sie zum Browser unter //localhost:3000/loginund Sie sehen die folgende Login-Benutzeroberfläche.

Versuchen Sie, falsche Anmeldeinformationen einzugeben oder eine leere Anfrage zu senden, und Sie erhalten die Fehler. Senden Sie eine gültige Anfrage. Gehen Sie zur Entwicklerkonsole> Anwendung . Sie werden sehen, dass das Benutzertoken im lokalen Speicher gespeichert ist. Sobald die Anmeldung erfolgreich ist, werden wir zurück zur Startseite geleitet.

3. Anmeldeformular:

Um das Anmeldeformular zu entwickeln, gehen Sie zu App.jsund aktualisieren Sie die vorhandene RouteKomponente mit der folgenden Zeile:

// App.js 

Vergessen Sie nicht zu importieren:

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

Erstellen Sie eine Datei mit dem Namen signup.jsim Seitenverzeichnis .

In der Datei signup.js importieren wir das Paket Material UI und 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';

Wir werden unserer Anmeldeseite die folgenden Stile hinzufügen:

// 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' } }); 

Wir werden eine Klasse namens signup erstellen, die ein Formular und einen Submit-Handler enthält.

// 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 ); } }

Fügen Sie am Ende dieser Datei den folgenden Export hinzu:

export default withStyles(styles)(signup); 

Die Logik für die Anmeldekomponente ist dieselbe wie für die Anmeldekomponente. Gehen Sie zum Browser unter //localhost:3000/signupund Sie sehen die folgende Anmelde-Benutzeroberfläche. Sobald die Anmeldung erfolgreich ist, werden wir zurück zur Startseite geleitet.

Versuchen Sie, falsche Anmeldeinformationen einzugeben oder eine leere Anfrage zu senden, und Sie erhalten die Fehler. Senden Sie eine gültige Anfrage. Gehen Sie zur Entwicklerkonsole> Anwendung . Sie werden sehen, dass das Benutzertoken im lokalen Speicher gespeichert ist.

4. Kontobereich:

Um das Konto Seite bauen wir müssen zuerst unsere erstellen Startseite von wo aus wir die Last Konto Abschnitt . Gehen Sie zu App.jsund aktualisieren Sie die folgende Route:

// App.js 

Vergessen Sie nicht den Import:

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

Erstellen Sie eine neue Datei mit dem Namen home.js. Diese Datei ist der Index unserer Anwendung. Die Abschnitte "Konto" und "Todo" werden auf dieser Seite basierend auf dem Klick auf die Schaltfläche geladen.

Importieren Sie die Material UI-Pakete, das Axios-Paket, unser benutzerdefiniertes Konto, die Aufgabenkomponenten und die Authentifizierungs-Middleware.

// 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'

Wir werden unsere Schubladenbreite wie folgt einstellen:

const drawerWidth = 240;

Wir werden unserer Homepage den folgenden Stil hinzufügen:

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 });

Wir werden eine Klasse namens home erstellen. Diese Klasse hat einen API-Aufruf, um das Profilbild, den Vor- und Nachnamen des Benutzers abzurufen. Außerdem kann logisch ausgewählt werden, welche Komponente angezeigt werden soll, entweder Todo oder 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 ? : } ); } } }

Hier im Code sehen Sie, dass authMiddleWare(this.props.history);verwendet wird. Diese Middleware prüft, ob das authToken null ist. Wenn ja, wird der Benutzer zurück zum login.js. Dies wird hinzugefügt, damit unser Benutzer /ohne Anmeldung oder Anmeldung nicht auf die Route zugreifen kann. Fügen Sie am Ende dieser Datei den folgenden Export hinzu:

export default withStyles(styles)(home); 

Fragen Sie sich jetzt, was dieser Code home.jsbewirkt?

 {this.state.render ?  : } 

Es überprüft den Renderstatus, den wir beim Klicken auf die Schaltfläche einstellen. Lassen Sie uns das Komponentenverzeichnis erstellen und unter diesem Verzeichnis zwei Dateien erstellen: account.jsund todo.js.

Erstellen wir ein Verzeichnis mit dem Namen util und eine Datei mit dem Namen auth.jsunter diesem Verzeichnis. Kopieren Sie den folgenden Code unter auth.js:

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

Vorerst in der todo.jsDatei schreiben wir nur eine Klasse, die den Text rendert Hallo ich bin todo . Wir werden im nächsten Abschnitt an unseren Aufgaben arbeiten:

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));

Jetzt ist es Zeit für den Account-Bereich. Importieren Sie das Dienstprogramm Material UI, clsx, axios und authmiddleWare in unser 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';

Wir werden unserer Kontoseite das folgende Styling hinzufügen:

// 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' } });

Wir werden eine Klassenkomponente namens account erstellen. Zur Zeit kopieren und fügen Sie einfach den folgenden Code ein:

// 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}  

Fügen Sie am Ende dieser Datei den folgenden Export hinzu:

export default withStyles(styles)(account); 

In account.jswerden viele Komponenten verwendet. Lassen Sie uns zuerst sehen, wie unsere Anwendung aussieht. Danach erkläre ich alle verwendeten Komponenten und warum sie verwendet werden.

Gehen Sie zum Browser und wenn Ihr Token abgelaufen ist, werden Sie zur   loginSeite weitergeleitet. Fügen Sie Ihre Daten hinzu und melden Sie sich erneut an. Sobald Sie dies getan haben, gehen Sie zur Registerkarte Konto und Sie finden die folgende Benutzeroberfläche:

Es gibt 3 Handler im Account-Bereich:

  1. componentWillMount : Dies ist die in React integrierte Lebenszyklusmethode. Wir verwenden es, um die Daten vor dem Render-Lebenszyklus zu laden und unsere Statuswerte zu aktualisieren.
  2. ProfilePictureUpdate: Dies ist unser benutzerdefinierter Handler, den wir verwenden. Wenn unser Benutzer auf die Schaltfläche Foto hochladen klickt, sendet er die Daten an einen Server und lädt die Seite neu, um das neue Profilbild des Benutzers anzuzeigen.
  3. updateFormValues: Dies ist auch unser benutzerdefinierter Handler zum Aktualisieren der Benutzerdetails. Hier kann der Benutzer seinen Vor- und Nachnamen sowie sein Land aktualisieren. Wir erlauben keine E-Mail- und Benutzernamen-Updates, da unsere Backend-Logik von diesen Schlüsseln abhängt.

Abgesehen von diesen 3 Handlern handelt es sich um eine Formularseite mit darüber liegendem Styling. Hier ist die Verzeichnisstruktur bis zu diesem Punkt im Ansichtsordner:

+-- 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

Damit haben wir unser Account Dashboard fertiggestellt. Jetzt geh einen Kaffee trinken, mach eine Pause und im nächsten Abschnitt werden wir das Todo Dashboard erstellen.

Abschnitt 4: Todo Dashboard

In diesemIn diesem Abschnitt werden wir die Benutzeroberfläche für diese Funktionen des Todos-Dashboards entwickeln:

  1. Fügen Sie einen Todo hinzu:
  2. Holen Sie sich alle Aufgaben:
  3. Löschen Sie eine Aufgabe
  4. Bearbeiten Sie eine Aufgabe
  5. Holen Sie sich eine Aufgabe
  6. Thema anwenden

Der in diesem Abschnitt implementierte Todo Dashboard-Code befindet sich bei diesem Commit.

Gehen Sie zu todos.jsunter dem Komponentenverzeichnis . Fügen Sie den vorhandenen Importen die folgenden Importe hinzu:

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';

Wir müssen außerdem die folgenden CSS-Elemente zu den vorhandenen Stilkomponenten hinzufügen:

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] } });

Wir werden den Übergang für das Popup-Dialogfeld hinzufügen:

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

Entfernen Sie die vorhandene ToDo-Klasse und kopieren Sie die folgende Klasse:

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}       ); } } }

Fügen Sie am Ende dieser Datei den folgenden Export hinzu:

export default withStyles(styles)(todo); 

Zuerst werden wir verstehen, wie unsere Benutzeroberfläche funktioniert, und danach werden wir den Code verstehen. Gehen Sie zum Browser und Sie erhalten die folgende Benutzeroberfläche:

Klicken Sie unten rechts auf die Schaltfläche Hinzufügen, um den folgenden Bildschirm anzuzeigen:

Fügen Sie den Todo-Titel und die Details hinzu und klicken Sie auf die Schaltfläche "Senden". Sie erhalten den folgenden Bildschirm:

Klicken Sie anschließend auf die Schaltfläche "Ansicht", um alle Details des Todo anzuzeigen:

Klicken Sie auf die Schaltfläche Bearbeiten und Sie können die Aufgabe bearbeiten:

Klicken Sie auf die Schaltfläche Löschen und Sie können den Todo löschen. Nachdem wir nun wissen, wie Dashboard funktioniert, werden wir die darin verwendeten Komponenten verstehen.

1. Add Todo: Für die Implementierung des Add todo verwenden wir die Dialogue-Komponente der Material-Benutzeroberfläche. Diese Komponente implementiert eine Hook-Funktionalität. Wir verwenden die Klassen, um diese Funktionalität zu entfernen.

// 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 }); };

Ansonsten ändern wir auch die Platzierung der Schaltfläche Todo hinzufügen.

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

Jetzt werden wir das Listen-Tag durch ein Formular in diesem Dialog ersetzen. Es wird uns helfen, die neue Aufgabe hinzuzufügen.

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

DashandleSubmitbesteht aus Logik zum Lesen des buttonTypeZustands. Wenn der Status eine leere Zeichenfolge (“”)ist, wird er in der Add Todo-API veröffentlicht. Wenn der Status ein Editist, wird in diesem Szenario das Edit Todo aktualisiert.

2. Get Todos: Um die Aufgaben anzuzeigen, die wir verwenden Grid container, platzieren wir die darin Grid item. Darin verwenden wir eine CardKomponente, um die Daten anzuzeigen.

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

Wir verwenden die Karte, um das Aufgabenelement anzuzeigen, während die API es in einer Liste sendet. Wir werden den componentWillMount-Lebenszyklus verwenden, um den Status abzurufen und festzulegen, bevor das Rendern ausgeführt wird. Es gibt 3 Schaltflächen ( Anzeigen, Bearbeiten und Löschen ), sodass wir 3 Handler benötigen, um den Vorgang auszuführen, wenn auf die Schaltfläche geklickt wird. Wir werden diese Schaltflächen in ihren jeweiligen Unterabschnitten kennenlernen.

3. Todo bearbeiten : Für die Bearbeitungsaufgabe verwenden wir den Dialog-Popup-Code, der beim Hinzufügen von Aufgaben verwendet wird. Um zwischen den Schaltflächenklicks zu unterscheiden, verwenden wir einen buttonTypeStatus. Für Add Todo ist der   buttonTypeStatus, (“”)während für Edit todo es ist Edit.

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

In der handleSubmitMethode lesen wir den buttonTypeStatus und senden dann die Anfrage entsprechend.

4. Todo löschen : Wenn Sie auf diese Schaltfläche klicken, senden wir das ToDo-Objekt an unseren deleteTodoHandler und senden die Anforderung weiter an das Backend.

 this.deleteTodoHandler({ todo })}>Delete

5. Todo anzeigen: Wenn wir die Daten anzeigen, haben wir sie abgeschnitten, damit der Benutzer einen Eindruck davon bekommt, worum es bei der Aufgabe geht. Wenn ein Benutzer jedoch mehr darüber erfahren möchte, muss er auf die Schaltfläche "Anzeigen" klicken.

Hierzu verwenden wir den benutzerdefinierten Dialog. Darin verwenden wir DialogTitle und DialogContent. Es zeigt unseren Titel und Inhalt. In DialougeContent verwenden wir das Formular, um den Inhalt anzuzeigen, den der Benutzer gepostet hat. (Dies ist eine Lösung, von der ich festgestellt habe, dass es viele gibt, und Sie können andere ausprobieren.)

// 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. Thema anwenden: Dies ist der letzte Schritt unserer Anwendung. Wir werden ein Thema auf unsere Bewerbung anwenden. Dafür verwenden wir createMuiThemeund ThemeProvidervon der Material-Benutzeroberfläche. Kopieren Sie den folgenden Code und fügen Sie ihn ein 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.  ); }

Wir haben es verpasst, ein Thema auf unseren Button todo.jsin der CardActions. Fügen Sie das Farb-Tag für die Schaltfläche zum Anzeigen, Bearbeiten und Löschen hinzu.

Gehen Sie zum Browser und Sie werden feststellen, dass alles gleich ist, außer dass die App eine andere Farbe hat.

Und wir sind fertig! Wir haben eine TodoApp mit ReactJS und Firebase erstellt. Wenn Sie es bis zu diesem Punkt gebaut haben, dann herzlichen Glückwunsch an Sie zu diesem Erfolg.

Fühlen Sie sich frei, mit mir auf Twitter und Github zu verbinden.