Files
smoothschedule/frontend/src/pages/marketing/PricingPage.tsx
poduck b384d9912a Add TenantCustomTier system and fix BusinessEditModal feature loading
Backend:
- Add TenantCustomTier model for per-tenant feature overrides
- Update EntitlementService to check custom tier before plan features
- Add custom_tier action on TenantViewSet (GET/PUT/DELETE)
- Add Celery task for grace period management (30-day expiry)

Frontend:
- Add DynamicFeaturesEditor component for dynamic feature management
- Fix BusinessEditModal to load features from plan defaults when no custom tier
- Update limits (max_users, max_resources, etc.) to use featureValues
- Remove outdated canonical feature check from FeaturePicker (removes warning icons)
- Add useBillingPlans hook for accessing billing system data
- Add custom tier API functions to platform.ts

Features now follow consistent rules:
- Load from plan defaults when no custom tier exists
- Load from custom tier when one exists
- Reset to plan defaults when plan changes
- Save to custom tier on edit

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-12 21:00:54 -05:00

78 lines
2.8 KiB
TypeScript

import React from 'react';
import { useTranslation } from 'react-i18next';
import DynamicPricingCards from '../../components/marketing/DynamicPricingCards';
import FeatureComparisonTable from '../../components/marketing/FeatureComparisonTable';
import FAQAccordion from '../../components/marketing/FAQAccordion';
import CTASection from '../../components/marketing/CTASection';
const PricingPage: React.FC = () => {
const { t } = useTranslation();
return (
<div className="bg-gray-50 dark:bg-gray-900 min-h-screen">
{/* Header */}
<div className="pt-24 pb-12 text-center px-4 sm:px-6 lg:px-8">
<h1 className="text-4xl font-bold text-gray-900 dark:text-white mb-4">
{t('marketing.pricing.title')}
</h1>
<p className="text-xl text-gray-600 dark:text-gray-400 max-w-2xl mx-auto">
{t('marketing.pricing.subtitle')}
</p>
</div>
{/* Dynamic Pricing Cards */}
<div className="pb-20">
<DynamicPricingCards />
</div>
{/* Feature Comparison Table */}
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 pb-20">
<h2 className="text-3xl font-bold text-center text-gray-900 dark:text-white mb-4">
{t('marketing.pricing.featureComparison.title', 'Compare Plans')}
</h2>
<p className="text-center text-gray-600 dark:text-gray-400 mb-12 max-w-2xl mx-auto">
{t(
'marketing.pricing.featureComparison.subtitle',
'See exactly what you get with each plan'
)}
</p>
<div className="bg-white dark:bg-gray-800 rounded-2xl shadow-sm border border-gray-200 dark:border-gray-700 p-6">
<FeatureComparisonTable />
</div>
</div>
{/* FAQ Section */}
<div className="max-w-3xl mx-auto px-4 sm:px-6 lg:px-8 pb-20">
<h2 className="text-3xl font-bold text-center text-gray-900 dark:text-white mb-12">
{t('marketing.pricing.faq.title')}
</h2>
<FAQAccordion
items={[
{
question: t('marketing.pricing.faq.needPython.question'),
answer: t('marketing.pricing.faq.needPython.answer'),
},
{
question: t('marketing.pricing.faq.exceedLimits.question'),
answer: t('marketing.pricing.faq.exceedLimits.answer'),
},
{
question: t('marketing.pricing.faq.customDomain.question'),
answer: t('marketing.pricing.faq.customDomain.answer'),
},
{
question: t('marketing.pricing.faq.dataSafety.question'),
answer: t('marketing.pricing.faq.dataSafety.answer'),
},
]}
/>
</div>
{/* CTA */}
<CTASection />
</div>
);
};
export default PricingPage;