feat(ui): password strength meter with zxcvbn

Add PasswordStrengthMeter component showing visual bar and feedback.
Integrated in setup, settings change-password, and admin create-user forms.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Frederic Cabassut
2026-03-09 01:10:37 +01:00
committed by Fred Cabassut
co-authored by Claude Opus 4.6
parent 5aaebfefe5
commit 34c95fae92
5 changed files with 51 additions and 1 deletions
@@ -0,0 +1,36 @@
<script lang="ts">
import zxcvbn from 'zxcvbn';
let { password = '' }: { password: string } = $props();
const result = $derived(password.length > 0 ? zxcvbn(password) : null);
const labels = ['Very weak', 'Weak', 'Fair', 'Strong', 'Very strong'];
const colors = [
'bg-red-500',
'bg-orange-500',
'bg-yellow-500',
'bg-lime-500',
'bg-green-500'
];
</script>
{#if result}
<div class="mt-1 mb-2" data-testid="password-strength">
<div class="flex gap-1">
{#each { length: 4 } as _, i}
<div
class="h-1 flex-1 rounded-full transition-colors {i <= result.score
? colors[result.score]
: 'bg-gray-200 dark:bg-gray-600'}"
></div>
{/each}
</div>
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
{labels[result.score]}
{#if result.feedback.warning}
— {result.feedback.warning}
{/if}
</p>
</div>
{/if}
+2
View File
@@ -1,5 +1,6 @@
<script lang="ts">
import { goto } from '$app/navigation';
import PasswordStrengthMeter from '$lib/components/PasswordStrengthMeter.svelte';
let { data } = $props();
@@ -140,6 +141,7 @@
placeholder="New password (min 8 characters)"
class="w-full rounded-lg border border-gray-300 px-4 py-2 text-sm outline-none focus:border-amber-500 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-100"
/>
<PasswordStrengthMeter password={newPassword} />
<input
type="password"
bind:value={confirmNewPassword}
+2
View File
@@ -1,5 +1,6 @@
<script lang="ts">
import type { User } from '$lib/types/index.js';
import PasswordStrengthMeter from '$lib/components/PasswordStrengthMeter.svelte';
let { data } = $props();
let users = $state<User[]>(data.users);
@@ -123,6 +124,7 @@
placeholder="Password (min 8 characters)"
class="w-full rounded-lg border border-gray-300 px-4 py-2 text-sm outline-none focus:border-amber-500 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-100"
/>
<PasswordStrengthMeter password={newPassword} />
<select
bind:value={newRole}
class="w-full rounded-lg border border-gray-300 px-4 py-2 text-sm outline-none focus:border-amber-500 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-100"