분류 Reactjs

ReactJS 및 Firebase를 사용하여 TodoApp을 빌드하는 방법

컨텐츠 정보

  • 조회 495 (작성일 )

본문

여러분 안녕하세요,이 튜토리얼에 오신 것을 환영합니다. 

시작하기 전에 기본 ReactJS 개념에 익숙해야 합니다. 그렇지 않은 경우 ReactJS 설명서를 참조하십시오.


이 응용 프로그램에서 다음 구성 요소를 사용합니다.


https://www.freecodecamp.org/news/how-to-build-a-todo-application-using-reactjs-and-firebase/ 


응용 프로그램이 어떻게 보일지 :

Account-1.gif 

계정 생성


ezgif.com-optimize.gif 

TodoApp 대시 보드


응용 프로그램 아키텍처 : 


TodoApp-1.png 

응용 프로그램 아키텍처


구성 요소 이해 : 


이 애플리케이션에서 왜 Firebase를 사용하는지 궁금 할 것입니다. 보안 인증, 실시간 데이터베이스, 서버리스 구성 요소 및 스토리지 버킷을 제공합니다.


여기서는 HTTP 예외를 처리 할 필요가 없도록 Express를 사용하고 있습니다. 함수 구성 요소에 모든 firebase 패키지를 사용하려고 합니다. 클라이언트 응용 프로그램을 너무 크게 만들고 싶지 않기 때문에 UI의 로딩 프로세스가 느려집니다.


참고 :이 튜토리얼을 4 개의 개별 섹션으로 나누겠습니다. 모든 섹션의 시작 부분에는 해당 섹션에서 개발 된 코드가 있는 git commit이 있습니다. 또한 전체 코드를 보려면 이 저장소에서 사용할 수 있습니다.


섹션 1 : Todo API 개발 


이 섹션에서는 다음 요소를 개발할 것입니다.


  1. Firebase 함수를 구성하십시오.
  2. Express 프레임 워크를 설치하고 Todo API를 빌드하십시오.
  3. Firestore를 데이터베이스로 구성

이 섹션에서 구현 된 Todo API 코드는 이 커밋에서 찾을 수 있습니다.


Firebase 기능 구성 : 


Firebase 콘솔로 이동합니다.


FirebaseFunctions.png 

Firebase 콘솔


프로젝트 추가 옵션을 선택하십시오. 그런 다음 아래 gif를 단계별로 따라 Firebase 프로젝트를 구성하십시오.


FirebaseConfigure.gif 

Firebase 구성


함수 탭으로 이동하여 시작하기 버튼을 클릭하십시오.


FirebaseFunctionConfig1.png 

함수 대시 보드


Firebase 함수를 설정하는 방법에 대한 지침이 있는 대화 상자가 표시됩니다. 지역 환경으로 이동하십시오. 명령 줄 도구를 엽니다. 머신에 파이어베이스 도구를 설치하려면 아래 명령을 사용하십시오.


npm install -g firebase-tools

완료되면 firebase init 명령을 사용하여 로컬 환경에서 firebase 함수를 구성하십시오. 로컬 환경에서 firebase 함수를 초기화 할 때 다음 옵션을 선택하십시오.

  1. 이 폴더에 어떤 Firebase CLI 함수를 설정 하시겠습니까? Space를 눌러 기능을 선택한 다음 Enter를 눌러 선택 사항을 확인하십시오. => 함수 : 클라우드 함수 구성 및 배치
  2. 먼저 이 프로젝트 디렉토리를 Firebase 프로젝트와 연결해 보겠습니다. => 기존 프로젝트 사용
  3. 이 디렉토리의 기본 Firebase 프로젝트를 선택하십시오. => application_name
  4. Cloud Functions를 작성하는 데 어떤 언어를 사용 하시겠습니까? => 자바 스크립트
  5. ESLint를 사용하여 가능한 버그를 포착하고 스타일을 적용 하시겠습니까? => N
  6. 지금 npm으로 종속성을 설치 하시겠습니까? (Y / n) => Y

구성이 완료되면 다음 메시지가 표시됩니다.


✔ Firebase initialization complete!

초기화가 완료되면 디렉토리 구조가 됩니다.

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

