+
+
+ {billingPeriod === 'annually' && (
+
+
+
+ Save 15% with annual billing
+
+
+ )}
)}
@@ -218,7 +237,7 @@ export default function PricingTable({
: 'bg-gray-800 hover:bg-brand-500 dark:bg-white/10 dark:hover:bg-brand-600'
} ${plan.disabled ? 'opacity-50 cursor-not-allowed' : ''}`}
>
- {plan.buttonText || 'Choose Plan'}
+ {plan.buttonText || (plan.price === 0 || plan.monthlyPrice === 0 ? 'Start Free' : 'Choose Plan')}
);
diff --git a/frontend/src/marketing/pages/Pricing.tsx b/frontend/src/marketing/pages/Pricing.tsx
index 272dfdd4..835a803b 100644
--- a/frontend/src/marketing/pages/Pricing.tsx
+++ b/frontend/src/marketing/pages/Pricing.tsx
@@ -1,108 +1,92 @@
-import React from "react";
-import { Link } from "react-router-dom";
+import React, { useState, useEffect } from "react";
import {
RocketLaunchIcon,
ChatBubbleLeftRightIcon,
CheckIcon,
XMarkIcon,
- SparklesIcon,
- PhotoIcon,
- BoltIcon,
- ChartBarIcon,
- UserGroupIcon,
CreditCardIcon,
ShieldCheckIcon,
} from "@heroicons/react/24/outline";
import SEO from "../components/SEO";
import { getMetaTags } from "../config/metaTags";
+import { getPublicPlans } from "../../services/billing.api";
+import PricingTable, { PricingPlan } from "../../components/ui/pricing-table/PricingTable";
+import { Link } from "react-router-dom";
+
+interface Plan {
+ id: number;
+ name: string;
+ slug?: string;
+ price: number | string;
+ annual_discount_percent?: number;
+ is_featured?: boolean;
+ max_sites?: number;
+ max_users?: number;
+ max_keywords?: number;
+ max_clusters?: number;
+ max_content_ideas?: number;
+ max_content_words?: number;
+ max_images_basic?: number;
+ max_images_premium?: number;
+ max_image_prompts?: number;
+ included_credits?: number;
+}
+
+const formatNumber = (num: number | undefined | null): string => {
+ if (!num || num === 0) return '0';
+ if (num >= 1000000) return `${(num / 1000).toFixed(0)}M`;
+ if (num >= 1000) return `${(num / 1000).toFixed(0)}K`;
+ return num.toString();
+};
+
+const convertToPricingPlan = (plan: Plan): PricingPlan => {
+ const monthlyPrice = typeof plan.price === 'number' ? plan.price : parseFloat(String(plan.price || 0));
+ const features: string[] = [];
+
+ if (plan.max_sites) features.push(`${plan.max_sites === 999999 ? 'Unlimited' : plan.max_sites} Site${plan.max_sites > 1 ? 's' : ''}`);
+ if (plan.max_users) features.push(`${plan.max_users} Team User${plan.max_users > 1 ? 's' : ''}`);
+ if (plan.included_credits) features.push(`${formatNumber(plan.included_credits)} Monthly Credits`);
+ if (plan.max_content_words) features.push(`${formatNumber(plan.max_content_words)} Words/Month`);
+ if (plan.max_clusters) features.push(`${plan.max_clusters} AI Keyword Clusters`);
+ if (plan.max_content_ideas) features.push(`${formatNumber(plan.max_content_ideas)} Content Ideas`);
+ if (plan.max_images_basic && plan.max_images_premium) {
+ features.push(`${formatNumber(plan.max_images_basic)} Basic / ${formatNumber(plan.max_images_premium)} Premium Images`);
+ }
+ if (plan.max_image_prompts) features.push(`${formatNumber(plan.max_image_prompts)} Image Prompts`);
+
+ return {
+ id: plan.id,
+ name: plan.name,
+ monthlyPrice: monthlyPrice,
+ price: monthlyPrice,
+ annualDiscountPercent: plan.annual_discount_percent || 15,
+ period: '/month',
+ description: `Perfect for ${plan.name.toLowerCase()} needs`,
+ features,
+ buttonText: monthlyPrice === 0 ? 'Start Free' : 'Choose Plan',
+ highlighted: plan.is_featured || false,
+ };
+};
const Pricing: React.FC = () => {
- const renderCta = (cta: { label: string; href: string }, className: string) => {
- const isExternal = cta.href.startsWith("http");
-
- if (isExternal) {
- return (
-
- {cta.label}
-
- );
- }
-
- return (
-
- {cta.label}
-
- );
- };
+ const [plans, setPlans] = useState
([]);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
- const tiers = [
- {
- name: "Starter",
- slug: "starter",
- price: "$99",
- cadence: "per month",
- description: "For small teams starting workflows.",
- icon: SparklesIcon,
- iconColor: "from-[var(--color-primary)] to-[var(--color-primary-dark)]",
- features: [
- "2 sites",
- "1 team user",
- "1,000 monthly credits",
- "100K words content generation",
- "100 AI keyword clusters",
- "300 content ideas",
- "300 basic / 60 premium images",
- "300 image prompts",
- ],
- badge: "For startups",
- },
- {
- name: "Growth",
- slug: "growth",
- price: "$199",
- cadence: "per month",
- description: "For teams automating multiple workflows.",
- icon: BoltIcon,
- iconColor: "from-[var(--color-success)] to-[var(--color-success-dark)]",
- features: [
- "5 sites",
- "3 team users",
- "3,000 monthly credits",
- "300K words content generation",
- "300 AI keyword clusters",
- "900 content ideas",
- "900 basic / 180 premium images",
- "900 image prompts",
- ],
- featured: true,
- badge: "Best value",
- },
- {
- name: "Scale",
- slug: "scale",
- price: "$299",
- cadence: "per month",
- description: "For publishers and large orgs needing deeper control.",
- icon: ChartBarIcon,
- iconColor: "from-[var(--color-purple)] to-[var(--color-purple-dark)]",
- features: [
- "Unlimited sites",
- "5 team users",
- "5,000 monthly credits",
- "500K words content generation",
- "500 AI keyword clusters",
- "1,500 content ideas",
- "1,500 basic / 300 premium images",
- "1,500 image prompts",
- ],
- badge: "For scaling teams",
- },
- ];
+ useEffect(() => {
+ const fetchPlans = async () => {
+ try {
+ const data = await getPublicPlans();
+ setPlans(data);
+ setLoading(false);
+ } catch (err) {
+ console.error('Error fetching public plans:', err);
+ setError('Failed to load pricing plans');
+ setLoading(false);
+ }
+ };
+ fetchPlans();
+ }, []);
const featureMatrix = [
{ feature: "ACCOUNT & ACCESS", starter: null, growth: null, scale: null, isCategory: true },
@@ -224,95 +208,50 @@ const Pricing: React.FC = () => {