feat: improve auth pages
This commit is contained in:
@@ -135,7 +135,7 @@ onMounted(() => { inputRef.value?.focus(); });
|
|||||||
<!-- AGENT ROW -->
|
<!-- AGENT ROW -->
|
||||||
<div v-if="item.type === 'agent'" :id="item.uiId" role="option"
|
<div v-if="item.type === 'agent'" :id="item.uiId" role="option"
|
||||||
:aria-selected="selectedIndex === index" @click="selectItem(item.uiId)"
|
:aria-selected="selectedIndex === index" @click="selectItem(item.uiId)"
|
||||||
class="group flex items-center px-3 h-10 rounded-md cursor-pointer transition-colors mt-2 hover:bg-[var(--color-hover)]"
|
class="group flex items-center px-3 h-10 rounded-md cursor-pointer transition-colors hover:bg-[var(--color-hover)]"
|
||||||
:class="selectedIndex === index ? 'bg-[var(--color-hover)] text-[var(--text-primary)]' : 'text-[var(--text-secondary)]'">
|
:class="selectedIndex === index ? 'bg-[var(--color-hover)] text-[var(--text-primary)]' : 'text-[var(--text-secondary)]'">
|
||||||
<div
|
<div
|
||||||
:class="['mr-2 w-[28px] h-[28px] flex-shrink-0 rounded-lg overflow-hidden bg-[var(--bg-surface)] flex items-center justify-center', item?.imageUrl ? '' : 'border border-[var(--color-border)]']">
|
:class="['mr-2 w-[28px] h-[28px] flex-shrink-0 rounded-lg overflow-hidden bg-[var(--bg-surface)] flex items-center justify-center', item?.imageUrl ? '' : 'border border-[var(--color-border)]']">
|
||||||
@@ -152,8 +152,8 @@ onMounted(() => { inputRef.value?.focus(); });
|
|||||||
class="group flex items-center h-10 pr-3 rounded-md cursor-pointer transition-colors relative ml-10 hover:bg-[var(--color-hover)]"
|
class="group flex items-center h-10 pr-3 rounded-md cursor-pointer transition-colors relative ml-10 hover:bg-[var(--color-hover)]"
|
||||||
:class="selectedIndex === index ? 'bg-[var(--color-hover)] text-[var(--text-primary)]' : 'text-[var(--text-secondary)]'">
|
:class="selectedIndex === index ? 'bg-[var(--color-hover)] text-[var(--text-primary)]' : 'text-[var(--text-secondary)]'">
|
||||||
<!-- Visual Tree Lines -->
|
<!-- Visual Tree Lines -->
|
||||||
<div class="absolute -left-4 top-0 bottom-0 w-[1px] h-[110%] bg-[var(--color-border)]"></div>
|
<div class="absolute -left-4 top-0 bottom-0 w-[1px] h-[110%] bg-[var(--text-dim)]"></div>
|
||||||
<div class="absolute -left-4 top-5 w-3 h-[1px] bg-[var(--color-border)]"></div>
|
<div class="absolute -left-4 top-5 w-3 h-[1px] bg-[var(--text-dim)]"></div>
|
||||||
|
|
||||||
<span class="i-mynaui-hash mx-2 shrink-0 text-[var(--text-dim)] w-4 h-4"></span>
|
<span class="i-mynaui-hash mx-2 shrink-0 text-[var(--text-dim)] w-4 h-4"></span>
|
||||||
<span class="text-sm truncate">{{ item.name }}</span>
|
<span class="text-sm truncate">{{ item.name }}</span>
|
||||||
|
|||||||
@@ -151,7 +151,7 @@ const navKind = computed(() => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex justify-end gap-1">
|
<div class="flex justify-end gap-1">
|
||||||
<ThemeSwitcher />
|
<ThemeSwitcher size="small" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { DropdownItem } from '~/types/dropdown';
|
import type { DropdownItem } from '~/types/dropdown';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
size: 'small' | 'medium' | 'large';
|
||||||
|
}>();
|
||||||
|
|
||||||
type Theme = 'light' | 'dark' | 'system';
|
type Theme = 'light' | 'dark' | 'system';
|
||||||
|
|
||||||
const { updateSettings, settings } = await useUserSettings();
|
const { updateSettings, settings } = await useUserSettings();
|
||||||
@@ -34,7 +38,12 @@ const toggleDropdown = (e: MouseEvent) => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<button aria-label="Open theme switcher" @click="toggleDropdown"
|
<button aria-label="Open theme switcher" @click="toggleDropdown"
|
||||||
class="h-7 w-7 flex items-center justify-center rounded-lg hover:bg-[var(--color-hover)] focus-visible:bg-[var(--color-hover)] transition-colors active:text-[var(--text-primary)]">
|
class="flex items-center justify-center rounded-lg hover:bg-[var(--color-hover)] focus-visible:bg-[var(--color-hover)] transition-colors active:text-[var(--text-primary)]"
|
||||||
<span :class="['text-5 pointer-events-none', currentOption!.icon]"></span>
|
:class="{
|
||||||
|
'h-7 w-7 text-5': size === 'small',
|
||||||
|
'h-9 w-9 text-6': size === 'medium',
|
||||||
|
'h-11 w-11 text-7': size === 'large',
|
||||||
|
}">
|
||||||
|
<span :class="['pointer-events-none', currentOption!.icon]"></span>
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
+17
-4
@@ -1,14 +1,27 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
// TODO: when a user changes the color scheme and then signs in/up, the color
|
||||||
|
// scheme will not be updated in the user's settings.
|
||||||
|
const { colorScheme } = await useUserSettings();
|
||||||
|
|
||||||
|
useHead({
|
||||||
|
htmlAttrs: {
|
||||||
|
class: colorScheme.class
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
preloadRouteComponents('/');
|
preloadRouteComponents('/');
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="h-full w-full grid place-items-center">
|
<main
|
||||||
<div
|
class="bg-[var(--bg-surface)] flex flex-col h-full w-full border border-solid border-[var(--color-border)] rounded-lg overflow-hidden">
|
||||||
class="max-w-xs p-4 bg-[var(--bg-surface)] border border-solid border-[var(--color-border)] w-full rounded-lg">
|
<div class="flex-1 overflow-y-auto">
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</main>
|
||||||
|
<ClientOnly>
|
||||||
|
<Dropdown />
|
||||||
|
</ClientOnly>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|||||||
+58
-13
@@ -2,7 +2,7 @@
|
|||||||
import { deriveKey } from '~/utils/crypto';
|
import { deriveKey } from '~/utils/crypto';
|
||||||
import { initSettings } from '~/utils/settings';
|
import { initSettings } from '~/utils/settings';
|
||||||
|
|
||||||
const { client: authClient, signIn } = useAuth();
|
const { client: authClient, signIn, session } = useAuth();
|
||||||
|
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
layout: 'auth',
|
layout: 'auth',
|
||||||
@@ -10,6 +10,12 @@ definePageMeta({
|
|||||||
|
|
||||||
const to = useRoute().query.to as string | undefined;
|
const to = useRoute().query.to as string | undefined;
|
||||||
|
|
||||||
|
if (session.value !== null) {
|
||||||
|
await navigateTo(to ?? '/');
|
||||||
|
}
|
||||||
|
|
||||||
|
const config = useRuntimeConfig();
|
||||||
|
|
||||||
const form = reactive({
|
const form = reactive({
|
||||||
email: '',
|
email: '',
|
||||||
password: '',
|
password: '',
|
||||||
@@ -102,19 +108,58 @@ const submit = async () => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<h1 class="font-bold text-center mb-4">Login</h1>
|
<div class="relative flex flex-col items-center justify-center w-full h-full p-4 overflow-hidden">
|
||||||
<form class="flex flex-col [&>input]:mb-2" @submit.prevent="submit">
|
<!-- Background decorative blobs -->
|
||||||
<label for="email">Email</label>
|
<div
|
||||||
<input required pattern=".{1,}@.{1,}\..{2,3}" ref="emailInputEl" type="email" autocomplete="email" id="email"
|
class="absolute -top-24 -left-24 w-96 h-96 bg-[var(--color-accent)] opacity-10 blur-[100px] rounded-full pointer-events-none">
|
||||||
v-model="form.email" />
|
</div>
|
||||||
<label for="password">Password</label>
|
<div
|
||||||
|
class="absolute -bottom-24 -right-24 w-96 h-96 bg-[var(--color-accent)] opacity-5 blur-[100px] rounded-full pointer-events-none">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="z-10 max-w-sm w-full">
|
||||||
|
<div class="flex flex-col items-center mb-4 text-center">
|
||||||
|
<h1 class="text-3xl font-bold tracking-tight">Veridian</h1>
|
||||||
|
<p class="text-[var(--text-secondary)]">Sign in to your account</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="rounded-2xl">
|
||||||
|
<form class="flex flex-col gap-2" @submit.prevent="submit">
|
||||||
|
<div class="flex flex-col gap-1.5">
|
||||||
|
<label for="email"
|
||||||
|
class="text-sm font-medium uppercase tracking-wider text-[var(--text-secondary)] ml-1">Email</label>
|
||||||
|
<input required pattern=".{1,}@.{1,}\..{2,3}" ref="emailInputEl" type="email"
|
||||||
|
autocomplete="email" id="email" v-model="form.email" placeholder="name@example.com"
|
||||||
|
class="focus:ring-2 focus:ring-[var(--color-accent)]/20 border border-[var(--color-border)] transition-all" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-col gap-1.5">
|
||||||
|
<label for="password"
|
||||||
|
class="text-sm font-medium uppercase tracking-wider text-[var(--text-secondary)] ml-1">Password</label>
|
||||||
<input required minlength="8" maxlength="128" ref="passwordInputEl" type="password"
|
<input required minlength="8" maxlength="128" ref="passwordInputEl" type="password"
|
||||||
autocomplete="current-password" id="password" v-model="form.password" />
|
autocomplete="current-password" id="password" v-model="form.password" placeholder="••••••••"
|
||||||
<button :disabled="!hydrated" class="accent" type="submit">
|
class="focus:ring-2 focus:ring-[var(--color-accent)]/20 border border-[var(--color-border)] transition-all" />
|
||||||
<span v-if="loading" class="i-svg-spinners-90-ring-with-bg text-6"></span>
|
</div>
|
||||||
<span v-else>Login</span>
|
|
||||||
|
<button :disabled="!hydrated"
|
||||||
|
class="accent w-full mt-2 h-11 flex items-center justify-center font-semibold" type="submit">
|
||||||
|
<span v-if="loading" class="i-svg-spinners-90-ring-with-bg text-xl"></span>
|
||||||
|
<span v-else>Sign In</span>
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
<p class="text-center">Dont have an account? <a
|
</div>
|
||||||
:href="to ? `/auth/register?to=${to}` : '/auth/register'">Register</a></p>
|
|
||||||
|
<p v-if="!config.public.disableSignup" class="text-sm text-center mt-3 text-[var(--text-secondary)]">
|
||||||
|
New to Veridian?
|
||||||
|
<NuxtLink :to="to ? `/auth/register?to=${to}` : '/auth/register'"
|
||||||
|
class="text-[var(--color-accent)] font-medium hover:underline">
|
||||||
|
Create an account
|
||||||
|
</NuxtLink>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="absolute bottom-4 right-4">
|
||||||
|
<ThemeSwitcher size="medium" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
+74
-18
@@ -1,4 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { deriveKey } from '~/utils/crypto';
|
||||||
|
import { initSettings } from '~/utils/settings';
|
||||||
|
|
||||||
const { client: authClient, signUp, session } = useAuth();
|
const { client: authClient, signUp, session } = useAuth();
|
||||||
|
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
@@ -10,11 +13,11 @@ if (session.value !== null) {
|
|||||||
await navigateTo(to ?? '/');
|
await navigateTo(to ?? '/');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (import.meta.server) {
|
const config = useRuntimeConfig();
|
||||||
if (process.env.DISABLE_SIGNUP?.toLowerCase() === 'true' || process.env.DISABLE_SIGNUP === '1') {
|
|
||||||
|
if (config.public.disableSignup) {
|
||||||
await navigateTo(to ? `/auth/login?to=${to}` : '/auth/login');
|
await navigateTo(to ? `/auth/login?to=${to}` : '/auth/login');
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
const form = reactive({
|
const form = reactive({
|
||||||
name: '',
|
name: '',
|
||||||
@@ -138,23 +141,76 @@ const submit = async () => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<h1 class="font-bold text-center mb-4">Register</h1>
|
<div class="relative flex flex-col items-center justify-center w-full h-full p-4 overflow-hidden">
|
||||||
<form class="flex flex-col [&>input]:mb-2" @submit.prevent="submit">
|
<!-- Background decorative blobs -->
|
||||||
<label for="name">Name</label>
|
<div
|
||||||
<input required ref="nameInputEl" type="text" id="name" v-model="form.name" />
|
class="absolute -top-24 -right-24 w-96 h-96 bg-[var(--color-accent)] opacity-10 blur-[100px] rounded-full pointer-events-none">
|
||||||
<label for="email">Email</label>
|
</div>
|
||||||
<input required pattern=".{1,}@.{1,}\..{2,3}" ref="emailInputEl" type="email" autocomplete="email" id="email"
|
<div
|
||||||
v-model="form.email" />
|
class="absolute -bottom-24 -left-24 w-96 h-96 bg-[var(--color-accent)] opacity-5 blur-[100px] rounded-full pointer-events-none">
|
||||||
<label for="password">Password</label>
|
</div>
|
||||||
<input required minlength="8" maxlength="128" ref="passwordInputEl" type="password" autocomplete="new-password"
|
|
||||||
id="password" v-model="form.password" />
|
<div class="z-10 max-w-sm w-full">
|
||||||
<label for="confirmPassword">Confirm Password</label>
|
<div class="flex flex-col items-center mb-4 text-center">
|
||||||
|
<h1 class="text-3xl font-bold tracking-tight">Veridian</h1>
|
||||||
|
<p class="text-[var(--text-secondary)]">Create your account</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="rounded-2xl">
|
||||||
|
<form class="flex flex-col gap-2" @submit.prevent="submit">
|
||||||
|
<div class="flex flex-col gap-1.5">
|
||||||
|
<label for="name"
|
||||||
|
class="text-sm font-medium uppercase tracking-wider text-[var(--text-secondary)] ml-1">Name</label>
|
||||||
|
<input required ref="nameInputEl" type="text" id="name" v-model="form.name"
|
||||||
|
placeholder="John Doe"
|
||||||
|
class="focus:ring-2 focus:ring-[var(--color-accent)]/20 border border-[var(--color-border)] transition-all" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-col gap-1.5">
|
||||||
|
<label for="email"
|
||||||
|
class="text-sm font-medium uppercase tracking-wider text-[var(--text-secondary)] ml-1">Email</label>
|
||||||
|
<input required pattern=".{1,}@.{1,}\..{2,3}" ref="emailInputEl" type="email"
|
||||||
|
autocomplete="email" id="email" v-model="form.email" placeholder="name@example.com"
|
||||||
|
class="focus:ring-2 focus:ring-[var(--color-accent)]/20 border border-[var(--color-border)] transition-all" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-col gap-1.5">
|
||||||
|
<label for="password"
|
||||||
|
class="text-sm font-medium uppercase tracking-wider text-[var(--text-secondary)] ml-1">Password</label>
|
||||||
|
<input required minlength="8" maxlength="128" ref="passwordInputEl" type="password"
|
||||||
|
autocomplete="new-password" id="password" v-model="form.password" placeholder="••••••••"
|
||||||
|
class="focus:ring-2 focus:ring-[var(--color-accent)]/20 border border-[var(--color-border)] transition-all" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-col gap-1.5">
|
||||||
|
<label for="confirmPassword"
|
||||||
|
class="text-sm font-medium uppercase tracking-wider text-[var(--text-secondary)] ml-1">Confirm
|
||||||
|
Password</label>
|
||||||
<input required minlength="8" maxlength="128" ref="confirmPasswordInputEl" type="password"
|
<input required minlength="8" maxlength="128" ref="confirmPasswordInputEl" type="password"
|
||||||
autocomplete="new-password" id="confirmPassword" v-model="form.confirmPassword" />
|
autocomplete="new-password" id="confirmPassword" v-model="form.confirmPassword"
|
||||||
<button :disabled="!hydrated" class="accent" type="submit">
|
placeholder="••••••••"
|
||||||
<span v-if="loading" class="i-svg-spinners-90-ring-with-bg text-6"></span>
|
class="focus:ring-2 focus:ring-[var(--color-accent)]/20 border border-[var(--color-border)] transition-all" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button :disabled="!hydrated"
|
||||||
|
class="accent w-full mt-2 h-11 flex items-center justify-center font-semibold" type="submit">
|
||||||
|
<span v-if="loading" class="i-svg-spinners-90-ring-with-bg text-xl"></span>
|
||||||
<span v-else>Register</span>
|
<span v-else>Register</span>
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
<p class="text-center">Already have an account? <a :href="to ? `/auth/login?to=${to}` : '/auth/login'">Login</a></p>
|
</div>
|
||||||
|
|
||||||
|
<p class="text-sm text-center mt-3 text-[var(--text-secondary)]">
|
||||||
|
Already have an account?
|
||||||
|
<NuxtLink :to="to ? `/auth/login?to=${to}` : '/auth/login'"
|
||||||
|
class="text-[var(--color-accent)] font-medium hover:underline">
|
||||||
|
Log in here
|
||||||
|
</NuxtLink>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="absolute bottom-4 right-4">
|
||||||
|
<ThemeSwitcher size="medium" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -178,6 +178,12 @@ export default defineNuxtConfig({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
runtimeConfig: {
|
||||||
|
public: {
|
||||||
|
disableSignup: process.env.DISABLE_SIGNUP === 'true' || process.env.DISABLE_SIGNUP === '1',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
compatibilityDate: '2025-07-15',
|
compatibilityDate: '2025-07-15',
|
||||||
|
|
||||||
// sentry: {
|
// sentry: {
|
||||||
|
|||||||
Reference in New Issue
Block a user