Como criar um TodoApp usando ReactJS e Firebase

Criação de conta

TodoApp Dashboard


Arquitetura de Aplicação:

Arquitetura de Aplicação

Entendendo nossos componentes:

Você pode estar se perguntando por que estamos usando o firebase nesta aplicação. Bem, ele fornece segurança Autenticação, uma Banco de dados em tempo real, uma Componente sem servidor, e um Balde de armazenamento.

Estamos usando o Express aqui para não precisarmos lidar com exceções HTTP. Nós vamos usar todos os pacotes firebase em nosso componente de funções. Isso ocorre porque não queremos tornar nosso aplicativo cliente muito grande, o que tende a retardar o processo de carregamento da interface do usuário.

Nota: Vou dividir este tutorial em quatro seções separadas. No início de cada seção, você encontrará um commit do git que possui o código desenvolvido nessa seção. Ademais, se você quiser ver o código completo, ele estará disponível neste repositório.

Seção 1: Desenvolvendo APIs de Todo

Nisso seção, nós vamos desenvolver esses elementos:

  1. Configure as funções do firebase.
  2. Instale a estrutura Express e construa APIs Todo.
  3. Configurando o Firestore como Banco de Dados.

o Todo API code implementados nesta seção podem ser encontrados neste comprometer.

Configure as funções do Firebase:

Vou ao Console do Firebase.

Firebase Console

Selecione os Adicionar projeto opção. Depois disso, siga o gif abaixo passo a passo para configurar o projeto firebase.

Configuração do Firebase

Vá para a guia de funções e clique no Iniciar botão:

Painel de funções

Você verá uma caixa de diálogo com instruções sobre Como configurar as funções do Firebase. Vá para o seu ambiente local. Abra uma ferramenta de linha de comando. Para instalar as ferramentas do firebase na sua máquina, use o comando abaixo:

 npm install -g firebase-tools

Feito isso, use o comando firebase init para configurar as funções do firebase no seu ambiente local. Selecione as seguintes opções ao inicializar a função firebase no ambiente local:

  1. Quais recursos da CLI do Firebase você deseja configurar para esta pasta? Pressione Espaço para selecionar os recursos e, em seguida, Enter para confirmar suas escolhas => Funções: configurar e implantar funções da nuvem
  2. Primeiro, vamos associar este diretório de projeto a um projeto Firebase…. => Use um projeto existente
  3. Selecione um projeto Firebase padrão para este diretório => Nome da Aplicação
  4. Qual idioma você gostaria de usar para escrever o Cloud Functions? => Javascript
  5. Deseja usar o ESLint para detectar erros prováveis ​​e aplicar estilo? => N
  6. Deseja instalar dependências com o npm agora? (S / n) => Y

Após a configuração, você receberá a seguinte mensagem:

✔ Firebase initialization complete!

Esta será a nossa estrutura de diretórios assim que a inicialização estiver concluída:

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

Agora abra o index.js no diretório de funções e copie e cole o seguinte código:

const functions = require('firebase-functions');

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

Implante o código nas funções do firebase usando o seguinte comando:

firebase deploy

Depois que a implantação estiver concluída, você receberá a seguinte linha de log no final da sua linha de comando:

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

Vou ao Console de projeto> Funções e você encontrará o URL da API. O URL ficará assim:

https://-todoapp-.cloudfunctions.net/helloWorld

Copie este URL e cole-o no navegador. Você receberá a seguinte resposta:

Hello from Firebase!

Isso confirma que nossa função Firebase foi configurada corretamente.

Instale o Express Framework:

Agora vamos instalar o Express estrutura em nosso projeto usando o seguinte comando:

npm i express

Agora vamos criar um APIs diretório dentro do funções diretório. Dentro desse diretório, criaremos um arquivo chamado todos.js. Remova tudo do index.js e copie e cole o seguinte código:

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

Nós atribuímos a função getAllTodos ao / todos rota. Portanto, todas as chamadas de API nessa rota serão executadas através da função getAllTodos. Agora vá para o todos.js no diretório APIs e aqui escreveremos a função 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);
}

Aqui declaramos um objeto JSON de amostra. Mais tarde, derivaremos isso do Firestore. Mas, por enquanto, retornaremos isso. Agora implante isso na sua função firebase usando o comando firebase deploy. Vai perguntar para obter permissão para excluir o módulo Olá Mundo – basta entrar 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

