popeye-cli 1.5.0 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +54 -0
- package/README.md +184 -31
- package/dist/cli/commands/create.d.ts.map +1 -1
- package/dist/cli/commands/create.js +54 -4
- package/dist/cli/commands/create.js.map +1 -1
- package/dist/cli/interactive.d.ts +29 -0
- package/dist/cli/interactive.d.ts.map +1 -1
- package/dist/cli/interactive.js +90 -7
- package/dist/cli/interactive.js.map +1 -1
- package/dist/generators/all.d.ts +4 -1
- package/dist/generators/all.d.ts.map +1 -1
- package/dist/generators/all.js +36 -316
- package/dist/generators/all.js.map +1 -1
- package/dist/generators/doc-parser.d.ts +18 -3
- package/dist/generators/doc-parser.d.ts.map +1 -1
- package/dist/generators/doc-parser.js +81 -10
- package/dist/generators/doc-parser.js.map +1 -1
- package/dist/generators/frontend-design-analyzer.d.ts +30 -0
- package/dist/generators/frontend-design-analyzer.d.ts.map +1 -0
- package/dist/generators/frontend-design-analyzer.js +208 -0
- package/dist/generators/frontend-design-analyzer.js.map +1 -0
- package/dist/generators/shared-packages.d.ts +45 -0
- package/dist/generators/shared-packages.d.ts.map +1 -0
- package/dist/generators/shared-packages.js +456 -0
- package/dist/generators/shared-packages.js.map +1 -0
- package/dist/generators/templates/index.d.ts +4 -0
- package/dist/generators/templates/index.d.ts.map +1 -1
- package/dist/generators/templates/index.js +4 -0
- package/dist/generators/templates/index.js.map +1 -1
- package/dist/generators/templates/website-components.d.ts.map +1 -1
- package/dist/generators/templates/website-components.js +36 -11
- package/dist/generators/templates/website-components.js.map +1 -1
- package/dist/generators/templates/website-config.d.ts +15 -1
- package/dist/generators/templates/website-config.d.ts.map +1 -1
- package/dist/generators/templates/website-config.js +155 -13
- package/dist/generators/templates/website-config.js.map +1 -1
- package/dist/generators/templates/website-landing.d.ts +24 -0
- package/dist/generators/templates/website-landing.d.ts.map +1 -0
- package/dist/generators/templates/website-landing.js +276 -0
- package/dist/generators/templates/website-landing.js.map +1 -0
- package/dist/generators/templates/website-layout.d.ts +42 -0
- package/dist/generators/templates/website-layout.d.ts.map +1 -0
- package/dist/generators/templates/website-layout.js +408 -0
- package/dist/generators/templates/website-layout.js.map +1 -0
- package/dist/generators/templates/website-pricing.d.ts +11 -0
- package/dist/generators/templates/website-pricing.d.ts.map +1 -0
- package/dist/generators/templates/website-pricing.js +313 -0
- package/dist/generators/templates/website-pricing.js.map +1 -0
- package/dist/generators/templates/website-sections.d.ts +102 -0
- package/dist/generators/templates/website-sections.d.ts.map +1 -0
- package/dist/generators/templates/website-sections.js +444 -0
- package/dist/generators/templates/website-sections.js.map +1 -0
- package/dist/generators/templates/website.d.ts +10 -50
- package/dist/generators/templates/website.d.ts.map +1 -1
- package/dist/generators/templates/website.js +12 -788
- package/dist/generators/templates/website.js.map +1 -1
- package/dist/generators/website-content-scanner.d.ts +37 -0
- package/dist/generators/website-content-scanner.d.ts.map +1 -0
- package/dist/generators/website-content-scanner.js +165 -0
- package/dist/generators/website-content-scanner.js.map +1 -0
- package/dist/generators/website-context.d.ts +38 -2
- package/dist/generators/website-context.d.ts.map +1 -1
- package/dist/generators/website-context.js +179 -19
- package/dist/generators/website-context.js.map +1 -1
- package/dist/generators/website-debug.d.ts +68 -0
- package/dist/generators/website-debug.d.ts.map +1 -0
- package/dist/generators/website-debug.js +93 -0
- package/dist/generators/website-debug.js.map +1 -0
- package/dist/generators/website.d.ts +2 -0
- package/dist/generators/website.d.ts.map +1 -1
- package/dist/generators/website.js +66 -4
- package/dist/generators/website.js.map +1 -1
- package/dist/generators/workspace-root.d.ts +27 -0
- package/dist/generators/workspace-root.d.ts.map +1 -0
- package/dist/generators/workspace-root.js +100 -0
- package/dist/generators/workspace-root.js.map +1 -0
- package/dist/state/index.d.ts +8 -0
- package/dist/state/index.d.ts.map +1 -1
- package/dist/state/index.js +11 -0
- package/dist/state/index.js.map +1 -1
- package/dist/types/consensus.d.ts +3 -0
- package/dist/types/consensus.d.ts.map +1 -1
- package/dist/types/consensus.js +1 -0
- package/dist/types/consensus.js.map +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +2 -0
- package/dist/types/index.js.map +1 -1
- package/dist/types/tester.d.ts +138 -0
- package/dist/types/tester.d.ts.map +1 -0
- package/dist/types/tester.js +110 -0
- package/dist/types/tester.js.map +1 -0
- package/dist/types/workflow.d.ts +151 -0
- package/dist/types/workflow.d.ts.map +1 -1
- package/dist/types/workflow.js +14 -0
- package/dist/types/workflow.js.map +1 -1
- package/dist/upgrade/handlers.d.ts +15 -0
- package/dist/upgrade/handlers.d.ts.map +1 -1
- package/dist/upgrade/handlers.js +52 -0
- package/dist/upgrade/handlers.js.map +1 -1
- package/dist/workflow/auto-fix-bundler.d.ts +37 -0
- package/dist/workflow/auto-fix-bundler.d.ts.map +1 -0
- package/dist/workflow/auto-fix-bundler.js +320 -0
- package/dist/workflow/auto-fix-bundler.js.map +1 -0
- package/dist/workflow/auto-fix.d.ts.map +1 -1
- package/dist/workflow/auto-fix.js +10 -3
- package/dist/workflow/auto-fix.js.map +1 -1
- package/dist/workflow/execution-mode.js +2 -2
- package/dist/workflow/execution-mode.js.map +1 -1
- package/dist/workflow/index.d.ts +2 -0
- package/dist/workflow/index.d.ts.map +1 -1
- package/dist/workflow/index.js +13 -0
- package/dist/workflow/index.js.map +1 -1
- package/dist/workflow/overview.d.ts.map +1 -1
- package/dist/workflow/overview.js +4 -0
- package/dist/workflow/overview.js.map +1 -1
- package/dist/workflow/plan-mode.d.ts +4 -3
- package/dist/workflow/plan-mode.d.ts.map +1 -1
- package/dist/workflow/plan-mode.js +69 -5
- package/dist/workflow/plan-mode.js.map +1 -1
- package/dist/workflow/task-workflow.d.ts +5 -0
- package/dist/workflow/task-workflow.d.ts.map +1 -1
- package/dist/workflow/task-workflow.js +172 -6
- package/dist/workflow/task-workflow.js.map +1 -1
- package/dist/workflow/tester.d.ts +120 -0
- package/dist/workflow/tester.d.ts.map +1 -0
- package/dist/workflow/tester.js +589 -0
- package/dist/workflow/tester.js.map +1 -0
- package/dist/workflow/website-strategy.d.ts +9 -0
- package/dist/workflow/website-strategy.d.ts.map +1 -1
- package/dist/workflow/website-strategy.js +73 -1
- package/dist/workflow/website-strategy.js.map +1 -1
- package/dist/workflow/website-updater.d.ts.map +1 -1
- package/dist/workflow/website-updater.js +15 -4
- package/dist/workflow/website-updater.js.map +1 -1
- package/dist/workflow/workflow-logger.d.ts +1 -1
- package/dist/workflow/workflow-logger.d.ts.map +1 -1
- package/dist/workflow/workflow-logger.js.map +1 -1
- package/package.json +1 -1
- package/src/cli/commands/create.ts +58 -4
- package/src/cli/interactive.ts +96 -7
- package/src/generators/all.ts +44 -332
- package/src/generators/doc-parser.ts +87 -10
- package/src/generators/frontend-design-analyzer.ts +261 -0
- package/src/generators/shared-packages.ts +500 -0
- package/src/generators/templates/index.ts +4 -0
- package/src/generators/templates/website-components.ts +36 -11
- package/src/generators/templates/website-config.ts +166 -13
- package/src/generators/templates/website-landing.ts +331 -0
- package/src/generators/templates/website-layout.ts +443 -0
- package/src/generators/templates/website-pricing.ts +330 -0
- package/src/generators/templates/website-sections.ts +541 -0
- package/src/generators/templates/website.ts +38 -851
- package/src/generators/website-content-scanner.ts +208 -0
- package/src/generators/website-context.ts +248 -20
- package/src/generators/website-debug.ts +130 -0
- package/src/generators/website.ts +71 -3
- package/src/generators/workspace-root.ts +113 -0
- package/src/state/index.ts +15 -0
- package/src/types/consensus.ts +3 -0
- package/src/types/index.ts +21 -0
- package/src/types/tester.ts +136 -0
- package/src/types/workflow.ts +32 -0
- package/src/upgrade/handlers.ts +65 -0
- package/src/workflow/auto-fix-bundler.ts +392 -0
- package/src/workflow/auto-fix.ts +11 -3
- package/src/workflow/execution-mode.ts +2 -2
- package/src/workflow/index.ts +13 -0
- package/src/workflow/overview.ts +6 -0
- package/src/workflow/plan-mode.ts +81 -7
- package/src/workflow/task-workflow.ts +227 -5
- package/src/workflow/tester.ts +723 -0
- package/src/workflow/website-strategy.ts +75 -1
- package/src/workflow/website-updater.ts +17 -6
- package/src/workflow/workflow-logger.ts +2 -0
- package/tests/cli/project-naming.test.ts +136 -0
- package/tests/generators/doc-parser.test.ts +121 -0
- package/tests/generators/frontend-design-analyzer.test.ts +90 -0
- package/tests/generators/quality-gate.test.ts +183 -0
- package/tests/generators/shared-packages.test.ts +83 -0
- package/tests/generators/website-components.test.ts +1 -1
- package/tests/generators/website-config.test.ts +84 -0
- package/tests/generators/website-content-scanner.test.ts +181 -0
- package/tests/generators/website-context.test.ts +109 -0
- package/tests/generators/website-debug.test.ts +77 -0
- package/tests/generators/website-landing.test.ts +188 -0
- package/tests/generators/website-pricing.test.ts +98 -0
- package/tests/generators/website-sections.test.ts +245 -0
- package/tests/generators/workspace-root.test.ts +105 -0
- package/tests/types/tester.test.ts +174 -0
- package/tests/upgrade/handlers.test.ts +162 -0
- package/tests/workflow/auto-fix-bundler.test.ts +242 -0
- package/tests/workflow/plan-mode.test.ts +111 -1
- package/tests/workflow/tester.test.ts +401 -0
- package/tests/workflow/website-strategy.test.ts +55 -0
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pricing page generator with monthly/annual toggle, feature comparison,
|
|
3
|
+
* pricing FAQ, and enterprise CTA section
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { WebsiteContentContext } from '../website-context.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Escape a string for safe use inside JSX template literals
|
|
10
|
+
*/
|
|
11
|
+
function escapeJsx(str: string): string {
|
|
12
|
+
return str
|
|
13
|
+
.replace(/\\/g, '\\\\')
|
|
14
|
+
.replace(/'/g, "\\'")
|
|
15
|
+
.replace(/`/g, '\\`')
|
|
16
|
+
.replace(/\$/g, '\\$');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Generate pricing page with optional context-driven tiers, comparison, and FAQ
|
|
21
|
+
* Rendered as client component for monthly/annual toggle
|
|
22
|
+
*/
|
|
23
|
+
export function generateWebsitePricingPage(
|
|
24
|
+
_projectName: string,
|
|
25
|
+
context?: WebsiteContentContext
|
|
26
|
+
): string {
|
|
27
|
+
const strategy = context?.strategy;
|
|
28
|
+
const tiers = context?.pricing && context.pricing.length > 0
|
|
29
|
+
? context.pricing
|
|
30
|
+
: null;
|
|
31
|
+
|
|
32
|
+
// Build tiers array
|
|
33
|
+
const tiersBlock = tiers
|
|
34
|
+
? tiers.map((t) => {
|
|
35
|
+
const featuresStr = t.features.map((f) => ` '${escapeJsx(f)}'`).join(',\n');
|
|
36
|
+
return ` {
|
|
37
|
+
name: '${escapeJsx(t.name)}',
|
|
38
|
+
price: '${escapeJsx(t.price)}',
|
|
39
|
+
period: '${t.period ? escapeJsx(t.period) : '/month'}',
|
|
40
|
+
description: '${escapeJsx(t.description)}',
|
|
41
|
+
features: [
|
|
42
|
+
${featuresStr}
|
|
43
|
+
],
|
|
44
|
+
cta: '${escapeJsx(t.cta)}',
|
|
45
|
+
featured: ${t.featured ? 'true' : 'false'},
|
|
46
|
+
}`;
|
|
47
|
+
}).join(',\n')
|
|
48
|
+
: ` {
|
|
49
|
+
name: 'Starter',
|
|
50
|
+
price: 'Free',
|
|
51
|
+
period: '',
|
|
52
|
+
description: 'Perfect for getting started',
|
|
53
|
+
features: ['Core features', 'Community support', 'Basic analytics'],
|
|
54
|
+
cta: 'Get started',
|
|
55
|
+
featured: false,
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
name: 'Pro',
|
|
59
|
+
price: '$29',
|
|
60
|
+
period: '/month',
|
|
61
|
+
description: 'For growing teams',
|
|
62
|
+
features: ['Everything in Starter', 'Priority support', 'Advanced analytics', 'Custom integrations'],
|
|
63
|
+
cta: 'Start free trial',
|
|
64
|
+
featured: true,
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
name: 'Enterprise',
|
|
68
|
+
price: 'Custom',
|
|
69
|
+
period: '',
|
|
70
|
+
description: 'For large organizations',
|
|
71
|
+
features: ['Everything in Pro', 'Dedicated support', 'SLA guarantee', 'Custom contracts'],
|
|
72
|
+
cta: 'Contact sales',
|
|
73
|
+
featured: false,
|
|
74
|
+
}`;
|
|
75
|
+
|
|
76
|
+
// Enterprise CTA from strategy
|
|
77
|
+
const enterpriseCtaText = strategy?.conversionStrategy.primaryCta.text || 'Contact Sales';
|
|
78
|
+
|
|
79
|
+
// Build feature comparison data if tiers have features
|
|
80
|
+
const hasComparison = tiers && tiers.some(t => t.features.length > 0);
|
|
81
|
+
const allFeatureNames = new Set<string>();
|
|
82
|
+
if (tiers) {
|
|
83
|
+
for (const tier of tiers) {
|
|
84
|
+
for (const feature of tier.features) {
|
|
85
|
+
// Normalize feature name (remove "FeatureName: value" to just "FeatureName")
|
|
86
|
+
const name = feature.includes(':') ? feature.split(':')[0].trim() : feature;
|
|
87
|
+
allFeatureNames.add(name);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const comparisonFeatures = Array.from(allFeatureNames).slice(0, 10);
|
|
93
|
+
const comparisonBlock = hasComparison ? buildComparisonTable(comparisonFeatures, tiers!) : '';
|
|
94
|
+
|
|
95
|
+
// Pricing FAQ
|
|
96
|
+
const pricingFaq = [
|
|
97
|
+
{ q: 'Can I switch plans later?', a: 'Yes, you can upgrade or downgrade your plan at any time. Changes take effect on your next billing cycle.' },
|
|
98
|
+
{ q: 'Is there a free trial?', a: 'Yes, all paid plans come with a 14-day free trial. No credit card required.' },
|
|
99
|
+
{ q: 'What payment methods do you accept?', a: 'We accept all major credit cards, PayPal, and bank transfers for enterprise plans.' },
|
|
100
|
+
];
|
|
101
|
+
|
|
102
|
+
const faqStr = pricingFaq
|
|
103
|
+
.map(item => ` { question: '${escapeJsx(item.q)}', answer: '${escapeJsx(item.a)}' }`)
|
|
104
|
+
.join(',\n');
|
|
105
|
+
|
|
106
|
+
return `'use client';
|
|
107
|
+
|
|
108
|
+
import { useState } from 'react';
|
|
109
|
+
import Link from 'next/link';
|
|
110
|
+
import { Check, ChevronDown } from 'lucide-react';
|
|
111
|
+
import Header from '@/components/Header';
|
|
112
|
+
import Footer from '@/components/Footer';
|
|
113
|
+
|
|
114
|
+
const tiers = [
|
|
115
|
+
${tiersBlock}
|
|
116
|
+
];
|
|
117
|
+
|
|
118
|
+
const pricingFaq = [
|
|
119
|
+
${faqStr}
|
|
120
|
+
];
|
|
121
|
+
|
|
122
|
+
function FaqItem({ question, answer }: { question: string; answer: string }) {
|
|
123
|
+
const [open, setOpen] = useState(false);
|
|
124
|
+
|
|
125
|
+
return (
|
|
126
|
+
<div className="py-4">
|
|
127
|
+
<button
|
|
128
|
+
type="button"
|
|
129
|
+
className="flex w-full items-center justify-between text-left"
|
|
130
|
+
onClick={() => setOpen(!open)}
|
|
131
|
+
aria-expanded={open}
|
|
132
|
+
>
|
|
133
|
+
<span className="text-base font-medium text-foreground">{question}</span>
|
|
134
|
+
<ChevronDown className={\`h-5 w-5 text-muted-foreground transition-transform \${open ? 'rotate-180' : ''}\`} />
|
|
135
|
+
</button>
|
|
136
|
+
{open && (
|
|
137
|
+
<p className="mt-3 text-muted-foreground">{answer}</p>
|
|
138
|
+
)}
|
|
139
|
+
</div>
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export default function PricingPage() {
|
|
144
|
+
const [annual, setAnnual] = useState(false);
|
|
145
|
+
|
|
146
|
+
return (
|
|
147
|
+
<>
|
|
148
|
+
<Header />
|
|
149
|
+
<main className="py-20 sm:py-32">
|
|
150
|
+
<div className="container">
|
|
151
|
+
{/* Header */}
|
|
152
|
+
<div className="mx-auto max-w-2xl text-center">
|
|
153
|
+
<h1 className="text-4xl font-bold tracking-tight text-foreground sm:text-5xl">
|
|
154
|
+
Simple, transparent pricing
|
|
155
|
+
</h1>
|
|
156
|
+
<p className="mt-6 text-lg text-muted-foreground">
|
|
157
|
+
Choose the plan that works best for you. All plans include a 14-day free trial.
|
|
158
|
+
</p>
|
|
159
|
+
|
|
160
|
+
{/* Monthly/Annual Toggle */}
|
|
161
|
+
<div className="mt-8 flex items-center justify-center gap-3">
|
|
162
|
+
<span className={\`text-sm font-medium \${!annual ? 'text-foreground' : 'text-muted-foreground'}\`}>
|
|
163
|
+
Monthly
|
|
164
|
+
</span>
|
|
165
|
+
<button
|
|
166
|
+
type="button"
|
|
167
|
+
role="switch"
|
|
168
|
+
aria-checked={annual}
|
|
169
|
+
onClick={() => setAnnual(!annual)}
|
|
170
|
+
className={\`relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors \${
|
|
171
|
+
annual ? 'bg-primary-600' : 'bg-muted'
|
|
172
|
+
}\`}
|
|
173
|
+
>
|
|
174
|
+
<span className={\`pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition-transform \${
|
|
175
|
+
annual ? 'translate-x-5' : 'translate-x-0'
|
|
176
|
+
}\`} />
|
|
177
|
+
</button>
|
|
178
|
+
<span className={\`text-sm font-medium \${annual ? 'text-foreground' : 'text-muted-foreground'}\`}>
|
|
179
|
+
Annual
|
|
180
|
+
<span className="ml-1.5 inline-block rounded-full bg-primary-100 px-2 py-0.5 text-xs font-semibold text-primary-700">
|
|
181
|
+
Save 20%
|
|
182
|
+
</span>
|
|
183
|
+
</span>
|
|
184
|
+
</div>
|
|
185
|
+
</div>
|
|
186
|
+
|
|
187
|
+
{/* Tier Cards */}
|
|
188
|
+
<div className="mx-auto mt-16 grid max-w-lg grid-cols-1 gap-8 lg:max-w-5xl lg:grid-cols-3">
|
|
189
|
+
{tiers.map((tier) => (
|
|
190
|
+
<div
|
|
191
|
+
key={tier.name}
|
|
192
|
+
className={\`relative rounded-2xl p-8 transition-shadow hover:shadow-lg \${
|
|
193
|
+
tier.featured
|
|
194
|
+
? 'border-2 border-primary-600 bg-card shadow-lg'
|
|
195
|
+
: 'border border-border bg-card'
|
|
196
|
+
}\`}
|
|
197
|
+
>
|
|
198
|
+
{tier.featured && (
|
|
199
|
+
<div className="absolute -top-4 left-1/2 -translate-x-1/2">
|
|
200
|
+
<span className="rounded-full bg-primary-600 px-4 py-1 text-sm font-semibold text-white">
|
|
201
|
+
Most Popular
|
|
202
|
+
</span>
|
|
203
|
+
</div>
|
|
204
|
+
)}
|
|
205
|
+
<h2 className="text-lg font-semibold text-foreground">
|
|
206
|
+
{tier.name}
|
|
207
|
+
</h2>
|
|
208
|
+
<p className="mt-2 text-sm text-muted-foreground">
|
|
209
|
+
{tier.description}
|
|
210
|
+
</p>
|
|
211
|
+
<p className="mt-6">
|
|
212
|
+
<span className="text-4xl font-bold text-foreground">
|
|
213
|
+
{tier.price === 'Custom' || tier.price === 'Free'
|
|
214
|
+
? tier.price
|
|
215
|
+
: annual
|
|
216
|
+
? \`$\${Math.round(parseFloat(tier.price.replace('$', '')) * 0.8)}\`
|
|
217
|
+
: tier.price}
|
|
218
|
+
</span>
|
|
219
|
+
{tier.period && tier.price !== 'Custom' && tier.price !== 'Free' && (
|
|
220
|
+
<span className="text-sm text-muted-foreground">
|
|
221
|
+
{annual ? '/month, billed annually' : tier.period}
|
|
222
|
+
</span>
|
|
223
|
+
)}
|
|
224
|
+
</p>
|
|
225
|
+
<ul className="mt-8 space-y-4">
|
|
226
|
+
{tier.features.map((feature) => (
|
|
227
|
+
<li key={feature} className="flex text-sm text-muted-foreground">
|
|
228
|
+
<Check className="h-5 w-5 flex-shrink-0 text-primary-600" />
|
|
229
|
+
<span className="ml-3">{feature}</span>
|
|
230
|
+
</li>
|
|
231
|
+
))}
|
|
232
|
+
</ul>
|
|
233
|
+
<button
|
|
234
|
+
className={\`mt-8 w-full rounded-lg px-4 py-2.5 text-sm font-semibold transition-colors \${
|
|
235
|
+
tier.featured
|
|
236
|
+
? 'bg-primary-600 text-white hover:bg-primary-500 shadow-lg shadow-primary-600/25'
|
|
237
|
+
: 'bg-card text-foreground border border-border hover:bg-muted'
|
|
238
|
+
}\`}
|
|
239
|
+
>
|
|
240
|
+
{tier.cta}
|
|
241
|
+
</button>
|
|
242
|
+
</div>
|
|
243
|
+
))}
|
|
244
|
+
</div>
|
|
245
|
+
|
|
246
|
+
${comparisonBlock}
|
|
247
|
+
|
|
248
|
+
{/* Pricing FAQ */}
|
|
249
|
+
<div className="mx-auto mt-20 max-w-3xl">
|
|
250
|
+
<h2 className="text-2xl font-bold text-foreground text-center">
|
|
251
|
+
Pricing FAQ
|
|
252
|
+
</h2>
|
|
253
|
+
<div className="mt-8 divide-y divide-border">
|
|
254
|
+
{pricingFaq.map((item, i) => (
|
|
255
|
+
<FaqItem key={i} question={item.question} answer={item.answer} />
|
|
256
|
+
))}
|
|
257
|
+
</div>
|
|
258
|
+
</div>
|
|
259
|
+
|
|
260
|
+
{/* Enterprise CTA */}
|
|
261
|
+
<div className="mx-auto mt-20 max-w-2xl rounded-2xl bg-muted/50 p-8 text-center sm:p-12">
|
|
262
|
+
<h2 className="text-2xl font-bold text-foreground">
|
|
263
|
+
Need a custom plan?
|
|
264
|
+
</h2>
|
|
265
|
+
<p className="mt-4 text-muted-foreground">
|
|
266
|
+
Contact our sales team for enterprise pricing, custom contracts, and dedicated support.
|
|
267
|
+
</p>
|
|
268
|
+
<Link
|
|
269
|
+
href="/contact"
|
|
270
|
+
className="mt-6 inline-block rounded-lg border border-primary-600 px-6 py-3 text-sm font-semibold text-primary-600 hover:bg-primary-50 transition-colors"
|
|
271
|
+
>
|
|
272
|
+
${escapeJsx(enterpriseCtaText)}
|
|
273
|
+
</Link>
|
|
274
|
+
</div>
|
|
275
|
+
</div>
|
|
276
|
+
</main>
|
|
277
|
+
<Footer />
|
|
278
|
+
</>
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
`;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Build feature comparison table section
|
|
286
|
+
*/
|
|
287
|
+
function buildComparisonTable(
|
|
288
|
+
featureNames: string[],
|
|
289
|
+
tiers: NonNullable<WebsiteContentContext['pricing']>
|
|
290
|
+
): string {
|
|
291
|
+
if (featureNames.length === 0) return '';
|
|
292
|
+
|
|
293
|
+
return `
|
|
294
|
+
{/* Feature Comparison */}
|
|
295
|
+
<div className="mx-auto mt-20 max-w-5xl">
|
|
296
|
+
<h2 className="text-2xl font-bold text-foreground text-center">
|
|
297
|
+
Compare plans
|
|
298
|
+
</h2>
|
|
299
|
+
<div className="mt-8 overflow-x-auto">
|
|
300
|
+
<table className="w-full border-collapse text-sm">
|
|
301
|
+
<thead>
|
|
302
|
+
<tr className="border-b border-border">
|
|
303
|
+
<th className="py-4 pr-4 text-left font-medium text-muted-foreground">Feature</th>
|
|
304
|
+
${tiers.map(t => ` <th className="px-4 py-4 text-center font-medium text-foreground">${escapeJsx(t.name)}</th>`).join('\n')}
|
|
305
|
+
</tr>
|
|
306
|
+
</thead>
|
|
307
|
+
<tbody>
|
|
308
|
+
${featureNames.map(feature => {
|
|
309
|
+
const cells = tiers.map(t => {
|
|
310
|
+
const hasFeature = t.features.some(f => f.startsWith(feature));
|
|
311
|
+
const featureValue = t.features.find(f => f.startsWith(feature));
|
|
312
|
+
const value = featureValue && featureValue.includes(':')
|
|
313
|
+
? featureValue.split(':')[1].trim()
|
|
314
|
+
: hasFeature ? 'check' : 'no';
|
|
315
|
+
return value === 'check'
|
|
316
|
+
? ' <td className="px-4 py-3 text-center"><Check className="mx-auto h-5 w-5 text-primary-600" /></td>'
|
|
317
|
+
: value === 'no'
|
|
318
|
+
? ' <td className="px-4 py-3 text-center text-muted-foreground">—</td>'
|
|
319
|
+
: ` <td className="px-4 py-3 text-center text-foreground">${escapeJsx(value)}</td>`;
|
|
320
|
+
});
|
|
321
|
+
return ` <tr className="border-b border-border">
|
|
322
|
+
<td className="py-3 pr-4 font-medium text-foreground">${escapeJsx(feature)}</td>
|
|
323
|
+
${cells.join('\n')}
|
|
324
|
+
</tr>`;
|
|
325
|
+
}).join('\n')}
|
|
326
|
+
</tbody>
|
|
327
|
+
</table>
|
|
328
|
+
</div>
|
|
329
|
+
</div>`;
|
|
330
|
+
}
|