mirror of https://github.com/kortix-ai/suna.git
feat(billing): introduce token price multiplier and update cost calculations; enhance billing UI with new pricing buttons
This commit is contained in:
parent
79b71db250
commit
e7f02f31bc
|
@ -20,6 +20,9 @@ import time
|
|||
# Initialize Stripe
|
||||
stripe.api_key = config.STRIPE_SECRET_KEY
|
||||
|
||||
# Token price multiplier
|
||||
TOKEN_PRICE_MULTIPLIER = 2
|
||||
|
||||
# Initialize router
|
||||
router = APIRouter(prefix="/billing", tags=["billing"])
|
||||
|
||||
|
@ -278,8 +281,8 @@ async def calculate_monthly_usage(client, user_id: str) -> float:
|
|||
|
||||
total_cost += message_cost
|
||||
|
||||
# Return total cost * 2 (as per original logic)
|
||||
total_cost = total_cost * 2
|
||||
# Return total cost * TOKEN_PRICE_MULTIPLIER (as per original logic)
|
||||
total_cost = total_cost * TOKEN_PRICE_MULTIPLIER
|
||||
logger.info(f"Total cost for user {user_id}: {total_cost}")
|
||||
|
||||
return total_cost
|
||||
|
@ -1038,8 +1041,8 @@ async def get_available_models(
|
|||
if hardcoded_pricing:
|
||||
input_cost_per_million, output_cost_per_million = hardcoded_pricing
|
||||
pricing_info = {
|
||||
"input_cost_per_million_tokens": input_cost_per_million,
|
||||
"output_cost_per_million_tokens": output_cost_per_million,
|
||||
"input_cost_per_million_tokens": input_cost_per_million * TOKEN_PRICE_MULTIPLIER,
|
||||
"output_cost_per_million_tokens": output_cost_per_million * TOKEN_PRICE_MULTIPLIER,
|
||||
"max_tokens": None
|
||||
}
|
||||
else:
|
||||
|
@ -1090,8 +1093,8 @@ async def get_available_models(
|
|||
|
||||
if input_cost_per_token is not None and output_cost_per_token is not None:
|
||||
pricing_info = {
|
||||
"input_cost_per_million_tokens": round(input_cost_per_token * 2, 2),
|
||||
"output_cost_per_million_tokens": round(output_cost_per_token * 2, 2),
|
||||
"input_cost_per_million_tokens": input_cost_per_token * TOKEN_PRICE_MULTIPLIER,
|
||||
"output_cost_per_million_tokens": output_cost_per_token * TOKEN_PRICE_MULTIPLIER,
|
||||
"max_tokens": None # cost_per_token doesn't provide max_tokens info
|
||||
}
|
||||
else:
|
||||
|
|
|
@ -1,226 +1,183 @@
|
|||
'use client';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { SectionHeader } from '@/components/home/section-header';
|
||||
import { getAvailableModels, Model } from '@/lib/api';
|
||||
import { Loader2, AlertCircle } from 'lucide-react';
|
||||
import { AlertCircle } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { useAvailableModels } from '@/hooks/react-query/subscriptions/use-billing';
|
||||
import type { Model } from '@/lib/api';
|
||||
import { Loader2 } from 'lucide-react';
|
||||
import Link from 'next/link';
|
||||
|
||||
interface PricingTableRowProps {
|
||||
interface ModelCardProps {
|
||||
model: Model;
|
||||
index: number;
|
||||
}
|
||||
|
||||
function PricingTableRow({ model, index }: PricingTableRowProps) {
|
||||
function ModelCard({ model, index }: ModelCardProps) {
|
||||
return (
|
||||
<tr
|
||||
className="border-b border-border/50 transition-all duration-200 hover:bg-muted/30"
|
||||
<div
|
||||
className="group relative bg-gradient-to-br from-background to-background/80 border border-border/50 rounded-2xl p-6 hover:border-border transition-all duration-300 hover:shadow-xl hover:shadow-black/5 hover:-translate-y-1"
|
||||
style={{ animationDelay: `${index * 100}ms` }}
|
||||
>
|
||||
<td className="py-6 px-6">
|
||||
<div>
|
||||
<div className="font-semibold text-foreground capitalize">
|
||||
<div className="space-y-4">
|
||||
{/* Model Header */}
|
||||
<div className="space-y-2">
|
||||
<h3 className="text-xl font-bold text-foreground group-hover:text-blue-600 transition-colors duration-300">
|
||||
{model.display_name}
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
{/* Pricing Grid */}
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
{/* Input Cost */}
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-2 h-2 bg-green-500 rounded-full"></div>
|
||||
<span className="text-sm font-medium text-muted-foreground">
|
||||
Input
|
||||
</span>
|
||||
</div>
|
||||
{model.input_cost_per_million_tokens !== null &&
|
||||
model.input_cost_per_million_tokens !== undefined ? (
|
||||
<>
|
||||
<div className="text-2xl font-bold text-foreground">
|
||||
${model.input_cost_per_million_tokens.toFixed(2)}
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
per 1M tokens
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div className="text-2xl font-bold text-muted-foreground">—</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="text-sm text-muted-foreground font-mono truncate max-w-[200px]">
|
||||
{model.id}
|
||||
|
||||
{/* Output Cost */}
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-2 h-2 bg-blue-500 rounded-full"></div>
|
||||
<span className="text-sm font-medium text-muted-foreground">
|
||||
Output
|
||||
</span>
|
||||
</div>
|
||||
{model.output_cost_per_million_tokens !== null &&
|
||||
model.output_cost_per_million_tokens !== undefined ? (
|
||||
<>
|
||||
<div className="text-2xl font-bold text-foreground">
|
||||
${model.output_cost_per_million_tokens.toFixed(2)}
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
per 1M tokens
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div className="text-2xl font-bold text-muted-foreground">—</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td className="py-6 px-6 text-right">
|
||||
{model.input_cost_per_million_tokens !== null &&
|
||||
model.input_cost_per_million_tokens !== undefined ? (
|
||||
<>
|
||||
<div className="font-semibold text-foreground">
|
||||
${model.input_cost_per_million_tokens.toFixed(2)}
|
||||
</div>
|
||||
<div className="text-sm text-muted-foreground">per 1M tokens</div>
|
||||
</>
|
||||
) : (
|
||||
<div className="text-muted-foreground">—</div>
|
||||
)}
|
||||
</td>
|
||||
<td className="py-6 px-6 text-right">
|
||||
{model.output_cost_per_million_tokens !== null &&
|
||||
model.output_cost_per_million_tokens !== undefined ? (
|
||||
<>
|
||||
<div className="font-semibold text-foreground">
|
||||
${model.output_cost_per_million_tokens.toFixed(2)}
|
||||
</div>
|
||||
<div className="text-sm text-muted-foreground">per 1M tokens</div>
|
||||
</>
|
||||
) : (
|
||||
<div className="text-muted-foreground">—</div>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
export default function PricingPage() {
|
||||
const [models, setModels] = useState<Model[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchPricing = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
const response = await getAvailableModels();
|
||||
// Filter to only show models that have pricing information available
|
||||
const filteredModels = response.models.filter((model) => {
|
||||
return (
|
||||
model.input_cost_per_million_tokens !== null &&
|
||||
model.input_cost_per_million_tokens !== undefined &&
|
||||
model.output_cost_per_million_tokens !== null &&
|
||||
model.output_cost_per_million_tokens !== undefined
|
||||
);
|
||||
});
|
||||
setModels(filteredModels);
|
||||
} catch (err) {
|
||||
console.error('Error fetching model pricing:', err);
|
||||
|
||||
// If there's an authentication error, show fallback data for the specific models requested
|
||||
if (
|
||||
err instanceof Error &&
|
||||
(err.message.includes('token') || err.message.includes('auth'))
|
||||
) {
|
||||
const fallbackModels: Model[] = [
|
||||
{
|
||||
id: 'anthropic/claude-sonnet-4-20250514',
|
||||
display_name: 'Claude Sonnet 4',
|
||||
requires_subscription: true,
|
||||
is_available: false,
|
||||
input_cost_per_million_tokens: 3.0,
|
||||
output_cost_per_million_tokens: 15.0,
|
||||
max_tokens: 200000,
|
||||
},
|
||||
{
|
||||
id: 'openrouter/qwen/qwen3-235b-a22b',
|
||||
display_name: 'Qwen3 A22B',
|
||||
requires_subscription: false,
|
||||
is_available: false,
|
||||
input_cost_per_million_tokens: 0.4,
|
||||
output_cost_per_million_tokens: 0.4,
|
||||
max_tokens: 32768,
|
||||
},
|
||||
{
|
||||
id: 'openrouter/google/gemini-2.5-flash-preview-05-20',
|
||||
display_name: 'Gemini Flash 2.5',
|
||||
requires_subscription: false,
|
||||
is_available: false,
|
||||
input_cost_per_million_tokens: 0.075,
|
||||
output_cost_per_million_tokens: 0.3,
|
||||
max_tokens: 1000000,
|
||||
},
|
||||
{
|
||||
id: 'openrouter/deepseek/deepseek-chat',
|
||||
display_name: 'DeepSeek Chat',
|
||||
requires_subscription: false,
|
||||
is_available: false,
|
||||
input_cost_per_million_tokens: 0.14,
|
||||
output_cost_per_million_tokens: 0.28,
|
||||
max_tokens: 65536,
|
||||
},
|
||||
];
|
||||
setModels(fallbackModels);
|
||||
} else {
|
||||
setError(
|
||||
err instanceof Error
|
||||
? err.message
|
||||
: 'Failed to fetch model pricing',
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchPricing();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="relative min-h-screen w-full">
|
||||
{/* Header */}
|
||||
<SectionHeader>
|
||||
<div className="text-center space-y-4">
|
||||
<div className="inline-flex items-center px-3 py-1.5 rounded-full border border-border bg-background/50 backdrop-blur-sm">
|
||||
<span className="text-sm font-medium text-muted-foreground">
|
||||
💰 MODEL PRICING
|
||||
</span>
|
||||
</div>
|
||||
<h1 className="text-4xl md:text-5xl font-bold bg-gradient-to-br from-foreground to-foreground/70 bg-clip-text text-transparent">
|
||||
AI Model Pricing
|
||||
</h1>
|
||||
<p className="text-xl text-muted-foreground max-w-2xl mx-auto leading-relaxed">
|
||||
Compare pricing across our supported AI models. All prices are shown
|
||||
per million tokens.
|
||||
</p>
|
||||
</div>
|
||||
</SectionHeader>
|
||||
|
||||
{/* Content */}
|
||||
<div className="relative z-10 max-w-7xl mx-auto px-6 py-12">
|
||||
{loading ? (
|
||||
<div className="flex items-center justify-center py-20">
|
||||
<div className="flex flex-col items-center gap-4">
|
||||
<Loader2 className="w-8 h-8 animate-spin text-blue-500" />
|
||||
<p className="text-muted-foreground">Loading model pricing...</p>
|
||||
</div>
|
||||
</div>
|
||||
) : error ? (
|
||||
<div className="flex items-center justify-center py-20">
|
||||
<div className="flex flex-col items-center gap-4 text-center">
|
||||
<div className="w-16 h-16 rounded-full bg-red-500/10 border border-red-500/20 flex items-center justify-center">
|
||||
<AlertCircle className="w-8 h-8 text-red-500" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-xl font-semibold text-foreground mb-2">
|
||||
Failed to load pricing
|
||||
</h3>
|
||||
<p className="text-muted-foreground mb-4">{error}</p>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => window.location.reload()}
|
||||
className="hover:bg-muted/50"
|
||||
>
|
||||
Try Again
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-8">
|
||||
{/* Pricing Table */}
|
||||
<div className="border border-border rounded-xl bg-background/50 backdrop-blur-sm overflow-hidden">
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full">
|
||||
<thead>
|
||||
<tr className="border-b border-border bg-muted/30">
|
||||
<th className="text-left py-4 px-6 font-semibold text-foreground">
|
||||
Model
|
||||
</th>
|
||||
<th className="text-right py-4 px-6 font-semibold text-foreground">
|
||||
Input Cost
|
||||
</th>
|
||||
<th className="text-right py-4 px-6 font-semibold text-foreground">
|
||||
Output Cost
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{models.map((model, index) => (
|
||||
<PricingTableRow
|
||||
key={model.id}
|
||||
model={model}
|
||||
index={index}
|
||||
/>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{/* Model Stats */}
|
||||
{model.max_tokens && (
|
||||
<div className="pt-4 border-t border-border/50">
|
||||
<div className="flex items-center justify-between text-sm">
|
||||
<span className="text-muted-foreground">Max Tokens</span>
|
||||
<span className="font-medium text-foreground">
|
||||
{model.max_tokens.toLocaleString()}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function PricingPage() {
|
||||
const {
|
||||
data: modelsResponse,
|
||||
isLoading: loading,
|
||||
error,
|
||||
refetch,
|
||||
} = useAvailableModels();
|
||||
|
||||
// Filter to only show models that have pricing information available
|
||||
const models =
|
||||
modelsResponse?.models?.filter((model: Model) => {
|
||||
return (
|
||||
model.input_cost_per_million_tokens !== null &&
|
||||
model.input_cost_per_million_tokens !== undefined &&
|
||||
model.output_cost_per_million_tokens !== null &&
|
||||
model.output_cost_per_million_tokens !== undefined
|
||||
);
|
||||
}) || [];
|
||||
|
||||
return (
|
||||
<div className="relative min-h-screen w-full bg-gradient-to-br from-background via-background to-background/90">
|
||||
{/* Animated Background */}
|
||||
<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_top,_var(--tw-gradient-stops))] from-blue-50/20 via-transparent to-purple-50/20 dark:from-blue-950/20 dark:to-purple-950/20"></div>
|
||||
|
||||
{/* Header */}
|
||||
<SectionHeader>
|
||||
<div className="text-center space-y-6">
|
||||
<div className="space-y-4">
|
||||
<h1 className="text-5xl md:text-6xl font-bold bg-gradient-to-br from-foreground via-foreground/90 to-foreground/70 bg-clip-text text-transparent">
|
||||
Compute Pricing
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
</SectionHeader>
|
||||
|
||||
{/* Content */}
|
||||
<div className="relative z-10 max-w-7xl mx-auto px-6 pb-20 pt-4">
|
||||
{loading ? (
|
||||
<div className="flex items-center justify-center py-32">
|
||||
<div className="flex flex-col items-center gap-6">
|
||||
<div className="relative">
|
||||
<div className="w-16 h-16 border-4 border-blue-200 dark:border-blue-800 rounded-full animate-spin border-t-blue-500"></div>
|
||||
<div className="absolute inset-0 w-16 h-16 border-4 border-transparent rounded-full animate-ping border-t-blue-500/20"></div>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<p className="text-lg font-medium text-foreground">
|
||||
Loading compute pricing
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Fetching the latest pricing data...
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : error ? (
|
||||
<div className="flex items-center justify-center py-32">
|
||||
<div className="max-w-md text-center space-y-6">
|
||||
<div className="w-20 h-20 mx-auto rounded-full bg-gradient-to-br from-red-50 to-red-100 dark:from-red-950/50 dark:to-red-900/50 border border-red-200 dark:border-red-800 flex items-center justify-center">
|
||||
<AlertCircle className="w-10 h-10 text-red-500" />
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
<h3 className="text-2xl font-bold text-foreground">
|
||||
Pricing Unavailable
|
||||
</h3>
|
||||
<p className="text-muted-foreground leading-relaxed">
|
||||
{error instanceof Error
|
||||
? error.message
|
||||
: 'Failed to fetch model pricing'}
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
onClick={() => refetch()}
|
||||
className="bg-gradient-to-r from-blue-500 to-purple-600 hover:from-blue-600 hover:to-purple-700 text-white border-0 shadow-lg hover:shadow-xl transition-all duration-300"
|
||||
>
|
||||
<Loader2 className="w-4 h-4 mr-2" />
|
||||
Try Again
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-12">
|
||||
{/* Model Cards Grid */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{models.map((model, index) => (
|
||||
<ModelCard key={model.id} model={model} index={index} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
@ -122,6 +122,13 @@ export default function AccountBillingStatus({ accountId, returnUrl }: Props) {
|
|||
|
||||
<div className="mt-20"></div>
|
||||
{/* Manage Subscription Button */}
|
||||
<Button
|
||||
onClick={() => window.open('/model-pricing', '_blank')}
|
||||
variant="outline"
|
||||
className="w-full border-border hover:bg-muted/50 shadow-sm hover:shadow-md transition-all mb-3"
|
||||
>
|
||||
View Compute Pricing
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleManageSubscription}
|
||||
disabled={isManaging}
|
||||
|
@ -161,11 +168,11 @@ export default function AccountBillingStatus({ accountId, returnUrl }: Props) {
|
|||
{/* Action Buttons */}
|
||||
<div className="space-y-3">
|
||||
<Button
|
||||
onClick={() => window.open('/models', '_blank')}
|
||||
onClick={() => window.open('/model-pricing', '_blank')}
|
||||
variant="outline"
|
||||
className="w-full border-border hover:bg-muted/50 shadow-sm hover:shadow-md transition-all"
|
||||
>
|
||||
View Model Pricing
|
||||
View Compute Pricing
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleManageSubscription}
|
||||
|
|
|
@ -88,7 +88,7 @@ export function BillingModal({ open, onOpenChange, returnUrl = window?.location?
|
|||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="max-w-4xl max-h-[80vh] overflow-y-auto">
|
||||
<DialogContent className="max-w-5xl max-h-[80vh] overflow-y-auto">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Upgrade Your Plan</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
@ -127,7 +127,7 @@ export function BillingModal({ open, onOpenChange, returnUrl = window?.location?
|
|||
<Button
|
||||
onClick={handleManageSubscription}
|
||||
disabled={isManaging}
|
||||
className="w-full bg-primary hover:bg-primary/90 shadow-md hover:shadow-lg transition-all mt-4"
|
||||
className="max-w-xs mx-auto w-full bg-primary hover:bg-primary/90 shadow-md hover:shadow-lg transition-all mt-4"
|
||||
>
|
||||
{isManaging ? 'Loading...' : 'Manage Subscription'}
|
||||
</Button>
|
||||
|
|
|
@ -491,7 +491,7 @@ export default function UsageLogs({ accountId }: Props) {
|
|||
<TableHead>Time</TableHead>
|
||||
<TableHead>Model</TableHead>
|
||||
<TableHead className="text-right">
|
||||
Tokens
|
||||
Compute
|
||||
</TableHead>
|
||||
<TableHead className="text-right">Cost</TableHead>
|
||||
<TableHead className="text-center">
|
||||
|
|
|
@ -278,7 +278,7 @@ function PricingTier({
|
|||
isScheduled && currentSubscription?.scheduled_price_id === tierPriceId;
|
||||
const isPlanLoading = isLoading[tierPriceId];
|
||||
|
||||
let buttonText = isAuthenticated ? 'Select Plan' : 'Try Free';
|
||||
let buttonText = isAuthenticated ? 'Select Plan' : 'Start Free';
|
||||
let buttonDisabled = isPlanLoading;
|
||||
let buttonVariant: ButtonVariant = null;
|
||||
let ringClass = '';
|
||||
|
@ -480,7 +480,7 @@ function PricingTier({
|
|||
|
||||
{billingPeriod === 'yearly' && tier.yearlyPrice && tier.discountPercentage ? (
|
||||
<div className="inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold bg-green-50 border-green-200 text-green-700 w-fit">
|
||||
Save ${Math.round((parseFloat(tier.originalYearlyPrice?.slice(1) || '0') - parseFloat(tier.yearlyPrice.slice(1))) / 12)} per month
|
||||
Save ${Math.round(parseFloat(tier.originalYearlyPrice?.slice(1) || '0') - parseFloat(tier.yearlyPrice.slice(1)))} per year
|
||||
</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">
|
||||
|
@ -631,7 +631,7 @@ export function PricingSection({
|
|||
return (
|
||||
<section
|
||||
id="pricing"
|
||||
className={cn("flex flex-col items-center justify-center gap-10 w-full relative", { "pb-20": !insideDialog })}
|
||||
className={cn("flex flex-col items-center justify-center gap-10 w-full relative")}
|
||||
>
|
||||
{showTitleAndTabs && (
|
||||
<>
|
||||
|
|
|
@ -116,7 +116,7 @@ export const siteConfig = {
|
|||
name: 'Free',
|
||||
price: '$0',
|
||||
description: 'Get started with',
|
||||
buttonText: 'Try Free',
|
||||
buttonText: 'Start Free',
|
||||
buttonColor: 'bg-secondary text-white',
|
||||
isPopular: false,
|
||||
/** @deprecated */
|
||||
|
@ -136,7 +136,7 @@ export const siteConfig = {
|
|||
originalYearlyPrice: '$240',
|
||||
discountPercentage: 15,
|
||||
description: 'Everything in Free, plus:',
|
||||
buttonText: 'Try Free',
|
||||
buttonText: 'Start Free',
|
||||
buttonColor: 'bg-primary text-white dark:text-black',
|
||||
isPopular: true,
|
||||
/** @deprecated */
|
||||
|
@ -157,7 +157,7 @@ export const siteConfig = {
|
|||
originalYearlyPrice: '$600',
|
||||
discountPercentage: 15,
|
||||
description: 'Everything in Free, plus:',
|
||||
buttonText: 'Try Free',
|
||||
buttonText: 'Start Free',
|
||||
buttonColor: 'bg-secondary text-white',
|
||||
isPopular: false,
|
||||
/** @deprecated */
|
||||
|
@ -178,7 +178,7 @@ export const siteConfig = {
|
|||
originalYearlyPrice: '$1200',
|
||||
discountPercentage: 15,
|
||||
description: 'Everything in Pro, plus:',
|
||||
buttonText: 'Try Free',
|
||||
buttonText: 'Start Free',
|
||||
buttonColor: 'bg-secondary text-white',
|
||||
isPopular: false,
|
||||
hours: '12 hours',
|
||||
|
@ -200,7 +200,7 @@ export const siteConfig = {
|
|||
originalYearlyPrice: '$2400',
|
||||
discountPercentage: 15,
|
||||
description: 'Everything in Free, plus:',
|
||||
buttonText: 'Try Free',
|
||||
buttonText: 'Start Free',
|
||||
buttonColor: 'bg-primary text-white dark:text-black',
|
||||
isPopular: false,
|
||||
hours: '25 hours',
|
||||
|
@ -220,7 +220,7 @@ export const siteConfig = {
|
|||
originalYearlyPrice: '$4800',
|
||||
discountPercentage: 15,
|
||||
description: 'Everything in Ultra, plus:',
|
||||
buttonText: 'Try Free',
|
||||
buttonText: 'Start Free',
|
||||
buttonColor: 'bg-secondary text-white',
|
||||
isPopular: false,
|
||||
hours: '50 hours',
|
||||
|
@ -243,7 +243,7 @@ export const siteConfig = {
|
|||
originalYearlyPrice: '$9600',
|
||||
discountPercentage: 15,
|
||||
description: 'Everything in Enterprise, plus:',
|
||||
buttonText: 'Try Free',
|
||||
buttonText: 'Start Free',
|
||||
buttonColor: 'bg-secondary text-white',
|
||||
isPopular: false,
|
||||
hours: '125 hours',
|
||||
|
@ -267,7 +267,7 @@ export const siteConfig = {
|
|||
originalYearlyPrice: '$12000',
|
||||
discountPercentage: 15,
|
||||
description: 'Everything in Scale, plus:',
|
||||
buttonText: 'Try Free',
|
||||
buttonText: 'Start Free',
|
||||
buttonColor: 'bg-secondary text-white',
|
||||
isPopular: false,
|
||||
hours: '200 hours',
|
||||
|
|
Loading…
Reference in New Issue