이제 functions 디렉토리에서 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: https://console.firebase.google.com/project/todoapp-<id>/overview

Project Console> Functions로 이동하면 API의 URL을 찾을 수 있습니다. URL은 다음과 같습니다.


https://<hosting-region>-todoapp-<id>.cloudfunctions.net/helloWorld

이 URL을 복사하여 브라우저에 붙여 넣으십시오. 다음과 같은 응답이 나타납니다.

Hello from Firebase!

Firebase 기능이 올바르게 구성되었음을 확인합니다.


Express Framework를 설치하십시오. 


이제 다음 명령을 사용하여 프로젝트에 Express 프레임 워크를 설치하겠습니다.


npm i express

이제 functions 디렉토리 안에 APIs 디렉토리를 만들어 봅시다. 그 디렉토리 안에 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 함수를 통해 실행됩니다. 이제 API 디렉토리 아래의 todos.js 파일로 이동하여 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 deploy 명령을 사용하여 이것을 firebase 함수에 배포하십시오. 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> Functions로 이동하여 API의 URL을 찾으십시오. API는 다음과 같습니다.

https://<hosting-region>-todoapp-<id>.cloudfunctions.net/api

이제 브라우저로 이동하여 URL을 복사하여 붙여넣고 이 URL 끝에 / todos를 추가하십시오. 다음과 같은 결과가 나타납니다.


