Как да изградим TodoApp с помощта на ReactJS и Firebase

Здравейте хора, добре дошли в този урок. Преди да започнем, трябва да сте запознати с основните концепции на ReactJS. Ако не сте, бих препоръчал да преминете през документацията на ReactJS.

В това приложение ще използваме следните компоненти:

  1. ReactJS
  2. Потребителски интерфейс на материала
  3. Firebase
  4. ExpressJS
  5. Пощальон

Как ще изглежда нашето приложение:

Архитектура на приложението:

Разбиране на нашите компоненти:

Може би се чудите защо използваме firebase в това приложение. Е, той осигурява сигурно удостоверяване , база данни в реално време , безсървърен компонент и сегмент за съхранение .

Тук използваме Express, така че не е необходимо да обработваме HTTP изключения. Ще използваме всички пакети на firebase в компонента на нашите функции. Това е така, защото не искаме да правим нашето клиентско приложение твърде голямо, което има тенденция да забавя процеса на зареждане на потребителския интерфейс.

Забележка: Ще разделя този урок на четири отделни раздела. В началото на всеки раздел ще намерите git commit, който има разработен код в този раздел. Също така, ако искате да видите пълния код, той е достъпен в това хранилище.

Раздел 1: Разработване на API на Todo

В товараздел , ще разработим следните елементи:

  1. Конфигурирайте функциите на firebase.
  2. Инсталирайте рамката Express и изградете API на Todo.
  3. Конфигуриране на firestore като база данни.

Кодът на Todo API, реализиран в този раздел, може да бъде намерен в този комит.

Конфигурирайте функциите на Firebase:

Отидете до конзолата на Firebase.

Изберете опцията Добавяне на проект . След това следвайте gif долу, стъпка по стъпка, за да конфигурирате проекта на firebase.

Отидете в раздела с функции и кликнете върху бутона Първи стъпки :

Ще видите диалогов прозорец с инструкции за това как да настроите функциите на Firebase . Отидете в местната среда. Отворете инструмента за команден ред. За да инсталирате инструментите на firebase във вашата машина, използвайте командата по-долу:

 npm install -g firebase-tools

След като приключите, използвайте командата, за firebase initда конфигурирате функциите на firebase във вашата локална среда. Изберете следните опции при инициализиране на функцията firebase в локалната среда:

  1. Кои функции на Firebase CLI искате да настроите за тази папка? Натиснете Space, за да изберете функции, след което Enter, за да потвърдите избора си => Функции: Конфигуриране и внедряване на облачни функции
  2. Първо, нека свържем тази директория на проекта с проект на Firebase…. => Използвайте съществуващ проект
  3. Изберете проект по подразбиране на Firebase за тази директория => име_на приложение
  4. Кой език бихте искали да използвате за писане на облачни функции? => JavaScript
  5. Искате ли да използвате ESLint за улавяне на вероятни грешки и налагане на стил? => N
  6. Искате ли да инсталирате зависимости с npm сега? (Y / n) => Y

След като конфигурацията приключи, ще получите следното съобщение:

✔ Firebase initialization complete!

Това ще бъде нашата структура на директории, след като инициализацията приключи:

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

Сега отворете директорията index.jsпод функции и копирайте и поставете следния код:

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

Разположете кода във функциите на firebase, като използвате следната команда:

firebase deploy

След като внедряването приключи, ще получите следния лог в края на вашия команден ред:

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

Отидете на Project Console> Функции и там ще намерите URL адреса на API. URL адресът ще изглежда така:

//-todoapp-.cloudfunctions.net/helloWorld

Копирайте този URL и го поставете в браузъра. Ще получите следния отговор:

Hello from Firebase!

Това потвърждава, че нашата функция Firebase е конфигурирана правилно.

Инсталирайте Express Framework:

Сега нека инсталираме Expressрамката в нашия проект, като използваме следната команда:

npm i express

Сега нека създадем API директория вътре в директорията с функции . Вътре в тази директория ще създадем файл с име todos.js. Премахнете всичко от index.jsи след това копирайте и поставете следния код:

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

