Skip to content

Commit 66965eb

Browse files
committed
Add upgrade confirmation dialog and plan features
1 parent d137d35 commit 66965eb

3 files changed

Lines changed: 140 additions & 45 deletions

File tree

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

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1176,12 +1176,12 @@ export class PostHogAPIClient {
11761176

11771177
async getMySeat(): Promise<SeatData | null> {
11781178
try {
1179-
const url = new URL(`${this.api.baseUrl}/api/code/seats/me/`);
1179+
const url = new URL(`${this.api.baseUrl}/api/seats/me/`);
11801180
url.searchParams.set("product_key", SEAT_PRODUCT_KEY);
11811181
const response = await this.api.fetcher.fetch({
11821182
method: "get",
11831183
url,
1184-
path: "/api/code/seats/me/",
1184+
path: "/api/seats/me/",
11851185
});
11861186
return (await response.json()) as SeatData;
11871187
} catch (error) {
@@ -1194,11 +1194,11 @@ export class PostHogAPIClient {
11941194

11951195
async createSeat(planKey: string): Promise<SeatData> {
11961196
try {
1197-
const url = new URL(`${this.api.baseUrl}/api/code/seats/`);
1197+
const url = new URL(`${this.api.baseUrl}/api/seats/`);
11981198
const response = await this.api.fetcher.fetch({
11991199
method: "post",
12001200
url,
1201-
path: "/api/code/seats/",
1201+
path: "/api/seats/",
12021202
overrides: {
12031203
body: JSON.stringify({
12041204
product_key: SEAT_PRODUCT_KEY,
@@ -1214,11 +1214,11 @@ export class PostHogAPIClient {
12141214

12151215
async upgradeSeat(planKey: string): Promise<SeatData> {
12161216
try {
1217-
const url = new URL(`${this.api.baseUrl}/api/code/seats/me/`);
1217+
const url = new URL(`${this.api.baseUrl}/api/seats/me/`);
12181218
const response = await this.api.fetcher.fetch({
12191219
method: "patch",
12201220
url,
1221-
path: "/api/code/seats/me/",
1221+
path: "/api/seats/me/",
12221222
overrides: {
12231223
body: JSON.stringify({
12241224
product_key: SEAT_PRODUCT_KEY,
@@ -1234,12 +1234,12 @@ export class PostHogAPIClient {
12341234

12351235
async cancelSeat(): Promise<void> {
12361236
try {
1237-
const url = new URL(`${this.api.baseUrl}/api/code/seats/me/`);
1237+
const url = new URL(`${this.api.baseUrl}/api/seats/me/`);
12381238
url.searchParams.set("product_key", SEAT_PRODUCT_KEY);
12391239
await this.api.fetcher.fetch({
12401240
method: "delete",
12411241
url,
1242-
path: "/api/code/seats/me/",
1242+
path: "/api/seats/me/",
12431243
});
12441244
} catch (error) {
12451245
if (this.isFetcherStatusError(error, 204)) {
@@ -1251,11 +1251,11 @@ export class PostHogAPIClient {
12511251

12521252
async reactivateSeat(): Promise<SeatData> {
12531253
try {
1254-
const url = new URL(`${this.api.baseUrl}/api/code/seats/me/reactivate/`);
1254+
const url = new URL(`${this.api.baseUrl}/api/seats/me/reactivate/`);
12551255
const response = await this.api.fetcher.fetch({
12561256
method: "post",
12571257
url,
1258-
path: "/api/code/seats/me/reactivate/",
1258+
path: "/api/seats/me/reactivate/",
12591259
overrides: {
12601260
body: JSON.stringify({ product_key: SEAT_PRODUCT_KEY }),
12611261
},

apps/code/src/renderer/features/onboarding/components/BillingStep.tsx

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ import {
88
Check,
99
WarningCircle,
1010
} from "@phosphor-icons/react";
11-
import { Badge, Button, Callout, Flex, Spinner, Text } from "@radix-ui/themes";
11+
import { Badge, Button, Callout, Dialog, Flex, Spinner, Text } from "@radix-ui/themes";
1212
import codeLogo from "@renderer/assets/images/code.svg";
13-
import { useEffect } from "react";
13+
import { useEffect, useState } from "react";
1414

1515
interface BillingStepProps {
1616
onNext: () => void;
@@ -36,6 +36,7 @@ export function BillingStep({ onNext, onBack }: BillingStepProps) {
3636
const selectPlan = useOnboardingStore((state) => state.selectPlan);
3737
const { isLoading, error, redirectUrl } = useSeat();
3838
const { provisionFreeSeat, upgradeToPro, clearError } = useSeatStore();
39+
const [showUpgradeDialog, setShowUpgradeDialog] = useState(false);
3940

4041
useEffect(() => {
4142
if (!selectedPlan) {
@@ -50,10 +51,18 @@ export function BillingStep({ onNext, onBack }: BillingStepProps) {
5051
const handleContinue = async () => {
5152
if (selectedPlan === "free") {
5253
await provisionFreeSeat();
54+
const storeState = useSeatStore.getState();
55+
if (!storeState.error) {
56+
onNext();
57+
}
5358
} else {
54-
await upgradeToPro();
59+
setShowUpgradeDialog(true);
5560
}
61+
};
5662

63+
const handleConfirmUpgrade = async () => {
64+
setShowUpgradeDialog(false);
65+
await upgradeToPro();
5766
const storeState = useSeatStore.getState();
5867
if (!storeState.error) {
5968
onNext();
@@ -190,6 +199,40 @@ export function BillingStep({ onNext, onBack }: BillingStepProps) {
190199
</Button>
191200
</Flex>
192201
</Flex>
202+
203+
<Dialog.Root open={showUpgradeDialog} onOpenChange={setShowUpgradeDialog}>
204+
<Dialog.Content maxWidth="420px" size="2">
205+
<Dialog.Title size="3">Upgrade to Pro</Dialog.Title>
206+
<Dialog.Description size="2" color="gray">
207+
You are about to subscribe to the Pro plan. Your organization will
208+
be charged $200/month starting immediately.
209+
</Dialog.Description>
210+
<Flex direction="column" gap="2" mt="3">
211+
<Flex align="center" gap="2">
212+
<Check size={14} weight="bold" style={{ color: "var(--accent-9)" }} />
213+
<Text size="2">Unlimited token usage</Text>
214+
</Flex>
215+
<Flex align="center" gap="2">
216+
<Check size={14} weight="bold" style={{ color: "var(--accent-9)" }} />
217+
<Text size="2">Local and cloud execution</Text>
218+
</Flex>
219+
</Flex>
220+
<Flex justify="end" gap="3" mt="4">
221+
<Dialog.Close>
222+
<Button variant="soft" color="gray" size="2">
223+
Cancel
224+
</Button>
225+
</Dialog.Close>
226+
<Button
227+
size="2"
228+
onClick={handleConfirmUpgrade}
229+
disabled={isLoading}
230+
>
231+
{isLoading ? <Spinner size="1" /> : "Subscribe — $200/mo"}
232+
</Button>
233+
</Flex>
234+
</Dialog.Content>
235+
</Dialog.Root>
193236
</Flex>
194237
</Flex>
195238
);

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

Lines changed: 84 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,20 @@ import { useSeatStore } from "@features/billing/stores/seatStore";
22
import { useSeat } from "@hooks/useSeat";
33
import {
44
ArrowSquareOut,
5+
Check,
56
CreditCard,
67
WarningCircle,
78
} from "@phosphor-icons/react";
89
import {
910
Button,
1011
Callout,
12+
Dialog,
1113
Flex,
1214
Progress,
1315
Spinner,
1416
Text,
1517
} from "@radix-ui/themes";
18+
import { useState } from "react";
1619
import { getPostHogUrl } from "@shared/utils/urls";
1720

1821
export function PlanUsageSettings() {
@@ -27,6 +30,7 @@ export function PlanUsageSettings() {
2730
} = useSeat();
2831
const { upgradeToPro, cancelSeat, reactivateSeat, clearError } =
2932
useSeatStore();
33+
const [showUpgradeDialog, setShowUpgradeDialog] = useState(false);
3034

3135
const formattedActiveUntil = activeUntil
3236
? activeUntil.toLocaleDateString(undefined, {
@@ -89,14 +93,14 @@ export function PlanUsageSettings() {
8993
name="Free"
9094
price="$0"
9195
period="/mo"
92-
description="Limited usage"
96+
features={["Limited usage", "Local execution only"]}
9397
isCurrent={!isPro}
9498
/>
9599
<PlanCard
96100
name="Pro"
97101
price="$200"
98102
period="/mo"
99-
description="Unlimited usage and cloud execution"
103+
features={["Unlimited usage*", "Local and cloud execution"]}
100104
isCurrent={isPro}
101105
resetLabel={
102106
isPro && isCanceling && formattedActiveUntil
@@ -133,7 +137,7 @@ export function PlanUsageSettings() {
133137
<Button
134138
size="1"
135139
variant="solid"
136-
onClick={upgradeToPro}
140+
onClick={() => setShowUpgradeDialog(true)}
137141
disabled={isLoading}
138142
style={{ alignSelf: "flex-start" }}
139143
>
@@ -281,6 +285,42 @@ export function PlanUsageSettings() {
281285
</Flex>
282286
</Flex>
283287
)}
288+
<Dialog.Root open={showUpgradeDialog} onOpenChange={setShowUpgradeDialog}>
289+
<Dialog.Content maxWidth="420px" size="2">
290+
<Dialog.Title size="3">Upgrade to Pro</Dialog.Title>
291+
<Dialog.Description size="2" color="gray">
292+
You are about to subscribe to the Pro plan. Your organization will
293+
be charged $200/month starting immediately.
294+
</Dialog.Description>
295+
<Flex direction="column" gap="2" mt="3">
296+
<Flex align="center" gap="2">
297+
<Check size={14} weight="bold" style={{ color: "var(--accent-9)" }} />
298+
<Text size="2">Unlimited token usage</Text>
299+
</Flex>
300+
<Flex align="center" gap="2">
301+
<Check size={14} weight="bold" style={{ color: "var(--accent-9)" }} />
302+
<Text size="2">Local and cloud execution</Text>
303+
</Flex>
304+
</Flex>
305+
<Flex justify="end" gap="3" mt="4">
306+
<Dialog.Close>
307+
<Button variant="soft" color="gray" size="2">
308+
Cancel
309+
</Button>
310+
</Dialog.Close>
311+
<Button
312+
size="2"
313+
onClick={async () => {
314+
setShowUpgradeDialog(false);
315+
await upgradeToPro();
316+
}}
317+
disabled={isLoading}
318+
>
319+
{isLoading ? <Spinner size="1" /> : "Subscribe — $200/mo"}
320+
</Button>
321+
</Flex>
322+
</Dialog.Content>
323+
</Dialog.Root>
284324
</Flex>
285325
);
286326
}
@@ -289,7 +329,7 @@ interface PlanCardProps {
289329
name: string;
290330
price: string;
291331
period: string;
292-
description: string;
332+
features: string[];
293333
isCurrent: boolean;
294334
resetLabel?: string;
295335
action?: React.ReactNode;
@@ -299,7 +339,7 @@ function PlanCard({
299339
name,
300340
price,
301341
period,
302-
description,
342+
features,
303343
isCurrent,
304344
resetLabel,
305345
action,
@@ -319,37 +359,49 @@ function PlanCard({
319359
opacity: isCurrent ? 1 : 0.7,
320360
}}
321361
>
322-
<Flex direction="column" gap="2">
323-
<Text
324-
size="1"
325-
weight="medium"
326-
style={{
327-
color: isCurrent ? "var(--accent-9)" : "var(--gray-9)",
328-
letterSpacing: "0.05em",
329-
}}
330-
>
331-
{isCurrent ? "CURRENT PLAN" : name.toUpperCase()}
332-
</Text>
333-
<Flex align="baseline" gap="2">
334-
<Text size="5" weight="bold">
335-
{name}
362+
<Flex direction="column" gap="3">
363+
<Flex direction="column" gap="1">
364+
<Text
365+
size="1"
366+
weight="medium"
367+
style={{
368+
color: isCurrent ? "var(--accent-9)" : "var(--gray-9)",
369+
letterSpacing: "0.05em",
370+
}}
371+
>
372+
{isCurrent ? "CURRENT PLAN" : name.toUpperCase()}
336373
</Text>
337-
<Text size="3" style={{ color: "var(--gray-11)" }}>
338-
{price}
374+
<Flex align="baseline" gap="2">
375+
<Text size="5" weight="bold">
376+
{name}
377+
</Text>
378+
<Text size="3" style={{ color: "var(--gray-11)" }}>
379+
{price}
380+
<Text size="1" style={{ color: "var(--gray-9)" }}>
381+
{period}
382+
</Text>
383+
</Text>
384+
</Flex>
385+
{resetLabel && (
339386
<Text size="1" style={{ color: "var(--gray-9)" }}>
340-
{period}
387+
{resetLabel}
341388
</Text>
342-
</Text>
389+
)}
390+
</Flex>
391+
<Flex direction="column" gap="1">
392+
{features.map((feature) => (
393+
<Flex key={feature} align="center" gap="2">
394+
<Check
395+
size={14}
396+
weight="bold"
397+
style={{ color: "var(--accent-9)", flexShrink: 0 }}
398+
/>
399+
<Text size="2" style={{ color: "var(--gray-11)" }}>
400+
{feature}
401+
</Text>
402+
</Flex>
403+
))}
343404
</Flex>
344-
{resetLabel ? (
345-
<Text size="1" style={{ color: "var(--gray-9)" }}>
346-
{resetLabel}
347-
</Text>
348-
) : (
349-
<Text size="1" style={{ color: "var(--gray-9)" }}>
350-
{description}
351-
</Text>
352-
)}
353405
</Flex>
354406
{action}
355407
</Flex>

0 commit comments

Comments
 (0)