'use client'; import { useState, useEffect, Suspense } from 'react'; import { useSearchParams, useRouter } from 'next/navigation'; import { Search, ChevronRight, ArrowRight, ExternalLink, Zap, Key, Package, TrendingUp, Building2, ShieldCheck, AlertTriangle, List, Home, Menu, X, CheckCircle2, Clock, Info, } from 'lucide-react'; import { CodeBlock } from '@/components/docs/CodeBlock'; import { DOC_SECTIONS, ALL_NAV_ITEMS } from '@/components/docs/docsNav'; // ─── Shared sub-components ──────────────────────────────────────────────────── function H2({ children }: { children: React.ReactNode }) { return

{children}

; } function H3({ children }: { children: React.ReactNode }) { return

{children}

; } function P({ children }: { children: React.ReactNode }) { return

{children}

; } function Divider() { return
; } function Callout({ type = 'info', children }: { type?: 'info' | 'warning' | 'success'; children: React.ReactNode }) { const styles = { info: 'bg-blue-50 border-blue-200 text-blue-800', warning: 'bg-amber-50 border-amber-200 text-amber-800', success: 'bg-green-50 border-green-200 text-green-800', }; const icons = { info: Info, warning: AlertTriangle, success: CheckCircle2 }; const Icon = icons[type]; return (
{children}
); } function InlineCode({ children }: { children: React.ReactNode }) { return ( {children} ); } function Table({ headers, rows }: { headers: string[]; rows: (string | React.ReactNode)[][] }) { return (
{headers.map(h => ( ))} {rows.map((row, i) => ( {row.map((cell, j) => ( ))} ))}
{h}
{cell}
); } function SectionHeader({ title, description, badge }: { title: string; description: string; badge?: string }) { return (
{badge && ( {badge} )}

{title}

{description}

); } // ─── Section: Home ──────────────────────────────────────────────────────────── function HomeSection({ onNavigate }: { onNavigate: (id: string) => void }) { const cards = [ { id: 'quickstart', icon: Zap, title: 'Guide de démarrage', description: 'Faites votre première requête API en moins de 5 minutes.', color: 'text-amber-500', bg: 'bg-amber-50', }, { id: 'authentication', icon: Key, title: 'Authentification', description: 'Créez et gérez vos clés API pour accéder à la plateforme.', color: 'text-blue-500', bg: 'bg-blue-50', }, { id: 'bookings', icon: Package, title: 'Bookings', description: 'Créez, consultez et gérez des réservations de fret maritime.', color: 'text-violet-500', bg: 'bg-violet-50', }, { id: 'rates', icon: TrendingUp, title: 'Tarifs & Recherche', description: 'Recherchez et comparez des tarifs en temps réel.', color: 'text-emerald-500', bg: 'bg-emerald-50', }, { id: 'organizations', icon: Building2, title: 'Organisations', description: 'Accédez aux données de votre organisation.', color: 'text-orange-500', bg: 'bg-orange-50', }, { id: 'endpoints', icon: List, title: 'Référence complète', description: 'Tous les endpoints, paramètres et réponses.', color: 'text-gray-500', bg: 'bg-gray-50', }, ]; return (
{/* Hero */}
Plans Gold & Platinium uniquement

API Xpeditis

Intégrez la puissance de la freight logistics maritime dans vos applications. Tarifs en temps réel, gestion de bookings, suivi d'expéditions.

{/* Cards grid */}

Explorer la documentation

{cards.map(card => ( ))}
{/* Base URL */}

URL de base

Toutes les requêtes API doivent être envoyées à l'URL de base suivante :

En environnement de développement local :

); } // ─── Section: Quick Start ───────────────────────────────────────────────────── function QuickStartSection({ onNavigate }: { onNavigate: (id: string) => void }) { return (
L'accès API est disponible uniquement sur les plans Gold et Platinium. Rendez-vous dans Paramètres → Abonnement pour upgrader. {/* Step 1 */}
1

Obtenir votre clé API

Dans le dashboard, rendez-vous dans Paramètres → Clés API, puis cliquez sur Créer une clé. La clé complète vous sera montrée une seule fois — conservez-la immédiatement.

Format de la clé :

{/* Step 2 */}
2

Faire votre première requête

Passez la clé dans l'en-tête X-API-Key :

{/* Step 3 */}
3

Lire la réponse

Toutes les réponses sont en JSON. En cas de succès :

En cas d'erreur :

Étapes suivantes

