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} ); diff --git a/apps/web/src/app/(agents)/components/AgentsHeader/AgentsHeader.tsx b/apps/web/src/app/(agents)/components/AgentsHeader/AgentsHeader.tsx index 62f852b53c2..243db056bb1 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,14 @@ export function AgentsHeader() { trpc.user.myOrganizations.queryOptions(), ); + const { data: activePlan } = useQuery(trpc.billing.activePlan.queryOptions()); + + const isPro = isPaidPlan(activePlan?.plan); + const planLabel = + isPro && activePlan?.plan + ? activePlan.plan.charAt(0).toUpperCase() + activePlan.plan.slice(1) + : null; + const user = session?.user; const activeOrganizationId = session?.session?.activeOrganizationId; const activeOrganization = organizations?.find( @@ -191,7 +201,14 @@ export function AgentsHeader() { Account menu
-

{user?.name}

+
+

{user?.name}

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

{user?.email}

@@ -251,7 +268,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..664f17daf29 100644 --- a/packages/trpc/src/router/billing/billing.ts +++ b/packages/trpc/src/router/billing/billing.ts @@ -1,8 +1,9 @@ import { stripeClient } from "@superset/auth/stripe"; import { db } from "@superset/db/client"; import { members, subscriptions } from "@superset/db/schema"; +import { ACTIVE_SUBSCRIPTION_STATUSES } 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 +66,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) { + 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) {