mirror of https://github.com/kortix-ai/suna.git
refactor(pricing): update pricing section layout and simplify pricing tier logic; add hidden property for conditional display
This commit is contained in:
parent
4d4faca12a
commit
29c9fa78db
|
@ -24,7 +24,9 @@ export default function Home() {
|
||||||
{/* <FeatureSection /> */}
|
{/* <FeatureSection /> */}
|
||||||
{/* <GrowthSection /> */}
|
{/* <GrowthSection /> */}
|
||||||
<OpenSourceSection />
|
<OpenSourceSection />
|
||||||
<PricingSection />
|
<div className='flex flex-col items-center px-4'>
|
||||||
|
<PricingSection />
|
||||||
|
</div>
|
||||||
{/* <TestimonialSection /> */}
|
{/* <TestimonialSection /> */}
|
||||||
{/* <FAQSection /> */}
|
{/* <FAQSection /> */}
|
||||||
<CTASection />
|
<CTASection />
|
||||||
|
|
|
@ -141,8 +141,9 @@ export default function AccountBillingStatus({ accountId, returnUrl }: Props) {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Plans Comparison */}
|
{/* Plans Comparison */}
|
||||||
<PricingSection returnUrl={returnUrl} showTitleAndTabs={false} />
|
<PricingSection returnUrl={returnUrl} showTitleAndTabs={false} insideDialog={true} />
|
||||||
|
|
||||||
|
<div className="mt-20"></div>
|
||||||
{/* Manage Subscription Button */}
|
{/* Manage Subscription Button */}
|
||||||
<Button
|
<Button
|
||||||
onClick={handleManageSubscription}
|
onClick={handleManageSubscription}
|
||||||
|
@ -178,7 +179,7 @@ export default function AccountBillingStatus({ accountId, returnUrl }: Props) {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Plans Comparison */}
|
{/* Plans Comparison */}
|
||||||
<PricingSection returnUrl={returnUrl} showTitleAndTabs={false} />
|
<PricingSection returnUrl={returnUrl} showTitleAndTabs={false} insideDialog={true} />
|
||||||
|
|
||||||
{/* Manage Subscription Button */}
|
{/* Manage Subscription Button */}
|
||||||
<Button
|
<Button
|
||||||
|
|
|
@ -5,16 +5,8 @@ import type { PricingTier } from '@/lib/home';
|
||||||
import { siteConfig } from '@/lib/home';
|
import { siteConfig } from '@/lib/home';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { motion } from 'motion/react';
|
import { motion } from 'motion/react';
|
||||||
import { useState, useEffect, useRef } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { CheckIcon } from 'lucide-react';
|
import { CheckIcon } from 'lucide-react';
|
||||||
import Link from 'next/link';
|
|
||||||
import {
|
|
||||||
Select,
|
|
||||||
SelectContent,
|
|
||||||
SelectItem,
|
|
||||||
SelectTrigger,
|
|
||||||
SelectValue,
|
|
||||||
} from '@/components/ui/select';
|
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import {
|
import {
|
||||||
getSubscription,
|
getSubscription,
|
||||||
|
@ -26,7 +18,6 @@ import { toast } from 'sonner';
|
||||||
import { isLocalMode } from '@/lib/config';
|
import { isLocalMode } from '@/lib/config';
|
||||||
|
|
||||||
// Constants
|
// Constants
|
||||||
const DEFAULT_SELECTED_PLAN = '6 hours';
|
|
||||||
export const SUBSCRIPTION_PLANS = {
|
export const SUBSCRIPTION_PLANS = {
|
||||||
FREE: 'free',
|
FREE: 'free',
|
||||||
PRO: 'base',
|
PRO: 'base',
|
||||||
|
@ -53,16 +44,6 @@ interface PriceDisplayProps {
|
||||||
isCompact?: boolean;
|
isCompact?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CustomPriceDisplayProps {
|
|
||||||
price: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface UpgradePlan {
|
|
||||||
hours: string;
|
|
||||||
price: string;
|
|
||||||
stripePriceId: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface PricingTierProps {
|
interface PricingTierProps {
|
||||||
tier: PricingTier;
|
tier: PricingTier;
|
||||||
isCompact?: boolean;
|
isCompact?: boolean;
|
||||||
|
@ -142,24 +123,6 @@ function PriceDisplay({ price, isCompact }: PriceDisplayProps) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function CustomPriceDisplay({ price }: CustomPriceDisplayProps) {
|
|
||||||
return (
|
|
||||||
<motion.span
|
|
||||||
key={price}
|
|
||||||
className="text-4xl font-semibold"
|
|
||||||
initial={{
|
|
||||||
opacity: 0,
|
|
||||||
x: 10,
|
|
||||||
filter: 'blur(5px)',
|
|
||||||
}}
|
|
||||||
animate={{ opacity: 1, x: 0, filter: 'blur(0px)' }}
|
|
||||||
transition={{ duration: 0.25, ease: [0.4, 0, 0.2, 1] }}
|
|
||||||
>
|
|
||||||
{price}
|
|
||||||
</motion.span>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function PricingTier({
|
function PricingTier({
|
||||||
tier,
|
tier,
|
||||||
isCompact = false,
|
isCompact = false,
|
||||||
|
@ -173,37 +136,7 @@ function PricingTier({
|
||||||
returnUrl,
|
returnUrl,
|
||||||
insideDialog = false,
|
insideDialog = false,
|
||||||
}: PricingTierProps) {
|
}: PricingTierProps) {
|
||||||
const [localSelectedPlan, setLocalSelectedPlan] = useState(
|
// Auto-select the correct plan only on initial load - simplified since no more Custom tier
|
||||||
selectedPlan || DEFAULT_SELECTED_PLAN,
|
|
||||||
);
|
|
||||||
const hasInitialized = useRef(false);
|
|
||||||
|
|
||||||
// Auto-select the correct plan only on initial load
|
|
||||||
useEffect(() => {
|
|
||||||
if (
|
|
||||||
!hasInitialized.current &&
|
|
||||||
tier.name === 'Custom' &&
|
|
||||||
tier.upgradePlans &&
|
|
||||||
currentSubscription?.price_id
|
|
||||||
) {
|
|
||||||
const matchingPlan = tier.upgradePlans.find(
|
|
||||||
(plan) => plan.stripePriceId === currentSubscription.price_id,
|
|
||||||
);
|
|
||||||
if (matchingPlan) {
|
|
||||||
setLocalSelectedPlan(matchingPlan.hours);
|
|
||||||
}
|
|
||||||
hasInitialized.current = true;
|
|
||||||
}
|
|
||||||
}, [currentSubscription, tier.name, tier.upgradePlans]);
|
|
||||||
|
|
||||||
// Only refetch when plan is selected
|
|
||||||
const handlePlanSelect = (value: string) => {
|
|
||||||
setLocalSelectedPlan(value);
|
|
||||||
if (tier.name === 'Custom' && onSubscriptionUpdate) {
|
|
||||||
onSubscriptionUpdate();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSubscribe = async (planStripePriceId: string) => {
|
const handleSubscribe = async (planStripePriceId: string) => {
|
||||||
if (!isAuthenticated) {
|
if (!isAuthenticated) {
|
||||||
window.location.href = '/auth';
|
window.location.href = '/auth';
|
||||||
|
@ -215,22 +148,11 @@ function PricingTier({
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// For custom tier, get the selected plan's stripePriceId
|
onPlanSelect?.(planStripePriceId);
|
||||||
let finalPriceId = planStripePriceId;
|
|
||||||
if (tier.name === 'Custom' && tier.upgradePlans) {
|
|
||||||
const selectedPlan = tier.upgradePlans.find(
|
|
||||||
(plan) => plan.hours === localSelectedPlan,
|
|
||||||
);
|
|
||||||
if (selectedPlan?.stripePriceId) {
|
|
||||||
finalPriceId = selectedPlan.stripePriceId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onPlanSelect?.(finalPriceId);
|
|
||||||
|
|
||||||
const response: CreateCheckoutSessionResponse =
|
const response: CreateCheckoutSessionResponse =
|
||||||
await createCheckoutSession({
|
await createCheckoutSession({
|
||||||
price_id: finalPriceId,
|
price_id: planStripePriceId,
|
||||||
success_url: returnUrl,
|
success_url: returnUrl,
|
||||||
cancel_url: returnUrl,
|
cancel_url: returnUrl,
|
||||||
});
|
});
|
||||||
|
@ -295,76 +217,12 @@ function PricingTier({
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getPriceValue = (
|
const tierPriceId = tier.stripePriceId;
|
||||||
tier: (typeof siteConfig.cloudPricingItems)[0],
|
|
||||||
selectedHours?: string,
|
|
||||||
): string => {
|
|
||||||
if (tier.upgradePlans && selectedHours) {
|
|
||||||
const plan = tier.upgradePlans.find(
|
|
||||||
(plan) => plan.hours === selectedHours,
|
|
||||||
);
|
|
||||||
if (plan) {
|
|
||||||
return plan.price;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return tier.price;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getDisplayedHours = (
|
|
||||||
tier: (typeof siteConfig.cloudPricingItems)[0],
|
|
||||||
) => {
|
|
||||||
if (tier.name === 'Custom' && localSelectedPlan) {
|
|
||||||
return localSelectedPlan;
|
|
||||||
}
|
|
||||||
return tier.hours;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getSelectedPlanPriceId = (
|
|
||||||
tier: (typeof siteConfig.cloudPricingItems)[0],
|
|
||||||
): string => {
|
|
||||||
if (tier.name === 'Custom' && tier.upgradePlans) {
|
|
||||||
const selectedPlan = tier.upgradePlans.find(
|
|
||||||
(plan) => plan.hours === localSelectedPlan,
|
|
||||||
);
|
|
||||||
return selectedPlan?.stripePriceId || tier.stripePriceId;
|
|
||||||
}
|
|
||||||
return tier.stripePriceId;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getSelectedPlanPrice = (
|
|
||||||
tier: (typeof siteConfig.cloudPricingItems)[0],
|
|
||||||
): string => {
|
|
||||||
if (tier.name === 'Custom' && tier.upgradePlans) {
|
|
||||||
const selectedPlan = tier.upgradePlans.find(
|
|
||||||
(plan) => plan.hours === localSelectedPlan,
|
|
||||||
);
|
|
||||||
return selectedPlan?.price || tier.price;
|
|
||||||
}
|
|
||||||
return tier.price;
|
|
||||||
};
|
|
||||||
|
|
||||||
const tierPriceId = getSelectedPlanPriceId(tier);
|
|
||||||
const isCurrentActivePlan =
|
const isCurrentActivePlan =
|
||||||
isAuthenticated &&
|
isAuthenticated && currentSubscription?.price_id === tierPriceId;
|
||||||
// For custom tier, check if the selected plan matches the current subscription
|
|
||||||
(tier.name === 'Custom'
|
|
||||||
? tier.upgradePlans?.some(
|
|
||||||
(plan) =>
|
|
||||||
plan.hours === localSelectedPlan &&
|
|
||||||
plan.stripePriceId === currentSubscription?.price_id,
|
|
||||||
)
|
|
||||||
: currentSubscription?.price_id === tierPriceId);
|
|
||||||
const isScheduled = isAuthenticated && currentSubscription?.has_schedule;
|
const isScheduled = isAuthenticated && currentSubscription?.has_schedule;
|
||||||
const isScheduledTargetPlan =
|
const isScheduledTargetPlan =
|
||||||
isScheduled &&
|
isScheduled && currentSubscription?.scheduled_price_id === tierPriceId;
|
||||||
// For custom tier, check if the selected plan matches the scheduled subscription
|
|
||||||
(tier.name === 'Custom'
|
|
||||||
? tier.upgradePlans?.some(
|
|
||||||
(plan) =>
|
|
||||||
plan.hours === localSelectedPlan &&
|
|
||||||
plan.stripePriceId === currentSubscription?.scheduled_price_id,
|
|
||||||
)
|
|
||||||
: currentSubscription?.scheduled_price_id === tierPriceId);
|
|
||||||
const isPlanLoading = isLoading[tierPriceId];
|
const isPlanLoading = isLoading[tierPriceId];
|
||||||
|
|
||||||
let buttonText = isAuthenticated ? 'Select Plan' : 'Try Free';
|
let buttonText = isAuthenticated ? 'Select Plan' : 'Try Free';
|
||||||
|
@ -411,41 +269,15 @@ function PricingTier({
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// For custom tier, find the current plan in upgradePlans
|
// Find the current tier
|
||||||
const currentTier =
|
const currentTier = siteConfig.cloudPricingItems.find(
|
||||||
tier.name === 'Custom' && tier.upgradePlans
|
(p) => p.stripePriceId === currentSubscription?.price_id,
|
||||||
? tier.upgradePlans.find(
|
|
||||||
(p) => p.stripePriceId === currentSubscription?.price_id,
|
|
||||||
)
|
|
||||||
: siteConfig.cloudPricingItems.find(
|
|
||||||
(p) => p.stripePriceId === currentSubscription?.price_id,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Find the highest active plan from upgradePlans
|
|
||||||
const highestActivePlan = siteConfig.cloudPricingItems.reduce(
|
|
||||||
(highest, item) => {
|
|
||||||
if (item.upgradePlans) {
|
|
||||||
const activePlan = item.upgradePlans.find(
|
|
||||||
(p) => p.stripePriceId === currentSubscription?.price_id,
|
|
||||||
);
|
|
||||||
if (activePlan) {
|
|
||||||
const activeAmount =
|
|
||||||
parseFloat(activePlan.price.replace(/[^\d.]/g, '') || '0') *
|
|
||||||
100;
|
|
||||||
const highestAmount =
|
|
||||||
parseFloat(highest?.price?.replace(/[^\d.]/g, '') || '0') * 100;
|
|
||||||
return activeAmount > highestAmount ? activePlan : highest;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return highest;
|
|
||||||
},
|
|
||||||
null as { price: string; hours: string; stripePriceId: string } | null,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const currentPriceString = currentSubscription
|
const currentPriceString = currentSubscription
|
||||||
? highestActivePlan?.price || currentTier?.price || '$0'
|
? currentTier?.price || '$0'
|
||||||
: '$0';
|
: '$0';
|
||||||
const selectedPriceString = getSelectedPlanPrice(tier);
|
const selectedPriceString = tier.price;
|
||||||
const currentAmount =
|
const currentAmount =
|
||||||
currentPriceString === '$0'
|
currentPriceString === '$0'
|
||||||
? 0
|
? 0
|
||||||
|
@ -501,14 +333,20 @@ function PricingTier({
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'rounded-xl flex flex-col relative h-fit min-h-[400px] min-[650px]:h-full min-[900px]:h-fit',
|
'rounded-xl flex flex-col relative',
|
||||||
|
insideDialog
|
||||||
|
? 'min-h-[420px]'
|
||||||
|
: 'h-full min-h-[480px]',
|
||||||
tier.isPopular && !insideDialog
|
tier.isPopular && !insideDialog
|
||||||
? 'md:shadow-[0px_61px_24px_-10px_rgba(0,0,0,0.01),0px_34px_20px_-8px_rgba(0,0,0,0.05),0px_15px_15px_-6px_rgba(0,0,0,0.09),0px_4px_8px_-2px_rgba(0,0,0,0.10),0px_0px_0px_1px_rgba(0,0,0,0.08)] bg-accent'
|
? 'md:shadow-[0px_61px_24px_-10px_rgba(0,0,0,0.01),0px_34px_20px_-8px_rgba(0,0,0,0.05),0px_15px_15px_-6px_rgba(0,0,0,0.09),0px_4px_8px_-2px_rgba(0,0,0,0.10),0px_0px_0px_1px_rgba(0,0,0,0.08)] bg-accent'
|
||||||
: 'bg-[#F3F4F6] dark:bg-[#F9FAFB]/[0.02] border border-border',
|
: 'bg-[#F3F4F6] dark:bg-[#F9FAFB]/[0.02] border border-border',
|
||||||
!insideDialog && ringClass,
|
!insideDialog && ringClass,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="flex flex-col gap-4 p-4">
|
<div className={cn(
|
||||||
|
"flex flex-col gap-3",
|
||||||
|
insideDialog ? "p-3" : "p-4"
|
||||||
|
)}>
|
||||||
<p className="text-sm flex items-center gap-2">
|
<p className="text-sm flex items-center gap-2">
|
||||||
{tier.name}
|
{tier.name}
|
||||||
{tier.isPopular && (
|
{tier.isPopular && (
|
||||||
|
@ -519,54 +357,20 @@ function PricingTier({
|
||||||
{isAuthenticated && statusBadge}
|
{isAuthenticated && statusBadge}
|
||||||
</p>
|
</p>
|
||||||
<div className="flex items-baseline mt-2">
|
<div className="flex items-baseline mt-2">
|
||||||
{tier.name === 'Custom' ? (
|
<PriceDisplay price={tier.price} isCompact={insideDialog} />
|
||||||
<CustomPriceDisplay
|
|
||||||
price={getPriceValue(tier, localSelectedPlan)}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<PriceDisplay price={tier.price} />
|
|
||||||
)}
|
|
||||||
<span className="ml-2">{tier.price !== '$0' ? '/month' : ''}</span>
|
<span className="ml-2">{tier.price !== '$0' ? '/month' : ''}</span>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-sm mt-2">{tier.description}</p>
|
<p className="text-sm mt-2">{tier.description}</p>
|
||||||
|
|
||||||
{tier.name === 'Custom' && tier.upgradePlans ? (
|
<div className="inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold bg-primary/10 border-primary/20 text-primary w-fit">
|
||||||
<div className="w-full space-y-2">
|
{tier.hours}/month
|
||||||
<p className="text-xs font-medium text-muted-foreground">
|
</div>
|
||||||
Customize your monthly usage
|
|
||||||
</p>
|
|
||||||
<Select value={localSelectedPlan} onValueChange={handlePlanSelect}>
|
|
||||||
<SelectTrigger className="w-full bg-white dark:bg-background">
|
|
||||||
<SelectValue placeholder="Select a plan" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
{tier.upgradePlans.map((plan) => (
|
|
||||||
<SelectItem
|
|
||||||
key={plan.hours}
|
|
||||||
value={plan.hours}
|
|
||||||
className={
|
|
||||||
localSelectedPlan === plan.hours
|
|
||||||
? 'font-medium bg-primary/5'
|
|
||||||
: ''
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{plan.hours} - {plan.price}
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
<div className="inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold bg-primary/10 border-primary/20 text-primary w-fit">
|
|
||||||
{localSelectedPlan}/month
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold bg-primary/10 border-primary/20 text-primary w-fit">
|
|
||||||
{getDisplayedHours(tier)}/month
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="p-4 flex-grow">
|
<div className={cn(
|
||||||
|
"flex-grow",
|
||||||
|
insideDialog ? "px-3 pb-2" : "px-4 pb-3"
|
||||||
|
)}>
|
||||||
{tier.features && tier.features.length > 0 && (
|
{tier.features && tier.features.length > 0 && (
|
||||||
<ul className="space-y-3">
|
<ul className="space-y-3">
|
||||||
{tier.features.map((feature) => (
|
{tier.features.map((feature) => (
|
||||||
|
@ -581,14 +385,17 @@ function PricingTier({
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-auto p-4">
|
<div className={cn(
|
||||||
|
"mt-auto",
|
||||||
|
insideDialog ? "px-3 pt-1 pb-3" : "px-4 pt-2 pb-4"
|
||||||
|
)}>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => handleSubscribe(tierPriceId)}
|
onClick={() => handleSubscribe(tierPriceId)}
|
||||||
disabled={buttonDisabled}
|
disabled={buttonDisabled}
|
||||||
variant={buttonVariant || 'default'}
|
variant={buttonVariant || 'default'}
|
||||||
className={cn(
|
className={cn(
|
||||||
'w-full font-medium transition-all duration-200',
|
'w-full font-medium transition-all duration-200',
|
||||||
isCompact ? 'h-7 rounded-md text-xs' : 'h-10 rounded-full text-sm',
|
isCompact || insideDialog ? 'h-8 rounded-md text-xs' : 'h-10 rounded-full text-sm',
|
||||||
buttonClassName,
|
buttonClassName,
|
||||||
isPlanLoading && 'animate-pulse',
|
isPlanLoading && 'animate-pulse',
|
||||||
)}
|
)}
|
||||||
|
@ -712,14 +519,19 @@ export function PricingSection({
|
||||||
|
|
||||||
{deploymentType === 'cloud' && (
|
{deploymentType === 'cloud' && (
|
||||||
<div className={cn(
|
<div className={cn(
|
||||||
"grid gap-4 w-full max-w-6xl mx-auto",
|
"grid gap-4 w-full mx-auto",
|
||||||
{
|
{
|
||||||
"px-6": !insideDialog
|
"px-6 max-w-7xl": !insideDialog,
|
||||||
|
"max-w-5xl": insideDialog
|
||||||
},
|
},
|
||||||
(!hideFree || siteConfig.cloudPricingItems.filter((tier) => !hideFree || tier.price !== '$0').length > 2)
|
insideDialog
|
||||||
? "min-[650px]:grid-cols-2 min-[900px]:grid-cols-3" : "min-[650px]:grid-cols-2"
|
? "grid-cols-1 sm:grid-cols-2 lg:grid-cols-2 xl:grid-cols-4"
|
||||||
|
: "min-[650px]:grid-cols-2 lg:grid-cols-4",
|
||||||
|
!insideDialog && "grid-rows-1 items-stretch"
|
||||||
)}>
|
)}>
|
||||||
{siteConfig.cloudPricingItems.filter((tier) => !hideFree || tier.price !== '$0').map((tier) => (
|
{siteConfig.cloudPricingItems
|
||||||
|
.filter((tier) => !tier.hidden && (!hideFree || tier.price !== '$0'))
|
||||||
|
.map((tier) => (
|
||||||
<PricingTier
|
<PricingTier
|
||||||
key={tier.name}
|
key={tier.name}
|
||||||
tier={tier}
|
tier={tier}
|
||||||
|
|
|
@ -46,6 +46,7 @@ export interface PricingTier {
|
||||||
features: string[];
|
features: string[];
|
||||||
stripePriceId: string;
|
stripePriceId: string;
|
||||||
upgradePlans: UpgradePlan[];
|
upgradePlans: UpgradePlan[];
|
||||||
|
hidden?: boolean; // Optional property to hide plans from display while keeping them in code
|
||||||
}
|
}
|
||||||
|
|
||||||
export const siteConfig = {
|
export const siteConfig = {
|
||||||
|
@ -117,7 +118,7 @@ export const siteConfig = {
|
||||||
upgradePlans: [],
|
upgradePlans: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Pro',
|
name: 'Plus',
|
||||||
price: '$20',
|
price: '$20',
|
||||||
description: 'Everything in Free, plus:',
|
description: 'Everything in Free, plus:',
|
||||||
buttonText: 'Try Free',
|
buttonText: 'Try Free',
|
||||||
|
@ -133,47 +134,114 @@ export const siteConfig = {
|
||||||
upgradePlans: [],
|
upgradePlans: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Custom',
|
name: 'Pro',
|
||||||
price: '$50',
|
price: '$50',
|
||||||
description: 'Everything in Pro, plus:',
|
description: 'Everything in Free, plus:',
|
||||||
buttonText: 'Try Free',
|
buttonText: 'Try Free',
|
||||||
buttonColor: 'bg-secondary text-white',
|
buttonColor: 'bg-secondary text-white',
|
||||||
isPopular: false,
|
isPopular: false,
|
||||||
hours: '6 hours',
|
hours: '6 hours',
|
||||||
features: ['Suited to your needs'],
|
features: [
|
||||||
upgradePlans: [
|
'6 hours',
|
||||||
{
|
'Private projects',
|
||||||
hours: '6 hours',
|
'Access to intelligent Model (Full Suna)',
|
||||||
price: '$50',
|
|
||||||
stripePriceId: config.SUBSCRIPTION_TIERS.TIER_6_50.priceId,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
hours: '12 hours',
|
|
||||||
price: '$100',
|
|
||||||
stripePriceId: config.SUBSCRIPTION_TIERS.TIER_12_100.priceId,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
hours: '25 hours',
|
|
||||||
price: '$200',
|
|
||||||
stripePriceId: config.SUBSCRIPTION_TIERS.TIER_25_200.priceId,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
hours: '50 hours',
|
|
||||||
price: '$400',
|
|
||||||
stripePriceId: config.SUBSCRIPTION_TIERS.TIER_50_400.priceId,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
hours: '125 hours',
|
|
||||||
price: '$800',
|
|
||||||
stripePriceId: config.SUBSCRIPTION_TIERS.TIER_125_800.priceId,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
hours: '200 hours',
|
|
||||||
price: '$1000',
|
|
||||||
stripePriceId: config.SUBSCRIPTION_TIERS.TIER_200_1000.priceId,
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
stripePriceId: config.SUBSCRIPTION_TIERS.TIER_6_50.priceId,
|
stripePriceId: config.SUBSCRIPTION_TIERS.TIER_6_50.priceId,
|
||||||
|
upgradePlans: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Business',
|
||||||
|
price: '$100',
|
||||||
|
description: 'Everything in Pro, plus:',
|
||||||
|
buttonText: 'Try Free',
|
||||||
|
buttonColor: 'bg-secondary text-white',
|
||||||
|
isPopular: false,
|
||||||
|
hours: '12 hours',
|
||||||
|
features: [
|
||||||
|
'12 hours',
|
||||||
|
'Private projects',
|
||||||
|
'Access to intelligent Model (Full Suna)',
|
||||||
|
'Priority support',
|
||||||
|
],
|
||||||
|
stripePriceId: config.SUBSCRIPTION_TIERS.TIER_12_100.priceId,
|
||||||
|
upgradePlans: [],
|
||||||
|
hidden: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Ultra',
|
||||||
|
price: '$200',
|
||||||
|
description: 'Everything in Free, plus:',
|
||||||
|
buttonText: 'Try Free',
|
||||||
|
buttonColor: 'bg-primary text-white dark:text-black',
|
||||||
|
isPopular: false,
|
||||||
|
hours: '25 hours',
|
||||||
|
features: [
|
||||||
|
'25 hours',
|
||||||
|
'Private projects',
|
||||||
|
'Access to intelligent Model (Full Suna)',
|
||||||
|
],
|
||||||
|
stripePriceId: config.SUBSCRIPTION_TIERS.TIER_25_200.priceId,
|
||||||
|
upgradePlans: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Enterprise',
|
||||||
|
price: '$400',
|
||||||
|
description: 'Everything in Ultra, plus:',
|
||||||
|
buttonText: 'Try Free',
|
||||||
|
buttonColor: 'bg-secondary text-white',
|
||||||
|
isPopular: false,
|
||||||
|
hours: '50 hours',
|
||||||
|
features: [
|
||||||
|
'50 hours',
|
||||||
|
'Private projects',
|
||||||
|
'Access to intelligent Model (Full Suna)',
|
||||||
|
'Priority support',
|
||||||
|
'Custom integrations',
|
||||||
|
],
|
||||||
|
stripePriceId: config.SUBSCRIPTION_TIERS.TIER_50_400.priceId,
|
||||||
|
upgradePlans: [],
|
||||||
|
hidden: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Scale',
|
||||||
|
price: '$800',
|
||||||
|
description: 'Everything in Enterprise, plus:',
|
||||||
|
buttonText: 'Try Free',
|
||||||
|
buttonColor: 'bg-secondary text-white',
|
||||||
|
isPopular: false,
|
||||||
|
hours: '125 hours',
|
||||||
|
features: [
|
||||||
|
'125 hours',
|
||||||
|
'Private projects',
|
||||||
|
'Access to intelligent Model (Full Suna)',
|
||||||
|
'Priority support',
|
||||||
|
'Custom integrations',
|
||||||
|
'Dedicated account manager',
|
||||||
|
],
|
||||||
|
stripePriceId: config.SUBSCRIPTION_TIERS.TIER_125_800.priceId,
|
||||||
|
upgradePlans: [],
|
||||||
|
hidden: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Premium',
|
||||||
|
price: '$1000',
|
||||||
|
description: 'Everything in Scale, plus:',
|
||||||
|
buttonText: 'Try Free',
|
||||||
|
buttonColor: 'bg-secondary text-white',
|
||||||
|
isPopular: false,
|
||||||
|
hours: '200 hours',
|
||||||
|
features: [
|
||||||
|
'200 hours',
|
||||||
|
'Private projects',
|
||||||
|
'Access to intelligent Model (Full Suna)',
|
||||||
|
'Priority support',
|
||||||
|
'Custom integrations',
|
||||||
|
'Dedicated account manager',
|
||||||
|
'Custom SLA',
|
||||||
|
],
|
||||||
|
stripePriceId: config.SUBSCRIPTION_TIERS.TIER_200_1000.priceId,
|
||||||
|
upgradePlans: [],
|
||||||
|
hidden: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
companyShowcase: {
|
companyShowcase: {
|
||||||
|
|
Loading…
Reference in New Issue