Sådan opbygges en TodoApp ved hjælp af ReactJS og Firebase

Hej folk, velkommen til denne tutorial. Før vi begynder, skal du være bekendt med grundlæggende ReactJS-koncepter. Hvis du ikke er det, vil jeg anbefale, at du gennemgår ReactJS-dokumentationen.

Vi bruger følgende komponenter i denne applikation:

  1. ReactJS
  2. Materielt brugergrænseflade
  3. Firebase
  4. ExpressJS
  5. Postbud

Hvordan vores ansøgning vil se ud:

Applikationsarkitektur:

Forståelse af vores komponenter:

Du undrer dig måske over, hvorfor vi bruger firebase i denne applikation. Nå, det giver sikker godkendelse , en database i realtid , en serverløs komponent og en opbevaringsbøtte .

Vi bruger Express her, så vi ikke behøver at håndtere HTTP-undtagelser. Vi skal bruge alle firebase-pakkerne i vores funktionskomponent. Dette skyldes, at vi ikke ønsker at gøre vores klientapplikation for stor, hvilket har en tendens til at bremse indlæsningsprocessen af ​​brugergrænsefladen.

Bemærk: Jeg vil opdele denne vejledning i fire separate sektioner. I starten af ​​hvert afsnit finder du en git-forpligtelse, der har koden udviklet i dette afsnit. Også Hvis du vil se den komplette kode, er den tilgængelig i dette lager.

Afsnit 1: Udvikling af Todo API'er

Heriafsnit , vil vi udvikle disse elementer:

  1. Konfigurer firebase-funktionerne.
  2. Installer Express-rammen og opbyg Todo API'er.
  3. Konfiguration af firestore som database.

Den Todo API kode implementeret i dette afsnit kan findes på dette begå.

Konfigurer Firebase-funktioner:

Gå til Firebase-konsollen.

Vælg indstillingen Tilføj projekt . Derefter følg gif nedenunder trin for trin for at konfigurere firebase-projektet.

Gå til fanen funktioner, og klik på knappen Kom godt i gang :

Du vil se en dialogboks, der indeholder instruktioner om, hvordan du konfigurerer Firebase-funktionerne . Gå til dit lokale miljø. Åbn et kommandolinjeværktøj. Brug kommandoen nedenfor til at installere firebase-værktøjerne på din maskine:

 npm install -g firebase-tools

Når det er gjort, skal du bruge kommandoen firebase inittil at konfigurere firebase-funktionerne i dit lokale miljø. Vælg følgende muligheder, når firebase-funktionen initialiseres i det lokale miljø:

  1. Hvilke Firebase CLI-funktioner vil du konfigurere til denne mappe? Tryk på Mellemrum for at vælge funktioner, derefter Enter for at bekræfte dine valg => Funktioner: Konfigurer og implementer Cloud-funktioner
  2. Lad os først knytte dette projektkatalog til et Firebase-projekt…. => Brug et eksisterende projekt
  3. Vælg et standard Firebase-projekt til denne mappe => applikationsnavn
  4. Hvilket sprog vil du bruge til at skrive Cloud-funktioner? => JavaScript
  5. Vil du bruge ESLint til at fange sandsynlige fejl og håndhæve stil? => N
  6. Vil du installere afhængigheder med npm nu? (Y / n) => Y

Når konfigurationen er udført, får du følgende meddelelse:

✔ Firebase initialization complete!

Dette vil være vores katalogstruktur, når initialiseringen er afsluttet:

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

Åbn nu index.jsunder funktionsmappen, og kopier og indsæt følgende kode:

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

Distribuer koden til firebase-funktioner ved hjælp af følgende kommando:

firebase deploy

Når implementeringen er færdig, får du følgende logline i slutningen af ​​din kommandolinje:

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

Gå til projektkonsollen> Funktioner, og der finder du URL'en til API'en. URL'en ser sådan ud:

//-todoapp-.cloudfunctions.net/helloWorld

Kopier denne URL og indsæt den i browseren. Du får følgende svar:

Hello from Firebase!

Dette bekræfter, at vores Firebase-funktion er konfigureret korrekt.

Installer Express Framework:

Lad os nu installere Expressrammen i vores projekt ved hjælp af følgende kommando:

npm i express

Lad os nu oprette en API- mappe inde i funktionskataloget . Inde i denne mappe opretter vi en fil med navnet todos.js. Fjern alt fra index.jsog kopier og indsæt derefter følgende kode:

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

Vi har tildelt getAllTodos-funktionen til / todos- ruten. Så alle API-opkald på denne rute udføres via getAllTodos-funktionen. Gå nu til todos.jsfilen under API-biblioteket, og her skriver vi getAllTodos-funktionen.

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

Her har vi erklæret et eksempel på et JSON-objekt. Senere vil vi udlede det fra Firestore. Men for øjeblikket vil vi returnere dette. Brug dette nu til din firebase-funktion ved hjælp af kommandoen firebase deploy. Det vil spørgefor tilladelse til at slette modulet helloworld - indtast bare 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

Når dette er gjort, skal du gå til Projektkonsol> Funktioner, og der finder du URL'en til API'en. API'en vil se sådan ud:

//-todoapp-.cloudfunctions.net/api

Gå nu til browseren, og kopier og indsæt URL'en og tilføj / todos i slutningen af ​​denne URL. Du får følgende output:

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

Firebase Firestore:

Vi bruger en firebase-firestore som en realtidsdatabase til vores applikation. Gå nu til Konsol> Database i Firebase Console. Følg gif nedenfor for at konfigurere firestore:

Når konfigurationen er udført, skal du klikke på knappen Start samling og indstille samling-id som todos . Klik på Næste, og du får følgende popup:

Ignorer DocumentID-nøglen. For feltet, typen og værdien henvises til JSON nedenunder. Opdater værdien i overensstemmelse hermed:

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

Tryk på gem-knappen. Du vil se, at samlingen og dokumentet oprettes. Gå tilbage til det lokale miljø. Vi skal installere, firebase-adminder har den firestore-pakke, vi har brug for. Brug denne kommando til at installere den:

npm i firebase-admin

Opret en mappe med navnet util under funktionsmappen .Gå til denne mappe, og opret et filnavn admin.js. I denne fil importerer vi firebase admin-pakken og initialiserer firestore-databaseobjektet. Vi eksporterer dette, så andre moduler kan bruge det.

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

Lad os nu skrive en API for at hente disse data. Gå til todos.jsunder biblioteket funktioner> API'er . Fjern den gamle kode, og kopier og indsæt koden nedenfor:

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

Her henter vi alle todos fra databasen og videresender dem til klienten på en liste.

Du kan også køre applikationen lokalt ved hjælp af firebase servekommando i stedet for at implementere den hver gang. Når du kører denne kommando, kan du få en fejl vedrørende legitimationsoplysninger. For at løse det skal du følge nedenstående trin:

  1. Gå til projektindstillinger (ikonet Indstillinger øverst til venstre)
  2. Gå til fanen Servicekonti  
  3. Dernede vil der være mulighed for at generere en ny nøgle . Klik på denne mulighed, og den downloader en fil med en JSON-udvidelse.
  4. Vi skal eksportere disse legitimationsoplysninger til vores kommandolinjesession. Brug kommandoen nedenfor til at gøre det:
export GOOGLE_APPLICATION_CREDENTIALS="/home/user/Downloads/[FILE_NAME].json"

Derefter kører firebase serverkommando. Hvis du stadig får fejlen derefter bruge følgende kommando: firebase login --reauth. Det åbner Google-login-siden i en browser. Når login er udført, fungerer det uden nogen fejl.

Du finder en URL i logfilerne til dit kommandolinjeværktøj, når du kører en firebase-serverkommando. Åbn denne URL i browseren, og tilføj /todosefter den.

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

Du får følgende JSON-output i din browser:

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

Skrivning af andre API'er:

Det er tid til at skrive alle de andre todo-API'er, som vi skal bruge til vores ansøgning.

  1. Opret Todo-element: Gå til index.jsunder funktionsmappen. Importer postOneTodo-metoden under den eksisterende getAllTodos. Tildel også POST-ruten til denne metode.
//index.js const { .., postOneTodo } = require('./APIs/todos') app.post('/todo', postOneTodo);