Присвоихме функцията getAllTodos на маршрута / todos . Така че всички API извиквания по този маршрут ще се изпълняват чрез функцията getAllTodos. Сега отидете на todos.jsфайла в директорията на API и тук ще напишем функцията 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); }

Тук декларирахме примерен JSON обект. По-късно ще извлечем това от Firestore. Но засега ще върнем това. Сега внедрете това във вашата функция на firebase с помощта на командата firebase deploy. Ще попитаза разрешение да изтриете модула helloworld - просто въведете 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

След като това стане, отидете на Project Console> Функции и там ще намерите URL адреса на API. API ще изглежда така:

//-todoapp-.cloudfunctions.net/api

Сега отидете в браузъра и копирайте-поставете URL и добавете / todos в края на този URL. Ще получите следния изход:

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

Firebase Firestore:

Ние ще използваме firebase firestore като база данни в реално време за нашето приложение. Сега отидете на Конзола> База данни във Firebase Console. За да конфигурирате firestore, следвайте gif по-долу:

След като конфигурирането приключи, щракнете върху бутона Стартиране на колекцията и задайте ID на колекция като todos . Щракнете върху Напред и ще получите следния изскачащ прозорец:

Игнорирайте бутона DocumentID. За полето, типа и стойността вижте JSON долу. Актуализирайте съответно стойността:

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

Натиснете бутона за запазване. Ще видите, че колекцията и документът са създадени. Върнете се в местната среда. Трябва да инсталираме firebase-adminпакета с firestore, от който се нуждаем. Използвайте тази команда, за да го инсталирате:

npm i firebase-admin

Създайте директория с име util под директорията с функции .Отидете в тази директория и създайте име на файл admin.js. В този файл ще импортираме администраторския пакет на firebase и ще инициализираме обекта на базата данни на firestore. Ще експортираме това, за да могат други модули да го използват.

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

Сега нека напишем API за извличане на тези данни. Отидете todos.jsв директорията функции> APIs . Премахнете стария код и копирайте и поставете кода по-долу:

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

Тук извличаме всички задачи от базата данни и ги препращаме на клиента в списък.

Можете също така да стартирате приложението локално, като използвате firebase serveкоманда, вместо да го разгръщате всеки път. Когато изпълните тази команда, може да получите грешка по отношение на идентификационните данни. За да го поправите, следвайте стъпките, споменати по-долу:

  1. Отидете в настройките на проекта (икона за настройки в горната лява част)
  2. Отидете в раздела акаунти за услуги  
  3. Долу ще има опция за генериране на нов ключ . Кликнете върху тази опция и тя ще изтегли файл с разширение JSON.
  4. Трябва да експортираме тези идентификационни данни в нашата сесия на командния ред. Използвайте командата по-долу, за да направите това:
export GOOGLE_APPLICATION_CREDENTIALS="/home/user/Downloads/[FILE_NAME].json"

След това изпълнете командата за обслужване на firebase. Ако грешката продължи и след това да използвате следната команда: firebase login --reauth. Той ще отвори страницата за вход в Google в браузър. След като влезете в системата, той ще работи без грешка.

Ще намерите URL адрес в дневниците на инструмента за команден ред, когато стартирате команда за обслужване на firebase. Отворете този URL в браузъра и добавете /todosслед него.

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

Ще получите следния изход JSON във вашия браузър:

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

Писане на други API:

Време е да напишем всички други API на todo, които ще изискваме за нашето приложение.

  1. Създаване на елемент на Todo: Отидете в index.jsдиректорията с функции. Импортиране на метод postOneTodo съгласно съществуващия getAllTodos. Също така задайте POST маршрута на този метод.
//index.js const { .., postOneTodo } = require('./APIs/todos') app.post('/todo', postOneTodo);

Отидете във todos.jsвътрешността на директорията с функции и добавете нов метод postOneTodoпод съществуващия getAllTodosметод.

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

По този метод добавяме нов Todo към нашата база данни. Ако елементите на нашето тяло са празни, тогава ще върнем отговор от 400 или в противен случай ще добавим данните.

