Initial LinkedSign platform
This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user