From 5512777b13b9740df53a11e413c723dfea53f618 Mon Sep 17 00:00:00 2001 From: Charles Vien Date: Fri, 24 Apr 2026 16:17:05 -0700 Subject: [PATCH 1/3] Resolve best seat across orgs for rate limits --- apps/code/src/renderer/api/posthogClient.ts | 1 + .../features/billing/stores/seatStore.ts | 53 +++++++++++++++---- .../components/sections/PlanUsageSettings.tsx | 22 +++++++- apps/code/src/renderer/hooks/useSeat.ts | 2 + apps/code/src/shared/types/seat.ts | 2 + 5 files changed, 68 insertions(+), 12 deletions(-) diff --git a/apps/code/src/renderer/api/posthogClient.ts b/apps/code/src/renderer/api/posthogClient.ts index a7f6b141b..96e6c1325 100644 --- a/apps/code/src/renderer/api/posthogClient.ts +++ b/apps/code/src/renderer/api/posthogClient.ts @@ -2260,6 +2260,7 @@ export class PostHogAPIClient { try { const url = new URL(`${this.api.baseUrl}/api/seats/me/`); url.searchParams.set("product_key", SEAT_PRODUCT_KEY); + url.searchParams.set("best", "true"); const response = await this.api.fetcher.fetch({ method: "get", url, diff --git a/apps/code/src/renderer/features/billing/stores/seatStore.ts b/apps/code/src/renderer/features/billing/stores/seatStore.ts index 46a1b92bf..531ab5c01 100644 --- a/apps/code/src/renderer/features/billing/stores/seatStore.ts +++ b/apps/code/src/renderer/features/billing/stores/seatStore.ts @@ -8,7 +8,6 @@ import type { SeatData } from "@shared/types/seat"; import { PLAN_FREE, PLAN_PRO } from "@shared/types/seat"; import { logger } from "@utils/logger"; import { queryClient } from "@utils/queryClient"; -import { getPostHogUrl } from "@utils/urls"; import { create } from "zustand"; const log = logger.scope("seat-store"); @@ -18,6 +17,7 @@ interface SeatStoreState { isLoading: boolean; error: string | null; redirectUrl: string | null; + billingOrgId: string | null; } interface SeatStoreActions { @@ -54,7 +54,7 @@ function handleSeatError( set({ isLoading: false, error: "Billing subscription required", - redirectUrl: getPostHogUrl("/organization/billing"), + redirectUrl: error.redirectUrl, }); return; } @@ -80,6 +80,7 @@ const initialState: SeatStoreState = { isLoading: false, error: null, redirectUrl: null, + billingOrgId: null, }; export const useSeatStore = create()((set, get) => ({ @@ -99,7 +100,11 @@ export const useSeatStore = create()((set, get) => ({ seat = await client.getMySeat(); } } - set({ seat, isLoading: false }); + set({ + seat, + isLoading: false, + billingOrgId: seat?.organization_id ?? null, + }); } catch (error) { const { seat: existingSeat } = get(); if (existingSeat) { @@ -122,12 +127,20 @@ export const useSeatStore = create()((set, get) => ({ plan: existing.plan_key, status: existing.status, }); - set({ seat: existing, isLoading: false }); + set({ + seat: existing, + isLoading: false, + billingOrgId: existing.organization_id ?? null, + }); return; } const seat = await client.createSeat(PLAN_FREE); log.info("Free seat created", { id: seat.id, plan: seat.plan_key }); - set({ seat, isLoading: false }); + set({ + seat, + isLoading: false, + billingOrgId: seat.organization_id ?? null, + }); invalidatePlanCache(); } catch (error) { log.error("provisionFreeSeat failed", error); @@ -142,16 +155,28 @@ export const useSeatStore = create()((set, get) => ({ const existing = await client.getMySeat(); if (existing) { if (existing.plan_key === PLAN_PRO) { - set({ seat: existing, isLoading: false }); + set({ + seat: existing, + isLoading: false, + billingOrgId: existing.organization_id ?? null, + }); return; } const seat = await client.upgradeSeat(PLAN_PRO); - set({ seat, isLoading: false }); + set({ + seat, + isLoading: false, + billingOrgId: seat.organization_id ?? null, + }); invalidatePlanCache(); return; } const seat = await client.createSeat(PLAN_PRO); - set({ seat, isLoading: false }); + set({ + seat, + isLoading: false, + billingOrgId: seat.organization_id ?? null, + }); invalidatePlanCache(); } catch (error) { handleSeatError(error, set); @@ -164,7 +189,11 @@ export const useSeatStore = create()((set, get) => ({ const client = await getClient(); await client.cancelSeat(); const seat = await client.getMySeat(); - set({ seat, isLoading: false }); + set({ + seat, + isLoading: false, + billingOrgId: seat?.organization_id ?? null, + }); invalidatePlanCache(); } catch (error) { handleSeatError(error, set); @@ -176,7 +205,11 @@ export const useSeatStore = create()((set, get) => ({ try { const client = await getClient(); const seat = await client.reactivateSeat(); - set({ seat, isLoading: false }); + set({ + seat, + isLoading: false, + billingOrgId: seat.organization_id ?? null, + }); invalidatePlanCache(); } catch (error) { handleSeatError(error, set); diff --git a/apps/code/src/renderer/features/settings/components/sections/PlanUsageSettings.tsx b/apps/code/src/renderer/features/settings/components/sections/PlanUsageSettings.tsx index 3b40ba1a1..a0c092fcc 100644 --- a/apps/code/src/renderer/features/settings/components/sections/PlanUsageSettings.tsx +++ b/apps/code/src/renderer/features/settings/components/sections/PlanUsageSettings.tsx @@ -1,3 +1,4 @@ +import { getAuthenticatedClient } from "@features/auth/hooks/authClient"; import { useUsage } from "@features/billing/hooks/useUsage"; import { useSeatStore } from "@features/billing/stores/seatStore"; import { useSeat } from "@hooks/useSeat"; @@ -19,9 +20,26 @@ import { } from "@radix-ui/themes"; import { Tooltip } from "@renderer/components/ui/Tooltip"; import { PLAN_PRO_ALPHA } from "@shared/types/seat"; +import { logger } from "@utils/logger"; import { getPostHogUrl } from "@utils/urls"; import { useEffect, useState } from "react"; +const log = logger.scope("plan-usage"); + +async function openBillingPage(orgId: string | null): Promise { + if (orgId) { + try { + const client = await getAuthenticatedClient(); + if (client) { + await client.switchOrganization(orgId); + } + } catch (err) { + log.warn("Failed to switch org before opening billing", err); + } + } + window.open(getPostHogUrl("/organization/billing"), "_blank"); +} + function formatResetTime(seconds: number): string { if (seconds < 3600) return "less than 1 hour"; if (seconds < 86400) { @@ -42,6 +60,7 @@ export function PlanUsageSettings() { isLoading, error, redirectUrl, + billingOrgId, } = useSeat(); const { fetchSeat, upgradeToPro, cancelSeat, reactivateSeat, clearError } = useSeatStore(); @@ -275,8 +294,7 @@ export function PlanUsageSettings() { size="1" variant="outline" onClick={() => { - const url = getPostHogUrl("/organization/billing"); - window.open(url, "_blank"); + void openBillingPage(billingOrgId); }} > Open diff --git a/apps/code/src/renderer/hooks/useSeat.ts b/apps/code/src/renderer/hooks/useSeat.ts index 0e65899d9..a3ff0883f 100644 --- a/apps/code/src/renderer/hooks/useSeat.ts +++ b/apps/code/src/renderer/hooks/useSeat.ts @@ -6,6 +6,7 @@ export function useSeat() { const isLoading = useSeatStore((s) => s.isLoading); const error = useSeatStore((s) => s.error); const redirectUrl = useSeatStore((s) => s.redirectUrl); + const billingOrgId = useSeatStore((s) => s.billingOrgId); const isPro = isProPlan(seat?.plan_key); const hasAccess = seat ? seatHasAccess(seat.status) : false; @@ -20,6 +21,7 @@ export function useSeat() { isLoading, error, redirectUrl, + billingOrgId, isPro, hasAccess, isCanceling, diff --git a/apps/code/src/shared/types/seat.ts b/apps/code/src/shared/types/seat.ts index 7cd5d6f6f..5ff3d88a5 100644 --- a/apps/code/src/shared/types/seat.ts +++ b/apps/code/src/shared/types/seat.ts @@ -16,6 +16,8 @@ export interface SeatData { created_at: number; active_until: number | null; active_from: number; + organization_id?: string; + organization_name?: string; } export const SEAT_PRODUCT_KEY = "posthog_code"; From d8273c354ac14a218b44dea555e1c604500aaafc Mon Sep 17 00:00:00 2001 From: Charles Vien Date: Fri, 24 Apr 2026 16:24:57 -0700 Subject: [PATCH 2/3] Fix redirectUrl test to match store passthrough behavior --- .../src/renderer/features/billing/stores/seatStore.test.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/code/src/renderer/features/billing/stores/seatStore.test.ts b/apps/code/src/renderer/features/billing/stores/seatStore.test.ts index e375a71eb..d5728536f 100644 --- a/apps/code/src/renderer/features/billing/stores/seatStore.test.ts +++ b/apps/code/src/renderer/features/billing/stores/seatStore.test.ts @@ -260,9 +260,7 @@ describe("seatStore", () => { const state = useSeatStore.getState(); expect(state.error).toBe("Billing subscription required"); - expect(state.redirectUrl).toBe( - "https://posthog.com/organization/billing", - ); + expect(state.redirectUrl).toBe("/organization/billing"); }); it("sets error on payment failure", async () => { From 37db95eebae6b0ae1c54871880a2e276518192b3 Mon Sep 17 00:00:00 2001 From: Charles Vien Date: Fri, 24 Apr 2026 16:33:45 -0700 Subject: [PATCH 3/3] Fix review issues from billing org changes --- .../src/renderer/features/billing/stores/seatStore.test.ts | 5 +---- .../settings/components/sections/PlanUsageSettings.tsx | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/apps/code/src/renderer/features/billing/stores/seatStore.test.ts b/apps/code/src/renderer/features/billing/stores/seatStore.test.ts index d5728536f..b2e4cffb3 100644 --- a/apps/code/src/renderer/features/billing/stores/seatStore.test.ts +++ b/apps/code/src/renderer/features/billing/stores/seatStore.test.ts @@ -37,10 +37,6 @@ vi.mock("@utils/logger", () => ({ }, })); -vi.mock("@utils/urls", () => ({ - getPostHogUrl: (path: string) => `https://posthog.com${path}`, -})); - vi.mock("@renderer/trpc", () => ({ trpcClient: { llmGateway: { @@ -92,6 +88,7 @@ describe("seatStore", () => { isLoading: false, error: null, redirectUrl: null, + billingOrgId: null, }); }); diff --git a/apps/code/src/renderer/features/settings/components/sections/PlanUsageSettings.tsx b/apps/code/src/renderer/features/settings/components/sections/PlanUsageSettings.tsx index a0c092fcc..3b586c8c4 100644 --- a/apps/code/src/renderer/features/settings/components/sections/PlanUsageSettings.tsx +++ b/apps/code/src/renderer/features/settings/components/sections/PlanUsageSettings.tsx @@ -121,7 +121,7 @@ export function PlanUsageSettings() { variant="outline" color="amber" onClick={() => { - window.open(redirectUrl, "_blank"); + window.open(getPostHogUrl(redirectUrl), "_blank"); clearError(); }} className="self-start"