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

This commit is contained in:
2026-04-18 22:10:19 +02:00
parent 6ec1a3ae84
commit 3e2063e4fa
89 changed files with 4405 additions and 967 deletions

View File

@@ -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>