Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions components/forms/EmailInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,25 @@ import React from "react";
interface EmailInputProps {
value: string;
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
onBlur?: (e: React.FocusEvent<HTMLInputElement>) => void;
placeholder?: string;
label?: string;
error?: string;
disabled?: boolean;
className?: string;
inputRef?: React.Ref<HTMLInputElement>;
}

export default function EmailInput({
value,
onChange,
onBlur,
placeholder = "Email",
label,
error,
disabled = false,
className = "",
inputRef,
}: EmailInputProps) {
return (
<div className={`flex flex-col ${className}`}>
Expand Down Expand Up @@ -45,8 +49,11 @@ export default function EmailInput({
type="email"
value={value}
onChange={onChange}
onBlur={onBlur}
placeholder={placeholder}
disabled={disabled}
ref={inputRef}
autoComplete="email"
className="pl-11 pr-4 border border-black py-2 rounded-3xl font-light text-[12px] md:text-[15px] text-left w-full disabled:opacity-50 disabled:cursor-not-allowed"
/>
</div>
Expand Down
46 changes: 45 additions & 1 deletion components/forms/PasswordInput.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,53 @@
import React, { useState } from "react";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We already show explicit password requirements elsewhere, so the strength meter feels redundant and adds vertical bloat to a screen that’s already dense. The requirements list is more actionable than a “weak/strong” bar. My vote would be to keep just the original requirements to avoid mixed signals.

Copy link
Contributor

@ryanpolasky ryanpolasky Jan 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should also verify that these exact requirements exist & are checked on the backend. Pretty sure they are, but I'll double check that. Just writing this here as a note for myself :) Disregard this comment! Totally forgot we're operating off of Firebase's existing restrictions, these are are already handles on the backend! :)

import { validatePassword } from "@/utils/validation";

interface PasswordInputProps {
value: string;
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
onBlur?: (e: React.FocusEvent<HTMLInputElement>) => void;
placeholder?: string;
label?: string;
error?: string;
disabled?: boolean;
className?: string;
showToggle?: boolean;
showStrength?: boolean;
autoComplete?: string;
inputRef?: React.Ref<HTMLInputElement>;
}

export default function PasswordInput({
value,
onChange,
onBlur,
placeholder = "Password",
label,
error,
disabled = false,
className = "",
showToggle = false,
showStrength = false,
autoComplete = "current-password",
inputRef,
}: PasswordInputProps) {
const [showPassword, setShowPassword] = useState(false);
const strength = showStrength ? validatePassword(value) : null;
const strengthScore = strength
? Object.values(strength.checks).filter(Boolean).length
: 0;

const strengthLabel =
!strength || value.length === 0
? ""
: strengthScore <= 2
? "Weak"
: strengthScore === 3
? "Good"
: "Strong";

const strengthColor =
strengthScore <= 2 ? "bg-rose-500" : strengthScore === 3 ? "bg-amber-400" : "bg-emerald-500";
const strengthPct = !strength ? 0 : Math.min(100, (strengthScore / 5) * 100);

return (
<div className={`flex flex-col ${className}`}>
Expand Down Expand Up @@ -49,16 +75,18 @@ export default function PasswordInput({
type={showPassword ? "text" : "password"}
value={value}
onChange={onChange}
onBlur={onBlur}
placeholder={placeholder}
disabled={disabled}
autoComplete={autoComplete}
ref={inputRef}
className="pl-11 pr-4 border border-black py-2 rounded-3xl font-light text-[12px] md:text-[15px] text-left w-full disabled:opacity-50 disabled:cursor-not-allowed"
/>
{showToggle && (
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
className="absolute right-4 top-1/2 transform -translate-y-1/2 text-gray-500 hover:text-gray-700"
tabIndex={-1}
>
{showPassword ? (
<svg
Expand Down Expand Up @@ -99,6 +127,22 @@ export default function PasswordInput({
</button>
)}
</div>
{showStrength && value.length > 0 ? (
<div className="mt-2">
<div className="flex items-center justify-between">
<p className="text-xs text-gray-600">Password strength</p>
<p className="text-xs font-semibold text-gray-700">{strengthLabel}</p>
</div>
<div className="mt-1 h-2 w-full rounded-full bg-gray-200 overflow-hidden">
<div className={`h-full ${strengthColor} transition-all`} style={{ width: `${strengthPct}%` }} />
</div>
{strength && !strength.isValid ? (
<p className="mt-1 text-[11px] text-gray-500">
Use 8+ chars with uppercase, lowercase, number, and special character.
</p>
) : null}
</div>
) : null}
{error && <p className="text-red-500 text-xs mt-1">{error}</p>}
</div>
);
Expand Down
17 changes: 16 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading