fix
This commit is contained in:
parent
301409624b
commit
10b45599ae
@ -27,14 +27,15 @@ export default function OrganizationSettingsPage() {
|
||||
const searchParams = useSearchParams();
|
||||
const [activeTab, setActiveTab] = useState<TabType>('information');
|
||||
|
||||
// Auto-switch to subscription tab if coming back from Stripe
|
||||
// Auto-switch to subscription tab if coming back from Stripe (only for ADMIN/MANAGER)
|
||||
useEffect(() => {
|
||||
const isSuccess = searchParams.get('success') === 'true';
|
||||
const isCanceled = searchParams.get('canceled') === 'true';
|
||||
if (isSuccess || isCanceled) {
|
||||
const canAccessBilling = user?.role === 'ADMIN' || user?.role === 'MANAGER';
|
||||
if ((isSuccess || isCanceled) && canAccessBilling) {
|
||||
setActiveTab('subscription');
|
||||
}
|
||||
}, [searchParams]);
|
||||
}, [searchParams, user?.role]);
|
||||
const [organization, setOrganization] = useState<OrganizationResponse | null>(null);
|
||||
const [formData, setFormData] = useState<OrganizationForm>({
|
||||
name: '',
|
||||
@ -165,6 +166,9 @@ export default function OrganizationSettingsPage() {
|
||||
);
|
||||
}
|
||||
|
||||
// Check if user can view subscription and licenses (only ADMIN and MANAGER)
|
||||
const canViewBilling = user?.role === 'ADMIN' || user?.role === 'MANAGER';
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
id: 'information' as TabType,
|
||||
@ -185,6 +189,8 @@ export default function OrganizationSettingsPage() {
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
// Only show subscription and licenses tabs for ADMIN and MANAGER roles
|
||||
...(canViewBilling ? [
|
||||
{
|
||||
id: 'subscription' as TabType,
|
||||
label: 'Abonnement',
|
||||
@ -203,6 +209,7 @@ export default function OrganizationSettingsPage() {
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
] : []),
|
||||
];
|
||||
|
||||
return (
|
||||
@ -432,9 +439,9 @@ export default function OrganizationSettingsPage() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{activeTab === 'subscription' && <SubscriptionTab />}
|
||||
{activeTab === 'subscription' && canViewBilling && <SubscriptionTab />}
|
||||
|
||||
{activeTab === 'licenses' && <LicensesTab />}
|
||||
{activeTab === 'licenses' && canViewBilling && <LicensesTab />}
|
||||
</div>
|
||||
|
||||
{/* Actions (only for information and address tabs) */}
|
||||
|
||||
@ -22,6 +22,12 @@ import {
|
||||
Container,
|
||||
FileText,
|
||||
LayoutDashboard,
|
||||
Bell,
|
||||
BookOpen,
|
||||
Users,
|
||||
Building2,
|
||||
Check,
|
||||
X,
|
||||
} from 'lucide-react';
|
||||
import { useAuth } from '@/lib/context/auth-context';
|
||||
|
||||
@ -50,6 +56,7 @@ export default function LandingPage() {
|
||||
const featuresRef = useRef(null);
|
||||
const statsRef = useRef(null);
|
||||
const toolsRef = useRef(null);
|
||||
const pricingRef = useRef(null);
|
||||
const testimonialsRef = useRef(null);
|
||||
const ctaRef = useRef(null);
|
||||
|
||||
@ -57,6 +64,7 @@ export default function LandingPage() {
|
||||
const isFeaturesInView = useInView(featuresRef, { once: true });
|
||||
const isStatsInView = useInView(statsRef, { once: true });
|
||||
const isToolsInView = useInView(toolsRef, { once: true });
|
||||
const isPricingInView = useInView(pricingRef, { once: true });
|
||||
const isTestimonialsInView = useInView(testimonialsRef, { once: true });
|
||||
const isCtaInView = useInView(ctaRef, { once: true });
|
||||
|
||||
@ -72,84 +80,90 @@ export default function LandingPage() {
|
||||
}, []);
|
||||
|
||||
const features = [
|
||||
{
|
||||
icon: Search,
|
||||
title: 'Recherche Intelligente',
|
||||
description:
|
||||
'Comparez instantanément les tarifs de plus de 50 compagnies maritimes en temps réel.',
|
||||
color: 'from-blue-500 to-cyan-500',
|
||||
},
|
||||
{
|
||||
icon: Zap,
|
||||
title: 'Réservation Rapide',
|
||||
description: 'Réservez vos containers LCL/FCL en quelques clics avec confirmation immédiate.',
|
||||
color: 'from-purple-500 to-pink-500',
|
||||
},
|
||||
{
|
||||
icon: BarChart3,
|
||||
title: 'Tableau de Bord',
|
||||
description: 'Suivez tous vos envois en temps réel avec des KPIs détaillés et des analytics.',
|
||||
title: 'Dashboard Analytics',
|
||||
description:
|
||||
'Suivez tous vos KPIs en temps réel : bookings, volumes, revenus et alertes personnalisées.',
|
||||
color: 'from-blue-500 to-cyan-500',
|
||||
link: '/dashboard',
|
||||
},
|
||||
{
|
||||
icon: Package,
|
||||
title: 'Gestion des Bookings',
|
||||
description: 'Créez, gérez et suivez vos réservations maritimes LCL/FCL avec un historique complet.',
|
||||
color: 'from-purple-500 to-pink-500',
|
||||
link: '/dashboard/bookings',
|
||||
},
|
||||
{
|
||||
icon: FileText,
|
||||
title: 'Documents Maritimes',
|
||||
description: 'Centralisez tous vos documents : B/L, factures, certificats et documents douaniers.',
|
||||
color: 'from-orange-500 to-red-500',
|
||||
link: '/dashboard/documents',
|
||||
},
|
||||
{
|
||||
icon: Globe,
|
||||
title: '10 000+ Ports',
|
||||
icon: Search,
|
||||
title: 'Track & Trace',
|
||||
description:
|
||||
'Accédez à un réseau mondial de ports avec des données actualisées quotidiennement.',
|
||||
'Suivez vos conteneurs en temps réel auprès de 10+ transporteurs majeurs (Maersk, MSC, CMA CGM...).',
|
||||
color: 'from-green-500 to-emerald-500',
|
||||
link: '/dashboard/track-trace',
|
||||
},
|
||||
{
|
||||
icon: TrendingUp,
|
||||
title: 'Meilleurs Prix',
|
||||
icon: BookOpen,
|
||||
title: 'Wiki Maritime',
|
||||
description:
|
||||
'Optimisation automatique des tarifs pour vous garantir les prix les plus compétitifs.',
|
||||
'Base de connaissances complète : Incoterms, documents, procédures douanières et plus encore.',
|
||||
color: 'from-yellow-500 to-orange-500',
|
||||
link: '/dashboard/wiki',
|
||||
},
|
||||
{
|
||||
icon: Shield,
|
||||
title: 'Sécurisé',
|
||||
icon: Bell,
|
||||
title: 'Notifications Temps Réel',
|
||||
description:
|
||||
'Plateforme conforme aux standards internationaux avec chiffrement de bout en bout.',
|
||||
'Restez informé avec des alertes instantanées sur vos bookings, documents et mises à jour.',
|
||||
color: 'from-indigo-500 to-purple-500',
|
||||
link: '/dashboard',
|
||||
},
|
||||
];
|
||||
|
||||
const tools = [
|
||||
{
|
||||
icon: Calculator,
|
||||
title: 'Calculateur de Fret',
|
||||
description: 'Estimez vos coûts de transport en temps réel',
|
||||
link: '/tools/calculator',
|
||||
},
|
||||
{
|
||||
icon: MapPin,
|
||||
title: 'Distance & Temps',
|
||||
description: 'Calculez la distance et le temps entre ports',
|
||||
link: '/tools/distance',
|
||||
icon: LayoutDashboard,
|
||||
title: 'Dashboard',
|
||||
description: 'Vue d\'ensemble de votre activité maritime',
|
||||
link: '/dashboard',
|
||||
},
|
||||
{
|
||||
icon: Package,
|
||||
title: 'Optimiseur de Chargement',
|
||||
description: "Maximisez l'utilisation de vos containers",
|
||||
link: '/tools/load-optimizer',
|
||||
},
|
||||
{
|
||||
icon: Ship,
|
||||
title: 'Suivi en Temps Réel',
|
||||
description: 'Trackez vos envois partout dans le monde',
|
||||
link: '/tracking',
|
||||
title: 'Mes Bookings',
|
||||
description: 'Gérez toutes vos réservations en un seul endroit',
|
||||
link: '/dashboard/bookings',
|
||||
},
|
||||
{
|
||||
icon: FileText,
|
||||
title: 'Documents Maritimes',
|
||||
description: 'Générez automatiquement vos documents',
|
||||
link: '/tools/documents',
|
||||
title: 'Documents',
|
||||
description: 'Accédez à tous vos documents maritimes',
|
||||
link: '/dashboard/documents',
|
||||
},
|
||||
{
|
||||
icon: TrendingUp,
|
||||
title: 'Index des Tarifs',
|
||||
description: 'Suivez les tendances du marché maritime',
|
||||
link: '/tools/freight-index',
|
||||
icon: Search,
|
||||
title: 'Track & Trace',
|
||||
description: 'Suivez vos conteneurs en temps réel',
|
||||
link: '/dashboard/track-trace',
|
||||
},
|
||||
{
|
||||
icon: BookOpen,
|
||||
title: 'Wiki Maritime',
|
||||
description: 'Base de connaissances du fret maritime',
|
||||
link: '/dashboard/wiki',
|
||||
},
|
||||
{
|
||||
icon: Users,
|
||||
title: 'Mon Profil',
|
||||
description: 'Gérez vos informations personnelles',
|
||||
link: '/dashboard/profile',
|
||||
},
|
||||
];
|
||||
|
||||
@ -160,6 +174,63 @@ export default function LandingPage() {
|
||||
{ value: '99.5%', label: 'Disponibilité', icon: CheckCircle2 },
|
||||
];
|
||||
|
||||
const pricingPlans = [
|
||||
{
|
||||
name: 'Starter',
|
||||
price: 'Gratuit',
|
||||
period: '',
|
||||
description: 'Idéal pour découvrir la plateforme',
|
||||
features: [
|
||||
{ text: 'Jusqu\'à 5 bookings/mois', included: true },
|
||||
{ text: 'Track & Trace illimité', included: true },
|
||||
{ text: 'Wiki maritime complet', included: true },
|
||||
{ text: 'Dashboard basique', included: true },
|
||||
{ text: 'Support par email', included: true },
|
||||
{ text: 'Gestion des documents', included: false },
|
||||
{ text: 'Notifications temps réel', included: false },
|
||||
{ text: 'API access', included: false },
|
||||
],
|
||||
cta: 'Commencer gratuitement',
|
||||
highlighted: false,
|
||||
},
|
||||
{
|
||||
name: 'Professional',
|
||||
price: '99€',
|
||||
period: '/mois',
|
||||
description: 'Pour les transitaires en croissance',
|
||||
features: [
|
||||
{ text: 'Bookings illimités', included: true },
|
||||
{ text: 'Track & Trace illimité', included: true },
|
||||
{ text: 'Wiki maritime complet', included: true },
|
||||
{ text: 'Dashboard avancé + KPIs', included: true },
|
||||
{ text: 'Support prioritaire', included: true },
|
||||
{ text: 'Gestion des documents', included: true },
|
||||
{ text: 'Notifications temps réel', included: true },
|
||||
{ text: 'API access', included: false },
|
||||
],
|
||||
cta: 'Essai gratuit 14 jours',
|
||||
highlighted: true,
|
||||
},
|
||||
{
|
||||
name: 'Enterprise',
|
||||
price: 'Sur mesure',
|
||||
period: '',
|
||||
description: 'Pour les grandes entreprises',
|
||||
features: [
|
||||
{ text: 'Tout Professional +', included: true },
|
||||
{ text: 'API access complet', included: true },
|
||||
{ text: 'Intégrations personnalisées', included: true },
|
||||
{ text: 'Account manager dédié', included: true },
|
||||
{ text: 'SLA garanti 99.9%', included: true },
|
||||
{ text: 'Formation sur site', included: true },
|
||||
{ text: 'Multi-organisations', included: true },
|
||||
{ text: 'Audit & conformité', included: true },
|
||||
],
|
||||
cta: 'Contactez-nous',
|
||||
highlighted: false,
|
||||
},
|
||||
];
|
||||
|
||||
const testimonials = [
|
||||
{
|
||||
quote:
|
||||
@ -474,15 +545,23 @@ export default function LandingPage() {
|
||||
key={index}
|
||||
variants={itemVariants}
|
||||
whileHover={{ scale: 1.05, y: -10 }}
|
||||
className="group bg-white p-8 rounded-2xl shadow-lg hover:shadow-2xl transition-all border border-gray-100 cursor-pointer"
|
||||
>
|
||||
<Link
|
||||
href={feature.link}
|
||||
className="group block bg-white p-8 rounded-2xl shadow-lg hover:shadow-2xl transition-all border border-gray-100"
|
||||
>
|
||||
<div
|
||||
className={`w-14 h-14 rounded-xl bg-gradient-to-br ${feature.color} flex items-center justify-center mb-4 group-hover:scale-110 transition-transform`}
|
||||
>
|
||||
<IconComponent className="w-7 h-7 text-white" />
|
||||
</div>
|
||||
<h3 className="text-2xl font-bold text-brand-navy mb-3">{feature.title}</h3>
|
||||
<h3 className="text-2xl font-bold text-brand-navy mb-3 group-hover:text-brand-turquoise transition-colors">{feature.title}</h3>
|
||||
<p className="text-gray-600 leading-relaxed">{feature.description}</p>
|
||||
<div className="mt-4 flex items-center text-brand-turquoise font-medium opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
<span>Découvrir</span>
|
||||
<ArrowRight className="w-4 h-4 ml-2 group-hover:translate-x-1 transition-transform" />
|
||||
</div>
|
||||
</Link>
|
||||
</motion.div>
|
||||
);
|
||||
})}
|
||||
@ -603,6 +682,103 @@ export default function LandingPage() {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Pricing Section */}
|
||||
<section
|
||||
ref={pricingRef}
|
||||
id="pricing"
|
||||
className="py-20 lg:py-32 bg-gradient-to-br from-gray-50 to-white"
|
||||
>
|
||||
<div className="max-w-7xl mx-auto px-6 lg:px-8">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
animate={isPricingInView ? { opacity: 1, y: 0 } : {}}
|
||||
transition={{ duration: 0.8 }}
|
||||
className="text-center mb-16"
|
||||
>
|
||||
<h2 className="text-4xl lg:text-5xl font-bold text-brand-navy mb-4">
|
||||
Tarifs simples et transparents
|
||||
</h2>
|
||||
<p className="text-xl text-gray-600 max-w-2xl mx-auto">
|
||||
Choisissez le plan adapté à vos besoins. Évoluez à tout moment.
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
<motion.div
|
||||
variants={containerVariants}
|
||||
initial="hidden"
|
||||
animate={isPricingInView ? 'visible' : 'hidden'}
|
||||
className="grid grid-cols-1 md:grid-cols-3 gap-8"
|
||||
>
|
||||
{pricingPlans.map((plan, index) => (
|
||||
<motion.div
|
||||
key={index}
|
||||
variants={itemVariants}
|
||||
whileHover={{ y: -10 }}
|
||||
className={`relative bg-white rounded-2xl shadow-lg border-2 transition-all ${
|
||||
plan.highlighted
|
||||
? 'border-brand-turquoise shadow-2xl scale-105'
|
||||
: 'border-gray-200 hover:border-brand-turquoise/50'
|
||||
}`}
|
||||
>
|
||||
{plan.highlighted && (
|
||||
<div className="absolute -top-4 left-1/2 transform -translate-x-1/2">
|
||||
<span className="bg-brand-turquoise text-white text-sm font-bold px-4 py-1 rounded-full">
|
||||
Populaire
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="p-8">
|
||||
<h3 className="text-2xl font-bold text-brand-navy mb-2">{plan.name}</h3>
|
||||
<p className="text-gray-600 mb-6">{plan.description}</p>
|
||||
<div className="mb-6">
|
||||
<span className="text-5xl font-bold text-brand-navy">{plan.price}</span>
|
||||
<span className="text-gray-500">{plan.period}</span>
|
||||
</div>
|
||||
<ul className="space-y-3 mb-8">
|
||||
{plan.features.map((feature, featureIndex) => (
|
||||
<li key={featureIndex} className="flex items-center">
|
||||
{feature.included ? (
|
||||
<Check className="w-5 h-5 text-brand-green mr-3 flex-shrink-0" />
|
||||
) : (
|
||||
<X className="w-5 h-5 text-gray-300 mr-3 flex-shrink-0" />
|
||||
)}
|
||||
<span className={feature.included ? 'text-gray-700' : 'text-gray-400'}>
|
||||
{feature.text}
|
||||
</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<Link
|
||||
href={plan.name === 'Enterprise' ? '/contact' : '/register'}
|
||||
className={`block w-full text-center py-3 px-6 rounded-lg font-semibold transition-all ${
|
||||
plan.highlighted
|
||||
? 'bg-brand-turquoise text-white hover:bg-brand-turquoise/90 shadow-lg hover:shadow-xl'
|
||||
: 'bg-gray-100 text-brand-navy hover:bg-gray-200'
|
||||
}`}
|
||||
>
|
||||
{plan.cta}
|
||||
</Link>
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</motion.div>
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={isPricingInView ? { opacity: 1, y: 0 } : {}}
|
||||
transition={{ duration: 0.8, delay: 0.4 }}
|
||||
className="mt-12 text-center"
|
||||
>
|
||||
<p className="text-gray-600">
|
||||
Tous les plans incluent un essai gratuit de 14 jours. Aucune carte bancaire requise.
|
||||
</p>
|
||||
<p className="text-sm text-gray-500 mt-2">
|
||||
Des questions ? <Link href="/contact" className="text-brand-turquoise hover:underline">Contactez notre équipe commerciale</Link>
|
||||
</p>
|
||||
</motion.div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* How It Works Section */}
|
||||
<section className="py-20 lg:py-32 bg-gradient-to-br from-brand-navy to-brand-navy/95 relative overflow-hidden">
|
||||
<div className="absolute inset-0 opacity-10">
|
||||
|
||||
@ -274,9 +274,6 @@ export default function SubscriptionTab() {
|
||||
: `Plus que ${subscription.availableLicenses} licence${subscription.availableLicenses === 1 ? '' : 's'} disponible${subscription.availableLicenses === 1 ? '' : 's'}.`}
|
||||
</p>
|
||||
)}
|
||||
<p className="mt-2 text-xs text-gray-400">
|
||||
Les administrateurs (ADMIN) ont des licences illimitées et ne sont pas comptés.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Billing Period */}
|
||||
@ -416,28 +413,6 @@ export default function SubscriptionTab() {
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Info about webhooks in development */}
|
||||
{process.env.NODE_ENV === 'development' && (
|
||||
<div className="bg-yellow-50 border border-yellow-200 rounded-lg p-4">
|
||||
<div className="flex">
|
||||
<div className="flex-shrink-0">
|
||||
<svg className="h-5 w-5 text-yellow-400" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fillRule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clipRule="evenodd" />
|
||||
</svg>
|
||||
</div>
|
||||
<div className="ml-3">
|
||||
<h3 className="text-sm font-medium text-yellow-800">Mode développement</h3>
|
||||
<div className="mt-2 text-sm text-yellow-700">
|
||||
<p>
|
||||
Pour que les webhooks Stripe fonctionnent en local, exécutez :{' '}
|
||||
<code className="bg-yellow-100 px-1 rounded">stripe listen --forward-to localhost:4000/api/v1/subscriptions/webhook</code>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user