Skip to content

Commit 5512777

Browse files
committed
Resolve best seat across orgs for rate limits
1 parent de71216 commit 5512777

5 files changed

Lines changed: 68 additions & 12 deletions

File tree

apps/code/src/renderer/api/posthogClient.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2260,6 +2260,7 @@ export class PostHogAPIClient {
22602260
try {
22612261
const url = new URL(`${this.api.baseUrl}/api/seats/me/`);
22622262
url.searchParams.set("product_key", SEAT_PRODUCT_KEY);
2263+
url.searchParams.set("best", "true");
22632264
const response = await this.api.fetcher.fetch({
22642265
method: "get",
22652266
url,

apps/code/src/renderer/features/billing/stores/seatStore.ts

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import type { SeatData } from "@shared/types/seat";
88
import { PLAN_FREE, PLAN_PRO } from "@shared/types/seat";
99
import { logger } from "@utils/logger";
1010
import { queryClient } from "@utils/queryClient";
11-
import { getPostHogUrl } from "@utils/urls";
1211
import { create } from "zustand";
1312

1413
const log = logger.scope("seat-store");
@@ -18,6 +17,7 @@ interface SeatStoreState {
1817
isLoading: boolean;
1918
error: string | null;
2019
redirectUrl: string | null;
20+
billingOrgId: string | null;
2121
}
2222

2323
interface SeatStoreActions {
@@ -54,7 +54,7 @@ function handleSeatError(
5454
set({
5555
isLoading: false,
5656
error: "Billing subscription required",
57-
redirectUrl: getPostHogUrl("/organization/billing"),
57+
redirectUrl: error.redirectUrl,
5858
});
5959
return;
6060
}
@@ -80,6 +80,7 @@ const initialState: SeatStoreState = {
8080
isLoading: false,
8181
error: null,
8282
redirectUrl: null,
83+
billingOrgId: null,
8384
};
8485

8586
export const useSeatStore = create<SeatStore>()((set, get) => ({
@@ -99,7 +100,11 @@ export const useSeatStore = create<SeatStore>()((set, get) => ({
99100
seat = await client.getMySeat();
100101
}
101102
}
102-
set({ seat, isLoading: false });
103+
set({
104+
seat,
105+
isLoading: false,
106+
billingOrgId: seat?.organization_id ?? null,
107+
});
103108
} catch (error) {
104109
const { seat: existingSeat } = get();
105110
if (existingSeat) {
@@ -122,12 +127,20 @@ export const useSeatStore = create<SeatStore>()((set, get) => ({
122127
plan: existing.plan_key,
123128
status: existing.status,
124129
});
125-
set({ seat: existing, isLoading: false });
130+
set({
131+
seat: existing,
132+
isLoading: false,
133+
billingOrgId: existing.organization_id ?? null,
134+
});
126135
return;
127136
}
128137
const seat = await client.createSeat(PLAN_FREE);
129138
log.info("Free seat created", { id: seat.id, plan: seat.plan_key });
130-
set({ seat, isLoading: false });
139+
set({
140+
seat,
141+
isLoading: false,
142+
billingOrgId: seat.organization_id ?? null,
143+
});
131144
invalidatePlanCache();
132145
} catch (error) {
133146
log.error("provisionFreeSeat failed", error);
@@ -142,16 +155,28 @@ export const useSeatStore = create<SeatStore>()((set, get) => ({
142155
const existing = await client.getMySeat();
143156
if (existing) {
144157
if (existing.plan_key === PLAN_PRO) {
145-
set({ seat: existing, isLoading: false });
158+
set({
159+
seat: existing,
160+
isLoading: false,
161+
billingOrgId: existing.organization_id ?? null,
162+
});
146163
return;
147164
}
148165
const seat = await client.upgradeSeat(PLAN_PRO);
149-
set({ seat, isLoading: false });
166+
set({
167+
seat,
168+
isLoading: false,
169+
billingOrgId: seat.organization_id ?? null,
170+
});
150171
invalidatePlanCache();
151172
return;
152173
}
153174
const seat = await client.createSeat(PLAN_PRO);
154-
set({ seat, isLoading: false });
175+
set({
176+
seat,
177+
isLoading: false,
178+
billingOrgId: seat.organization_id ?? null,
179+
});
155180
invalidatePlanCache();
156181
} catch (error) {
157182
handleSeatError(error, set);
@@ -164,7 +189,11 @@ export const useSeatStore = create<SeatStore>()((set, get) => ({
164189
const client = await getClient();
165190
await client.cancelSeat();
166191
const seat = await client.getMySeat();
167-
set({ seat, isLoading: false });
192+
set({
193+
seat,
194+
isLoading: false,
195+
billingOrgId: seat?.organization_id ?? null,
196+
});
168197
invalidatePlanCache();
169198
} catch (error) {
170199
handleSeatError(error, set);
@@ -176,7 +205,11 @@ export const useSeatStore = create<SeatStore>()((set, get) => ({
176205
try {
177206
const client = await getClient();
178207
const seat = await client.reactivateSeat();
179-
set({ seat, isLoading: false });
208+
set({
209+
seat,
210+
isLoading: false,
211+
billingOrgId: seat.organization_id ?? null,
212+
});
180213
invalidatePlanCache();
181214
} catch (error) {
182215
handleSeatError(error, set);

apps/code/src/renderer/features/settings/components/sections/PlanUsageSettings.tsx

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { getAuthenticatedClient } from "@features/auth/hooks/authClient";
12
import { useUsage } from "@features/billing/hooks/useUsage";
23
import { useSeatStore } from "@features/billing/stores/seatStore";
34
import { useSeat } from "@hooks/useSeat";
@@ -19,9 +20,26 @@ import {
1920
} from "@radix-ui/themes";
2021
import { Tooltip } from "@renderer/components/ui/Tooltip";
2122
import { PLAN_PRO_ALPHA } from "@shared/types/seat";
23+
import { logger } from "@utils/logger";
2224
import { getPostHogUrl } from "@utils/urls";
2325
import { useEffect, useState } from "react";
2426

27+
const log = logger.scope("plan-usage");
28+
29+
async function openBillingPage(orgId: string | null): Promise<void> {
30+
if (orgId) {
31+
try {
32+
const client = await getAuthenticatedClient();
33+
if (client) {
34+
await client.switchOrganization(orgId);
35+
}
36+
} catch (err) {
37+
log.warn("Failed to switch org before opening billing", err);
38+
}
39+
}
40+
window.open(getPostHogUrl("/organization/billing"), "_blank");
41+
}
42+
2543
function formatResetTime(seconds: number): string {
2644
if (seconds < 3600) return "less than 1 hour";
2745
if (seconds < 86400) {
@@ -42,6 +60,7 @@ export function PlanUsageSettings() {
4260
isLoading,
4361
error,
4462
redirectUrl,
63+
billingOrgId,
4564
} = useSeat();
4665
const { fetchSeat, upgradeToPro, cancelSeat, reactivateSeat, clearError } =
4766
useSeatStore();
@@ -275,8 +294,7 @@ export function PlanUsageSettings() {
275294
size="1"
276295
variant="outline"
277296
onClick={() => {
278-
const url = getPostHogUrl("/organization/billing");
279-
window.open(url, "_blank");
297+
void openBillingPage(billingOrgId);
280298
}}
281299
>
282300
Open

apps/code/src/renderer/hooks/useSeat.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export function useSeat() {
66
const isLoading = useSeatStore((s) => s.isLoading);
77
const error = useSeatStore((s) => s.error);
88
const redirectUrl = useSeatStore((s) => s.redirectUrl);
9+
const billingOrgId = useSeatStore((s) => s.billingOrgId);
910

1011
const isPro = isProPlan(seat?.plan_key);
1112
const hasAccess = seat ? seatHasAccess(seat.status) : false;
@@ -20,6 +21,7 @@ export function useSeat() {
2021
isLoading,
2122
error,
2223
redirectUrl,
24+
billingOrgId,
2325
isPro,
2426
hasAccess,
2527
isCanceling,

apps/code/src/shared/types/seat.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ export interface SeatData {
1616
created_at: number;
1717
active_until: number | null;
1818
active_from: number;
19+
organization_id?: string;
20+
organization_name?: string;
1921
}
2022

2123
export const SEAT_PRODUCT_KEY = "posthog_code";

0 commit comments

Comments
 (0)