Android CI / RC / Android · lint, tests, debug APK, release compile (push) Failing after 1m25s
DH V2 CI / API · typecheck, tests, build (push) Successful in 34s
DH V2 CI / WEB · typecheck, build (push) Successful in 20s
Production dependency audit / API · production dependencies (push) Successful in 9s
Production dependency audit / WEB · production dependencies (push) Successful in 9s
DH V2 CI / Docker / scripts contract (push) Successful in 1m16s
194 lines
9.2 KiB
TypeScript
194 lines
9.2 KiB
TypeScript
import { useEffect, useState } from 'react';
|
|
import type { FormEvent } from 'react';
|
|
import { Alert, LoadingBlock, errorMessage } from '../components/Feedback';
|
|
import { useAuth } from '../auth/AuthContext';
|
|
import { initials } from '../lib/format';
|
|
import {
|
|
getSelfProfile,
|
|
getSelfSmtpSettings,
|
|
saveSelfSmtpSettings,
|
|
testSelfSmtpSettings,
|
|
updateSelfProfile,
|
|
} from '../lib/myProfileApi';
|
|
import type {
|
|
UserSmtpMode,
|
|
UserSmtpSecurityMode,
|
|
UserSmtpSettings,
|
|
} from '../lib/myProfileApi';
|
|
import type { AdministrativeUserProfile } from '../lib/userProfileApi';
|
|
|
|
export function MyProfilePage() {
|
|
const { user } = useAuth();
|
|
const [profile, setProfile] = useState<AdministrativeUserProfile | null>(null);
|
|
const [smtp, setSmtp] = useState<UserSmtpSettings | null>(null);
|
|
const [mode, setMode] = useState<UserSmtpMode>('SYSTEM');
|
|
const [host, setHost] = useState('');
|
|
const [port, setPort] = useState(587);
|
|
const [securityMode, setSecurityMode] = useState<UserSmtpSecurityMode>('STARTTLS');
|
|
const [username, setUsername] = useState('');
|
|
const [password, setPassword] = useState('');
|
|
const [fromName, setFromName] = useState('');
|
|
const [loading, setLoading] = useState(true);
|
|
const [saving, setSaving] = useState('');
|
|
const [error, setError] = useState('');
|
|
const [success, setSuccess] = useState('');
|
|
|
|
const applySmtp = (settings: UserSmtpSettings) => {
|
|
setSmtp(settings);
|
|
setMode(settings.mode);
|
|
setHost(settings.custom?.host ?? '');
|
|
setPort(settings.custom?.port ?? 587);
|
|
setSecurityMode(settings.custom?.securityMode ?? 'STARTTLS');
|
|
setUsername(settings.custom?.username ?? '');
|
|
setFromName(settings.custom?.fromName ?? '');
|
|
setPassword('');
|
|
};
|
|
|
|
const load = async () => {
|
|
setLoading(true);
|
|
try {
|
|
const [loadedProfile, loadedSmtp] = await Promise.all([
|
|
getSelfProfile(),
|
|
getSelfSmtpSettings(),
|
|
]);
|
|
setProfile(loadedProfile);
|
|
applySmtp(loadedSmtp);
|
|
} catch (requestError) {
|
|
setError(errorMessage(requestError));
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
useEffect(() => { void load(); }, []);
|
|
|
|
const saveProfile = async (event: FormEvent<HTMLFormElement>) => {
|
|
event.preventDefault();
|
|
if (!profile) return;
|
|
setError(''); setSuccess(''); setSaving('profile');
|
|
try {
|
|
const form = new FormData(event.currentTarget);
|
|
const updated = await updateSelfProfile({
|
|
email: String(form.get('email') ?? '').trim(),
|
|
phone: String(form.get('phone') ?? '').trim() || null,
|
|
jobTitle: String(form.get('jobTitle') ?? '').trim() || null,
|
|
});
|
|
setProfile(updated);
|
|
setSuccess('Perfil actualizado correctamente.');
|
|
} catch (requestError) {
|
|
setError(errorMessage(requestError));
|
|
} finally {
|
|
setSaving('');
|
|
}
|
|
};
|
|
|
|
const saveSmtp = async () => {
|
|
setError(''); setSuccess(''); setSaving('smtp');
|
|
try {
|
|
const updated = await saveSelfSmtpSettings(mode === 'SYSTEM' ? {
|
|
mode: 'SYSTEM',
|
|
} : {
|
|
mode: 'CUSTOM', host: host.trim(), port, securityMode,
|
|
username: username.trim() || null,
|
|
password: password || undefined,
|
|
fromName: fromName.trim() || null,
|
|
enabled: true,
|
|
});
|
|
applySmtp(updated);
|
|
setSuccess(mode === 'SYSTEM' ? 'Usarás el SMTP general.' : 'SMTP propio guardado.');
|
|
} catch (requestError) {
|
|
setError(errorMessage(requestError));
|
|
} finally {
|
|
setSaving('');
|
|
}
|
|
};
|
|
|
|
const testSmtp = async () => {
|
|
setError(''); setSuccess(''); setSaving('test');
|
|
try {
|
|
const result = await testSelfSmtpSettings();
|
|
setSuccess(`Correo de prueba enviado a ${result.recipient}.`);
|
|
} catch (requestError) {
|
|
setError(errorMessage(requestError));
|
|
} finally {
|
|
setSaving('');
|
|
}
|
|
};
|
|
|
|
if (loading) return <LoadingBlock label="Cargando tu perfil…" />;
|
|
if (!profile || !smtp) return <Alert>{error || 'No se pudo cargar el perfil.'}</Alert>;
|
|
|
|
return <section>
|
|
<div className="page-heading user-heading">
|
|
<div className="profile-title">
|
|
<span className="profile-avatar">{initials(profile.firstName, profile.lastName)}</span>
|
|
<div>
|
|
<span className="eyebrow">MI PERFIL</span>
|
|
<h1>{profile.firstName} {profile.lastName}</h1>
|
|
<p>@{profile.username} · {profile.jobTitle || 'Función sin informar'}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{error && <Alert>{error}</Alert>}
|
|
{success && <Alert type="success">{success}</Alert>}
|
|
|
|
<div className="detail-grid">
|
|
<form className="panel form-panel" onSubmit={saveProfile}>
|
|
<div className="panel-heading">
|
|
<div><span className="eyebrow">CONTACTO</span><h2>Datos del usuario</h2></div>
|
|
</div>
|
|
<div className="form-grid">
|
|
<label className="field"><span>Nombres y apellidos</span><input value={`${profile.firstName} ${profile.lastName}`} disabled /></label>
|
|
<label className="field"><span>Email institucional *</span><input name="email" type="email" defaultValue={profile.email ?? ''} required /></label>
|
|
<label className="field"><span>Teléfono</span><input name="phone" type="tel" defaultValue={profile.phone ?? ''} /></label>
|
|
<label className="field"><span>Cargo / función</span><input name="jobTitle" defaultValue={profile.jobTitle ?? ''} /></label>
|
|
</div>
|
|
<div className="form-actions">
|
|
<button className="button primary" type="submit" disabled={saving === 'profile'}>
|
|
{saving === 'profile' ? 'Guardando…' : 'Guardar perfil'}
|
|
</button>
|
|
</div>
|
|
</form>
|
|
|
|
<div className="panel form-panel">
|
|
<div className="panel-heading">
|
|
<div><span className="eyebrow">CORREO SALIENTE</span><h2>SMTP personal</h2></div>
|
|
</div>
|
|
<p className="muted-copy">Por defecto, tus correos salen mediante el SMTP general. Podés cambiar a una cuenta SMTP propia con un clic.</p>
|
|
<div className="choice-grid">
|
|
<label className={`choice-card ${mode === 'SYSTEM' ? 'selected' : ''}`}>
|
|
<input type="radio" name="smtpMode" checked={mode === 'SYSTEM'} onChange={() => setMode('SYSTEM')} />
|
|
<span><strong>Usar SMTP general</strong><small>{smtp.generalConfigured ? 'Configurado y disponible' : 'Todavía no configurado por el administrador'}</small></span>
|
|
<span className="icon">✓</span>
|
|
</label>
|
|
<label className={`choice-card ${mode === 'CUSTOM' ? 'selected' : ''}`}>
|
|
<input type="radio" name="smtpMode" checked={mode === 'CUSTOM'} onChange={() => setMode('CUSTOM')} />
|
|
<span><strong>Usar SMTP propio</strong><small>Los envíos saldrán con tu cuenta {profile.email}</small></span>
|
|
<span className="icon">✓</span>
|
|
</label>
|
|
</div>
|
|
{mode === 'CUSTOM' && <>
|
|
<div className="form-grid" style={{ marginTop: 16 }}>
|
|
<label className="field"><span>Servidor SMTP *</span><input value={host} onChange={(event) => setHost(event.target.value)} placeholder="smtp.ejemplo.com" /></label>
|
|
<label className="field"><span>Puerto *</span><input type="number" min={1} max={65535} value={port} onChange={(event) => setPort(Number(event.target.value))} /></label>
|
|
<label className="field"><span>Seguridad *</span><select value={securityMode} onChange={(event) => setSecurityMode(event.target.value as UserSmtpSecurityMode)}><option value="STARTTLS">STARTTLS</option><option value="TLS">TLS</option><option value="NONE">Sin cifrado</option></select></label>
|
|
<label className="field"><span>Usuario SMTP</span><input value={username} onChange={(event) => setUsername(event.target.value)} autoComplete="username" /></label>
|
|
<label className="field"><span>Contraseña SMTP</span><input type="password" value={password} onChange={(event) => setPassword(event.target.value)} autoComplete="new-password" placeholder={smtp.custom?.hasPassword ? 'Dejar vacío para conservarla' : 'Contraseña SMTP'} /></label>
|
|
<label className="field"><span>Nombre del remitente</span><input value={fromName} onChange={(event) => setFromName(event.target.value)} placeholder={`${profile.firstName} ${profile.lastName}`} /></label>
|
|
</div>
|
|
<p className="muted-copy">La contraseña se cifra en el servidor y nunca vuelve a mostrarse. El remitente será <strong>{profile.email}</strong>.</p>
|
|
</>}
|
|
<div className="form-actions wrap-actions">
|
|
<button className="button primary" type="button" onClick={saveSmtp} disabled={saving === 'smtp' || (mode === 'CUSTOM' && (!host.trim() || !port))}>{saving === 'smtp' ? 'Guardando…' : 'Guardar configuración'}</button>
|
|
<button className="button secondary" type="button" onClick={testSmtp} disabled={Boolean(saving)}>{saving === 'test' ? 'Enviando…' : 'Enviar correo de prueba'}</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className="panel" style={{ marginTop: 16 }}>
|
|
<div className="panel-heading"><div><span className="eyebrow">SEGURIDAD</span><h2>Cuenta</h2></div></div>
|
|
<p>Tu email es obligatorio para operar en DH Inspección. La contraseña SMTP nunca se muestra ni se incluye en auditorías.</p>
|
|
<p className="muted-copy">Usuario autenticado: @{user?.username ?? profile.username}</p>
|
|
</div>
|
|
</section>;
|
|
}
|