@windrun-huaiin/third-ui 5.14.2 → 6.1.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/dist/clerk/index.d.mts +2 -21
- package/dist/clerk/index.d.ts +2 -21
- package/dist/clerk/index.js +5 -2884
- package/dist/clerk/index.js.map +1 -1
- package/dist/clerk/index.mjs +3 -2872
- package/dist/clerk/index.mjs.map +1 -1
- package/dist/clerk/server.d.mts +28 -0
- package/dist/clerk/server.d.ts +28 -0
- package/dist/clerk/server.js +3025 -0
- package/dist/clerk/server.js.map +1 -0
- package/dist/clerk/server.mjs +2991 -0
- package/dist/clerk/server.mjs.map +1 -0
- package/dist/fuma/mdx/index.d.mts +1 -12
- package/dist/fuma/mdx/index.d.ts +1 -12
- package/dist/fuma/mdx/index.js +47 -262
- package/dist/fuma/mdx/index.js.map +1 -1
- package/dist/fuma/mdx/index.mjs +48 -261
- package/dist/fuma/mdx/index.mjs.map +1 -1
- package/dist/fuma/server.d.mts +15 -2
- package/dist/fuma/server.d.ts +15 -2
- package/dist/fuma/server.js +234 -49
- package/dist/fuma/server.js.map +1 -1
- package/dist/fuma/server.mjs +231 -48
- package/dist/fuma/server.mjs.map +1 -1
- package/dist/lib/server.d.mts +509 -465
- package/dist/lib/server.d.ts +509 -465
- package/dist/main/index.d.mts +5 -56
- package/dist/main/index.d.ts +5 -56
- package/dist/main/index.js +646 -1322
- package/dist/main/index.js.map +1 -1
- package/dist/main/index.mjs +675 -1342
- package/dist/main/index.mjs.map +1 -1
- package/dist/main/server.d.mts +64 -0
- package/dist/main/server.d.ts +64 -0
- package/dist/main/server.js +4166 -0
- package/dist/main/server.js.map +1 -0
- package/dist/main/server.mjs +4128 -0
- package/dist/main/server.mjs.map +1 -0
- package/package.json +12 -2
- package/src/clerk/clerk-organization-client.tsx +50 -0
- package/src/clerk/clerk-organization.tsx +21 -38
- package/src/clerk/clerk-page-generator.tsx +0 -2
- package/src/clerk/clerk-provider-client.tsx +1 -1
- package/src/clerk/clerk-user-client.tsx +64 -0
- package/src/clerk/clerk-user.tsx +29 -58
- package/src/clerk/index.ts +1 -4
- package/src/clerk/server.ts +3 -0
- package/src/fuma/{mdx/fuma-banner-suit.tsx → fuma-banner-suit.tsx} +5 -7
- package/src/fuma/mdx/banner.tsx +51 -52
- package/src/fuma/mdx/index.ts +0 -2
- package/src/fuma/mdx/toc-footer-wrapper.tsx +1 -0
- package/src/fuma/mdx/zia-file.tsx +0 -1
- package/src/fuma/server.ts +3 -1
- package/src/fuma/{mdx/site-x.tsx → site-x.tsx} +4 -5
- package/src/main/cta.tsx +33 -10
- package/src/main/faq-interactive.tsx +68 -0
- package/src/main/faq.tsx +62 -38
- package/src/main/features.tsx +40 -11
- package/src/main/footer.tsx +27 -16
- package/src/main/gallery-interactive.tsx +171 -0
- package/src/main/gallery.tsx +54 -101
- package/src/main/index.ts +1 -10
- package/src/main/language-detector.tsx +175 -0
- package/src/main/price-plan-interactive.tsx +273 -0
- package/src/main/price-plan.tsx +112 -129
- package/src/main/seo-content.tsx +46 -13
- package/src/main/server.ts +10 -0
- package/src/main/tips.tsx +48 -22
- package/src/main/usage.tsx +43 -11
package/src/main/price-plan.tsx
CHANGED
|
@@ -1,14 +1,10 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
-
'use client';
|
|
3
|
-
|
|
4
|
-
import React, { useState } from 'react'
|
|
5
|
-
import { useTranslations } from 'next-intl'
|
|
6
2
|
import { cn } from '@lib/utils';
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
3
|
+
import { getTranslations } from 'next-intl/server';
|
|
4
|
+
import { PricePlanInteractive } from './price-plan-interactive';
|
|
9
5
|
|
|
10
6
|
export interface PricePlanProps {
|
|
11
|
-
|
|
7
|
+
locale: string
|
|
12
8
|
currency?: string
|
|
13
9
|
pricePlanConfig: PricePlanAppConfig
|
|
14
10
|
sectionClassName?: string
|
|
@@ -30,9 +26,10 @@ export interface PricePlanAppConfig {
|
|
|
30
26
|
minPlanFeaturesCount: number
|
|
31
27
|
}
|
|
32
28
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
29
|
+
interface PricePlanData {
|
|
30
|
+
title: string;
|
|
31
|
+
subtitle: string;
|
|
32
|
+
billingSwitch: {
|
|
36
33
|
options: Array<{
|
|
37
34
|
key: string
|
|
38
35
|
name: string
|
|
@@ -41,175 +38,172 @@ export function PricePlan({ currency = '$', pricePlanConfig, sectionClassName }:
|
|
|
41
38
|
subTitle?: string
|
|
42
39
|
}>
|
|
43
40
|
defaultKey: string
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
const billingOptions = pricePlanConfig.billingOptions
|
|
50
|
-
const prices = pricePlanConfig.prices
|
|
51
|
-
const minPlanFeaturesCount = pricePlanConfig.minPlanFeaturesCount
|
|
52
|
-
|
|
53
|
-
// current billing key
|
|
54
|
-
const [billingKey, setBillingKey] = useState(billingSwitch.defaultKey)
|
|
41
|
+
};
|
|
42
|
+
plans: Array<any>;
|
|
43
|
+
currency: string;
|
|
44
|
+
pricePlanConfig: PricePlanAppConfig;
|
|
45
|
+
}
|
|
55
46
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
47
|
+
export async function PricePlan({
|
|
48
|
+
locale,
|
|
49
|
+
currency = '$',
|
|
50
|
+
pricePlanConfig,
|
|
51
|
+
sectionClassName
|
|
52
|
+
}: PricePlanProps) {
|
|
53
|
+
const t = await getTranslations({ locale, namespace: 'pricePlan' })
|
|
54
|
+
|
|
55
|
+
const data: PricePlanData = {
|
|
56
|
+
title: t('title'),
|
|
57
|
+
subtitle: t('subtitle'),
|
|
58
|
+
billingSwitch: t.raw('billingSwitch') as {
|
|
59
|
+
options: Array<{
|
|
60
|
+
key: string
|
|
61
|
+
name: string
|
|
62
|
+
unit: string
|
|
63
|
+
discountText: string
|
|
64
|
+
subTitle?: string
|
|
65
|
+
}>
|
|
66
|
+
defaultKey: string
|
|
67
|
+
},
|
|
68
|
+
plans: t.raw('plans') as Array<any>,
|
|
69
|
+
currency,
|
|
70
|
+
pricePlanConfig
|
|
71
|
+
};
|
|
63
72
|
|
|
64
|
-
//
|
|
65
|
-
const
|
|
66
|
-
const
|
|
73
|
+
// Static data processing for server-side rendering
|
|
74
|
+
const billingOptions = data.pricePlanConfig.billingOptions;
|
|
75
|
+
const prices = data.pricePlanConfig.prices;
|
|
76
|
+
const minPlanFeaturesCount = data.pricePlanConfig.minPlanFeaturesCount;
|
|
77
|
+
|
|
78
|
+
// Use default billing for static rendering
|
|
79
|
+
const defaultBilling = billingOptions.find((opt: any) => opt.key === data.billingSwitch.defaultKey) || billingOptions[0];
|
|
80
|
+
const defaultBillingDisplay = data.billingSwitch.options.find((opt: any) => opt.key === data.billingSwitch.defaultKey) || data.billingSwitch.options[0];
|
|
67
81
|
|
|
68
|
-
//
|
|
82
|
+
// Calculate features count
|
|
69
83
|
const maxFeaturesCount = Math.max(
|
|
70
|
-
...plans.map((plan: any) => plan.features?.length || 0),
|
|
84
|
+
...data.plans.map((plan: any) => plan.features?.length || 0),
|
|
71
85
|
minPlanFeaturesCount || 0
|
|
72
|
-
)
|
|
86
|
+
);
|
|
73
87
|
|
|
74
|
-
//
|
|
88
|
+
// Handle card height alignment
|
|
75
89
|
const getFeatureRows = (plan: any) => {
|
|
76
|
-
const features = plan.features || []
|
|
77
|
-
const filled = [...features]
|
|
78
|
-
while (filled.length < maxFeaturesCount) filled.push(null)
|
|
79
|
-
return filled
|
|
80
|
-
}
|
|
90
|
+
const features = plan.features || [];
|
|
91
|
+
const filled = [...features];
|
|
92
|
+
while (filled.length < maxFeaturesCount) filled.push(null);
|
|
93
|
+
return filled;
|
|
94
|
+
};
|
|
81
95
|
|
|
82
|
-
// price render logic
|
|
83
|
-
function renderPrice(plan: any) {
|
|
96
|
+
// Static price render logic for default billing
|
|
97
|
+
function renderPrice(plan: any, billingKey = data.billingSwitch.defaultKey) {
|
|
84
98
|
const priceValue = prices[plan.key];
|
|
85
|
-
|
|
86
|
-
const
|
|
87
|
-
|
|
99
|
+
const currentBilling = billingOptions.find((opt: any) => opt.key === billingKey) || defaultBilling;
|
|
100
|
+
const currentBillingDisplay = data.billingSwitch.options.find((opt: any) => opt.key === billingKey) || defaultBillingDisplay;
|
|
101
|
+
const billingSubTitle = currentBillingDisplay?.subTitle || '';
|
|
102
|
+
|
|
103
|
+
// Non-numeric (like 'Custom') directly display
|
|
88
104
|
if (typeof priceValue !== 'number' || isNaN(priceValue)) {
|
|
89
105
|
return (
|
|
90
|
-
<div className="flex flex-col items-start w-full">
|
|
106
|
+
<div className="flex flex-col items-start w-full" data-price-container={plan.key}>
|
|
91
107
|
<div className="flex items-end gap-2">
|
|
92
|
-
<span className="text-4xl font-extrabold text-gray-900 dark:text-gray-100">{priceValue}</span>
|
|
108
|
+
<span className="text-4xl font-extrabold text-gray-900 dark:text-gray-100" data-price-value={plan.key}>{priceValue}</span>
|
|
93
109
|
</div>
|
|
94
110
|
<div className="flex items-center gap-2 min-h-[24px] mt-1">
|
|
95
|
-
<span className={cn('text-xs text-gray-700 dark:text-gray-300 font-medium', plan.showBillingSubTitle === false && 'opacity-0 select-none')}>
|
|
111
|
+
<span className={cn('text-xs text-gray-700 dark:text-gray-300 font-medium', plan.showBillingSubTitle === false && 'opacity-0 select-none')} data-price-subtitle={plan.key}>
|
|
96
112
|
{plan.showBillingSubTitle === false ? '' : billingSubTitle}
|
|
97
113
|
</span>
|
|
98
114
|
</div>
|
|
99
115
|
</div>
|
|
100
116
|
);
|
|
101
117
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
const
|
|
105
|
-
const
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
const formatPrice = (v: number) => Number(v.toFixed(2)).toString()
|
|
109
|
-
const unit = currentBillingDisplay.unit || ''
|
|
110
|
-
let discountText = ''
|
|
118
|
+
|
|
119
|
+
// Numeric price logic
|
|
120
|
+
const originValue = Number(priceValue);
|
|
121
|
+
const discount = currentBilling.discount;
|
|
122
|
+
const hasDiscount = discount !== 0;
|
|
123
|
+
const saleValue = originValue * (1 - discount);
|
|
124
|
+
const formatPrice = (v: number) => Number(v.toFixed(2)).toString();
|
|
125
|
+
const unit = currentBillingDisplay.unit || '';
|
|
126
|
+
let discountText = '';
|
|
111
127
|
if (hasDiscount && currentBillingDisplay.discountText) {
|
|
112
|
-
discountText = currentBillingDisplay.discountText.replace('{percent}', String(Math.round(Math.abs(discount) * 100)))
|
|
128
|
+
discountText = currentBillingDisplay.discountText.replace('{percent}', String(Math.round(Math.abs(discount) * 100)));
|
|
113
129
|
}
|
|
114
|
-
|
|
115
|
-
|
|
130
|
+
const showNaN = saleValue < 0;
|
|
131
|
+
|
|
116
132
|
return (
|
|
117
|
-
<div className="flex flex-col items-start w-full">
|
|
133
|
+
<div className="flex flex-col items-start w-full" data-price-container={plan.key}>
|
|
118
134
|
<div className="flex items-end gap-2">
|
|
119
|
-
<span className="text-4xl font-extrabold text-gray-900 dark:text-gray-100">
|
|
120
|
-
{currency}{showNaN ? 'NaN' : (hasDiscount ? formatPrice(saleValue) : formatPrice(originValue))}
|
|
135
|
+
<span className="text-4xl font-extrabold text-gray-900 dark:text-gray-100" data-price-value={plan.key}>
|
|
136
|
+
{data.currency}{showNaN ? 'NaN' : (hasDiscount ? formatPrice(saleValue) : formatPrice(originValue))}
|
|
121
137
|
</span>
|
|
122
|
-
<span className="text-lg text-gray-700 dark:text-gray-300 font-medium mb-1">{unit}</span>
|
|
138
|
+
<span className="text-lg text-gray-700 dark:text-gray-300 font-medium mb-1" data-price-unit={plan.key}>{unit}</span>
|
|
123
139
|
</div>
|
|
124
|
-
{/* sub title row, always take place */}
|
|
125
140
|
<div className="flex items-center gap-2 min-h-[24px] mt-1">
|
|
126
141
|
{hasDiscount && (
|
|
127
142
|
<>
|
|
128
|
-
<span className="text-base text-gray-400 line-through"
|
|
143
|
+
<span className="text-base text-gray-400 line-through" data-price-original={plan.key}>
|
|
144
|
+
{data.currency}{showNaN ? 'NaN' : formatPrice(originValue)}
|
|
145
|
+
</span>
|
|
129
146
|
{discountText && (
|
|
130
|
-
<span className="px-2 py-0.5 text-xs rounded bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200 font-semibold align-middle"
|
|
147
|
+
<span className="px-2 py-0.5 text-xs rounded bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200 font-semibold align-middle" data-price-discount={plan.key}>
|
|
148
|
+
{discountText}
|
|
149
|
+
</span>
|
|
131
150
|
)}
|
|
132
151
|
</>
|
|
133
152
|
)}
|
|
134
|
-
<span className={cn('text-xs text-gray-700 dark:text-gray-300 font-medium', plan.showBillingSubTitle === false && 'opacity-0 select-none')}>
|
|
153
|
+
<span className={cn('text-xs text-gray-700 dark:text-gray-300 font-medium', plan.showBillingSubTitle === false && 'opacity-0 select-none')} data-price-subtitle={plan.key}>
|
|
135
154
|
{plan.showBillingSubTitle === false ? '' : billingSubTitle}
|
|
136
155
|
</span>
|
|
137
156
|
</div>
|
|
138
157
|
</div>
|
|
139
|
-
)
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// tooltip component
|
|
143
|
-
const Tooltip = ({ show, content, x, y }: typeof tooltip) => {
|
|
144
|
-
if (!show) return null
|
|
145
|
-
// simple boundary handling, prevent overflow
|
|
146
|
-
const style: React.CSSProperties = {
|
|
147
|
-
position: 'fixed',
|
|
148
|
-
left: Math.max(8, x),
|
|
149
|
-
top: Math.max(8, y),
|
|
150
|
-
zIndex: 9999,
|
|
151
|
-
maxWidth: 200,
|
|
152
|
-
transform: 'translateY(-50%)',
|
|
153
|
-
pointerEvents: 'none',
|
|
154
|
-
whiteSpace: 'pre-line',
|
|
155
|
-
}
|
|
156
|
-
return (
|
|
157
|
-
<div
|
|
158
|
-
style={style}
|
|
159
|
-
className="bg-gray-700 dark:bg-gray-200 text-gray-100 dark:text-gray-800 text-xs leading-relaxed px-3 py-2 rounded-lg shadow-lg border border-gray-300 dark:border-gray-600 backdrop-blur-sm"
|
|
160
|
-
>
|
|
161
|
-
{content}
|
|
162
|
-
</div>
|
|
163
|
-
)
|
|
158
|
+
);
|
|
164
159
|
}
|
|
165
160
|
|
|
166
161
|
return (
|
|
167
162
|
<section id="pricing" className={cn("px-4 py-10 md:px-16 md:py-16 mx-auto max-w-7xl scroll-mt-10", sectionClassName)}>
|
|
168
163
|
{/* title and subtitle */}
|
|
169
164
|
<h2 className="text-3xl md:text-4xl font-bold text-center mb-3">
|
|
170
|
-
{
|
|
165
|
+
{data.title}
|
|
171
166
|
</h2>
|
|
172
167
|
<p className="text-center text-gray-600 dark:text-gray-400 mb-8 text-base md:text-lg mx-auto">
|
|
173
|
-
{
|
|
168
|
+
{data.subtitle}
|
|
174
169
|
</p>
|
|
175
170
|
|
|
176
171
|
{/* billing switch button */}
|
|
177
172
|
<div className="flex flex-col items-center">
|
|
178
|
-
{/* Binary toggle buttons */}
|
|
179
173
|
<div className="flex items-center relative mb-3">
|
|
180
|
-
<div className="flex bg-white dark:bg-gray-900 border border-gray-300 dark:border-gray-700 rounded-full p-1">
|
|
174
|
+
<div className="flex bg-white dark:bg-gray-900 border border-gray-300 dark:border-gray-700 rounded-full p-1" data-billing-switch>
|
|
181
175
|
<button
|
|
182
176
|
className={cn(
|
|
183
177
|
'min-w-[120px] px-6 py-2 font-medium transition text-lg relative',
|
|
184
|
-
|
|
178
|
+
data.billingSwitch.defaultKey === 'monthly'
|
|
185
179
|
? 'text-white bg-gradient-to-r from-purple-400 to-pink-500 hover:from-purple-500 hover:to-pink-600 dark:from-purple-500 dark:to-pink-600 dark:hover:from-purple-600 rounded-full shadow-sm'
|
|
186
180
|
: 'text-gray-800 dark:text-gray-200 hover:text-gray-900 dark:hover:text-gray-100 rounded-full'
|
|
187
181
|
)}
|
|
188
|
-
|
|
182
|
+
data-billing-button="monthly"
|
|
189
183
|
type="button"
|
|
190
184
|
>
|
|
191
|
-
{(billingSwitch.options.find((opt: any) => opt.key === 'monthly')?.name) || 'Monthly'}
|
|
185
|
+
{(data.billingSwitch.options.find((opt: any) => opt.key === 'monthly')?.name) || 'Monthly'}
|
|
192
186
|
</button>
|
|
193
187
|
<button
|
|
194
188
|
className={cn(
|
|
195
189
|
'min-w-[120px] px-6 py-2 font-medium transition text-lg relative',
|
|
196
|
-
|
|
190
|
+
data.billingSwitch.defaultKey === 'yearly'
|
|
197
191
|
? 'text-white bg-gradient-to-r from-purple-400 to-pink-500 hover:from-purple-500 hover:to-pink-600 dark:from-purple-500 dark:to-pink-600 dark:hover:from-purple-600 rounded-full shadow-sm'
|
|
198
192
|
: 'text-gray-800 dark:text-gray-200 hover:text-gray-900 dark:hover:text-gray-100 rounded-full'
|
|
199
193
|
)}
|
|
200
|
-
|
|
194
|
+
data-billing-button="yearly"
|
|
201
195
|
type="button"
|
|
202
196
|
>
|
|
203
|
-
{(billingSwitch.options.find((opt: any) => opt.key === 'yearly')?.name) || 'Yearly'}
|
|
197
|
+
{(data.billingSwitch.options.find((opt: any) => opt.key === 'yearly')?.name) || 'Yearly'}
|
|
204
198
|
</button>
|
|
205
199
|
</div>
|
|
206
200
|
</div>
|
|
207
201
|
|
|
208
|
-
{/* Discount info -
|
|
209
|
-
<div className="h-8 flex items-center justify-center mb-3">
|
|
202
|
+
{/* Discount info - static for default billing */}
|
|
203
|
+
<div className="h-8 flex items-center justify-center mb-3" data-discount-info>
|
|
210
204
|
{(() => {
|
|
211
|
-
const opt = billingSwitch.options.find((opt: any) => opt.key ===
|
|
212
|
-
const bOpt = billingOptions.find((opt: any) => opt.key ===
|
|
205
|
+
const opt = data.billingSwitch.options.find((opt: any) => opt.key === data.billingSwitch.defaultKey);
|
|
206
|
+
const bOpt = billingOptions.find((opt: any) => opt.key === data.billingSwitch.defaultKey);
|
|
213
207
|
if (!(opt && bOpt && opt.discountText && bOpt.discount !== 0)) return null;
|
|
214
208
|
return (
|
|
215
209
|
<span className="px-2 py-1 text-xs rounded bg-yellow-100 text-yellow-800 font-semibold align-middle text-center inline-flex items-center justify-center whitespace-nowrap">
|
|
@@ -225,9 +219,10 @@ export function PricePlan({ currency = '$', pricePlanConfig, sectionClassName }:
|
|
|
225
219
|
|
|
226
220
|
{/* price card area */}
|
|
227
221
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
|
|
228
|
-
{plans.map((plan: any, _idx: number) => (
|
|
222
|
+
{data.plans.map((plan: any, _idx: number) => (
|
|
229
223
|
<div
|
|
230
224
|
key={plan.key}
|
|
225
|
+
data-price-plan={plan.key}
|
|
231
226
|
className={cn(
|
|
232
227
|
'flex flex-col bg-white dark:bg-gray-800/60 rounded-2xl border border-gray-300 dark:border-[#7c3aed40] transition p-8 h-full shadow-sm dark:shadow-none',
|
|
233
228
|
'hover:border-2 hover:border-purple-500',
|
|
@@ -247,7 +242,7 @@ export function PricePlan({ currency = '$', pricePlanConfig, sectionClassName }:
|
|
|
247
242
|
{/* feature list */}
|
|
248
243
|
<ul className="flex-1 mb-6 mt-4">
|
|
249
244
|
{getFeatureRows(plan).map((feature: any, i: number) => (
|
|
250
|
-
<li key={i} className="flex items-center gap-2 mb-2 min-h-[28px]">
|
|
245
|
+
<li key={i} className="flex items-center gap-2 mb-2 min-h-[28px]" data-feature-item={`${plan.key}-${i}`}>
|
|
251
246
|
{/* icon */}
|
|
252
247
|
{feature ? (
|
|
253
248
|
<span className="inline-flex items-center justify-center w-5 h-5 rounded-full bg-green-100 text-green-700 dark:bg-green-900 dark:text-green-200 mr-1">
|
|
@@ -267,20 +262,12 @@ export function PricePlan({ currency = '$', pricePlanConfig, sectionClassName }:
|
|
|
267
262
|
{feature.tooltip && (
|
|
268
263
|
<span
|
|
269
264
|
className="ml-1 align-middle inline-flex"
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
show: true,
|
|
273
|
-
content: feature.tooltip,
|
|
274
|
-
x: e.clientX,
|
|
275
|
-
y: e.clientY
|
|
276
|
-
})
|
|
277
|
-
}}
|
|
278
|
-
onMouseMove={e => {
|
|
279
|
-
setTooltip(t => ({ ...t, x: e.clientX, y: e.clientY }))
|
|
280
|
-
}}
|
|
281
|
-
onMouseLeave={() => setTooltip(t => ({ ...t, show: false }))}
|
|
265
|
+
data-tooltip-trigger={`${plan.key}-${i}`}
|
|
266
|
+
data-tooltip-content={feature.tooltip}
|
|
282
267
|
>
|
|
283
|
-
<
|
|
268
|
+
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
269
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
|
270
|
+
</svg>
|
|
284
271
|
</span>
|
|
285
272
|
)}
|
|
286
273
|
</span>
|
|
@@ -302,19 +289,15 @@ export function PricePlan({ currency = '$', pricePlanConfig, sectionClassName }:
|
|
|
302
289
|
)}
|
|
303
290
|
disabled={plan.button?.disabled}
|
|
304
291
|
type="button"
|
|
305
|
-
|
|
306
|
-
if (!plan.button?.disabled) {
|
|
307
|
-
router.push('/');
|
|
308
|
-
}
|
|
309
|
-
}}
|
|
292
|
+
data-plan-button={plan.key}
|
|
310
293
|
>
|
|
311
294
|
{plan.button?.text || '--'}
|
|
312
295
|
</button>
|
|
313
296
|
</div>
|
|
314
297
|
))}
|
|
315
298
|
</div>
|
|
316
|
-
|
|
317
|
-
<
|
|
299
|
+
|
|
300
|
+
<PricePlanInteractive data={data} />
|
|
318
301
|
</section>
|
|
319
302
|
)
|
|
320
303
|
}
|
package/src/main/seo-content.tsx
CHANGED
|
@@ -1,42 +1,75 @@
|
|
|
1
1
|
/* eslint-disable react/no-unescaped-entities */
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
import { useTranslations } from 'next-intl'
|
|
2
|
+
import { getTranslations } from 'next-intl/server';
|
|
5
3
|
import { cn } from '@lib/utils';
|
|
6
4
|
import { richText } from '@third-ui/main/rich-text-expert';
|
|
7
5
|
|
|
8
|
-
interface
|
|
6
|
+
interface SeoSection {
|
|
7
|
+
id: string;
|
|
9
8
|
title: string;
|
|
10
9
|
content: string;
|
|
11
10
|
}
|
|
12
11
|
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
interface SeoContentData {
|
|
13
|
+
title: string;
|
|
14
|
+
eyesOn: string;
|
|
15
|
+
description: string;
|
|
16
|
+
intro: string;
|
|
17
|
+
sections: SeoSection[];
|
|
18
|
+
conclusion: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function SeoContent({
|
|
22
|
+
locale,
|
|
23
|
+
sectionClassName
|
|
24
|
+
}: {
|
|
25
|
+
locale: string;
|
|
26
|
+
sectionClassName?: string;
|
|
27
|
+
}) {
|
|
28
|
+
const t = await getTranslations({ locale, namespace: 'seoContent' });
|
|
29
|
+
|
|
30
|
+
// Process translation data
|
|
31
|
+
const rawSections = t.raw('sections') as Array<{
|
|
32
|
+
title: string;
|
|
33
|
+
content: string;
|
|
34
|
+
}>;
|
|
35
|
+
|
|
36
|
+
const data: SeoContentData = {
|
|
37
|
+
title: t('title'),
|
|
38
|
+
eyesOn: t('eyesOn'),
|
|
39
|
+
description: t('description'),
|
|
40
|
+
intro: richText(t, 'intro'),
|
|
41
|
+
sections: rawSections.map((section, index) => ({
|
|
42
|
+
id: `seo-section-${index}`,
|
|
43
|
+
title: section.title,
|
|
44
|
+
content: richText(t, `sections.${index}.content`)
|
|
45
|
+
})),
|
|
46
|
+
conclusion: richText(t, 'conclusion')
|
|
47
|
+
};
|
|
15
48
|
|
|
16
49
|
return (
|
|
17
50
|
<section id="seo" className={cn("px-16 py-10 mx-16 md:mx-32 scroll-mt-20", sectionClassName)}>
|
|
18
51
|
<h2 className="text-3xl md:text-4xl font-bold text-center mb-8">
|
|
19
|
-
{
|
|
52
|
+
{data.title} <span className="text-purple-500">{data.eyesOn}</span>
|
|
20
53
|
</h2>
|
|
21
54
|
<h3 className="text-center text-gray-600 dark:text-gray-400 mb-12 text-lg">
|
|
22
|
-
{
|
|
55
|
+
{data.description}
|
|
23
56
|
</h3>
|
|
24
57
|
<div className="bg-gray-50 dark:bg-gray-800/60 border border-gray-200 dark:border-gray-700 rounded-2xl p-8 md:p-12 shadow-sm dark:shadow-none">
|
|
25
58
|
<div className="space-y-10">
|
|
26
59
|
<p className="text-gray-600 dark:text-gray-400 text-lg">
|
|
27
|
-
{
|
|
60
|
+
{data.intro}
|
|
28
61
|
</p>
|
|
29
|
-
{
|
|
30
|
-
<div key={
|
|
62
|
+
{data.sections.map((section) => (
|
|
63
|
+
<div key={section.id} data-seo-section={section.id}>
|
|
31
64
|
<h2 className="text-xl font-semibold mb-3 text-gray-900 dark:text-gray-100 flex items-center">
|
|
32
65
|
{section.title}
|
|
33
66
|
</h2>
|
|
34
|
-
<p className="text-gray-700 dark:text-gray-300">{
|
|
67
|
+
<p className="text-gray-700 dark:text-gray-300">{section.content}</p>
|
|
35
68
|
</div>
|
|
36
69
|
))}
|
|
37
70
|
</div>
|
|
38
71
|
<p className="mt-10 text-gray-600 dark:text-gray-400 text-lg">
|
|
39
|
-
{
|
|
72
|
+
{data.conclusion}
|
|
40
73
|
</p>
|
|
41
74
|
</div>
|
|
42
75
|
</section>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Main application Server components
|
|
2
|
+
export * from './gallery';
|
|
3
|
+
export * from './usage';
|
|
4
|
+
export * from './features';
|
|
5
|
+
export * from './tips';
|
|
6
|
+
export * from './faq';
|
|
7
|
+
export * from './seo-content';
|
|
8
|
+
export * from './cta';
|
|
9
|
+
export * from './footer';
|
|
10
|
+
export * from './price-plan';
|
package/src/main/tips.tsx
CHANGED
|
@@ -1,40 +1,66 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import { useTranslations } from 'next-intl'
|
|
1
|
+
import { getTranslations } from 'next-intl/server';
|
|
4
2
|
import { cn } from '@lib/utils';
|
|
5
3
|
import { richText } from '@third-ui/main/rich-text-expert';
|
|
6
4
|
|
|
7
|
-
interface
|
|
5
|
+
interface TipSection {
|
|
6
|
+
id: string;
|
|
8
7
|
title: string;
|
|
9
8
|
description: string;
|
|
10
9
|
}
|
|
11
10
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
interface TipsData {
|
|
12
|
+
title: string;
|
|
13
|
+
eyesOn: string;
|
|
14
|
+
leftColumn: TipSection[];
|
|
15
|
+
rightColumn: TipSection[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function Tips({
|
|
19
|
+
locale,
|
|
20
|
+
sectionClassName
|
|
21
|
+
}: {
|
|
22
|
+
locale: string;
|
|
23
|
+
sectionClassName?: string;
|
|
24
|
+
}) {
|
|
25
|
+
const t = await getTranslations({ locale, namespace: 'tips' });
|
|
26
|
+
|
|
27
|
+
// Process translation data
|
|
28
|
+
const sections = t.raw('sections') as Array<{
|
|
29
|
+
title: string;
|
|
30
|
+
description: string;
|
|
31
|
+
}>;
|
|
32
|
+
|
|
33
|
+
const processedSections = sections.map((section, index) => ({
|
|
34
|
+
id: `tip-section-${index}`,
|
|
35
|
+
title: section.title,
|
|
36
|
+
description: richText(t, `sections.${index}.description`)
|
|
37
|
+
}));
|
|
38
|
+
|
|
39
|
+
const midPoint = Math.ceil(processedSections.length / 2);
|
|
40
|
+
const leftColumn = processedSections.slice(0, midPoint);
|
|
41
|
+
const rightColumn = processedSections.slice(midPoint);
|
|
15
42
|
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
43
|
+
const data: TipsData = {
|
|
44
|
+
title: t('title'),
|
|
45
|
+
eyesOn: t('eyesOn'),
|
|
46
|
+
leftColumn,
|
|
47
|
+
rightColumn
|
|
48
|
+
};
|
|
19
49
|
|
|
20
50
|
return (
|
|
21
51
|
<section id="tips" className={cn("px-16 py-10 mx-16 md:mx-32 scroll-mt-20", sectionClassName)}>
|
|
22
52
|
<h2 className="text-3xl md:text-4xl font-bold text-center mb-16">
|
|
23
|
-
{
|
|
53
|
+
{data.title} <span className="text-purple-500">{data.eyesOn}</span>
|
|
24
54
|
</h2>
|
|
25
55
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-12 bg-gray-50 dark:bg-gray-800/60 border border-gray-200 dark:border-gray-700 rounded-2xl p-8 md:p-12 shadow-sm dark:shadow-none">
|
|
26
|
-
{[leftColumn, rightColumn].map((column:
|
|
56
|
+
{[data.leftColumn, data.rightColumn].map((column: TipSection[], colIndex) => (
|
|
27
57
|
<div key={colIndex} className="space-y-8">
|
|
28
|
-
{column.map((tip:
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
<p className="">{richText(t, `sections.${actualIndex}.description`)}</p>
|
|
35
|
-
</div>
|
|
36
|
-
);
|
|
37
|
-
})}
|
|
58
|
+
{column.map((tip: TipSection) => (
|
|
59
|
+
<div key={tip.id} data-tip-id={tip.id} className="space-y-4">
|
|
60
|
+
<h3 className="text-2xl font-semibold">{tip.title}</h3>
|
|
61
|
+
<p className="">{tip.description}</p>
|
|
62
|
+
</div>
|
|
63
|
+
))}
|
|
38
64
|
</div>
|
|
39
65
|
))}
|
|
40
66
|
</div>
|
package/src/main/usage.tsx
CHANGED
|
@@ -1,40 +1,72 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import { useTranslations } from 'next-intl'
|
|
1
|
+
import { getTranslations } from 'next-intl/server';
|
|
4
2
|
import { cn } from '@lib/utils';
|
|
5
3
|
import { globalLucideIcons as icons, getGlobalIcon } from '@base-ui/components/global-icon'
|
|
6
4
|
import { richText } from '@third-ui/main/rich-text-expert';
|
|
7
5
|
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
interface UsageData {
|
|
7
|
+
title: string;
|
|
8
|
+
eyesOn: string;
|
|
9
|
+
description: string;
|
|
10
|
+
steps: Array<{
|
|
11
|
+
id: string;
|
|
12
|
+
title: string;
|
|
13
|
+
description: string;
|
|
14
|
+
iconKey: keyof typeof icons;
|
|
15
|
+
stepNumber: number;
|
|
16
|
+
}>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export async function Usage({
|
|
20
|
+
locale,
|
|
21
|
+
sectionClassName
|
|
22
|
+
}: {
|
|
23
|
+
locale: string;
|
|
24
|
+
sectionClassName?: string;
|
|
25
|
+
}) {
|
|
26
|
+
const t = await getTranslations({ locale, namespace: 'usage' });
|
|
27
|
+
|
|
28
|
+
// Process translation data
|
|
10
29
|
const steps = t.raw('steps') as Array<{
|
|
11
30
|
title: string;
|
|
12
31
|
description: string;
|
|
13
32
|
iconKey: keyof typeof icons;
|
|
14
33
|
}>;
|
|
34
|
+
|
|
35
|
+
const data: UsageData = {
|
|
36
|
+
title: t('title'),
|
|
37
|
+
eyesOn: t('eyesOn'),
|
|
38
|
+
description: richText(t, 'description'),
|
|
39
|
+
steps: steps.map((step, index) => ({
|
|
40
|
+
id: `usage-step-${index}`,
|
|
41
|
+
title: step.title,
|
|
42
|
+
description: richText(t, `steps.${index}.description`),
|
|
43
|
+
iconKey: step.iconKey,
|
|
44
|
+
stepNumber: index + 1
|
|
45
|
+
}))
|
|
46
|
+
};
|
|
15
47
|
|
|
16
48
|
return (
|
|
17
49
|
<section id="usage" className={cn("px-16 py-10 mx-16 md:mx-32 scroll-mt-20", sectionClassName)}>
|
|
18
50
|
<h2 className="text-3xl md:text-4xl font-bold text-center mb-4">
|
|
19
|
-
{
|
|
51
|
+
{data.title} <span className="text-purple-500">{data.eyesOn}</span>
|
|
20
52
|
</h2>
|
|
21
53
|
<p className="text-center text-gray-600 dark:text-gray-400 mb-12 text-base md:text-lg mx-auto whitespace-nowrap">
|
|
22
|
-
{
|
|
54
|
+
{data.description}
|
|
23
55
|
</p>
|
|
24
56
|
<div className="bg-gray-50 dark:bg-gray-800/60 border border-gray-200 dark:border-gray-700 rounded-2xl p-8 md:p-12 shadow-sm dark:shadow-none">
|
|
25
57
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 gap-y-12">
|
|
26
|
-
{steps.map((step
|
|
58
|
+
{data.steps.map((step) => {
|
|
27
59
|
const Icon = getGlobalIcon(step.iconKey);
|
|
28
60
|
return (
|
|
29
|
-
<div key={
|
|
61
|
+
<div key={step.id} data-usage-step={step.id} className="flex items-start">
|
|
30
62
|
<div className="flex-shrink-0 mr-4">
|
|
31
63
|
<Icon className="w-8 h-8 text-purple-500" />
|
|
32
64
|
</div>
|
|
33
65
|
<div>
|
|
34
66
|
<h3 className="text-xl font-semibold mb-3 text-gray-900 dark:text-gray-100 flex items-center">
|
|
35
|
-
{`${
|
|
67
|
+
{`${step.stepNumber}. ${step.title}`}
|
|
36
68
|
</h3>
|
|
37
|
-
<p className="text-gray-700 dark:text-gray-300">{
|
|
69
|
+
<p className="text-gray-700 dark:text-gray-300">{step.description}</p>
|
|
38
70
|
</div>
|
|
39
71
|
</div>
|
|
40
72
|
)
|