todos.jsind i funktionskataloget og tilføj en ny metode postOneTodounder den eksisterende getAllTodosmetode.

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

I denne metode tilføjer vi en ny Todo til vores database. Hvis elementerne i vores krop er tomme, returnerer vi et svar på 400, ellers tilføjer vi dataene.

Kør firebase serve-kommandoen, og åbn postbudsprogrammet. Opret en ny anmodning, og vælg metodetypen som POST . Tilføj URL'en og en brødtekst af typen JSON.

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

Tryk på send-knappen, og du får følgende svar:

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

2. Slet Todo-element: Gå til index.jsunder funktionsmappen. Importér deleteTodo-metoden under den eksisterende postOneTodo. Tildel også SLET-ruten til denne metode.

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

Gå til todos.jsog tilføj en ny metode deleteTodounder den eksisterende postOneTodometode.

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

I denne metode sletter vi en Todo fra vores database. Kør kommandoen firebase serve og gå til postbudet. Opret en ny anmodning, vælg metodetypen som SLET og tilføj URL'en.

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

Tryk på send-knappen, og du får følgende svar:

{ "message": "Delete successfull" }

3. Rediger Todo-element: Gå til index.jsunder funktionsmappen. Importer editTodo-metoden under den eksisterende deleteTodo. Tildel også PUT-ruten til denne metode.

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

Gå til todos.jsog tilføj en ny metode editTodounder den eksisterende deleteTodometode.

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

I denne metode redigerer vi en Todo fra vores database. Husk her, vi tillader ikke brugeren at redigere todoId eller createdAt-felterne. Kør kommandoen firebase serve og gå til postbudet. Opret en ny anmodning, vælg metodetypen som PUT, og tilføj URL'en.

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

Tryk på send-knappen, og du får følgende svar:

{ "message": "Updated successfully" }

Directory-struktur indtil nu:

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

Med dette har vi afsluttet den første del af ansøgningen. Du kan fortsætte med at tage lidt kaffe, tage en pause, og derefter vil vi arbejde på at udvikle User API'erne.

Afsnit 2: Udvikling af bruger-API'er

Heriafsnit , vil vi udvikle disse komponenter:

  1. Brugergodkendelse (login og tilmelding) API.
  2. GET og opdater brugeroplysninger API.
  3. Opdater API'en til brugerprofilbilledet.
  4. Sikring af den eksisterende Todo API.

User API-koden, der er implementeret i dette afsnit, kan findes ved denne forpligtelse.

Så lad os begynde at opbygge User Authentication API. Gå til Firebase-konsol> Godkendelse.

Klik på knappen Opsæt login-metode . Vi bruger e-mail og adgangskode til brugervalidering. Aktivér indstillingen E-mail / adgangskode .

Lige nu opretter vi vores bruger manuelt. Først bygger vi Login API. Derefter bygger vi Sign-Up API.

Gå til fanen Brugere under Godkendelse, udfyld brugeroplysningerne, og klik på knappen Tilføj bruger .

1. Bruger login API:

Først skal vi installere firebasepakken, der består af Firebase Authentication-biblioteket, ved hjælp af følgende kommando:

npm i firebase

Når installationen er færdig, skal du gå til biblioteket funktioner> API'er . Her opretter vi en users.jsfil. Nu indeni index.jsimporterer vi en loginUser-metode og tildeler POST-ruten til den.

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

Gå til Projektindstillinger> Generelt, og der finder du følgende kort:

Vælg webikonet og følg derefter gif nedenunder:

Vælg indstillingen Fortsæt til konsol . Når dette er gjort, vil du se en JSON med firebase-konfiguration. Gå til funktionerne> util- biblioteket, og opret en   config.jsfil. Kopier og indsæt følgende kode i denne fil:

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

Erstat ............med de værdier, du får under Firebase-konsol> Projektindstillinger> Generelt> dine apps> Firebase SD-uddrag> konfiguration .

Kopier og indsæt følgende kode i users.jsfilen:

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

Her bruger vi et firebase-tegnInWithEmailAndPassword- modul til at kontrollere, om de brugerindleverede legitimationsoplysninger er rigtige. Hvis de har ret, sender vi tokens for den bruger ellers en 403-status med en "forkert legitimationsoplysninger" -meddelelse.