Изпълнете командата за обслужване на firebase и отворете приложението пощальон. Създайте нова заявка и изберете типа на метода като POST . Добавете URL и тяло от тип JSON.

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

Натиснете бутона за изпращане и ще получите следния отговор:

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

2. Изтриване на елемента Todo: Отидете в index.jsдиректорията с функции. Импортирайте метода deleteTodo под съществуващия postOneTodo. Също така задайте маршрута DELETE на този метод.

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

Отидете на todos.jsи добавете нов метод deleteTodoпод съществуващия postOneTodoметод.

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

По този метод изтриваме Todo от нашата база данни. Изпълнете командата за обслужване на firebase и отидете при пощальона. Създайте нова заявка, изберете типа на метода като ИЗТРИВАНЕ и добавете URL адреса.

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

Натиснете бутона за изпращане и ще получите следния отговор:

{ "message": "Delete successfull" }

3. Редактиране на елемента Todo: Отидете в index.jsпод директорията с функции. Импортирайте метода editTodo под съществуващия deleteTodo. Също така задайте маршрута PUT на този метод.

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

Отидете на todos.jsи добавете нов метод editTodoпод съществуващия deleteTodoметод.

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

По този метод редактираме Todo от нашата база данни. Не забравяйте, че тук не позволяваме на потребителя да редактира полетата todoId или createdAt. Изпълнете командата за обслужване на firebase и отидете при пощальона. Създайте нова заявка, изберете типа на метода като PUT и добавете URL адреса.

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

Натиснете бутона за изпращане и ще получите следния отговор:

{ "message": "Updated successfully" }

Структура на каталога досега:

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

С това завършихме първия раздел на приложението. Можете да продължите да пиете кафе, да си вземете почивка и след това ще работим върху разработването на потребителски API.

Раздел 2: Разработване на потребителски API

В товараздел , ще разработим следните компоненти:

  1. API за удостоверяване на потребителя (вход и регистрация).
  2. Вземете и актуализирайте API за потребителски подробности.
  3. Актуализирайте API на снимката на потребителския профил.
  4. Защита на съществуващия API на Todo.

Потребителският API код, внедрен в този раздел, може да бъде намерен в този комит.

Така че нека започнем да изграждаме API за удостоверяване на потребителя. Отидете на конзолата на Firebase> Удостоверяване.

Щракнете върху бутона Set up sign-in method . Ще използваме имейл и парола за проверка на потребителя. Активирайте опцията Email / Password .

Точно сега ще създадем ръчно нашия потребител. Първо ще изградим API за вход. След това ще изградим API за регистрация.

Отидете в раздела Потребители под Удостоверяване, попълнете данните за потребителя и кликнете върху бутона Добавяне на потребител .

1. API за потребителски вход:

Първо, трябва да инсталираме firebaseпакета, който се състои от библиотеката за удостоверяване на Firebase, като използваме следната команда:

npm i firebase

След като инсталацията приключи, отидете в директорията functions> APIs . Тук ще създадем users.jsфайл. Сега отвътре index.jsимпортираме метод loginUser и му присвояваме POST маршрута.

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

Отидете в Настройки на проекта> Общи и там ще намерите следната карта:

Изберете уеб иконата и след това следвайте gif долу:

Изберете опцията продължи към конзолата . След като направите това, ще видите JSON с конфигурация на firebase. Отидете в директорията functions> util и създайте   config.jsфайл. Копирайте и поставете следния код в този файл:

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

Заменете ............със стойностите, които получавате в конзолата на Firebase> Настройки на проекта> Общи> вашите приложения> Фрагмент на Firebase SD> config .

Копирайте и поставете следния код във users.jsфайла:

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

Тук използваме модул firebase signInWithEmailAndPassword , за да проверим дали предоставените от потребителя идентификационни данни са правилни. Ако те са прави, ние изпращаме токена на този потребител или в противен случай статус 403 със съобщение „грешни идентификационни данни“.

Сега нека създадем validators.jsв директорията functions> util . Копирайте и поставете следния код в този файл:

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

