add: register & recovery password
This commit is contained in:
16
src/App.vue
Normal file
16
src/App.vue
Normal 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
220
src/assets/main.css
Normal 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
99
src/components/Footer.vue
Normal 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
110
src/components/Header.vue
Normal 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>
|
||||
47
src/components/ui/CustomInput.vue
Normal file
47
src/components/ui/CustomInput.vue
Normal 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>
|
||||
85
src/components/ui/Notification.vue
Normal file
85
src/components/ui/Notification.vue
Normal 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>
|
||||
56
src/components/ui/NotificationBadge.vue
Normal file
56
src/components/ui/NotificationBadge.vue
Normal 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>
|
||||
50
src/components/ui/Spiner.vue
Normal file
50
src/components/ui/Spiner.vue
Normal 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>
|
||||
12
src/helpers/date_formats.js
Normal file
12
src/helpers/date_formats.js
Normal 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',
|
||||
|
||||
})
|
||||
}
|
||||
18
src/helpers/validations.js
Normal file
18
src/helpers/validations.js
Normal 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;
|
||||
}
|
||||
};
|
||||
11
src/layouts/AdminLayout.vue
Normal file
11
src/layouts/AdminLayout.vue
Normal file
@@ -0,0 +1,11 @@
|
||||
<template>
|
||||
<RouterView />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
68
src/layouts/AuthLayout.vue
Normal file
68
src/layouts/AuthLayout.vue
Normal 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
9
src/lib/axios.js
Normal 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
14
src/main.js
Normal 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
45
src/router/index.js
Normal 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
83
src/services/auth.js
Normal 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
117
src/services/public.js
Normal 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
12
src/stores/counter.js
Normal 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 }
|
||||
})
|
||||
27
src/stores/notifications.js
Normal file
27
src/stores/notifications.js
Normal 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
6
src/views/HomeView.vue
Normal file
@@ -0,0 +1,6 @@
|
||||
<script setup>
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h1>HomeView</h1>
|
||||
</template>
|
||||
133
src/views/LoginView.vue
Normal file
133
src/views/LoginView.vue
Normal 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>
|
||||
235
src/views/RecoveryPasswordView.vue
Normal file
235
src/views/RecoveryPasswordView.vue
Normal 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
247
src/views/RegisterView.vue
Normal 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>
|
||||
Reference in New Issue
Block a user