Skip to content

Commit df40c53

Browse files
committed
ui: improve login and register
1 parent 9c60f25 commit df40c53

2 files changed

Lines changed: 337 additions & 180 deletions

File tree

Lines changed: 168 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
<script setup lang="ts">
22
import { computed, onMounted, ref } from "vue";
33
import { RouterLink, useRoute, useRouter } from "vue-router";
4+
import {
5+
ArrowLeft,
6+
Fingerprint,
7+
KeyRound,
8+
Mail,
9+
Sparkles,
10+
} from "lucide-vue-next";
411
512
import { Alert, AlertDescription } from "@/components/ui/alert";
613
import { Button } from "@/components/ui/button";
@@ -76,135 +83,179 @@ async function onSubmit() {
7683
</script>
7784

7885
<template>
79-
<div class="mx-auto w-full max-w-md">
80-
<Card>
81-
<CardHeader>
82-
<CardTitle>Sign in</CardTitle>
83-
<CardDescription>
84-
Use your account email and password, or start a local demo session.
85-
</CardDescription>
86-
</CardHeader>
87-
<CardContent>
88-
<Alert
89-
v-if="registeredOk"
90-
class="mb-4"
91-
role="status"
92-
>
93-
<AlertDescription>
94-
Account created. Sign in with the same email and password.
95-
</AlertDescription>
96-
</Alert>
97-
98-
<Alert
99-
v-if="lastError"
100-
class="mb-4"
101-
role="alert"
102-
variant="destructive"
103-
>
104-
<AlertDescription>
105-
{{ lastError }}
106-
</AlertDescription>
107-
</Alert>
108-
109-
<form class="space-y-4" @submit.prevent="onSubmit">
110-
<div class="space-y-2">
111-
<Label for="login-email">Email</Label>
112-
<Input
113-
id="login-email"
114-
v-model="email"
115-
autocomplete="username"
116-
:disabled="loading"
117-
name="email"
118-
required
119-
type="email"
120-
/>
121-
</div>
122-
<div class="space-y-2">
123-
<Label for="login-password">Password</Label>
124-
<Input
125-
id="login-password"
126-
v-model="password"
127-
autocomplete="current-password"
128-
:disabled="loading"
129-
name="password"
130-
required
131-
type="password"
132-
/>
133-
</div>
134-
<div class="flex items-center gap-2">
135-
<Checkbox
136-
id="remember"
137-
v-model:checked="rememberSession"
138-
:disabled="loading"
139-
/>
140-
<Label
141-
class="text-muted-foreground font-normal"
142-
for="remember"
143-
>
144-
Remember this device
145-
</Label>
146-
</div>
86+
<div class="flex flex-1 flex-col items-center justify-center px-4 py-12">
87+
<div class="w-full max-w-sm">
88+
<Card>
89+
<CardHeader class="pb-4">
90+
<CardTitle class="text-lg">Sign in</CardTitle>
91+
<CardDescription>
92+
Use your email and password, or start a local demo.
93+
</CardDescription>
94+
</CardHeader>
14795

148-
<div
149-
v-if="twoFactorRequired"
150-
class="border-border space-y-3 border-t pt-4"
96+
<CardContent class="space-y-4">
97+
<Alert
98+
v-if="registeredOk"
99+
class="border-emerald-500/20 bg-emerald-500/10 text-emerald-700"
100+
role="status"
151101
>
152-
<p class="text-muted-foreground text-sm">
153-
Enter a 6-digit code from your authenticator app, or a recovery
154-
code.
155-
</p>
102+
<AlertDescription>
103+
Account created successfully. Sign in with the same email and
104+
password.
105+
</AlertDescription>
106+
</Alert>
107+
108+
<Alert
109+
v-if="lastError"
110+
class="border-destructive/20 bg-destructive/10 text-destructive"
111+
role="alert"
112+
>
113+
<AlertDescription>{{ lastError }}</AlertDescription>
114+
</Alert>
115+
116+
<Button
117+
class="w-full"
118+
:disabled="loading"
119+
type="button"
120+
variant="outline"
121+
@click="onDemo"
122+
>
123+
<Sparkles class="mr-2 size-4" />
124+
Try the demo
125+
</Button>
126+
127+
<div class="relative">
128+
<div class="absolute inset-0 flex items-center">
129+
<span class="w-full border-t"></span>
130+
</div>
131+
<div class="relative flex justify-center text-xs uppercase">
132+
<span class="bg-card text-muted-foreground px-2">
133+
or continue with email
134+
</span>
135+
</div>
136+
</div>
137+
138+
<form class="space-y-4" @submit.prevent="onSubmit">
156139
<div class="space-y-2">
157-
<Label for="login-otp">Authenticator code</Label>
158-
<Input
159-
id="login-otp"
160-
v-model="authenticatorToken"
161-
autocomplete="one-time-code"
162-
:disabled="loading"
163-
inputmode="numeric"
164-
maxlength="6"
165-
pattern="[0-9]*"
166-
placeholder="000000"
167-
type="text"
168-
/>
140+
<Label for="login-email">Email</Label>
141+
<div class="relative">
142+
<Mail
143+
class="text-muted-foreground absolute top-1/2 left-3 size-4 -translate-y-1/2"
144+
/>
145+
<Input
146+
id="login-email"
147+
v-model="email"
148+
autocomplete="username"
149+
class="pl-9"
150+
:disabled="loading"
151+
name="email"
152+
placeholder="you@example.com"
153+
required
154+
type="email"
155+
/>
156+
</div>
169157
</div>
158+
170159
<div class="space-y-2">
171-
<Label for="login-recovery">Recovery code (optional)</Label>
172-
<Input
173-
id="login-recovery"
174-
v-model="recoveryCode"
160+
<Label for="login-password">Password</Label>
161+
<div class="relative">
162+
<KeyRound
163+
class="text-muted-foreground absolute top-1/2 left-3 size-4 -translate-y-1/2"
164+
/>
165+
<Input
166+
id="login-password"
167+
v-model="password"
168+
autocomplete="current-password"
169+
class="pl-9"
170+
:disabled="loading"
171+
name="password"
172+
placeholder="••••••••"
173+
required
174+
type="password"
175+
/>
176+
</div>
177+
</div>
178+
179+
<div class="flex items-center gap-2">
180+
<Checkbox
181+
id="remember"
182+
v-model:checked="rememberSession"
175183
:disabled="loading"
176-
inputmode="text"
177-
maxlength="32"
178-
placeholder="32 hex characters"
179-
spellcheck="false"
180-
type="text"
181184
/>
185+
<Label
186+
class="text-muted-foreground text-sm font-normal"
187+
for="remember"
188+
>
189+
Remember this device
190+
</Label>
182191
</div>
183-
</div>
184192

185-
<div class="flex flex-wrap gap-2">
186-
<Button :disabled="loading" type="submit" variant="default">
193+
<div
194+
v-if="twoFactorRequired"
195+
class="border-border space-y-3 border-t pt-4"
196+
>
197+
<p class="text-muted-foreground flex items-center gap-2 text-sm">
198+
<Fingerprint class="size-4" />
199+
Enter a 6-digit code from your authenticator app, or a recovery
200+
code.
201+
</p>
202+
<div class="space-y-2">
203+
<Label for="login-otp">Authenticator code</Label>
204+
<Input
205+
id="login-otp"
206+
v-model="authenticatorToken"
207+
autocomplete="one-time-code"
208+
:disabled="loading"
209+
inputmode="numeric"
210+
maxlength="6"
211+
pattern="[0-9]*"
212+
placeholder="000000"
213+
type="text"
214+
/>
215+
</div>
216+
<div class="space-y-2">
217+
<Label for="login-recovery">Recovery code (optional)</Label>
218+
<Input
219+
id="login-recovery"
220+
v-model="recoveryCode"
221+
:disabled="loading"
222+
inputmode="text"
223+
maxlength="32"
224+
placeholder="32 hex characters"
225+
spellcheck="false"
226+
type="text"
227+
/>
228+
</div>
229+
</div>
230+
231+
<Button :disabled="loading" class="w-full" type="submit">
232+
<LogIn class="mr-2 size-4" />
187233
{{ loading ? "Signing in…" : "Sign in" }}
188234
</Button>
189-
<Button
190-
:disabled="loading"
191-
type="button"
192-
variant="secondary"
193-
@click="onDemo"
235+
</form>
236+
</CardContent>
237+
238+
<CardFooter
239+
class="text-muted-foreground flex flex-col items-center gap-3 text-sm"
240+
>
241+
<div>
242+
Don't have an account?
243+
<RouterLink
244+
class="text-primary hover:text-primary/80 font-medium underline underline-offset-4"
245+
to="/register"
194246
>
195-
Try demo
196-
</Button>
247+
Create one
248+
</RouterLink>
197249
</div>
198-
</form>
199-
</CardContent>
200-
<CardFooter class="flex flex-col items-start gap-2">
201-
<Button as-child class="p-0" size="sm" variant="link">
202-
<RouterLink to="/register">Create an account</RouterLink>
203-
</Button>
204-
<Button as-child class="p-0" size="sm" variant="link">
205-
<RouterLink to="/">← Home</RouterLink>
206-
</Button>
207-
</CardFooter>
208-
</Card>
250+
<RouterLink
251+
class="inline-flex items-center gap-1.5 text-xs underline underline-offset-4"
252+
to="/"
253+
>
254+
<ArrowLeft class="size-3.5" />
255+
Back to home
256+
</RouterLink>
257+
</CardFooter>
258+
</Card>
259+
</div>
209260
</div>
210261
</template>

0 commit comments

Comments
 (0)