169 lines
5.6 KiB
Vue
169 lines
5.6 KiB
Vue
<template>
|
|
<div>
|
|
<FAQ />
|
|
<div class="container">
|
|
<div class="block xl:grid grid-cols-2 gap-4">
|
|
<!-- BEGIN: Login Info -->
|
|
<LogoInfo />
|
|
<!-- END: Login Info -->
|
|
|
|
<!-- BEGIN: Login Form -->
|
|
<div class="h-screen xl:h-auto md:flex py-5 xl:py-0 my-10 xl:my-0">
|
|
<div class="md:hidden mb-6">
|
|
<a>
|
|
<Logo class="mx-auto" />
|
|
<div class="text-center text-white text-lg ml-3">
|
|
{{ $t('APP_TITLE') }}
|
|
</div>
|
|
</a>
|
|
</div>
|
|
|
|
<div
|
|
class="my-auto mx-auto xl:ml-20 bg-white dark:bg-darkmode-600 xl:bg-transparent px-5 sm:px-8 py-8 xl:p-0 rounded-md shadow-md xl:shadow-none w-full sm:w-3/4 lg:w-2/4 xl:w-auto">
|
|
<div class="flex items-center justify-between">
|
|
<h2 class="intro-x font-bold text-2xl xl:text-3xl text-center xl:text-left">
|
|
{{ $t("SIGN_IN") }}
|
|
</h2>
|
|
<Lang />
|
|
</div>
|
|
|
|
<div class="intro-x mt-2 text-slate-400 xl:hidden text-center">
|
|
{{ $t('SING_IN_MOTIVATION_MOBILE') }}
|
|
</div>
|
|
<div class="intro-x mt-8">
|
|
<input type="text" v-model.trim="validate.email.$model"
|
|
class="intro-x login__input form-control py-3 px-4 block mt-4"
|
|
:class="{ 'border-danger': validate.email.$error }" :placeholder="$t('EMAIL')" />
|
|
<template v-if="validate.email.$error">
|
|
<div v-for="(error, index) in validate.email.$errors" :key="index" class="text-danger mt-2">
|
|
{{ error.$message }}
|
|
</div>
|
|
</template>
|
|
<input type="password" v-model.trim="validate.password.$model"
|
|
class="intro-x login__input form-control py-3 px-4 block mt-4"
|
|
:class="{ 'border-danger': validate.password.$error }" :placeholder="$t('PASSWORD')"
|
|
@keyup.enter="onLogin" />
|
|
<template v-if="validate.password.$error">
|
|
<div v-for="(error, index) in validate.password.$errors" :key="index" class="text-danger mt-2">
|
|
{{ error.$message }}
|
|
</div>
|
|
</template>
|
|
</div>
|
|
<div class="intro-x flex text-slate-600 dark:text-slate-500 text-xs sm:text-sm mt-4">
|
|
<a type="button" class="flex items-center ml-auto cursor-pointer" @click="onForgotPassword">
|
|
{{ $t('FORGOT_PASSWORD') }}
|
|
<LoadingIcon icon="oval" class="w-4 h-4 ml-2 primary" v-if="isForgotPwdLoading" />
|
|
</a>
|
|
</div>
|
|
<div class="intro-x mt-5 xl:mt-8 text-center xl:text-left">
|
|
<button class="btn btn-primary py-3 px-4 w-full xl:mr-3 align-top custom-btns" @click.prevent="onLogin">
|
|
{{ $t('LOGIN') }}
|
|
<LoadingIcon icon="oval" color="white" class="w-4 h-4 ml-2" v-if="isLoading" />
|
|
</button>
|
|
</div>
|
|
<div class="intro-x mt-5 xl:mt-8 text-primary xl:text-left">
|
|
<i18n-t keypath="YOU_HAVE_NO_ACCOUNT" for="REGISTER" class="w-full text-center" tag="p">
|
|
<a @click.prevent="onRegister" href="#" class="font-bold underline">
|
|
{{ $t('REGISTER') }}
|
|
</a>
|
|
</i18n-t>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
<!-- END: Login Form -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { onMounted, reactive, toRefs, ref } from "vue";
|
|
import dom from "@left4code/tw-starter/dist/js/dom";
|
|
import { useVuelidate } from "@vuelidate/core";
|
|
import { required, helpers, minLength, email } from "@vuelidate/validators";
|
|
import { useAuthStore } from "@/stores";
|
|
import i18nn from "@/i18n";
|
|
import router from "@/router";
|
|
import Logo from "@/components/logo/Main.vue";
|
|
import LogoInfo from "@/components/logo-info/Main.vue";
|
|
import Lang from "@/components/lang/Main.vue";
|
|
import FAQ from "@/components/faq/Main.vue";
|
|
|
|
const formData = reactive({
|
|
email: "",
|
|
password: "",
|
|
});
|
|
|
|
const isLoading = ref(false);
|
|
const isForgotPwdLoading = ref(false);
|
|
|
|
const rules = {
|
|
email: {
|
|
required: helpers.withMessage(i18nn.global.t("REQUIRED_VALIDATION"), required),
|
|
email: helpers.withMessage(i18nn.global.t("EMAIL_VALIDATION"), email),
|
|
},
|
|
|
|
password: {
|
|
required: helpers.withMessage(i18nn.global.t("REQUIRED_VALIDATION"), required),
|
|
minLength: helpers.withMessage(
|
|
i18nn.global.t("MIN_LENGTH_VALIDATION", { min: 8 }), minLength(8)
|
|
)
|
|
},
|
|
};
|
|
|
|
const validate = useVuelidate(rules, toRefs(formData));
|
|
|
|
const onLogin = async () => {
|
|
// prevent double request
|
|
if (isLoading.value) return;
|
|
|
|
const authStore = useAuthStore();
|
|
|
|
validate.value.$touch();
|
|
|
|
// if form is invalid
|
|
if (validate.value.$invalid) return;
|
|
|
|
isLoading.value = true;
|
|
await authStore.login(formData.email, formData.password);
|
|
isLoading.value = false;
|
|
};
|
|
|
|
const onForgotPassword = async () => {
|
|
// prevent double request
|
|
if (isForgotPwdLoading.value) return;
|
|
|
|
validate.value.email.$touch();
|
|
|
|
// return if email is not valid
|
|
if (validate.value.email.$invalid) return;
|
|
|
|
isForgotPwdLoading.value = true;
|
|
|
|
const authStore = useAuthStore();
|
|
await authStore.forgotPassword(formData.email);
|
|
|
|
isForgotPwdLoading.value = false;
|
|
};
|
|
|
|
const onRegister = () => router.push({ path: "/register" });
|
|
|
|
onMounted(() => {
|
|
dom("body").removeClass("main").removeClass("error-page").addClass("login");
|
|
});
|
|
</script>
|
|
|
|
<style scoped>
|
|
.custom-btns {
|
|
min-width: 128px;
|
|
}
|
|
|
|
.form-check-input[type="radio"],
|
|
.login .login__input,
|
|
.form-select {
|
|
border-color: rgb(var(--color-slate-400) / var(--tw-border-opacity));
|
|
;
|
|
}
|
|
</style>
|
|
|