Files
tgCrewAdmin/src/pages/LoginPage.vue
2025-08-02 11:20:01 +03:00

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>