diff --git a/frontend/src/components/ui/pricing-table/PricingTable.tsx b/frontend/src/components/ui/pricing-table/PricingTable.tsx index 6ed59bb6..d5b99da6 100644 --- a/frontend/src/components/ui/pricing-table/PricingTable.tsx +++ b/frontend/src/components/ui/pricing-table/PricingTable.tsx @@ -212,6 +212,8 @@ export default function PricingTable({ {plan.features.map((feature, idx) => { const isExcluded = feature.startsWith('!'); const featureText = isExcluded ? feature.substring(1) : feature; + const isBold = featureText.startsWith('**') && featureText.endsWith('**'); + const displayText = isBold ? featureText.slice(2, -2) : featureText; return (
  • {isExcluded ? : } - {featureText} + {displayText}
  • ); })} diff --git a/frontend/src/marketing/pages/Pricing.tsx b/frontend/src/marketing/pages/Pricing.tsx index 88d1c7fe..ebceb16e 100644 --- a/frontend/src/marketing/pages/Pricing.tsx +++ b/frontend/src/marketing/pages/Pricing.tsx @@ -13,76 +13,7 @@ import { getPublicPlans } from "../../services/billing.api"; import { PricingTable, PricingPlan } from "../../components/ui/pricing-table"; import PricingTable1 from "../../components/ui/pricing-table/pricing-table-1"; import { Link } from "react-router-dom"; - -interface Plan { - id: number; - name: string; - slug?: string; - price: number | string; - original_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 === 999 ? '500' : 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`); - - // Custom descriptions based on plan name - let description = `Perfect for ${plan.name.toLowerCase()} needs`; - if (plan.name.toLowerCase().includes('free')) { - description = 'Explore core features risk free'; - } else if (plan.name.toLowerCase().includes('starter')) { - description = 'Launch SEO workflows for small teams'; - } else if (plan.name.toLowerCase().includes('growth')) { - description = 'Scale content production with confidence'; - } else if (plan.name.toLowerCase().includes('scale')) { - description = 'Enterprise power for high volume growth'; - } - - return { - id: plan.id, - slug: plan.slug, - name: plan.name, - monthlyPrice: monthlyPrice, - price: monthlyPrice, - originalPrice: plan.original_price ? (typeof plan.original_price === 'number' ? plan.original_price : parseFloat(String(plan.original_price))) : undefined, - annualDiscountPercent: plan.annual_discount_percent || 15, - period: '/month', - description: description, - features, - buttonText: monthlyPrice === 0 ? 'Free Trial' : 'Choose Plan', - highlighted: plan.is_featured || false, - }; -}; +import { Plan, convertToPricingPlan } from "../../utils/pricingHelpers"; const Pricing: React.FC = () => { const [plans, setPlans] = useState([]); @@ -341,14 +272,44 @@ const Pricing: React.FC = () => {
    -
    +
    Pricing -

    +

    Simple plans that scale with your automation goals.

    + + {/* SPECIAL PROMO BANNER */} +
    +
    +
    + {/* Fire Icon */} + + + + + {/* Message Text */} +
    + + + + + Limited Time + +

    + Lifetime Discount on all Plans for First 100 Users! +

    +
    + + {/* Fire Icon */} + + + +
    +
    +
    {/* LOADING/ERROR STATES */} @@ -377,7 +338,7 @@ const Pricing: React.FC = () => { {/* PRICING TABLES SECTION - Dynamic Backend Plans */} {!loading && !error && plans.length > 0 && ( -
    +
    { }} />
    + + {/* What's Included Explanation */} +
    +

    + Every content piece includes High-Opportunity Keywords, AI clustering, idea generation, standard/premium images, Automation, WP publishing, SEO optimization, and internal linking. +

    +
    )} - {/* COMPARISON TABLE SECTION - Keep hardcoded for now */} - {!loading && !error && ( + {/* COMPARISON TABLE SECTION REMOVED */} + {!loading && !error && false && (

    Compare plan capabilities diff --git a/frontend/src/pages/account/PlansAndBillingPage.tsx b/frontend/src/pages/account/PlansAndBillingPage.tsx index 4fa4c145..619e8363 100644 --- a/frontend/src/pages/account/PlansAndBillingPage.tsx +++ b/frontend/src/pages/account/PlansAndBillingPage.tsx @@ -17,6 +17,7 @@ import PricingTable1 from '../../components/ui/pricing-table/pricing-table-1'; import CreditCostBreakdownPanel from '../../components/billing/CreditCostBreakdownPanel'; import CreditCostsPanel from '../../components/billing/CreditCostsPanel'; import UsageLimitsPanel from '../../components/billing/UsageLimitsPanel'; +import { convertToPricingPlan } from '../../utils/pricingHelpers'; import { getCreditBalance, getCreditPackages, @@ -727,46 +728,11 @@ export default function PlansAndBillingPage() { const planPrice = plan.price || 0; return planPrice > 0 && !planName.includes('free'); }) - .map(plan => { - const discount = plan.annual_discount_percent || 15; - - // Get custom description based on plan name - let description = 'Standard plan'; - const planName = plan.name.toLowerCase(); - if (planName.includes('starter')) { - description = 'Launch SEO workflows for small teams'; - } else if (planName.includes('growth')) { - description = 'Scale content production with confidence'; - } else if (planName.includes('scale')) { - description = 'Enterprise power for high volume growth'; - } - - // Build features array - 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(`${(plan.included_credits / 1000).toFixed(0)}K Monthly Credits`); - if (plan.max_content_words) features.push(`${(plan.max_content_words / 1000).toFixed(0)}K Words/Month`); - if (plan.max_clusters) features.push(`${plan.max_clusters} AI Keyword Clusters`); - if (plan.max_content_ideas) features.push(`${plan.max_content_ideas} Content Ideas`); - if (plan.max_images_basic && plan.max_images_premium) { - features.push(`${plan.max_images_basic} Basic / ${plan.max_images_premium} Premium Images`); - } - - return { - id: plan.id, - name: plan.name, - monthlyPrice: plan.price || 0, - price: plan.price || 0, - annualDiscountPercent: discount, - period: '/month', - description: description, - features: features.length > 0 ? features : ['Monthly credits included', 'Module access', 'Email support'], - buttonText: plan.id === currentPlanId ? 'Current Plan' : 'Choose Plan', - highlighted: plan.is_featured || false, - disabled: plan.id === currentPlanId || planLoadingId === plan.id, - }; - })} + .map(plan => ({ + ...convertToPricingPlan(plan), + buttonText: plan.id === currentPlanId ? 'Current Plan' : 'Choose Plan', + disabled: plan.id === currentPlanId || planLoadingId === plan.id, + }))} showToggle={true} onPlanSelect={(plan) => plan.id && handleSelectPlan(plan.id)} /> diff --git a/frontend/src/utils/pricingHelpers.ts b/frontend/src/utils/pricingHelpers.ts new file mode 100644 index 00000000..7bc04a62 --- /dev/null +++ b/frontend/src/utils/pricingHelpers.ts @@ -0,0 +1,93 @@ +/** + * Shared pricing utilities for converting backend Plan data to PricingPlan format + * Used by both marketing pricing page and app's plans page + */ + +import { PricingPlan } from '../components/ui/pricing-table'; + +export interface Plan { + id: number; + name: string; + slug?: string; + price: number | string; + original_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; +} + +export 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(); +}; + +export const convertToPricingPlan = (plan: Plan): PricingPlan => { + const monthlyPrice = typeof plan.price === 'number' ? plan.price : parseFloat(String(plan.price || 0)); + const features: string[] = []; + + // Dynamic counts - shown with numbers from backend + if (plan.max_content_ideas) { + features.push(`**${formatNumber(plan.max_content_ideas)} Pages/Articles per month**`); + } + if (plan.max_sites) { + features.push(`${plan.max_sites === 999999 ? 'Unlimited' : formatNumber(plan.max_sites)} Site${plan.max_sites > 1 && plan.max_sites !== 999999 ? 's' : ''}`); + } + if (plan.max_users) { + features.push(`${formatNumber(plan.max_users)} Team User${plan.max_users > 1 ? 's' : ''}`); + } + + // Features - shown with checkmarks only (no counts) + features.push('AI Keyword Clustering'); + features.push('AI Content Ideas'); + features.push('AI Image Generation'); + features.push('Internal Linking'); + features.push('SEO Optimization'); + features.push('WordPress Publishing'); + features.push('Pre-Researched High-Opportunity Keywords'); + + // Priority Support - only for Scale plan (excluded for others) + const planName = plan.name.toLowerCase(); + if (planName.includes('scale')) { + features.push('Priority Support'); + } else { + features.push('!Priority Support'); + } + + // Custom descriptions based on plan name + let description = `Perfect for ${plan.name.toLowerCase()} needs`; + if (plan.name.toLowerCase().includes('free')) { + description = 'Explore core features risk free'; + } else if (plan.name.toLowerCase().includes('starter')) { + description = 'Launch SEO workflows for small teams'; + } else if (plan.name.toLowerCase().includes('growth')) { + description = 'Scale content production with confidence'; + } else if (plan.name.toLowerCase().includes('scale')) { + description = 'Enterprise power for high volume growth'; + } + + return { + id: plan.id, + slug: plan.slug, + name: plan.name, + monthlyPrice: monthlyPrice, + price: monthlyPrice, + originalPrice: plan.original_price ? (typeof plan.original_price === 'number' ? plan.original_price : parseFloat(String(plan.original_price))) : undefined, + annualDiscountPercent: plan.annual_discount_percent || 15, + period: '/month', + description: description, + features, + buttonText: monthlyPrice === 0 ? 'Free Trial' : 'Choose Plan', + highlighted: plan.is_featured || false, + }; +};