'use client'; import { useState } from 'react'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { listApiKeys, createApiKey, revokeApiKey } from '@/lib/api/api-keys'; import type { ApiKeyDto, CreateApiKeyResultDto } from '@/lib/api/api-keys'; import { useSubscription } from '@/lib/context/subscription-context'; import { Key, Plus, Trash2, Copy, Check, AlertTriangle, Clock, X, ShieldCheck, Lock, } from 'lucide-react'; // ─── Helpers ──────────────────────────────────────────────────────────────── function formatDate(iso: string | null): string { if (!iso) return '—'; return new Intl.DateTimeFormat('fr-FR', { dateStyle: 'medium' }).format(new Date(iso)); } function keyStatusBadge(key: ApiKeyDto) { if (!key.isActive) { return ( Révoquée ); } if (key.expiresAt && new Date(key.expiresAt) < new Date()) { return ( Expirée ); } return ( Active ); } // ─── Copy button ───────────────────────────────────────────────────────────── function CopyButton({ text }: { text: string }) { const [copied, setCopied] = useState(false); const handleCopy = async () => { await navigator.clipboard.writeText(text); setCopied(true); setTimeout(() => setCopied(false), 2000); }; return ( ); } // ─── Creation success modal ────────────────────────────────────────────────── function CreatedKeyModal({ result, onClose, }: { result: CreateApiKeyResultDto; onClose: () => void; }) { return (
{/* Header */}

Clé API créée

{result.name}

{/* Warning */}

Copiez cette clé maintenant. Elle ne sera plus jamais affichée après la fermeture de cette fenêtre.

{/* Key */}
{result.fullKey}

Stockez-la dans vos variables d'environnement ou un gestionnaire de secrets.

{/* Footer */}
); } // ─── Create key form modal ─────────────────────────────────────────────────── function CreateKeyModal({ onSuccess, onClose, }: { onSuccess: (result: CreateApiKeyResultDto) => void; onClose: () => void; }) { const [name, setName] = useState(''); const [expiresAt, setExpiresAt] = useState(''); const queryClient = useQueryClient(); const mutation = useMutation({ mutationFn: createApiKey, onSuccess: result => { queryClient.invalidateQueries({ queryKey: ['api-keys'] }); onSuccess(result); }, }); const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); mutation.mutate({ name: name.trim(), ...(expiresAt ? { expiresAt: new Date(expiresAt).toISOString() } : {}), }); }; return (
{/* Header */}

Nouvelle clé API

{/* Name */}
setName(e.target.value)} placeholder="ex: Intégration ERP Production" maxLength={100} required className="w-full px-3.5 py-2.5 border border-gray-200 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-[#34CCCD] focus:border-transparent" />

{name.length}/100 caractères

{/* Expiry */}
setExpiresAt(e.target.value)} min={new Date().toISOString().split('T')[0]} className="w-full px-3.5 py-2.5 border border-gray-200 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-[#34CCCD] focus:border-transparent" />

Si vide, la clé n'expire jamais.

{/* Error */} {mutation.isError && (
Une erreur est survenue. Veuillez réessayer.
)} {/* Actions */}
); } // ─── Revoke confirm modal ──────────────────────────────────────────────────── function RevokeConfirmModal({ apiKey, onConfirm, onClose, }: { apiKey: ApiKeyDto; onConfirm: () => void; onClose: () => void; }) { return (

Révoquer cette clé ?

{apiKey.name}

Cette action est immédiate et irréversible. Toute requête utilisant cette clé sera refusée.

); } // ─── Main page ──────────────────────────────────────────────────────────────── export default function ApiKeysPage() { const { hasFeature } = useSubscription(); const queryClient = useQueryClient(); const hasApiAccess = hasFeature('api_access'); const [showCreateModal, setShowCreateModal] = useState(false); const [createdKey, setCreatedKey] = useState(null); const [revokeTarget, setRevokeTarget] = useState(null); const { data: apiKeys, isLoading } = useQuery({ queryKey: ['api-keys'], queryFn: listApiKeys, enabled: hasApiAccess, }); const revokeMutation = useMutation({ mutationFn: revokeApiKey, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['api-keys'] }); setRevokeTarget(null); }, }); // Plan upsell screen if (!hasApiAccess) { return (

Accès API

L'accès programmatique à l'API Xpeditis est disponible sur les plans{' '} Gold et Platinium uniquement.

Voir les plans
); } const activeKeys = apiKeys?.filter(k => k.isActive) ?? []; return ( <> {/* Modals */} {showCreateModal && ( { setShowCreateModal(false); setCreatedKey(result); }} onClose={() => setShowCreateModal(false)} /> )} {createdKey && ( setCreatedKey(null)} /> )} {revokeTarget && ( revokeMutation.mutate(revokeTarget.id)} onClose={() => setRevokeTarget(null)} /> )} {/* Page header */}

Clés API

Gérez les clés d'accès programmatique à l'API Xpeditis.

{/* Info banner */}

Comment utiliser vos clés API

Ajoutez l'en-tête{' '} X-API-Key: xped_live_... {' '} à chaque requête HTTP.{' '} Voir la documentation

{/* Keys list */}
{isLoading ? (
) : !apiKeys || apiKeys.length === 0 ? (

Aucune clé API pour le moment.

) : (
{/* Table header */}
Nom / Préfixe Dernière utilisation Expiration Statut
{apiKeys.map(key => (
{/* Name + prefix */}

{key.name}

{key.keyPrefix}…
{/* Last used */} {formatDate(key.lastUsedAt)} {/* Expiry */} {formatDate(key.expiresAt)} {/* Status */}
{keyStatusBadge(key)}
{/* Actions */}
))}
)}
{/* Quota */} {apiKeys && apiKeys.length > 0 && (

{activeKeys.length} / 20 clés actives utilisées

)} ); }