feat(admin-panel): add read-only Billing tab and workspace logos (#20012)
## 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>
This commit is contained in:
+1
@@ -7,6 +7,7 @@ export const ADMIN_PANEL_TOP_WORKSPACES = gql`
|
||||
name
|
||||
totalUsers
|
||||
subdomain
|
||||
logo
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
export const GET_WORKSPACE_BILLING_ADMIN_PANEL = gql`
|
||||
query WorkspaceBillingAdminPanel($workspaceId: UUID!) {
|
||||
workspaceBillingAdminPanel(workspaceId: $workspaceId) {
|
||||
stripeCustomerId
|
||||
creditBalance
|
||||
subscription {
|
||||
stripeSubscriptionId
|
||||
status
|
||||
interval
|
||||
currency
|
||||
planKey
|
||||
currentPeriodStart
|
||||
currentPeriodEnd
|
||||
trialStart
|
||||
trialEnd
|
||||
cancelAt
|
||||
canceledAt
|
||||
cancelAtPeriodEnd
|
||||
items {
|
||||
productName
|
||||
productKey
|
||||
stripePriceId
|
||||
quantity
|
||||
unitAmount
|
||||
includedCredits
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
Reference in New Issue
Block a user