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
Expand Up @@ -4,6 +4,8 @@ import { useSettingsDialogStore } from "@features/settings/stores/settingsDialog
import { useFeatureFlag } from "@hooks/useFeatureFlag";
import { Circle } from "@phosphor-icons/react";
import { BILLING_FLAG } from "@shared/constants";
import { ANALYTICS_EVENTS } from "@shared/types/analytics";
import { track } from "@utils/analytics";

export function SidebarUsageBar() {
const billingEnabled = useFeatureFlag(BILLING_FLAG);
Expand All @@ -12,6 +14,7 @@ export function SidebarUsageBar() {
if (!billingEnabled) return null;

const handleUpgrade = () => {
track(ANALYTICS_EVENTS.UPGRADE_PROMPT_CLICKED, { surface: "sidebar" });
useSettingsDialogStore.getState().open("plan-usage");
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,26 @@ import { useUsageLimitStore } from "@features/billing/stores/usageLimitStore";
import { useSettingsDialogStore } from "@features/settings/stores/settingsDialogStore";
import { WarningCircle } from "@phosphor-icons/react";
import { Button, Dialog, Flex, Text } from "@radix-ui/themes";
import { ANALYTICS_EVENTS } from "@shared/types/analytics";
import { track } from "@utils/analytics";
import { useEffect } from "react";

export function UsageLimitModal() {
const isOpen = useUsageLimitStore((s) => s.isOpen);
const hide = useUsageLimitStore((s) => s.hide);

useEffect(() => {
if (isOpen) {
track(ANALYTICS_EVENTS.UPGRADE_PROMPT_SHOWN, {
surface: "usage_limit_modal",
});
}
}, [isOpen]);

const handleUpgrade = () => {
track(ANALYTICS_EVENTS.UPGRADE_PROMPT_CLICKED, {
surface: "usage_limit_modal",
});
hide();
useSettingsDialogStore.getState().open("plan-usage");
};
Expand All @@ -22,20 +36,22 @@ export function UsageLimitModal() {
<Flex direction="column" gap="3">
<Flex align="center" gap="2">
<WarningCircle size={20} weight="bold" color="var(--red-9)" />
<Dialog.Title className="mb-0">Usage limit reached</Dialog.Title>
<Dialog.Title className="mb-0">
You're out of usage for this month
</Dialog.Title>
</Flex>
<Dialog.Description>
<Text color="gray" className="text-sm">
You've reached your free plan usage limit. Upgrade to Pro for
unlimited usage.
You've hit your Free usage limit. Upgrade to Pro for 20× more
usage.
</Text>
Comment thread
k11kirky marked this conversation as resolved.
</Dialog.Description>
<Flex justify="end" gap="3" mt="2">
<Button type="button" variant="soft" color="gray" onClick={hide}>
Not now
</Button>
<Button type="button" onClick={handleUpgrade}>
View upgrade options
See Pro
</Button>
</Flex>
</Flex>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ import { useSeat } from "@hooks/useSeat";
import type { UsageBucket } from "@main/services/llm-gateway/schemas";
import {
ArrowSquareOut,
Check,
CreditCard,
Info,
WarningCircle,
} from "@phosphor-icons/react";
import {
Badge,
Button,
Callout,
Dialog,
Expand All @@ -23,8 +23,9 @@ import {
Spinner,
Text,
} from "@radix-ui/themes";
import { Tooltip } from "@renderer/components/ui/Tooltip";
import { ANALYTICS_EVENTS } from "@shared/types/analytics";
import { PLAN_PRO_ALPHA } from "@shared/types/seat";
import { track } from "@utils/analytics";
import { logger } from "@utils/logger";
import { getBillingUrl, getPostHogUrl } from "@utils/urls";
import { useEffect, useState } from "react";
Expand Down Expand Up @@ -86,6 +87,14 @@ export function PlanUsageSettings() {
void refetchUsage();
}, [fetchSeat, refetchUsage]);

useEffect(() => {
if (showUpgradeDialog) {
track(ANALYTICS_EVENTS.UPGRADE_PROMPT_SHOWN, {
surface: "upgrade_dialog",
});
}
}, [showUpgradeDialog]);

const formattedActiveUntil = activeUntil
? activeUntil.toLocaleDateString(undefined, {
month: "short",
Expand Down Expand Up @@ -183,22 +192,13 @@ export function PlanUsageSettings() {
name="Free"
price="$0"
period="/mo"
features={[
"Limited usage",
"Local and cloud execution",
"All Claude and Codex models",
]}
isCurrent={!isOrgPro}
/>
<PlanCard
name="Pro"
price="$200"
period="/mo"
features={[
"Higher usage limits",
"Local and cloud execution",
"All Claude and Codex models",
]}
badge="20× Free usage"
isCurrent={isOrgPro && !isAlpha}
resetLabel={
isOrgPro && !isAlpha && isCanceling && formattedActiveUntil
Expand Down Expand Up @@ -238,7 +238,12 @@ export function PlanUsageSettings() {
<Button
size="1"
variant="solid"
onClick={() => setShowUpgradeDialog(true)}
onClick={() => {
track(ANALYTICS_EVENTS.UPGRADE_PROMPT_CLICKED, {
surface: "plan_page_card",
});
setShowUpgradeDialog(true);
}}
disabled={isLoading}
className="self-start"
>
Expand Down Expand Up @@ -351,28 +356,16 @@ export function PlanUsageSettings() {
<Dialog.Content maxWidth="420px" size="2">
<Dialog.Title className="text-base">Upgrade to Pro</Dialog.Title>
<Dialog.Description color="gray" className="text-sm">
Pro is for teams using Code as part of their daily development
workflow: longer cloud runs, repeated agent iterations, and fewer
stops as work scales.{" "}
{seat?.organization_name ? (
<Text weight="medium">{seat.organization_name}</Text>
) : (
"Your organization"
)}{" "}
will be charged $200/month using the payment method on file in
PostHog.
will be charged $200/month for 20× the Free usage limit.
</Dialog.Description>
<Flex direction="column" gap="2" mt="3">
<Flex align="center" gap="2">
<Check size={14} weight="bold" className="text-(--accent-9)" />
<Text className="text-sm">Higher usage limits</Text>
</Flex>
<Flex align="center" gap="2">
<Check size={14} weight="bold" className="text-(--accent-9)" />
<Text className="text-sm">Local and cloud execution</Text>
</Flex>
<Flex align="center" gap="2">
<Check size={14} weight="bold" className="text-(--accent-9)" />
<Text className="text-sm">All Claude and Codex models</Text>
</Flex>
</Flex>
<Flex
align="start"
gap="2"
Expand All @@ -395,6 +388,9 @@ export function PlanUsageSettings() {
<Button
size="2"
onClick={async () => {
track(ANALYTICS_EVENTS.UPGRADE_PROMPT_CLICKED, {
surface: "upgrade_dialog",
});
setShowUpgradeDialog(false);
await upgradeToPro();
}}
Expand Down Expand Up @@ -450,19 +446,19 @@ interface PlanCardProps {
name: string;
price: string;
period: string;
features: string[];
isCurrent: boolean;
resetLabel?: string;
badge?: string;
action?: React.ReactNode;
}

function PlanCard({
name,
price,
period,
features,
isCurrent,
resetLabel,
badge,
action,
}: PlanCardProps) {
return (
Expand All @@ -477,8 +473,13 @@ function PlanCard({
: "1px solid var(--gray-5)",
opacity: isCurrent ? 1 : 0.7,
}}
className="flex-1 rounded-(--radius-3)"
className="relative flex-1 rounded-(--radius-3)"
>
{badge && (
<Badge variant="soft" radius="full" className="absolute top-4 right-4">
{badge}
</Badge>
)}
<Flex direction="column" gap="3">
<Flex direction="column" gap="1">
<Text
Expand All @@ -501,29 +502,6 @@ function PlanCard({
<Text className="text-(--gray-9) text-[13px]">{resetLabel}</Text>
)}
</Flex>
<Flex direction="column" gap="1">
{features.map((feature) => (
<Flex key={feature} align="center" gap="2">
<Check
size={14}
weight="bold"
className="shrink-0 text-(--accent-9)"
/>
<Text className="text-(--gray-11) text-sm">
{feature.endsWith("*") ? (
<>
{feature.slice(0, -1)}
<Tooltip content="Usage is limited to human-level usage. This cannot be used as your API key. If you hit this limit, please contact support.">
<span className="cursor-help">*</span>
</Tooltip>
</>
) : (
feature
)}
</Text>
</Flex>
))}
</Flex>
</Flex>
{action}
</Flex>
Expand Down
21 changes: 21 additions & 0 deletions apps/code/src/shared/types/analytics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -594,6 +594,23 @@ export interface SignalSourceConnectedProperties {
}

// Subscription / billing events

export type UpgradePromptShownSurface = "usage_limit_modal" | "upgrade_dialog";

export type UpgradePromptClickedSurface =
| "usage_limit_modal"
| "sidebar"
| "plan_page_card"
| "upgrade_dialog";

export interface UpgradePromptShownProperties {
surface: UpgradePromptShownSurface;
}

export interface UpgradePromptClickedProperties {
surface: UpgradePromptClickedSurface;
}

export interface SubscriptionStartedProperties {
plan_key: string;
previous_plan_key?: string;
Expand Down Expand Up @@ -722,6 +739,8 @@ export const ANALYTICS_EVENTS = {
PROMPT_HISTORY_SELECTED: "Prompt history selected",

// Subscription events
UPGRADE_PROMPT_SHOWN: "Upgrade prompt shown",
UPGRADE_PROMPT_CLICKED: "Upgrade prompt clicked",
SUBSCRIPTION_STARTED: "Subscription started",
SUBSCRIPTION_CANCELLED: "Subscription cancelled",
} as const;
Expand Down Expand Up @@ -838,6 +857,8 @@ export type EventPropertyMap = {
[ANALYTICS_EVENTS.PROMPT_HISTORY_SELECTED]: PromptHistorySelectedProperties;

// Subscription events
[ANALYTICS_EVENTS.UPGRADE_PROMPT_SHOWN]: UpgradePromptShownProperties;
[ANALYTICS_EVENTS.UPGRADE_PROMPT_CLICKED]: UpgradePromptClickedProperties;
[ANALYTICS_EVENTS.SUBSCRIPTION_STARTED]: SubscriptionStartedProperties;
[ANALYTICS_EVENTS.SUBSCRIPTION_CANCELLED]: SubscriptionCancelledProperties;
};
Loading