Feito isso, vá para o Console de projeto> Funções e você encontrará o URL da API. A API terá a seguinte aparência:

https://-todoapp-.cloudfunctions.net/api

Agora vá para o navegador e copie e cole o URL e adicione / todos no final deste URL. Você obterá a seguinte saída:

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

Firebase Firestore:

Usaremos um firestase do firebase como um banco de dados em tempo real para nosso aplicativo. Agora vá para o Console> Banco de Dados no Firebase Console. Para configurar o firestore, siga o gif abaixo:

Configurando o Firestore

Depois que a configuração estiver concluída, clique no ícone Iniciar coleção botão e definir ID da coleção Como todos. Clique em Avançar e você obterá o seguinte pop-up:

Criando banco de dados manualmente

Ignore a chave DocumentID. Para o campo, tipo e valor, consulte o JSON abaixo. Atualize o valor de acordo:

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

Pressione o botão Salvar. Você verá que a coleção e o documento foram criados. Volte para o ambiente local. Precisamos instalar firebase-admin que possui o pacote firestore que precisamos. Use este comando para instalá-lo:

npm i firebase-admin

Crie um diretório chamado util debaixo de funções diretório. Vá para este diretório e crie um nome de arquivo admin.js. Neste arquivo, importaremos o pacote de administração do firebase e inicializaremos o objeto de banco de dados do firestore. Exportaremos isso para que outros módulos pode usá-lo.

//admin.js

const admin = require('firebase-admin');

admin.initializeApp();

const db = admin.firestore();

module.exports = { admin, db };

Agora vamos escrever uma API para buscar esses dados. Vou ao todos.js debaixo de funções> APIs diretório. Remova o código antigo e copie e cole o código abaixo:

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

Aqui, estamos buscando todos os todos do banco de dados e encaminhando-os para o cliente em uma lista.

Você também pode executar o aplicativo localmente usando firebase serve comando em vez de implantá-lo sempre. Ao executar esse comando, você pode receber um erro em relação às credenciais. Para corrigi-lo, siga as etapas mencionadas abaixo:

  1. Vou ao Configurações do projeto (Ícone de configurações no lado superior esquerdo)
  2. Vou ao guia contas de serviço
  3. Lá embaixo, haverá a opção de Gerando uma nova chave. Clique nessa opção e ele fará o download de um arquivo com uma extensão JSON.
  4. Precisamos exportar essas credenciais para nossa sessão de linha de comando. Use o comando abaixo para fazer isso:

export GOOGLE_APPLICATION_CREDENTIALS="/home/user/Downloads/[FILE_NAME].json"

Depois disso, execute o comando firebase serve. Se você ainda receber o erro, use o seguinte comando: firebase login --reauth. Ele abrirá a página de login do Google em um navegador. Uma vez feito o login, ele funcionará sem nenhum erro.

Você encontrará uma URL nos logs da sua ferramenta de linha de comando quando executar um comando servir da base de firmas. Abra este URL no navegador e acrescente /todos depois disso.

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

Você obterá a seguinte saída JSON no seu navegador:

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

Escrevendo outras APIs:

É hora de escrever todas as outras APIs de tarefas necessárias para nosso aplicativo.

  1. Criar item Todo: Vou ao index.js no diretório de funções. Importe o método postOneTodo no getAllTodos existente. Ademais, atribua a rota POST a esse método.

//index.js

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

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

Vou ao todos.js dentro do diretório de funções e adicione um novo método postOneTodo sob o existente getAllTodos método.

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

Neste método, estamos adicionando um novo Todo ao nosso banco de dados. Se os elementos do nosso corpo estiverem vazios, retornaremos uma resposta de 400 ou então adicionaremos os dados.

Execute o comando firebase serve e abra o aplicativo carteiro. Crie uma nova solicitação e selecione o tipo de método como POSTAR. Adicione a URL e um corpo do tipo JSON.

URL: http://localhost:5000/todoapp-//api/todo

METHOD: POST

Body: {
   "title":"Hello World",
   "body": "We are writing this awesome API"
}

Pressione o botão enviar e você receberá a seguinte resposta:

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

