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"