Skip to content

Commit 041e0c4

Browse files
committed
[frontend] improve autocomplete
1 parent 1de4cbc commit 041e0c4

1 file changed

Lines changed: 160 additions & 140 deletions

File tree

frontend/src/lib/pages/settings/security.svelte

Lines changed: 160 additions & 140 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,13 @@
3535
3636
let passwordsMatch = $derived(newPassword === confirmPassword);
3737
let passwordValid = $derived(newPassword.length >= 8);
38-
let canSubmit = $derived(!!oldPassword && !!newPassword && !!confirmPassword && passwordValid && passwordsMatch);
38+
let canSubmit = $derived(
39+
!!oldPassword &&
40+
!!newPassword &&
41+
!!confirmPassword &&
42+
passwordValid &&
43+
passwordsMatch,
44+
);
3945
4046
$effect(() => {
4147
passkeySupported =
@@ -123,7 +129,10 @@
123129
saving = true;
124130
error = "";
125131
try {
126-
await changePasswordApi({ old_password: oldPassword, new_password: newPassword });
132+
await changePasswordApi({
133+
old_password: oldPassword,
134+
new_password: newPassword,
135+
});
127136
toast.success("Password changed successfully");
128137
oldPassword = "";
129138
newPassword = "";
@@ -154,145 +163,156 @@
154163
</div>
155164

156165
<div class="flex flex-col gap-6">
157-
<Card.Root>
158-
<Card.Header>
159-
<Card.Title>Change Password</Card.Title>
160-
<Card.Description>Update your account password</Card.Description>
161-
</Card.Header>
162-
<Card.Content>
163-
<form onsubmit={(e) => { e.preventDefault(); handleSubmit(); }} class="flex flex-col gap-4">
164-
<div class="flex flex-col gap-2">
165-
<Label for="old_password">Current Password</Label>
166-
<Input
167-
id="old_password"
168-
type="password"
169-
bind:value={oldPassword}
170-
disabled={saving}
171-
placeholder="Enter current password"
172-
/>
173-
</div>
174-
<div class="flex flex-col gap-2">
175-
<Label for="new_password">New Password</Label>
176-
<Input
177-
id="new_password"
178-
type="password"
179-
bind:value={newPassword}
180-
disabled={saving}
181-
placeholder="Enter new password"
182-
/>
183-
{#if newPassword && !passwordValid}
184-
<p class="text-destructive text-xs">Password must be at least 8 characters</p>
185-
{/if}
186-
</div>
187-
<div class="flex flex-col gap-2">
188-
<Label for="confirm_password">Confirm New Password</Label>
189-
<Input
190-
id="confirm_password"
191-
type="password"
192-
bind:value={confirmPassword}
193-
disabled={saving}
194-
placeholder="Confirm new password"
195-
/>
196-
{#if confirmPassword && !passwordsMatch}
197-
<p class="text-destructive text-xs">Passwords do not match</p>
198-
{/if}
199-
</div>
200-
<Button type="submit" disabled={!canSubmit || saving} class="mt-2">
201-
{saving ? "Saving..." : "Change Password"}
202-
</Button>
203-
</form>
204-
</Card.Content>
205-
</Card.Root>
166+
<Card.Root>
167+
<Card.Header>
168+
<Card.Title>Change Password</Card.Title>
169+
<Card.Description>Update your account password</Card.Description>
170+
</Card.Header>
171+
<Card.Content>
172+
<form
173+
onsubmit={(e) => {
174+
e.preventDefault();
175+
handleSubmit();
176+
}}
177+
class="flex flex-col gap-4"
178+
>
179+
<div class="flex flex-col gap-2">
180+
<Label for="old_password">Current Password</Label>
181+
<Input
182+
id="old_password"
183+
type="password"
184+
autocomplete="current-password"
185+
bind:value={oldPassword}
186+
disabled={saving}
187+
placeholder="Enter current password"
188+
/>
189+
</div>
190+
<div class="flex flex-col gap-2">
191+
<Label for="new_password">New Password</Label>
192+
<Input
193+
id="new_password"
194+
type="password"
195+
autocomplete="new-password"
196+
bind:value={newPassword}
197+
disabled={saving}
198+
placeholder="Enter new password"
199+
/>
200+
{#if newPassword && !passwordValid}
201+
<p class="text-destructive text-xs">
202+
Password must be at least 8 characters
203+
</p>
204+
{/if}
205+
</div>
206+
<div class="flex flex-col gap-2">
207+
<Label for="confirm_password">Confirm New Password</Label>
208+
<Input
209+
id="confirm_password"
210+
type="password"
211+
autocomplete="new-password"
212+
bind:value={confirmPassword}
213+
disabled={saving}
214+
placeholder="Confirm new password"
215+
/>
216+
{#if confirmPassword && !passwordsMatch}
217+
<p class="text-destructive text-xs">Passwords do not match</p>
218+
{/if}
219+
</div>
220+
<Button type="submit" disabled={!canSubmit || saving} class="mt-2">
221+
{saving ? "Saving..." : "Change Password"}
222+
</Button>
223+
</form>
224+
</Card.Content>
225+
</Card.Root>
206226

207-
<Card.Root>
208-
<Card.Header>
209-
<Card.Title>Passkeys</Card.Title>
210-
<Card.Description>
211-
Passkeys let you sign in without a password using biometrics, PIN, or
212-
security keys.
213-
</Card.Description>
214-
</Card.Header>
215-
<Card.Content>
216-
{#if loading}
217-
<p class="text-muted-foreground text-sm">Loading...</p>
218-
{:else if credentials.length === 0}
219-
<div class="flex flex-col items-center gap-2 py-8 text-center">
220-
<KeyIcon class="text-muted-foreground size-8" />
221-
<p class="text-muted-foreground text-sm">
222-
{passkeySupported
223-
? "No passkeys registered yet."
224-
: "Passkeys are not supported in this browser."}
225-
</p>
226-
</div>
227-
{:else}
228-
<ul class="flex flex-col gap-2">
229-
{#each credentials as cred (cred.id)}
230-
<li
231-
class="flex items-center justify-between rounded-lg border border-border p-3"
232-
>
233-
{#if editingId === cred.id}
234-
<div class="flex flex-1 items-center gap-2">
235-
<Input
236-
bind:value={editName}
237-
class="h-8"
238-
placeholder="Passkey name"
239-
onkeydown={(e) => {
240-
if (e.key === "Enter") saveEdit(cred);
241-
if (e.key === "Escape") cancelEdit();
242-
}}
243-
/>
244-
<Button
245-
size="sm"
246-
variant="ghost"
247-
aria-label="Save passkey name"
248-
onclick={() => saveEdit(cred)}
249-
>
250-
<CheckIcon class="size-4" />
251-
</Button>
252-
<Button
253-
size="sm"
254-
variant="ghost"
255-
aria-label="Cancel rename"
256-
onclick={cancelEdit}
257-
>
258-
<XIcon class="size-4" />
259-
</Button>
260-
</div>
261-
{:else}
262-
<div class="flex flex-1 items-center gap-3">
263-
<KeyIcon class="text-muted-foreground size-4" />
264-
<div>
265-
<p class="text-sm font-medium">{cred.name}</p>
266-
<p class="text-muted-foreground text-xs">
267-
Registered {formatDate(cred.created_at)}
268-
</p>
227+
<Card.Root>
228+
<Card.Header>
229+
<Card.Title>Passkeys</Card.Title>
230+
<Card.Description>
231+
Passkeys let you sign in without a password using biometrics, PIN, or
232+
security keys.
233+
</Card.Description>
234+
</Card.Header>
235+
<Card.Content>
236+
{#if loading}
237+
<p class="text-muted-foreground text-sm">Loading...</p>
238+
{:else if credentials.length === 0}
239+
<div class="flex flex-col items-center gap-2 py-8 text-center">
240+
<KeyIcon class="text-muted-foreground size-8" />
241+
<p class="text-muted-foreground text-sm">
242+
{passkeySupported
243+
? "No passkeys registered yet."
244+
: "Passkeys are not supported in this browser."}
245+
</p>
246+
</div>
247+
{:else}
248+
<ul class="flex flex-col gap-2">
249+
{#each credentials as cred (cred.id)}
250+
<li
251+
class="flex items-center justify-between rounded-lg border border-border p-3"
252+
>
253+
{#if editingId === cred.id}
254+
<div class="flex flex-1 items-center gap-2">
255+
<Input
256+
bind:value={editName}
257+
class="h-8"
258+
placeholder="Passkey name"
259+
onkeydown={(e) => {
260+
if (e.key === "Enter") saveEdit(cred);
261+
if (e.key === "Escape") cancelEdit();
262+
}}
263+
/>
264+
<Button
265+
size="sm"
266+
variant="ghost"
267+
aria-label="Save passkey name"
268+
onclick={() => saveEdit(cred)}
269+
>
270+
<CheckIcon class="size-4" />
271+
</Button>
272+
<Button
273+
size="sm"
274+
variant="ghost"
275+
aria-label="Cancel rename"
276+
onclick={cancelEdit}
277+
>
278+
<XIcon class="size-4" />
279+
</Button>
280+
</div>
281+
{:else}
282+
<div class="flex flex-1 items-center gap-3">
283+
<KeyIcon class="text-muted-foreground size-4" />
284+
<div>
285+
<p class="text-sm font-medium">{cred.name}</p>
286+
<p class="text-muted-foreground text-xs">
287+
Registered {formatDate(cred.created_at)}
288+
</p>
289+
</div>
290+
</div>
291+
<div class="flex items-center gap-1">
292+
<Button
293+
size="sm"
294+
variant="ghost"
295+
aria-label="Rename passkey"
296+
onclick={() => startEdit(cred)}
297+
>
298+
<PencilIcon class="size-4" />
299+
</Button>
300+
<Button
301+
size="sm"
302+
variant="ghost"
303+
aria-label="Delete passkey"
304+
class="text-destructive hover:text-destructive"
305+
onclick={() => handleDelete(cred)}
306+
>
307+
<Trash2Icon class="size-4" />
308+
</Button>
269309
</div>
270-
</div>
271-
<div class="flex items-center gap-1">
272-
<Button
273-
size="sm"
274-
variant="ghost"
275-
aria-label="Rename passkey"
276-
onclick={() => startEdit(cred)}
277-
>
278-
<PencilIcon class="size-4" />
279-
</Button>
280-
<Button
281-
size="sm"
282-
variant="ghost"
283-
aria-label="Delete passkey"
284-
class="text-destructive hover:text-destructive"
285-
onclick={() => handleDelete(cred)}
286-
>
287-
<Trash2Icon class="size-4" />
288-
</Button>
289-
</div>
290-
{/if}
291-
</li>
292-
{/each}
293-
</ul>
294-
{/if}
295-
</Card.Content>
296-
</Card.Root>
310+
{/if}
311+
</li>
312+
{/each}
313+
</ul>
314+
{/if}
315+
</Card.Content>
316+
</Card.Root>
297317
</div>
298318
</div>

0 commit comments

Comments
 (0)