History

Add 14-day free trial to the Team plan

Adds a 14-day free trial to the $150/mo Team plan and reflects it across the surfaces customers see: - autumn.config.ts: freeTrial on the team plan (14 days, card required, bills automatically when the trial ends). This is the deploy-time source synced via atmn. - marketing pricing: Team card now leads with "14-day free trial, cancel anytime", a "Start free trial" CTA, and a "14-day free trial, then $150 / month" line item. - in-app Choose-a-plan page: reads plan.freeTrial from Autumn, so the Team card shows "14-day free trial, then $150 / month" and a "Start free trial" button while the customer is still trial-eligible. - in-app Billing page: shows a "Free trial" badge and "Trial ends, then billing starts <date>" while a customer is mid-trial.

Rhys Sullivan committed 4 months ago

Commit 60d56ec1 parent f13cdea4 files · +44 −5Browse files

Showing the changes to apps/cloud/src (2 of 4 files). Show all

Unified · Split

2 files
  1. apps/cloud/src/routes/app/billing.tsx +9 −3
  2. apps/cloud/src/routes/app/billing_.plans.tsx +26 −1
apps/cloud/src/routes/app/billing.tsx+9−3
@@ -42,6 +42,7 @@ function BillingPage() { · 41 unmodified lines
42 );42 );
43 const isCanceling = activePlan?.customerEligibility?.canceling ?? false;43 const isCanceling = activePlan?.customerEligibility?.canceling ?? false;
44 const isSwitching = isCanceling && scheduledPlan != null;44 const isSwitching = isCanceling && scheduledPlan != null;
45 const isTrialing = activePlan?.customerEligibility?.trialing ?? false;
45 46
46 const displayPlan = isSwitching ? scheduledPlan : activePlan;47 const displayPlan = isSwitching ? scheduledPlan : activePlan;
47 const planId = displayPlan?.id ?? "free";48 const planId = displayPlan?.id ?? "free";
@@ -78,15 +79,20 @@ function BillingPage() { · 30 unmodified lines
78 Canceling79 Canceling
79 </Badge>80 </Badge>
80 )}81 )}
82 {isTrialing && !isCanceling && (
83 <Badge className="bg-primary/10 text-primary">Free trial</Badge>
84 )}
81 </div>85 </div>
82 <p className="mt-1 text-xs text-muted-foreground leading-none">86 <p className="mt-1 text-xs text-muted-foreground leading-none">
83 {isSwitching && sub?.currentPeriodEnd87 {isSwitching && sub?.currentPeriodEnd
84 ? `Starts ${new Date(sub.currentPeriodEnd).toLocaleDateString(undefined, { month: "long", day: "numeric", year: "numeric" })}`88 ? `Starts ${new Date(sub.currentPeriodEnd).toLocaleDateString(undefined, { month: "long", day: "numeric", year: "numeric" })}`
85 : isCanceling && sub?.currentPeriodEnd89 : isCanceling && sub?.currentPeriodEnd
86 ? `Access until ${new Date(sub.currentPeriodEnd).toLocaleDateString(undefined, { month: "long", day: "numeric", year: "numeric" })}`90 ? `Access until ${new Date(sub.currentPeriodEnd).toLocaleDateString(undefined, { month: "long", day: "numeric", year: "numeric" })}`
87 : sub?.currentPeriodEnd91 : isTrialing && sub?.currentPeriodEnd
88 ? `Renews ${new Date(sub.currentPeriodEnd).toLocaleDateString(undefined, { month: "long", day: "numeric", year: "numeric" })}`92 ? `Trial ends, then billing starts ${new Date(sub.currentPeriodEnd).toLocaleDateString(undefined, { month: "long", day: "numeric", year: "numeric" })}`
89 : tagline}93 : sub?.currentPeriodEnd
94 ? `Renews ${new Date(sub.currentPeriodEnd).toLocaleDateString(undefined, { month: "long", day: "numeric", year: "numeric" })}`
95 : tagline}
90 </p>96 </p>
91 </div>97 </div>
92 <div className="flex items-center gap-2">98 <div className="flex items-center gap-2">
apps/cloud/src/routes/app/billing_.plans.tsx+26−1
@@ -66,6 +66,12 @@ const ACTION_LABELS: Record<string, string> = { · 65 unmodified lines
66 66
67const PLAN_ORDER = ["free", "team", "enterprise"];67const PLAN_ORDER = ["free", "team", "enterprise"];
68 68
69// "14-day free trial", "1-month free trial", etc. — reads the trial config
70// synced from the repo-root autumn.config.ts so copy tracks the plan. The unit
71// stays singular because it reads as a compound adjective ("14-day", "2-month").
72const trialLabel = (freeTrial: NonNullable<Plan["freeTrial"]>): string =>
73 `${freeTrial.durationLength}-${freeTrial.durationType} free trial`;
74
69function PlansPage() {75function PlansPage() {
70 const { attach, openCustomerPortal, isLoading: customerLoading } = useCustomer();76 const { attach, openCustomerPortal, isLoading: customerLoading } = useCustomer();
71 const { data: plans, isLoading: plansLoading, isFetching } = useListPlans();77 const { data: plans, isLoading: plansLoading, isFetching } = useListPlans();
@@ -127,9 +133,20 @@ function PlansPage() { · 55 unmodified lines
127 const isCanceling = eligibility?.canceling ?? false;133 const isCanceling = eligibility?.canceling ?? false;
128 const isCurrent = status === "active" && !isCanceling;134 const isCurrent = status === "active" && !isCanceling;
129 const isScheduled = status === "scheduled";135 const isScheduled = status === "scheduled";
130 const label = isCanceling ? "Resume" : (ACTION_LABELS[action] ?? "Select");
131 const isUpgradeAction = action === "upgrade" || action === "activate";136 const isUpgradeAction = action === "upgrade" || action === "activate";
132 const isEnterprise = plan.id === "enterprise";137 const isEnterprise = plan.id === "enterprise";
138 // Offer the trial only when the plan defines one and this customer
139 // is still eligible (trialAvailable is false once they've used it).
140 const freeTrial = plan.freeTrial;
141 const trialOffered =
142 freeTrial != null &&
143 eligibility?.trialAvailable !== false &&
144 (action === "activate" || action === "upgrade");
145 const label = isCanceling
146 ? "Resume"
147 : trialOffered
148 ? "Start free trial"
149 : (ACTION_LABELS[action] ?? "Select");
133 150
134 return (151 return (
135 <div152 <div
@@ -179,6 +196,14 @@ function PlansPage() { · 43 unmodified lines
179 )}196 )}
180 </div>197 </div>
181 198
199 {trialOffered && freeTrial && (
200 <p className="mt-2 text-xs font-medium text-primary">
201 {trialLabel(freeTrial)}
202 {plan.price?.amount != null &&
203 `, then $${plan.price.amount} / ${plan.price.interval ?? "month"}`}
204 </p>
205 )}
206
182 <div className="mt-4">207 <div className="mt-4">
183 {(isCurrent && !isCanceling) || isScheduled ? (208 {(isCurrent && !isCanceling) || isScheduled ? (
184 <div className="flex h-9 items-center justify-center rounded-md border border-border bg-muted/30 text-sm font-medium text-muted-foreground">209 <div className="flex h-9 items-center justify-center rounded-md border border-border bg-muted/30 text-sm font-medium text-muted-foreground">

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t