feat(frontend): app do tenant, dashboard ao vivo, fluxo de login multi-tenant

Corrige um gap real: o login sempre mandava pra /platform mesmo pra
usuarios sem role de plataforma, sem nunca chamar /auth/tenants ou
select-tenant. Agora POST /api/post-login decide o destino server-side
(plataforma / tenant unico / seletor com >1 tenant) antes de redirecionar,
trocando o access token quando necessario sem nunca expor token ao client.

Adiciona GET /reports/dashboard (secao 162) e a tela /app correspondente
— chamadas/agentes/TME/TMA/rates ao vivo, "consumo do plano" e "valor
estimado" seguindo a mesma disciplina de honestidade (null > numero
inventado) do dashboard de plataforma.

Shell (sidebar/topbar/drawer mobile) extraido pra components/shell,
compartilhado entre os menus Platform e Tenant (secao 168-169) via
wrappers client-only por area — corrige de quebra um erro real de
serializacao RSC (passar NavSection[] com icones como prop de Server
Component pra Client Component quebra: "Functions cannot be passed
directly to Client Components").

Testado ponta a ponta com um tenant semeado (Acme Call Center): login →
/app direto, platform admin barrado de /app e vice-versa, dashboard com
numeros reais (zeros honestos), drawer mobile, dark mode.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EWHKmcVJtstQFErbZ1AanY
This commit is contained in:
2026-08-29 09:30:16 -03:00
parent aad16d136e
commit 565110eac3
28 changed files with 662 additions and 67 deletions

View File

@@ -55,7 +55,22 @@ export class AuthController {
select: { id: true, email: true, name: true },
});
if (!dbUser) throw new NotFoundException();
return { ...dbUser, isPlatformUser: await isPlatformUser(user.sub) };
// Nome do tenant ativo pra topbar (frontend, PHASE 23) — `tenants` não
// tem RLS (tabela raiz, ver docs/TENANT_ISOLATION.md), leitura direta
// sem `withTenantContext` é segura aqui.
const tenant = user.tenantId
? await prisma.tenant.findUnique({
where: { id: user.tenantId },
select: { id: true, code: true, tradeName: true, legalName: true },
})
: null;
return {
...dbUser,
isPlatformUser: await isPlatformUser(user.sub),
tenant: tenant ? { id: tenant.id, code: tenant.code, name: tenant.tradeName ?? tenant.legalName } : null,
};
}
@UseGuards(JwtAuthGuard)