С това нашият LoginAPI е завършен. Изпълнете firebase serveкомандата и отидете при пощальона. Създайте нова заявка, изберете типа на метода като POST и добавете URL и тялото.

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

Натиснете бутона за изпращане на заявка в пощальона и ще получите следния изход:

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

Ще използваме този маркер в предстояща част, за да получим подробностите за потребителя . Не забравяйте, че този маркер изтича след 60 минути . За да генерирате нов маркер, използвайте този API отново.

2. API за регистрация на потребителя:

Механизмът за удостоверяване по подразбиране на firebase ви позволява да съхранявате само информация като имейл, парола и др. Но ние се нуждаем от повече информация, за да идентифицираме дали този потребител притежава тази задача, за да може да извършва операции за четене, актуализиране и изтриване върху нея.

За да постигнем тази цел, ще създадем нова колекция, наречена потребители . Под тази колекция ще съхраняваме данните на потребителя, които ще бъдат картографирани в заданието въз основа на потребителското име. Всяко потребителско име ще бъде уникално за всички потребители на платформата.

Отидете на index.js. Внасяме метод на signUpUser и му присвояваме POST маршрута.

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

Сега отидете на validators.jsи добавете следния код под validateLoginDataметода.

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

Сега отидете на users.jsи добавете следния код под loginUserмодула.

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

Ние проверяваме потребителските си данни и след това изпращаме имейл и парола до модула firebase createUserWithEmailAndPassword , за да създадем потребителя. След като потребителят е създаден успешно, ние записваме идентификационните данни на потребителя в базата данни.

С това нашият API за регистрация е завършен. Изпълнете firebase serveкомандата и отидете при пощальона. Създайте нова заявка, изберете типа метод като POST . Добавете URL и тялото.

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

Натиснете бутона за изпращане на заявка в пощальона и ще получите следния изход:

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

Сега отидете на конзолата на Firebase> База данни и там ще видите следния изход:

Както можете да видите колекцията на нашия потребител е успешно създадена с един документ в нея.

3. Качете снимка на потребителския профил:

Потребителите ни ще могат да качват своя снимка на профила. За да постигнем това, ще използваме кофа за съхранение. Отидете до конзолата на Firebase> Хранилище и кликнете върху бутона Първи стъпки . Следвайте GIF по-долу за конфигурацията:

Сега отидете в раздела Правила под Storage и актуализирайте разрешението за достъп до групата, както е показано на изображението по-долу:

За да качим снимката на профила, ще използваме пакета с име busboy. За да инсталирате този пакет, използвайте следната команда:

npm i busboy

Отидете на index.js. Импортирайте метода uploadProfilePhoto под съществуващия метод signUpUser. Също така задайте POST маршрута на този метод.

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

Тук сме добавили слой за удостоверяване, така че само потребител, свързан с този акаунт, може да качи изображението. Сега създайте файл с име auth.jsв директорията functions> utils . Копирайте и поставете следния код в този файл:

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

Тук използваме модула firebase verifyIdToken за проверка на маркера. След това декодираме потребителските данни и ги предаваме в съществуващата заявка.

Отидете на users.jsи добавете следния код под signupметода:

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

С това нашият API за снимка на профила за качване е завършен. Изпълнете firebase serveкомандата и отидете при пощальона. Създайте нова заявка, изберете типа на метода като POST , добавете URL и в раздела за тяло изберете тип като данни от формуляра.

Заявката е защитена, така че ще трябва да изпратите и токена на приносителя . За да изпратите токена на приносителя, влезте отново, ако токенът е изтекъл. След това в Postman App> раздела Authorization> Type> Bearer Token и в раздела token поставете маркера.

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

Натиснете бутона за изпращане на заявка в пощальона и ще получите следния изход:

{ "message": "Image uploaded successfully" }

4. Вземете подробности за потребителя:

Тук извличаме данните на нашия потребител от базата данни. Отидете до index.jsметода getUserDetail и импортирайте и му задайте GET маршрут.

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

