Як створити TodoApp за допомогою ReactJS та Firebase

Привіт, люди, ласкаво просимо до цього підручника. Перш ніж ми почнемо, ви повинні бути ознайомлені з основними концепціями ReactJS. Якщо ви цього не зробите, я рекомендую вам переглянути документацію ReactJS.

У цьому додатку ми використовуватимемо такі компоненти:

  1. ReactJS
  2. Інтерфейс користувача
  3. Firebase
  4. ExpressJS
  5. Листоноша

Як буде виглядати наш додаток:

Архітектура програми:

Розуміння наших компонентів:

Можливо, вам цікаво, чому ми використовуємо firebase у цій програмі. Ну, він забезпечує безпечну автентифікацію , базу даних у реальному часі , безсерверний компонент та сегмент сховища .

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

Примітка: Я збираюся розділити цей підручник на чотири окремі розділи. На початку кожного розділу ви знайдете коміт git, у якому є код, розроблений у цьому розділі. Також якщо ви хочете побачити повний код, тоді він доступний у цьому сховищі.

Розділ 1: Розробка API Todo

У цьомурозділу , ми збираємось розробити такі елементи:

  1. Налаштуйте функції firebase.
  2. Встановіть фреймворк Express та побудуйте API Todo.
  3. Налаштування firestore як бази даних.

Код API Todo, реалізований у цьому розділі, можна знайти в цьому коміті.

Налаштування функцій Firebase:

Перейдіть на консоль Firebase.

Виберіть опцію Додати проект . Після цього дотримуйтесь gif нижче, крок за кроком, щоб налаштувати проект firebase.

Перейдіть на вкладку функцій і натисніть кнопку Почати :

Ви побачите діалогове вікно з інструкціями щодо налаштування функцій Firebase . Перейдіть до свого місцевого середовища. Відкрийте інструмент командного рядка. Щоб встановити інструменти firebase у свою машину, використовуйте команду нижче:

 npm install -g firebase-tools

Після цього скористайтеся командою, firebase initщоб налаштувати функції firebase у вашому локальному середовищі. Виберіть такі параметри при ініціалізації функції firebase у локальному середовищі:

  1. Які функції Firebase CLI ви хочете налаштувати для цієї папки? Натисніть пробіл, щоб вибрати функції, а потім 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

Перейдіть до Консолі проекту> Функції, і там ви знайдете 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

Після цього перейдіть до Консолі проекту> Функції і там ви знайдете 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 нижче:

Після завершення конфігурації натисніть кнопку Почати колекцію та встановіть ідентифікатор колекції як 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розділу " Функції> API" . Видаліть старий код і скопіюйте-вставте код нижче:

//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 serve. Якщо ви все ще отримуєте повідомлення про помилку , то використовуйте наступну команду: firebase login --reauth. Він відкриє сторінку входу в Google у веб-переглядачі. Після входу в систему він працюватиме без помилок.

URL-адресу ви знайдете в журналах інструменту командного рядка під час запуску команди firebase serve. Відкрийте цю 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 serve і відкрийте програму листоноші. Створіть новий запит і виберіть тип методу як 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. Також призначте для цього методу маршрут ВИДАЛИТИ.

//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. Редагувати пункт завдання: перейдіть до розділу 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> Аутентифікація.

Натисніть кнопку Налаштування способу входу . Ми будемо використовувати електронну пошту та пароль для перевірки користувача. Увімкніть опцію Електронна пошта / Пароль .

Зараз ми створимо користувача вручну. Спочатку ми створимо 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> Налаштування проекту> Загальне> Ваші програми> Фрагмент SD Firebase> конфігурація .

Скопіюйте та вставте у 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. Завантажте зображення профілю користувача:

Наші користувачі зможуть завантажити свою фотографію профілю. Для досягнення цього ми будемо використовувати сегмент Storage. Перейдіть до консолі Firebase> Зберігання та натисніть кнопку Почати . Дотримуйтесь GIF нижче для налаштування:

Тепер перейдіть на вкладку Правила в розділі Зберігання та оновіть дозвіл на доступ до сегмента відповідно до зображення нижче:

Для завантаження зображення профілю ми будемо використовувати пакет із назвою 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> вкладка «Авторизація»> «Тип»> «Маркер на пред'явника» та в розділі маркера вставте маркер.

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 Інформація про користувача завершено. Виконайте 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:

Щоб захистити API Todo, щоб лише обраний користувач міг отримати до нього доступ, ми внесемо кілька змін у наш існуючий код. По-перше, ми оновимо 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розділу functions> APIs directory.

  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 та інтерфейсу матеріалу:

Ми будемо використовувати шаблон create-response-app. Це дає нам фундаментальну структуру для розробки програми. Щоб встановити його, використовуйте таку команду:

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/і ви побачите такий результат:

Тепер ми видалимо всі непотрібні компоненти. Перейдіть до каталогу перегляду, а потім видаліть усі файлиякі мають [Видалити] перед собою. Для цього зверніться до деревної структури каталогів нижче.

+-- 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/і ви отримаєте порожній екран.

Щоб встановити Інтерфейс користувача, перейдіть до каталогу подання та скопіюйте та вставте цю команду в термінал:

npm install @material-ui/core

Не забувайте використовувати версію v4.9.8 бібліотеки 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у каталозі сторінок .

Ми імпортуємо компоненти Material UI та пакет 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 наступним чином:

Пам'ятайте: додайте ключ із іменем проксі під існуючим об'єктом 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. Якщо запит успішний, ми зберігаємо маркер. Якщо ми отримуємо помилки у відповіді, ми просто робимо їх на UI.

Перейдіть до браузера за адресою, //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. Цей файл буде індексом нашої програми. Обидва розділи "Обліковий запис" та "Завдання" завантажуються на цю сторінку, натискаючи кнопку.

Імпортуйте пакети Material UI, пакет Axios, наш власний обліковий запис, компоненти todo та проміжне програмне забезпечення для автентифікації.

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

Тепер настав час для розділу облікового запису. Імпортуйте у нас утиліту Material UI, 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. Наразі просто скопіюйте та вставте такий код:

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

Розділ 4: Інформаційна панель Todo

У цьомурозділ , ми будемо розвивати призначений для користувача інтерфейс для цих особливостей Todos Dashboard:

  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 в інтерфейсі матеріалу. Цей компонент реалізує функціональність гачка. Ми використовуємо класи, тому ми видалимо цю функціональність.

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

Окрім цього, ми також змінимо розташування кнопки "Додати завдання".

// 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стану. Якщо стан - це порожній рядок, (“”)він буде розміщений на API Add Todo. Якщо стан Editу такому випадку є тоді, він оновить редагування завдання.

2. Отримати Todos: Для відображення todos ми будемо використовувати Grid containerі всередині нього, ми розміщуємо Grid item. Усередині цього ми будемо використовувати Cardкомпонент для відображення даних.

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

Ми використовуємо карту для відображення елемента завдання, оскільки API надсилає їх у список. Ми використаємо життєвий цикл componentWillMount, щоб отримати та встановити стан перед тим, як буде виконано візуалізацію. Є 3 кнопки ( перегляд, редагування та видалення ), тому нам знадобляться 3 обробники для обробки операції при натисканні кнопки. Ми дізнаємось про ці кнопки у відповідних підрозділах.

3. Редагувати завдання: Для редагування завдання ми повторно використовуємо спливаючий код діалогу, який використовується в додаванні завдання. Для розрізнення натискань на кнопку ми використовуємо buttonTypeстан. Для Add Todo - це   buttonTypeстан, (“”)тоді як для редагування - це Edit.

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

У handleSubmitметоді ми зчитуємо buttonTypeстан, а потім надсилаємо запит відповідно.

4. Видалити Todo: Після натискання цієї кнопки ми надсилаємо об'єкт todo на наш deleteTodoHandler, а потім він надсилає запит до серверної бази.

 this.deleteTodoHandler({ todo })}>Delete

5. Переглянути Todo: Коли ми показуємо дані, ми їх усікали, щоб користувач міг побачити, про що йдеться в todo. Але якщо користувач хоче дізнатись більше про це, йому потрібно натиснути кнопку перегляду.

Для цього ми будемо використовувати Індивідуальний діалог. Всередині цього ми використовуємо 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.