add: register & recovery password

This commit is contained in:
Alexandro Uc Santos
2023-11-18 19:58:42 -06:00
commit afa8e1983b
37 changed files with 2730 additions and 0 deletions

16
src/App.vue Normal file
View File

@@ -0,0 +1,16 @@
<template>
<main>
<RouterView />
</main>
<Notification/>
</template>
<script setup>
import Notification from './components/ui/Notification.vue';
</script>
<style scoped>
</style>

220
src/assets/main.css Normal file
View File

@@ -0,0 +1,220 @@
body {
margin: 0px 0px;
padding: 0px;
}
.bg-body{
background-color: red;
}
.w-lg-45{
width: 45%;
}
.radius-1 {
border-radius: 1rem !important;
}
.radius-2 {
border-radius: 2rem !important;
}
.radius-3 {
border-radius: 3rem !important;
}
.divider {
display: block;
height: 2px;
width: 100%;
background-color: rgb(243, 226, 226);
}
.btn-text{
font-size: 1.2rem;
font-weight: 700;
color: #FBBA33;
text-decoration: none;
cursor: pointer;
}
.btn-lg-block{
width: 100%;
}
.btn-primary-lg {
background-color: #FBBA33;
color: #FFF;
padding: 12px 30px;
border: none;
border-radius: 25px;
font-size: 18px;
text-decoration: none;
font-weight: 900;
}
.btn-primary-lg:hover {
background-color: #e3a11e;
transition: background-color 300ms ease;
}
.btn-primary-sm {
background-color: #FBBA33;
padding: 8px 16px;
color: #FFF;
font-size: 16px;
border: none;
text-decoration: none;
text-align: center;
border-radius: 13px;
font-weight: 700;
}
.btn-primary-sm:hover {
background-color: #e3a11e;
transition: background-color 300ms ease;
}
.title{
font-size: 1.6rem;
font-weight: 900;
color: #323030;
}
.text-content {
font-size: 1.2rem;
font-weight: normal;
color: #323030;
}
.title-main{
color: #FBBA33;
}
.card-info,
.card-fixed {
width: 100%;
background-color: #FFF;
padding: 24px 32px;
border: none;
border-radius: 13px;
display: flex;
filter: drop-shadow(0px 4px 4px rgba(0, 0, 0, 0.10));
margin-bottom: 12px;
}
.card-info h2{
font-size: 1.5rem;
font-weight: 800;
color: #323030;
}
.card-info p{
font-size: 1rem;
font-weight: 400;
color: #323030;
}
.loads-table {
overflow: scroll;
}
th {
font-size: 16px;
font-weight: 600;
align-items: center;
justify-content: center;
color: #323030;
}
td {
font-size: 14px;
font-weight: 400;
color: #323030;
}
.custom-label {
font-size: 1.2rem;
font-weight: 600;
color: #645555;
}
.custom-input {
background-color: rgb(233, 233, 241);
border-radius: 13px;
border: none;
padding: 10px 12px;
font-size: 1rem;
}
.custom-input:enabled{
border: none;
}
.custom-input:active{
border: none;
}
.custom-input:focus{
border: none;
}
@media (max-width: 1024px) {
th {
font-size: 13px;
font-weight: 500;
}
td {
font-size: 12px;
font-weight: 300;
}
}
@media (max-width: 768px) {
.card-info {
padding: 16px 16px;
}
.card-info h2{
font-size: 1.2rem;
font-weight: 700;
}
.card-info p{
font-size: 0.8rem;
font-weight: 400;
}
.w-lg-45 {
width: 100%;
}
.btn-primary-lg {
padding: 8px 15px;
border: none;
border-radius: 25px;
font-size: 16px;
font-weight: 700;
}
.btn-primary-sm {
padding: 8px 12px;
font-size: 14px;
border: none;
border-radius: 13px;
font-weight: 700;
}
}
@media (max-width: 568px) {
.card-info {
width: 100%;
background-color: transparent;
padding: 16px 8px;
flex-direction: column;
}
th {
font-size: 13px;
font-weight: 400;
}
td {
font-size: 12px;
font-weight: 300;
}
}

99
src/components/Footer.vue Normal file
View File

