feat: implement core platform features including business directory, admin dashboard, authentication, and API infrastructure
Some checks failed
Build and Push App / build (push) Failing after 16m2s
Some checks failed
Build and Push App / build (push) Failing after 16m2s
This commit is contained in:
@@ -1,80 +1,52 @@
|
||||
"use client";
|
||||
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Check, X, Smartphone, CreditCard, Loader, ShieldCheck } from 'lucide-react';
|
||||
|
||||
interface Plan {
|
||||
id: string;
|
||||
tier: string;
|
||||
name: string;
|
||||
priceXOF: string;
|
||||
priceEUR: string;
|
||||
description: string;
|
||||
features: string[];
|
||||
recommended?: boolean;
|
||||
recommended: boolean;
|
||||
color: string;
|
||||
offerLimit: number;
|
||||
}
|
||||
|
||||
const PLANS: Plan[] = [
|
||||
{
|
||||
id: 'starter',
|
||||
name: 'Starter',
|
||||
priceXOF: 'Gratuit',
|
||||
priceEUR: '0€',
|
||||
description: 'Pour démarrer votre présence en ligne.',
|
||||
features: [
|
||||
'Fiche entreprise basique',
|
||||
'Visible dans la recherche',
|
||||
'1 Offre produit/service',
|
||||
'Support par email'
|
||||
],
|
||||
color: 'gray'
|
||||
},
|
||||
{
|
||||
id: 'booster',
|
||||
name: 'Booster',
|
||||
priceXOF: '5.000 FCFA',
|
||||
priceEUR: '8€',
|
||||
description: 'L\'indispensable pour les entreprises en croissance.',
|
||||
recommended: true,
|
||||
features: [
|
||||
'Tout du plan Starter',
|
||||
'Badge "Vérifié" ✅',
|
||||
'Jusqu\'à 10 Offres produits',
|
||||
'Lien vers réseaux sociaux & Site Web',
|
||||
'Statistiques de base (Vues)'
|
||||
],
|
||||
color: 'brand'
|
||||
},
|
||||
{
|
||||
id: 'empire',
|
||||
name: 'Empire',
|
||||
priceXOF: '15.000 FCFA',
|
||||
priceEUR: '23€',
|
||||
description: 'Dominez votre marché avec une visibilité maximale.',
|
||||
features: [
|
||||
'Tout du plan Booster',
|
||||
'Badge "Recommandé" 🏆',
|
||||
'Offres illimitées',
|
||||
'Intégration vidéo Youtube',
|
||||
'Interview écrite sur le Blog',
|
||||
'Support prioritaire WhatsApp'
|
||||
],
|
||||
color: 'gray'
|
||||
}
|
||||
];
|
||||
|
||||
const PricingSection = () => {
|
||||
const [plans, setPlans] = useState<Plan[]>([]);
|
||||
const [loadingPlans, setLoadingPlans] = useState(true);
|
||||
const [selectedPlan, setSelectedPlan] = useState<Plan | null>(null);
|
||||
const [billingCycle, setBillingCycle] = useState<'monthly' | 'yearly'>('monthly');
|
||||
const [isPaymentModalOpen, setIsPaymentModalOpen] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchPlans = async () => {
|
||||
try {
|
||||
const res = await fetch('/api/plans');
|
||||
const data = await res.json();
|
||||
if (!data.error) {
|
||||
setPlans(data);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching plans:', error);
|
||||
} finally {
|
||||
setLoadingPlans(false);
|
||||
}
|
||||
};
|
||||
fetchPlans();
|
||||
}, []);
|
||||
|
||||
// Payment State
|
||||
const [paymentStep, setPaymentStep] = useState<'method' | 'processing' | 'success'>('method');
|
||||
const [paymentMethod, setPaymentMethod] = useState<'mobile_money' | 'card' | null>(null);
|
||||
|
||||
const handleSelectPlan = (plan: Plan) => {
|
||||
if (plan.id === 'starter') return; // Free plan, no payment
|
||||
if (plan.tier === 'STARTER') return; // Free plan, no payment
|
||||
setSelectedPlan(plan);
|
||||
setPaymentStep('method');
|
||||
setIsPaymentModalOpen(true);
|
||||
@@ -130,53 +102,68 @@ const PricingSection = () => {
|
||||
|
||||
{/* Plans Grid */}
|
||||
<div className="mt-12 space-y-4 sm:mt-16 sm:space-y-0 sm:grid sm:grid-cols-2 sm:gap-6 lg:max-w-4xl lg:mx-auto xl:max-w-none xl:mx-0 xl:grid-cols-3">
|
||||
{PLANS.map((plan) => (
|
||||
<div key={plan.id} className={`rounded-2xl shadow-xl bg-white border-2 flex flex-col ${plan.recommended ? 'border-brand-500 ring-4 ring-brand-50 relative transform scale-105 z-10' : 'border-gray-100'}`}>
|
||||
{plan.recommended && (
|
||||
<div className="absolute top-0 inset-x-0 -mt-4 flex justify-center">
|
||||
<span className="bg-brand-500 text-white px-4 py-1 rounded-full text-xs font-bold uppercase tracking-wider shadow-sm">
|
||||
Populaire
|
||||
</span>
|
||||
{loadingPlans ? (
|
||||
[1, 2, 3].map((i) => (
|
||||
<div key={i} className="rounded-2xl shadow-xl bg-white border border-gray-100 p-8 h-96 animate-pulse">
|
||||
<div className="h-8 bg-gray-200 rounded w-1/2 mb-4"></div>
|
||||
<div className="h-4 bg-gray-200 rounded w-3/4 mb-10"></div>
|
||||
<div className="h-10 bg-gray-200 rounded mb-4"></div>
|
||||
<div className="space-y-3 mt-10">
|
||||
<div className="h-4 bg-gray-200 rounded w-full"></div>
|
||||
<div className="h-4 bg-gray-200 rounded w-full"></div>
|
||||
<div className="h-4 bg-gray-200 rounded w-full"></div>
|
||||
</div>
|
||||
)}
|
||||
<div className="p-6 md:p-8 flex-1">
|
||||
<h3 className="text-2xl font-bold text-gray-900 font-serif">{plan.name}</h3>
|
||||
<p className="mt-4 text-sm text-gray-500">{plan.description}</p>
|
||||
<div className="mt-8 flex items-baseline">
|
||||
<span className="text-4xl font-extrabold text-gray-900 tracking-tight">
|
||||
{plan.priceXOF === 'Gratuit' ? 'Gratuit' : (billingCycle === 'yearly' && plan.id !== 'starter' ? 'Sur Devis' : plan.priceXOF)}
|
||||
</span>
|
||||
{plan.priceXOF !== 'Gratuit' && <span className="ml-1 text-xl font-medium text-gray-500">/mois</span>}
|
||||
</div>
|
||||
{plan.priceXOF !== 'Gratuit' && (
|
||||
<p className="text-xs text-gray-400 mt-1">soit env. {plan.priceEUR} /mois</p>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
plans.map((plan) => (
|
||||
<div key={plan.id} className={`rounded-2xl shadow-xl bg-white border-2 flex flex-col ${plan.recommended ? 'border-brand-500 ring-4 ring-brand-50 relative transform scale-105 z-10' : 'border-gray-100'}`}>
|
||||
{plan.recommended && (
|
||||
<div className="absolute top-0 inset-x-0 -mt-4 flex justify-center">
|
||||
<span className="bg-brand-500 text-white px-4 py-1 rounded-full text-xs font-bold uppercase tracking-wider shadow-sm">
|
||||
Populaire
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="p-6 md:p-8 flex-1">
|
||||
<h3 className="text-2xl font-bold text-gray-900 font-serif">{plan.name}</h3>
|
||||
<p className="mt-4 text-sm text-gray-500">{plan.description}</p>
|
||||
<div className="mt-8 flex items-baseline">
|
||||
<span className="text-4xl font-extrabold text-gray-900 tracking-tight">
|
||||
{plan.priceXOF === 'Gratuit' ? 'Gratuit' : (billingCycle === 'yearly' && plan.tier !== 'STARTER' ? 'Sur Devis' : plan.priceXOF)}
|
||||
</span>
|
||||
{plan.priceXOF !== 'Gratuit' && <span className="ml-1 text-xl font-medium text-gray-500">/mois</span>}
|
||||
</div>
|
||||
{plan.priceXOF !== 'Gratuit' && (
|
||||
<p className="text-xs text-gray-400 mt-1">soit env. {plan.priceEUR} /mois</p>
|
||||
)}
|
||||
|
||||
<ul className="mt-8 space-y-4">
|
||||
{plan.features.map((feature) => (
|
||||
<li key={feature} className="flex items-start">
|
||||
<div className="flex-shrink-0">
|
||||
<Check className="h-5 w-5 text-green-500" />
|
||||
</div>
|
||||
<p className="ml-3 text-sm text-gray-700">{feature}</p>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<ul className="mt-8 space-y-4">
|
||||
{plan.features.map((feature) => (
|
||||
<li key={feature} className="flex items-start">
|
||||
<div className="flex-shrink-0">
|
||||
<Check className="h-5 w-5 text-green-500" />
|
||||
</div>
|
||||
<p className="ml-3 text-sm text-gray-700">{feature}</p>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
<div className="p-6 bg-gray-50 rounded-b-2xl">
|
||||
<button
|
||||
onClick={() => handleSelectPlan(plan)}
|
||||
className={`w-full block text-center rounded-lg border border-transparent px-6 py-3 text-base font-medium transition-colors ${
|
||||
plan.tier === 'STARTER'
|
||||
? 'text-brand-700 bg-brand-100 hover:bg-brand-200'
|
||||
: 'text-white bg-brand-600 hover:bg-brand-700 shadow-md hover:shadow-lg'
|
||||
}`}
|
||||
>
|
||||
{plan.tier === 'STARTER' ? 'Commencer Gratuitement' : `Choisir ${plan.name}`}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-6 bg-gray-50 rounded-b-2xl">
|
||||
<button
|
||||
onClick={() => handleSelectPlan(plan)}
|
||||
className={`w-full block text-center rounded-lg border border-transparent px-6 py-3 text-base font-medium transition-colors ${
|
||||
plan.id === 'starter'
|
||||
? 'text-brand-700 bg-brand-100 hover:bg-brand-200'
|
||||
: 'text-white bg-brand-600 hover:bg-brand-700 shadow-md hover:shadow-lg'
|
||||
}`}
|
||||
>
|
||||
{plan.id === 'starter' ? 'Commencer Gratuitement' : `Choisir ${plan.name}`}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user