241 lines
6.5 KiB
Vue
241 lines
6.5 KiB
Vue
<template>
|
|
<q-page
|
|
class="q-ma-none w100"
|
|
>
|
|
<q-scroll-area
|
|
style="height: 100vh"
|
|
>
|
|
<div class="flex items-center justify-center" style="height: 100vh">
|
|
<q-card
|
|
id="login_block"
|
|
flat
|
|
class="flex column no-wrap items-center justify-between q-px-xl q-py-md login-card"
|
|
style="max-width: 400px;"
|
|
>
|
|
<base-logo
|
|
class="col-grow q-pa-lg text-h5"
|
|
/>
|
|
|
|
<div class="flex column w100">
|
|
<q-input
|
|
v-model="login"
|
|
autofocus
|
|
dense
|
|
filled
|
|
class="q-mb-sm"
|
|
:label="$t('login__email')"
|
|
:rules="validationRules.email"
|
|
lazy-rules="ondemand"
|
|
no-error-icon
|
|
@focus="emailInput?.resetValidation()"
|
|
@blur="delayValidity('login')"
|
|
ref="emailInput"
|
|
/>
|
|
|
|
<q-input
|
|
v-model="password"
|
|
dense
|
|
filled
|
|
:label="$t('login__password')"
|
|
class="q-mb-none q-mt-xs"
|
|
:type="isPwd ? 'password' : 'text'"
|
|
hide-hint
|
|
:hint="passwordHint"
|
|
:rules="validationRules.password"
|
|
lazy-rules="ondemand"
|
|
no-error-icon
|
|
@focus="passwordInput?.resetValidation()"
|
|
@blur="delayValidity('password')"
|
|
ref="passwordInput"
|
|
>
|
|
<template #append>
|
|
<q-icon
|
|
color="grey-5"
|
|
:name="isPwd ? 'mdi-eye-off-outline' : 'mdi-eye-outline'"
|
|
class="cursor-pointer"
|
|
@click="isPwd = !isPwd"
|
|
/>
|
|
</template>
|
|
|
|
</q-input>
|
|
|
|
<div class="self-end">
|
|
<q-btn
|
|
@click.prevent="forgotPwd"
|
|
flat
|
|
no-caps
|
|
dense
|
|
class="text-grey"
|
|
>
|
|
{{$t('login__forgot_password')}}
|
|
</q-btn>
|
|
</div>
|
|
</div>
|
|
<q-btn
|
|
@click="sendAuth()"
|
|
color="primary"
|
|
class="w100 q-my-md"
|
|
:disabled="!isEmailValid || !isPasswordValid"
|
|
>
|
|
{{$t('login__sign_in')}}
|
|
</q-btn>
|
|
|
|
<q-btn
|
|
flat
|
|
sm
|
|
no-caps
|
|
class="q-my-md"
|
|
color="primary"
|
|
@click="createAccount"
|
|
>
|
|
{{$t('login__register')}}
|
|
</q-btn>
|
|
|
|
<div
|
|
v-if="isTelegramApp"
|
|
id="alt_login"
|
|
class="flex w100 column items-center "
|
|
>
|
|
<div
|
|
class="orline w100 text-grey"
|
|
>
|
|
<span class="q-mx-sm text-caption">{{$t('login__or_continue_as')}}</span>
|
|
</div>
|
|
<q-btn
|
|
flat
|
|
sm
|
|
no-caps
|
|
color="primary"
|
|
class="q-my-md"
|
|
@click="handleTelegramLogin"
|
|
>
|
|
<div class="flex items-center text-blue">
|
|
<q-avatar size="md" class="q-mr-sm">
|
|
<q-img v-if="tgUser?.photo_url" :src="tgUser.photo_url"/>
|
|
<q-icon v-else size="md" class="q-mr-none" name="telegram"/>
|
|
</q-avatar>
|
|
|
|
<span>
|
|
{{
|
|
tgUser?.first_name +
|
|
(tgUser?.first_name && tgUser?.last_name ? ' ' : '') +
|
|
tgUser?.last_name +
|
|
(!(tgUser?.first_name || tgUser?.last_name) ? tgUser?.username : '')
|
|
}}
|
|
</span>
|
|
</div>
|
|
</q-btn>
|
|
</div>
|
|
</q-card>
|
|
</div>
|
|
</q-scroll-area>
|
|
</q-page>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, computed, inject, onUnmounted } from 'vue'
|
|
import { useRouter } from 'vue-router'
|
|
import baseLogo from 'components/BaseLogo.vue'
|
|
import { useI18n } from "vue-i18n"
|
|
import { useAuthStore } from 'stores/auth'
|
|
import type { WebApp } from '@twa-dev/types'
|
|
import { QInput } from 'quasar'
|
|
|
|
type ValidationRule = (val: string) => boolean | string
|
|
|
|
const tg = inject('tg') as WebApp
|
|
const tgUser = tg.initDataUnsafe.user
|
|
|
|
const authStore = useAuthStore()
|
|
const router = useRouter()
|
|
const { t } = useI18n()
|
|
|
|
const login = ref<string>('')
|
|
const password = ref<string>('')
|
|
const isPwd = ref<boolean>(true)
|
|
|
|
const emailInput = ref<InstanceType<typeof QInput>>()
|
|
const passwordInput = ref<InstanceType<typeof QInput>>()
|
|
|
|
const validationRules = {
|
|
email: [(val: string) => /.+@.+\..+/.test(val) || t('login__incorrect_email')] as [ValidationRule],
|
|
password: [(val: string) => val.length >= 8 || t('login__password_require')] as [ValidationRule]
|
|
}
|
|
|
|
const isEmailValid = computed(() =>
|
|
validationRules.email.every(f => f(login.value) === true)
|
|
)
|
|
|
|
const isPasswordValid = computed(() =>
|
|
validationRules.password.every(f => f(password.value) === true)
|
|
)
|
|
|
|
const passwordHint = computed(() => {
|
|
const result = validationRules.password[0](password.value)
|
|
return typeof result === 'string' ? result : ''
|
|
})
|
|
|
|
// fix validity problem with router.push
|
|
type Field = 'login' | 'password'
|
|
|
|
const validateTimerId = ref<Record<Field, ReturnType<typeof setTimeout> | null>>({
|
|
login: null,
|
|
password: null
|
|
})
|
|
|
|
const delayValidity = (type: Field) => {
|
|
validateTimerId.value[type] = setTimeout(() => {
|
|
void (async () => {
|
|
if (validateTimerId.value[type] !== null) {
|
|
clearTimeout(validateTimerId.value[type])
|
|
}
|
|
if (type === 'login' && login.value !== '') await emailInput.value?.validate()
|
|
if (type === 'password' && password.value !== '') await passwordInput.value?.validate()
|
|
})()
|
|
}, 500)
|
|
}
|
|
|
|
async function sendAuth() {
|
|
try { void await authStore.loginWithCredentials(login.value, password.value) }
|
|
catch (error) {
|
|
console.error(error)
|
|
}
|
|
await router.push({ name: 'projects' })
|
|
}
|
|
|
|
|
|
async function forgotPwd() {
|
|
sessionStorage.setItem('pendingLogin', login.value)
|
|
await router.push({ name: 'recovery_password' })
|
|
}
|
|
|
|
async function createAccount() {
|
|
sessionStorage.setItem('pendingLogin', login.value)
|
|
await router.push({ name: 'create_account' })
|
|
}
|
|
|
|
const isTelegramApp = computed(() => {
|
|
// @ts-expect-ignore
|
|
return !!window.Telegram?.WebApp?.initData
|
|
})
|
|
|
|
async function handleTelegramLogin () {
|
|
// @ts-expect-ignore
|
|
const initData = window.Telegram.WebApp.initData
|
|
await authStore.loginWithTelegram(initData)
|
|
await router.push({ name: 'projects' })
|
|
}
|
|
|
|
onUnmounted(() => {
|
|
Object.values(validateTimerId.value).forEach(timer => timer && clearTimeout(timer))
|
|
})
|
|
|
|
</script>
|
|
|
|
<style scoped>
|
|
.login-card {
|
|
opacity: 0.9 !important;
|
|
border-radius: var(--top-raduis);
|
|
}
|
|
</style>
|