@@ -0,0 +1,99 @@
<template>
<div class="footer">
<div class="d-flex justify-content-between">
<div class="social-media">
<div class="circle-btn">
<i class="fa-brands fa-facebook"></i>
</div>
<div class="circle-btn">
<i class="fa-brands fa-instagram"></i>
</div>
<div class="circle-btn">
<i class="fa-brands fa-x-twitter"></i>
</div>
<div class="circle-btn">
<i class="fa-brands fa-tiktok"></i>
</div>
<div class="circle-btn">
<i class="fa-brands fa-linkedin"></i>
</div>
</div>
<div class="d-flex flex-column">
<a class="btn-links mb-1" href="">Aviso de privacidad</a>
<a class="btn-links" href="">Terminos y condiciones</a>
</div>
</div>
<h4 class="copy"><i class="fa fa-copyright" aria-hidden="true"></i> 2023 ETA VIAPORTE | TODOS LOS DERECHOS RESERVADOS</h4>
</div>
</template>
<style scoped>
.footer {
width: 100%;
padding: 36px 100px;
background-color: #303030;
display: flex;
flex-direction: column;
}
.social-media{
display: flex;
}
.copy{
padding-top: 32px;
color: #FFF;
opacity: 0.2;
justify-content: center;
margin: 0 auto;
font-size: 22px;
font-weight: 400;
}
.circle-btn{
cursor: pointer;
margin: 0px 5px;
display: flex;
border-radius: 13px;
border: none;
background-color: #000;
color: #FFF;
padding: 15px;
justify-content: center;
align-items: center;
}
.btn-links {
font-size: 16px;
font-weight: 500;
color: rgb(85, 85, 223);
text-decoration: none;
}
.circle-btn i{
font-size: 30px;
}
@media (max-width: 768px) {
.footer {
width: 100%;
padding: 32px 24px;
background-color: #303030;
display: flex;
flex-direction: column;
}
.circle-btn {
padding: 8px;
margin: 0px 2px;
}
.circle-btn i{
font-size: 18px;
}
.btn-links {
font-size: 12px;
font-weight: 400;
}
.copy{
font-size: 16px;
font-weight: 400;
text-align: center;
}
}
</style>

110
src/components/Header.vue Normal file
View File

@@ -0,0 +1,110 @@
<script setup>
</script>
<template>
<div class="header-content">
<div class="box-content">
<img src="/images/logo-eta.png" class="logo" alt="Eta Viaporte" >
<div class="box-register">
<p class="title-header">Tablero de <span class="title-main">Embarques</span> y <span class="title-main">Transportes</span></p>
</div>
</div>
</div>
</template>
<style scoped>
.header-content {
position: relative;
width: 100%;
background-color: #323030;
display: flex;
flex-direction: row;
margin: 0 auto;
padding: 20px 24px;
align-items: center;
justify-content: space-between;
}
.logo{
height: 70px;
margin-right: 10px;
}
.box-content {
display: flex;
align-items: center;
align-content: center;
/* width: 65%; */
}
.box-register{
margin-left: 24px;
}
.title-header{
font-size: 1.8rem;
font-weight: 900;
color: #FFF;
}
.app-btn{
background-color: #FBBA33;
/* background-color: #000; */
border: none;
color: #FFF;
border-radius: 25px;
padding: 12px 32px;
}
.app-btn:hover{
background-color: #e3a11e;
transition: background-color 300ms ease;
}
.app-btn i{
font-size: 1.4rem;
}
.app-btn span{
margin-left: 8px;
font-size: 1.4rem;
}
@media (max-width: 1024px) {
.header-content {
padding: 16px 36px;
}
.box-register{
margin-left: 10px;
}
.app-btn{
padding: 10px 24px;
}
.app-btn i{
font-size: 1rem;
}
.app-btn span{
margin-left: 8px;
font-size: 1rem;
}
}
@media (max-width: 768px) {
.header-content {
padding: 16px 16px;
}
.box-buttons{
display: none !important;
}
}
@media (max-width: 568px) {
.logo{
height: 80px;
}
.title-header{
font-size: 1rem;
font-weight: 700;
color: #FFF;
}
.title-main{
color: #FBBA33;
}
}
</style>

View File

@@ -0,0 +1,47 @@
<script setup>
defineProps({
field: {
type: String
},
label: {
type: String,
required: false,
},
name: {
type: String,
required: true,
},
type: {
type: String,
default: 'text',
},
helpText: {
type: String,
default: '',
}
})
defineEmits(['update:field'])
</script>
<template>
<div class="d-flex flex-column gap-2 mb-4">
<label class="custom-label" :for="name">{{ label }}</label>
<input
class="custom-input"
:type="type"
:id="name"
:name="name"
:value="field"
@input="$event => $emit('update:field', $event.target.value)">
<span class="help" v-if="helpText.length > 0">{{ helpText }}</span>
</div>
</template>
<style scoped>
.help {
font-size: 12px;
font-weight: 300;
color: rgb(108, 92, 92);
}
</style>

View File

