diff --git a/backend/igny8_core/auth/admin.py b/backend/igny8_core/auth/admin.py
index 3d80f73c..f8082a97 100644
--- a/backend/igny8_core/auth/admin.py
+++ b/backend/igny8_core/auth/admin.py
@@ -111,14 +111,15 @@ class AccountAdminForm(forms.ModelForm):
@admin.register(Plan)
class PlanAdmin(admin.ModelAdmin):
"""Plan admin - Global, no account filtering needed"""
- list_display = ['name', 'slug', 'price', 'billing_cycle', 'max_sites', 'max_users', 'max_keywords', 'max_content_words', 'included_credits', 'is_active']
- list_filter = ['is_active', 'billing_cycle', 'is_internal']
+ list_display = ['name', 'slug', 'price', 'billing_cycle', 'max_sites', 'max_users', 'max_keywords', 'max_content_words', 'included_credits', 'is_active', 'is_featured']
+ list_filter = ['is_active', 'billing_cycle', 'is_internal', 'is_featured']
search_fields = ['name', 'slug']
readonly_fields = ['created_at']
fieldsets = (
('Plan Info', {
- 'fields': ('name', 'slug', 'price', 'billing_cycle', 'features', 'is_active', 'is_internal')
+ 'fields': ('name', 'slug', 'price', 'original_price', 'annual_discount_percent', 'billing_cycle', 'features', 'is_active', 'is_featured', 'is_internal'),
+ 'description': 'Price: Current price | Original Price: Crossed-out price (optional) | Annual Discount %: For annual billing | Is Featured: Show as popular/recommended plan'
}),
('Account Management Limits', {
'fields': ('max_users', 'max_sites', 'max_industries', 'max_author_profiles'),
diff --git a/backend/igny8_core/auth/migrations/0015_add_plan_original_price.py b/backend/igny8_core/auth/migrations/0015_add_plan_original_price.py
new file mode 100644
index 00000000..7f303812
--- /dev/null
+++ b/backend/igny8_core/auth/migrations/0015_add_plan_original_price.py
@@ -0,0 +1,24 @@
+# Generated manually
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('igny8_core_auth', '0014_add_usage_tracking_to_account'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='plan',
+ name='original_price',
+ field=models.DecimalField(
+ blank=True,
+ decimal_places=2,
+ help_text='Original price (before discount) - shows as crossed out price. Leave empty if no discount.',
+ max_digits=10,
+ null=True
+ ),
+ ),
+ ]
diff --git a/backend/igny8_core/auth/models.py b/backend/igny8_core/auth/models.py
index c9bae6dc..e70016b7 100644
--- a/backend/igny8_core/auth/models.py
+++ b/backend/igny8_core/auth/models.py
@@ -177,6 +177,13 @@ class Plan(models.Model):
name = models.CharField(max_length=255)
slug = models.SlugField(unique=True, max_length=255)
price = models.DecimalField(max_digits=10, decimal_places=2)
+ original_price = models.DecimalField(
+ max_digits=10,
+ decimal_places=2,
+ null=True,
+ blank=True,
+ help_text="Original price (before discount) - shows as crossed out price. Leave empty if no discount."
+ )
billing_cycle = models.CharField(max_length=20, choices=BILLING_CYCLE_CHOICES, default='monthly')
annual_discount_percent = models.DecimalField(
max_digits=5,
diff --git a/backend/igny8_core/auth/serializers.py b/backend/igny8_core/auth/serializers.py
index 31ba7b17..7dde2a6b 100644
--- a/backend/igny8_core/auth/serializers.py
+++ b/backend/igny8_core/auth/serializers.py
@@ -10,7 +10,7 @@ class PlanSerializer(serializers.ModelSerializer):
class Meta:
model = Plan
fields = [
- 'id', 'name', 'slug', 'price', 'billing_cycle', 'annual_discount_percent',
+ 'id', 'name', 'slug', 'price', 'original_price', 'billing_cycle', 'annual_discount_percent',
'is_featured', 'features', 'is_active',
'max_users', 'max_sites', 'max_industries', 'max_author_profiles',
'max_keywords', 'max_clusters',
diff --git a/frontend/src/components/ui/pricing-table/PricingTable.tsx b/frontend/src/components/ui/pricing-table/PricingTable.tsx
index 6c4b3dc6..ebeef4d0 100644
--- a/frontend/src/components/ui/pricing-table/PricingTable.tsx
+++ b/frontend/src/components/ui/pricing-table/PricingTable.tsx
@@ -142,7 +142,7 @@ export default function PricingTable({
d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
/>
- Save 15% with annual billing
+ Save {plans[0]?.annualDiscountPercent || 15}% with annual billing
)}
diff --git a/frontend/src/components/ui/pricing-table/index.tsx b/frontend/src/components/ui/pricing-table/index.tsx
index be846d3f..bd7258d3 100644
--- a/frontend/src/components/ui/pricing-table/index.tsx
+++ b/frontend/src/components/ui/pricing-table/index.tsx
@@ -5,20 +5,20 @@
import { useState } from 'react';
import { Check } from 'lucide-react';
-import Button from '../button/Button';
-import Badge from '../badge/Badge';
export interface PricingPlan {
id: number;
name: string;
monthlyPrice: number;
price: number;
+ originalPrice?: number;
period: string;
description: string;
features: string[];
buttonText: string;
highlighted?: boolean;
disabled?: boolean;
+ annualDiscountPercent?: number;
// Plan limits
max_sites?: number;
max_users?: number;
@@ -44,7 +44,8 @@ export function PricingTable({ variant = '1', title, plans, showToggle = false,
const getPrice = (plan: PricingPlan) => {
if (billingPeriod === 'annual') {
- return (plan.monthlyPrice * 12 * 0.8).toFixed(0); // 20% discount for annual
+ const discount = plan.annualDiscountPercent || 20;
+ return (plan.monthlyPrice * 12 * (100 - discount) / 100).toFixed(0);
}
return plan.monthlyPrice.toFixed(0);
};
@@ -63,30 +64,34 @@ export function PricingTable({ variant = '1', title, plans, showToggle = false,
{showToggle && (
-
-
-
+
+
+
+
+
+ {billingPeriod === 'annual' && (
+
+ Save {Math.round(plans[0]?.annualDiscountPercent || 20)}%
+
+ )}
)}
@@ -97,15 +102,15 @@ export function PricingTable({ variant = '1', title, plans, showToggle = false,
key={plan.id}
className={`relative rounded-lg border ${
plan.highlighted
- ? 'border-primary shadow-lg ring-2 ring-primary ring-opacity-50'
+ ? 'pricing-card-featured border-gray-700 shadow-lg ring-2 ring-gray-700'
: 'border-gray-200 dark:border-gray-700'
- } bg-white dark:bg-gray-800 p-6 flex flex-col`}
+ } ${plan.highlighted ? '' : 'bg-white dark:bg-gray-800'} p-6 flex flex-col`}
>
{plan.highlighted && (
-
+
Popular
-
+
)}
@@ -115,15 +120,20 @@ export function PricingTable({ variant = '1', title, plans, showToggle = false,
-
-
+
+
${getPrice(plan)}
- {getPeriod()}
+ {getPeriod()}
+ {plan.originalPrice && billingPeriod === 'monthly' && (
+
+ ${plan.originalPrice.toFixed(2)}
+
+ )}
{billingPeriod === 'annual' && plan.monthlyPrice > 0 && (
- Billed ${(plan.monthlyPrice * 12 * 0.8).toFixed(0)}/year
+ Billed ${(plan.monthlyPrice * 12 * (100 - (plan.annualDiscountPercent || 20)) / 100).toFixed(0)}/year
)}
@@ -192,14 +202,13 @@ export function PricingTable({ variant = '1', title, plans, showToggle = false,
)}
-
+
))}
diff --git a/frontend/src/components/ui/pricing-table/pricing-table-1.tsx b/frontend/src/components/ui/pricing-table/pricing-table-1.tsx
new file mode 100644
index 00000000..c22cea13
--- /dev/null
+++ b/frontend/src/components/ui/pricing-table/pricing-table-1.tsx
@@ -0,0 +1,25 @@
+import { PricingTable, PricingPlan } from "./index";
+
+interface PricingTable1Props {
+ plans: PricingPlan[];
+ title?: string;
+ showToggle?: boolean;
+ onPlanSelect?: (plan: PricingPlan) => void;
+}
+
+export default function PricingTable1({
+ plans,
+ title = "Flexible Plans Tailored to Fit Your Unique Needs!",
+ showToggle = true,
+ onPlanSelect
+}: PricingTable1Props) {
+ return (
+
+ );
+}
diff --git a/frontend/src/marketing/pages/Pricing.tsx b/frontend/src/marketing/pages/Pricing.tsx
index 835a803b..dbb99e50 100644
--- a/frontend/src/marketing/pages/Pricing.tsx
+++ b/frontend/src/marketing/pages/Pricing.tsx
@@ -10,7 +10,8 @@ import {
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 { 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 {
@@ -47,13 +48,25 @@ const convertToPricingPlan = (plan: Plan): PricingPlan => {
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_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,
name: plan.name,
@@ -61,9 +74,9 @@ const convertToPricingPlan = (plan: Plan): PricingPlan => {
price: monthlyPrice,
annualDiscountPercent: plan.annual_discount_percent || 15,
period: '/month',
- description: `Perfect for ${plan.name.toLowerCase()} needs`,
+ description: description,
features,
- buttonText: monthlyPrice === 0 ? 'Start Free' : 'Choose Plan',
+ buttonText: monthlyPrice === 0 ? 'Free Trial' : 'Choose Plan',
highlighted: plan.is_featured || false,
};
};
@@ -190,21 +203,148 @@ const Pricing: React.FC = () => {
return (
<>
+
{/* PRICING HERO SECTION */}
-
+
Pricing
-
+
Simple plans that scale with your automation goals.
-
- Flexible pricing for teams of all sizes. No seat limits. No hidden charges. Built for growth.
-
@@ -232,27 +372,25 @@ const Pricing: React.FC = () => {
)}
- {/* PRICING TIERS SECTION */}
- {!loading && !error && (
-
- {
- const plan = plans.find(p => p.id === pricingPlan.id);
- if (plan) {
- window.location.href = `https://app.igny8.com/signup?plan=${plan.slug}`;
- }
- }}
- />
-
+ {/* PRICING TABLES SECTION - Dynamic Backend Plans */}
+ {!loading && !error && plans.length > 0 && (
+
+
+
{
+ window.location.href = `/signup?plan=${plan.id}`;
+ }}
+ />
+
+
)}
{/* COMPARISON TABLE SECTION - Keep hardcoded for now */}
{!loading && !error && (
-
+
Compare plan capabilities
diff --git a/frontend/src/pages/Settings/Plans.tsx b/frontend/src/pages/Settings/Plans.tsx
index 74398316..7e26395e 100644
--- a/frontend/src/pages/Settings/Plans.tsx
+++ b/frontend/src/pages/Settings/Plans.tsx
@@ -2,7 +2,8 @@ import { useState, useEffect } from 'react';
import PageMeta from '../../components/common/PageMeta';
import { useToast } from '../../components/ui/toast/ToastContainer';
import { fetchAPI } from '../../services/api';
-import { PricingTable, PricingPlan } from '../../components/ui/pricing-table';
+import { PricingPlan } from '../../components/ui/pricing-table';
+import PricingTable1 from '../../components/ui/pricing-table/pricing-table-1';
interface Plan {
id: number;
@@ -86,7 +87,7 @@ const transformPlanToPricingPlan = (plan: Plan, index: number, totalPlans: numbe
period: '/month',
description: getPlanDescription(plan),
features: extractFeatures(plan),
- buttonText: monthlyPrice === 0 ? 'Start Free' : 'Select Plan',
+ buttonText: monthlyPrice === 0 ? 'Free Trial' : 'Choose Plan',
highlighted: highlighted,
};
};
@@ -94,14 +95,16 @@ const transformPlanToPricingPlan = (plan: Plan, index: number, totalPlans: numbe
// Get plan description based on plan name or features
const getPlanDescription = (plan: Plan): string => {
const slug = plan.slug.toLowerCase();
- if (slug.includes('free')) {
- return 'Perfect for getting started';
- } else if (slug.includes('starter')) {
- return 'For solo designers & freelancers';
- } else if (slug.includes('growth')) {
- return 'For growing businesses';
- } else if (slug.includes('scale') || slug.includes('enterprise')) {
- return 'For teams and large organizations';
+ const name = plan.name.toLowerCase();
+
+ if (slug.includes('free') || name.includes('free')) {
+ return 'Explore core features risk free';
+ } else if (slug.includes('starter') || name.includes('starter')) {
+ return 'Launch SEO workflows for small teams';
+ } else if (slug.includes('growth') || name.includes('growth')) {
+ return 'Scale content production with confidence';
+ } else if (slug.includes('scale') || slug.includes('enterprise') || name.includes('scale') || name.includes('enterprise')) {
+ return 'Enterprise power for high volume growth';
}
return 'Choose the perfect plan for your needs';
};
@@ -165,8 +168,7 @@ export default function Plans() {
) : (
<>
- (
@@ -21,9 +44,95 @@ const StarIcon = () => (
);
+const formatNumber = (num: number | undefined | null): string => {
+ if (!num || num === 0) return '0';
+ if (num >= 1000000) return `${(num / 1000000).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`);
+
+ // 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,
+ 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,
+ period: '/month',
+ description: description,
+ features,
+ buttonText: monthlyPrice === 0 ? 'Free Trial' : 'Choose Plan',
+ highlighted: plan.is_featured || false,
+ annualDiscountPercent: plan.annual_discount_percent || 15,
+ };
+};
+
export default function PricingTablePage() {
+ const [backendPlans, setBackendPlans] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+
+ useEffect(() => {
+ const fetchPlans = async () => {
+ try {
+ const data = await getPublicPlans();
+ setBackendPlans(data);
+ setLoading(false);
+ } catch (err) {
+ console.error('Error fetching plans:', err);
+ setError('Failed to load plans');
+ setLoading(false);
+ }
+ };
+ fetchPlans();
+ }, []);
+
// Sample plans for variant 1
const plans1: PricingPlan[] = [
+ {
+ id: 0,
+ name: 'Free Plan',
+ price: 0.00,
+ period: '/month',
+ description: 'Perfect for free plan needs',
+ features: [
+ '1 Site',
+ '1 Team User',
+ '1K Monthly Credits',
+ '100K Words/Month',
+ '100 AI Keyword Clusters',
+ '300 Content Ideas',
+ '300 Basic / 60 Premium Images',
+ '300 Image Prompts',
+ ],
+ buttonText: 'Start Free',
+ },
{
id: 1,
name: 'Starter',
@@ -205,10 +314,29 @@ export default function PricingTablePage() {
description="This is React.js Pricing Tables page for TailAdmin - React.js Tailwind CSS Admin Dashboard Template"
/>
+
+ {loading && (
+
+
+
Loading backend plans...
+
+ )}
+ {error && (
+
+ )}
+ {!loading && !error && backendPlans.length > 0 && (
+ console.log('Selected backend plan:', plan)}
+ />
+ )}
+
+
- console.log('Selected plan:', plan)}
diff --git a/frontend/src/pages/account/PlansAndBillingPage.tsx b/frontend/src/pages/account/PlansAndBillingPage.tsx
index e0ead5c5..39dd9493 100644
--- a/frontend/src/pages/account/PlansAndBillingPage.tsx
+++ b/frontend/src/pages/account/PlansAndBillingPage.tsx
@@ -12,7 +12,8 @@ import { Card } from '../../components/ui/card';
import Badge from '../../components/ui/badge/Badge';
import Button from '../../components/ui/button/Button';
import { useToast } from '../../components/ui/toast/ToastContainer';
-import { PricingTable } from '../../components/ui/pricing-table';
+import { PricingPlan } from '../../components/ui/pricing-table';
+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';
@@ -716,9 +717,9 @@ export default function PlansAndBillingPage() {
Select the plan that best fits your needs
-
-
+ {
// Only show paid plans (exclude Free Plan)
@@ -728,29 +729,42 @@ export default function PlansAndBillingPage() {
})
.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: `/${plan.interval || 'month'}`,
- description: plan.description || 'Standard plan',
- features: plan.features && plan.features.length > 0
- ? plan.features
- : ['Monthly credits included', 'Module access', 'Email support'],
- buttonText: plan.id === currentPlanId ? 'Current Plan' : 'Select Plan',
+ 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,
- max_sites: plan.max_sites,
- max_users: plan.max_users,
- max_keywords: plan.max_keywords,
- max_clusters: plan.max_clusters,
- max_content_ideas: plan.max_content_ideas,
- max_content_words: plan.max_content_words,
- max_images_basic: plan.max_images_basic,
- max_images_premium: plan.max_images_premium,
- included_credits: plan.included_credits,
};
})}
showToggle={true}
diff --git a/frontend/src/services/billing.api.ts b/frontend/src/services/billing.api.ts
index 21dc8754..72995da8 100644
--- a/frontend/src/services/billing.api.ts
+++ b/frontend/src/services/billing.api.ts
@@ -856,6 +856,7 @@ export interface Plan {
name: string;
slug?: string;
price?: number | string;
+ original_price?: number;
currency?: string;
interval?: 'month' | 'year';
description?: string;