2) Excluir item Todo: Vou ao index.js no diretório de funções. Importe o método deleteTodo sob o postOneTodo existente. Ademais, atribua a rota DELETE a esse método.

//index.js

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

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

Vou ao todos.js e adicione um novo método deleteTodo sob o existente postOneTodo método.

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

Nesse método, estamos excluindo um Todo do nosso banco de dados. Execute o comando serve do firebase e vá para o carteiro. Crie uma nova solicitação, selecione o tipo de método como EXCLUIR e adicione o URL.

URL: http://localhost:5000/todoapp-//api/todo/

METHOD: DELETE

Pressione o botão enviar e você receberá a seguinte resposta:

{
   "message": "Delete successfull"
}

3) Editar item Todo: Vou ao index.js no diretório de funções. Importe o método editTodo sob o deleteTodo existente. Ademais, atribua a rota PUT a esse método.

//index.js

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

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

Vou ao todos.js e adicione um novo método editTodo sob o existente deleteTodo método.

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

Nesse método, estamos editando um Todo do nosso banco de dados. Lembre-se de que não estamos permitindo que o usuário edite os campos todoId ou createdAt. Execute o comando serve do firebase e vá para o carteiro. Crie uma nova solicitação, selecione o tipo de método como COLOCAR, e adicione o URL.

URL: http://localhost:5000/todoapp-//api/todo/

METHOD: PUT

Pressione o botão enviar e você receberá a seguinte resposta:

{  
   "message": "Updated successfully"
}

Estrutura de diretório até agora:

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

Com isso, concluímos a primeira seção do aplicativo. Você pode tomar um café, fazer uma pausa e, depois disso, trabalharemos no desenvolvimento das APIs do usuário.

Seção 2: Desenvolvendo APIs de usuário

Nisso seção, nós vamos desenvolver esses componentes:

  1. API de autenticação do usuário (logon e inscrição).
  2. API GET e atualização de detalhes do usuário.
  3. Atualize a API da imagem do perfil do usuário.
  4. Protegendo a API Todo existente.

O código da API do usuário implementado nesta seção pode ser encontrado neste comprometer.

Então, vamos começar a criar a API de autenticação do usuário. Vou ao Console Firebase> Autenticação.

Página de autenticação do Firebase

Clique no Configuração método de login botão. Usaremos email e senha para validação do usuário. Ative o Senha do e-mail opção.

Página de configuração do Firebase Setup

No momento, criaremos manualmente nosso usuário. Primeiro, criaremos a API de login. Depois disso, criaremos a API de inscrição.

Vá para a guia Usuários em Autenticação, preencha os detalhes do usuário e clique no Adicionar usuário botão.

Adicionando usuário manualmente

1. API de login do usuário:

Primeiro, precisamos instalar o firebase pacote, que consiste no Biblioteca de autenticação do Firebase, usando o seguinte comando:

npm i firebase

Quando a instalação estiver concluída, vá para o funções> APIs diretório. Aqui vamos criar um users.js Arquivo. Agora dentro index.js nós importamos um método loginUser e atribuímos a rota POST a ele.

//index.js

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

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

Vou ao Configurações do projeto> Geral e lá você encontrará o seguinte cartão:

Obtendo a configuração do Firebase

Selecione o ícone da Web e siga o gif abaixo:

Selecione os continue para o console opção. Feito isso, você verá um JSON com a configuração do firebase. Vou ao funções> util diretório e crie um config.js Arquivo. Copie e cole o seguinte código neste arquivo:

// config.js

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

Substituir ............ com os valores que você obtém Console do Firebase> Configurações do projeto> Geral> seus aplicativos> Snebet do Firebase SD> config.

Copie e cole o seguinte código no diretório users.js Arquivo:

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

Aqui estamos usando uma base de fogo signInWithEmailAndPassword módulo para verificar se as credenciais enviadas pelo usuário estão corretas. Se eles estiverem certos, enviaremos o token desse usuário ou um status 403 com uma mensagem “credenciais incorretas”.

Agora vamos criar validators.js debaixo de funções> util diretório. Copie e cole o seguinte código neste arquivo:

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

Com isso nossa LoginAPI está completo. Execute o firebase serve comando e vá para o carteiro. Crie uma nova solicitação, selecione o tipo de método como POSTARe adicione o URL e o corpo.