@@ -0,0 +1,85 @@
<script setup>
import { useNotificationsStore } from '../../stores/notifications';
const notifications = useNotificationsStore();
</script>
<template>
<div class="noty-fixed">
<div class="content-noty" v-if="notifications.show">
<div class="body">
<i v-if="notifications.error === false" class="fa-regular fa-circle-check text-success icon-category"></i>
<i v-else class="fa-solid fa-circle-exclamation text-danger"></i>
<div>
<h4 class="noty-title">Notificación</h4>
<p class="noty-body">{{ notifications.text }}</p>
</div>
<i class="fa-solid fa-xmark close-icon" @click="notifications.show = false"></i>
</div>
</div>
</div>
</template>
<style scoped>
.noty-fixed {
position: fixed;
right: 100px;
top: 50px;
}
.content-noty {
width: 100%;
max-width: 500px;
padding: 14px 20px;
background-color: white;
border-radius: 8px;
box-shadow: -5px 5px 9px -1px rgba(0,0,0,0.20);
-webkit-box-shadow: -5px 5px 9px -1px rgba(0,0,0,0.20);
-moz-box-shadow: -5px 5px 9px -1px rgba(0,0,0,0.20);
}
.body {
display: flex;
flex-direction: row;
align-items: center;
gap: 1rem;
/* align-content: center; */
justify-content: space-between;
}
.noty-title {
font-size: 1.2rem;
color: black;
margin: 0px;
margin-bottom: 4px;
}
.noty-body {
font-size: 1rem;
color: black;
margin: 0px;
}
.icon-category {
font-size: 24px;
}
.close-icon {
position: absolute;
right: 12px;
top: 10px;
font-size: 20px;
cursor: pointer;
}
@media (max-width: 768px) {
.noty-fixed {
right: 20px;
top: 30px;
}
.content-noty {
width: 100%;
max-width: 320px;
padding: 14px 16px;
background-color: white;
border-radius: 8px;
}
}
</style>

View File

@@ -0,0 +1,56 @@
<script setup>
defineProps({
msg: {
type: String,
required: true,
},
isError: {
type: Boolean,
default: true,
},
showIcon: {
type: Boolean,
default: true,
}
})
</script>
<template>
<div class="badge" :class="[isError ? 'badge-error' : 'badge-success']">
<span>
<i v-if="showIcon && isError" class="fa-solid fa-circle-exclamation me-2"></i>
<i v-if="showIcon && !isError" class="fa-solid fa-circle-check"></i>
{{ msg }}
</span>
</div>
</template>
<style scoped>
.badge {
display: flex;
width: 100%;
color: #FFF;
font-size: 1rem;
font-weight: 700;
border-radius: 8px;
justify-content: center;
padding: 10px 16px;
margin-bottom: 12px;
}
.badge-error {
background-color: rgb(238, 101, 101);
}
.badge-success {
background-color: rgb(29, 162, 113);
}
@media (max-width: 768px) {
.badge {
font-size: 0.8rem;
font-weight: 400;
border-radius: 8px;
padding: 10px 12px;
}
}
</style>

View File

@@ -0,0 +1,50 @@
<template>
<div class="spinner">
<div class="bounce1"></div>
<div class="bounce2"></div>
<div class="bounce3"></div>
</div>
</template>
<style scoped>
.spinner {
margin: 20px auto 0;
width: 70px;
text-align: center;
}
.spinner > div {
width: 18px;
height: 18px;
background-color: #FBBA33;
border-radius: 100%;
display: inline-block;
-webkit-animation: sk-bouncedelay 1.4s infinite ease-in-out both;
animation: sk-bouncedelay 1.4s infinite ease-in-out both;
}
.spinner .bounce1 {
-webkit-animation-delay: -0.32s;
animation-delay: -0.32s;
}
.spinner .bounce2 {
-webkit-animation-delay: -0.16s;
animation-delay: -0.16s;
}
@-webkit-keyframes sk-bouncedelay {
0%, 80%, 100% { -webkit-transform: scale(0) }
40% { -webkit-transform: scale(1.0) }
}
@keyframes sk-bouncedelay {
0%, 80%, 100% {
-webkit-transform: scale(0);
transform: scale(0);
} 40% {
-webkit-transform: scale(1.0);
transform: scale(1.0);
}
}
</style>

View File

@@ -0,0 +1,12 @@
export const getDateMonthDay = (value) => {
const date = new Date(value)
return date.toLocaleString(['en-US'], {
month: 'short',
day: '2-digit',
year: 'numeric',
})
}

View File

@@ -0,0 +1,18 @@
export const validateEmail = (email) => {
return String(email)
.toLowerCase()
.match(
/^(([^<>()[\]\\.,;:\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,}))$/
);
};
export const messagesError = (msg) => {
switch (msg) {
case 'Email is not registered!':
return 'No se encontro una cuenta con este email';
case 'Wrong OTP':
return 'Código ingresado incorrecto';
default:
return msg;
}
};

View File

