+
+
+
+
+
+ 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
+
+ )}
+
+
+ );
+}