Initial LinkedSign platform

This commit is contained in:
2026-04-06 20:33:59 -05:00
commit 1074c7c9d2
159 changed files with 19072 additions and 0 deletions
+160
View File
@@ -0,0 +1,160 @@
import { useEffect, useState } from 'react';
import { Link, useNavigate, useLocation } from 'react-router-dom';
import { useAuth } from '../../contexts/AuthContext';
import { FileText, Shield, PenTool, LayoutDashboard, LogOut, Palette, Menu, X, KeyRound, Search, Plus, Users, Settings, ScrollText } from 'lucide-react';
import ChangePasswordModal from '../ui/ChangePasswordModal';
const navSections = [
{
label: 'Principal',
items: [
{ to: '/dashboard', icon: LayoutDashboard, label: 'Dashboard' },
{ to: '/documents', icon: FileText, label: 'Mis documentos' },
{ to: '/signatures', icon: PenTool, label: 'Firmar ahora' },
{ to: '/certificates', icon: Users, label: 'Firmantes' },
],
},
{
label: 'Sistema',
items: [
{ to: '/signature-style', icon: Settings, label: 'Configuración' },
],
},
];
const topbarTitles = {
'/dashboard': 'Dashboard',
'/documents': 'Mis documentos',
'/certificates': 'Firmantes',
'/signature-style': 'Configuración',
'/signatures': 'Firmar ahora',
};
export default function Layout({ children }) {
const { user, logout } = useAuth();
const navigate = useNavigate();
const location = useLocation();
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
const [showChangePassword, setShowChangePassword] = useState(false);
useEffect(() => {
setMobileMenuOpen(false);
}, [location.pathname]);
const handleLogout = async () => {
await logout();
navigate('/login');
};
const currentTitle = topbarTitles[location.pathname] || 'FirmaDoc';
return (
<div className="app-layout">
{showChangePassword && (
<ChangePasswordModal onClose={() => setShowChangePassword(false)} />
)}
<header className="mobile-topbar">
<button
className="mobile-menu-toggle"
type="button"
onClick={() => setMobileMenuOpen(true)}
aria-label="Abrir menú"
>
<Menu size={20} />
</button>
<div className="mobile-topbar-brand">
<FileText size={18} />
<span>FirmaDoc</span>
</div>
</header>
{mobileMenuOpen && (
<button
type="button"
className="mobile-sidebar-backdrop"
aria-label="Cerrar menú"
onClick={() => setMobileMenuOpen(false)}
/>
)}
<aside className={`sidebar ${mobileMenuOpen ? 'sidebar--mobile-open' : ''}`}>
<div className="sidebar-brand">
<div className="sidebar-brand-mark">
<FileText size={18} />
</div>
<div className="sidebar-brand-copy">
<span>FirmaDoc</span>
<small>Plataforma web</small>
</div>
<button
type="button"
className="mobile-sidebar-close"
onClick={() => setMobileMenuOpen(false)}
aria-label="Cerrar menú"
>
<X size={18} />
</button>
</div>
<nav className="sidebar-nav">
{navSections.map((section) => (
<div key={section.label} className="sidebar-group">
<div className="nav-group-label">{section.label}</div>
{section.items.map(({ to, icon: Icon, label }) => (
<Link
key={to}
to={to}
className={`nav-item ${location.pathname.startsWith(to) ? 'active' : ''}`}
onClick={() => setMobileMenuOpen(false)}
>
<Icon size={15} />
<span>{label}</span>
</Link>
))}
</div>
))}
<div className="sidebar-group sidebar-group--disabled">
<div className="nav-group-label">Extra</div>
<span className="nav-item nav-item--static">
<ScrollText size={15} />
<span>Auditoría</span>
</span>
</div>
</nav>
<div className="sidebar-footer">
<button
type="button"
className="user-info-button"
onClick={() => setShowChangePassword(true)}
title="Cambiar contraseña"
>
<div className="user-info">
<div className="user-avatar">{user?.name?.[0]?.toUpperCase()}</div>
<div className="user-details">
<span className="user-name">{user?.name}</span>
<span className="user-email">{user?.email}</span>
</div>
</div>
<KeyRound size={16} />
</button>
<button className="btn-logout" onClick={handleLogout} title="Cerrar sesión">
<LogOut size={18} />
</button>
</div>
</aside>
<main className="main-content">
<div className="topbar">
<div className="topbar-title">{currentTitle}</div>
<div className="search-box">
<Search size={13} />
<input placeholder="Buscar documentos..." aria-label="Buscar documentos" />
</div>
<button className="btn btn-primary topbar-cta" type="button" onClick={() => navigate('/documents')}>
<Plus size={14} />
Nuevo documento
</button>
</div>
{children}
</main>
</div>
);
}