[
        {
            '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 콘솔에서 콘솔> 데이터베이스로 이동하십시오. Firestore를 구성하려면 아래 gif를 따르십시오.


Firestore.gif 

Firestore 구성


구성이 완료되면 콜렉션 시작 단추를 클릭하고 콜렉션 ID를 작업 관리로 설정하십시오. 다음을 클릭하면 다음과 같은 팝업이 나타납니다.

FireStore-collection.png 

데이터베이스 수동 생성


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
}

저장 버튼을 누릅니다. 컬렉션과 문서가 생성 된 것을 볼 수 있습니다. 로컬 환경으로 돌아갑니다. 필요한 firestore 패키지가 있는 firebase-admin을 설치해야 합니다. 이 명령을 사용하여 설치하십시오.


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를 작성해 보겠습니다. 함수> API 디렉토리에서 todos.js로 이동하십시오. 이전 코드를 제거하고 아래 코드를 복사하여 붙여 넣습니다.


//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 로그인 페이지가 열립니다. 로그인이 완료되면 오류 없이 작동합니다.


firebase serve 명령을 실행할 때 명령 행 도구의 로그에서 URL을 찾을 수 있습니다. 브라우저에서 이 URL을 열고 그 뒤에 / todos를 추가하십시오.

✔ functions[api]: http function initialized (http://localhost:5000/todoapp-<project-id>/<region-name>/api).

브라우저에서 다음 JSON 출력을 얻을 수 있습니다.

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

다른 API 작성 : 


이제 애플리케이션에 필요한 다른 모든 할 일 API를 작성해야 합니다.


1. 작업 관리 항목 작성 : 함수 디렉토리 아래의 index.js로 이동하십시오. 기존 getAllTodos에서 postOneTodo 메소드를 가져 오십시오. 또한 해당 방법에 POST 경로를 할당하십시오.


//index.js

const {
    ..,
    postOneTodo
} = require('./APIs/todos')

app.post('/todo', postOneTodo);

함수 디렉토리 내의 todos.js로 이동하여 기존 getAllTodos 메소드 아래에 postOneTodo 새 메소드를 추가하십시오.


//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: http://localhost:5000/todoapp-<app-id>/<region-name>/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. 작업 관리 항목 삭제 : 기능 디렉토리 아래의 index.js로 이동하십시오. 기존 postOneTodo에서 deleteTodo 메소드를 가져 오십시오. 또한 해당 메소드에 DELETE 경로를 지정하십시오.


//index.js

const {
    ..,
    deleteTodo
} = require('./APIs/todos')

app.delete('/todo/:todoId', deleteTodo);

todos.js로 이동하여 기존 postOneTodo 메소드 아래에 새 메소드 deleteTodo를 추가하십시오.


//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 serve 명령을 실행하고 우편 배달부로 이동하십시오. 새 요청을 작성하고 메소드 유형을 DELETE로 선택한 후 URL을 추가하십시오.


URL: http://localhost:5000/todoapp-<app-id>/<region-name>/api/todo/<todo-id>

METHOD: DELETE

보내기 버튼을 누르면 다음과 같은 응답이 나타납니다.


{
   "message": "Delete successfull"
}

3. Todo 항목 편집 : functions 디렉토리 아래의 index.js로 이동하십시오. 기존 deleteTodo에서 editTodo 메소드를 가져 오십시오. 또한 해당 방법에 PUT 경로를 할당하십시오.


//index.js

const {
    ..,
    editTodo
} = require('./APIs/todos')

app.put('/todo/:todoId', editTodo);

todos.js로 이동하여 기존 deleteTodo 메소드 아래에 새 메소드 editTodo를 추가하십시오.


//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 serve 명령을 실행하고 우편 배달부로 이동하십시오. 새 요청을 작성하고 메소드 유형을 PUT으로 선택한 후 URL을 추가하십시오.


URL: http://localhost:5000/todoapp-<app-id>/<region-name>/api/todo/<todo-id>

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. GET 및 업데이트 사용자 세부 사항 API.
  3. 사용자 프로필 사진 API를 업데이트하십시오.
  4. 기존 Todo API 보안

이 섹션에서 구현 된 사용자 API 코드는 이 커밋에서 찾을 수 있습니다.


이제 사용자 인증 API 구축을 시작하겠습니다. Firebase 콘솔> 인증으로 이동합니다.


FirebaseAuthentication.png 

Firebase 인증 페이지



로그인 방법 설정 버튼을 클릭하십시오. 사용자 확인을 위해 이메일과 비밀번호를 사용합니다. 이메일 / 비밀번호 옵션을 활성화하십시오.


FirebaseAuth1.png 

Firebase 설정 가입 페이지


지금 우리는 수동으로 사용자를 만들 것입니다. 먼저 Login API를 빌드 합니다. 그런 다음 Sign-Up API를 빌드 합니다.


인증 아래의 사용자 탭으로 이동하여 사용자 세부 사항을 입력 한 후 사용자 추가 단추를 클릭하십시오.


Login.png 

수동으로 사용자 추가


1. 사용자 로그인 API : 


먼저 다음 명령을 사용하여 Firebase 인증 라이브러리로 구성된 firebase 패키지를 설치해야 합니다.


npm i firebase

설치가 완료되면 기능> API 디렉토리로 이동하십시오. 여기서 users.js 파일을 만듭니다. 이제 index.js 내부에서 loginUser 메소드를 가져오고 POST 경로를 지정하십시오.


//index.js

const {
    loginUser
} = require('./APIs/users')

// Users
app.post('/login', loginUser);

프로젝트 설정> 일반으로 이동하여 다음 카드를 찾으십시오.

app.png 

Firebase 구성하기


웹 아이콘을 선택한 다음 아래 gif를 따르십시오.

project.gif 


콘솔 계속 옵션을 선택하십시오. 이 작업이 완료되면 firebase 구성을 사용하는 JSON이 표시됩니다. 함수> util 디렉토리로 이동하여 config.js 파일을 작성하십시오. 이 파일에 다음 코드를 복사하여 붙여 넣습니다.


// config.js

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

............를 Firebase 콘솔> 프로젝트 설정> 일반> 앱> Firebase SD 스 니펫> 구성에서 얻은 값으로 바꿉니다.


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 상태를 보냅니다.


이제 함수> util 디렉토리에서 validators.js를 만들어 봅시다. 이 파일에 다음 코드를 복사하여 붙여 넣습니다.


// 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: http://localhost:5000/todoapp-<app-id>/<region-name>/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의 기본 인증 메커니즘을 사용하면 이메일, 비밀번호 등의 정보 만 저장할 수 있습니다. 그러나 이 사용자가 할 일을 소유하고 있는지 식별하여 추가 읽기, 업데이트 및 삭제 작업을 수행 할 수 있도록 하려면 추가 정보가 필요합니다.


이 목표를 달성하기 위해 users라는 새로운 컬렉션을 만들려고 합니다. 이 컬렉션에는 사용자 이름을 기반으로 할 일에 매핑 될 사용자 데이터가 저장됩니다. 각 사용자 이름은 플랫폼의 모든 사용자에게 고유합니다.


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 = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[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 모듈에 이메일과 비밀번호를 보내 사용자를 만듭니다. 사용자가 성공적으로 만들어지면 사용자 자격 증명을 데이터베이스에 저장합니다.


이것으로 SignUp API가 완성되었습니다. firebase serve 명령을 실행하고 우편 배달부로 이동하십시오. 새 요청을 작성하고 메소드 유형을 POST로 선택하십시오. URL과 본문을 추가하십시오.


URL: http://localhost:5000/todoapp-<app-id>/<region-name>/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 콘솔> 데이터베이스로 이동하면 다음과 같은 결과가 표시됩니다.

database.png 

보시다시피, 사용자의 컬렉션은 하나의 문서로 성공적으로 생성되었습니다.


3. 사용자 프로필 사진 업로드 : 


사용자는 자신의 프로필 사진을 업로드 할 수 있습니다. 이를 위해 스토리지 버킷을 사용합니다. Firebase 콘솔> 저장소로 이동하여 시작하기 버튼을 클릭하십시오. 구성에 대해서는 아래 GIF를 따르십시오.


storage.gif 


이제 스토리지 아래의 규칙 탭으로 이동하여 아래 이미지에 따라 버킷 액세스 권한을 업데이트하십시오.


storageRule.png 


프로필 사진을 업로드하기 위해 busyboy라는 패키지를 사용할 것입니다. 이 패키지를 설치하려면 다음 명령을 사용하십시오.


npm i busyboy

index.js로 이동하십시오. 기존 signUpUser 메소드 아래로 uploadProfilePhoto 메소드를 가져 오십시오. 또한 해당 방법에 POST 경로를 할당하십시오.


//index.js

const auth = require('./util/auth');

const {
    ..,
    uploadProfilePhoto
} = require('./APIs/users')

app.post('/user/image', auth, uploadProfilePhoto);

여기에는 해당 계정과 연결된 사용자 만 이미지를 업로드 할 수 있도록 인증 계층을 추가했습니다. 이제 functions> utils 디렉토리에 auth.js라는 파일을 작성하십시오. 해당 파일에 다음 코드를 복사하여 붙여 넣습니다.


// 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로 이동하여 가입 방법 아래에 다음 코드를 추가하십시오.


// 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 = `https://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);
};

이것으로 Upload Profile Picture API가 완성되었습니다. firebase serve 명령을 실행하고 우편 배달부로 이동하십시오. 새 요청을 작성하고 메소드 유형을 POST로 선택하고 URL을 추가 한 후 본문 섹션에서 유형을 양식 데이터로 선택하십시오.


요청이 보호되므로 베어러 토큰도 보내야 합니다. 베어러 토큰을 보내려면 토큰이 만료 된 경우 다시 로그인하십시오. 그런 다음 Postman App> Authorization 탭> Type> Bearer Token에서 토큰 섹션에 토큰을 붙여 넣습니다.


URL: http://localhost:5000/todoapp-<app-id>/<region-name>/api/user/image

METHOD: GET

Body: { REFER THE IMAGE down below }

cover.png 



우편 배달부에서 요청 보내기 버튼을 누르면 다음과 같은 결과가 나타납니다.


{        
    "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 User Details API가 완성되었습니다. firebase serve 명령을 실행하고 우편 배달부로 이동하십시오. 새 요청을 작성하고 메소드 유형 GET을 선택한 후 URL 및 본문을 추가하십시오.


요청이 보호되므로 베어러 토큰도 보내야 합니다. 베어러 토큰을 보내려면 토큰이 만료 된 경우 다시 로그인하십시오.


URL: http://localhost:5000/todoapp-<app-id>/<region-name>/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로 이동하여 기존 getUserDetails 아래에 updateUserDetails 모듈을 추가하십시오.


// 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 업데이트 방법을 사용하고 있습니다. 이것으로 Update User Details API가 완성되었습니다. 한 번의 변경으로 위의 사용자 세부 사항 가져 오기 API와 동일한 요청 절차를 따르십시오. 여기에 요청에 본문을 추가하고 POST로 메소드를 추가하십시오.


URL: http://localhost:5000/todoapp-<app-id>/<region-name>/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. Todo API 보안 : 


선택한 사용자 만 액세스 할 수 있도록 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);

모든 API 호출에 토큰이 필요하고 특정 사용자 만 액세스 할 수 있도록 auth를 추가하여 모든 Todo 라우트를 업데이트했습니다.


그런 다음 함수> API 디렉토리 아래의 todos.js로 이동하십시오.



1. Todo API 작성 : todos.js를 열고 postOneTodo 메소드에서 다음과 같이 사용자 이름 키를 추가하십시오.

const newTodoItem = {
     ..,
     username: request.user.username,
     ..
}

2. 모든 Todos API 가져 오기 : todos.js를 열고 getAllTodos 메소드 아래에 where 절을 다음과 같이 추가하십시오.


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

firebase serve를 실행하고 GET API를 테스트하십시오. 무기명 토큰을 보내는 것을 잊지 마십시오. 여기에 다음과 같은 응답 오류가 발생합니다.

{   
    "error": 9
}

명령 행으로 이동하면 다음 행이 기록됩니다.


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

브라우저에서 이 <URL>을 열고 색인 작성을 클릭하십시오.

index.png 


색인이 작성되면 요청을 다시 보내면 다음과 같은 출력이 표시됩니다.


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

3. Todo API 삭제 : todos.js를 열고 deleteTodo 메소드에서 다음 조건을 추가하십시오. ! doc.exists 조건 아래 document.get().then() 쿼리 안에 이 조건을 추가하십시오.

..
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 및 Material UI를 구성하십시오.
  2. 로그인 및 가입 양식 작성.
  3. 빌딩 계정 섹션.

이 섹션에서 구현 된 사용자 대시 보드 코드는 이 커밋에서 찾을 수 있습니다.


1. ReactJS 및 Material UI를 설정합니다 : 


create-react-app 템플릿을 사용합니다. 응용 프로그램 개발을위한 기본 구조를 제공합니다. 설치하려면 다음 명령을 사용하십시오.

npm install -g create-react-app

함수 디렉토리가 있는 프로젝트의 루트 폴더로 이동하십시오. 다음 명령을 사용하여 프런트 엔드 응용 프로그램을 초기화하십시오.


create-react-app view

ReactJS 라이브러리 버전 v16.13.1을 사용해야 합니다.


설치가 완료되면 명령 행 로그에 다음이 표시됩니다.


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 명령을 사용하여 응용 프로그램을 실행하십시오. http : // localhost : 3000 /의 브라우저로 이동하면 다음 출력이 표시됩니다.

React1.png 


이제 불필요한 구성 요소를 모두 제거합니다. 보기 디렉토리로 이동 한 후 앞에 [제거]가있는 모든 파일을 제거하십시오. 이에 대해서는 아래의 디렉토리 트리 구조를 참조하십시오.


+-- 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로 이동하여 다음 행을 제거하십시오.


<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />

이제 src 디렉토리 아래의 App.js로 이동하여 이전 코드를 다음 코드로 바꿉니다.



import React from 'react';
function App() {
  return (
    <div>
    </div>
  );
}
export default App;

index.js로 이동하여 다음 가져 오기를 제거하십시오.


import './index.css'

App.css를 삭제하지 않았으며 이 응용 프로그램에서 사용하고 있습니다. 그러나 삭제하거나 사용하려면 자유롭게 할 수 있습니다.


http : // localhost : 3000 /의 브라우저로 이동하면 빈 화면이 출력 됩니다.


머티리얼 UI를 설치하려면 뷰 디렉토리로 이동하여 터미널에서 이 명령을 복사하여 붙여 넣습니다.


npm install @material-ui/core

머티리얼 UI 라이브러리 버전 v4.9.8을 사용해야 합니다.


2. 로그인 양식 : 


로그인 양식을 개발하려면 App.js로 이동하십시오. App.js 맨 위에 다음 가져 오기를 추가하십시오.

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

우리는 TodoApp에 경로를 할당하기 위해 스위치 및 경로를 사용하고 있습니다. 지금은 / login 라우트 만 추가하고 로그인 구성 요소를 지정합니다.

// App.js

<Router>
    <div>
       <Switch>
           <Route exact path="/login" component={login}/>
       </Switch>
    </div>
</Router>

기존 보기 디렉토리 아래에 pages 디렉토리를 작성하고 pages 디렉토리 아래에 login.js라는 파일을 작성하십시오.


우리는 login.js에 머티리얼 UI 컴포넌트와 Axios 패키지를 가져옵니다 :


// 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 클래스를 만들고 그 안에 제출 핸들러를 제출할 것입니다.


// 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 (
			<Container component="main" maxWidth="xs">
				<CssBaseline />
				<div className={classes.paper}>
					<Avatar className={classes.avatar}>
						<LockOutlinedIcon />
					</Avatar>
					<Typography component="h1" variant="h5">
						Login
					</Typography>
					<form className={classes.form} noValidate>
						<TextField
							variant="outlined"
							margin="normal"
							required
							fullWidth
							id="email"
							label="Email Address"
							name="email"
							autoComplete="email"
							autoFocus
							helperText={errors.email}
							error={errors.email ? true : false}
							onChange={this.handleChange}
						/>
						<TextField
							variant="outlined"
							margin="normal"
							required
							fullWidth
							name="password"
							label="Password"
							type="password"
							id="password"
							autoComplete="current-password"
							helperText={errors.password}
							error={errors.password ? true : false}
							onChange={this.handleChange}
						/>
						<Button
							type="submit"
							fullWidth
							variant="contained"
							color="primary"
							className={classes.submit}
							onClick={this.handleSubmit}
							disabled={loading || !this.state.email || !this.state.password}
						>
							Sign In
							{loading && <CircularProgress size={30} className={classes.progess} />}
						</Button>
						<Grid container>
							<Grid item>
								<Link href="signup" variant="body2">
									{"Don't have an account? Sign Up"}
								</Link>
							</Grid>
						</Grid>
						{errors.general && (
							<Typography variant="body2" className={classes.customError}>
								{errors.general}
							</Typography>
						)}
					</form>
				</div>
			</Container>
		);
	}
}

