DUNGEONS-34: implement Webka local streaming app
Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
140
frontend/pages/login.vue
Normal file
140
frontend/pages/login.vue
Normal file
@@ -0,0 +1,140 @@
|
||||
<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>
|
||||
Reference in New Issue
Block a user