Сега отидете на users.jsи добавете следния код след uploadProfilePhotoмодула:

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

Използваме модула firebase doc (). Get () , за да извлечем потребителски подробности. С това нашият GET API за потребителски данни е завършен. Изпълнете firebase serveкомандата и отидете при пощальона. Създайте нова заявка, изберете типа на метода: GET и добавете URL и тялото.

Заявката е защитена, така че ще трябва да изпратите и токена на приносителя . За да изпратите токена на приносителя, влезте отново, ако токенът е изтекъл.

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

Натиснете бутона за изпращане на заявка в пощальона и ще получите следния изход:

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

5. Актуализирайте подробности за потребителя:

Сега нека добавим функционалността, за да актуализираме потребителските подробности. Отидете на index.jsи копирайте и поставете следния код:

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

Сега отидете на users.jsи добавете updateUserDetailsмодула под съществуващия 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" }); }); }

Тук използваме метода за актуализиране на firebase . С това нашият API за актуализация на потребителските данни е завършен. Следвайте същата процедура за заявка, както при API за получаване на подробности за потребителя по-горе с една промяна. Добавете тяло в заявката тук и метод като 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 }

Натиснете бутона за изпращане на заявка в пощальона и ще получите следния изход:

{ "message": "Updated successfully" }

6. Осигуряване на API на Todo:

За да осигурим Todo API, така че само избраният потребител да има достъп до него, ще направим няколко промени в нашия съществуващ код. Първо, ще актуализираме, index.jsкакто следва:

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

Актуализирахме всички маршрути на Todo, като добавихме authтака, че всички API извиквания ще изискват маркер и могат да бъдат достъпни само от конкретния потребител.

След това отидете todos.jsв директорията функции> APIs .

  1. Създайте API на Todo: Отворете todos.jsи под метода postOneTodo добавете ключа за потребителско име, както следва:
const newTodoItem = { .., username: request.user.username, .. }

2. GET All Todos API: Отворете todos.jsи под метода getAllTodos добавете клаузата where, както следва:

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

Стартирайте сервиза на firebase и тествайте нашия GET API. Не забравяйте да изпратите знака на приносителя. Тук ще получите грешка в отговора, както следва:

{ "error": 9 }

Отидете до командния ред и ще видите следните редове регистрирани:

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

Отворете това в браузъра и кликнете върху създаване на индекс.

След като индексът е изграден, изпратете заявката отново и ще получите следния изход:

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

3.   Изтриване на API на Todo: Отворете todos.jsи под метода deleteTodo добавете следното условие. Добавете това условие в заявката document.get (). Then () под условието ! Doc.exists .

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

Структура на директории досега:

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

С това завършихме нашия API бекенд. Направете почивка, пийте кафе и след това ще започнем да изграждаме предния край на нашето приложение

Раздел 3: Табло за управление на потребителя

В товараздел , ще разработим следните компоненти:

  1. Конфигурирайте ReactJS и потребителския интерфейс на материала.
  2. Изграждане на формуляр за вход и регистрация.
  3. Раздел за изграждане на акаунт.

Кодът на потребителското табло, реализиран в този раздел, може да бъде намерен в този комит.

1. Конфигурирайте ReactJS и потребителския интерфейс на материала:

Ще използваме шаблона за създаване-реакция-приложение. Това ни дава основна структура за разработване на приложението. За да го инсталирате, използвайте следната команда:

npm install -g create-react-app

Отидете в основната папка на проекта, където присъства директорията с функции. Инициализирайте нашето приложение отпред, като използвате следната команда:

create-react-app view

Не забравяйте да използвате версия v16.13.1 набиблиотеката на ReactJS .

След като инсталацията приключи, ще видите следното в дневниците на командния ред:

cd view npm start Happy hacking!

С това конфигурирахме нашето приложение React. Ще получите следната структура на директорията:

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

Сега стартирайте приложението, като използвате командата npm start. Отидете в браузъра //localhost:3000/и ще видите следния изход:

