- New reports backend (registry, service, controller, outputs, types) with catalog endpoint + slug/CSV/XLSX/PDF/print outputs. - /reportes catalog + /reportes/[slug] runner; ReportRunner + ContextReports components wire pre-filtered links from domain pages. - Fix: /reportes/[slug] now reads searchParams and forwards initialParams to ReportRunner so /reportes/edo-cuenta-datos?customerId=... auto-runs instead of dropping the id and forcing a manual customer search. - /inicio landing page; root + login redirect to /inicio. - Company header env vars + logo asset for PDF/print rendering. - exceljs + pdfkit deps.
173 lines
5.1 KiB
TypeScript
173 lines
5.1 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useState } from "react";
|
|
import { useRouter } from "next/navigation";
|
|
import { ApiError, login, me } from "@/lib/api";
|
|
|
|
export default function LoginPage() {
|
|
const router = useRouter();
|
|
const [email, setEmail] = useState("");
|
|
const [password, setPassword] = useState("");
|
|
const [submitting, setSubmitting] = useState(false);
|
|
const [error, setError] = useState<string | null>(null);
|
|
const [bootChecking, setBootChecking] = useState(true);
|
|
|
|
// If already signed in, skip straight to the customer browser.
|
|
useEffect(() => {
|
|
let alive = true;
|
|
me()
|
|
.then(() => router.replace("/inicio"))
|
|
.catch(() => {
|
|
if (alive) setBootChecking(false);
|
|
});
|
|
return () => {
|
|
alive = false;
|
|
};
|
|
}, [router]);
|
|
|
|
async function handleSubmit(e: React.FormEvent) {
|
|
e.preventDefault();
|
|
setError(null);
|
|
setSubmitting(true);
|
|
try {
|
|
await login(email.trim(), password);
|
|
router.replace("/inicio");
|
|
} catch (err) {
|
|
if (err instanceof ApiError && err.status === 401) {
|
|
setError("Correo o contraseña incorrectos");
|
|
} else if (err instanceof ApiError && err.status === 0) {
|
|
setError(err.message);
|
|
} else {
|
|
setError("No se pudo iniciar sesión. Inténtalo de nuevo.");
|
|
}
|
|
setSubmitting(false);
|
|
}
|
|
}
|
|
|
|
if (bootChecking) {
|
|
return (
|
|
<div
|
|
style={{
|
|
minHeight: "100vh",
|
|
display: "grid",
|
|
placeItems: "center",
|
|
color: "var(--brand-700)",
|
|
}}
|
|
>
|
|
<span className="spinner" aria-label="Cargando" />
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="login-wrap">
|
|
{/* Brand / narrative panel */}
|
|
<aside className="login-aside" aria-hidden="false">
|
|
<div className="login-aside-top">
|
|
<div className="login-brand">
|
|
<img
|
|
src="/images/company_logo.png"
|
|
alt=""
|
|
className="brand-mark"
|
|
/>
|
|
<div className="brand-text">
|
|
<span className="brand-name" style={{ color: "#f6f3ec" }}>
|
|
Jorge Cuadros
|
|
</span>
|
|
<span className="brand-sub">& Asociados</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="login-aside-mid">
|
|
<p className="eyebrow" style={{ color: "rgba(242,239,231,0.6)" }}>
|
|
Plataforma interna
|
|
</p>
|
|
<h1 className="login-headline">
|
|
Un solo expediente para <em>Servicios</em> y <em>Seguros</em>.
|
|
</h1>
|
|
<p className="login-lede">
|
|
Consulta en un mismo lugar las propiedades, pólizas y el estado de
|
|
cuenta de cada cliente en Baja California.
|
|
</p>
|
|
</div>
|
|
|
|
<div className="login-aside-foot">
|
|
<div className="login-lob">
|
|
<span className="badge badge-servicios">
|
|
<span className="dot" /> Servicios
|
|
</span>
|
|
<span className="badge badge-seguros">
|
|
<span className="dot" /> Seguros
|
|
</span>
|
|
</div>
|
|
<span className="login-foot-note">
|
|
Gestión de propiedades y correduría de seguros
|
|
</span>
|
|
</div>
|
|
</aside>
|
|
|
|
{/* Form panel */}
|
|
<section className="login-form-panel">
|
|
<div className="login-form-inner rise">
|
|
<p className="eyebrow">Acceso del personal</p>
|
|
<h2 className="login-form-title">Iniciar sesión</h2>
|
|
<p className="muted" style={{ marginTop: 6, marginBottom: 28 }}>
|
|
Ingresa con tu cuenta para continuar.
|
|
</p>
|
|
|
|
<form onSubmit={handleSubmit} noValidate>
|
|
<label className="field">
|
|
<span className="field-label">Correo electrónico</span>
|
|
<input
|
|
type="email"
|
|
autoComplete="username"
|
|
className="input"
|
|
value={email}
|
|
onChange={(e) => setEmail(e.target.value)}
|
|
placeholder="nombre@jorgecuadros.local"
|
|
required
|
|
autoFocus
|
|
/>
|
|
</label>
|
|
|
|
<label className="field">
|
|
<span className="field-label">Contraseña</span>
|
|
<input
|
|
type="password"
|
|
autoComplete="current-password"
|
|
className="input"
|
|
value={password}
|
|
onChange={(e) => setPassword(e.target.value)}
|
|
placeholder="••••••••"
|
|
required
|
|
/>
|
|
</label>
|
|
|
|
{error && (
|
|
<div className="login-error" role="alert">
|
|
{error}
|
|
</div>
|
|
)}
|
|
|
|
<button
|
|
type="submit"
|
|
className="btn btn-primary login-submit"
|
|
disabled={submitting}
|
|
>
|
|
{submitting ? (
|
|
<>
|
|
<span className="spinner" style={{ width: 15, height: 15 }} />
|
|
Entrando…
|
|
</>
|
|
) : (
|
|
"Entrar"
|
|
)}
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
);
|
|
}
|