URL: http://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"
}

Aperte o botão enviar solicitação no carteiro e você obterá a seguinte saída:

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

Usaremos esse token em uma parte futura para obtenha os detalhes do usuário. Lembre-se de que este token expira em 60 minutos. Para gerar um novo token, use esta API novamente.

2. API de inscrição do usuário:

O mecanismo de autenticação padrão do firebase apenas permite que você armazene informações como email, senha etc. Mas precisamos de mais informações para identificar se esse usuário é o proprietário de todo o trabalho para que ele possa executar operações de leitura, atualização e exclusão.

Para atingir esse objetivo, criaremos uma nova coleção chamada Comercial. Nesta coleção, armazenaremos os dados do usuário que serão mapeados para o todo com base no nome de usuário. Cada nome de usuário será exclusivo para todos os usuários na plataforma.

Vou ao index.js. Importamos um método signUpUser e atribuímos a rota POST a ele.

//index.js

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

app.post('/signup', signUpUser);

Agora vá para o validators.js e adicione o seguinte código abaixo do validateLoginData método.

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

Agora vá para o users.js e adicione o seguinte código abaixo do loginUser módulo.

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

Nós validamos nossos dados de usuário e, depois disso, enviamos um e-mail e senha para o firebase createUserWithEmailAndPassword módulo para criar o usuário. Depois que o usuário é criado com sucesso, salvamos as credenciais do usuário no banco de dados.

Com isso nossa API de inscrição está completo. Execute o firebase serve comando e vá para o carteiro. Crie uma nova solicitação, selecione o tipo de método como POSTAR. Adicione o URL e o corpo.

URL: http://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"
}

Aperte o botão enviar solicitação no carteiro e você obterá a seguinte saída:

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

Agora vá para o Console do Firebase> Banco de Dados e você verá a seguinte saída:

Como você pode ver, a coleção de nossos usuários foi criada com sucesso com um documento.

3. Carregar imagem do perfil de usuário:

Nossos usuários poderão fazer upload de suas fotos de perfil. Para conseguir isso, usaremos o balde de armazenamento. Vou ao Console do Firebase> Armazenamento e clique no iniciar botão. Siga o GIF abaixo para a configuração:

Agora vá para o Regras na guia Armazenamento e atualize a permissão para o acesso ao bucket, conforme a imagem abaixo:

Para fazer upload da foto do perfil, usaremos o pacote chamado busyboy. Para instalar este pacote, use o seguinte comando:

npm i busyboy

Vamos para index.js. Importe o método uploadProfilePhoto abaixo do método signUpUser existente. Atribua também a rota POST a esse método.

//index.js

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

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

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

Aqui, adicionamos uma camada de autenticação para que apenas um usuário associado a essa conta possa fazer upload da imagem. Agora crie um arquivo chamado auth.js no funções> utils diretório. Copie e cole o seguinte código nesse arquivo:

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

Aqui estamos usando o firebase VerifiqueIdToken módulo para verificar o token. Depois disso, decodificamos os detalhes do usuário e os transmitimos na solicitação existente.

Vou ao users.js e adicione o seguinte código abaixo do signup método:

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

Com isso nossa Carregar API de imagem de perfil está completo. Execute o firebase serve comando e vá para o carteiro. Crie uma nova solicitação, selecione o tipo de método como POSTAR, adicione o URL e, na seção corpo, selecione o tipo como dados do formulário.

A solicitação está protegida, então você precisará enviar o token do portador Ademais. Para enviar o token do portador, efetue login novamente se o token expirou. Depois disso em Carteiro App> guia Autorização> Tipo> Token de portador e, na seção token, cole o token.

URL: http://localhost:5000/todoapp-//api/user/image

METHOD: GET

Body: { REFER THE IMAGE down below }

Aperte o botão enviar solicitação no carteiro e você obterá a seguinte saída:

{        
    "message": "Image uploaded successfully"
}

4. Obtenha detalhes do usuário:

Aqui estamos buscando os dados de nosso usuário no banco de dados. Vou ao index.js e importe o método getUserDetail e atribua a rota GET a ele.

// index.js

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

app.get('/user', auth, getUserDetail);

Agora vá para o users.js e adicione o seguinte código após o uploadProfilePhoto módulo:

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

