feat: Implementação de submenus laterais (flyout), correções de UI e proteção de rotas (AuthGuard)

This commit is contained in:
Erik Silva
2025-12-12 15:24:38 -03:00
parent 83ce15bb36
commit 04c954c3d9
36 changed files with 2628 additions and 923 deletions

View File

@@ -0,0 +1,44 @@
'use client';
import { useEffect, useState } from 'react';
import { useRouter, usePathname } from 'next/navigation';
import { isAuthenticated } from '@/lib/auth';
export default function AuthGuard({ children }: { children: React.ReactNode }) {
const router = useRouter();
const pathname = usePathname();
const [authorized, setAuthorized] = useState(false);
useEffect(() => {
const checkAuth = () => {
const isAuth = isAuthenticated();
if (!isAuth) {
setAuthorized(false);
// Evitar redirect loop se já estiver no login
if (pathname !== '/login') {
router.push('/login');
}
} else {
setAuthorized(true);
}
};
checkAuth();
const handleStorageChange = (e: StorageEvent) => {
if (e.key === 'token' || e.key === 'user') {
checkAuth();
}
};
window.addEventListener('storage', handleStorageChange);
return () => window.removeEventListener('storage', handleStorageChange);
}, [router, pathname]);
if (!authorized) {
return null;
}
return <>{children}</>;
}

View File

@@ -1,14 +1,15 @@
'use client';
import React, { useState } from 'react';
import { SidebarRail } from './SidebarRail';
import { SidebarRail, MenuItem } from './SidebarRail';
import { TopBar } from './TopBar';
interface DashboardLayoutProps {
children: React.ReactNode;
menuItems: MenuItem[];
}
export const DashboardLayout: React.FC<DashboardLayoutProps> = ({ children }) => {
export const DashboardLayout: React.FC<DashboardLayoutProps> = ({ children, menuItems }) => {
// Estado centralizado do layout
const [isExpanded, setIsExpanded] = useState(true);
const [activeTab, setActiveTab] = useState('dashboard');
@@ -21,6 +22,7 @@ export const DashboardLayout: React.FC<DashboardLayoutProps> = ({ children }) =>
onTabChange={setActiveTab}
isExpanded={isExpanded}
onToggle={() => setIsExpanded(!isExpanded)}
menuItems={menuItems}
/>
{/* Área de Conteúdo (Children) */}

View File

@@ -20,11 +20,19 @@ import {
MoonIcon,
} from '@heroicons/react/24/outline';
export interface MenuItem {
id: string;
label: string;
href: string;
icon: any;
}
interface SidebarRailProps {
activeTab: string;
onTabChange: (tab: string) => void;
isExpanded: boolean;
onToggle: () => void;
menuItems: MenuItem[];
}
export const SidebarRail: React.FC<SidebarRailProps> = ({
@@ -32,6 +40,7 @@ export const SidebarRail: React.FC<SidebarRailProps> = ({
onTabChange,
isExpanded,
onToggle,
menuItems,
}) => {
const pathname = usePathname();
const router = useRouter();
@@ -42,14 +51,6 @@ export const SidebarRail: React.FC<SidebarRailProps> = ({
setMounted(true);
}, []);
const menuItems = [
{ id: 'dashboard', label: 'Dashboard', href: '/superadmin', icon: HomeIcon },
{ id: 'agencies', label: 'Agências', href: '/superadmin/agencies', icon: BuildingOfficeIcon },
{ id: 'templates', label: 'Templates', href: '/superadmin/signup-templates', icon: LinkIcon },
{ id: 'agency-templates', label: 'Templates Agência', href: '/superadmin/agency-templates', icon: DocumentTextIcon },
{ id: 'settings', label: 'Configurações', href: '/superadmin/settings', icon: Cog6ToothIcon },
];
const handleLogout = () => {
localStorage.removeItem('token');
localStorage.removeItem('user');
@@ -107,7 +108,7 @@ export const SidebarRail: React.FC<SidebarRailProps> = ({
label={item.label}
icon={item.icon}
href={item.href}
active={pathname === item.href || (item.href !== '/superadmin' && pathname?.startsWith(item.href))}
active={pathname === item.href || (item.href !== '/superadmin' && item.href !== '/dashboard' && pathname?.startsWith(item.href))}
onClick={() => onTabChange(item.id)}
isExpanded={isExpanded}
/>
@@ -136,7 +137,7 @@ export const SidebarRail: React.FC<SidebarRailProps> = ({
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<Menu.Items className={`absolute ${isExpanded ? 'left-0' : 'left-14'} bottom-0 mb-2 w-48 origin-bottom-left rounded-xl bg-white dark:bg-zinc-900 border border-gray-200 dark:border-zinc-800 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none overflow-hidden z-50`}>
<Menu.Items className={`absolute ${isExpanded ? 'left-0' : 'left-14'} bottom-0 mb-2 w-48 origin-bottom-left rounded-xl bg-white dark:bg-zinc-900 border border-gray-200 dark:border-zinc-800 shadow-lg focus:outline-none overflow-hidden z-50`}>
<div className="p-1">
<Menu.Item>
{({ active }) => (
@@ -148,6 +149,17 @@ export const SidebarRail: React.FC<SidebarRailProps> = ({
</button>
)}
</Menu.Item>
<Menu.Item>
{({ active }) => (
<Link
href="/superadmin/settings"
className={`${active ? 'bg-gray-100 dark:bg-zinc-800' : ''} text-gray-700 dark:text-gray-300 group flex w-full items-center rounded-lg px-3 py-2 text-xs`}
>
<Cog6ToothIcon className="mr-2 h-4 w-4" />
Configurações
</Link>
)}
</Menu.Item>
<Menu.Item>
{({ active }) => (
<button