Lad os nu oprette validators.jsunder funktionerne> util- biblioteket. Kopier og indsæt følgende kode i denne fil:

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

Med dette er vores LoginAPI afsluttet. Kør firebase servekommandoen og gå til postbudmanden. Opret en ny anmodning, vælg metodetypen som POST , og tilføj URL og brødtekst.

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

Klik på knappen Send anmodning i postbud, og du får følgende output:

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

Vi bruger dette token i en kommende del for at få brugeroplysningerne . Husk, at dette token udløber om 60 minutter . For at generere et nyt token skal du bruge denne API igen.

2. Brugertilmeldings-API:

Standardgodkendelsesmekanismen for firebase giver dig kun mulighed for at gemme oplysninger som e-mail, adgangskode osv. Men vi har brug for flere oplysninger for at identificere, om denne bruger ejer denne todo, så de kan udføre læsning, opdatering og sletning af handlinger på den.

For at nå dette mål skal vi oprette en ny samling kaldet brugere . Under denne samling gemmer vi brugerens data, som vil blive kortlagt til todo baseret på brugernavnet. Hvert brugernavn vil være unikt for alle brugere på platformen.

Gå til index.js. Vi importerer en signUpUser-metode og tildeler POST-ruten til den.

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

Gå nu til validators.jsog tilføj følgende kode under validateLoginDatametoden.

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

Gå nu til users.jsog tilføj følgende kode under loginUsermodulet.

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

Vi validerer vores brugerdata, og derefter sender vi en e-mail og adgangskode til firebase createUserWithEmailAndPassword- modulet for at oprette brugeren. Når brugeren er oprettet med succes, gemmer vi brugeroplysningerne i databasen.

Med dette er vores SignUp API afsluttet. Kør firebase servekommandoen og gå til postbudmanden. Opret en ny anmodning, vælg metodetypen som POST . Tilføj URL og brødtekst.

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

Klik på knappen Send anmodning i postbud, og du får følgende output:

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

Gå nu til Firebase-konsollen> Database, og der vil du se følgende output:

Som du kan se, oprettes vores brugers samling med et dokument i den.

3. Upload brugerprofilbillede:

Vores brugere kan uploade deres profilbillede. For at opnå dette bruger vi Storage bucket. Gå til Firebase-konsol> Opbevaring, og klik på knappen Kom godt i gang . Følg GIF nedenfor for konfigurationen:

Gå nu til fanen Regler under Opbevaring, og opdater tilladelsen til skoveadgang i henhold til billedet nedenfor:

For at uploade profilbilledet bruger vi den navngivne pakke busboy. Brug følgende kommando for at installere denne pakke:

npm i busboy

Gå til index.js. Importer uploadProfilePhoto-metoden under den eksisterende signUpUser-metode. Tildel også POST-ruten til denne metode.

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

Her har vi tilføjet et godkendelseslag, så kun en bruger, der er tilknyttet den konto, kan uploade billedet. Opret nu en fil, der er navngivet auth.jsi biblioteket med funktioner> bibliotek. Kopier og indsæt følgende kode i den fil:

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

Her bruger vi Firebase- verificeringsdokumentmodulet til at verificere tokenet. Derefter afkoder vi brugeroplysningerne og sender dem i den eksisterende anmodning.

Gå til users.jsog tilføj følgende kode under signupmetoden:

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

Med dette er vores Upload Profile Picture API afsluttet. Kør firebase servekommandoen og gå til postbudmanden. Opret en ny anmodning, vælg metodetypen som POST , tilføj URL'en, og vælg type som formdata i brødtekstsektionen.

Anmodningen er beskyttet, så du bliver også nødt til at sende bærer-token . For at sende bærer-token skal du logge ind igen, hvis tokenet er udløbet. Derefter i Postman App> Autorisations-fanen> Type> Bearer-token, og indsæt tokenet i tokensektionen.

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

Klik på knappen Send anmodning i postbud, og du får følgende output:

{ "message": "Image uploaded successfully" }

4. Få brugeroplysninger:

