diff --git a/apps/code/src/renderer/features/billing/components/SidebarUsageBar.tsx b/apps/code/src/renderer/features/billing/components/SidebarUsageBar.tsx index e2dfdcb60..ec1f27bfb 100644 --- a/apps/code/src/renderer/features/billing/components/SidebarUsageBar.tsx +++ b/apps/code/src/renderer/features/billing/components/SidebarUsageBar.tsx @@ -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); @@ -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"); }; diff --git a/apps/code/src/renderer/features/billing/components/UsageLimitModal.tsx b/apps/code/src/renderer/features/billing/components/UsageLimitModal.tsx index 386687dc5..5022542a5 100644 --- a/apps/code/src/renderer/features/billing/components/UsageLimitModal.tsx +++ b/apps/code/src/renderer/features/billing/components/UsageLimitModal.tsx @@ -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"); }; @@ -22,12 +36,14 @@ export function UsageLimitModal() { - Usage limit reached + + You're out of usage for this month + - 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. @@ -35,7 +51,7 @@ export function UsageLimitModal() { Not now 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 b3001f596..9046e271b 100644 --- a/apps/code/src/renderer/features/settings/components/sections/PlanUsageSettings.tsx +++ b/apps/code/src/renderer/features/settings/components/sections/PlanUsageSettings.tsx @@ -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, @@ -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"; @@ -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", @@ -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} /> setShowUpgradeDialog(true)} + onClick={() => { + track(ANALYTICS_EVENTS.UPGRADE_PROMPT_CLICKED, { + surface: "plan_page_card", + }); + setShowUpgradeDialog(true); + }} disabled={isLoading} className="self-start" > @@ -351,28 +356,16 @@ export function PlanUsageSettings() { Upgrade to Pro + 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 ? ( {seat.organization_name} ) : ( "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. - - - - Higher usage limits - - - - Local and cloud execution - - - - All Claude and Codex models - - { + track(ANALYTICS_EVENTS.UPGRADE_PROMPT_CLICKED, { + surface: "upgrade_dialog", + }); setShowUpgradeDialog(false); await upgradeToPro(); }} @@ -450,9 +446,9 @@ interface PlanCardProps { name: string; price: string; period: string; - features: string[]; isCurrent: boolean; resetLabel?: string; + badge?: string; action?: React.ReactNode; } @@ -460,9 +456,9 @@ function PlanCard({ name, price, period, - features, isCurrent, resetLabel, + badge, action, }: PlanCardProps) { return ( @@ -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} + + )} {resetLabel} )} - - {features.map((feature) => ( - - - - {feature.endsWith("*") ? ( - <> - {feature.slice(0, -1)} - - * - - - ) : ( - feature - )} - - - ))} - {action} diff --git a/apps/code/src/shared/types/analytics.ts b/apps/code/src/shared/types/analytics.ts index 58d12542e..3bb504aae 100644 --- a/apps/code/src/shared/types/analytics.ts +++ b/apps/code/src/shared/types/analytics.ts @@ -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; @@ -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; @@ -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; };