Frontend completo (Etapas 11-18) e extensões de backend (Etapas 13 e 18)
Adiciona o frontend Next.js em frontend/ (setup, design system, autenticação/tema, layout/navegação, área master, área tenant completa: clientes, produtos, vendas, recebimentos, whatsapp, e-mail, alertas, equipe e perfil). Estende o backend de forma aditiva e autorizada: cookie httpOnly para refresh token (Etapa 13) e CRUD de equipe + edição de perfil do tenant (Etapa 18, módulo src/modules/equipe/ novo e PUT /tenant/perfil). Detalhes completos em Etapas/V2/*. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
'use client';
|
||||
|
||||
import * as React from 'react';
|
||||
import Image from 'next/image';
|
||||
|
||||
import { AppShell } from '@/components/layout/app-shell';
|
||||
import { UserFooter } from '@/components/layout/user-footer';
|
||||
import { useAuth } from '@/lib/auth/auth-context';
|
||||
import { TenantThemeProvider, useTenantTheme } from '@/lib/theme/theme-provider';
|
||||
import { TENANT_NAV_ITEMS, TENANT_BOTTOM_HREFS, filterByRole } from '@/lib/layout/nav-items';
|
||||
import type { UserRole } from '@/lib/auth/jwt';
|
||||
|
||||
const ROLE_LABELS: Record<UserRole, string> = {
|
||||
tenant_admin: 'Administrador',
|
||||
vendedor: 'Vendedor',
|
||||
financeiro: 'Financeiro',
|
||||
visualizador: 'Visualizador',
|
||||
};
|
||||
|
||||
/** Logo/nome resolvidos do tema do tenant (Etapa 13); cai para a marca padrão sem tema. */
|
||||
function TenantBrand({ collapsed = false }: { collapsed?: boolean }) {
|
||||
const { theme } = useTenantTheme();
|
||||
const name = theme?.nome ?? 'SeConectai Vendas';
|
||||
|
||||
return (
|
||||
<>
|
||||
{theme?.logoUrl ? (
|
||||
<Image
|
||||
src={theme.logoUrl}
|
||||
alt={name}
|
||||
width={36}
|
||||
height={36}
|
||||
className="size-9 shrink-0 rounded-lg object-contain"
|
||||
/>
|
||||
) : (
|
||||
<span className="flex size-9 shrink-0 items-center justify-center rounded-lg bg-primary text-sm font-bold text-primary-foreground">
|
||||
{name.charAt(0).toUpperCase()}
|
||||
</span>
|
||||
)}
|
||||
{!collapsed && <span className="truncate text-sm font-semibold text-neutral-950">{name}</span>}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function TenantShellInner({
|
||||
initialCollapsed,
|
||||
children,
|
||||
}: {
|
||||
initialCollapsed: boolean;
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
const { user, logout } = useAuth();
|
||||
const handleLogout = React.useCallback(() => {
|
||||
void logout();
|
||||
}, [logout]);
|
||||
|
||||
const items = React.useMemo(() => filterByRole(TENANT_NAV_ITEMS, user?.role), [user?.role]);
|
||||
const roleLabel = user?.role ? ROLE_LABELS[user.role] : 'Usuário';
|
||||
|
||||
return (
|
||||
<AppShell
|
||||
brand={<TenantBrand />}
|
||||
brandCollapsed={<TenantBrand collapsed />}
|
||||
items={items}
|
||||
bottomHrefs={TENANT_BOTTOM_HREFS}
|
||||
footer={<UserFooter label={roleLabel} onLogout={handleLogout} />}
|
||||
footerCollapsed={<UserFooter label={roleLabel} compact onLogout={handleLogout} />}
|
||||
initialCollapsed={initialCollapsed}
|
||||
>
|
||||
{children}
|
||||
</AppShell>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shell da área `/app`. Resolve o tema do tenant a partir do `tenant_id` do
|
||||
* usuário autenticado (Etapa 13) e filtra os itens de menu por `role` — ex:
|
||||
* "Equipe" só aparece para `tenant_admin` (Etapa 14, critério de aceite).
|
||||
*/
|
||||
export function TenantShell({
|
||||
initialCollapsed,
|
||||
children,
|
||||
}: {
|
||||
initialCollapsed: boolean;
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
const { user } = useAuth();
|
||||
|
||||
return (
|
||||
<TenantThemeProvider tenantId={user?.tenant_id ?? null}>
|
||||
<TenantShellInner initialCollapsed={initialCollapsed}>{children}</TenantShellInner>
|
||||
</TenantThemeProvider>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user