# Implementation Plan: Navegação Unificada no Header (Remoção do Sidebar) **Branch**: `013-header-only-nav` | **Date**: 2026-04-14 | **Spec**: [spec.md](spec.md) **Depends On**: Nenhuma dependência de feature anterior — alterações puramente de layout/componente frontend. ## Summary Remoção dos sidebars de `AdminLayout` e `ClientLayout`, consolidando todos os itens de navegação no `Navbar` (header fixo) via dropdowns contextuais. O dropdown "Admin ▾" aparece apenas para `isAdmin`; o dropdown "Minha Conta ▾" aparece apenas para usuários autenticados não-admin. Nenhuma rota nova, nenhuma alteração de backend. Alterações em 3 arquivos frontend: `Navbar.tsx`, `AdminLayout.tsx` e `ClientLayout.tsx`. **Observação sobre ClientLayout + Navbar**: `ClientLayout.tsx` atualmente **não** importa nem renderiza `` (ao contrário de `AdminLayout`). O `App.tsx` também não possui um `` global. Para que o dropdown "Minha Conta ▾" seja exibido nas rotas `/area-do-cliente/*`, **`ClientLayout` deve passar a renderizar ``** como parte desta feature. ## Technical Context **Language/Version**: TypeScript 5.5 (frontend) **Primary Dependencies**: React 18, react-router-dom v6, Tailwind CSS 3.4 (já utilizados — sem novas dependências) **Storage**: Nenhuma alteração de banco de dados **Testing**: Vite build check (frontend) — sem testes unitários para componentes de layout no projeto atual **Target Platform**: SPA (React) servida via Vite proxy **Project Type**: Web SPA (React) **Performance Goals**: Sem re-renders desnecessários no toggle de dropdown; fechamento por clique externo via `useRef + useEffect` **Constraints**: Sem bibliotecas externas novas; dropdowns implementados com React + Tailwind puro; mobile: itens expandidos inline no menu hamburger (sem toggle aninhado) **Scale/Scope**: Refactor de layout puro — 3 arquivos frontend, sem impacto em rotas ou dados ## Constitution Check | Princípio | Status | Observação | |-----------|--------|------------| | I. Design-First | ✅ PASS | Tokens `bg-[#0f1011]`, `border-white/[0.06]`, `bg-white/[0.06]`, `shadow-xl`, `rounded-xl` são idênticos aos já utilizados no projeto conforme `DESIGN.md`. | | II. Separation of Concerns | ✅ PASS | Navbar contém lógica de navegação; layouts são thin wrappers sem lógica própria. | | III. Spec-Driven | ✅ PASS | spec.md aprovado → plan.md (este) → implementação. | | IV. Data Integrity | ✅ PASS | Nenhuma alteração de dados — feature puramente de UI. | | V. Security | ✅ PASS | Visibilidade dos dropdowns controlada pelos mesmos guards já existentes (`isAdmin`, `isAuthenticated`). Nenhuma rota protegida exposta. | | VI. Simplicity First | ✅ PASS | Remoção de código (sidebars) > adição. Sem abstração nova, sem nova lib. Alterações cirúrgicas em 3 arquivos. | **POST-DESIGN RE-CHECK**: ✅ Adicionar `useRef + useEffect` ao Navbar e `` ao ClientLayout são as únicas adições de infraestrutura — totalmente justificadas pelo requisito de UX. ## Architecture Overview ``` App.tsx (inalterado) ├── /area-do-cliente → ProtectedRoute → ClientLayout → │ ClientLayout: <>
│ ↑ adicionado nesta feature │ └── /admin → AdminRoute → AdminLayout → AdminLayout: <>
(sidebar removido; flex wrapper removido) Navbar.tsx (ampliado) ├── adminNavItems[] — 7 itens /admin/* ├── clientNavItems[] — 5 itens /area-do-cliente/* ├── adminDropdownOpen (useState) ├── clientDropdownOpen (useState) ├── adminDropdownRef (useRef) + useEffect para fechar no outside click ├── clientDropdownRef (useRef) + useEffect para fechar no outside click ├── Desktop: dropdown "Admin ▾" (isAdmin) / "Minha Conta ▾" (isAuthenticated não-admin) └── Mobile: itens admin ou cliente expandidos inline no menu hamburger ``` ## Frontend Changes ### 1. `Navbar.tsx` — `frontend/src/components/Navbar.tsx` #### 1.1 Imports adicionais ```tsx import { useState, useRef, useEffect } from 'react' import { Link, NavLink } from 'react-router-dom' import { useAuth } from '../contexts/AuthContext' import { ThemeToggle } from './ThemeToggle' ``` > `NavLink` substitui `Link` nos itens de dropdown para suporte a `isActive`. #### 1.2 Arrays de navegação ```tsx const adminNavItems = [ { to: '/admin/properties', label: 'Imóveis' }, { to: '/admin/clientes', label: 'Clientes' }, { to: '/admin/boletos', label: 'Boletos' }, { to: '/admin/visitas', label: 'Visitas' }, { to: '/admin/favoritos', label: 'Favoritos' }, { to: '/admin/cidades', label: 'Cidades' }, { to: '/admin/amenidades', label: 'Amenidades' }, ] const clientNavItems = [ { to: '/area-do-cliente', label: 'Painel', end: true }, { to: '/area-do-cliente/favoritos',label: 'Favoritos',end: false }, { to: '/area-do-cliente/comparar', label: 'Comparar', end: false }, { to: '/area-do-cliente/visitas', label: 'Visitas', end: false }, { to: '/area-do-cliente/boletos', label: 'Boletos', end: false }, ] ``` #### 1.3 Estado e refs no componente ```tsx const [menuOpen, setMenuOpen] = useState(false) const [adminDropdownOpen, setAdminDropdownOpen] = useState(false) const [clientDropdownOpen, setClientDropdownOpen] = useState(false) const adminDropdownRef = useRef(null) const clientDropdownRef = useRef(null) // Fechar dropdown ao clicar fora useEffect(() => { function handleClickOutside(e: MouseEvent) { if (adminDropdownRef.current && !adminDropdownRef.current.contains(e.target as Node)) { setAdminDropdownOpen(false) } if (clientDropdownRef.current && !clientDropdownRef.current.contains(e.target as Node)) { setClientDropdownOpen(false) } } document.addEventListener('mousedown', handleClickOutside) return () => document.removeEventListener('mousedown', handleClickOutside) }, []) ``` #### 1.4 Desktop — Dropdown "Admin ▾" Substituir o `
  • ` do link "Painel Admin" no `navLinks` map por: ```tsx {isAdmin && (
  • {adminDropdownOpen && (
    {adminNavItems.map(item => ( setAdminDropdownOpen(false)} className={({ isActive }) => `block px-4 py-2 text-sm transition-colors ${ isActive ? 'bg-white/[0.06] text-white font-medium' : 'text-white/60 hover:text-white hover:bg-white/[0.04]' }` } > {item.label} ))}
    )}
  • )} ``` #### 1.5 Desktop — Auth section refatorada Substituir o bloco `isAuthenticated && user` na auth section (atualmente com "Admin" button + avatar link + "Sair"): ```tsx {isAuthenticated && user ? ( <> {isAdmin ? ( /* Dropdown Admin já está no navLinks — apenas avatar + sair aqui */ <> {user.name.charAt(0).toUpperCase()} ) : ( /* Dropdown "Minha Conta ▾" para cliente */
    {clientDropdownOpen && (
    {clientNavItems.map(item => ( setClientDropdownOpen(false)} className={({ isActive }) => `block px-4 py-2 text-sm transition-colors ${ isActive ? 'bg-white/[0.06] text-white font-medium' : 'text-white/60 hover:text-white hover:bg-white/[0.04]' }` } > {item.label} ))}
    )}
    )} ) : ( Entrar )} ``` > **Nota**: O link "Admin" (botão amarelo) e o link "Painel Admin" em `navLinks` são removidos. O dropdown no botão admin já contém todos os itens. #### 1.6 Mobile — Itens admin/cliente inline No mobile menu, substituir o bloco auth existente por: ```tsx {!isLoading && ( isAuthenticated && user ? ( <> {isAdmin ? adminNavItems.map(item => (
  • setMenuOpen(false)} className={({ isActive }) => `block py-2.5 text-sm font-medium transition-colors ${ isActive ? 'text-[#5e6ad2]' : 'text-[#5e6ad2]/70 hover:text-[#5e6ad2]' }` } > {item.label}
  • )) : clientNavItems.map(item => (
  • setMenuOpen(false)} className={({ isActive }) => `block py-2.5 text-sm transition-colors ${ isActive ? 'text-white font-medium' : 'text-text-secondary hover:text-text-primary' }` } > {item.label}
  • )) }
  • ) : (
  • setMenuOpen(false)} > Entrar
  • ) )} ``` --- ### 2. `AdminLayout.tsx` — `frontend/src/layouts/AdminLayout.tsx` Remover `