Her henter vi data fra vores bruger fra databasen. Gå til index.jsog importer metoden getUserDetail og tildel GET-rute til den.

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

Gå nu til users.jsog tilføj følgende kode efter uploadProfilePhotomodulet:

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

Vi bruger firebase doc (). Get () -modulet til at udlede brugeroplysningerne. Med dette er vores GET User Details API afsluttet. Kør firebase servekommandoen og gå til postbudmanden. Opret en ny anmodning, vælg metodetypen: GET , og tilføj URL'en og brødteksten.

Anmodningen er beskyttet, så du bliver også nødt til at sende bærer-token . For at sende bærer-token skal du logge ind igen, hvis tokenet er udløbet.

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

Klik på knappen Send anmodning i postbud, og du får følgende output:

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

5. Opdater brugeroplysninger:

Lad os nu tilføje funktionaliteten til at opdatere brugeroplysningerne. Gå til index.jsog kopier og indsæt følgende kode:

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

Gå nu til users.jsog tilføj updateUserDetailsmodulet under det eksisterende 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" }); }); }

Her bruger vi Firebase- opdateringsmetoden . Med dette er vores opdatering af brugeroplysninger API afsluttet. Følg den samme procedure for en anmodning som med Get User Details API ovenfor med en ændring. Tilføj brødtekst i anmodningen her og metode som 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 }

Klik på knappen Send anmodning i postbud, og du får følgende output:

{ "message": "Updated successfully" }

6. Sikring af Todo API'er:

For at sikre Todo API, så kun den valgte bruger kan få adgang til den, foretager vi et par ændringer i vores eksisterende kode. For det første opdaterer vi vores index.jssom følger:

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

Vi har opdateret alle Todo-ruterne ved at tilføje, authså alle API-opkald kræver et token og kun kan tilgås af den bestemte bruger.

Derefter gå til todos.jsunder biblioteket funktioner> API'er .

  1. Opret Todo API: Åbn todos.jsog under metoden postOneTodo tilføj brugernavnet som følger:
const newTodoItem = { .., username: request.user.username, .. }

2. GET All Todos API: Åbn todos.jsog under getAllTodos- metoden tilføj hvor-klausulen som følger:

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

Kør firebase-serveren og test vores GET API. Glem ikke at sende bærertegnet. Her får du en svarfejl som følger:

{ "error": 9 }

Gå til kommandolinjen, og du vil se følgende linjer logget:

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

Åbn dette i browseren og klik på Opret indeks.

Når indekset er bygget, send anmodningen igen, og du får følgende output:

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

3.   Slet Todo API: Åbn todos.jsog under deleteTodo- metoden tilføj følgende betingelse. Tilføj denne betingelse inde i document.get (). Derefter () forespørgsel under ! Doc. Eksisterer betingelse.

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

Katalogstruktur indtil nu:

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

Med dette har vi afsluttet vores API-backend. Tag en pause, tag en kop kaffe, og derefter begynder vi at bygge frontenden af ​​vores ansøgning

Afsnit 3: Brugerdashboard

Heriafsnit , vil vi udvikle disse komponenter:

  1. Konfigurer ReactJS og Material UI.
  2. Bygge login- og tilmeldingsformular.
  3. Afdeling for bygningskonto.

Brugerdashboard-koden, der er implementeret i dette afsnit, kan findes ved denne forpligtelse.

1. Konfigurer ReactJS og Material UI:

Vi bruger skabelonen create-react-app. Det giver os en grundlæggende struktur til udvikling af applikationen. For at installere det skal du bruge følgende kommando:

npm install -g create-react-app

Gå til rodmappen på projektet, hvor funktionsmappen er til stede. Initialiser vores frontend-applikation ved hjælp af følgende kommando:

create-react-app view

Husk at bruge version v16.13.1 afReactJS-biblioteket .

Når installationen er afsluttet, ser du følgende i dine kommandolinjelogfiler:

cd view npm start Happy hacking!

Med dette har vi konfigureret vores React-applikation. Du får følgende bibliotekstruktur:

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

Kør nu applikationen ved hjælp af kommandoen npm start. Gå til browseren, //localhost:3000/og du vil se følgende output:

