From b2f7cbf8b70e7139ce802fde82aa27e7a059dd70 Mon Sep 17 00:00:00 2001 From: yousefed Date: Fri, 27 Mar 2026 11:50:37 +0100 Subject: [PATCH] docs: add yearly pricing --- docs/app/pricing/PricingTiers.tsx | 59 +++++++++++++++++++++++++++++++ docs/app/pricing/page.tsx | 9 ++--- docs/app/pricing/tiers.tsx | 55 ++++++++++++++++++++++------ docs/lib/auth.ts | 4 +++ docs/lib/product-list.ts | 8 +++++ 5 files changed, 120 insertions(+), 15 deletions(-) create mode 100644 docs/app/pricing/PricingTiers.tsx diff --git a/docs/app/pricing/PricingTiers.tsx b/docs/app/pricing/PricingTiers.tsx new file mode 100644 index 0000000000..0c7c1c9d48 --- /dev/null +++ b/docs/app/pricing/PricingTiers.tsx @@ -0,0 +1,59 @@ +"use client"; + +import { cn } from "@/lib/fumadocs/cn"; +import { useState } from "react"; +import { Tier, Tiers } from "./tiers"; + +type Frequency = "month" | "year"; + +export function PricingTiers({ tiers }: { tiers: Tier[] }) { + const [frequency, setFrequency] = useState("year"); + + return ( + <> + {/* Frequency Toggle */} +
+ + Monthly + + + + Yearly + + + Save 50% + +
+ + + + ); +} diff --git a/docs/app/pricing/page.tsx b/docs/app/pricing/page.tsx index 4664aa6d81..ae4e110756 100644 --- a/docs/app/pricing/page.tsx +++ b/docs/app/pricing/page.tsx @@ -1,5 +1,5 @@ import { FAQ } from "@/app/pricing/faq"; -import { Tier, Tiers } from "@/app/pricing/tiers"; +import { Tier } from "@/app/pricing/tiers"; import { InfiniteSlider } from "@/components/InfiniteSlider"; import { Tooltip, @@ -9,6 +9,7 @@ import { } from "@/components/ui/tooltip"; import { getFullMetadata } from "@/lib/getFullMetadata"; import Link from "next/link"; +import { PricingTiers } from "./PricingTiers"; export const metadata = getFullMetadata({ title: "Pricing", @@ -80,7 +81,7 @@ const tiers: Tier[] = [ badge: "Recommended", description: "Commercial license for access to advanced features and technical support.", - price: { month: 390, year: 48 }, + price: { month: 390, year: 2340 }, features: [ Commercial license for XL packages: @@ -162,8 +163,8 @@ export default function Pricing() {

- {/* Pricing Tiers */} - + {/* Pricing Tiers with Toggle */} + {/* Social proof */}
diff --git a/docs/app/pricing/tiers.tsx b/docs/app/pricing/tiers.tsx index 70702d76ec..e9a6013284 100644 --- a/docs/app/pricing/tiers.tsx +++ b/docs/app/pricing/tiers.tsx @@ -23,7 +23,13 @@ export type Tier = { cta?: "get-started" | "buy" | "contact"; }; -function TierCTAButton({ tier }: { tier: Tier }) { +const BUSINESS_PLAN_TYPES = new Set(["business", "business-yearly"]); + +function isBusinessPlan(planType: string) { + return BUSINESS_PLAN_TYPES.has(planType); +} + +function TierCTAButton({ tier, frequency }: { tier: Tier; frequency: Frequency }) { const { data: session } = useSession(); let text = tier.cta === "get-started" @@ -38,10 +44,11 @@ function TierCTAButton({ tier }: { tier: Tier }) { if (session.planType === "free") { text = "Buy now"; } else { - text = - session.planType === tier.id - ? "Manage subscription" - : "Update subscription"; + const isCurrentPlan = + tier.id === "business" + ? isBusinessPlan(session.planType ?? "") + : session.planType === tier.id; + text = isCurrentPlan ? "Manage subscription" : "Update subscription"; } } @@ -68,9 +75,6 @@ function TierCTAButton({ tier }: { tier: Tier }) { } track("Signup", { tier: tier.id }); - // ... rest of analytic logic kept simple for brevity in replacement, - // in real implementation we keep the existing logic. - // Re-injecting existing analytics logic below to ensure no regression. if (!session) { Sentry.captureEvent({ message: "click-pricing-signup", @@ -90,9 +94,16 @@ function TierCTAButton({ tier }: { tier: Tier }) { track("click-pricing-buy-now", { tier: tier.id }); e.preventDefault(); e.stopPropagation(); - await authClient.checkout({ slug: tier.id }); + const checkoutSlug = frequency === "year" && tier.id === "business" + ? "business-yearly" + : tier.id; + await authClient.checkout({ slug: checkoutSlug }); } else { - if (session.planType === tier.id) { + const isCurrentPlan = + tier.id === "business" + ? isBusinessPlan(session.planType ?? "") + : session.planType === tier.id; + if (isCurrentPlan) { Sentry.captureEvent({ message: "click-pricing-manage-subscription", level: "info", @@ -208,6 +219,28 @@ export function Tiers({ {tier.price} + ) : frequency === "year" ? ( +
+
+ + ${Math.round(tier.price.year / 12)} + + + /month + +
+
+ + ${tier.price.month}/mo + + + now -50% + +
+

+ ${tier.price.year.toLocaleString()} billed yearly +

+
) : (
@@ -227,7 +260,7 @@ export function Tiers({ {/* CTA */}
- +
{/* Features */} diff --git a/docs/lib/auth.ts b/docs/lib/auth.ts index cb5a519044..654ab8e9e9 100644 --- a/docs/lib/auth.ts +++ b/docs/lib/auth.ts @@ -207,6 +207,10 @@ export const auth = betterAuth({ productId: PRODUCTS.business.id, // ID of Product from Polar Dashboard slug: PRODUCTS.business.slug, // Custom slug for easy reference in Checkout URL, e.g. /checkout/pro }, + { + productId: PRODUCTS["business-yearly"].id, + slug: PRODUCTS["business-yearly"].slug, + }, { productId: PRODUCTS.starter.id, slug: PRODUCTS.starter.slug, diff --git a/docs/lib/product-list.ts b/docs/lib/product-list.ts index b0e29a553c..49e52033c7 100644 --- a/docs/lib/product-list.ts +++ b/docs/lib/product-list.ts @@ -7,6 +7,14 @@ export const PRODUCTS = { name: "Business", slug: "business", } as const, + "business-yearly": { + id: + process.env.NODE_ENV === "production" + ? "ba3965dc-e1ca-494e-b36a-62e2e41615d4" + : "NOT-CREATED", + name: "Business Yearly", + slug: "business-yearly", + } as const, starter: { id: process.env.NODE_ENV === "production"