085c0b9b7f
## Summary - Adds a **Billing** tab on the admin-panel workspace detail page that surfaces Stripe customer + active subscription details (status, plan, interval, current period, trial, cancellation, line items, credit balance). Tab is gated on `IS_BILLING_ENABLED` both in the backend service and in the frontend tab list — completely hidden on instances where billing is disabled. - Renders a **workspace avatar next to the name** in the admin Top Workspaces list by plumbing the workspace `logo` field through the admin DTO, statistics SQL query, and generated admin GraphQL types. - **Read-only** by design: no Stripe API calls, no mutations — data comes from the existing \`BillingCustomerEntity\` / \`BillingSubscriptionEntity\` / \`BillingPriceEntity\` tables via \`BillingSubscriptionService.getCurrentBillingSubscription\`. ### What the tab shows - **Customer** container — Stripe customer ID (with link to the Stripe dashboard, monospaced), credit balance (formatted, from \`creditBalanceMicro\`). - **Subscription** container — status tag (color-coded), plan tag, billing interval, current period range, trial range (if trialing), \`cancelAtPeriodEnd\` / \`cancelAt\` / \`canceledAt\` (only when set), Stripe subscription ID (external link). - **Line items** — one card per subscription item with product name, product key tag, seats (if quantity), credits per period (for metered), unit price (formatted with currency). ### Design choices - Styling matches the user-facing billing page (\`SubscriptionInfoContainer\` + \`Tag\` + \`H2Title\` + \`Section\`) — no new UI primitives. - Currency is rendered inline with amounts via \`Intl.NumberFormat\` (e.g. \`\$19.00\`) instead of as a separate row. - Uses the generated admin GraphQL types (\`WorkspaceBillingAdminPanelQuery\`, \`SubscriptionStatus\`, \`SubscriptionInterval\`) — no hand-typed response shapes. ## Test plan - [x] \`npx nx typecheck twenty-server\` — passes - [x] \`npx nx typecheck twenty-front\` — passes - [x] oxlint + prettier on all touched files — clean - [x] \`graphql:generate --configuration=admin\` — regenerated; new \`workspaceBillingAdminPanel\` query + \`logo\` field on \`AdminPanelTopWorkspace\` appear in \`generated-admin/graphql.ts\` - [x] Backend GraphQL schema introspection shows \`workspaceBillingAdminPanel\` query on \`/admin-panel\` - [x] Direct GraphQL call with seeded \`BillingCustomer\` + \`BillingSubscription\` + \`BillingPrice\` rows returns the expected shape (\`status: "Trialing"\`, plan \`PRO\`, items with quantity/unitAmount/includedCredits, trial period dates) - [x] With \`IS_BILLING_ENABLED=false\` (default) the Billing tab is hidden — verified in the admin panel UI - [x] Top Workspaces list renders workspace avatars next to names — verified in the admin panel UI - [ ] Smoke test the Billing tab render in a real instance that has \`IS_BILLING_ENABLED=true\` + live Stripe data (skipped locally due to dev-env auth friction after toggling billing/multi-workspace; recommend a reviewer check) 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>