Files
veridian/app/pages/auth/login.vue
T

123 lines
3.6 KiB
Vue

<script setup lang="ts">
import { authClient } from '~~/lib/auth-client';
import { deriveKey } from '~/utils/crypto';
definePageMeta({
layout: 'auth',
});
const to = useRoute().query.to as string | undefined;
const form = reactive({
email: '',
password: '',
});
const loading = ref(false);
const emailInputEl = ref<HTMLInputElement | null>(null);
const passwordInputEl = ref<HTMLInputElement | null>(null);
const tempForm = {
email: '',
password: '',
};
// prevent text fields from clearing on hydration
onBeforeMount(() => {
tempForm.email = (document.getElementById('email') as HTMLInputElement)?.value ?? '';
tempForm.password = (document.getElementById('password') as HTMLInputElement)?.value ?? '';
});
const hydrated = ref(false);
onMounted(() => {
form.email = tempForm.email;
form.password = tempForm.password;
hydrated.value = true;
emailInputEl.value!.addEventListener('input', () => {
emailInputEl.value!.setCustomValidity('');
});
passwordInputEl.value!.addEventListener('input', () => {
passwordInputEl.value!.setCustomValidity('');
});
});
const submit = async () => {
const inputs = [emailInputEl, passwordInputEl];
for (const input of inputs) {
if (input.value?.validity.valid === false) {
input.value!.reportValidity();
return;
}
}
loading.value = true;
const { data, error } = await authClient.signIn.email({
email: form.email,
password: form.password,
});
loading.value = false;
if (error) {
const errorCode = error.code! as keyof typeof authClient.$ERROR_CODES;
// TODO: i18n
// ref https://www.better-auth.com/docs/concepts/client#error-codes
switch (errorCode) {
case 'INVALID_PASSWORD':
passwordInputEl.value!.setCustomValidity(error.message!);
passwordInputEl.value!.reportValidity();
break;
case 'ACCOUNT_NOT_FOUND':
case 'USER_NOT_FOUND':
case 'USER_EMAIL_NOT_FOUND':
emailInputEl.value!.setCustomValidity(error.message!);
emailInputEl.value!.reportValidity();
break;
default:
console.log(error);
alert(`Something went wrong. ${error.message}`);
break;
}
return;
}
const key = await deriveKey(form.password, data.user.id);
localStorage.setItem('encryptionKey', JSON.stringify(key));
// force a session refetch
clearNuxtData();
// success
const triplit = useTriplitClient();
if ('startSession' in triplit) {
await triplit.startSession(data.token);
}
return navigateTo(to ?? '/');
};
</script>
<template>
<h1 class="font-bold text-center mb-4">Login</h1>
<form class="flex flex-col [&>input]:mb-2" @submit.prevent="submit">
<label for="email">Email</label>
<input required pattern=".{1,}@.{1,}\..{2,3}" ref="emailInputEl" type="email" autocomplete="email" id="email"
v-model="form.email" />
<label for="password">Password</label>
<input required minlength="8" maxlength="128" ref="passwordInputEl" type="password"
autocomplete="current-password" id="password" v-model="form.password" />
<button :disabled="!hydrated" class="accent" type="submit">
<Icon v-if="loading" class="text-6" name="svg-spinners:90-ring-with-bg" />
<span v-else>Login</span>
</button>
</form>
<p class="text-center">Dont have an account? <a
:href="to ? `/auth/register?to=${to}` : '/auth/register'">Register</a></p>
</template>