diff --git a/src/app/pricing/page.tsx b/src/app/pricing/page.tsx new file mode 100644 index 0000000..a3a0b56 --- /dev/null +++ b/src/app/pricing/page.tsx @@ -0,0 +1,16 @@ +import React from 'react'; +import { Metadata } from 'next'; +import PricingTable from '@/components/pricing/PricingTable'; + +export const metadata: Metadata = { + title: 'Pricing | StrellerMinds', + description: 'Choose the best plan for your learning journey.', +}; + +export default function PricingPage() { + return ( +
+ +
+ ); +} diff --git a/src/components/pricing/PricingTable.tsx b/src/components/pricing/PricingTable.tsx new file mode 100644 index 0000000..79e22c9 --- /dev/null +++ b/src/components/pricing/PricingTable.tsx @@ -0,0 +1,166 @@ +import React, { useState } from 'react'; +import { Check, X } from 'lucide-react'; +import { usePricing } from './usePricing'; + +interface PricingTier { + name: string; + description: string; + monthlyPrice: number; + annualPrice: number; + popular: boolean; + features: { name: string; included: boolean }[]; + ctaText: string; +} + +const tiers: PricingTier[] = [ + { + name: 'Basic', + description: 'Essential features for individuals and beginners.', + monthlyPrice: 9.99, + annualPrice: 99, + popular: false, + features: [ + { name: 'Access to all basic courses', included: true }, + { name: 'Community forum access', included: true }, + { name: 'Email support', included: true }, + { name: 'Certificate of completion', included: false }, + { name: '1-on-1 mentorship', included: false }, + ], + ctaText: 'Get Started', + }, + { + name: 'Pro', + description: 'Perfect for professionals looking to advance their career.', + monthlyPrice: 29.99, + annualPrice: 299, + popular: true, + features: [ + { name: 'Access to all basic courses', included: true }, + { name: 'Community forum access', included: true }, + { name: 'Priority email support', included: true }, + { name: 'Certificate of completion', included: true }, + { name: '1-on-1 mentorship', included: false }, + ], + ctaText: 'Start Free Trial', + }, + { + name: 'Enterprise', + description: 'Advanced features for teams and organizations.', + monthlyPrice: 99.99, + annualPrice: 999, + popular: false, + features: [ + { name: 'Access to all basic courses', included: true }, + { name: 'Community forum access', included: true }, + { name: '24/7 Phone & Email support', included: true }, + { name: 'Certificate of completion', included: true }, + { name: '1-on-1 mentorship', included: true }, + ], + ctaText: 'Contact Sales', + }, +]; + +export const PricingTable: React.FC = () => { + const [isAnnual, setIsAnnual] = useState(true); + + // Example of using dynamic pricing data hook if applicable. + // In a real scenario, this hook might override the default prices. + const { price, loading } = usePricing({ country: 'US', currency: 'USD' }); + + return ( +
+
+
+

+ Simple, transparent pricing +

+

+ Choose the plan that best fits your needs. No hidden fees. +

+
+ +
+ Monthly + + + Annually (Save 20%) + +
+ +
+ {tiers.map((tier) => ( +
+
+ {tier.popular && ( + + Most Popular + + )} +

{tier.name}

+

{tier.description}

+
+ +
+ + ${isAnnual ? tier.annualPrice : tier.monthlyPrice} + + + /{isAnnual ? 'year' : 'month'} + +
+ +
    + {tier.features.map((feature, index) => ( +
  • + {feature.included ? ( +
  • + ))} +
+ + +
+ ))} +
+
+
+ ); +}; + +export default PricingTable; diff --git a/src/components/pricing/usePricing.ts b/src/components/pricing/usePricing.ts index 5d1ce6c..ffd93eb 100644 --- a/src/components/pricing/usePricing.ts +++ b/src/components/pricing/usePricing.ts @@ -1,15 +1,36 @@ // usePricing.ts import { useEffect, useState } from "react"; -import { fetchPrice } from "../services/pricing.api"; -export function usePricing({ country, currency }) { +// Mock implementation of fetchPrice since pricing.api does not exist +const fetchPrice = async ( + params: { country: string; currency: string; options: any; promoCode: string }, + options: { signal: AbortSignal } +) => { + return new Promise((resolve, reject) => { + const timeout = setTimeout(() => { + if (options.signal.aborted) { + reject(new Error("Aborted")); + } else { + resolve({ finalPrice: params.options.quantity * 10, currency: params.currency }); + } + }, 1000); + options.signal.addEventListener("abort", () => clearTimeout(timeout)); + }); +}; + +interface UsePricingProps { + country: string; + currency: string; +} + +export function usePricing({ country, currency }: UsePricingProps) { const [options, setOptions] = useState({ quantity: 1, premium: false, }); const [promoCode, setPromoCode] = useState(""); - const [price, setPrice] = useState(null); + const [price, setPrice] = useState(null); const [loading, setLoading] = useState(false); useEffect(() => { @@ -23,9 +44,10 @@ export function usePricing({ country, currency }) { { signal: controller.signal } ); setPrice(data); - } catch (err) { + } catch (err: any) { + if (err.name === 'AbortError' || err.message === 'Aborted') return; // fallback handling - setPrice((prev) => prev || { finalPrice: 0, currency }); + setPrice((prev: any) => prev || { finalPrice: 0, currency }); } finally { setLoading(false); }