Estamos usando a base do firebase doc (). get () módulo para derivar os detalhes do usuário. Com isso nossa API de detalhes do usuário GET está completo. Execute o firebase serve comando e vá para o carteiro. Crie uma nova solicitação, selecione o tipo de método: OBTERe adicione o URL e o corpo.

A solicitação está protegida, então você precisará enviar o token do portador Ademais. Para enviar o token do portador, efetue login novamente se o token expirou.

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

Aperte o botão enviar solicitação no carteiro e você obterá a seguinte saída:

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

5. Atualize os detalhes do usuário:

Agora vamos adicionar a funcionalidade para atualizar os detalhes do usuário. Vou ao index.js e copie e cole o seguinte código:

// index.js

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

app.post('/user', auth, updateUserDetails);

Agora vá para o users.js e adicione o updateUserDetails módulo abaixo do existente 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"
        });
    });
}

Aqui estamos usando o firebase atualizar método. Com isso nossa Atualizar API de detalhes do usuário está completo. Siga o mesmo procedimento para uma solicitação, como na API Obter detalhes do usuário acima, com uma alteração. Inclua o corpo na solicitação aqui e o método como POST.

URL: http://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
}

Aperte o botão enviar solicitação no carteiro e você obterá a seguinte saída:

{
    "message": "Updated successfully"
}

6. Protegendo APIs de Todo:

Para proteger a API Todo, para que somente o usuário escolhido possa acessá-la, faremos algumas alterações em nosso código existente. Em primeiro lugar, atualizaremos nossa index.js do seguinte modo:

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

Atualizamos todos os Todo rotas adicionando auth para que todas as chamadas da API exijam um token e só possam ser acessadas pelo usuário específico.

Depois disso, vá para o todos.js debaixo de funções> APIs diretório.

  1. Criar API Todo: Abra o todos.js e sob o postOneTodo adicione a chave de nome de usuário da seguinte maneira:

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

2) API GET All Todos: Abra o todos.js e sob o getAllTodos Adicione a cláusula where da seguinte maneira:

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

Execute o serviço firebase e teste nossa API GET. Não se esqueça de enviar o token do portador. Aqui você receberá um erro de resposta da seguinte maneira:

{   
    "error": 9
}

Vá para a linha de comando e você verá as seguintes linhas registradas:

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

Abra isto no navegador e clique em criar índice.

Depois que o índice for criado, envie a solicitação novamente e você obterá a seguinte saída:

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

3) Excluir API Todo: Abra o todos.js e sob o deleteTodo método adicione a seguinte condição. Adicione esta condição dentro do document.get (). then () consulta abaixo do ! doc.exists condição.

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

Estrutura de diretório até agora:

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

Com isso, concluímos o back-end da API. Faça uma pausa, tome um café e, depois disso, começaremos a criar o front end do nosso aplicativo

Seção 3: Painel do usuário

Nisso seção, nós vamos desenvolver esses componentes:

  1. Configure o ReactJS e a interface do usuário do material.
  2. Criação de Login e Formulário de Inscrição.
  3. Seção Conta de construção.

O código do Painel do Usuário implementado nesta seção pode ser encontrado neste comprometer.

1. Configure o ReactJS e a interface do usuário do material:

Usaremos o modelo create-react-app. Ele nos fornece uma estrutura fundamental para o desenvolvimento do aplicativo. Para instalá-lo, use o seguinte comando:

npm install -g create-react-app

Vá para a pasta raiz do projeto onde o diretório de funções está presente. Inicialize nosso aplicativo front-end usando o seguinte comando:

create-react-app view

Lembre-se de usar a versão v16.13.1 do a biblioteca ReactJS.

Depois que a instalação estiver concluída, você verá o seguinte nos logs da linha de comando:

cd view
  npm start
Happy hacking!

Com isso, configuramos nosso aplicativo React. Você obterá a seguinte estrutura de diretórios:

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

Agora execute o aplicativo usando o comando npm start . Vá para o navegador em http://localhost:3000/ e você verá a seguinte saída:

Agora vamos remover todos os componentes desnecessários. Vá para o diretório view e remova todos os arquivos que tem [ Remove ] na frente deles. Para isso, consulte a estrutura da árvore de diretórios abaixo.

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

Vamos para index.html no diretório público e remova as seguintes linhas:



