From 374e2055591356132ed154754972f4f0cdf7cc17 Mon Sep 17 00:00:00 2001 From: Kiet Ho Date: Wed, 29 Apr 2026 16:10:51 -0700 Subject: [PATCH 1/3] feat(web): show Pro badge in account dropdown Adds a `billing.activePlan` tRPC query and renders a Pro/Enterprise badge next to the user's name in the AgentsHeader dropdown and mobile drawer when the active org has a paid subscription. --- .../components/AgentsHeader/AgentsHeader.tsx | 27 +++++++++++++++++-- packages/trpc/src/router/billing/billing.ts | 25 ++++++++++++++++- 2 files changed, 49 insertions(+), 3 deletions(-) diff --git a/apps/web/src/app/(agents)/components/AgentsHeader/AgentsHeader.tsx b/apps/web/src/app/(agents)/components/AgentsHeader/AgentsHeader.tsx index 62f852b53c2..4075046a441 100644 --- a/apps/web/src/app/(agents)/components/AgentsHeader/AgentsHeader.tsx +++ b/apps/web/src/app/(agents)/components/AgentsHeader/AgentsHeader.tsx @@ -1,7 +1,9 @@ "use client"; import { authClient } from "@superset/auth/client"; +import { isPaidPlan } from "@superset/shared/billing"; import { Avatar, AvatarFallback, AvatarImage } from "@superset/ui/avatar"; +import { Badge } from "@superset/ui/badge"; import { Drawer, DrawerContent, DrawerTitle } from "@superset/ui/drawer"; import { DropdownMenu, @@ -44,6 +46,13 @@ export function AgentsHeader() { trpc.user.myOrganizations.queryOptions(), ); + const { data: activePlan } = useQuery(trpc.billing.activePlan.queryOptions()); + + const isPro = isPaidPlan(activePlan?.plan); + const planLabel = activePlan?.plan + ? activePlan.plan.charAt(0).toUpperCase() + activePlan.plan.slice(1) + : "Pro"; + const user = session?.user; const activeOrganizationId = session?.session?.activeOrganizationId; const activeOrganization = organizations?.find( @@ -191,7 +200,14 @@ export function AgentsHeader() { Account menu
-

{user?.name}

+
+

{user?.name}

+ {isPro && ( + + {planLabel} + + )} +

{user?.email}