Nu fjerner vi alle de unødvendige komponenter. Gå til visningskataloget, og fjern derefter alle filernesom har [Fjern] foran sig. For dette, henvises til katalogtræstrukturen nedenfor.

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

Gå til index.htmlunder det offentlige bibliotek og fjern følgende linjer:

Gå nu til App.jsunder src-biblioteket, og udskift den gamle kode med følgende kode:

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

Gå til index.jsog fjern følgende import:

import './index.css'

Jeg har ikke slettet App.csseller bruger det i denne applikation. Men hvis du vil slette eller bruge det, kan du gøre det.

Gå til browseren, //localhost:3000/og du får en blank skærmoutput.

For at installere Material UI skal du gå til visningsmappen og kopiere og indsætte denne kommando i terminalen:

npm install @material-ui/core

Husk at bruge version v4.9.8 af Material UI-biblioteket.

2. Loginformular:

Gå til for at udvikle loginformularen App.js. Øverst på App.jstilføj følgende import:

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

Vi bruger Switch og Route til at tildele ruter til vores TodoApp. Lige nu vil vi kun tilføje / login- ruten og tildele en login-komponent til den.

// App.js 

Opret en sider mappe under den eksisterende visning mappe og en fil med navnet login.jsunder sider mappe.

Vi importerer Material UI-komponenter og Axios-pakken i 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';

Vi tilføjer følgende stilarter til vores login-side:

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

Vi opretter en klasse med navnet login, der har en formular og indsender handler inde i den.

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

I slutningen af ​​denne fil skal du tilføje følgende eksport:

export default withStyles(styles)(login); 

Tilføj vores firebase-funktioner URL for at se> package.json som følger:

Husk: Tilføj en nøgle med navnet proxy under det eksisterende JSON-objekt i browseren
"proxy": "//-todoapp-.cloudfunctions.net/api"

Installer Axios og materiale-ikonpakken ved hjælp af følgende kommandoer:

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

Vi har tilføjet en loginrute i App.js. I det har login.jsvi oprettet en klassekomponent, der håndterer tilstanden, sender postanmodningen til login-API'en ved hjælp af Axios-pakken. Hvis anmodningen er vellykket, gemmer vi tokenet. Hvis vi får fejl i svaret, gengiver vi dem simpelthen på brugergrænsefladen.

Gå til browseren på, //localhost:3000/loginog du vil se følgende login-brugergrænseflade.

Prøv at udfylde forkerte legitimationsoplysninger eller send en tom anmodning, så får du fejlene. Send en gyldig anmodning. Gå til udviklerkonsollen> Applikation . Du vil se, at brugerens token gemmes i det lokale lager. Når login er vellykket, dirigeres vi tilbage til startsiden.

3. Tilmeldingsformular:

For at udvikle tilmeldingsformularen skal du gå til App.jsog opdatere den eksisterende Routekomponent med nedenstående linje:

// App.js 

Glem ikke at importere:

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

Opret en fil med navnet signup.jsunder sidekataloget .

Inde i signup.js importerer vi pakken Material UI og 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';

Vi tilføjer følgende stilarter til vores tilmeldingsside:

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

Vi opretter en klasse med navnet tilmelding, der har en formular og indsender handler inde i den.

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

I slutningen af ​​denne fil skal du tilføje følgende eksport:

export default withStyles(styles)(signup); 

Logikken for tilmeldingskomponenten er den samme som loginkomponenten. Gå til browseren på, //localhost:3000/signupog du vil se følgende tilmeldingsgrænseflade. Når tilmeldingen er vellykket, dirigeres vi tilbage til startsiden.

Prøv at udfylde forkerte legitimationsoplysninger eller send en tom anmodning, så får du fejlene. Send en gyldig anmodning. Gå til udviklerkonsollen> Applikation . Du vil se, at brugerens token gemmes i det lokale lager.

4. Kontosektion:

For at oprette kontosiden skal vi først oprette vores startside, hvorfra vi indlæser kontosektionen . Gå til App.jsog opdater følgende rute:

// App.js 

Glem ikke importen:

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

Opret en ny fil med navnet home.js. Denne fil vil være indekset for vores ansøgning. Sektionerne Konto og Todo indlæses begge på denne side baseret på klik på knappen.

