Introdução
Neste post vou te mostrar como criar o cadastro, autenticação de usuário e o vínculo dos usuários com as tarefas.
Este é uma continuação do Projeto ToDo List com VueJS, caso você queira pegar o código fonte atual do projeto, clique aqui. Ou se preferir ver a playlist com o desenvolvimento do projeto, clique aqui.
Vou implementar a tela de cadastro, para que um novo usuário consiga se registrar.
A autenticação para que consiga acessar o sistema.
E o vínculo do usuário com as tarefas, para quando o usuário se logar, ver somente as suas tarefas.
Cadastro de usuário
A primeira etapa será criar a model de user, que ficará em /src/models/UsersModel.js
import Model from "./Model";
export default class Users extends Model {
resource() {
return "users";
}
}
Agora vou criar o componente em /src/views/Register.vue
.
Ele vai ter uma estrutura parecida com a do Login, porém vou inverter a ordem das colunas, somente para diferenciar da tela de Login.
Vou utilizar uma imagem diferente também, então vou no unDraw, pesquisei por create e peguei a primeira imagem.
Salvei a imagem no computador, troquei de nome e movi para a pasta /src/assets/images/register.vue
O formulário terá quatro campos, sendo eles de Nome, E-mail, Senha e Confirmação de senha.
Criei os quatro campos e ajustei a validação, para fazer a validação do e-mail e a validação da senha e confirmação de senha.
Depois disso criei o método register, que valida os dados, cria um novo usuário e redireciona para a tela de login.
Segue o código fonte completo do Register.vue
<template>
<b-row class="vh-100 vw-100 row-login">
<b-col
sm="7"
class="side-login d-flex justify-content-center align-items-center"
>
<img src="../assets/images/register.svg" class="img-register" />
</b-col>
<b-col
sm="5"
class="d-flex justify-content-center align-items-center right-register"
>
<div class="col-8">
<h2 class="text-center mb-5 title-login">Faça o seu cadastro</h2>
<b-form>
<b-form-group label="Nome" label-for="name">
<b-form-input
id="name"
type="text"
placeholder="João Silva"
autocomplete="off"
v-model.trim="$v.form.name.$model"
:state="getValidation('name')"
></b-form-input>
</b-form-group>
<b-form-group label="E-mail" label-for="email">
<b-form-input
id="email"
type="email"
placeholder="joaosilva@email.com"
autocomplete="off"
v-model.trim="$v.form.email.$model"
:state="getValidation('email')"
></b-form-input>
</b-form-group>
<b-form-group label="Senha" label-for="password">
<b-form-input
id="password"
type="password"
placeholder="Digite aqui a sua senha"
v-model.trim="$v.form.password.$model"
:state="getValidation('password')"
></b-form-input>
</b-form-group>
<b-form-group label="Confirme sua senha" label-for="confirmPassword">
<b-form-input
id="confirmPassword"
type="password"
placeholder="Confirme sua senha"
v-model.trim="$v.form.confirmPassword.$model"
:state="getValidation('confirmPassword')"
></b-form-input>
</b-form-group>
<b-button type="button" variant="primary" block @click="register"
><i class="fas fa-sign-in-alt"></i> Cadastrar</b-button
>
<hr />
<b-button
type="button"
variant="outline-secondary"
block
@click="goToLogin"
><i class="fas fa-arrow-left"></i> Voltar</b-button
>
</b-form>
</div>
</b-col>
</b-row>
</template>
<script>
import { required, minLength, email, sameAs } from "vuelidate/lib/validators";
import UsersModel from "@/models/UsersModel";
import ToastMixin from "@/mixins/toastMixin.js";
export default {
mixins: [ToastMixin],
data() {
return {
form: {
name: "",
email: "",
password: "",
confirmPassword: "",
},
};
},
validations: {
form: {
name: {
required,
minLength: minLength(3),
},
email: {
required,
email,
},
password: {
required,
minLength: minLength(6),
},
confirmPassword: {
required,
sameAsPassword: sameAs('password')
}
},
},
methods: {
register() {
this.$v.$touch();
if (this.$v.$error) return;
const user = new UsersModel(this.form);
user.save();
this.showToast("success", "Sucesso!", "Usuário criado com sucesso");
this.goToLogin();
},
getValidation(field) {
if (this.$v.form.$dirty === false) {
return null;
}
return !this.$v.form[field].$error;
},
goToLogin() {
this.$router.push({ name: "login" });
},
},
};
</script>
<style>
*,
*:after,
*:before {
margin: 0;
padding: 0;
box-sizing: border-box;
text-decoration: none;
}
.row-login {
margin-left: 0 !important;
}
.img-register {
width: 600px;
height: 600px;
}
.right-register {
background-color: #f2f2f2;
}
.title-login {
font-weight: bold;
}
</style>
Vou registrar esse componente dentro do vue-router para poder acessar a rota /register
e mostrar esse componente.
import Vue from 'vue'
import VueRouter from 'vue-router'
import List from '../views/List.vue'
import Form from '../views/Form.vue'
import Login from '../views/Login.vue'
import Register from '../views/Register.vue'
Vue.use(VueRouter)
const routes = [
{
path: '/',
name: 'list',
component: List
},
{
path: '/form',
name: 'form',
component: Form
},
{
path: '/login',
name: 'login',
component: Login
},
{
path: '/register',
name: 'register',
component: Register
},
]
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
export default router
No Login.vue
, vou criar um método goToRegister
, e vou utilizar ele no botão “não tenho conta” para quando o usuário clicar nesse botão, redirecionar para a tela de registro.
<!-- Login.vue -->
<b-button
type="button"
variant="outline-secondary"
block
@click="goToRegister"
><i class="fas fa-user-plus"></i> Não tenho conta</b-button>
//Login.vue
goToRegister() {
this.$router.push({ name: "register" });
}
Ao acessar a rota /register
, ou clicar no botão “não tenho conta” na tela de login será mostrado o componente de cadastro.
Autenticação
Com o usuário cadastrado, será possível realizar a autenticação dele.
No login será preciso buscar o usuário, caso ele exista vou verificar se a senha digitada é igual a senha salva.
Caso seja, vou salvar esse usuário no localStorage
, para conseguir identificar que ele está logado e depois ter o id dele para salvar nas tarefas.
Também vou fazer o redirecionamento do usuário para a lista de tarefas.
O método de autenticação fica assim:
//Login.vue
async login() {
this.$v.$touch();
if (this.$v.$error) return;
let user = await UsersModel.params({email: this.form.email}).get();
if(!user || !user[0] || !user[0].email) {
this.showToast("danger", "Erro!", "Usuário e/ou senha incorretos");
this.clearForm();
return;
}
user = user[0];
if(user.password !== this.form.password) {
this.showToast("danger", "Erro!", "Usuário e/ou senha incorretos");
this.clearForm();
return;
}
localStorage.setItem('authUser', JSON.stringify(user));
this.$router.push({name: "list"});
},
clearForm() {
this.form = {
email: "",
password: ""
}
},
Adicionei um método chamado clearForm
, caso o usuário erre os dados de autenticação, uso esse método para limpar o formulário.
Vou implementar agora a proteção das rotas privadas, que vão ser a de listagem e cadastro das tarefas.
Para isso vou alterar o index.js
do vue-router.
Nele vou fazer uma função que vai ser executada antes do usuário entrar em qualquer rota.
E nessa função vou verificar, se não é tela de login ou tela de cadastro, e se ele já está com autenticação.
Caso não esteja com a autenticação e esteja tentando acessar a listagem ou cadastro, vou redirecionar para a tela de login.
//index.js
router.beforeEach((to, from, next) => {
if(to.name !== 'login' && to.name !== 'register' && !localStorage.getItem('authUser')) {
next({ name: 'login' });
} else {
next();
}
})
Agora se o usuário tentar acessar a tela de listagem ou de cadastro de tarefas, sem estar com a autenticação, irá ser redirecionado para o login.
Vincular usuário com as tarefas
Nesse ponto, o usuário já pode se cadastrar no sistema, e também realizar a sua autenticação.
Agora vou vincular cada tarefa a um usuário, para que quando ele acesse o sistema, ele veja somente as tarefas dele.
No Form.vue, no data
do componente, vou recuperar o id que estará no localStorage
, e vou colocar junto dos campos que são preenchidos no formulário.
Assim quando o usuário submeter o formulário, irá enviar o seu id junto com os dados da tarefa, salvando assim seu id na tarefa.
//Form.vue
data() {
return {
form: {
subject: "",
description: "",
status: Status.OPEN,
dateOverdue: "",
userId: JSON.parse(localStorage.getItem('authUser')).id
},
...
};
},
Se já tiver tarefas cadastradas, você pode excluir todas manualmente, ou colocar o userId
em cada uma.
As próximas tarefas cadastradas ficarão com o userId
.
Com o userId
para cada tarefa, vou alterar o List.vue
, fazendo com que a busca das tarefas, procure somente as tarefas que contém o userId
do usuário logado.
E cada usuário terá acesso somente as suas tarefas.
Criei um novo método para isolar toda a lógica de filtro, que agora vai ser sempre passado o userId
e também por padrão os status aberto e fechado.
//List.vue
async getTasks() {
this.isLoading = true;
let self = this;
await setTimeout(function(){ self.isLoading = false; }, 1000);
return await TasksModel.params({ userId: JSON.parse(localStorage.getItem('authUser')).id, status: this.statusList }).get();
}
E por fim vou utilizar esta busca em todos os métodos do List.vue
que faz a busca pelas tarefas.
Como mudei em muitos métodos, vou deixar abaixo toda a parte do Javascript do List.vue
.
//List.vue
<script>
import TasksModel from "@/models/TasksModel";
import ToastMixin from "@/mixins/toastMixin.js";
import Status from "@/valueObjects/status";
export default {
name: "List",
mixins: [ToastMixin],
data() {
return {
tasks: [],
taskSelected: [],
statusList: [Status.OPEN, Status.FINISHED],
status: Status,
filter: {
status: null,
subject: null,
},
optionsStatus: [
{ value: null, text: "Selecione um status" },
{ value: Status.OPEN, text: "Aberto" },
{ value: Status.FINISHED, text: "Finalizado" },
{ value: Status.ARCHIVED, text: "Arquivado" },
],
isLoading: false
};
},
async created() {
this.tasks = await this.getTasks();
},
methods: {
edit(taskId) {
this.$router.push({ name: "form", params: { taskId } });
},
async remove(taskId) {
this.taskSelected = await TasksModel.find(taskId);
this.$refs.modalRemove.show();
},
hideModal() {
this.$refs.modalRemove.hide();
},
async confirmRemoveTask() {
this.taskSelected.delete();
this.tasks = await this.getTasks();
this.hideModal();
},
async updateStatus(taskId, status) {
let task = await TasksModel.find(taskId);
task.status = status;
await task.save();
this.tasks = await this.getTasks();
this.showToast(
"success",
"Sucesso!",
"Status da tarefa atualizado com sucesso"
);
},
isFinished(task) {
return task.status === Status.FINISHED;
},
async filterTasks() {
let filters = this.clean(this.filter);
filters.userId = JSON.parse(localStorage.getItem('authUser')).id;
this.tasks = await TasksModel.params(filters).get();
},
clean(obj) {
for (var propName in obj) {
if (obj[propName] === null || obj[propName] === undefined) {
delete obj[propName];
}
}
return obj;
},
async clearFilter() {
this.filter = {
status: null,
subject: null,
};
this.tasks = await this.getTasks();
},
overduePresenter(dateOverdue) {
if(!dateOverdue) {
return;
}
return dateOverdue.split('-').reverse().join('/');
},
variantOverdue(dateOverdue, taskStatus) {
if(!dateOverdue) {
return 'light';
}
if(taskStatus === this.status.FINISHED){
return 'success';
}
let dateNow = new Date().toISOString().split('T')[0];
if(dateOverdue === dateNow) {
return 'warning';
}
if(dateOverdue < dateNow) {
return 'danger';
}
return 'light';
},
async getTasks() {
this.isLoading = true;
let self = this;
await setTimeout(function(){ self.isLoading = false; }, 1000);
return await TasksModel.params({ userId: JSON.parse(localStorage.getItem('authUser')).id, status: this.statusList }).get();
}
},
computed: {
isTasksEmpty() {
return this.tasks.length === 0;
},
},
};
</script>
Agora, caso o usuário crie tarefas, toda vez que fizer a autenticação no sistema, só irá conseguir as tarefas que cadastrou.
Caso outro usuário faça a autenticação, não irá conseguir ver essas tarefas, cada um só poderá ver o seu.
Vídeo
Código fonte
O código fonte está no meu CodeSandbox, neste link.
Para ver outros canais onde o posto conteúdo sobre VueJS, veja os Links do Programando Soluções.
Conclusão
Agora o projeto tem o módulo de usuário, onde ele pode se cadastrar, fazer a autenticação, e ver somente as suas tarefas.
Um ponto importante é que a parte de login e autenticação, foi criada pensando somente nesse projeto, onde há só um frontend para simular comportamentos.
Caso você utilize login e autenticação em outros projetos, considere fazer pesquisas para buscar melhores práticas de segurança dos dados de usuário.