Agora vá para o App.js no diretório src e substitua o código antigo pelo seguinte código:

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

Vou ao index.js e remova a seguinte importação:

import './index.css'

Eu não apaguei o App.css nem estou usando-o nesta aplicação. Mas se você deseja excluir ou usá-lo, você é livre para fazer isso.

Vá para o navegador em http://localhost:3000/ e você verá uma tela em branco.

Para instalar a interface do usuário do material, vá para o diretório view e copie e cole este comando no terminal:

npm install @material-ui/core

Lembre-se de usar a versão v4.9.8 da biblioteca de UI do material.

2. Formulário de login:

Para desenvolver o formulário de login, vá para App.js. No topo de App.js adicione as seguintes importações:

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

Nós estamos usando Interruptor e Rota para atribuir rotas para o nosso TodoApp. No momento, adicionaremos apenas o /Conecte-se rotear e atribuir um componente de login a ele.

// App.js


    

Crie um Páginas diretório no diretório existente Visão diretório e um arquivo chamado login.js debaixo de Páginas diretório.

Importaremos os componentes da UI do material e o pacote Axios no diretório 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';

Adicionaremos os seguintes estilos à nossa página de login:

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

Criaremos uma classe chamada login, que possui um formulário e enviará um manipulador dentro dele.

// 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
{"Don't have an account? Sign Up"} {errors.general && ( {errors.general} )}
); } }

No final deste arquivo, adicione a seguinte exportação:

export default withStyles(styles)(login);

Adicione nosso URL de funções do firebase a ver> package.json do seguinte modo:

Lembre-se: adicione uma chave chamada procuração abaixo do objeto JSON da lista de navegadores existente

"proxy": "https://-todoapp-.cloudfunctions.net/api"

Instale o Axios e ícone material pacote usando os seguintes comandos:

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

Adicionamos uma rota de login em App.js. No login.js criamos um componente de classe que lida com o estado, envia a solicitação de postagem para a API de logon usando o pacote Axios. Se a solicitação for bem-sucedida, armazenamos o token. Se recebermos erros na resposta, simplesmente os renderizamos na interface do usuário.

Vá para o navegador em http://localhost:3000/login e você verá a seguinte interface do usuário de login.

Página de login

Tente preencher credenciais incorretas ou enviar uma solicitação vazia e você obterá os erros. Envie uma solicitação válida. Vou ao Console do desenvolvedor> Aplicativo. Você verá que o token de usuário é armazenado no armazenamento local. Quando o login for bem-sucedido, seremos roteados de volta para a página inicial.

Console do desenvolvedor do Google Chrome

3. Formulário de inscrição:

Para desenvolver o formulário de inscrição, vá para App.js e atualize o existente Route componente com a linha abaixo:

// App.js

Não se esqueça de importar:

// App.js

import signup from './pages/signup';

Crie um arquivo chamado signup.js debaixo de diretório de páginas.

Dentro do signup.js, importaremos o pacote Material UI e 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';

Adicionaremos os seguintes estilos à nossa página de inscrição:

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

Criaremos uma classe chamada signup que possui um formulário e enviaremos um manipulador dentro dele.

// 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
Already have an account? Sign in
); } }

No final deste arquivo, adicione a seguinte exportação:

export default withStyles(styles)(signup);

A lógica do componente de inscrição é a mesma do componente de login. Vá para o navegador em http://localhost:3000/signup e você verá a seguinte interface do usuário de inscrição. Quando a inscrição for bem-sucedida, seremos roteados de volta para a página inicial.

Formulário de inscrição

Tente preencher credenciais incorretas ou enviar uma solicitação vazia e você obterá os erros. Envie uma solicitação válida. Vou ao Console do desenvolvedor> Aplicativo. Você verá que o token de usuário é armazenado no armazenamento local.

Console do desenvolvedor do Chrome

4. Seção da conta:

Para criar a página da conta, primeiro precisamos criar nosso Pagina inicial de onde vamos carregar o seção da conta. Vou ao App.js e atualize a seguinte rota:

// App.js

Não esqueça a importação:

// App.js

import home from './pages/home';

Crie um novo arquivo chamado home.js . Este arquivo será o índice do nosso aplicativo. As seções Conta e Todo são carregadas nesta página com base no clique do botão.

