Login GRH, marca de agua tipo membretado y logo en PNG sin fondo

This commit is contained in:
Marlene-Angel
2026-01-12 13:45:25 -08:00
parent 4d807babf7
commit dd3997a3a8
9 changed files with 378 additions and 68 deletions

View File

@@ -10,8 +10,10 @@ interface TopMenuProps {
userEmail?: string;
avatarUrl?: string | null;
onLogout?: () => void;
onOpenProfile?: () => void;
// ✅ NUEVO: en vez de cerrar, pedimos confirmación desde App
onRequestLogout?: () => void;
}
const TopMenu: React.FC<TopMenuProps> = ({
@@ -23,11 +25,10 @@ const TopMenu: React.FC<TopMenuProps> = ({
userEmail,
avatarUrl = null,
onLogout,
onOpenProfile,
onRequestLogout,
}) => {
const [openUserMenu, setOpenUserMenu] = useState(false);
const menuRef = useRef<HTMLDivElement | null>(null);
const initials = useMemo(() => {
@@ -37,7 +38,6 @@ const TopMenu: React.FC<TopMenuProps> = ({
return (a + b).toUpperCase();
}, [userName]);
// Cerrar al click afuera
useEffect(() => {
function handleClickOutside(e: MouseEvent) {
if (!openUserMenu) return;
@@ -48,7 +48,6 @@ const TopMenu: React.FC<TopMenuProps> = ({
return () => document.removeEventListener("mousedown", handleClickOutside);
}, [openUserMenu]);
// Cerrar con ESC
useEffect(() => {
function handleEsc(e: KeyboardEvent) {
if (e.key === "Escape") setOpenUserMenu(false);
@@ -117,12 +116,8 @@ const TopMenu: React.FC<TopMenuProps> = ({
role="menu"
className="
absolute right-0 mt-2 w-80
rounded-2xl
bg-white
border border-slate-200
shadow-xl
overflow-hidden
z-50
rounded-2xl bg-white border border-slate-200
shadow-xl overflow-hidden z-50
"
>
{/* Header usuario */}
@@ -157,7 +152,6 @@ const TopMenu: React.FC<TopMenuProps> = ({
</div>
</div>
{/* Items (solo 2) */}
<MenuItem
label="Ver / editar perfil"
onClick={() => {
@@ -174,11 +168,7 @@ const TopMenu: React.FC<TopMenuProps> = ({
tone="danger"
onClick={() => {
setOpenUserMenu(false);
if (onLogout) onLogout();
else {
localStorage.removeItem("token");
window.location.href = "/login";
}
onRequestLogout?.(); // ✅ abre confirm modal en App
}}
left={<LogOut size={16} />}
/>
@@ -225,4 +215,3 @@ function MenuItem({
}
export default TopMenu;

View File

@@ -54,12 +54,17 @@ export default function ProfileModal({
// Limpieza de object URLs
useEffect(() => {
return () => {
if (lastPreviewUrlRef.current) URL.revokeObjectURL(lastPreviewUrlRef.current);
if (lastPreviewUrlRef.current) {
URL.revokeObjectURL(lastPreviewUrlRef.current);
}
};
}, []);
const initials = useMemo(() => {
const parts = (name || "").trim().split(/\s+/).filter(Boolean);
const parts = (name || "")
.trim()
.split(/\s+/)
.filter(Boolean);
const a = parts[0]?.[0] ?? "U";
const b = parts[1]?.[0] ?? "";
return (a + b).toUpperCase();
@@ -148,7 +153,9 @@ export default function ProfileModal({
<div className="rounded-2xl bg-white shadow-xl border border-slate-200 overflow-hidden">
{/* Header */}
<div className="px-6 py-4 border-b border-slate-200">
<div className="text-base font-semibold text-slate-900">Editar perfil</div>
<div className="text-base font-semibold text-slate-900">
Editar perfil
</div>
</div>
{/* Body */}
@@ -206,7 +213,6 @@ export default function ProfileModal({
{/* RIGHT: Form */}
<div className="rounded-2xl border border-slate-200 p-5">
{/* “correo electronico” como en tu dibujo */}
<div className="text-xs font-semibold text-slate-500 uppercase tracking-wide">
correo electrónico
</div>
@@ -253,20 +259,20 @@ export default function ProfileModal({
>
Cancelar
</button>
<button
type="button"
onClick={handleSubmit}
disabled={loading}
className={[
"rounded-xl px-4 py-2 text-sm font-semibold",
"bg-blue-600 text-white hover:bg-blue-700",
"focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2",
loading ? "opacity-60 cursor-not-allowed" : "",
].join(" ")}
>
{loading ? "Guardando..." : "Guardar"}
</button>
<button
type="button"
onClick={handleSubmit}
disabled={loading}
className={[
"rounded-xl px-4 py-2 text-sm font-semibold",
"bg-blue-600 text-white hover:bg-blue-700",
"focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2",
loading ? "opacity-60 cursor-not-allowed" : "",
].join(" ")}
>
{loading ? "Guardando..." : "Guardar"}
</button>
</div>
</div>
</div>

View File

@@ -0,0 +1,51 @@
import React from "react";
import grhWatermark from "../../../assets/images/grhWatermark.png";
export default function Watermark({
opacity = 0.08,
size = 520,
}: {
opacity?: number;
size?: number;
}) {
return (
<div className="pointer-events-none fixed inset-0 z-20 overflow-hidden">
{/* Marca centrada (SIN rotación) */}
<div
className="absolute left-1/2 top-1/2"
style={{
transform: "translate(-50%, -50%)",
opacity,
}}
>
<img
src={grhWatermark}
alt="GRH Watermark"
width={size}
height={size}
className="select-none object-contain"
draggable={false}
style={{ filter: "grayscale(100%)" }} // opcional
/>
</div>
{/* Marca secundaria (SIN rotación) */}
<div
className="absolute right-[-140px] bottom-[-180px]"
style={{
opacity: Math.max(0.04, opacity * 0.55),
}}
>
<img
src={grhWatermark}
alt="GRH Watermark"
width={Math.round(size * 0.75)}
height={Math.round(size * 0.75)}
className="select-none object-contain"
draggable={false}
style={{ filter: "grayscale(100%)" }} // opcional
/>
</div>
</div>
);
}