Сега ще премахнем всички ненужни компоненти. Отидете в директорията на изгледа и след това премахнете всички файловекоито имат [Remove] пред себе си. За това вижте дървовидната структура на директориите по-долу.

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

Отидете index.htmlв публичната директория и премахнете следните редове:

Сега отидете в App.jsдиректорията src и заменете стария код със следния код:

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

Отидете на index.jsи премахнете следния импорт:

import './index.css'

Не съм изтрил, App.cssнито го използвам в това приложение. Но ако искате да го изтриете или използвате, можете да го направите.

Отидете в браузъра //localhost:3000/и ще получите изход за празен екран.

За да инсталирате Material UI, отидете в директорията на изгледа и копирайте и поставете тази команда в терминала:

npm install @material-ui/core

Не забравяйте да използвате версия v4.9.8 на библиотеката Material UI.

2. Формуляр за вход:

За да разработите формата за вход, отидете на App.js. В горната част на App.jsдобавете следния внос:

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

Използваме Switch and Route, за да зададем маршрути за нашето TodoApp. Точно сега ще добавим само маршрута / login и ще му присвоим компонент за вход.

// App.js 

Създайте директория на страници под съществуващата директория на изглед и файл, наречен login.jsпод директорията на страниците .

Ще импортираме компоненти за потребителски интерфейс и пакета Axios в 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';

Ще добавим следните стилове към нашата страница за вход:

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

Ще създадем клас с име за вход, който има формуляр и ще изпрати манипулатор вътре в него.

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

В края на този файл добавете следния експорт:

export default withStyles(styles)(login); 

Добавете URL адреса на нашите функции на firebase, за да видите> package.json, както следва:

Запомнете: Добавете ключ с име proxy под съществуващия обект JSON на браузъра
"proxy": "//-todoapp-.cloudfunctions.net/api"

Инсталирайте пакета с икони Axios и материал, като използвате следните команди:

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

Добавихме маршрут за вход през App.js. В login.jsсъздадохме компонент на клас, който обработва състоянието, изпраща заявката за публикуване до API за вход, използвайки пакета Axios. Ако заявката е успешна, тогава ние съхраняваме маркера. Ако получим грешки в отговора, ние просто ги изобразяваме в потребителския интерфейс.

Отидете в браузъра на //localhost:3000/loginи ще видите следния потребителски интерфейс за вход.

Опитайте да попълните грешни идентификационни данни или да изпратите празна заявка и ще получите грешките. Изпратете валидна заявка. Отидете на конзолата за програмисти> Приложение . Ще видите, че потребителският маркер се съхранява в локалното хранилище. След като входът е успешен, ще бъдем пренасочени обратно към началната страница.

3. Форма за регистрация:

За да разработите формуляра за регистрация, отидете App.jsи актуализирайте съществуващия Routeкомпонент с реда по-долу:

// App.js 

Не забравяйте да импортирате:

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

Създайте файл с име signup.jsпод директорията на страниците .

Вътре в signup.js ще импортираме потребителския интерфейс на материала и пакета 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';

Ще добавим следните стилове към нашата страница за регистрация:

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

Ще създадем клас с име регистрация, който има формуляр и ще изпрати манипулатор вътре в него.

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

В края на този файл добавете следния експорт:

export default withStyles(styles)(signup); 

Логиката за компонента за регистрация е същата като компонента за вход. Отидете в браузъра на адрес //localhost:3000/signupи ще видите следния потребителски интерфейс за регистрация. След като регистрацията е успешна, ще бъдем пренасочени обратно към началната страница.

Опитайте да попълните грешни идентификационни данни или да изпратите празна заявка и ще получите грешките. Изпратете валидна заявка. Отидете на конзолата за програмисти> Приложение . Ще видите, че потребителският маркер се съхранява в локалното хранилище.

4. Сметка:

За да изградим страницата на акаунта, първо ще трябва да създадем нашата начална страница, от която ще заредим секцията за акаунта . Отидете до App.jsи актуализирайте следния маршрут:

// App.js 

Не забравяйте вноса:

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

