feat: add reusable PasswordInput with show/hide toggle

Replaces all type="password" inputs site-wide with a PasswordInput
component that includes an eye icon toggle for visibility.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Michael Chihlas
2026-02-14 23:22:19 -05:00
parent e7d6f992c6
commit 771889ab4f
6 changed files with 46 additions and 18 deletions

View File

@@ -0,0 +1,32 @@
import { useState } from 'react'
import { Eye, EyeOff } from 'lucide-react'
import { cn } from '@/lib/utils'
interface PasswordInputProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'type'> {
className?: string
}
export function PasswordInput({ className, ...props }: PasswordInputProps) {
const [visible, setVisible] = useState(false)
return (
<div className="relative">
<input
{...props}
type={visible ? 'text' : 'password'}
className={cn(className, 'pr-10')}
/>
<button
type="button"
onClick={() => setVisible((v) => !v)}
className="absolute right-2 top-1/2 -translate-y-1/2 rounded p-1 text-white/40 hover:bg-white/10 hover:text-white"
tabIndex={-1}
title={visible ? 'Hide password' : 'Show password'}
>
{visible ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
</button>
</div>
)
}
export default PasswordInput

View File

@@ -1,5 +1,6 @@
import { useState } from 'react' import { useState } from 'react'
import type { IntakeFormField } from '@/types' import type { IntakeFormField } from '@/types'
import { PasswordInput } from '@/components/common/PasswordInput'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
interface IntakeFormModalProps { interface IntakeFormModalProps {
@@ -119,8 +120,7 @@ export function IntakeFormModal({ isOpen, fields, treeName, onSubmit, onCancel }
case 'password': case 'password':
input = ( input = (
<input <PasswordInput
type="password"
value={value} value={value}
onChange={(e) => setValue(field.variable_name, e.target.value)} onChange={(e) => setValue(field.variable_name, e.target.value)}
placeholder={field.placeholder} placeholder={field.placeholder}

View File

@@ -3,6 +3,7 @@ import { useNavigate } from 'react-router-dom'
import { useAuthStore } from '@/store/authStore' import { useAuthStore } from '@/store/authStore'
import { authApi } from '@/api/auth' import { authApi } from '@/api/auth'
import { toast } from '@/lib/toast' import { toast } from '@/lib/toast'
import { PasswordInput } from '@/components/common/PasswordInput'
import { BrandLogo } from '@/components/common/BrandLogo' import { BrandLogo } from '@/components/common/BrandLogo'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
@@ -87,9 +88,8 @@ export function ChangePasswordPage() {
<label htmlFor="current-password" className="mb-1 block text-sm font-medium text-white"> <label htmlFor="current-password" className="mb-1 block text-sm font-medium text-white">
Current Password Current Password
</label> </label>
<input <PasswordInput
id="current-password" id="current-password"
type="password"
autoComplete="current-password" autoComplete="current-password"
required required
value={currentPassword} value={currentPassword}
@@ -107,9 +107,8 @@ export function ChangePasswordPage() {
<label htmlFor="new-password" className="mb-1 block text-sm font-medium text-white"> <label htmlFor="new-password" className="mb-1 block text-sm font-medium text-white">
New Password New Password
</label> </label>
<input <PasswordInput
id="new-password" id="new-password"
type="password"
autoComplete="new-password" autoComplete="new-password"
required required
value={newPassword} value={newPassword}
@@ -131,9 +130,8 @@ export function ChangePasswordPage() {
<label htmlFor="confirm-password" className="mb-1 block text-sm font-medium text-white"> <label htmlFor="confirm-password" className="mb-1 block text-sm font-medium text-white">
Confirm New Password Confirm New Password
</label> </label>
<input <PasswordInput
id="confirm-password" id="confirm-password"
type="password"
autoComplete="new-password" autoComplete="new-password"
required required
value={confirmPassword} value={confirmPassword}

View File

@@ -2,6 +2,7 @@ import { useState } from 'react'
import { Link, useNavigate, useLocation } from 'react-router-dom' import { Link, useNavigate, useLocation } from 'react-router-dom'
import { useAuthStore } from '@/store/authStore' import { useAuthStore } from '@/store/authStore'
import { BrandLogo } from '@/components/common/BrandLogo' import { BrandLogo } from '@/components/common/BrandLogo'
import { PasswordInput } from '@/components/common/PasswordInput'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
export function LoginPage() { export function LoginPage() {
@@ -95,10 +96,9 @@ export function LoginPage() {
<label htmlFor="password" className="mb-1 block text-sm font-medium text-white"> <label htmlFor="password" className="mb-1 block text-sm font-medium text-white">
Password Password
</label> </label>
<input <PasswordInput
id="password" id="password"
name="password" name="password"
type="password"
autoComplete="current-password" autoComplete="current-password"
required required
value={password} value={password}

View File

@@ -3,6 +3,7 @@ import { Link, useNavigate } from 'react-router-dom'
import { useAuthStore } from '@/store/authStore' import { useAuthStore } from '@/store/authStore'
import { inviteApi } from '@/api/invite' import { inviteApi } from '@/api/invite'
import { BrandLogo } from '@/components/common/BrandLogo' import { BrandLogo } from '@/components/common/BrandLogo'
import { PasswordInput } from '@/components/common/PasswordInput'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
export function RegisterPage() { export function RegisterPage() {
@@ -187,10 +188,9 @@ export function RegisterPage() {
<label htmlFor="password" className="block text-sm font-medium text-white"> <label htmlFor="password" className="block text-sm font-medium text-white">
Password Password
</label> </label>
<input <PasswordInput
id="password" id="password"
name="password" name="password"
type="password"
autoComplete="new-password" autoComplete="new-password"
required required
value={password} value={password}
@@ -211,10 +211,9 @@ export function RegisterPage() {
<label htmlFor="confirmPassword" className="block text-sm font-medium text-white"> <label htmlFor="confirmPassword" className="block text-sm font-medium text-white">
Confirm password Confirm password
</label> </label>
<input <PasswordInput
id="confirmPassword" id="confirmPassword"
name="confirmPassword" name="confirmPassword"
type="password"
autoComplete="new-password" autoComplete="new-password"
required required
value={confirmPassword} value={confirmPassword}

View File

@@ -3,6 +3,7 @@ import { Link, useSearchParams, useNavigate } from 'react-router-dom'
import { authApi } from '@/api/auth' import { authApi } from '@/api/auth'
import { toast } from '@/lib/toast' import { toast } from '@/lib/toast'
import { BrandLogo } from '@/components/common/BrandLogo' import { BrandLogo } from '@/components/common/BrandLogo'
import { PasswordInput } from '@/components/common/PasswordInput'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
export function ResetPasswordPage() { export function ResetPasswordPage() {
@@ -123,9 +124,8 @@ export function ResetPasswordPage() {
<label htmlFor="new-password" className="mb-1 block text-sm font-medium text-white"> <label htmlFor="new-password" className="mb-1 block text-sm font-medium text-white">
New Password New Password
</label> </label>
<input <PasswordInput
id="new-password" id="new-password"
type="password"
autoComplete="new-password" autoComplete="new-password"
required required
value={newPassword} value={newPassword}
@@ -147,9 +147,8 @@ export function ResetPasswordPage() {
<label htmlFor="confirm-password" className="mb-1 block text-sm font-medium text-white"> <label htmlFor="confirm-password" className="mb-1 block text-sm font-medium text-white">
Confirm New Password Confirm New Password
</label> </label>
<input <PasswordInput
id="confirm-password" id="confirm-password"
type="password"
autoComplete="new-password" autoComplete="new-password"
required required
value={confirmPassword} value={confirmPassword}