196 lines
5.4 KiB
Vue
Executable File
196 lines
5.4 KiB
Vue
Executable File
<!-- ❗Errors in the form are set on line 60 -->
|
|
<script setup lang="ts">
|
|
import { VForm } from 'vuetify/components/VForm'
|
|
import { useGenerateImageVariant } from '@core/composable/useGenerateImageVariant'
|
|
import authV2LoginIllustrationBorderedDark from '@images/pages/auth-v2-login-illustration-bordered-dark.png'
|
|
import authV2LoginIllustrationBorderedLight from '@images/pages/auth-v2-login-illustration-bordered-light.png'
|
|
import authV2LoginIllustrationDark from '@images/pages/auth-v2-login-illustration-dark.png'
|
|
import authV2LoginIllustrationLight from '@images/pages/auth-v2-login-illustration-light.png'
|
|
import authV2MaskDark from '@images/pages/misc-mask-dark.png'
|
|
import authV2MaskLight from '@images/pages/misc-mask-light.png'
|
|
import { VNodeRenderer } from '@layouts/components/VNodeRenderer'
|
|
import { userStore } from '@stores/user.store'
|
|
import { themeConfig } from '@themeConfig'
|
|
|
|
const authThemeImg = useGenerateImageVariant(authV2LoginIllustrationLight, authV2LoginIllustrationDark, authV2LoginIllustrationBorderedLight, authV2LoginIllustrationBorderedDark, true)
|
|
|
|
const authThemeMask = useGenerateImageVariant(authV2MaskLight, authV2MaskDark)
|
|
|
|
definePage({
|
|
meta: {
|
|
layout: 'blank',
|
|
unauthenticatedOnly: true,
|
|
},
|
|
})
|
|
|
|
const isPasswordVisible = ref(false)
|
|
|
|
const route = useRoute()
|
|
const router = useRouter()
|
|
|
|
const ability = useAbility()
|
|
|
|
const errors = ref<Record<string, string | undefined>>({
|
|
username: undefined,
|
|
password: undefined,
|
|
})
|
|
|
|
const refVForm = ref<VForm>()
|
|
|
|
const credentials = ref({
|
|
username: '',
|
|
password: '',
|
|
})
|
|
|
|
const rememberMe = ref(false)
|
|
const isSnackbarVisibility = ref(false)
|
|
|
|
const useUserStore = userStore()
|
|
|
|
const login = async () => {
|
|
try {
|
|
await useUserStore.login(credentials.value.username, credentials.value.password, ability)
|
|
|
|
// Redirect to `to` query if exist or redirect to index route
|
|
// ❗ nextTick is required to wait for DOM updates and later redirect
|
|
await nextTick(() => {
|
|
router.replace(route.query.to ? String(route.query.to) : '/')
|
|
})
|
|
}
|
|
catch (err) {
|
|
// ❗ show invalid credentials error
|
|
isSnackbarVisibility.value = true
|
|
}
|
|
}
|
|
|
|
const onSubmit = () => {
|
|
refVForm.value?.validate()
|
|
.then(({ valid: isValid }) => {
|
|
if (isValid)
|
|
login()
|
|
})
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<VRow
|
|
no-gutters
|
|
class="auth-wrapper bg-surface"
|
|
>
|
|
<VCol
|
|
lg="8"
|
|
class="d-none d-lg-flex"
|
|
>
|
|
<div class="position-relative bg-background rounded-lg w-100 ma-8 me-0">
|
|
<div class="d-flex align-center justify-center w-100 h-100">
|
|
<VImg
|
|
max-width="505"
|
|
:src="authThemeImg"
|
|
class="auth-illustration mt-16 mb-2"
|
|
/>
|
|
</div>
|
|
|
|
<VImg
|
|
:src="authThemeMask"
|
|
class="auth-footer-mask"
|
|
/>
|
|
</div>
|
|
</VCol>
|
|
|
|
<VCol
|
|
cols="12"
|
|
lg="4"
|
|
class="auth-card-v2 d-flex align-center justify-center"
|
|
>
|
|
<VCard
|
|
flat
|
|
:max-width="500"
|
|
class="mt-12 mt-sm-0 pa-4"
|
|
>
|
|
<VCardText>
|
|
<VNodeRenderer
|
|
:nodes="themeConfig.app.logo"
|
|
class="mb-6"
|
|
/>
|
|
|
|
<h4 class="text-h4 mb-1">
|
|
Welcome to <span class="text-capitalize"> {{ themeConfig.app.title }} </span>! 👋🏻
|
|
</h4>
|
|
<p class="mb-0">
|
|
Please sign-in to your account and start the adventure
|
|
</p>
|
|
</VCardText>
|
|
<VCardText>
|
|
<VForm
|
|
ref="refVForm"
|
|
@submit.prevent="onSubmit"
|
|
>
|
|
<VRow>
|
|
<!-- username -->
|
|
<VCol cols="12">
|
|
<AppTextField
|
|
v-model="credentials.username"
|
|
label="Username"
|
|
placeholder="First name"
|
|
type="username"
|
|
autofocus
|
|
:rules="[requiredValidator]"
|
|
:error-messages="errors.username"
|
|
/>
|
|
</VCol>
|
|
|
|
<!-- password -->
|
|
<VCol cols="12">
|
|
<AppTextField
|
|
v-model="credentials.password"
|
|
label="Password"
|
|
placeholder="············"
|
|
:rules="[requiredValidator]"
|
|
:type="isPasswordVisible ? 'text' : 'password'"
|
|
:error-messages="errors.password"
|
|
:append-inner-icon="isPasswordVisible ? 'tabler-eye-off' : 'tabler-eye'"
|
|
@click:append-inner="isPasswordVisible = !isPasswordVisible"
|
|
/>
|
|
|
|
<div class="d-flex align-center flex-wrap justify-space-between mt-1 mb-4">
|
|
<VCheckbox
|
|
v-model="rememberMe"
|
|
label="Remember me"
|
|
/>
|
|
</div>
|
|
|
|
<VBtn
|
|
block
|
|
type="submit"
|
|
>
|
|
Login
|
|
</VBtn>
|
|
</VCol>
|
|
</VRow>
|
|
</VForm>
|
|
</VCardText>
|
|
</VCard>
|
|
</VCol>
|
|
</VRow>
|
|
<!-- Snackbar -->
|
|
<VSnackbar
|
|
v-model="isSnackbarVisibility"
|
|
location="center"
|
|
>
|
|
Incorrect username or password ...
|
|
|
|
<template #actions>
|
|
<VBtn
|
|
color="error"
|
|
@click="isSnackbarVisibility = false"
|
|
>
|
|
Close
|
|
</VBtn>
|
|
</template>
|
|
</VSnackbar>
|
|
</template>
|
|
|
|
<style lang="scss">
|
|
@use "@core/scss/template/pages/page-auth.scss";
|
|
</style>
|