chore: import DH V2 D5.6.4 production baseline

This commit is contained in:
DH V2
2026-09-05 10:12:35 -03:00
commit 82213e72f5
757 changed files with 84218 additions and 0 deletions
+57
View File
@@ -0,0 +1,57 @@
import { useEffect, useState } from 'react';
import type { FormEvent } from 'react';
import { Link, useNavigate } from 'react-router';
import { Alert, LoadingBlock, errorMessage } from '../components/Feedback';
import { Icon } from '../components/Icon';
import { createUser, listRoles } from '../lib/api';
import type { AdministrativeRole } from '../lib/api';
export function NewUserPage() {
const navigate = useNavigate();
const [roles, setRoles] = useState<AdministrativeRole[]>([]);
const [loading, setLoading] = useState(true);
const [submitting, setSubmitting] = useState(false);
const [error, setError] = useState('');
const [roleIds, setRoleIds] = useState<string[]>([]);
useEffect(() => {
listRoles().then(setRoles).catch((requestError) => setError(errorMessage(requestError))).finally(() => setLoading(false));
}, []);
const toggleRole = (id: string) => setRoleIds((current) => current.includes(id) ? current.filter((value) => value !== id) : [...current, id]);
const submit = async (event: FormEvent<HTMLFormElement>) => {
event.preventDefault();
setError('');
setSubmitting(true);
const data = new FormData(event.currentTarget);
try {
const created = await createUser({
username: String(data.get('username')),
email: String(data.get('email') ?? '') || undefined,
firstName: String(data.get('firstName')),
lastName: String(data.get('lastName')),
password: String(data.get('password')),
mustChangePassword: data.get('mustChangePassword') === 'on',
roleIds,
});
navigate(`/admin/users/${created.id}`, { replace: true, state: { created: true } });
} catch (requestError) {
setError(errorMessage(requestError));
} finally {
setSubmitting(false);
}
};
return <section className="narrow-section">
<div className="breadcrumb"><Link to="/admin/users">Usuarios</Link><span>/</span><strong>Nuevo usuario</strong></div>
<div className="page-heading"><div><span className="eyebrow">NUEVO ACCESO</span><h1>Crear usuario</h1><p>La contraseña inicial puede obligarse a cambiar en el primer ingreso.</p></div></div>
{error && <Alert>{error}</Alert>}
{loading ? <LoadingBlock /> : <form className="panel form-panel" onSubmit={submit}>
<div className="form-section"><h2>Datos personales</h2><div className="form-grid"><label className="field"><span>Nombre</span><input name="firstName" required maxLength={120} /></label><label className="field"><span>Apellido</span><input name="lastName" required maxLength={120} /></label><label className="field"><span>Usuario</span><input name="username" required minLength={3} maxLength={80} pattern="[a-zA-Z][a-zA-Z0-9._-]+" /></label><label className="field"><span>Email <em>opcional</em></span><input name="email" type="email" maxLength={320} /></label></div></div>
<div className="form-section"><h2>Seguridad</h2><label className="field"><span>Contraseña temporal</span><input name="password" type="password" minLength={12} maxLength={128} required autoComplete="new-password" /><small>Mínimo 12 caracteres.</small></label><label className="check-row"><input name="mustChangePassword" type="checkbox" defaultChecked /><span><strong>Exigir cambio de contraseña</strong><small>El usuario no podrá acceder a otros módulos hasta actualizarla.</small></span></label></div>
<div className="form-section"><h2>Roles</h2><div className="choice-grid">{roles.map((role) => <label className={`choice-card ${roleIds.includes(role.id) ? 'selected' : ''}`} key={role.id}><input type="checkbox" checked={roleIds.includes(role.id)} onChange={() => toggleRole(role.id)} /><span><strong>{role.name}</strong><small>{role.description}</small></span><Icon name="check" /></label>)}</div></div>
<div className="form-actions"><Link className="button secondary" to="/admin/users">Cancelar</Link><button className="button primary" disabled={submitting}>{submitting ? 'Creando…' : 'Crear usuario'}</button></div>
</form>}
</section>;
}