- Validação cross-tenant no login e rotas protegidas
- File serving via /api/files/{bucket}/{path} (eliminação DNS)
- Mensagens de erro humanizadas inline (sem pop-ups)
- Middleware tenant detection via headers customizados
- Upload de logos retorna URLs via API
- README atualizado com changelog v1.4 completo
111 lines
5.4 KiB
TypeScript
111 lines
5.4 KiB
TypeScript
'use client';
|
|
|
|
import React, { useState } from 'react';
|
|
import { usePathname } from 'next/navigation';
|
|
import Link from 'next/link';
|
|
import { MagnifyingGlassIcon, ChevronRightIcon, HomeIcon, BellIcon, Cog6ToothIcon } from '@heroicons/react/24/outline';
|
|
import CommandPalette from '@/components/ui/CommandPalette';
|
|
|
|
export const TopBar: React.FC = () => {
|
|
const pathname = usePathname();
|
|
const [isCommandPaletteOpen, setIsCommandPaletteOpen] = useState(false);
|
|
|
|
const generateBreadcrumbs = () => {
|
|
const paths = pathname?.split('/').filter(Boolean) || [];
|
|
const breadcrumbs: Array<{ name: string; href: string; icon?: React.ComponentType<{ className?: string }> }> = [
|
|
{ name: 'Home', href: '/dashboard', icon: HomeIcon }
|
|
];
|
|
let currentPath = '';
|
|
paths.forEach((path, index) => {
|
|
currentPath += `/${path}`;
|
|
|
|
// Mapeamento de nomes amigáveis
|
|
const nameMap: Record<string, string> = {
|
|
'dashboard': 'Dashboard',
|
|
'clientes': 'Clientes',
|
|
'projetos': 'Projetos',
|
|
'financeiro': 'Financeiro',
|
|
'configuracoes': 'Configurações',
|
|
'novo': 'Novo',
|
|
};
|
|
|
|
if (path !== 'dashboard') { // Evita duplicar Home/Dashboard se a rota for /dashboard
|
|
breadcrumbs.push({
|
|
name: nameMap[path] || path.charAt(0).toUpperCase() + path.slice(1),
|
|
href: currentPath,
|
|
});
|
|
}
|
|
});
|
|
|
|
return breadcrumbs;
|
|
};
|
|
|
|
const breadcrumbs = generateBreadcrumbs();
|
|
|
|
return (
|
|
<>
|
|
<div className="bg-white dark:bg-zinc-900 border-b border-gray-200 dark:border-zinc-800 px-6 py-3 flex items-center justify-between transition-colors">
|
|
{/* Breadcrumbs */}
|
|
<nav className="flex items-center gap-2 text-xs">
|
|
{breadcrumbs.map((crumb, index) => {
|
|
const Icon = crumb.icon;
|
|
const isLast = index === breadcrumbs.length - 1;
|
|
|
|
return (
|
|
<div key={crumb.href} className="flex items-center gap-2">
|
|
{Icon ? (
|
|
<Link
|
|
href={crumb.href}
|
|
className="flex items-center gap-1.5 text-gray-500 dark:text-zinc-400 hover:text-gray-900 dark:hover:text-zinc-200 transition-colors"
|
|
>
|
|
<Icon className="w-3.5 h-3.5" />
|
|
<span>{crumb.name}</span>
|
|
</Link>
|
|
) : (
|
|
<Link
|
|
href={crumb.href}
|
|
className={`${isLast ? 'text-gray-900 dark:text-white font-medium' : 'text-gray-500 dark:text-zinc-400 hover:text-gray-900 dark:hover:text-zinc-200'} transition-colors`}
|
|
>
|
|
{crumb.name}
|
|
</Link>
|
|
)}
|
|
|
|
{!isLast && <ChevronRightIcon className="w-3 h-3 text-gray-400 dark:text-zinc-600" />}
|
|
</div>
|
|
);
|
|
})}
|
|
</nav>
|
|
|
|
{/* Search Bar Trigger */}
|
|
<div className="flex items-center gap-4">
|
|
<button
|
|
onClick={() => setIsCommandPaletteOpen(true)}
|
|
className="flex items-center gap-2 px-3 py-1.5 text-sm text-gray-500 dark:text-zinc-400 bg-gray-100 dark:bg-zinc-800 rounded-lg hover:bg-gray-200 dark:hover:bg-zinc-700 transition-colors"
|
|
>
|
|
<MagnifyingGlassIcon className="w-4 h-4" />
|
|
<span className="hidden sm:inline">Buscar...</span>
|
|
<kbd className="hidden sm:inline-flex items-center gap-1 px-1.5 py-0.5 text-[10px] font-medium text-gray-400 bg-white dark:bg-zinc-900 rounded border border-gray-200 dark:border-zinc-700">
|
|
Ctrl K
|
|
</kbd>
|
|
</button>
|
|
<div className="flex items-center gap-2 border-l border-gray-200 dark:border-zinc-800 pl-4">
|
|
<button className="p-2 text-gray-500 dark:text-zinc-400 hover:bg-gray-100 dark:hover:bg-zinc-800 rounded-lg transition-colors relative">
|
|
<BellIcon className="w-5 h-5" />
|
|
<span className="absolute top-2 right-2 w-2 h-2 bg-red-500 rounded-full border-2 border-white dark:border-zinc-900"></span>
|
|
</button>
|
|
<Link
|
|
href="/configuracoes"
|
|
className="p-2 text-gray-500 dark:text-zinc-400 hover:bg-gray-100 dark:hover:bg-zinc-800 rounded-lg transition-colors"
|
|
>
|
|
<Cog6ToothIcon className="w-5 h-5" />
|
|
</Link>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Command Palette */}
|
|
<CommandPalette isOpen={isCommandPaletteOpen} setIsOpen={setIsCommandPaletteOpen} />
|
|
</>
|
|
);
|
|
};
|