{[ { id: 'authentication', label: 'Gérer vos clés API', desc: 'Créer, lister et révoquer des clés' }, { id: 'bookings', label: 'Créer un booking', desc: 'Réservez du fret maritime via l\'API' }, { id: 'rates', label: 'Rechercher des tarifs', desc: 'Comparez les tarifs en temps réel' }, { id: 'errors', label: 'Gestion des erreurs', desc: 'Tous les codes d\'erreur expliqués' }, ].map(item => ( ))}
); } // ─── Section: Authentication ────────────────────────────────────────────────── function AuthenticationSection() { return (

Vue d'ensemble

L'API Xpeditis utilise des clés API pour authentifier les requêtes. Transmettez votre clé dans l'en-tête X-API-Key de chaque requête.

Vos clés API sont confidentielles. Ne les partagez jamais dans du code public, des dépôts Git ou des forums. En cas de compromission, révoquez immédiatement la clé depuis le dashboard.

Format de la clé

Toutes les clés Xpeditis commencent par xped_live_ suivi de 64 caractères hexadécimaux :

Utilisation

Header HTTP

Passez votre clé dans l'en-tête X-API-Key :

Exemples par langage

Endpoints de gestion

Ces endpoints nécessitent une authentification par token JWT (connexion via le dashboard) et non une clé API.

Créer une clé

" \\ -H "Content-Type: application/json" \\ -d '{ "name": "Intégration ERP Production", "expiresAt": "2027-01-01T00:00:00.000Z" }'`} /> Le champ fullKey est retourné une seule fois. Stockez-le immédiatement dans un gestionnaire de secrets.

Sécurité

Stockage recommandé

Rotation des clés

Effectuez une rotation régulière (tous les 90 jours recommandé) : créez une nouvelle clé, migrez votre système, puis révoquez l'ancienne.

); } // ─── Section: Bookings ──────────────────────────────────────────────────────── function BookingsSection() { return (

Lister les bookings

status, 'string', 'Filtrer par statut (draft, confirmed, in_transit, delivered, cancelled)'], [page, 'number', 'Numéro de page (défaut: 1)'], [limit, 'number', 'Résultats par page (défaut: 20, max: 100)'], [origin, 'string', 'Code port d\'origine (ex: FRLEH)'], [destination, 'string', 'Code port de destination (ex: CNSHA)'], ]} />

Créer un booking

Statuts d'un booking

draft, 'Réservation créée, non confirmée'], [pending_confirmation, 'En attente de confirmation du transporteur'], [confirmed, 'Confirmée par le transporteur'], [in_transit, 'Expédition en cours'], [delivered, 'Livraison confirmée'], [cancelled, 'Annulée'], ]} /> ); } // ─── Section: Rates ─────────────────────────────────────────────────────────── function RatesSection() { return (

Rechercher des tarifs

origin, '✅', 'Code port d\'origine (UN/LOCODE, ex: FRLEH)'], [destination, '✅', 'Code port de destination (ex: CNSHA)'], [containerType, '✅', 'Type de conteneur: 20GP, 40GP, 40HC, 45HC, 20FR, 40FR'], [departureDate, '❌', 'Date souhaitée de départ (YYYY-MM-DD)'], [sortBy, '❌', 'Tri: price_asc | price_desc | transit_time'], ]} /> Les tarifs sont mis en cache pendant 15 minutes. Au-delà, une nouvelle recherche est effectuée auprès des transporteurs en temps réel.

Codes de ports (UN/LOCODE)

Les ports sont identifiés par le code standard UN/LOCODE (5 caractères).

); } // ─── Section: Organizations ─────────────────────────────────────────────────── function OrganizationsSection() { return (

Profil de l'organisation

Membres de l'organisation

); } // ─── Section: Endpoints ─────────────────────────────────────────────────────── function EndpointsSection() { const endpoints = [ { method: 'GET', path: '/bookings', desc: 'Lister les bookings' }, { method: 'POST', path: '/bookings', desc: 'Créer un booking' }, { method: 'GET', path: '/bookings/:id', desc: 'Détail d\'un booking' }, { method: 'PATCH', path: '/bookings/:id/status', desc: 'Mettre à jour le statut' }, { method: 'GET', path: '/rates/search', desc: 'Rechercher des tarifs' }, { method: 'GET', path: '/rates/:id', desc: 'Détail d\'un tarif' }, { method: 'GET', path: '/ports', desc: 'Lister les ports' }, { method: 'GET', path: '/organizations/me', desc: 'Profil de l\'organisation' }, { method: 'GET', path: '/users', desc: 'Membres de l\'organisation' }, { method: 'POST', path: '/api-keys', desc: 'Créer une clé API (JWT requis)' }, { method: 'GET', path: '/api-keys', desc: 'Lister les clés API (JWT requis)' }, { method: 'DELETE', path: '/api-keys/:id', desc: 'Révoquer une clé (JWT requis)' }, ]; const methodColor: Record = { GET: 'text-emerald-700 bg-emerald-50 border-emerald-200', POST: 'text-blue-700 bg-blue-50 border-blue-200', PATCH: 'text-amber-700 bg-amber-50 border-amber-200', DELETE: 'text-red-700 bg-red-50 border-red-200', }; return (