@@ -0,0 +1,11 @@
<template>
<RouterView />
</template>
<script setup>
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,68 @@
<script setup>
import Header from '../components/Header.vue';
import Footer from '../components/Footer.vue';
</script>
<template>
<Header/>
<div class="auth-layout">
<!-- <div class="img-auth">
<img src="/images/auth.png" class="img" alt="fondo">
</div> -->
<!-- <div class="auth-view"> -->
<!-- <img src="/images/logo.png" width="150"/> -->
<RouterView/>
<!-- </div> -->
</div>
<Footer/>
</template>
<style scoped>
/* .auth-layout {
display: flex;
flex-direction: row;
width: 100%;
margin: 0px 0px !important;
padding: 0px 0px !important;
overflow: hidden;
} */
.auth-layout {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
align-content: center;
width: 50%;
margin: 0px auto !important;
padding: 0px 0px !important;
overflow: hidden;
min-height: 700px;
}
.img-auth {
width: 50%;
/* object-fit: cover; */
margin: 24px 50px;
/* padding: 24p */
}
.img {
width: 100%;
/* max-height: 80vh; */
border-radius: 13px;
/* object-fit: cover; */
/* padding: 24px; */
}
.auth-view {
display: flex;
width: 50%;
margin: 0px 0px;
padding: 0px 0px;
/* background-color: red; */
flex-direction: column;
align-items: center;
justify-content: center;
}
</style>

9
src/lib/axios.js Normal file
View File

@@ -0,0 +1,9 @@
import axios from "axios";
const baseUrl = import.meta.env.VITE_API_URL;
const api = axios.create({
baseURL: baseUrl
});
export default api;

14
src/main.js Normal file
View File

@@ -0,0 +1,14 @@
import './assets/main.css'
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import router from './router'
const app = createApp(App)
app.use(createPinia())
app.use(router)
app.mount('#app')

45
src/router/index.js Normal file
View File

@@ -0,0 +1,45 @@
import { createRouter, createWebHistory } from 'vue-router'
import AuthLayout from '../layouts/AuthLayout.vue'
import LoginView from '../views/LoginView.vue'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
name: 'auth',
component: AuthLayout,
children: [
{
path: '',
name: 'login',
component: () => import('../views/LoginView.vue'),
},
{
path: 'registro',
name: 'register',
component: () => import('../views/RegisterView.vue'),
},
{
path: 'recuperar-cuenta',
name: 'recovery',
component: () => import('../views/RecoveryPasswordView.vue'),
},
]
},
{
path: '/dashboard',
name: 'dashboard',
component: () => import('../layouts/AdminLayout.vue'),
children: [
{
path: 'inicio',
name: 'inicio',
component: () => import('../views/HomeView.vue'),
},
]
}
]
})
export default router

83
src/services/auth.js Normal file
View File

@@ -0,0 +1,83 @@
import api from "../lib/axios";
import {messagesError} from '../helpers/validations';
export const regiter = async(body) => {
try {
const endpoint = "/account/signup";
const {data} = await api.post(endpoint, body);
return {
msg: 'success',
data
};
} catch (error) {
return {
msg: error.response.data.error ?? 'Algo salio mal, intente más tarde',
data: null
};
}
}
export const regiterConfirm = async(body) => {
try {
const endpoint = "/account/signup";
const {data} = await api.patch(endpoint, body);
return {
msg: 'success',
data
};
} catch (error) {
let msg = 'Algo salio mal, intente más tarde';
if(error.response.data.error) {
if(error.response.data.error === 'Wrong OTP'){
msg = 'Codigo ingresado incorrecto';
} else {
msg = error.response.data.error;
}
}
return {
msg,
data: null
};
}
}
export const recoveryPassword = async(body) => {
try {
const endpoint = "/account/recover";
const {data} = await api.post(endpoint, body);
return {
msg: 'success',
data
};
} catch (error) {
const errStr = error.response.data.error ?? 'Algo salio mal, intente más tarde';
return {
msg: messagesError(errStr),
data: null
};
}
}
export const recoveryPasswordConfirm = async(body) => {
try {
const endpoint = "/account/recover";
const {data} = await api.patch(endpoint, body);
return {
msg: 'success',
data
};
} catch (error) {
let msg = 'Algo salio mal, intente más tarde';
if(error.response.data.error) {
if(error.response.data.error === 'Wrong OTP'){
msg = 'Codigo ingresado incorrecto';
} else {
msg = error.response.data.error;
}
}
return {
msg,
data: null
};
}
}

117
src/services/public.js Normal file
View File

