Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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";
Expand Down Expand Up @@ -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 ? (
<Badge
variant="default"
className="px-1 py-0 text-[9px] leading-none uppercase tracking-wide h-3.5"
>
{planLabel}
</Badge>
) : null;

const triggerButton =
variant === "collapsed" ? (
<button
Expand Down Expand Up @@ -96,6 +110,7 @@ export function OrganizationDropdown({
className="rounded size-4 shrink-0"
/>
<span className="truncate">{displayName}</span>
{planBadge}
<HiChevronUpDown className="h-3.5 w-3.5 text-muted-foreground shrink-0" />
</button>
) : (
Expand All @@ -113,6 +128,7 @@ export function OrganizationDropdown({
<span className="text-xs font-medium truncate max-w-32">
{displayName}
</span>
{planBadge}
<HiChevronUpDown className="h-3.5 w-3.5 text-muted-foreground shrink-0" />
</button>
);
Expand Down
28 changes: 26 additions & 2 deletions apps/web/src/app/(agents)/components/AgentsHeader/AgentsHeader.tsx
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -191,7 +201,14 @@ export function AgentsHeader() {
<DrawerTitle className="sr-only">Account menu</DrawerTitle>
<div className="flex flex-col gap-1 p-3 pb-[max(1rem,env(safe-area-inset-bottom))]">
<div className="flex flex-col space-y-1 px-2 py-1.5">
<p className="text-sm font-medium">{user?.name}</p>
<div className="flex items-center gap-2">
<p className="text-sm font-medium">{user?.name}</p>
{isPro && (
<Badge variant="default" className="px-1.5 py-0 text-[10px]">
{planLabel}
</Badge>
)}
</div>
<p className="text-xs text-muted-foreground">{user?.email}</p>
</div>
<div className="my-1 h-px bg-border" />
Expand Down Expand Up @@ -251,7 +268,14 @@ export function AgentsHeader() {
<DropdownMenuContent align="end" className="min-w-56">
<DropdownMenuLabel>
<div className="flex flex-col space-y-1">
<p className="text-sm font-medium">{user?.name}</p>
<div className="flex items-center gap-2">
<p className="text-sm font-medium">{user?.name}</p>
{isPro && (
<Badge variant="default" className="px-1.5 py-0 text-[10px]">
{planLabel}
</Badge>
)}
</div>
<p className="text-xs text-muted-foreground">{user?.email}</p>
</div>
</DropdownMenuLabel>
Expand Down
22 changes: 21 additions & 1 deletion packages/trpc/src/router/billing/billing.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -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) {
Expand Down
Loading