From 7dc3adacf8004577295ac11a3f96b1dea3adb5c3 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 8 Nov 2025 04:10:33 +0000 Subject: [PATCH] Build custom pricing table with modern design Replace default Autumn pricing table with custom implementation featuring gradient accents, responsive grid layout, annual/monthly toggle, and enhanced visual hierarchy for better user experience. --- src/app/(home)/pricing/page-content.tsx | 26 +- .../autumn/custom-pricing-table.tsx | 313 ++++++++++++++++++ 2 files changed, 328 insertions(+), 11 deletions(-) create mode 100644 src/components/autumn/custom-pricing-table.tsx diff --git a/src/app/(home)/pricing/page-content.tsx b/src/app/(home)/pricing/page-content.tsx index 83a9fb90..a89347a0 100644 --- a/src/app/(home)/pricing/page-content.tsx +++ b/src/app/(home)/pricing/page-content.tsx @@ -1,26 +1,30 @@ "use client"; import Image from "next/image"; -import PricingTable from "@/components/autumn/pricing-table"; +import CustomPricingTable from "@/components/autumn/custom-pricing-table"; export function PricingPageContent() { return ( -
-
-
+
+
+
ZapDev - AI Development Platform +
+

+ Simple, Transparent Pricing +

+

+ Choose the perfect plan for your development needs. Upgrade or downgrade at any time. +

+
-

Pricing

-

- Choose the plan that fits your needs -

- +
); diff --git a/src/components/autumn/custom-pricing-table.tsx b/src/components/autumn/custom-pricing-table.tsx new file mode 100644 index 00000000..7e77a82d --- /dev/null +++ b/src/components/autumn/custom-pricing-table.tsx @@ -0,0 +1,313 @@ +'use client'; + +import React, { useState } from "react"; +import { useCustomer, usePricingTable, type ProductDetails } from "autumn-js/react"; +import type { Product, ProductItem } from "autumn-js"; +import { cn } from "@/lib/utils"; +import { Button } from "@/components/ui/button"; +import { Switch } from "@/components/ui/switch"; +import { Badge } from "@/components/ui/badge"; +import CheckoutDialog from "@/components/autumn/checkout-dialog"; +import { getPricingTableContent } from "@/lib/autumn/pricing-table-content"; +import { Check, Loader2, Sparkles, Zap } from "lucide-react"; + +interface CustomPricingTableProps { + productDetails?: ProductDetails[]; + className?: string; +} + +export default function CustomPricingTable({ productDetails, className }: CustomPricingTableProps) { + const { customer, checkout } = useCustomer({ errorOnNotFound: false }); + const [isAnnual, setIsAnnual] = useState(false); + const { products, isLoading, error } = usePricingTable({ productDetails }); + + if (isLoading) { + return ( +
+
+ +

Loading pricing plans...

+
+
+ ); + } + + if (error) { + return ( +
+
+

Unable to load pricing plans

+

Please try again later

+
+
+ ); + } + + if (!products || products.length === 0) { + return null; + } + + // Check if we have multiple intervals (monthly/annual) + const intervalGroups = products + .map((p) => p.properties?.interval_group) + .filter((intervalGroup): intervalGroup is string => Boolean(intervalGroup)); + const intervals = Array.from(new Set(intervalGroups)); + const multiInterval = intervals.length > 1; + + // Filter products based on selected interval + const intervalFilter = (product: Product) => { + if (!product.properties?.interval_group) { + return true; + } + + if (multiInterval) { + if (isAnnual) { + return product.properties?.interval_group === "year"; + } else { + return product.properties?.interval_group === "month"; + } + } + + return true; + }; + + const filteredProducts = products.filter(intervalFilter); + const hasRecommended = filteredProducts.some((p) => p.display?.recommend_text); + + return ( +
+ {/* Annual/Monthly Toggle */} + {multiInterval && ( +
+
+ + +
+
+ )} + + {/* Pricing Cards Grid */} +
3 && "grid-cols-1 md:grid-cols-2 lg:grid-cols-4" + )}> + {filteredProducts.map((product, index) => ( + + ))} +
+
+ ); +} + +interface PricingCardProps { + product: Product; + customer: any; + checkout: any; + isRecommended: boolean; +} + +function PricingCard({ product, customer, checkout, isRecommended }: PricingCardProps) { + const [loading, setLoading] = useState(false); + const { buttonText } = getPricingTableContent(product); + + const handleClick = async () => { + if (loading) return; + + setLoading(true); + try { + if (product.id && customer) { + await checkout({ + productId: product.id, + dialog: CheckoutDialog, + }); + } else if (product.display?.button_url) { + window.open(product.display?.button_url, "_blank", "noopener,noreferrer"); + } + } catch (error) { + console.error("Checkout error:", error); + } finally { + setLoading(false); + } + }; + + const isDisabled = + loading || + (product.scenario === "active" && !product.properties.updateable) || + product.scenario === "scheduled"; + + // Get main price + const mainPriceDisplay = product.properties?.is_free + ? { + primary_text: "Free", + secondary_text: "", + } + : product.items?.[0]?.display ?? { + primary_text: "Custom", + secondary_text: "", + }; + + // Get features (skip first item if it's the price) + const featureItems = product.properties?.is_free + ? product.items ?? [] + : (product.items?.length ?? 0) > 1 + ? product.items.slice(1) + : []; + + // Determine if this is the current plan + const isCurrentPlan = product.scenario === "active"; + + return ( +
+ {/* Recommended Badge */} + {isRecommended && product.display?.recommend_text && ( +
+ + + {product.display.recommend_text} + +
+ )} + +
+ {/* Plan Name & Description */} +
+

+ {product.display?.name || product.name} +

+ {product.display?.description && ( +

+ {product.display.description} +

+ )} +
+ + {/* Price */} +
+
+ {mainPriceDisplay.primary_text && ( + + {mainPriceDisplay.primary_text} + + )} + {mainPriceDisplay.secondary_text && ( + + {mainPriceDisplay.secondary_text} + + )} +
+
+ + {/* Features List */} + {featureItems.length > 0 && ( +
+ {product.display?.everything_from && ( +

+ Everything from {product.display.everything_from}, plus: +

+ )} +
    + {featureItems.map((item, index) => ( +
  • +
    +
    + +
    +
    +
    +

    + {item.display?.primary_text} +

    + {item.display?.secondary_text && ( +

    + {item.display.secondary_text} +

    + )} +
    +
  • + ))} +
+
+ )} +
+ + {/* CTA Button */} +
+ + + {isCurrentPlan && ( +

+ Your current plan +

+ )} +
+
+ ); +}