@@ -251,7 +267,14 @@ export function AgentsHeader() {
-

{user?.name}

+
+

{user?.name}

+ {isPro && ( + + {planLabel} + + )} +

{user?.email}

diff --git a/packages/trpc/src/router/billing/billing.ts b/packages/trpc/src/router/billing/billing.ts index 3616f241006..48abf29bea3 100644 --- a/packages/trpc/src/router/billing/billing.ts +++ b/packages/trpc/src/router/billing/billing.ts @@ -1,8 +1,12 @@ import { stripeClient } from "@superset/auth/stripe"; import { db } from "@superset/db/client"; import { members, subscriptions } from "@superset/db/schema"; +import { + ACTIVE_SUBSCRIPTION_STATUSES, + isActiveSubscriptionStatus, +} from "@superset/shared/billing"; import { TRPCError, type TRPCRouterRecord } from "@trpc/server"; -import { and, eq } from "drizzle-orm"; +import { and, desc, eq, inArray } from "drizzle-orm"; import type Stripe from "stripe"; import { z } from "zod"; import { env } from "../../env"; @@ -65,6 +69,25 @@ async function requireOwnerWithCustomer(ctx: { } export const billingRouter = { + activePlan: protectedProcedure.query(async ({ ctx }) => { + const activeOrgId = ctx.activeOrganizationId; + if (!activeOrgId) return { plan: "free" as const, status: null }; + + const subscription = await db.query.subscriptions.findFirst({ + where: and( + eq(subscriptions.referenceId, activeOrgId), + inArray(subscriptions.status, ACTIVE_SUBSCRIPTION_STATUSES), + ), + orderBy: desc(subscriptions.createdAt), + }); + + if (!subscription || !isActiveSubscriptionStatus(subscription.status)) { + return { plan: "free" as const, status: null }; + } + + return { plan: subscription.plan, status: subscription.status }; + }), + invoices: protectedProcedure.query(async ({ ctx }) => { const activeOrgId = ctx.activeOrganizationId; if (!activeOrgId) { From 4d931ba54f726099667691409f212be151d9df6d Mon Sep 17 00:00:00 2001 From: Kiet Ho Date: Wed, 29 Apr 2026 16:25:10 -0700 Subject: [PATCH 2/3] feat(desktop): show Pro badge in OrganizationDropdown Renders a Pro/Enterprise badge next to the org name in the desktop OrganizationDropdown trigger (topbar and expanded sidebar variants), using the existing useCurrentPlan hook. --- .../OrganizationDropdown.tsx | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/TopBar/components/OrganizationDropdown/OrganizationDropdown.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/TopBar/components/OrganizationDropdown/OrganizationDropdown.tsx index d918461f319..df88826a400 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/TopBar/components/OrganizationDropdown/OrganizationDropdown.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/TopBar/components/OrganizationDropdown/OrganizationDropdown.tsx @@ -1,5 +1,6 @@ import { COMPANY } from "@superset/shared/constants"; import { Avatar } from "@superset/ui/atoms/Avatar"; +import { Badge } from "@superset/ui/badge"; import { DropdownMenu, DropdownMenuContent, @@ -27,6 +28,7 @@ import { } from "react-icons/hi2"; import { IoBugOutline } from "react-icons/io5"; import { LuKeyboard } from "react-icons/lu"; +import { useCurrentPlan } from "renderer/hooks/useCurrentPlan"; import { useHotkeyDisplay } from "renderer/hotkeys"; import { authClient } from "renderer/lib/auth-client"; import { electronTrpc } from "renderer/lib/electron-trpc"; @@ -69,6 +71,18 @@ export function OrganizationDropdown({ const userName = session?.user?.name; const displayName = activeOrganization?.name ?? userName ?? "Organization"; + const currentPlan = useCurrentPlan(); + const isPaid = currentPlan !== "free"; + const planLabel = currentPlan.charAt(0).toUpperCase() + currentPlan.slice(1); + const planBadge = isPaid ? ( + + {planLabel} + + ) : null; + const triggerButton = variant === "collapsed" ? ( ) : ( @@ -113,6 +128,7 @@ export function OrganizationDropdown({ {displayName} + {planBadge} ); From 3c17e8e3962d1904ff389da547866bb5b1f4c6f8 Mon Sep 17 00:00:00 2001 From: Kiet Ho Date: Wed, 29 Apr 2026 16:50:15 -0700 Subject: [PATCH 3/3] fix: address PR review comments - billing.activePlan: drop redundant status check; the WHERE clause already restricts to ACTIVE_SUBSCRIPTION_STATUSES. - AgentsHeader: derive planLabel only when on a paid tier so a stale "Pro" string can't surface if loading-state logic changes. --- .../app/(agents)/components/AgentsHeader/AgentsHeader.tsx | 7 ++++--- packages/trpc/src/router/billing/billing.ts | 7 ++----- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/apps/web/src/app/(agents)/components/AgentsHeader/AgentsHeader.tsx b/apps/web/src/app/(agents)/components/AgentsHeader/AgentsHeader.tsx index 4075046a441..243db056bb1 100644 --- a/apps/web/src/app/(agents)/components/AgentsHeader/AgentsHeader.tsx +++ b/apps/web/src/app/(agents)/components/AgentsHeader/AgentsHeader.tsx @@ -49,9 +49,10 @@ export function AgentsHeader() { const { data: activePlan } = useQuery(trpc.billing.activePlan.queryOptions()); const isPro = isPaidPlan(activePlan?.plan); - const planLabel = activePlan?.plan - ? activePlan.plan.charAt(0).toUpperCase() + activePlan.plan.slice(1) - : "Pro"; + const planLabel = + isPro && activePlan?.plan + ? activePlan.plan.charAt(0).toUpperCase() + activePlan.plan.slice(1) + : null; const user = session?.user; const activeOrganizationId = session?.session?.activeOrganizationId; diff --git a/packages/trpc/src/router/billing/billing.ts b/packages/trpc/src/router/billing/billing.ts index 48abf29bea3..664f17daf29 100644 --- a/packages/trpc/src/router/billing/billing.ts +++ b/packages/trpc/src/router/billing/billing.ts @@ -1,10 +1,7 @@ import { stripeClient } from "@superset/auth/stripe"; import { db } from "@superset/db/client"; import { members, subscriptions } from "@superset/db/schema"; -import { - ACTIVE_SUBSCRIPTION_STATUSES, - isActiveSubscriptionStatus, -} from "@superset/shared/billing"; +import { ACTIVE_SUBSCRIPTION_STATUSES } from "@superset/shared/billing"; import { TRPCError, type TRPCRouterRecord } from "@trpc/server"; import { and, desc, eq, inArray } from "drizzle-orm"; import type Stripe from "stripe"; @@ -81,7 +78,7 @@ export const billingRouter = { orderBy: desc(subscriptions.createdAt), }); - if (!subscription || !isActiveSubscriptionStatus(subscription.status)) { + if (!subscription) { return { plan: "free" as const, status: null }; }