Skip to content

Commit 2862712

Browse files
committed
Add upgrade confirmation dialog and plan features
1 parent 15a4b12 commit 2862712

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
@@ -1111,12 +1111,12 @@ export class PostHogAPIClient {
11111111

11121112
async getMySeat(): Promise<SeatData | null> {
11131113
try {
1114-
const url = new URL(`${this.api.baseUrl}/api/code/seats/me/`);
1114+
const url = new URL(`${this.api.baseUrl}/api/seats/me/`);
11151115
url.searchParams.set("product_key", SEAT_PRODUCT_KEY);
11161116
const response = await this.api.fetcher.fetch({
11171117
method: "get",
11181118
url,
1119-
path: "/api/code/seats/me/",
1119+
path: "/api/seats/me/",
11201120
});
11211121
return (await response.json()) as SeatData;
11221122
} catch (error) {
@@ -1129,11 +1129,11 @@ export class PostHogAPIClient {
11291129

11301130
async createSeat(planKey: string): Promise<SeatData> {
11311131
try {
1132-
const url = new URL(`${this.api.baseUrl}/api/code/seats/`);
1132+
const url = new URL(`${this.api.baseUrl}/api/seats/`);
11331133
const response = await this.api.fetcher.fetch({
11341134
method: "post",
11351135
url,
1136-
path: "/api/code/seats/",
1136+
path: "/api/seats/",
11371137
overrides: {
11381138
body: JSON.stringify({
11391139
product_key: SEAT_PRODUCT_KEY,
@@ -1149,11 +1149,11 @@ export class PostHogAPIClient {
11491149

11501150
async upgradeSeat(planKey: string): Promise<SeatData> {
11511151
try {
1152-
const url = new URL(`${this.api.baseUrl}/api/code/seats/me/`);
1152+
const url = new URL(`${this.api.baseUrl}/api/seats/me/`);
11531153
const response = await this.api.fetcher.fetch({
11541154
method: "patch",
11551155
url,
1156-
path: "/api/code/seats/me/",
1156+
path: "/api/seats/me/",
11571157
overrides: {
11581158
body: JSON.stringify({
11591159
product_key: SEAT_PRODUCT_KEY,
@@ -1169,12 +1169,12 @@ export class PostHogAPIClient {
11691169

11701170
async cancelSeat(): Promise<void> {
11711171
try {
1172-
const url = new URL(`${this.api.baseUrl}/api/code/seats/me/`);
1172+
const url = new URL(`${this.api.baseUrl}/api/seats/me/`);
11731173
url.searchParams.set("product_key", SEAT_PRODUCT_KEY);
11741174
await this.api.fetcher.fetch({
11751175
method: "delete",
11761176
url,
1177-
path: "/api/code/seats/me/",
1177+
path: "/api/seats/me/",
11781178
});
11791179
} catch (error) {
11801180
if (this.isFetcherStatusError(error, 204)) {
@@ -1186,11 +1186,11 @@ export class PostHogAPIClient {
11861186

11871187
async reactivateSeat(): Promise<SeatData> {
11881188
try {
1189-
const url = new URL(`${this.api.baseUrl}/api/code/seats/me/reactivate/`);
1189+
const url = new URL(`${this.api.baseUrl}/api/seats/me/reactivate/`);
11901190
const response = await this.api.fetcher.fetch({
11911191
method: "post",
11921192
url,
1193-
path: "/api/code/seats/me/reactivate/",
1193+
path: "/api/seats/me/reactivate/",
11941194
overrides: {
11951195
body: JSON.stringify({ product_key: SEAT_PRODUCT_KEY }),
11961196
},

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 phWordmark from "@renderer/assets/images/wordmark.svg";
13-
import { useEffect } from "react";
13+
import { useEffect, useState } from "react";
1414

1515
interface BillingStepProps {
1616
onNext: () => void;
@@ -35,6 +35,7 @@ export function BillingStep({ onNext, onBack }: BillingStepProps) {
3535
const { selectedPlan, selectPlan } = useAuthStore();
3636
const { isLoading, error, redirectUrl } = useSeat();
3737
const { provisionFreeSeat, upgradeToPro, clearError } = useSeatStore();
38+
const [showUpgradeDialog, setShowUpgradeDialog] = useState(false);
3839

3940
useEffect(() => {
4041
if (!selectedPlan) {
@@ -49,10 +50,18 @@ export function BillingStep({ onNext, onBack }: BillingStepProps) {
4950
const handleContinue = async () => {
5051
if (selectedPlan === "free") {
5152
await provisionFreeSeat();
53+
const storeState = useSeatStore.getState();
54+
if (!storeState.error) {
55+
onNext();
56+
}
5257
} else {
53-
await upgradeToPro();
58+
setShowUpgradeDialog(true);
5459
}
60+
};
5561

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

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)