Създайте нов файл с име home.js. Този файл ще бъде индексът на нашето приложение. Разделите Account и Todo се зареждат на тази страница въз основа на щракване върху бутона.

Импортирайте пакетите за потребителски интерфейс Material, пакета Axios, нашия потребителски акаунт, всички компоненти и междинен софтуер за удостоверяване.

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

Ще зададем нашата чекмедже Ширина, както следва:

const drawerWidth = 240;

Ще добавим следния стил към нашата начална страница:

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

Ще създадем клас с име дом. Този клас ще има API извикване, за да получи снимката на потребителския профил, собственото и фамилното име. Също така ще има логика да избере кой компонент да се показва, Todo или 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 ? : } ); } } }

Тук в кода ще видите, че authMiddleWare(this.props.history);се използва. Този междинен софтуер проверява дали authToken е нулев. Ако отговорът е да, тогава потребителят ще се върне към login.js. Това се добавя, така че нашият потребител няма достъп до /маршрута без регистрация или влизане. В края на този файл добавете следния експорт:

export default withStyles(styles)(home); 

Сега чудите ли се какво прави този код home.js?

 {this.state.render ?  : } 

Проверява състоянието на изобразяване, което задаваме при щракване върху бутона. Нека създадем директорията на компонентите и под нея създадем два файла: account.jsи todo.js.

Нека създадем директория с име util и файл с име auth.jsпод тази директория. Копирайте и поставете следния код под auth.js:

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

За известно време в todo.jsфайл просто ще напишем клас, който изобразява текста Здравейте аз съм todo . Ще работим върху нашите задачи в следващия раздел:

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

Сега е време за секцията за акаунта. Импортирайте полезния потребителски интерфейс, clsx, axios и authmiddleWare в нашата 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';

Ще добавим следния стил към страницата на акаунта си:

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

Ще създадем компонент на клас с име акаунт. За момента просто копирайте и поставете следния код:

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

В края на този файл добавете следния експорт:

export default withStyles(styles)(account); 

В account.jsима много компоненти, използвани. Първо да видим как изглежда нашето приложение. След това ще обясня всички използвани компоненти и защо се използват.

Отидете в браузъра и ако вашият маркер изтече, той ще ви пренасочи към   loginстраницата. Добавете вашите данни и влезте отново. След като направите това, отидете в раздела Акаунт и ще намерите следния потребителски интерфейс:

В раздела за акаунти има 3 манипулатора:

  1. componentWillMount : Това е вграденият метод на жизнения цикъл на React. Използваме го, за да заредим данните преди жизнения цикъл на изобразяване и да актуализираме нашите стойности на състоянието.
  2. ProfilePictureUpdate: Това е нашият потребителски манипулатор, който използваме, така че когато нашият потребител щракне върху бутона Качване на снимка, той ще изпрати данните на сървър и ще презареди страницата, за да покаже новата снимка на профила на потребителя.
  3. updateFormValues: Това е и нашият потребителски манипулатор за актуализиране на данните на потребителя. Тук потребителят може да актуализира собственото си име, фамилия и държава. Не допускаме актуализации по имейл и потребителско име, защото нашата логика на бекенда зависи от тези ключове.

Освен тези 3 манипулатора, това е страница с формуляр със стил върху нея. Ето структурата на директориите до този момент в папката на изгледа:

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

С това завършихме нашето табло за управление на акаунта. Сега отидете да пиете кафе, да си починете и в следващия раздел ще изградим Todo Dashboard.

Раздел 4: Табло за управление на Todo

В товараздел , ние ще се развие потребителския интерфейс за тези характеристики на Todos таблото:

  1. Добавете задание:
  2. Вземете всички задачи:
  3. Изтрийте задание
  4. Редактирайте задание
  5. Вземете задание
  6. Прилагане на тема

Кодът на таблото за управление на Todo, реализиран в този раздел, може да бъде намерен в този комит.

Отидете в директорията todos.jsна компонентите . Добавете следния внос към съществуващия внос:

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

Също така трябва да добавим следните CSS елементи в съществуващите стилови компоненти:

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

