feat: boton de mostrar/ocultar clave en todos los campos de contraseña
Componente reutilizable PasswordInput (icono de ojo) usado en login, registro, restablecer clave, cambiar clave, crear usuario y la clave SMTP de Configuracion, para que el usuario pueda verificar que la esta escribiendo correctamente. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,7 @@ import { Flyout, FlyoutItem, FlyoutSeparator } from '../components/Flyout.jsx';
|
||||
import { DetailPopup } from '../components/DetailPopup.jsx';
|
||||
import { Field } from '../components/Field.jsx';
|
||||
import { Button } from '../components/Button.jsx';
|
||||
import { PasswordInput } from '../components/PasswordInput.jsx';
|
||||
import { useAuth } from '../context/auth-context.jsx';
|
||||
import { useUi } from '../context/ui-context.jsx';
|
||||
import { useAsync } from '../hooks/useAsync.js';
|
||||
@@ -131,13 +132,13 @@ export default function AppShell() {
|
||||
<DetailPopup open={changingPassword} title={t('change_password_title')} onClose={() => setChangingPassword(false)} width={420} height={380}>
|
||||
{passwordError && <div className="login-error">{passwordError}</div>}
|
||||
<Field label={t('change_password_current')}>
|
||||
<input className="input" type="password" value={passwordForm.currentPassword} onChange={(e) => setPasswordForm((f) => ({ ...f, currentPassword: e.target.value }))} autoFocus />
|
||||
<PasswordInput value={passwordForm.currentPassword} onChange={(e) => setPasswordForm((f) => ({ ...f, currentPassword: e.target.value }))} autoFocus />
|
||||
</Field>
|
||||
<Field label={t('reset_password_new')}>
|
||||
<input className="input" type="password" value={passwordForm.newPassword} onChange={(e) => setPasswordForm((f) => ({ ...f, newPassword: e.target.value }))} />
|
||||
<PasswordInput value={passwordForm.newPassword} onChange={(e) => setPasswordForm((f) => ({ ...f, newPassword: e.target.value }))} />
|
||||
</Field>
|
||||
<Field label={t('register_password_confirm')}>
|
||||
<input className="input" type="password" value={passwordForm.confirmPassword} onChange={(e) => setPasswordForm((f) => ({ ...f, confirmPassword: e.target.value }))} />
|
||||
<PasswordInput value={passwordForm.confirmPassword} onChange={(e) => setPasswordForm((f) => ({ ...f, confirmPassword: e.target.value }))} />
|
||||
</Field>
|
||||
<div style={{ display: 'flex', gap: 8 }}>
|
||||
<Button variant="accent" icon={Save} onClick={submitChangePassword}>{t('common_save')}</Button>
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import { useState } from 'react';
|
||||
import { Eye, EyeOff } from 'lucide-react';
|
||||
import { useUi } from '../context/ui-context.jsx';
|
||||
|
||||
export function PasswordInput({ value, onChange, required, autoFocus, placeholder, id }) {
|
||||
const { t } = useUi();
|
||||
const [visible, setVisible] = useState(false);
|
||||
return (
|
||||
<div className="password-input">
|
||||
<input
|
||||
id={id}
|
||||
className="input"
|
||||
type={visible ? 'text' : 'password'}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
required={required}
|
||||
autoFocus={autoFocus}
|
||||
placeholder={placeholder}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
className="icon-btn password-toggle"
|
||||
onClick={() => setVisible((v) => !v)}
|
||||
tabIndex={-1}
|
||||
aria-label={visible ? t('password_hide') : t('password_show')}
|
||||
title={visible ? t('password_hide') : t('password_show')}
|
||||
>
|
||||
{visible ? <EyeOff /> : <Eye />}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -59,6 +59,8 @@ export const messages = {
|
||||
smtp_test_send: 'Enviar prueba',
|
||||
smtp_test_sending: 'Enviando...',
|
||||
smtp_test_success: 'Correo de prueba enviado correctamente a',
|
||||
password_show: 'Mostrar clave',
|
||||
password_hide: 'Ocultar clave',
|
||||
common_new: 'Nuevo',
|
||||
common_edit: 'Editar',
|
||||
common_save: 'Guardar',
|
||||
@@ -242,6 +244,8 @@ export const messages = {
|
||||
smtp_test_send: 'Send test',
|
||||
smtp_test_sending: 'Sending...',
|
||||
smtp_test_success: 'Test email sent successfully to',
|
||||
password_show: 'Show password',
|
||||
password_hide: 'Hide password',
|
||||
common_new: 'New',
|
||||
common_edit: 'Edit',
|
||||
common_save: 'Save',
|
||||
|
||||
@@ -6,6 +6,7 @@ import { getSmtpConfig, updateSmtpConfig, testSmtpConfig } from '../../services/
|
||||
import { Field, FieldRow } from '../../components/Field.jsx';
|
||||
import { Button } from '../../components/Button.jsx';
|
||||
import { InfoBar } from '../../components/InfoBar.jsx';
|
||||
import { PasswordInput } from '../../components/PasswordInput.jsx';
|
||||
|
||||
export default function SmtpSettingsPanel() {
|
||||
const { t, pushToast } = useUi();
|
||||
@@ -66,7 +67,7 @@ export default function SmtpSettingsPanel() {
|
||||
</Field>
|
||||
</FieldRow>
|
||||
<Field label={t('smtp_pass')} hint={config.configured ? t('smtp_pass_configured_hint') : undefined}>
|
||||
<input className="input" type="password" value={form.pass} onChange={(e) => setForm((f) => ({ ...f, pass: e.target.value }))} />
|
||||
<PasswordInput value={form.pass} onChange={(e) => setForm((f) => ({ ...f, pass: e.target.value }))} />
|
||||
</Field>
|
||||
<Button variant="accent" icon={Save} onClick={save}>{t('common_save')}</Button>
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useAuth } from '../../context/auth-context.jsx';
|
||||
import { useUi } from '../../context/ui-context.jsx';
|
||||
import { Field } from '../../components/Field.jsx';
|
||||
import { Button } from '../../components/Button.jsx';
|
||||
import { PasswordInput } from '../../components/PasswordInput.jsx';
|
||||
import logoDark from '../../assets/logoLinkedCorpTxt.png';
|
||||
import logoLight from '../../assets/logoLinkedCorpTxtBlack.png';
|
||||
|
||||
@@ -43,7 +44,7 @@ export default function LoginPage() {
|
||||
<input className="input" type="email" value={email} onChange={(e) => setEmail(e.target.value)} required autoFocus />
|
||||
</Field>
|
||||
<Field label={t('login_password')}>
|
||||
<input className="input" type="password" value={password} onChange={(e) => setPassword(e.target.value)} required />
|
||||
<PasswordInput value={password} onChange={(e) => setPassword(e.target.value)} required />
|
||||
</Field>
|
||||
<Button variant="accent" icon={LogIn} type="submit" disabled={loading} style={{ width: '100%', justifyContent: 'center' }}>
|
||||
{t('login_submit')}
|
||||
|
||||
@@ -6,6 +6,7 @@ import { useUi } from '../../context/ui-context.jsx';
|
||||
import { register } from '../../services/auth.js';
|
||||
import { Field } from '../../components/Field.jsx';
|
||||
import { Button } from '../../components/Button.jsx';
|
||||
import { PasswordInput } from '../../components/PasswordInput.jsx';
|
||||
import logoDark from '../../assets/logoLinkedCorpTxt.png';
|
||||
import logoLight from '../../assets/logoLinkedCorpTxtBlack.png';
|
||||
|
||||
@@ -61,10 +62,10 @@ export default function RegisterPage() {
|
||||
<input className="input" type="email" value={email} onChange={(e) => setEmail(e.target.value)} required />
|
||||
</Field>
|
||||
<Field label={t('login_password')}>
|
||||
<input className="input" type="password" value={password} onChange={(e) => setPassword(e.target.value)} required />
|
||||
<PasswordInput value={password} onChange={(e) => setPassword(e.target.value)} required />
|
||||
</Field>
|
||||
<Field label={t('register_password_confirm')}>
|
||||
<input className="input" type="password" value={confirm} onChange={(e) => setConfirm(e.target.value)} required />
|
||||
<PasswordInput value={confirm} onChange={(e) => setConfirm(e.target.value)} required />
|
||||
</Field>
|
||||
<Button variant="accent" icon={UserPlus} type="submit" disabled={loading} style={{ width: '100%', justifyContent: 'center' }}>
|
||||
{t('register_submit')}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useUi } from '../../context/ui-context.jsx';
|
||||
import { resetPassword } from '../../services/auth.js';
|
||||
import { Field } from '../../components/Field.jsx';
|
||||
import { Button } from '../../components/Button.jsx';
|
||||
import { PasswordInput } from '../../components/PasswordInput.jsx';
|
||||
import logoDark from '../../assets/logoLinkedCorpTxt.png';
|
||||
import logoLight from '../../assets/logoLinkedCorpTxtBlack.png';
|
||||
|
||||
@@ -49,10 +50,10 @@ export default function ResetPasswordPage() {
|
||||
) : (
|
||||
<>
|
||||
<Field label={t('reset_password_new')}>
|
||||
<input className="input" type="password" value={password} onChange={(e) => setPassword(e.target.value)} required autoFocus />
|
||||
<PasswordInput value={password} onChange={(e) => setPassword(e.target.value)} required autoFocus />
|
||||
</Field>
|
||||
<Field label={t('register_password_confirm')}>
|
||||
<input className="input" type="password" value={confirm} onChange={(e) => setConfirm(e.target.value)} required />
|
||||
<PasswordInput value={confirm} onChange={(e) => setConfirm(e.target.value)} required />
|
||||
</Field>
|
||||
<Button variant="accent" icon={KeyRound} type="submit" disabled={loading || !token} style={{ width: '100%', justifyContent: 'center' }}>
|
||||
{t('reset_password_submit')}
|
||||
|
||||
@@ -12,6 +12,7 @@ import { Field, FieldRow } from '../../components/Field.jsx';
|
||||
import { ComboBox } from '../../components/ComboBox.jsx';
|
||||
import { Badge } from '../../components/Badge.jsx';
|
||||
import { DetailPopup } from '../../components/DetailPopup.jsx';
|
||||
import { PasswordInput } from '../../components/PasswordInput.jsx';
|
||||
|
||||
function flatten(nodes, acc = []) {
|
||||
for (const n of nodes) { acc.push(n); if (n.children?.length) flatten(n.children, acc); }
|
||||
@@ -169,7 +170,7 @@ export default function UsersPanel() {
|
||||
</Field>
|
||||
{!selected && (
|
||||
<Field label={t('user_password')}>
|
||||
<input className="input" type="password" value={form.password} onChange={(e) => setForm((f) => ({ ...f, password: e.target.value }))} />
|
||||
<PasswordInput value={form.password} onChange={(e) => setForm((f) => ({ ...f, password: e.target.value }))} />
|
||||
</Field>
|
||||
)}
|
||||
</FieldRow>
|
||||
|
||||
@@ -124,6 +124,15 @@ button { font-family: inherit; }
|
||||
.search-input input { padding-left: 32px; }
|
||||
.search-input svg { position: absolute; left: 9px; top: 50%; transform: translateY(-50%); width: 15px; height: 15px; color: rgb(var(--muted)); }
|
||||
|
||||
/* --- Password reveal toggle: input queda dentro para no romper el layout de .field --- */
|
||||
.password-input { position: relative; width: 100%; }
|
||||
.password-input input { padding-right: 34px; }
|
||||
.password-input .password-toggle {
|
||||
position: absolute; right: 2px; top: 50%; transform: translateY(-50%);
|
||||
width: 28px; height: 28px;
|
||||
}
|
||||
.password-input .password-toggle svg { width: 16px; height: 16px; }
|
||||
|
||||
.list-card { background: rgb(var(--surface)); border: 1px solid rgb(var(--line)); border-radius: var(--r-lg); overflow: hidden; }
|
||||
table.list-table { width: 100%; border-collapse: collapse; font-size: .8rem; }
|
||||
.list-table th {
|
||||
|
||||
Reference in New Issue
Block a user