Files
Webka/frontend/pages/login.vue
2026-09-06 16:23:09 +05:00

141 lines
4.1 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import { ArrowUpRight, LockKeyhole, Video, ShieldCheck } from "lucide-vue-next";
const { t } = useI18n();
useHead({ title: computed(() => `${t("login.title")} · Webka`) });
const session = useSessionStore();
const username = ref(""),
password = ref(""),
token = ref(""),
setup = ref(false),
loading = ref(true),
error = ref("");
const apiError = useApiError();
onMounted(async () => {
try {
if (await session.refresh()) {
await navigateTo(session.user?.role === "admin" ? "/host" : "/watch");
return;
}
setup.value = (
await $fetch<{ needs_setup: boolean }>("/api/bootstrap")
).needs_setup;
} catch (e) {
error.value = apiError(e);
} finally {
loading.value = false;
}
});
async function submit() {
error.value = "";
loading.value = true;
try {
if (setup.value)
await $fetch("/api/bootstrap", {
method: "POST",
body: {
username: username.value,
password: password.value,
setup_token: token.value,
},
});
await $fetch("/api/login", {
method: "POST",
body: { username: username.value, password: password.value },
});
await session.refresh();
await navigateTo(session.user?.role === "admin" ? "/host" : "/watch");
} catch (e) {
error.value = apiError(e);
try {
setup.value = (
await $fetch<{ needs_setup: boolean }>("/api/bootstrap")
).needs_setup;
} catch {}
} finally {
loading.value = false;
}
}
</script>
<template>
<div class="login-layout">
<div class="login-intro">
<span class="eyebrow"
><span class="status-dot" />{{ $t("login.local") }}</span
>
<h1>
{{ $t("login.headline") }}<br /><span>{{
$t("login.headlineAccent")
}}</span>
</h1>
<p>{{ $t("login.description") }}</p>
<div class="login-principle">
<ShieldCheck :size="22" /><span>{{ $t("login.privacy") }}</span>
</div>
</div>
<GlassCard class="login-card"
><div class="login-icon"><Video :size="27" /></div>
<h2>{{ $t(setup ? "login.setupTitle" : "login.title") }}</h2>
<p class="muted">
{{ $t(setup ? "login.setupDescription" : "login.subtitle") }}
</p>
<form @submit.prevent="submit">
<label
>{{ $t("accounts.username")
}}<input
v-model="username"
autocomplete="username"
required
minlength="3"
maxlength="40"
pattern="[A-Za-z0-9_.\-]+"
:placeholder="$t('login.usernamePlaceholder')"
/></label>
<label
>{{ $t("accounts.password")
}}<input
v-model="password"
type="password"
:autocomplete="setup ? 'new-password' : 'current-password'"
required
:minlength="setup ? 12 : 1"
maxlength="256"
:pattern="
setup ? '(?=.*[A-Za-zА-Яа-яЁёÀ-ÿ])(?=.*[0-9]).{12,}' : undefined
"
:placeholder="$t('login.passwordPlaceholder')"
/></label>
<template v-if="setup"
><p class="field-help">{{ $t("accounts.passwordHint") }}</p>
<label
>{{ $t("login.setupToken")
}}<input v-model="token" required autocomplete="off"
/></label>
<p class="field-help">{{ $t("login.tokenHint") }}</p></template
>
<p v-if="error" role="alert" class="error">{{ error }}</p>
<GlassButton
variant="primary"
type="submit"
class="full-width"
:disabled="loading"
>{{
$t(
loading
? "common.working"
: setup
? "login.create"
: "login.signin",
)
}}<ArrowUpRight :size="19"
/></GlassButton>
</form>
<div class="login-foot">
<LockKeyhole :size="14" />{{ $t("login.hostAccounts") }}
</div>
</GlassCard>
<div class="login-bottom">
WEBKA <span>01 / {{ $t("login.localUtility") }}</span>
</div>
</div>
</template>