diff --git a/lang/es.json b/lang/es.json index 2f6f9f5d..ad9510f7 100644 --- a/lang/es.json +++ b/lang/es.json @@ -714,6 +714,7 @@ "Hey there!": "¡Hola!", "Hey! A quick heads-up.": "¡Hola! Un aviso rápido.", "Hey! We have some great news.": "¡Hola! Tenemos buenas noticias.", + "Hide password": "Ocultar contraseña", "Hide subcategories": "Ocultar subcategorías", "Hi :name,": "Hola :name,", "Hi! I'm Victor, the founder of Whisper Money. I wanted to personally welcome you and thank you for joining us.": "¡Hola! Soy Víctor, el fundador de Whisper Money. Quería darte la bienvenida personalmente y agradecerte por unirte.", @@ -1317,6 +1318,7 @@ "Shared with": "Compartido con", "Shopping (Clothing, Electronics, Gifts)": "Compras (Ropa, Electrónica, Regalos)", "Show as cash inflow": "Mostrar como entrada de dinero", + "Show password": "Mostrar contraseña", "Show subcategories": "Mostrar subcategorías", "Show as cash outflow": "Mostrar como salida de dinero", "Show in account currency (:currency)": "Mostrar en moneda de la cuenta (:currency)", diff --git a/resources/js/components/delete-user.tsx b/resources/js/components/delete-user.tsx index 8c8c9566..0f484ec5 100644 --- a/resources/js/components/delete-user.tsx +++ b/resources/js/components/delete-user.tsx @@ -12,8 +12,8 @@ import { DialogTitle, DialogTrigger, } from '@/components/ui/dialog'; -import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; +import { PasswordInput } from '@/components/ui/password-input'; import { type SharedData } from '@/types'; import { __ } from '@/utils/i18n'; import { Form, usePage } from '@inertiajs/react'; @@ -104,9 +104,8 @@ export default function DeleteUser() { {__('Password')} - { + it('renders masked by default with a show toggle', () => { + render(); + + expect(screen.getByPlaceholderText('Password')).toHaveAttribute( + 'type', + 'password', + ); + + const toggle = screen.getByRole('button'); + expect(toggle).toHaveAttribute('aria-pressed', 'false'); + }); + + it('reveals and re-hides the value when the toggle is clicked', () => { + render(); + + const input = screen.getByPlaceholderText('Password'); + const toggle = screen.getByRole('button'); + + fireEvent.click(toggle); + expect(input).toHaveAttribute('type', 'text'); + expect(toggle).toHaveAttribute('aria-pressed', 'true'); + + fireEvent.click(toggle); + expect(input).toHaveAttribute('type', 'password'); + expect(toggle).toHaveAttribute('aria-pressed', 'false'); + }); + + it('forwards the ref to the underlying input', () => { + let node: HTMLInputElement | null = null; + render( + { + node = element; + }} + placeholder="Password" + />, + ); + + expect(node).toBeInstanceOf(HTMLInputElement); + }); + + it('disables the toggle when the input is disabled', () => { + render(); + + expect(screen.getByRole('button')).toBeDisabled(); + }); +}); diff --git a/resources/js/components/ui/password-input.tsx b/resources/js/components/ui/password-input.tsx new file mode 100644 index 00000000..9a161553 --- /dev/null +++ b/resources/js/components/ui/password-input.tsx @@ -0,0 +1,48 @@ +import * as React from 'react'; + +import { Input } from '@/components/ui/input'; +import { cn } from '@/lib/utils'; +import { __ } from '@/utils/i18n'; +import { Eye, EyeOff } from 'lucide-react'; + +type PasswordInputProps = Omit, 'type'>; + +const PasswordInput = React.forwardRef( + ({ className, ...props }, ref) => { + const [visible, setVisible] = React.useState(false); + + return ( +
+ + +
+ ); + }, +); +PasswordInput.displayName = 'PasswordInput'; + +export { PasswordInput }; diff --git a/resources/js/components/unlock-message-dialog.tsx b/resources/js/components/unlock-message-dialog.tsx index eb99abfb..3927f3bd 100644 --- a/resources/js/components/unlock-message-dialog.tsx +++ b/resources/js/components/unlock-message-dialog.tsx @@ -10,8 +10,8 @@ import { DialogHeader, DialogTitle, } from '@/components/ui/dialog'; -import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; +import { PasswordInput } from '@/components/ui/password-input'; import { Select, SelectContent, @@ -124,9 +124,8 @@ export default function UnlockMessageDialog({ - )} - {__('Password')} - {__('Confirm password')} - - {__('Confirm password')} - - - - - - -