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

@@ -0,0 +1,29 @@
import Image from "next/image";
import { requireSession } from "@/lib/session";
import { apiFetch } from "@/lib/api";
import { TenantPicker } from "./picker";
interface TenantOption {
tenantId: string;
code: string;
name: string;
status: string;
}
export default async function SelectTenantPage() {
const session = await requireSession();
const tenants = await apiFetch<TenantOption[]>("/auth/tenants", session.accessToken);
return (
<div className="flex min-h-dvh flex-col items-center justify-center bg-background px-6 py-12">
<div className="w-full max-w-sm">
<Image src="/branding/b2blogo.png" alt="B2BCall" width={140} height={34} priority className="mx-auto mb-8" />
<h1 className="text-center text-lg font-semibold text-foreground">Qual conta você quer acessar?</h1>
<p className="mt-1 text-center text-sm text-muted-foreground">Sua conta tem acesso a mais de um tenant.</p>
<div className="mt-6">
<TenantPicker tenants={tenants} />
</div>
</div>
</div>
);
}