이 파일의 끝에 다음 내보내기를 추가하십시오.

export default withStyles(styles)(login);

firebase 함수 URL을 추가하여 다음과 같이> package.json을 봅니다.


기억하십시오 : 기존 browserslist JSON 객체 아래에 proxy라는 키를 추가하십시오. 


"proxy": "https://<region-name>-todoapp-<id>.cloudfunctions.net/api"

다음 명령을 사용하여 Axios 및 재료 아이콘 패키지를 설치하십시오.

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

App.js에 로그인 경로를 추가했습니다. login.js에서 상태를 처리하고 Axios 패키지를 사용하여 로그인 API에 게시 요청을 보내는 클래스 구성 요소를 만들었습니다. 요청이 성공하면 토큰을 저장합니다. 응답에 오류가 발생하면 단순히 UI에서 오류를 렌더링 합니다.


http : // localhost : 3000 / login의 브라우저로 이동하면 다음 로그인 UI가 표시됩니다.


LoginPage.png 

로그인 페이지


잘못된 자격 증명을 작성하거나 빈 요청을 보내면 오류가 발생합니다. 유효한 요청을 보내십시오. 개발자 콘솔> 애플리케이션으로 이동하십시오. 사용자 토큰이 로컬 스토리지에 저장되어 있음을 알 수 있습니다. 로그인에 성공하면 홈 페이지로 다시 라우팅됩니다.


loginDev.png 

구글 크롬 개발자 콘솔


3. 가입 양식 : 


가입 양식을 개발하려면 App.js로 이동하여 기존 행 구성 요소를 아래 줄로 업데이트하십시오.


// App.js

<Route exact path="/signup" component={signup}/>

가져 오는 것을 잊지 마십시오 :


// App.js

import signup from './pages/signup';

pages 디렉토리 아래에 signup.js라는 파일을 작성하십시오.


signup.js 안에 머티리얼 UI 및 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이라는 클래스를 작성하고 그 안에 제출 핸들러를 제출합니다.


// 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 (
			<Container component="main" maxWidth=