@@ -3,8 +3,8 @@ import axios from "axios";
33import type { SVGProps } from "react" ;
44import { useEffect , useState } from "react" ;
55import { useNavigate } from "react-router-dom" ;
6- import { getURL } from "../../controllers/API/helpers/constants" ;
76import planConfigData from "../../../public/plan_config.json" ;
7+ import { getURL } from "../../controllers/API/helpers/constants" ;
88
99const MIN_SEATS = 1 ;
1010const PADDLE_PRICE_CACHE_KEY = "pricing_page_paddle_prices" ;
@@ -19,7 +19,10 @@ interface SharedPlanConfig {
1919 features : string [ ] ;
2020 paddle : {
2121 enabled : boolean ;
22- plan_key : "starter_pack_monthly" | "pro_pack_monthly" | "enterprise_pack_monthly" ;
22+ plan_key :
23+ | "starter_pack_monthly"
24+ | "pro_pack_monthly"
25+ | "enterprise_pack_monthly" ;
2326 monthly_price_usd_cents : number ;
2427 trial_days : number | null ;
2528 } ;
@@ -65,11 +68,16 @@ interface PlanConfig {
6568 hasTrial : boolean ;
6669 trialDays ?: number ;
6770 features : string [ ] ;
68- paddlePlanKey : "starter_pack_monthly" | "pro_pack_monthly" | "enterprise_pack_monthly" ;
71+ paddlePlanKey :
72+ | "starter_pack_monthly"
73+ | "pro_pack_monthly"
74+ | "enterprise_pack_monthly" ;
6975 defaultSeats : number ;
7076}
7177
72- const STATIC_PLANS : PlanConfig [ ] = ( planConfigData . plans as SharedPlanConfig [ ] ) . map ( ( plan ) => ( {
78+ const STATIC_PLANS : PlanConfig [ ] = (
79+ planConfigData . plans as SharedPlanConfig [ ]
80+ ) . map ( ( plan ) => ( {
7381 key : plan . key ,
7482 name : plan . name ,
7583 pricePerSeat : plan . paddle . monthly_price_usd_cents / 100 ,
@@ -100,7 +108,8 @@ export default function PricingPage() {
100108
101109 const [ selectedPlan , setSelectedPlan ] = useState < PlanKey > ( "starter" ) ;
102110 const [ seatsByPlan , setSeatsByPlan ] = useState < Record < PlanKey , number > > ( {
103- starter : STATIC_PLANS . find ( ( plan ) => plan . key === "starter" ) ?. defaultSeats ?? 1 ,
111+ starter :
112+ STATIC_PLANS . find ( ( plan ) => plan . key === "starter" ) ?. defaultSeats ?? 1 ,
104113 pro : STATIC_PLANS . find ( ( plan ) => plan . key === "pro" ) ?. defaultSeats ?? 1 ,
105114 enterprise :
106115 STATIC_PLANS . find ( ( plan ) => plan . key === "enterprise" ) ?. defaultSeats ?? 5 ,
@@ -200,147 +209,127 @@ export default function PricingPage() {
200209 } ;
201210
202211 return (
203- < div className = "w-full overflow-hidden" >
204- < div style = { {
205- transform : "scale(0.67)" ,
206- transformOrigin : "top center" ,
207- width : "100%" ,
208- display : "flex" ,
209- justifyContent : "center" ,
210- } }
211- >
212- < div style = { { width : "149.25%" } } >
213- < div >
214- < div className = "min-h-screen px-4 py-12 text-slate-900 overflow-hidden" >
215- < div className = "mx-auto max-w-6xl" >
216- < div className = "rounded-[28px] border border-slate-200 bg-white p-8 shadow-[0_24px_80px_rgba(15,23,42,0.08)] sm:p-10" >
217- < div className = "text-center" >
218- < span className = "inline-flex rounded-full border border-sky-200 bg-sky-50 px-3 py-1 text-xs font-medium uppercase tracking-[0.2em] text-sky-700" >
219- Pricing
220- </ span >
221- < h1 className = "mt-4 text-3xl font-semibold tracking-tight text-slate-900 sm:text-4xl" >
222- Simple, scalable pricing
223- </ h1 >
224- < p className = "mt-3 text-base text-slate-600 sm:text-lg" >
225- Choose the plan that matches your team size and move from
226- onboarding to workspace with a consistent experience.
227- </ p >
228- </ div >
212+ < div className = "w-full overflow-x-hidden" >
213+ < div className = "min-h-screen px-4 py-12 text-slate-900" >
214+ < div className = "mx-auto w-full max-w-5xl px-4 sm:px-6 lg:px-8" >
215+ < div className = "rounded-2xl border border-slate-200 bg-white p-6 sm:p-8 shadow-lg" >
216+ < div className = "text-center" >
217+ < h1 className = "text-2xl sm:text-3xl lg:text-4xl font-semibold" >
218+ Simple, scalable pricing
219+ </ h1 >
220+ </ div >
229221
230- < div className = "mt-10 grid gap-6 md:grid-cols-3" >
231- { STATIC_PLANS . map ( ( plan ) => {
232- const seats = seatsByPlan [ plan . key ] ;
233- const total = seats * plan . pricePerSeat ;
234-
235- return (
236- < div
237- key = { plan . key }
238- className = { `flex flex-col rounded-2xl border p-6 transition-all ${
239- selectedPlan === plan . key
240- ? "border-sky-400 bg-sky-50/80 shadow-[0_18px_45px_rgba(14,165,233,0.12)]"
241- : "border-slate-200 bg-white shadow-sm"
242- } `}
222+ < div className = "mt-8 grid gap-6 sm:grid-cols-2 lg:grid-cols-3" >
223+ { STATIC_PLANS . map ( ( plan ) => {
224+ const seats = seatsByPlan [ plan . key ] ;
225+ const total = seats * plan . pricePerSeat ;
226+
227+ return (
228+ < div
229+ key = { plan . key }
230+ className = { `flex flex-col rounded-2xl border p-5 sm:p-6 transition-all ${
231+ selectedPlan === plan . key
232+ ? "border-sky-400 bg-sky-50/80 shadow-[0_18px_45px_rgba(14,165,233,0.12)]"
233+ : "border-slate-200 bg-white shadow-sm"
234+ } `}
235+ >
236+ < div className = "text-sm font-medium uppercase tracking-[0.18em] text-slate-500" >
237+ { plan . name }
238+ </ div >
239+ < div className = "mt-2 text-3xl font-semibold text-slate-900" >
240+ ${ plan . pricePerSeat } /seat/month
241+ </ div >
242+
243+ { plan . hasTrial && (
244+ < div className = "mt-1 text-sm font-medium text-sky-700" >
245+ { plan . trialDays } -day free trial
246+ </ div >
247+ ) }
248+
249+ < div className = "mt-5 rounded-2xl border border-slate-200 bg-slate-50 p-4" >
250+ < div className = "text-xs uppercase tracking-[0.18em] text-slate-500" >
251+ Seats
252+ </ div >
253+
254+ < div className = "mt-2 flex items-center gap-3" >
255+ < button
256+ type = "button"
257+ onClick = { ( ) => handleDecrement ( plan . key ) }
258+ className = "h-9 w-9 rounded-xl border border-slate-200 bg-white text-lg text-slate-700 transition hover:border-sky-300 hover:text-sky-700"
243259 >
244- < div className = "text-sm font-medium uppercase tracking-[0.18em] text-slate-500" >
245- { plan . name }
246- </ div >
247- < div className = "mt-2 text-3xl font-semibold text-slate-900" >
248- ${ plan . pricePerSeat } /seat/month
249- </ div >
250-
251- { plan . hasTrial && (
252- < div className = "mt-1 text-sm font-medium text-sky-700" >
253- { plan . trialDays } -day free trial
254- </ div >
255- ) }
256-
257- < div className = "mt-5 rounded-2xl border border-slate-200 bg-slate-50 p-4" >
258- < div className = "text-xs uppercase tracking-[0.18em] text-slate-500" >
259- Seats
260- </ div >
261-
262- < div className = "mt-2 flex items-center gap-3" >
263- < button
264- type = "button"
265- onClick = { ( ) => handleDecrement ( plan . key ) }
266- className = "h-9 w-9 rounded-xl border border-slate-200 bg-white text-lg text-slate-700 transition hover:border-sky-300 hover:text-sky-700"
267- >
268- -
269- </ button >
270-
271- < span className = "min-w-8 text-center text-xl font-semibold text-slate-900" >
272- { seats }
273- </ span >
274-
275- < button
276- type = "button"
277- onClick = { ( ) => handleIncrement ( plan . key ) }
278- className = "h-9 w-9 rounded-xl border border-slate-200 bg-white text-lg text-slate-700 transition hover:border-sky-300 hover:text-sky-700"
279- >
280- +
281- </ button >
282- </ div >
283-
284- < div className = "mt-3 text-sm text-slate-500" >
285- Estimated monthly total
286- </ div >
287- < div className = "text-2xl font-semibold text-slate-900" >
288- ${ total } USD
289- </ div >
290- </ div >
291-
292- < ul className = "mt-5 space-y-3 text-sm text-slate-600" >
293- { plan . features . map ( ( feature ) => (
294- < li key = { feature } className = "flex items-center gap-2" >
295- < CheckIcon className = "h-4 w-4 text-sky-600" />
296- { feature }
297- </ li >
298- ) ) }
299- </ ul >
300-
301- < button
302- type = "button"
303- onClick = { ( ) => handleSelectPlan ( plan ) }
304- className = { `mt-6 rounded-xl px-4 py-3 text-sm font-semibold transition ${
305- plan . key === "enterprise"
306- ? "border border-slate-200 bg-white text-slate-700 hover:border-sky-300 hover:text-sky-700"
307- : "bg-slate-900 text-white hover:bg-slate-800"
308- } `}
309- >
310- Select { plan . name }
311- </ button >
312- </ div >
313- ) ;
314- } ) }
315- </ div >
260+ -
261+ </ button >
262+
263+ < span className = "min-w-8 text-center text-xl font-semibold text-slate-900" >
264+ { seats }
265+ </ span >
266+
267+ < button
268+ type = "button"
269+ onClick = { ( ) => handleIncrement ( plan . key ) }
270+ className = "h-9 w-9 rounded-xl border border-slate-200 bg-white text-lg text-slate-700 transition hover:border-sky-300 hover:text-sky-700"
271+ >
272+ +
273+ </ button >
274+ </ div >
275+
276+ < div className = "mt-3 text-sm text-slate-500" >
277+ Estimated monthly total
278+ </ div >
279+ < div className = "text-2xl font-semibold text-slate-900" >
280+ ${ total } USD
281+ </ div >
282+ </ div >
283+
284+ < ul className = "mt-5 space-y-3 text-sm text-slate-600" >
285+ { plan . features . map ( ( feature ) => (
286+ < li key = { feature } className = "flex items-center gap-2" >
287+ < CheckIcon className = "h-4 w-4 text-sky-600" />
288+ { feature }
289+ </ li >
290+ ) ) }
291+ </ ul >
316292
317- < div className = "mt-10 flex flex-wrap justify-center gap-3" >
318- < button
319- type = "button"
320- onClick = { ( ) => {
321- navigate ( "/flows?pricing_bypass=1" ) ;
322- } }
323- className = "rounded-xl bg-slate-900 px-4 py-2.5 text-sm font-semibold text-white transition hover:bg-slate-800"
324- >
325- Go to flows
326- </ button >
327293 < button
328294 type = "button"
329- onClick = { ( ) => navigate ( "/organization" ) }
330- className = "rounded-xl border border-slate-200 bg-white px-4 py-2.5 text-sm font-semibold text-slate-700 transition hover:border-sky-300 hover:text-sky-700"
295+ onClick = { ( ) => handleSelectPlan ( plan ) }
296+ className = { `mt-6 rounded-xl px-4 py-3 text-sm font-semibold transition ${
297+ plan . key === "enterprise"
298+ ? "border border-slate-200 bg-white text-slate-700 hover:border-sky-300 hover:text-sky-700"
299+ : "bg-slate-900 text-white hover:bg-slate-800"
300+ } `}
331301 >
332- Back to organization
302+ Select { plan . name }
333303 </ button >
334304 </ div >
305+ ) ;
306+ } ) }
307+ </ div >
335308
336- { showEnterpriseContactMessage && (
337- < p className = "mt-5 text-center text-sm font-medium text-sky-700" >
338- For Enterprise plans, please contact us.
339- </ p >
340- ) }
341- </ div >
342- </ div >
309+ < div className = "mt-8 flex flex-wrap justify-center gap-3" >
310+ < button
311+ type = "button"
312+ onClick = { ( ) => {
313+ navigate ( "/flows?pricing_bypass=1" ) ;
314+ } }
315+ className = "rounded-xl bg-slate-900 px-4 py-2.5 text-sm font-semibold text-white transition hover:bg-slate-800"
316+ >
317+ Go to flows
318+ </ button >
319+ < button
320+ type = "button"
321+ onClick = { ( ) => navigate ( "/organization" ) }
322+ className = "rounded-xl border border-slate-200 bg-white px-4 py-2.5 text-sm font-semibold text-slate-700 transition hover:border-sky-300 hover:text-sky-700"
323+ >
324+ Back to organization
325+ </ button >
343326 </ div >
327+
328+ { showEnterpriseContactMessage && (
329+ < p className = "mt-5 text-center text-sm font-medium text-sky-700" >
330+ For Enterprise plans, please contact us.
331+ </ p >
332+ ) }
344333 </ div >
345334 </ div >
346335 </ div >
0 commit comments