Importe os pacotes Material UI, pacote Axios, nossa conta personalizada, componentes de tarefas e middleware de autenticação.

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

Definiremos nossa drawerWidth da seguinte maneira:

const drawerWidth = 240;

Adicionaremos o seguinte estilo à nossa home page:

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

Criaremos uma classe chamada home. Essa classe terá uma chamada de API para obter a imagem do perfil do usuário, nome e sobrenome. Também terá lógica para escolher qual componente exibir, Todo ou Conta:

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

Aqui no código, você verá que authMiddleWare(this.props.history); é usado. Esse middleware verifica se o authToken é nulo. Se sim, ele empurrará o usuário de volta para o login.js. Isso é adicionado para que nosso usuário não possa acessar o / rota sem inscrição ou login. No final deste arquivo, adicione a seguinte exportação:

export default withStyles(styles)(home);

Agora você está se perguntando o que esse código home.js faz?

{this.state.render ? : }

Ele está verificando o estado de renderização que estamos definindo no clique do botão. Vamos criar o diretório de componentes e, sob esse diretório, criar dois arquivos: account.js e todo.js.

Vamos criar um diretório chamado util e arquivo chamado auth.js nesse diretório. Copie e cole o seguinte código em auth.js :

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

Por um tempo dentro do todo.js arquivo, vamos escrever uma classe que renderiza o texto Olá, estou todo. Trabalharemos em todos os nossos na próxima seção:

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

Agora é hora da seção da conta. Importe o utilitário Material UI, clsx, axios e authmiddleWare em nosso 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';

Adicionaremos o seguinte estilo à nossa página de Conta:

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

Criaremos um componente de classe chamado conta. Por enquanto, basta copiar e colar o seguinte código:

// 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} {this.state.imageError ? (
{' '} Wrong Image Format || Supported Format are PNG and JPG
) : ( false )}

); } } }

No final deste arquivo, adicione a seguinte exportação:

export default withStyles(styles)(account);

No account.js existem muitos componentes usados. Primeiro, vamos ver como o nosso aplicativo se parece. Depois disso, explicarei todos os componentes usados ​​e por que eles são usados.

Vá para o navegador e, se o seu token expirar, ele o redirecionará para o login página. Adicione seus detalhes e faça login novamente. Depois de fazer isso, vá para a guia Conta e você encontrará a seguinte interface do usuário:

Seção Conta

Existem 3 manipuladores na seção Conta:

  1. componentWillMount: Este é o método do ciclo de vida incorporado do React. Estamos usando-o para carregar os dados antes do ciclo de vida da renderização e atualizar nossos valores de estado.
  2. ProfilePictureUpdate: Este é o manipulador personalizado que estamos usando, para que, quando o usuário clicar no botão Carregar foto, ele envie os dados para um servidor e recarregue a página para mostrar a nova foto do perfil do usuário.
  3. updateFormValues: Este também é nosso manipulador personalizado para atualizar os detalhes do usuário. Aqui, o usuário pode atualizar seu nome, sobrenome e país. Não permitimos atualizações de e-mail e nome de usuário porque nossa lógica de back-end depende dessas chaves.

Além desses três manipuladores, é uma página de formulário com estilo em cima. Aqui está a estrutura de diretórios até este ponto dentro da pasta view:

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

Com isso, concluímos o Painel da conta. Agora vá tomar um café, faça uma pausa e, na próxima seção, criaremos o Todo Dashboard.

Seção 4: Painel Todo

Nisso seção, vamos desenvolver a interface do usuário para esses recursos do Painel Todos:

  1. Adicione um Todo:
  2. Obter todos os todos:
  3. Excluir um todo
  4. Editar um todo
  5. Obter um todo
  6. Aplicando Tema

O código Todo Dashboard implementado nesta seção pode ser encontrado neste comprometer.

Vamos para todos.js debaixo de componentes diretório. Adicione as seguintes importações às importações existentes:

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

Também precisamos adicionar os seguintes elementos CSS nos componentes de estilo existentes:

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

Adicionaremos a transição para a caixa de diálogo pop-up:

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

Remova a classe todo existente e copie e cole a seguinte classe:

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.todos.map((todo) => ( {todo.title} {dayjs(todo.createdAt).fromNow()} {`${todo.body.substring(0, 65)}`} ))} {this.state.title}
); } } }