Importér Material UI-pakkerne, Axios-pakken, vores brugerdefinerede konto, todo-komponenter og auth-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'

Vi indstiller vores skuffebredde som følger:

const drawerWidth = 240;

Vi tilføjer følgende stil til vores startside:

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

Vi opretter en klasse med navnet hjem. Denne klasse vil have et API-opkald for at få brugerens profilbillede, fornavn og efternavn. Det vil også have logik at vælge, hvilken komponent der skal vises, enten Todo eller 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 ? : } ); } } }

Her i koden vil du se, at den authMiddleWare(this.props.history);bruges. Denne mellemware kontrollerer, om authToken er nul. Hvis ja, vil det skubbe brugeren tilbage til login.js. Dette tilføjes, så vores bruger ikke kan få adgang til /ruten uden tilmelding eller log ind. I slutningen af ​​denne fil tilføj følgende eksport:

export default withStyles(styles)(home); 

Nu spekulerer du på, hvad denne kode fra home.jsgør?

 {this.state.render ?  : } 

Det kontrollerer gengivelsestilstanden, som vi indstiller ved klik på knappen. Lad os oprette komponentmappen, og oprette to filer under denne mappe: account.jsog todo.js.

Lad os oprette en mappe med navnet util og en fil med navnet auth.jsunder den mappe. Kopier og indsæt følgende kode under auth.js:

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

For tiden er inde i todo.jsfil, vi skriver bare en klasse, der gengiver teksten Hej jeg skal gøre . Vi arbejder på vores todos i det næste afsnit:

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

Nu er det tid til kontosektionen. Importér Material UI, clsx, axios og authmiddleWare-værktøjet i vores 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';

Vi tilføjer følgende styling til vores kontoside:

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

Vi opretter en klassekomponent med navnet konto. Indtil videre kopier og indsæt følgende kode:

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

I slutningen af ​​denne fil skal du tilføje følgende eksport:

export default withStyles(styles)(account); 

I account.jsder er masser af komponenter, der anvendes. Lad os først se, hvordan vores ansøgning ser ud. Derefter forklarer jeg alle de komponenter, der bruges, og hvorfor de bruges.

Gå til browseren, og hvis dit token er udløbet, omdirigeres du dig til   loginsiden. Tilføj dine oplysninger, og log ind igen. Når du har gjort det, skal du gå til fanen Konto, så finder du følgende brugergrænseflade:

Der er 3 handlere i Kontosektionen:

  1. componentWillMount : Dette er React's indbyggede livscyklusmetode. Vi bruger det til at indlæse dataene før renderecyklussen og opdatere vores tilstandsværdier.
  2. ProfilePictureUpdate: Dette er vores brugerdefinerede handler, som vi bruger, så når vores bruger klikker på knappen Upload foto, sender den dataene til en server og indlæser siden igen for at vise brugerens nye profilbillede.
  3. updateFormValues: Dette er også vores brugerdefinerede handler til at opdatere brugerens detaljer. Her kan brugeren opdatere deres fornavn, efternavn og land. Vi tillader ikke opdateringer af e-mail og brugernavn, fordi vores backend-logik afhænger af disse nøgler.

Bortset fra disse 3 håndterere er det en formularside med styling oven på den. Her er katalogstrukturen indtil dette punkt inde i visningsmappen:

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

Med dette har vi gennemført vores Kontoudashboard. Gå nu og tag en kop kaffe, tag en pause, og i det næste afsnit bygger vi Todo Dashboard.

Afsnit 4: Todo Dashboard

Heriafsnit , vil vi udvikle brugergrænsefladen til disse funktioner i Todos Dashboard:

  1. Tilføj en Todo:
  2. Få alle todos:
  3. Slet en todo
  4. Rediger en todo
  5. Få en todo
  6. Anvendelse af tema

Todo Dashboard-koden, der er implementeret i dette afsnit, kan findes på denne forpligtelse.

Gå til todos.jsunder komponentmappen . Føj følgende import til den eksisterende import:

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

Vi skal også tilføje følgende CSS-elementer i de eksisterende stilkomponenter:

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

