Files
B2BCall-dialer/apps/frontend/src/app/platform/clientes/assinaturas/page.tsx
Matheus b1a409da09 feat(frontend): Platform > Clientes > Assinaturas/Quotas + achado real no dashboard
GET /billing/subscriptions e POST /billing/plan-versions já existiam desde a fase
Billing (PHASE 22) sem tela nenhuma — Assinaturas agora deixa escolher um tenant, ver
o histórico de versões de preço assinadas, e criar uma nova assinatura reaproveitando
uma versão existente ou versionando um preço novo na mesma ação (preço nunca é
sobrescrito, sempre uma linha nova).

GET /platform/quotas (novo) agrega uso vs. limite do plano em todos os tenants de
uma vez (ramais/agentes/troncos/filas/campanhas/chamadas-mês/armazenamento), com a
tela destacando quem está em 80%+ (amarelo) ou 100%+ (vermelho) do limite.

Achado real ao revisar o dashboard "Visão Geral" antes de escrever a agregação
cross-tenant de Quotas: aiUsageThisMonth/recordingStorageBytes sempre devolviam
zero/vazio, porque a query rodava direto no Prisma sem nenhum app.current_tenant_id
setado — ai_usage_records/recordings têm FORCE RLS, então a policy nega a leitura
silenciosamente (0 linhas, sem erro), não importa quanto uso real existisse. Mesma
classe de bug já corrigida 2x antes nesta sessão; corrigido com o mesmo padrão (loop
withTenantContext por tenant). Confirmado inserindo um AIUsageRecord de teste no
Postgres, vendo o número aparecer, e removendo o teste depois.

Testado ponta a ponta: fluxo completo de criar assinatura via UI pro tenant Beta
Corp (nova versão de preço + assinatura, confirmado na tela e no banco), Quotas
mostrando os números reais dos dois tenants de teste.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BFaBaBSQGhyXGEgtTYZGV8
2026-08-30 08:32:53 -03:00

32 lines
1.2 KiB
TypeScript

import { requireSession } from "@/lib/session";
import { apiFetch } from "@/lib/api";
import type { Tenant } from "@/lib/platform-types";
import type { PlanVersion, TenantSubscription } from "@/lib/billing-types";
import { AssinaturasView } from "./assinaturas-view";
export default async function AssinaturasPage({ searchParams }: { searchParams: Promise<{ tenantId?: string }> }) {
const { tenantId } = await searchParams;
const session = await requireSession();
const tenants = await apiFetch<Tenant[]>("/tenants", session.accessToken);
const selectedTenant = tenantId ? (tenants.find((t) => t.id === tenantId) ?? null) : null;
const [subscriptions, planVersions] = tenantId
? await Promise.all([
apiFetch<TenantSubscription[]>(`/billing/subscriptions/by-tenant/${tenantId}`, session.accessToken),
selectedTenant
? apiFetch<PlanVersion[]>(`/billing/plan-versions/by-plan/${selectedTenant.planId}`, session.accessToken)
: Promise.resolve([]),
])
: [[], []];
return (
<AssinaturasView
tenants={tenants}
selectedTenant={selectedTenant}
subscriptions={subscriptions}
planVersions={planVersions}
/>
);
}