mirror of
https://github.com/bretzel-app/crumbs.git
synced 2026-10-12 20:06:57 +02:00
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:
committed by
Fred Cabassut
co-authored by
Claude Opus 4.6
parent
e3d3d5c499
commit
7803fc531a
+3
-1
@@ -47,7 +47,8 @@
|
||||
"markdown-it": "^14.1.1",
|
||||
"svelte-dnd-action": "^0.9.69",
|
||||
"tiptap-markdown": "^0.9.0",
|
||||
"uuid": "^11.1.0"
|
||||
"uuid": "^11.1.0",
|
||||
"zxcvbn": "^4.4.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.58.2",
|
||||
@@ -58,6 +59,7 @@
|
||||
"@types/better-sqlite3": "^7.6.13",
|
||||
"@types/markdown-it": "^14.1.2",
|
||||
"@types/uuid": "^10.0.0",
|
||||
"@types/zxcvbn": "^4.4.5",
|
||||
"@vite-pwa/sveltekit": "^1.1.0",
|
||||
"drizzle-kit": "^0.31.9",
|
||||
"svelte": "^5.53.7",
|
||||
|
||||
Generated
+8
@@ -70,6 +70,9 @@ importers:
|
||||
uuid:
|
||||
specifier: ^11.1.0
|
||||
version: 11.1.0
|
||||
zxcvbn:
|
||||
specifier: ^4.4.2
|
||||
version: 4.4.2
|
||||
devDependencies:
|
||||
'@playwright/test':
|
||||
specifier: ^1.58.2
|
||||
@@ -3248,6 +3251,9 @@ packages:
|
||||
zimmerframe@1.1.4:
|
||||
resolution: {integrity: sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==}
|
||||
|
||||
zxcvbn@4.4.2:
|
||||
resolution: {integrity: sha512-Bq0B+ixT/DMyG8kgX2xWcI5jUvCwqrMxSFam7m0lAf78nf04hv6lNCsyLYdyYTrCVMqNDY/206K7eExYCeSyUQ==}
|
||||
|
||||
snapshots:
|
||||
|
||||
'@apideck/better-ajv-errors@0.3.6(ajv@8.18.0)':
|
||||
@@ -6494,3 +6500,5 @@ snapshots:
|
||||
yaml@2.8.2: {}
|
||||
|
||||
zimmerframe@1.1.4: {}
|
||||
|
||||
zxcvbn@4.4.2: {}
|
||||
|
||||
@@ -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}
|
||||
@@ -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}
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user