109 lines
3.6 KiB
Vue
109 lines
3.6 KiB
Vue
<script setup lang="ts">
|
|
import type { User } from "~/types";
|
|
|
|
const { login, user, refreshMe } = useAuth();
|
|
const { claimAnonymousPushSubscriptions, consumePendingPushRoute } = usePush();
|
|
const route = useRoute();
|
|
const email = ref("");
|
|
const password = ref("");
|
|
const showPassword = ref(false);
|
|
const error = ref("");
|
|
const redirectTarget = computed(() =>
|
|
typeof route.query.redirect === "string" ? route.query.redirect : "/"
|
|
);
|
|
|
|
watch(
|
|
user,
|
|
(currentUser) => {
|
|
if (currentUser) {
|
|
void navigateTo(redirectTarget.value);
|
|
}
|
|
},
|
|
{ immediate: true }
|
|
);
|
|
|
|
const handleSubmit = async () => {
|
|
error.value = "";
|
|
|
|
try {
|
|
const result = await useApi<{ token?: string; user: User }>("/auth/login", {
|
|
method: "POST",
|
|
body: { email: email.value, password: password.value }
|
|
});
|
|
|
|
await login(result.token ?? null, result.user);
|
|
await refreshMe();
|
|
await claimAnonymousPushSubscriptions();
|
|
const redirectedFromPush = await consumePendingPushRoute();
|
|
if (!redirectedFromPush) {
|
|
await navigateTo(redirectTarget.value);
|
|
}
|
|
} catch (submitError) {
|
|
error.value = submitError instanceof Error ? submitError.message : "Ошибка входа";
|
|
}
|
|
};
|
|
</script>
|
|
|
|
<template>
|
|
<section class="mx-auto grid min-h-[calc(100vh-12rem)] w-full items-center gap-8 md:max-w-6xl md:grid-cols-[minmax(0,1.1fr)_minmax(22rem,28rem)] md:[min-height:calc(100vh-10rem)]">
|
|
<div class="grid max-w-lg gap-3">
|
|
<p class="m-0 text-xs font-semibold uppercase tracking-[0.18em] text-(--muted)">Alpinbet</p>
|
|
<h1 class="m-0 text-4xl font-semibold leading-tight">Вход в систему</h1>
|
|
<p class="m-0 text-base leading-7 text-(--muted)">
|
|
Авторизуйтесь, чтобы открыть доступ к ботам, сигналам и настройкам уведомлений.
|
|
</p>
|
|
</div>
|
|
|
|
<form
|
|
class="grid w-full max-w-md gap-4 rounded-[28px] border p-4"
|
|
:style="{ borderColor: 'var(--border)', backgroundColor: 'var(--surface-strong)' }"
|
|
@submit.prevent="handleSubmit"
|
|
>
|
|
<p class="m-0 text-xs font-semibold uppercase tracking-[0.18em] text-(--muted)">Авторизация</p>
|
|
|
|
<label class="grid gap-1">
|
|
<span>Email</span>
|
|
<input v-model="email" type="email" autocomplete="email" />
|
|
</label>
|
|
|
|
<label class="grid gap-1">
|
|
<span>Пароль</span>
|
|
<div class="password-field">
|
|
<input
|
|
v-model="password"
|
|
:type="showPassword ? 'text' : 'password'"
|
|
autocomplete="current-password"
|
|
/>
|
|
<button
|
|
type="button"
|
|
class="password-field__toggle"
|
|
:aria-label="showPassword ? 'Скрыть пароль' : 'Показать пароль'"
|
|
@click="showPassword = !showPassword"
|
|
>
|
|
<i class="pi" :class="showPassword ? 'pi-eye-slash' : 'pi-eye'" aria-hidden="true" />
|
|
</button>
|
|
</div>
|
|
</label>
|
|
|
|
<NuxtLink class="justify-self-end text-sm text-(--accent-strong)" to="/forgot-password">
|
|
Забыли пароль?
|
|
</NuxtLink>
|
|
|
|
<p
|
|
v-if="error"
|
|
class="rounded-2xl p-4 text-sm whitespace-pre-line"
|
|
:style="{ backgroundColor: 'var(--danger-bg)', color: 'var(--danger-text)' }"
|
|
>
|
|
{{ error }}
|
|
</p>
|
|
|
|
<button type="submit">Войти</button>
|
|
|
|
<p class="m-0 text-(--muted)">
|
|
Нет аккаунта?
|
|
<NuxtLink class="text-(--accent-strong)" to="/register">Зарегистрироваться</NuxtLink>
|
|
</p>
|
|
</form>
|
|
</section>
|
|
</template>
|