mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2026-02-01 20:10:35 +01:00
Realy subscription info.
This commit is contained in:
parent
3f073556e0
commit
aa9d67887c
@ -1,8 +1,9 @@
|
||||
import React, { useState, useMemo } from 'react';
|
||||
import { Button, Card, Badge, Text, Collapse } from '@mantine/core';
|
||||
import { Button, Collapse } from '@mantine/core';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import licenseService, { PlanTier, PlanTierGroup, LicenseInfo, mapLicenseToTier } from '@app/services/licenseService';
|
||||
import PlanCard from '@app/components/shared/config/configSections/plan/PlanCard';
|
||||
import FeatureComparisonTable from '@app/components/shared/config/configSections/plan/FeatureComparisonTable';
|
||||
|
||||
interface AvailablePlansSectionProps {
|
||||
plans: PlanTier[];
|
||||
@ -99,75 +100,7 @@ const AvailablePlansSection: React.FC<AvailablePlansSectionProps> = ({
|
||||
</div>
|
||||
|
||||
<Collapse in={showComparison}>
|
||||
<Card padding="lg" radius="md" withBorder style={{ marginTop: '1rem' }}>
|
||||
<Text size="lg" fw={600} mb="md">
|
||||
{t('plan.featureComparison', 'Feature Comparison')}
|
||||
</Text>
|
||||
|
||||
<div style={{ overflowX: 'auto' }}>
|
||||
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
|
||||
<thead>
|
||||
<tr style={{ borderBottom: '2px solid var(--mantine-color-gray-3)' }}>
|
||||
<th style={{ textAlign: 'left', padding: '0.75rem' }}>
|
||||
{t('plan.feature.title', 'Feature')}
|
||||
</th>
|
||||
{groupedPlans.map((group) => (
|
||||
<th
|
||||
key={group.tier}
|
||||
style={{
|
||||
textAlign: 'center',
|
||||
padding: '0.75rem',
|
||||
minWidth: '8rem',
|
||||
position: 'relative'
|
||||
}}
|
||||
>
|
||||
{group.name}
|
||||
{group.popular && (
|
||||
<Badge
|
||||
color="blue"
|
||||
variant="filled"
|
||||
size="xs"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: '0.5rem',
|
||||
right: '0.5rem',
|
||||
}}
|
||||
>
|
||||
{t('plan.popular', 'Popular')}
|
||||
</Badge>
|
||||
)}
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{groupedPlans[0]?.features.map((_, featureIndex) => (
|
||||
<tr
|
||||
key={featureIndex}
|
||||
style={{ borderBottom: '1px solid var(--mantine-color-gray-3)' }}
|
||||
>
|
||||
<td style={{ padding: '0.75rem' }}>
|
||||
{groupedPlans[0].features[featureIndex].name}
|
||||
</td>
|
||||
{groupedPlans.map((group) => (
|
||||
<td key={group.tier} style={{ textAlign: 'center', padding: '0.75rem' }}>
|
||||
{group.features[featureIndex]?.included ? (
|
||||
<Text c="green" fw={600} size="lg">
|
||||
✓
|
||||
</Text>
|
||||
) : (
|
||||
<Text c="gray" size="sm">
|
||||
−
|
||||
</Text>
|
||||
)}
|
||||
</td>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</Card>
|
||||
<FeatureComparisonTable plans={groupedPlans} />
|
||||
</Collapse>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -0,0 +1,93 @@
|
||||
import React from 'react';
|
||||
import { Card, Badge, Text } from '@mantine/core';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PlanFeature } from '@app/services/licenseService';
|
||||
|
||||
interface PlanWithFeatures {
|
||||
name: string;
|
||||
features: PlanFeature[];
|
||||
popular?: boolean;
|
||||
tier?: string;
|
||||
}
|
||||
|
||||
interface FeatureComparisonTableProps {
|
||||
plans: PlanWithFeatures[];
|
||||
}
|
||||
|
||||
const FeatureComparisonTable: React.FC<FeatureComparisonTableProps> = ({ plans }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Card padding="lg" radius="md" withBorder style={{ marginTop: '1rem' }}>
|
||||
<Text size="lg" fw={600} mb="md">
|
||||
{t('plan.featureComparison', 'Feature Comparison')}
|
||||
</Text>
|
||||
|
||||
<div style={{ overflowX: 'auto' }}>
|
||||
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
|
||||
<thead>
|
||||
<tr style={{ borderBottom: '2px solid var(--mantine-color-gray-3)' }}>
|
||||
<th style={{ textAlign: 'left', padding: '0.75rem' }}>
|
||||
{t('plan.feature.title', 'Feature')}
|
||||
</th>
|
||||
{plans.map((plan, index) => (
|
||||
<th
|
||||
key={plan.tier || plan.name || index}
|
||||
style={{
|
||||
textAlign: 'center',
|
||||
padding: '0.75rem',
|
||||
minWidth: '8rem',
|
||||
position: 'relative'
|
||||
}}
|
||||
>
|
||||
{plan.name}
|
||||
{plan.popular && (
|
||||
<Badge
|
||||
color="blue"
|
||||
variant="filled"
|
||||
size="xs"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: '0.5rem',
|
||||
right: '0.5rem',
|
||||
}}
|
||||
>
|
||||
{t('plan.popular', 'Popular')}
|
||||
</Badge>
|
||||
)}
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{plans[0]?.features.map((_, featureIndex) => (
|
||||
<tr
|
||||
key={featureIndex}
|
||||
style={{ borderBottom: '1px solid var(--mantine-color-gray-3)' }}
|
||||
>
|
||||
<td style={{ padding: '0.75rem' }}>
|
||||
{plans[0].features[featureIndex].name}
|
||||
</td>
|
||||
{plans.map((plan, planIndex) => (
|
||||
<td key={planIndex} style={{ textAlign: 'center', padding: '0.75rem' }}>
|
||||
{plan.features[featureIndex]?.included ? (
|
||||
<Text c="green" fw={600} size="lg">
|
||||
✓
|
||||
</Text>
|
||||
) : (
|
||||
<Text c="gray" size="sm">
|
||||
−
|
||||
</Text>
|
||||
)}
|
||||
</td>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default FeatureComparisonTable;
|
||||
@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { Button, Card, Badge, Text, Group, Stack, Divider } from '@mantine/core';
|
||||
import { Button, Card, Badge, Text, Stack, Divider } from '@mantine/core';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PlanTierGroup, LicenseInfo } from '@app/services/licenseService';
|
||||
|
||||
@ -45,6 +45,9 @@ const PlanCard: React.FC<PlanCardProps> = ({ planGroup, isCurrentTier, isDowngra
|
||||
<Text size="xl" fw={700} mb="xs">
|
||||
{planGroup.name}
|
||||
</Text>
|
||||
<Text size="xs" c="dimmed" mb="xs" style={{ opacity: 0 }}>
|
||||
{t('plan.from', 'From')}
|
||||
</Text>
|
||||
<Text size="2.5rem" fw={700} style={{ lineHeight: 1 }}>
|
||||
£0
|
||||
</Text>
|
||||
@ -53,7 +56,9 @@ const PlanCard: React.FC<PlanCardProps> = ({ planGroup, isCurrentTier, isDowngra
|
||||
</Text>
|
||||
</div>
|
||||
|
||||
<Stack gap="xs" mt="md">
|
||||
<Divider />
|
||||
|
||||
<Stack gap="xs">
|
||||
{planGroup.highlights.map((highlight, index) => (
|
||||
<Text key={index} size="sm" c="dimmed">
|
||||
• {highlight}
|
||||
@ -124,40 +129,33 @@ const PlanCard: React.FC<PlanCardProps> = ({ planGroup, isCurrentTier, isDowngra
|
||||
<Stack gap="md" style={{ height: '100%' }}>
|
||||
{/* Tier Name */}
|
||||
<div>
|
||||
<Text size="xl" fw={700}>
|
||||
<Text size="xl" fw={700} mb="xs">
|
||||
{planGroup.name}
|
||||
</Text>
|
||||
</div>
|
||||
|
||||
{/* "From" Pricing */}
|
||||
<div>
|
||||
<Text size="xs" c="dimmed" mb="xs">
|
||||
{t('plan.from', 'From')}
|
||||
</Text>
|
||||
|
||||
{/* Price */}
|
||||
{isEnterprise && displaySeatPrice !== undefined ? (
|
||||
<div>
|
||||
<Group gap="xs" align="baseline">
|
||||
<Text size="xl" fw={700}>
|
||||
{displayCurrency}{displayPrice}
|
||||
</Text>
|
||||
<Text size="sm" c="dimmed">
|
||||
{t('plan.perMonth', '/month')}
|
||||
</Text>
|
||||
</Group>
|
||||
<Text size="sm" c="dimmed">
|
||||
+ {displayCurrency}{displaySeatPrice}/seat/month
|
||||
</Text>
|
||||
</div>
|
||||
) : (
|
||||
<Group gap="xs" align="baseline">
|
||||
<Text size="xl" fw={700}>
|
||||
<>
|
||||
<Text size="2.5rem" fw={700} style={{ lineHeight: 1 }}>
|
||||
{displayCurrency}{displayPrice}
|
||||
</Text>
|
||||
<Text size="sm" c="dimmed">
|
||||
<Text size="sm" c="dimmed" mt="xs">
|
||||
+ {displayCurrency}{displaySeatPrice}/seat {t('plan.perMonth', '/month')}
|
||||
</Text>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Text size="2.5rem" fw={700} style={{ lineHeight: 1 }}>
|
||||
{displayCurrency}{displayPrice}
|
||||
</Text>
|
||||
<Text size="sm" c="dimmed" mt="xs">
|
||||
{t('plan.perMonth', '/month')}
|
||||
</Text>
|
||||
</Group>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Show seat count for enterprise plans when current */}
|
||||
|
||||
@ -7,7 +7,9 @@ import { useRestartServer } from '@app/components/shared/config/useRestartServer
|
||||
import { useAdminSettings } from '@app/hooks/useAdminSettings';
|
||||
import PendingBadge from '@app/components/shared/config/PendingBadge';
|
||||
import { alert } from '@app/components/toast';
|
||||
import { LicenseInfo } from '@app/services/licenseService';
|
||||
import { LicenseInfo, mapLicenseToTier } from '@app/services/licenseService';
|
||||
import { PLAN_FEATURES, PLAN_HIGHLIGHTS } from '@app/constants/planConstants';
|
||||
import FeatureComparisonTable from '@app/components/shared/config/configSections/plan/FeatureComparisonTable';
|
||||
|
||||
interface PremiumSettingsData {
|
||||
key?: string;
|
||||
@ -21,6 +23,7 @@ interface StaticPlanSectionProps {
|
||||
const StaticPlanSection: React.FC<StaticPlanSectionProps> = ({ currentLicenseInfo }) => {
|
||||
const { t } = useTranslation();
|
||||
const [showLicenseKey, setShowLicenseKey] = useState(false);
|
||||
const [showComparison, setShowComparison] = useState(false);
|
||||
|
||||
// Premium/License key management
|
||||
const { restartModalOpened, showRestartModal, closeRestartModal, restartServer } = useRestartServer();
|
||||
@ -59,40 +62,20 @@ const StaticPlanSection: React.FC<StaticPlanSectionProps> = ({ currentLicenseInf
|
||||
name: t('plan.free.name', 'Free'),
|
||||
price: 0,
|
||||
currency: '£',
|
||||
period: t('plan.period.month', '/month'),
|
||||
highlights: [
|
||||
t('plan.free.highlight1', 'Limited Tool Usage Per week'),
|
||||
t('plan.free.highlight2', 'Access to all tools'),
|
||||
t('plan.free.highlight3', 'Community support'),
|
||||
],
|
||||
features: [
|
||||
{ name: t('plan.feature.pdfTools', 'Basic PDF Tools'), included: true },
|
||||
{ name: t('plan.feature.fileSize', 'File Size Limit'), included: false },
|
||||
{ name: t('plan.feature.automation', 'Automate tool workflows'), included: false },
|
||||
{ name: t('plan.feature.api', 'API Access'), included: false },
|
||||
{ name: t('plan.feature.priority', 'Priority Support'), included: false },
|
||||
],
|
||||
period: '',
|
||||
highlights: PLAN_HIGHLIGHTS.FREE,
|
||||
features: PLAN_FEATURES.FREE,
|
||||
maxUsers: 5,
|
||||
},
|
||||
{
|
||||
id: 'pro',
|
||||
name: t('plan.pro.name', 'Pro'),
|
||||
price: 8,
|
||||
currency: '£',
|
||||
period: t('plan.period.perUserPerMonth', '/user/month'),
|
||||
popular: true,
|
||||
highlights: [
|
||||
t('plan.pro.highlight1', 'Unlimited Tool Usage per user'),
|
||||
t('plan.pro.highlight2', 'Advanced PDF tools'),
|
||||
t('plan.pro.highlight3', 'No watermarks'),
|
||||
],
|
||||
features: [
|
||||
{ name: t('plan.feature.pdfTools', 'Basic PDF Tools'), included: true },
|
||||
{ name: t('plan.feature.fileSize', 'File Size Limit'), included: true },
|
||||
{ name: t('plan.feature.automation', 'Automate tool workflows'), included: true },
|
||||
{ name: t('plan.feature.api', 'Weekly API Credits'), included: true },
|
||||
{ name: t('plan.feature.priority', 'Priority Support'), included: false },
|
||||
],
|
||||
id: 'server',
|
||||
name: 'Server',
|
||||
price: 0,
|
||||
currency: '',
|
||||
period: '',
|
||||
popular: false,
|
||||
highlights: PLAN_HIGHLIGHTS.SERVER_MONTHLY,
|
||||
features: PLAN_FEATURES.SERVER,
|
||||
maxUsers: 'Unlimited users',
|
||||
},
|
||||
{
|
||||
@ -101,27 +84,17 @@ const StaticPlanSection: React.FC<StaticPlanSectionProps> = ({ currentLicenseInf
|
||||
price: 0,
|
||||
currency: '',
|
||||
period: '',
|
||||
highlights: [
|
||||
t('plan.enterprise.highlight1', 'Custom pricing'),
|
||||
t('plan.enterprise.highlight2', 'Dedicated support'),
|
||||
t('plan.enterprise.highlight3', 'Latest features'),
|
||||
],
|
||||
features: [
|
||||
{ name: t('plan.feature.pdfTools', 'Basic PDF Tools'), included: true },
|
||||
{ name: t('plan.feature.fileSize', 'File Size Limit'), included: true },
|
||||
{ name: t('plan.feature.automation', 'Automate tool workflows'), included: true },
|
||||
{ name: t('plan.feature.api', 'Weekly API Credits'), included: true },
|
||||
{ name: t('plan.feature.priority', 'Priority Support'), included: true },
|
||||
],
|
||||
highlights: PLAN_HIGHLIGHTS.ENTERPRISE_MONTHLY,
|
||||
features: PLAN_FEATURES.ENTERPRISE,
|
||||
maxUsers: 'Custom',
|
||||
},
|
||||
];
|
||||
|
||||
const getCurrentPlan = () => {
|
||||
if (!currentLicenseInfo) return staticPlans[0];
|
||||
if (currentLicenseInfo.licenseType === 'ENTERPRISE') return staticPlans[2];
|
||||
if (currentLicenseInfo.maxUsers > 5) return staticPlans[1];
|
||||
return staticPlans[0];
|
||||
const tier = mapLicenseToTier(currentLicenseInfo || null);
|
||||
if (tier === 'enterprise') return staticPlans[2];
|
||||
if (tier === 'server') return staticPlans[1];
|
||||
return staticPlans[0]; // free
|
||||
};
|
||||
|
||||
const currentPlan = getCurrentPlan();
|
||||
@ -202,9 +175,21 @@ const StaticPlanSection: React.FC<StaticPlanSectionProps> = ({ currentLicenseInf
|
||||
position: 'relative',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
borderColor: plan.id === currentPlan.id ? 'var(--mantine-color-green-6)' : undefined,
|
||||
borderWidth: plan.id === currentPlan.id ? '2px' : undefined,
|
||||
}}
|
||||
>
|
||||
{plan.popular && (
|
||||
{plan.id === currentPlan.id && (
|
||||
<Badge
|
||||
color="green"
|
||||
variant="filled"
|
||||
size="sm"
|
||||
style={{ position: 'absolute', top: '1rem', right: '1rem' }}
|
||||
>
|
||||
{t('plan.current', 'Current Plan')}
|
||||
</Badge>
|
||||
)}
|
||||
{plan.popular && plan.id !== currentPlan.id && (
|
||||
<Badge
|
||||
variant="filled"
|
||||
size="xs"
|
||||
@ -251,7 +236,7 @@ const StaticPlanSection: React.FC<StaticPlanSectionProps> = ({ currentLicenseInf
|
||||
<div style={{ flexGrow: 1 }} />
|
||||
|
||||
<Button
|
||||
variant={plan.id === currentPlan.id ? 'filled' : 'outline'}
|
||||
variant={plan.id === currentPlan.id ? 'light' : 'filled'}
|
||||
disabled={plan.id === currentPlan.id}
|
||||
fullWidth
|
||||
onClick={() =>
|
||||
@ -266,6 +251,20 @@ const StaticPlanSection: React.FC<StaticPlanSectionProps> = ({ currentLicenseInf
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Feature Comparison Toggle */}
|
||||
<div style={{ textAlign: 'center', marginTop: '1rem' }}>
|
||||
<Button variant="subtle" onClick={() => setShowComparison(!showComparison)}>
|
||||
{showComparison
|
||||
? t('plan.hideComparison', 'Hide Feature Comparison')
|
||||
: t('plan.showComparison', 'Compare All Features')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Feature Comparison Table */}
|
||||
<Collapse in={showComparison}>
|
||||
<FeatureComparisonTable plans={staticPlans} />
|
||||
</Collapse>
|
||||
</div>
|
||||
|
||||
<Divider />
|
||||
|
||||
97
frontend/src/proprietary/constants/planConstants.ts
Normal file
97
frontend/src/proprietary/constants/planConstants.ts
Normal file
@ -0,0 +1,97 @@
|
||||
import { PlanFeature } from '@app/services/licenseService';
|
||||
|
||||
/**
|
||||
* Shared plan feature definitions for Stirling PDF Self-Hosted
|
||||
* Used by both dynamic (Stripe) and static (fallback) plan displays
|
||||
*/
|
||||
|
||||
export const PLAN_FEATURES = {
|
||||
FREE: [
|
||||
{ name: 'Self-hosted deployment', included: true },
|
||||
{ name: 'All PDF operations', included: true },
|
||||
{ name: 'Secure Login Support', included: true },
|
||||
{ name: 'Community support', included: true },
|
||||
{ name: 'Regular updates', included: true },
|
||||
{ name: 'up to 5 users', included: true },
|
||||
{ name: 'Unlimited users', included: false },
|
||||
{ name: 'Google drive integration', included: false },
|
||||
{ name: 'External Database', included: false },
|
||||
{ name: 'Editing text in pdfs', included: false },
|
||||
{ name: 'Users limited to seats', included: false },
|
||||
{ name: 'SSO', included: false },
|
||||
{ name: 'Auditing', included: false },
|
||||
{ name: 'Usage tracking', included: false },
|
||||
{ name: 'Prometheus Support', included: false },
|
||||
{ name: 'Custom PDF metadata', included: false },
|
||||
] as PlanFeature[],
|
||||
|
||||
SERVER: [
|
||||
{ name: 'Self-hosted deployment', included: true },
|
||||
{ name: 'All PDF operations', included: true },
|
||||
{ name: 'Secure Login Support', included: true },
|
||||
{ name: 'Community support', included: true },
|
||||
{ name: 'Regular updates', included: true },
|
||||
{ name: 'Up to 5 users', included: false },
|
||||
{ name: 'Unlimited users', included: true },
|
||||
{ name: 'Google drive integration', included: true },
|
||||
{ name: 'External Database', included: true },
|
||||
{ name: 'Editing text in pdfs', included: true },
|
||||
{ name: 'Users limited to seats', included: false },
|
||||
{ name: 'SSO', included: false },
|
||||
{ name: 'Auditing', included: false },
|
||||
{ name: 'Usage tracking', included: false },
|
||||
{ name: 'Prometheus Support', included: false },
|
||||
{ name: 'Custom PDF metadata', included: false },
|
||||
] as PlanFeature[],
|
||||
|
||||
ENTERPRISE: [
|
||||
{ name: 'Self-hosted deployment', included: true },
|
||||
{ name: 'All PDF operations', included: true },
|
||||
{ name: 'Secure Login Support', included: true },
|
||||
{ name: 'Community support', included: true },
|
||||
{ name: 'Regular updates', included: true },
|
||||
{ name: 'up to 5 users', included: false },
|
||||
{ name: 'Unlimited users', included: false },
|
||||
{ name: 'Google drive integration', included: true },
|
||||
{ name: 'External Database', included: true },
|
||||
{ name: 'Editing text in pdfs', included: true },
|
||||
{ name: 'Users limited to seats', included: true },
|
||||
{ name: 'SSO', included: true },
|
||||
{ name: 'Auditing', included: true },
|
||||
{ name: 'Usage tracking', included: true },
|
||||
{ name: 'Prometheus Support', included: true },
|
||||
{ name: 'Custom PDF metadata', included: true },
|
||||
] as PlanFeature[],
|
||||
} as const;
|
||||
|
||||
export const PLAN_HIGHLIGHTS = {
|
||||
FREE: [
|
||||
'Up to 5 users',
|
||||
'Self-hosted',
|
||||
'All basic features'
|
||||
],
|
||||
SERVER_MONTHLY: [
|
||||
'Self-hosted on your infrastructure',
|
||||
'Unlimited users',
|
||||
'Advanced integrations',
|
||||
'Cancel anytime'
|
||||
],
|
||||
SERVER_YEARLY: [
|
||||
'Self-hosted on your infrastructure',
|
||||
'Unlimited users',
|
||||
'Advanced integrations',
|
||||
'Save with annual billing'
|
||||
],
|
||||
ENTERPRISE_MONTHLY: [
|
||||
'Enterprise features (SSO, Auditing)',
|
||||
'Usage tracking & Prometheus',
|
||||
'Custom PDF metadata',
|
||||
'Per-seat licensing'
|
||||
],
|
||||
ENTERPRISE_YEARLY: [
|
||||
'Enterprise features (SSO, Auditing)',
|
||||
'Usage tracking & Prometheus',
|
||||
'Custom PDF metadata',
|
||||
'Save with annual billing'
|
||||
]
|
||||
} as const;
|
||||
@ -1,6 +1,7 @@
|
||||
import apiClient from '@app/services/apiClient';
|
||||
import { supabase, isSupabaseConfigured } from '@app/services/supabaseClient';
|
||||
import { getCheckoutMode } from '@app/utils/protocolDetection';
|
||||
import { PLAN_FEATURES, PLAN_HIGHLIGHTS } from '@app/constants/planConstants';
|
||||
|
||||
export interface PlanFeature {
|
||||
name: string;
|
||||
@ -15,7 +16,7 @@ export interface PlanTier {
|
||||
period: string;
|
||||
popular?: boolean;
|
||||
features: PlanFeature[];
|
||||
highlights: string[];
|
||||
highlights: readonly string[];
|
||||
isContactOnly?: boolean;
|
||||
seatPrice?: number; // Per-seat price for enterprise plans
|
||||
requiresSeats?: boolean; // Flag indicating seat selection is needed
|
||||
@ -28,7 +29,7 @@ export interface PlanTierGroup {
|
||||
monthly: PlanTier | null;
|
||||
yearly: PlanTier | null;
|
||||
features: PlanFeature[];
|
||||
highlights: string[];
|
||||
highlights: readonly string[];
|
||||
popular?: boolean;
|
||||
}
|
||||
|
||||
@ -170,20 +171,8 @@ const licenseService = {
|
||||
currency: currencySymbol,
|
||||
period: '/month',
|
||||
popular: false,
|
||||
features: [
|
||||
{ name: 'Self-hosted deployment', included: true },
|
||||
{ name: 'All PDF operations', included: true },
|
||||
{ name: 'Unlimited users', included: true },
|
||||
{ name: 'Community support', included: true },
|
||||
{ name: 'Regular updates', included: true },
|
||||
{ name: 'Priority support', included: false },
|
||||
{ name: 'Custom integrations', included: false },
|
||||
],
|
||||
highlights: [
|
||||
'Self-hosted on your infrastructure',
|
||||
'All features included',
|
||||
'Cancel anytime'
|
||||
]
|
||||
features: PLAN_FEATURES.SERVER,
|
||||
highlights: PLAN_HIGHLIGHTS.SERVER_MONTHLY
|
||||
},
|
||||
{
|
||||
id: 'selfhosted:server:yearly',
|
||||
@ -193,20 +182,8 @@ const licenseService = {
|
||||
currency: currencySymbol,
|
||||
period: '/year',
|
||||
popular: true,
|
||||
features: [
|
||||
{ name: 'Self-hosted deployment', included: true },
|
||||
{ name: 'All PDF operations', included: true },
|
||||
{ name: 'Unlimited users', included: true },
|
||||
{ name: 'Community support', included: true },
|
||||
{ name: 'Regular updates', included: true },
|
||||
{ name: 'Priority support', included: false },
|
||||
{ name: 'Custom integrations', included: false },
|
||||
],
|
||||
highlights: [
|
||||
'Self-hosted on your infrastructure',
|
||||
'All features included',
|
||||
'Save with annual billing'
|
||||
]
|
||||
features: PLAN_FEATURES.SERVER,
|
||||
highlights: PLAN_HIGHLIGHTS.SERVER_YEARLY
|
||||
},
|
||||
{
|
||||
id: 'selfhosted:enterprise:monthly',
|
||||
@ -218,20 +195,8 @@ const licenseService = {
|
||||
period: '/month',
|
||||
popular: false,
|
||||
requiresSeats: true,
|
||||
features: [
|
||||
{ name: 'Self-hosted deployment', included: true },
|
||||
{ name: 'All PDF operations', included: true },
|
||||
{ name: 'Per-seat licensing', included: true },
|
||||
{ name: 'Priority support', included: true },
|
||||
{ name: 'SLA guarantee', included: true },
|
||||
{ name: 'Custom integrations', included: true },
|
||||
{ name: 'Dedicated account manager', included: true },
|
||||
],
|
||||
highlights: [
|
||||
'Enterprise-grade support',
|
||||
'Custom integrations available',
|
||||
'SLA guarantee included'
|
||||
]
|
||||
features: PLAN_FEATURES.ENTERPRISE,
|
||||
highlights: PLAN_HIGHLIGHTS.ENTERPRISE_MONTHLY
|
||||
},
|
||||
{
|
||||
id: 'selfhosted:enterprise:yearly',
|
||||
@ -243,20 +208,8 @@ const licenseService = {
|
||||
period: '/year',
|
||||
popular: false,
|
||||
requiresSeats: true,
|
||||
features: [
|
||||
{ name: 'Self-hosted deployment', included: true },
|
||||
{ name: 'All PDF operations', included: true },
|
||||
{ name: 'Per-seat licensing', included: true },
|
||||
{ name: 'Priority support', included: true },
|
||||
{ name: 'SLA guarantee', included: true },
|
||||
{ name: 'Custom integrations', included: true },
|
||||
{ name: 'Dedicated account manager', included: true },
|
||||
],
|
||||
highlights: [
|
||||
'Enterprise-grade support',
|
||||
'Custom integrations available',
|
||||
'Save with annual billing'
|
||||
]
|
||||
features: PLAN_FEATURES.ENTERPRISE,
|
||||
highlights: PLAN_HIGHLIGHTS.ENTERPRISE_YEARLY
|
||||
},
|
||||
];
|
||||
|
||||
@ -277,22 +230,8 @@ const licenseService = {
|
||||
currency: currencySymbol,
|
||||
period: '',
|
||||
popular: false,
|
||||
features: [
|
||||
{ name: 'Self-hosted deployment', included: true },
|
||||
{ name: 'All PDF operations', included: true },
|
||||
{ name: 'Up to 5 users', included: true },
|
||||
{ name: 'Community support', included: true },
|
||||
{ name: 'Regular updates', included: true },
|
||||
{ name: 'Priority support', included: false },
|
||||
{ name: 'SLA guarantee', included: false },
|
||||
{ name: 'Custom integrations', included: false },
|
||||
{ name: 'Dedicated account manager', included: false },
|
||||
],
|
||||
highlights: [
|
||||
'Up to 5 users',
|
||||
'Self-hosted',
|
||||
'All basic features'
|
||||
]
|
||||
features: PLAN_FEATURES.FREE,
|
||||
highlights: PLAN_HIGHLIGHTS.FREE
|
||||
};
|
||||
|
||||
const allPlans = [freePlan, ...validPlans];
|
||||
|
||||
Loading…
Reference in New Issue
Block a user