@@ -0,0 +1,117 @@
export const getLoadDirectories = async(filterStr) => {
try {
const endpoint = `/public-loads${filterStr}`;
const {data} = await api.get(endpoint);
return data.data;
} catch (error) {
console.log(error);
return [];
}
}
export const getVehicleDirectories = async(filter) => {
try {
const endpoint = `/public-vehicles/published${filter}`;
const {data} = await api.get(endpoint);
return data.data;
} catch (error) {
console.log(error);
return [];
}
}
export const getFreeVehicles = async() => {
try {
// console.log(process.env.API_URL + "/vehicles/?status=Free&updatedAt[$gt]=" + moment.utc(lasthour).valueOf());
const endpoint = `/public-vehicles/location`;
console.log({endpoint});
const {data} = await api.get(endpoint);
return data.data;
} catch (error) {
console.log(error);
return [];
}
}
export const getNews = async() => {
try {
const endpoint = `/news`;
console.log({endpoint});
const {data} = await api.get(endpoint);
console.log(data);
return data.data;
} catch (error) {
console.log(error);
return [];
}
}
export const getCompanies = async(filter) => {
try {
const endpoint = `/public-companies/${filter}`;
console.log(endpoint);
const {data} = await api.get(endpoint);
return data.data;
} catch (error) {
console.log(error);
return [];
}
}
export const getUsersCompany = async(filter) => {
try {
const endpoint = `/users?${filter}`;
// console.log({endpoint});
const {data} = await api.get(endpoint);
// console.log(data);
return data.data;
} catch (error) {
console.log(error);
return [];
}
}
export const getSettingsQuery = async(filter) => {
try {
const endpoint = "/meta-data/find?regex=" + filter.query;
const {data} = await api.get(endpoint);
return data.data;
} catch (error) {
console.log(error);
return [];
}
}
export const searchcategories = async(query) => {
try {
const endpoint = "/product-categories/find?regex=" + query;
const {data} = await api.get(endpoint);
return data.data;
} catch (error) {
console.log(error);
return [];
}
}
export const searchstates = async(query) => {
try {
const endpoint = "/states/find?regex=" + query;
const {data} = await api.get(endpoint);
return data.data;
} catch (error) {
console.log(error);
return [];
}
}
export const searchcities = async(query) => {
try {
// const endpoint = "/cities/?city_name[$regex]=" + query + "&city_name[$options]=i";
const endpoint = "/cities/find?regex=" + query;
const {data} = await api.get(endpoint);
return data.data;
} catch (error) {
console.log(error);
return [];
}
}

12
src/stores/counter.js Normal file
View File

@@ -0,0 +1,12 @@
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
function increment() {
count.value++
}
return { count, doubleCount, increment }
})

View File

@@ -0,0 +1,27 @@
import { defineStore } from "pinia";
import { ref, watch } from "vue";
export const useNotificationsStore = defineStore('notifications', () => {
const text = ref('')
const error = ref(false)
const show = ref(false)
watch(show, () => {
if(show) {
setTimeout(() => {
text.value = '';
error.value = false;
show.value = false;
}, 4000);
}
});
return {
text,
error,
show,
}
});

6
src/views/HomeView.vue Normal file
View File

@@ -0,0 +1,6 @@
<script setup>
</script>
<template>
<h1>HomeView</h1>
</template>

133
src/views/LoginView.vue Normal file
View File

@@ -0,0 +1,133 @@
<script setup>
import { reactive, ref } from 'vue';
import CustomInput from '../components/ui/custominput.vue';
import NotificationBadge from '../components/ui/NotificationBadge.vue';
import {validateEmail} from '../helpers/validations';
import Spiner from '../components/ui/Spiner.vue';
import { RouterLink } from 'vue-router';
const form = reactive({
email: '',
password: '',
});
const loading = ref(false);
const msgError = ref('');
const msgSuccess = ref('');
const handleLogin = async() => {
msgError.value = '';
msgSuccess.value = '';
let resp = validations();
if(resp !== '') {
msgError.value = resp;
clearMessages();
return;
} else {
loading.value = true;
msgError.value = '';
const data = {
email: form.email,
password: form.password
}
console.log(data);
loading.value = false;
}
}
const validations = () => {
if(form.email.trim() == '' || form.password.trim() == '') {
return 'Todos los campos con obligatorios';
} else if (!validateEmail(form.email)) {
return 'Correo electrónico no es valido'
} else {
return '';
}
}
const clearMessages = () => {
setTimeout(() => {
msgError.value = '';
msgSuccess.value = '';
}, 3000);
}
</script>
<template>
<div class="d-flex flex-column my-5 justify-content-center align-items-center">
<h2 class="title">Iniciar sesión</h2>
<p class="subtitle mt-4 mb-5">Bienvenido de vuelta! Ingresa tu email y contraseña</p>
<form @submit.prevent="handleLogin" class="form-content">
<NotificationBadge :msg="msgError" v-if="msgError != ''"/>
<NotificationBadge :msg="msgSuccess" :is-error="false" v-if="msgSuccess != ''"/>
<CustomInput
label="Ingresa tu correo electrónico"
name="email"
type="email"
v-model:field="form.email"
/>
<CustomInput
label="Contraseña"
name="password"
type="password"
v-model:field="form.password"
/>
<Spiner v-if="loading" class="mt-5"/>
<input
v-else
type="submit"
class="btn-primary-lg btn-lg-block radius-1 mt-5" value="Ingresar">
</form>
<p class="mt-3 fs-6">¿Olvidaste tu contreseña? <RouterLink class="btn-text" :to="{name: 'recovery'}">Ingresa aqui</RouterLink></p>
<p class="mt-5 fs-6">¿No tienes una cuenta? <RouterLink class="btn-text" :to="{name: 'register'}">Registrate aqui</RouterLink></p>
</div>
</template>
<style scoped>
.subtitle {
font-size: 1.4rem;
font-weight: 500;
color: grey;
}
.form-content {
display: flex;
flex-direction: column;
width: 100%;
/* min-width: 420px; */
/* padding: 2rem 3rem; */
}
.icon-success{
font-size: 5rem;
color: green;
}
.help-info {
margin-bottom: 2rem;
font-size: 1.2rem;
font-weight: 400;
color: #5a4b4b;
}
.msg-success {
margin-bottom: 2rem;
font-size: 1.4rem;
font-weight: 500;
color: #5a4b4b;
}
@media (max-width: 1224px) {
.form-content {
width: 80%;
}
}
@media (max-width: 768px) {
.form-content {
width: 100%;
padding: 2rem 1rem;
}
}
</style>