Vi tilføjer overgangen til pop op-dialogboksen:

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

Fjern den eksisterende todo-klasse, og kopier og indsæt følgende 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}       ); } } }

I slutningen af ​​denne fil skal du tilføje følgende eksport:

export default withStyles(styles)(todo); 

Først vil vi forstå, hvordan vores brugergrænseflade fungerer, og derefter vil vi forstå koden. Gå til browseren, og du får følgende brugergrænseflade:

Klik på knappen Tilføj i nederste højre hjørne, og du får følgende skærmbillede:

Tilføj Todo-titlen og detaljerne, og tryk på knappen Send. Du får følgende skærmbillede:

Efter dette klik på visningsknappen, og du vil kunne se de fulde detaljer om Todo:

Klik på knappen Rediger, og du kan redigere todo:

Klik på knappen Slet, og du kan slette Todo. Nu da vi er opmærksomme på, hvordan Dashboard fungerer, vil vi forstå de komponenter, der bruges i det.

1. Tilføj Todo: Til implementering af add todo bruger vi Dialog-komponenten i Material UI. Denne komponent implementerer en krogfunktionalitet. Vi bruger klasser, så vi fjerner denne funktionalitet.

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

Bortset fra dette vil vi også ændre placeringen af ​​knappen Tilføj Todo.

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

Nu vil vi erstatte listerne med en formular inde i denne dialog. Det vil hjælpe os med at tilføje den nye 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   

Dethåndtag Sendbestår af logik til at læse buttonTypetilstanden. Hvis staten er en tom streng, (“”)sendes den på Add Todo API. Hvis staten er en, Editopdateres Rediger Todo i dette scenario.

2. Få Todos: For at vise todos, vi bruger Grid containerog inde i det, placerer vi Grid item. Inde i det bruger vi en Cardkomponent til at vise dataene.

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

Vi bruger kortet til at vise todo-elementet, når API'en sender dem på en liste. Vi bruger componentWillMount-livscyklussen til at hente og indstille tilstanden, før gengivelsen udføres. Der er 3 knapper ( se, redigere og slette ), så vi har brug for 3 håndterere til at håndtere operationen, når der klikkes på knappen. Vi lærer om disse knapper i deres respektive underafsnit.

3. Rediger Todo: For redigering af todo genbruger vi dialog pop-up-koden, der bruges i add todo. For at skelne mellem knappeklik bruger vi en buttonTypetilstand. For Add Todo er   buttonTypestaten, (“”)mens det er for redigering Edit.

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

I handleSubmitmetoden læser vi buttonTypetilstanden og sender derefter anmodningen i overensstemmelse hermed.

4. Slet Todo: Når der trykkes på denne knap, sender vi todo-objektet til vores deleteTodoHandler, og derefter sender den anmodningen videre til backend.

 this.deleteTodoHandler({ todo })}>Delete

5. Se Todo: Når vi viser dataene, har vi afkortet dem, så brugeren får et glimt af, hvad todo handler om. Men hvis en bruger ønsker at vide mere om det, skal de klikke på visningsknappen.

Til dette vil vi bruge den tilpassede dialog. Inde i det bruger vi DialogTitle og DialogContent. Det viser vores titel og indhold. I DialougeContent bruger vi formularen til at vise det indhold, som brugeren har sendt. (Dette er en løsning, som jeg fandt ud af, at der er mange, og du er fri til at prøve andre.)

// 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. Anvendelse af tema: Dette er det sidste trin i vores ansøgning. Vi anvender et tema på vores ansøgning. Til dette bruger vi createMuiThemeog ThemeProviderfra materiale UI. Kopier og indsæt følgende kode i 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.  ); }

Vi savnede at anvende et tema til vores knap i todo.jsi CardActions. Tilføj farvekoden til knappen Vis, rediger og slet.

Gå til browseren, så finder du ud af, at alt er det samme, bortset fra at appen har en anden farve.

Og vi er færdige! Vi har bygget en TodoApp ved hjælp af ReactJS og Firebase. Hvis du har bygget det hele vejen til dette punkt, så en meget stor tillykke til dig med denne præstation.

Du er velkommen til at oprette forbindelse til mig på Twitter og Github.