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.
Unified · Split
| @@ -42,6 +42,7 @@ function BillingPage() { · 41 unmodified lines | ||
| 42 | 42 | ); |
| 43 | 43 | const isCanceling = activePlan?.customerEligibility?.canceling ?? false; |
| 44 | 44 | const isSwitching = isCanceling && scheduledPlan != null; |
| 45 | + const isTrialing = activePlan?.customerEligibility?.trialing ?? false; | |
| 45 | 46 | |
| 46 | 47 | const displayPlan = isSwitching ? scheduledPlan : activePlan; |
| 47 | 48 | const planId = displayPlan?.id ?? "free"; |
| @@ -78,15 +79,20 @@ function BillingPage() { · 30 unmodified lines | ||
| 78 | 79 | Canceling |
| 79 | 80 | </Badge> |
| 80 | 81 | )} |
| 82 | + {isTrialing && !isCanceling && ( | |
| 83 | + <Badge className="bg-primary/10 text-primary">Free trial</Badge> | |
| 84 | + )} | |
| 81 | 85 | </div> |
| 82 | 86 | <p className="mt-1 text-xs text-muted-foreground leading-none"> |
| 83 | 87 | {isSwitching && sub?.currentPeriodEnd |
| 84 | 88 | ? `Starts ${new Date(sub.currentPeriodEnd).toLocaleDateString(undefined, { month: "long", day: "numeric", year: "numeric" })}` |
| 85 | 89 | : isCanceling && sub?.currentPeriodEnd |
| 86 | 90 | ? `Access until ${new Date(sub.currentPeriodEnd).toLocaleDateString(undefined, { month: "long", day: "numeric", year: "numeric" })}` |
| 87 | − : sub?.currentPeriodEnd | |
| 88 | − ? `Renews ${new Date(sub.currentPeriodEnd).toLocaleDateString(undefined, { month: "long", day: "numeric", year: "numeric" })}` | |
| 89 | − : tagline} | |
| 91 | + : isTrialing && sub?.currentPeriodEnd | |
| 92 | + ? `Trial ends, then billing starts ${new Date(sub.currentPeriodEnd).toLocaleDateString(undefined, { month: "long", day: "numeric", year: "numeric" })}` | |
| 93 | + : sub?.currentPeriodEnd | |
| 94 | + ? `Renews ${new Date(sub.currentPeriodEnd).toLocaleDateString(undefined, { month: "long", day: "numeric", year: "numeric" })}` | |
| 95 | + : tagline} | |
| 90 | 96 | </p> |
| 91 | 97 | </div> |
| 92 | 98 | <div className="flex items-center gap-2"> |
| @@ -66,6 +66,12 @@ const ACTION_LABELS: Record<string, string> = { · 65 unmodified lines | ||
| 66 | 66 | |
| 67 | 67 | const 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"). | |
| 72 | +const trialLabel = (freeTrial: NonNullable<Plan["freeTrial"]>): string => | |
| 73 | + `${freeTrial.durationLength}-${freeTrial.durationType} free trial`; | |
| 74 | + | |
| 69 | 75 | function PlansPage() { |
| 70 | 76 | const { attach, openCustomerPortal, isLoading: customerLoading } = useCustomer(); |
| 71 | 77 | const { data: plans, isLoading: plansLoading, isFetching } = useListPlans(); |
| @@ -127,9 +133,20 @@ function PlansPage() { · 55 unmodified lines | ||
| 127 | 133 | const isCanceling = eligibility?.canceling ?? false; |
| 128 | 134 | const isCurrent = status === "active" && !isCanceling; |
| 129 | 135 | const isScheduled = status === "scheduled"; |
| 130 | − const label = isCanceling ? "Resume" : (ACTION_LABELS[action] ?? "Select"); | |
| 131 | 136 | const isUpgradeAction = action === "upgrade" || action === "activate"; |
| 132 | 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 | 151 | return ( |
| 135 | 152 | <div |
| @@ -179,6 +196,14 @@ function PlansPage() { · 43 unmodified lines | ||
| 179 | 196 | )} |
| 180 | 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 | 207 | <div className="mt-4"> |
| 183 | 208 | {(isCurrent && !isCanceling) || isScheduled ? ( |
| 184 | 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"> |
| @@ -663,9 +663,12 @@ Source (and the place to start if something breaks): https://github.com/RhysSull · 662 unmodified lines | ||
| 663 | 663 | > |
| 664 | 664 | <span class="text-[13.5px] text-ink-3">/ org / month</span> |
| 665 | 665 | </div> |
| 666 | − <a href="/cloud" class="btn-primary self-start mb-7">Start Team →</a> | |
| 666 | + <a href="/cloud" class="btn-primary self-start mb-7" | |
| 667 | + >Start free trial →</a | |
| 668 | + > | |
| 667 | 669 | <ul class="check-list"> |
| 668 | 670 | {[ |
| 671 | + "14-day free trial, then $150 / month", | |
| 669 | 672 | "Unlimited members", |
| 670 | 673 | "250,000 included executions per month", |
| 671 | 674 | "5 minute execution timeout", |
| @@ -67,6 +67,11 @@ export const team = plan({ · 66 unmodified lines | ||
| 67 | 67 | amount: 150, |
| 68 | 68 | interval: "month", |
| 69 | 69 | }, |
| 70 | + freeTrial: { | |
| 71 | + durationLength: 14, | |
| 72 | + durationType: "day", | |
| 73 | + cardRequired: true, | |
| 74 | + }, | |
| 70 | 75 | items: [ |
| 71 | 76 | item({ |
| 72 | 77 | featureId: executions.id, |