View File

@@ -0,0 +1,235 @@
<script setup>
import { reactive, ref } from 'vue';
import CustomInput from '../components/ui/custominput.vue';
import NotificationBadge from '../components/ui/NotificationBadge.vue';
import {validateEmail} from '../helpers/validations';
import {recoveryPassword, recoveryPasswordConfirm} from "../services/auth";
import Spiner from '../components/ui/Spiner.vue';
import { useRouter } from 'vue-router';
import { useNotificationsStore } from '../stores/notifications';
const router = useRouter();
const notifications = useNotificationsStore()
const form = reactive({
email: '',
pwd: '',
retryPwd: '',
code: ''
});
const step = ref(1);
const loading = ref(false);
const checksum = ref('');
const msgError = ref('');
const msgSuccess = ref('');
const handleRegister = async() => {
msgError.value = '';
msgSuccess.value = '';
let resp = validations();
if(resp !== '') {
msgError.value = resp;
clearMessages();
return;
} else {
loading.value = true;
msgError.value = '';
const data = {
email: form.email,
password: form.pwd
}
const result = await recoveryPassword(data);
if(result.msg === 'success' && result.data !== null) {
msgSuccess.value = 'Te enviamos un código al correo, ingresado!';
checksum.value = result.data.checksum;
step.value = 2;
clearMessages();
} else {
msgError.value = result.msg;
clearMessages();
}
loading.value = false;
}
}
const handleConfirmRegister = async() => {
msgError.value = '';
msgSuccess.value = '';
if(form.code.length < 6) {
msgError.value = 'Ingresa código valido';
clearMessages();
return;
} else {
loading.value = true;
msgError.value = '';
const data = {
email: form.email,
password: form.pwd,
otp: form.code,
checksum: checksum.value
}
const result = await recoveryPasswordConfirm(data);
// console.log(result);
if(result.msg === 'success' && result.data !== null){
msgSuccess.value = 'Contraseña se ha cambiando exitosamente!';
step.value = 3;
checksum.value = '';
Object.assign(form, {
email: '',
pwd: '',
retryPwd: '',
code: ''
});
clearMessages();
notifications.$patch({
text : 'Contraseña se ha cambiando exitosamente!',
show : true,
error: false
});
router.push({name: 'login'});
} else {
msgError.value = result.msg;
clearMessages()
}
loading.value = false;
}
}
const validations = () => {
const pass = /^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[a-zA-Z]).{8,}$/;
if(form.email.trim() == '' || form.pwd.trim() == '' || form.retryPwd.trim() == '') {
return 'Todos los campos con obligatorios';
} else if (!validateEmail(form.email)) {
return 'Correo electrónico no es valido'
} else if(!pass.test(form.pwd)) {
return 'Contraseña poco segura';
} else if (form.pwd != form.retryPwd) {
return 'Las contraseñas no coinciden';
} else {
return '';
}
}
const clearMessages = () => {
setTimeout(() => {
msgError.value = '';
msgSuccess.value = '';
}, 3000);
}
const resendCode = async() => {
loading.value = true;
const data = {
email: form.email,
password: form.pwd
}
const result = await regiter(data);
if(result.msg === 'success' && result.data !== null) {
msgSuccess.value = 'Te enviamos un código al correo, ingresado!';
checksum.value = result.data.checksum;
clearMessages();
} else {
msgError.value = result.msg;
clearMessages();
}
loading.value = false;
}
const handleBack = (val) => {
step.value = val;
}
</script>
<template>
<div class="d-flex flex-column my-5 justify-content-center align-items-center">
<h2 class="title">Recuperar contraseña</h2>
<div class="form-content">
<form v-if="step === 1" @submit.prevent="handleRegister">
<NotificationBadge :msg="msgError" v-if="msgError != ''"/>
<NotificationBadge :msg="msgSuccess" :is-error="false" v-if="msgSuccess != ''"/>
<CustomInput
label="Ingresa tu correo electrónico"
name="email"
type="email"
v-model:field="form.email"
/>
<CustomInput
label="Nueva contraseña"
name="password"
type="password"
v-model:field="form.pwd"
help-text="La Contraseña debe ser mínimo 8 caracteres, al menos una mayúscula, al menos una minúscula, y un digito."
/>
<CustomInput
label="Confirmar Contraseña"
name="retryPassword"
type="password"
v-model:field="form.retryPwd"
/>
<Spiner v-if="loading" class="mt-5"/>
<input
v-else
type="submit"
class="btn-primary-lg btn-lg-block radius-1 mt-5" value="Continuar">
<p class="mt-5 fs-6 text-center"><RouterLink class="btn-text" :to="{name: 'login'}">Iniciar sesión</RouterLink></p>
</form>
<form v-if="step === 2" @submit.prevent="handleConfirmRegister" class="mx-5">
<div class="d-flex justify-content-center align-items-center mb-4">
<a
@click="handleBack(1)"
class="btn-text ms-2"><i class="fa-solid fa-arrow-left"></i> Volver</a>
</div>
<NotificationBadge :msg="msgError" v-if="msgError != ''"/>
<NotificationBadge :msg="msgSuccess" :is-error="false" v-if="msgSuccess != ''"/>
<p class="help-info">Te enviamos un código de verificación al correo electrónico, ingresalo para confirmar la recuperacion de contraseña. No olvides revisar en la carpeta spam</p>
<CustomInput
label="Ingresa el código"
name="code"
type="text"
v-model:field="form.code"
/>
<div v-if="!loading" class="d-flex justify-content-center align-items-center mt-4">
<a
@click="resendCode()"
class="btn-text ms-2"><i class="fa-solid fa-rotate-right"></i> Reenviar código</a>
</div>
<Spiner v-if="loading" class="mt-5"/>
<input
v-else
type="submit"
class="btn-primary-lg btn-lg-block radius-1 mt-5" value="Continuar">
</form>
</div>
</div>
</template>
<style scoped>
.form-content {
width: 100%;
padding: 2rem 3rem;
}
.help-info {
margin-bottom: 2rem;
font-size: 1.2rem;
font-weight: 400;
color: #5a4b4b;
}
@media (max-width: 1224px) {
.form-content {
width: 80%;
}
}
@media (max-width: 768px) {
.form-content {
width: 100%;
padding: 2rem 1rem;
}
}
</style>

