import {
PlanPicker,
type BillingInterval,
type PlanQuote,
} from "@/components/ui/subscription-plan-picker";
/* ==========================================================================
Northwind Studio is on Team — $89 a month, 13 people, 17 days left in the
period that renews on 14 Oct.
The picker opens on Scale, with the charge printed under the button:
($249 − $89) × 17 days ÷ 30 = $90.67 today, then $249 on 14 Oct. Pick
Starter and it refuses — the team is 13 people and Starter holds 5. Switch
to Yearly and it shows the year up front, less the unused part of this
month.
In your app, `quote` asks your billing backend. The picker never does the
proration itself; your processor owns that arithmetic.
========================================================================== */
const PLANS = [
{
id: "starter",
name: "Starter",
blurb: "For a small team finding its feet",
monthly: 2900,
yearly: 29000,
seats: 5,
features: ["5 seats included", "100k API calls a month", "Email support"],
},
{
id: "team",
name: "Team",
blurb: "For a studio with clients",
monthly: 8900,
yearly: 89000,
seats: 10,
badge: "Most teams pick this",
features: [
"10 seats included, $12 each after",
"500k API calls a month",
"Roles and permissions",
"Access reviews",
"Priority support",
],
},
{
id: "scale",
name: "Scale",
blurb: "For several teams under one roof",
monthly: 24900,
yearly: 249000,
seats: 40,
features: [
"40 seats included, $9 each after",
"2M API calls a month",
"SAML single sign-on",
"Audit log export",
"A named contact",
],
},
];
const CURRENT = "team";
const SEATS_USED = 13;
const DAYS_LEFT = 17;
const DAYS_IN_PERIOD = 30;
const RENEWAL = "14 Oct";
/** What your billing backend would answer. Integers in cents throughout. */
function quote(planId: string, interval: BillingInterval): PlanQuote {
const current = PLANS.find((p) => p.id === CURRENT)!;
const target = PLANS.find((p) => p.id === planId)!;
if (planId === CURRENT && interval === "month") return { kind: "current" };
if (target.seats < SEATS_USED) {
return {
kind: "blocked",
reason: `Your team uses ${SEATS_USED} seats. ${target.name} includes ${target.seats} — remove ${
SEATS_USED - target.seats
} people first, or stay on ${current.name}.`,
};
}
if (interval === "month") {
const diff = Math.round(((target.monthly - current.monthly) * DAYS_LEFT) / DAYS_IN_PERIOD);
return diff >= 0
? { kind: "charge", today: diff, next: target.monthly, nextDate: RENEWAL }
: { kind: "credit", credit: -diff, next: target.monthly, nextDate: RENEWAL };
}
const unused = Math.round((current.monthly * DAYS_LEFT) / DAYS_IN_PERIOD);
return { kind: "charge", today: target.yearly - unused, next: target.yearly, nextDate: "27 Sep 2027" };
}
export default function PlanPickerDemo() {
return (
<div className="flex min-h-screen w-full items-center justify-center bg-background p-8">
<div className="w-full max-w-[440px]">
<PlanPicker
plans={PLANS}
currentPlanId={CURRENT}
defaultSelected="scale"
quote={quote}
onConfirm={(id, interval) => {
/* Hand off to your billing backend here. */
console.info("switch plan", id, interval);
}}
/>
</div>
</div>
);
}