v1.4: Segurança multi-tenant, file serving via API e UX humanizada
- 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
This commit is contained in:
@@ -1,125 +1,34 @@
|
||||
"use client";
|
||||
import { Metadata } from 'next';
|
||||
import { getAgencyLogo, getAgencyColors } from '@/lib/server-api';
|
||||
import { AgencyLayoutClient } from './AgencyLayoutClient';
|
||||
|
||||
import { DashboardLayout } from '@/components/layout/DashboardLayout';
|
||||
import {
|
||||
HomeIcon,
|
||||
RocketLaunchIcon,
|
||||
ChartBarIcon,
|
||||
BriefcaseIcon,
|
||||
LifebuoyIcon,
|
||||
CreditCardIcon,
|
||||
DocumentTextIcon,
|
||||
FolderIcon,
|
||||
ShareIcon,
|
||||
} from '@heroicons/react/24/outline';
|
||||
// Forçar renderização dinâmica (não estática) para este layout
|
||||
// Necessário porque usamos headers() para pegar o subdomínio
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
const AGENCY_MENU_ITEMS = [
|
||||
{ id: 'dashboard', label: 'Visão Geral', href: '/dashboard', icon: HomeIcon },
|
||||
{
|
||||
id: 'crm',
|
||||
label: 'CRM',
|
||||
href: '/crm',
|
||||
icon: RocketLaunchIcon,
|
||||
subItems: [
|
||||
{ label: 'Dashboard', href: '/crm' },
|
||||
{ label: 'Clientes', href: '/crm/clientes' },
|
||||
{ label: 'Funis', href: '/crm/funis' },
|
||||
{ label: 'Negociações', href: '/crm/negociacoes' },
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'erp',
|
||||
label: 'ERP',
|
||||
href: '/erp',
|
||||
icon: ChartBarIcon,
|
||||
subItems: [
|
||||
{ label: 'Dashboard', href: '/erp' },
|
||||
{ label: 'Fluxo de Caixa', href: '/erp/fluxo-caixa' },
|
||||
{ label: 'Contas a Pagar', href: '/erp/contas-pagar' },
|
||||
{ label: 'Contas a Receber', href: '/erp/contas-receber' },
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'projetos',
|
||||
label: 'Projetos',
|
||||
href: '/projetos',
|
||||
icon: BriefcaseIcon,
|
||||
subItems: [
|
||||
{ label: 'Dashboard', href: '/projetos' },
|
||||
{ label: 'Meus Projetos', href: '/projetos/lista' },
|
||||
{ label: 'Tarefas', href: '/projetos/tarefas' },
|
||||
{ label: 'Cronograma', href: '/projetos/cronograma' },
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'helpdesk',
|
||||
label: 'Helpdesk',
|
||||
href: '/helpdesk',
|
||||
icon: LifebuoyIcon,
|
||||
subItems: [
|
||||
{ label: 'Dashboard', href: '/helpdesk' },
|
||||
{ label: 'Chamados', href: '/helpdesk/chamados' },
|
||||
{ label: 'Base de Conhecimento', href: '/helpdesk/kb' },
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'pagamentos',
|
||||
label: 'Pagamentos',
|
||||
href: '/pagamentos',
|
||||
icon: CreditCardIcon,
|
||||
subItems: [
|
||||
{ label: 'Dashboard', href: '/pagamentos' },
|
||||
{ label: 'Cobranças', href: '/pagamentos/cobrancas' },
|
||||
{ label: 'Assinaturas', href: '/pagamentos/assinaturas' },
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'contratos',
|
||||
label: 'Contratos',
|
||||
href: '/contratos',
|
||||
icon: DocumentTextIcon,
|
||||
subItems: [
|
||||
{ label: 'Dashboard', href: '/contratos' },
|
||||
{ label: 'Ativos', href: '/contratos/ativos' },
|
||||
{ label: 'Modelos', href: '/contratos/modelos' },
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'documentos',
|
||||
label: 'Documentos',
|
||||
href: '/documentos',
|
||||
icon: FolderIcon,
|
||||
subItems: [
|
||||
{ label: 'Meus Arquivos', href: '/documentos' },
|
||||
{ label: 'Compartilhados', href: '/documentos/compartilhados' },
|
||||
{ label: 'Lixeira', href: '/documentos/lixeira' },
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'social',
|
||||
label: 'Redes Sociais',
|
||||
href: '/social',
|
||||
icon: ShareIcon,
|
||||
subItems: [
|
||||
{ label: 'Dashboard', href: '/social' },
|
||||
{ label: 'Agendamento', href: '/social/agendamento' },
|
||||
{ label: 'Relatórios', href: '/social/relatorios' },
|
||||
]
|
||||
},
|
||||
];
|
||||
/**
|
||||
* generateMetadata - Executado no servidor antes do render
|
||||
* Define o favicon dinamicamente baseado no subdomínio da agência
|
||||
*/
|
||||
export async function generateMetadata(): Promise<Metadata> {
|
||||
const logoUrl = await getAgencyLogo();
|
||||
|
||||
import AuthGuard from '@/components/auth/AuthGuard';
|
||||
return {
|
||||
icons: {
|
||||
icon: logoUrl || '/favicon.ico',
|
||||
shortcut: logoUrl || '/favicon.ico',
|
||||
apple: logoUrl || '/favicon.ico',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export default function AgencyLayout({
|
||||
export default async function AgencyLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<AuthGuard>
|
||||
<DashboardLayout menuItems={AGENCY_MENU_ITEMS}>
|
||||
{children}
|
||||
</DashboardLayout>
|
||||
</AuthGuard>
|
||||
);
|
||||
// Buscar cores da agência no servidor
|
||||
const colors = await getAgencyColors();
|
||||
|
||||
return <AgencyLayoutClient colors={colors}>{children}</AgencyLayoutClient>;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user