Endpoints disponibles

{endpoints.map((ep, i) => (
{ep.method} {ep.path} {ep.desc}
))}

Format de réponse standard

); } // ─── Section: Errors ───────────────────────────────────────────────────────── function ErrorsSection() { return (

Format d'erreur

Toutes les erreurs retournent un JSON standardisé :

Codes HTTP

Erreurs courantes

401 — Clé API invalide

Solutions : Vérifiez que la clé commence par xped_live_, qu'elle n'est pas révoquée et que l'abonnement est toujours Gold ou Platinium.

403 — Plan insuffisant

429 — Rate limit

En cas de 429, attendez la durée indiquée dans retryAfter avant de réessayer.

); } // ─── Section: Rate Limiting ─────────────────────────────────────────────────── function RateLimitingSection() { return (

Limites par plan

Le rate limiting est appliqué par utilisateur (ID de l'utilisateur associé à la clé API).

En-têtes de réponse

Chaque réponse inclut des en-têtes pour suivre votre consommation :

Bonnes pratiques

{[ { icon: Clock, title: 'Exponential backoff', desc: 'En cas de 429, attendez avant de réessayer (1s, 2s, 4s, 8s…).' }, { icon: CheckCircle2, title: 'Mise en cache', desc: 'Cachez les résultats côté client pour éviter les appels redondants. Les tarifs restent valides 15 minutes.' }, { icon: ShieldCheck, title: 'Une clé par service', desc: 'Utilisez des clés séparées par service ou environnement pour un meilleur suivi et une révocation ciblée.' }, ].map((item, i) => (

{item.title}

{item.desc}

))}
); } // ─── Section map ────────────────────────────────────────────────────────────── function SectionContent({ activeSection, onNavigate, }: { activeSection: string; onNavigate: (id: string) => void; }) { switch (activeSection) { case 'home': return ; case 'quickstart': return ; case 'authentication': return ; case 'bookings': return ; case 'rates': return ; case 'organizations': return ; case 'endpoints': return ; case 'errors': return ; case 'rate-limiting': return ; default: return ; } } // ─── Main Page ──────────────────────────────────────────────────────────────── function DocsPageContent() { const searchParams = useSearchParams(); const router = useRouter(); const [activeSection, setActiveSection] = useState(searchParams.get('section') ?? 'home'); const [searchQuery, setSearchQuery] = useState(''); const [sidebarOpen, setSidebarOpen] = useState(false); const navigate = (id: string) => { setActiveSection(id); router.replace(`/dashboard/docs?section=${id}`, { scroll: false }); setSidebarOpen(false); window.scrollTo({ top: 0, behavior: 'smooth' }); }; // Filter nav based on search const filteredSections = DOC_SECTIONS.map(section => ({ ...section, items: section.items.filter(item => item.label.toLowerCase().includes(searchQuery.toLowerCase()) ), })).filter(s => s.items.length > 0); const Sidebar = () => (
{/* Logo + title */}
X
Xpeditis / API
{/* Search */}
setSearchQuery(e.target.value)} className="w-full pl-8 pr-3 py-1.5 text-sm bg-gray-50 border border-gray-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-[#34CCCD]/30 focus:border-[#34CCCD] placeholder-gray-400" />
{/* Navigation */} {/* Footer */}
); return ( /* Break out of dashboard's p-6 padding */
{/* Mobile sidebar overlay */} {sidebarOpen && (
setSidebarOpen(false)} /> )} {/* Docs sidebar — desktop */}
{/* Docs sidebar — mobile drawer */}
{/* Main content */}
{/* Mobile top bar */}
{ALL_NAV_ITEMS.find(i => i.id === activeSection)?.label ?? 'Documentation'}
{/* Breadcrumb */}
{activeSection !== 'home' && ( <> {ALL_NAV_ITEMS.find(i => i.id === activeSection)?.label} )}
{/* Bottom nav */} {activeSection !== 'home' && (
)}
); } export default function DocsPage() { return (
}>
); }