247
src/views/RegisterView.vue Normal file
View File

@@ -0,0 +1,247 @@
<script setup>
import { reactive, ref } from 'vue';
import CustomInput from '../components/ui/custominput.vue';
import NotificationBadge from '../components/ui/NotificationBadge.vue';
import {validateEmail} from '../helpers/validations';
import {regiter, regiterConfirm} from "../services/auth";
import Spiner from '../components/ui/Spiner.vue';
import { useRouter } from 'vue-router';
import { useNotificationsStore } from '../stores/notifications';
const router = useRouter();
const notifications = useNotificationsStore()
const form = reactive({
email: '',
pwd: '',
retryPwd: '',
code: ''
});
const step = ref(1);
const loading = ref(false);
const checksum = ref('');
const msgError = ref('');
const msgSuccess = ref('');
const handleRegister = async() => {
msgError.value = '';
msgSuccess.value = '';
let resp = validations();
if(resp !== '') {
msgError.value = resp;
clearMessages();
return;
} else {
loading.value = true;
msgError.value = '';
const data = {
email: form.email,
password: form.pwd
}
const result = await regiter(data);
if(result.msg === 'success' && result.data !== null) {
msgSuccess.value = 'Te enviamos un código al correo, ingresado!';
checksum.value = result.data.checksum;
step.value = 2;
clearMessages();
} else {
msgError.value = result.msg;
clearMessages();
}
loading.value = false;
}
}
const handleConfirmRegister = async() => {
msgError.value = '';
msgSuccess.value = '';
if(form.code.length < 6) {
msgError.value = 'Ingresa código valido';
clearMessages();
return;
} else {
loading.value = true;
msgError.value = '';
const data = {
email: form.email,
password: form.pwd,
otp: form.code,
checksum: checksum.value
}
const result = await regiterConfirm(data);
// console.log(result);
if(result.msg === 'success' && result.data !== null){
msgSuccess.value = 'Registro exitoso!';
step.value = 3;
checksum.value = '';
Object.assign(form, {
email: '',
pwd: '',
retryPwd: '',
code: ''
});
clearMessages();
notifications.$patch({
text : 'Registro exitoso, Inicie sesión!',
show : true,
error: false
});
router.push({name: 'login'});
} else {
msgError.value = result.msg;
clearMessages()
}
loading.value = false;
}
}
const validations = () => {
const pass = /^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[a-zA-Z]).{8,}$/;
if(form.email.trim() == '' || form.pwd.trim() == '' || form.retryPwd.trim() == '') {
return 'Todos los campos con obligatorios';
} else if (!validateEmail(form.email)) {
return 'Correo electrónico no es valido'
} else if(!pass.test(form.pwd)) {
return 'Contraseña poco segura';
} else if (form.pwd != form.retryPwd) {
return 'Las contraseñas no coinciden';
} else {
return '';
}
}
const clearMessages = () => {
setTimeout(() => {
msgError.value = '';
msgSuccess.value = '';
}, 3000);
}
const resendCode = async() => {
loading.value = true;
const data = {
email: form.email,
password: form.pwd
}
const result = await regiter(data);
if(result.msg === 'success' && result.data !== null) {
msgSuccess.value = 'Te enviamos un código al correo, ingresado!';
checksum.value = result.data.checksum;
clearMessages();
} else {
msgError.value = result.msg;
clearMessages();
}
loading.value = false;
}
const handleBack = (val) => {
step.value = val;
}
</script>
<template>
<div class="d-flex flex-column my-5 justify-content-center align-items-center">
<h2 class="title">Registro de nuevos usuarios</h2>
<div class="form-content">
<form v-if="step === 1" @submit.prevent="handleRegister">
<NotificationBadge :msg="msgError" v-if="msgError != ''"/>
<NotificationBadge :msg="msgSuccess" :is-error="false" v-if="msgSuccess != ''"/>
<CustomInput
label="Ingresa tu correo electrónico"
name="email"
type="email"
v-model:field="form.email"
/>
<CustomInput
label="Contraseña"
name="password"
type="password"
v-model:field="form.pwd"
help-text="La Contraseña debe ser mínimo 8 caracteres, al menos una mayúscula, al menos una minúscula, y un digito."
/>
<CustomInput
label="Confirma Contraseña"
name="retryPassword"
type="password"
v-model:field="form.retryPwd"
/>
<Spiner v-if="loading" class="mt-5"/>
<input
v-else
type="submit"
class="btn-primary-lg btn-lg-block radius-1 mt-5" value="Continuar">
<p class="mt-5 fs-6 text-center">Al registrarte aceptas nuestros <RouterLink class="btn-text" :to="{name: 'login'}">términos y cóndiciones</RouterLink></p>
<p class="mt-5 fs-6 text-center">¿Ya tienes una cuenta? <RouterLink class="btn-text" :to="{name: 'login'}">Ingresa aqui</RouterLink></p>
</form>
<form v-if="step === 2" @submit.prevent="handleConfirmRegister" class="mx-5">
<div class="d-flex justify-content-center align-items-center mb-4">
<a
@click="handleBack(1)"
class="btn-text ms-2"><i class="fa-solid fa-arrow-left"></i> Volver</a>
</div>
<NotificationBadge :msg="msgError" v-if="msgError != ''"/>
<NotificationBadge :msg="msgSuccess" :is-error="false" v-if="msgSuccess != ''"/>
<p class="help-info">Te enviamos un código de verificación al correo electrónico, ingresalo para confirmar registro. No olvides revisar en la carpeta spam</p>
<CustomInput
label="Ingresa el código"
name="code"
type="text"
v-model:field="form.code"
/>
<div v-if="!loading" class="d-flex justify-content-center align-items-center mt-4">
<a
@click="resendCode()"
class="btn-text ms-2"><i class="fa-solid fa-rotate-right"></i> Reenviar código</a>
</div>
<Spiner v-if="loading" class="mt-5"/>
<input
v-else
type="submit"
class="btn-primary-lg btn-lg-block radius-1 mt-5" value="Continuar">
</form>
</div>
</div>
</template>
<style scoped>
.form-content {
width: 100%;
padding: 2rem 3rem;
}
.icon-success{
font-size: 5rem;
color: green;
}
.help-info {
margin-bottom: 2rem;
font-size: 1.2rem;
font-weight: 400;
color: #5a4b4b;
}
.msg-success {
margin-bottom: 2rem;
font-size: 1.4rem;
font-weight: 500;
color: #5a4b4b;
}
@media (max-width: 1224px) {
.form-content {
width: 80%;
}
}
@media (max-width: 768px) {
.form-content {
width: 100%;
padding: 2rem 1rem;
}
}
</style>