password strength report change, added meter to account page

This commit is contained in:
Michael Freno
2026-01-02 15:28:30 -05:00
parent 4f1e9a6d6d
commit df3119d51e
4 changed files with 71 additions and 22 deletions

View File

@@ -540,7 +540,7 @@ export function LeftBar() {
{/* Navigation Links */} {/* Navigation Links */}
<div class="mt-auto"> <div class="mt-auto">
<Typewriter keepAlive={false}> <Typewriter keepAlive={false}>
<ul class="flex flex-col gap-4 py-6"> <ul class="flex flex-col gap-4 pt-6">
<li class="hover:text-subtext0 w-fit transition-transform duration-200 ease-in-out hover:-translate-y-0.5 hover:scale-110 hover:font-bold"> <li class="hover:text-subtext0 w-fit transition-transform duration-200 ease-in-out hover:-translate-y-0.5 hover:scale-110 hover:font-bold">
<a href="/" onClick={handleLinkClick}> <a href="/" onClick={handleLinkClick}>
Home Home
@@ -592,7 +592,7 @@ export function LeftBar() {
</Typewriter> </Typewriter>
{/* Get Lost button - outside Typewriter to allow glitch effect */} {/* Get Lost button - outside Typewriter to allow glitch effect */}
<ul class="flex flex-col gap-4 pb-6"> <ul class="pt-4 pb-6">
<li <li
class="hover:text-subtext0 w-fit transition-all duration-500 ease-in-out hover:-translate-y-0.5 hover:scale-110 hover:font-bold" class="hover:text-subtext0 w-fit transition-all duration-500 ease-in-out hover:-translate-y-0.5 hover:scale-110 hover:font-bold"
classList={{ classList={{

View File

@@ -1,5 +1,5 @@
import { createMemo, For, Show } from "solid-js"; import { createMemo, For, Show } from "solid-js";
import { validatePassword, type PasswordStrength } from "~/lib/validation"; import { validatePassword } from "~/lib/validation";
import { VALIDATION_CONFIG } from "~/config"; import { VALIDATION_CONFIG } from "~/config";
import CheckCircle from "./icons/CheckCircle"; import CheckCircle from "./icons/CheckCircle";
@@ -86,7 +86,7 @@ export default function PasswordStrengthMeter(
{/* Strength bar */} {/* Strength bar */}
<Show when={props.password.length > 0}> <Show when={props.password.length > 0}>
<div class="space-y-1"> <div class="space-y-1">
<div class="bg-surface h-2 w-full overflow-hidden rounded-full"> <div class="bg-surface border-yellow h-2 w-full overflow-hidden rounded-full border">
<div <div
class={`${config().color} h-full transition-all duration-300 ease-out`} class={`${config().color} h-full transition-all duration-300 ease-out`}
style={{ width: config().width }} style={{ width: config().width }}

View File

@@ -37,6 +37,7 @@ export function validatePassword(password: string): {
strength: PasswordStrength; strength: PasswordStrength;
} { } {
const errors: string[] = []; const errors: string[] = [];
let includesSpecial = false;
// Minimum length from config // Minimum length from config
if (password.length < VALIDATION_CONFIG.MIN_PASSWORD_LENGTH) { if (password.length < VALIDATION_CONFIG.MIN_PASSWORD_LENGTH) {
@@ -60,11 +61,11 @@ export function validatePassword(password: string): {
errors.push("Password must contain at least one number"); errors.push("Password must contain at least one number");
} }
if (/[^A-Za-z0-9]/.test(password)) {
includesSpecial = true;
}
// Require special character (if configured) // Require special character (if configured)
if ( if (VALIDATION_CONFIG.PASSWORD_REQUIRE_SPECIAL && !includesSpecial) {
VALIDATION_CONFIG.PASSWORD_REQUIRE_SPECIAL &&
!/[^A-Za-z0-9]/.test(password)
) {
errors.push("Password must contain at least one special character"); errors.push("Password must contain at least one special character");
} }
@@ -97,6 +98,13 @@ export function validatePassword(password: string): {
let strength: PasswordStrength = "weak"; let strength: PasswordStrength = "weak";
if (errors.length === 0) { if (errors.length === 0) {
if (includesSpecial) {
if (password.length >= 14) {
strength = "strong";
} else if (password.length >= VALIDATION_CONFIG.MIN_PASSWORD_LENGTH) {
strength = "good";
}
}
if (password.length >= 16) { if (password.length >= 16) {
strength = "strong"; strength = "strong";
} else if (password.length >= 12) { } else if (password.length >= 12) {

View File

@@ -16,6 +16,7 @@ import { VALIDATION_CONFIG } from "~/config";
import { api } from "~/lib/api"; import { api } from "~/lib/api";
import type { UserProfile } from "~/types/user"; import type { UserProfile } from "~/types/user";
import PasswordStrengthMeter from "~/components/PasswordStrengthMeter";
const getUserProfile = query(async (): Promise<UserProfile | null> => { const getUserProfile = query(async (): Promise<UserProfile | null> => {
"use server"; "use server";
@@ -84,6 +85,7 @@ export default function AccountPage() {
const [passwordBlurred, setPasswordBlurred] = createSignal(false); const [passwordBlurred, setPasswordBlurred] = createSignal(false);
const [passwordError, setPasswordError] = createSignal(false); const [passwordError, setPasswordError] = createSignal(false);
const [passwordDeletionError, setPasswordDeletionError] = createSignal(false); const [passwordDeletionError, setPasswordDeletionError] = createSignal(false);
const [newPassword, setNewPassword] = createSignal("");
const [showOldPasswordInput, setShowOldPasswordInput] = createSignal(false); const [showOldPasswordInput, setShowOldPasswordInput] = createSignal(false);
const [showPasswordInput, setShowPasswordInput] = createSignal(false); const [showPasswordInput, setShowPasswordInput] = createSignal(false);
@@ -433,6 +435,7 @@ export default function AccountPage() {
const handleNewPasswordChange = (e: Event) => { const handleNewPasswordChange = (e: Event) => {
const target = e.target as HTMLInputElement; const target = e.target as HTMLInputElement;
setNewPassword(target.value);
checkPasswordLength(target.value); checkPasswordLength(target.value);
if (newPasswordConfRef) { if (newPasswordConfRef) {
checkForMatch(target.value, newPasswordConfRef.value); checkForMatch(target.value, newPasswordConfRef.value);
@@ -838,9 +841,21 @@ export default function AccountPage() {
> >
<Show <Show
when={showOldPasswordInput()} when={showOldPasswordInput()}
fallback={<Eye />} fallback={
<EyeSlash
height={24}
width={24}
strokeWidth={1}
class="stroke-text"
/>
}
> >
<EyeSlash /> <Eye
height={24}
width={24}
strokeWidth={1}
class="stroke-text"
/>
</Show> </Show>
</button> </button>
</div> </div>
@@ -861,6 +876,9 @@ export default function AccountPage() {
/> />
<span class="bar"></span> <span class="bar"></span>
<label class="underlinedInputLabel">New Password</label> <label class="underlinedInputLabel">New Password</label>
<div class="pt-1">
<PasswordStrengthMeter password={newPassword()} />
</div>
<button <button
type="button" type="button"
onClick={() => onClick={() =>
@@ -868,18 +886,26 @@ export default function AccountPage() {
} }
class="text-subtext0 absolute top-2 right-0 transition-all hover:brightness-125" class="text-subtext0 absolute top-2 right-0 transition-all hover:brightness-125"
> >
<Show when={showPasswordInput()} fallback={<Eye />}> <Show
<EyeSlash /> when={showPasswordInput()}
fallback={
<EyeSlash
height={24}
width={24}
strokeWidth={1}
class="stroke-text"
/>
}
>
<Eye
height={24}
width={24}
strokeWidth={1}
class="stroke-text"
/>
</Show> </Show>
</button> </button>
</div> </div>
<Show when={showPasswordLengthWarning()}>
<div class="text-red mb-4 text-center text-sm">
Password too short! Min Length: 8
</div>
</Show>
<div class="input-group relative mx-4 mb-2"> <div class="input-group relative mx-4 mb-2">
<input <input
ref={newPasswordConfRef} ref={newPasswordConfRef}
@@ -894,7 +920,7 @@ export default function AccountPage() {
/> />
<span class="bar"></span> <span class="bar"></span>
<label class="underlinedInputLabel"> <label class="underlinedInputLabel">
Password Confirmation New Password Confirmation
</label> </label>
<button <button
type="button" type="button"
@@ -903,8 +929,23 @@ export default function AccountPage() {
} }
class="text-subtext0 absolute top-2 right-0 transition-all hover:brightness-125" class="text-subtext0 absolute top-2 right-0 transition-all hover:brightness-125"
> >
<Show when={showPasswordConfInput()} fallback={<Eye />}> <Show
<EyeSlash /> when={showPasswordConfInput()}
fallback={
<EyeSlash
height={24}
width={24}
strokeWidth={1}
class="stroke-text"
/>
}
>
<Eye
height={24}
width={24}
strokeWidth={1}
class="stroke-text"
/>
</Show> </Show>
</button> </button>
</div> </div>