No final deste arquivo, adicione a seguinte exportação:

export default withStyles(styles)(todo);

Primeiro entenderemos como nossa interface do usuário funciona e depois entenderemos o código. Vá para o navegador e você obterá a seguinte interface do usuário:

Todo Dashboard

Clique no botão Adicionar no canto inferior direito e você verá a seguinte tela:

Adicionar Todo

Adicione o título e os detalhes do Todo e pressione o botão enviar. Você verá a seguinte tela:

Todo Dashboard

Depois disso, clique no botão Visualizar e você poderá ver todos os detalhes do Todo:

Ver Todo Todo

Clique no botão Editar e você poderá editar o todo:

Editar Todo

Clique no botão Excluir e você poderá excluir o Todo. Agora, como estamos cientes de como o Dashboard funciona, entenderemos os componentes usados ​​nele.

1. Adicione Todo: Para implementar o add todo, usaremos o Componente de diálogo da interface do usuário do material. Este componente implementa uma funcionalidade de gancho. Como estamos usando as classes, removeremos essa funcionalidade.

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

Fora isso, também alteraremos o posicionamento do botão Adicionar todo.

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

Agora, substituiremos a tag da lista por um formulário dentro deste Diálogo. Isso nos ajudará a adicionar o novo todo.

// Show Edit or Save depending on buttonType state
{this.state.buttonType === 'Edit' ? 'Save' : 'Submit'}

// Our Form to add a todo
// TextField here // TextField here

o handleSubmit consiste em lógica para ler o buttonType Estado. Se o estado for uma sequência vazia (“”) em seguida, ele será postado na API Adicionar Todo. Se o estado é um Edit então, nesse cenário, ele atualizará o Editar Todo.

2. Obtenha Todos: Para exibir todos, usaremos o Grid container e dentro dela, colocamos o Grid item . Dentro disso, usaremos um Card componente para exibir os dados.


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

Usamos o mapa para exibir o item de tarefa à medida que a API os envia em uma lista. Usaremos o ciclo de vida componentWillMount para obter e definir o estado antes da execução da renderização. Existem 3 botões ( visualizar, editar e excluir ), portanto, precisaremos de três manipuladores para lidar com a operação quando o botão for clicado. Aprenderemos sobre esses botões em suas respectivas subseções.

3. Edite Todo: Para a edição de todo, estamos reutilizando o código pop-up de diálogo usado em adicionar todo. Para diferenciar os cliques no botão, estamos usando um buttonType Estado. Para Adicionar Todo o buttonType estado é (“”) enquanto para editar todo é Edit.

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

No handleSubmit método, lemos o buttonType estado e, em seguida, envie a solicitação adequadamente.

4. Excluir Todo: Quando esse botão é clicado, enviamos o objeto todo para nosso deleteTodoHandler e, em seguida, envia a solicitação para o back-end.

5. Visualizar Todo: Ao mostrar os dados, nós os truncamos para que o usuário tenha uma idéia do que é o todo. Mas se um usuário quiser saber mais sobre isso, precisará clicar no botão Visualizar.

Para isso, usaremos o Diálogo personalizado. Dentro disso, usamos DialogTitle e DialogContent. Ele exibe nosso título e conteúdo. No DialougeContent, usaremos o formulário para exibir o conteúdo que o usuário postou. (Esta é uma solução que eu achei que há muitas e você pode experimentar outra.)

// 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. Aplicando o Tema: Este é o último passo da nossa aplicação. Vamos aplicar um tema em nossa aplicação. Para isso, estamos usando createMuiTheme e ThemeProvider da interface do usuário do material. Copie e cole o seguinte código em 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.
        
    );
}

Perdemos a aplicação de um tema em nosso botão todo.js no CardActions . Adicione a etiqueta colorida ao botão visualizar, editar e excluir.

Vá para o navegador e você descobrirá que tudo é o mesmo, exceto que o aplicativo tem uma cor diferente.

TodoApp depois de aplicar o tema

E nós terminamos! Criamos um TodoApp usando ReactJS e Firebase. Se você construiu todo o caminho até esse ponto, parabéns muito grandes por essa conquista.

Sinta-se à vontade para se conectar comigo no Twitter e Github.