169 lines
6.0 KiB
TypeScript
169 lines
6.0 KiB
TypeScript
'use client';
|
|
import React, { useState } from 'react';
|
|
import { loadStripe, Stripe } from '@stripe/stripe-js';
|
|
import axios from 'axios';
|
|
|
|
interface Plan {
|
|
name: string;
|
|
desc: string;
|
|
monthly: number;
|
|
yearly: number;
|
|
features: string[];
|
|
btnStyle: string;
|
|
popular?: boolean;
|
|
planId: string;
|
|
}
|
|
|
|
// ⚡ Lazy load Stripe to avoid undefined.match errors
|
|
let stripePromise: Promise<Stripe | null> | null = null;
|
|
const getStripe = () => {
|
|
if (!stripePromise) {
|
|
const key = process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY;
|
|
if (!key) throw new Error('Stripe publishable key is not defined!');
|
|
stripePromise = loadStripe(key);
|
|
}
|
|
return stripePromise;
|
|
};
|
|
|
|
const ComponentsPricingTableToggle: React.FC = () => {
|
|
const [yearlyPrice, setYearlyPrice] = useState(false);
|
|
|
|
const plans: Plan[] = [
|
|
{
|
|
name: 'Starter SEO Crawl',
|
|
desc: 'Perfect for small websites. Crawl up to 5,000 pages per month.',
|
|
monthly: 19,
|
|
yearly: 19 * 12 * 0.8,
|
|
features: ['5,000 Pages/Month', 'Basic Crawl Reports', 'Email Support'],
|
|
btnStyle: 'btn-dark',
|
|
planId: 'starter-seo-crawl',
|
|
},
|
|
{
|
|
name: 'Pro SEO Crawl',
|
|
desc: 'Best for medium websites. Crawl up to 50,000 pages per month.',
|
|
monthly: 49,
|
|
yearly: 49 * 12 * 0.8,
|
|
features: ['50,000 Pages/Month', 'Advanced Crawl Reports', 'Priority Support'],
|
|
btnStyle: 'btn-primary',
|
|
popular: true,
|
|
planId: 'pro-seo-crawl',
|
|
},
|
|
{
|
|
name: 'Enterprise SEO Crawl',
|
|
desc: 'For large-scale websites. Unlimited crawling and dedicated support.',
|
|
monthly: 199,
|
|
yearly: 199 * 12 * 0.8,
|
|
features: ['Unlimited Pages', 'Custom Integrations', 'Dedicated Account Manager'],
|
|
btnStyle: 'btn-dark',
|
|
planId: 'enterprise-seo-crawl',
|
|
},
|
|
];
|
|
|
|
// 🔹 Stripe Checkout handler
|
|
const handleBuyNow = async (plan: Plan) => {
|
|
try {
|
|
const stripe = await getStripe();
|
|
if (!stripe) return;
|
|
|
|
// 🔹 Call backend API to create Checkout Session
|
|
const { data } = await axios.post(
|
|
'https://api.crawlerx.co/api/payment/create-intent',
|
|
{
|
|
planId: plan.planId,
|
|
price: yearlyPrice ? plan.yearly : plan.monthly,
|
|
billing: yearlyPrice ? 'yearly' : 'monthly',
|
|
}
|
|
);
|
|
|
|
if (!data.sessionId) {
|
|
console.error('No sessionId returned from backend!');
|
|
return;
|
|
}
|
|
|
|
// 🔹 Redirect to Stripe Checkout
|
|
const { error } = await stripe.redirectToCheckout({ sessionId: data.sessionId });
|
|
if (error) console.error('Stripe redirect error:', error.message);
|
|
} catch (err: any) {
|
|
console.error('Error creating checkout session:', err.response?.data || err.message);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="mb-5 panel">
|
|
<div className="mx-auto max-w-[320px] dark:text-white-dark md:max-w-[1140px]">
|
|
{/* Toggle */}
|
|
<div className="mt-5 flex justify-center space-x-4 text-center text-base font-semibold md:mt-10">
|
|
<span className={`${!yearlyPrice ? 'text-primary' : 'text-white-dark'}`}>Monthly</span>
|
|
<label className="relative h-6 w-12">
|
|
<input
|
|
type="checkbox"
|
|
className="custom_switch peer absolute top-0 z-10 h-full w-full cursor-pointer opacity-0"
|
|
onChange={() => setYearlyPrice(!yearlyPrice)}
|
|
/>
|
|
<span className="outline_checkbox bg-icon block h-full rounded-full border-2 border-[#ebedf2]
|
|
before:absolute before:bottom-1 before:h-4 before:w-4 before:rounded-full before:bg-[#ebedf2]
|
|
before:transition-all before:duration-300 peer-checked:border-primary peer-checked:before:bg-primary
|
|
ltr:before:left-1 ltr:peer-checked:before:left-7">
|
|
</span>
|
|
</label>
|
|
<span className={`relative ${yearlyPrice ? 'text-primary' : ' text-white-dark'} `}>
|
|
Yearly
|
|
<span className="badge absolute my-auto hidden whitespace-nowrap rounded-full bg-success ltr:left-full ltr:ml-2">
|
|
20% Off
|
|
</span>
|
|
</span>
|
|
</div>
|
|
|
|
{/* Plans */}
|
|
<div className="mt-5 space-y-4 text-white-dark md:mt-16 md:flex md:space-y-0">
|
|
{plans.map((plan, idx) => (
|
|
<div
|
|
key={idx}
|
|
className="relative rounded-md border border-white-light p-4 transition-all duration-300
|
|
hover:shadow-[0_0_15px_1px_rgba(113,106,202,0.20)] dark:border-[#1b2e4b] lg:p-9"
|
|
>
|
|
{plan.popular && (
|
|
<div className="absolute inset-x-0 top-0 flex h-10 items-center justify-center rounded-t-md bg-primary text-base text-white md:-top-[30px]">
|
|
Most Popular
|
|
</div>
|
|
)}
|
|
<h3 className="mb-5 text-xl font-semibold text-black dark:text-white-light">{plan.name}</h3>
|
|
<p>{plan.desc}</p>
|
|
<div className="my-7 p-2.5 text-center text-lg">
|
|
<strong
|
|
className={`text-xl ${
|
|
plan.popular ? 'text-primary lg:text-4xl'
|
|
: 'text-[#3b3f5c] dark:text-white-light lg:text-3xl'
|
|
}`}
|
|
>
|
|
${yearlyPrice ? plan.yearly.toFixed(0) : plan.monthly}
|
|
</strong>{' '}
|
|
/ {yearlyPrice ? 'yearly' : 'monthly'}
|
|
</div>
|
|
<div className="mb-6">
|
|
<strong className="mb-3 inline-block text-[15px] text-black dark:text-white-light">
|
|
{plan.name} Features
|
|
</strong>
|
|
<ul className="space-y-3">
|
|
{plan.features.map((f, i) => (
|
|
<li key={i}>{f}</li>
|
|
))}
|
|
</ul>
|
|
</div>
|
|
<button
|
|
type="button"
|
|
className={`btn ${plan.btnStyle} w-full`}
|
|
onClick={() => handleBuyNow(plan)}
|
|
>
|
|
Buy Now
|
|
</button>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default ComponentsPricingTableToggle;
|