Sign in, register, forgot and reset each wrote the same page: a centred column, the app icon, a title, a subtitle, the form and a footer link. That is now components/AuthLayout.vue, with the title as a prop and the subtitle, the form, the footer and anything after it as slots. The footer links wear the new .text-link class. Markup and classes are unchanged, so the pages render as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
92 lines
2.7 KiB
Vue
92 lines
2.7 KiB
Vue
<script setup lang="ts">
|
|
import { computed, ref } from "vue";
|
|
import { useRoute, useRouter } from "vue-router";
|
|
import { useSessionStore } from "../stores/session";
|
|
import { useConfigStore } from "../stores/config";
|
|
import BaseInput from "../components/BaseInput.vue";
|
|
import AuthLayout from "../components/AuthLayout.vue";
|
|
import BaseButton from "../components/BaseButton.vue";
|
|
import { errorMessage } from "../api/errors";
|
|
|
|
const session = useSessionStore();
|
|
const config = useConfigStore();
|
|
const router = useRouter();
|
|
const route = useRoute();
|
|
|
|
// The token from an invite link (/register?invite=…). It lets this one account in
|
|
// while registration is closed.
|
|
const invite = computed(() => (typeof route.query.invite === "string" ? route.query.invite : ""));
|
|
|
|
const displayName = ref("");
|
|
const email = ref("");
|
|
const password = ref("");
|
|
const error = ref("");
|
|
const loading = ref(false);
|
|
|
|
async function submit() {
|
|
error.value = "";
|
|
if (password.value.length < config.minPasswordLength) {
|
|
error.value = `Password must be at least ${config.minPasswordLength} characters.`;
|
|
return;
|
|
}
|
|
loading.value = true;
|
|
try {
|
|
await session.register(email.value, password.value, displayName.value, invite.value || undefined);
|
|
await router.replace("/");
|
|
} catch (e) {
|
|
error.value = errorMessage(e, "Could not create your account.");
|
|
} finally {
|
|
loading.value = false;
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<AuthLayout :title="invite ? `You're invited` : 'Create your space'">
|
|
<template #subtitle>{{ invite ? `Create your account on ${config.siteName}.` : "Start capturing in seconds." }}</template>
|
|
|
|
<form class="flex flex-col gap-4" novalidate @submit.prevent="submit">
|
|
<BaseInput
|
|
id="display_name"
|
|
v-model="displayName"
|
|
label="Name"
|
|
autocomplete="name"
|
|
placeholder="What should we call you?"
|
|
/>
|
|
<BaseInput
|
|
id="email"
|
|
v-model="email"
|
|
label="Email"
|
|
type="email"
|
|
autocomplete="email"
|
|
placeholder="you@example.com"
|
|
required
|
|
/>
|
|
<BaseInput
|
|
id="password"
|
|
v-model="password"
|
|
label="Password"
|
|
type="password"
|
|
autocomplete="new-password"
|
|
:placeholder="`At least ${config.minPasswordLength} characters`"
|
|
required
|
|
/>
|
|
<p
|
|
v-if="error"
|
|
role="alert"
|
|
class="alert-error"
|
|
>
|
|
{{ error }}
|
|
</p>
|
|
<BaseButton type="submit" :loading="loading">Create account</BaseButton>
|
|
</form>
|
|
|
|
<template #footer>
|
|
Already have an account?
|
|
<RouterLink to="/login" class="text-link"
|
|
>Sign in</RouterLink
|
|
>
|
|
</template>
|
|
</AuthLayout>
|
|
</template>
|