Ще добавим прехода за изскачащия диалогов прозорец:

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

Премахнете съществуващия клас todo и копирайте и поставете следния клас:

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

В края на този файл добавете следния експорт:

export default withStyles(styles)(todo); 

Първо ще разберем как работи нашият потребителски интерфейс и след това ще разберем кода. Отидете в браузъра и ще получите следния потребителски интерфейс:

Кликнете върху бутона Добавяне в долния десен ъгъл и ще получите следния екран:

Добавете заглавието и подробностите на Todo и натиснете бутона за изпращане. Ще получите следния екран:

След това щракнете върху бутона за изглед и ще можете да видите пълните подробности за Todo:

Кликнете върху бутона Редактиране и ще можете да редактирате заданието:

Щракнете върху бутона за изтриване и ще можете да изтриете Todo. Сега, когато сме наясно как работи таблото за управление, ще разберем компонентите, използвани в него.

1. Add Todo: За изпълнението на add todo ще използваме Dialogue компонента на Material UI. Този компонент изпълнява функционалност на куката. Използваме класовете, така че ще премахнем тази функционалност.

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

Освен това ще променим и разположението на бутона Add Todo.

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

Сега ще заменим маркера на списъка с формуляр в този диалог. Това ще ни помогне при добавянето на новата задача.

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

ThehandleSubmitсе състои от логика за четене на buttonTypeсъстоянието. Ако състоянието е празен низ, (“”)той ще публикува в Add Todo API. Ако състоянието е Editтогава в този сценарий, той ще актуализира Редактиране на заданието.

2. Вземете Todos: За да покажем todos, ще използваме Grid containerи вътре в него, ние поставяме Grid item. Вътре в това ще използваме Cardкомпонент за показване на данните.

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

Използваме картата, за да покажем елемента todo, докато API ги изпраща в списък. Ще използваме жизнения цикъл на компонентWillMount, за да получим и зададем състоянието преди изпълнението на изобразяването. Има 3 бутона ( преглед, редактиране и изтриване ), така че ще ни трябват 3 манипулатора, за да се справим с операцията при щракване върху бутона. Ще научим за тези бутони в съответните им подраздели.

3. Редактиране на задача: За редактиране на задача, ние използваме повторно изскачащия код за диалог, който се използва в добавянето на задача. За да правим разлика между кликванията върху бутона, използваме buttonTypeсъстояние. За Add Todo   buttonTypeсъстоянието е, (“”)докато за edit todo е Edit.

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

В handleSubmitметода четем buttonTypeсъстоянието и след това изпращаме заявката съответно.

4. Изтриване на Todo: Когато се натисне този бутон, ние изпращаме todo обекта на нашия deleteTodoHandler и след това той изпраща заявката допълнително към бекенда.

 this.deleteTodoHandler({ todo })}>Delete

5. Преглед на заданието: Когато показваме данните, ние сме го съкратили, така че потребителят да има представа за какво става въпрос. Но ако потребителят иска да научи повече за това, той трябва да кликне върху бутона за изглед.

За целта ще използваме персонализиран диалог. Вътре използваме DialogTitle и DialogContent. Той показва нашето заглавие и съдържание. В DialougeContent ще използваме формата за показване на съдържанието, което потребителят е публикувал. (Това е едно решение, което открих, че има много и можете да опитате друго.)

// 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. Прилагане на тема: Това е последната стъпка от нашето приложение. Ние ще приложим тема към нашето приложение. За това използваме createMuiThemeи ThemeProviderот потребителски интерфейс на материала. Копирайте и поставете следния код в 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.  ); }

Пропуснахме прилагане тема в нашия бутон todo.jsв CardActions. Добавете цветния маркер за бутона за изглед, редактиране и изтриване.

Отидете в браузъра и ще откриете, че всичко е същото, освен че приложението е с различен цвят.

И приключихме! Изградихме TodoApp, използвайки ReactJS и Firebase. Ако сте го изградили чак до този момент, тогава много големи поздравления за това постижение.

Чувствайте се свободни да се свържете с мен в Twitter и Github.