Spaces:
Sleeping
Sleeping
| /** | |
| * Composant APIDocumentation pour AfriDataHub | |
| * Created by BlackBenAI Team - AfriDataHub Platform | |
| */ | |
| import { useState, useEffect } from 'react' | |
| import { motion, AnimatePresence } from 'framer-motion' | |
| import { Button } from '@/components/ui/button' | |
| import { | |
| Code, | |
| Copy, | |
| RefreshCw, | |
| Terminal, | |
| Globe, | |
| Lock, | |
| Check, | |
| ExternalLink, | |
| BookOpen, | |
| Cpu, | |
| Database | |
| } from 'lucide-react' | |
| import { API_URL } from '../config' | |
| const APIDocumentation = () => { | |
| const [token, setToken] = useState('') | |
| const [loading, setLoading] = useState(false) | |
| const [copied, setCopied] = useState(false) | |
| const [activeTab, setActiveTab] = useState('python') | |
| useEffect(() => { | |
| fetchToken() | |
| }, []) | |
| const fetchToken = async () => { | |
| try { | |
| const storedToken = localStorage.getItem('token') | |
| const response = await fetch(`${API_URL}auth/token/`, { | |
| headers: { | |
| 'Authorization': `Token ${storedToken}` | |
| } | |
| }) | |
| const data = await response.json() | |
| if (response.ok) { | |
| setToken(data.token) | |
| } | |
| } catch (error) { | |
| console.error('Erreur lors de la récupération du token:', error) | |
| } | |
| } | |
| const regenerateToken = async () => { | |
| if (!confirm('Êtes-vous sûr de vouloir régénérer votre token ? L\'ancien token ne fonctionnera plus.')) return | |
| try { | |
| setLoading(true) | |
| const storedToken = localStorage.getItem('token') | |
| const response = await fetch(`${API_URL}auth/token/`, { | |
| method: 'POST', | |
| headers: { | |
| 'Authorization': `Token ${storedToken}` | |
| } | |
| }) | |
| const data = await response.json() | |
| if (response.ok) { | |
| setToken(data.token) | |
| } | |
| } catch (error) { | |
| console.error('Erreur lors de la régénération du token:', error) | |
| } finally { | |
| setLoading(false) | |
| } | |
| } | |
| const copyToClipboard = (text) => { | |
| navigator.clipboard.writeText(text) | |
| setCopied(true) | |
| setTimeout(() => setCopied(false), 2000) | |
| } | |
| const endpoints = [ | |
| { | |
| method: 'GET', | |
| path: '/api/datasets/', | |
| description: 'Liste tous les datasets publics disponibles.', | |
| params: [ | |
| { name: 'domain', type: 'string', desc: 'Filtrer par domaine (agriculture, health, etc.)' }, | |
| { name: 'search', type: 'string', desc: 'Recherche textuelle dans le titre ou la description' } | |
| ] | |
| }, | |
| { | |
| method: 'GET', | |
| path: '/api/datasets/{slug}/data/', | |
| description: 'Récupère les points de données pour un dataset spécifique.', | |
| params: [ | |
| { name: 'country', type: 'string', desc: 'Code pays ISO (ex: BJ, NG)' }, | |
| { name: 'limit', type: 'number', desc: 'Nombre maximum de résultats (défaut: 100)' } | |
| ] | |
| }, | |
| { | |
| method: 'GET', | |
| path: '/api/analytics/comprehensive/', | |
| description: 'Récupère des analyses consolidées et des statistiques.', | |
| params: [ | |
| { name: 'dataset_id', type: 'string', desc: 'ID ou Slug du dataset pour filtrer l\'analyse' } | |
| ] | |
| } | |
| ] | |
| const codeExamples = { | |
| python: `import requests | |
| url = "${API_URL}datasets/" | |
| headers = { | |
| "Authorization": "Token ${token || 'VOTRE_TOKEN_ICI'}" | |
| } | |
| response = requests.get(url, headers=headers) | |
| data = response.json() | |
| print(f"Nombre de datasets: {len(data['results'])}")`, | |
| javascript: `const fetchDatasets = async () => { | |
| const response = await fetch('${API_URL}datasets/', { | |
| headers: { | |
| 'Authorization': 'Token ${token || 'VOTRE_TOKEN_ICI'}' | |
| } | |
| }); | |
| const data = await response.json(); | |
| console.log(data); | |
| };`, | |
| curl: `curl -X GET ${API_URL}datasets/ \\ | |
| -H "Authorization: Token ${token || 'VOTRE_TOKEN_ICI'}"` | |
| } | |
| return ( | |
| <div className="space-y-10 pb-20"> | |
| {/* En-tête */} | |
| <motion.div | |
| initial={{ opacity: 0, y: -20 }} | |
| animate={{ opacity: 1, y: 0 }} | |
| className="flex flex-col md:flex-row md:items-end md:justify-between gap-6" | |
| > | |
| <div> | |
| <h1 className="text-4xl font-black text-foreground tracking-tight">API <span className="text-gradient">Developer Hub</span></h1> | |
| <p className="text-muted-foreground font-medium mt-2 text-lg"> | |
| Intégrez les données d'AfriDataHub directement dans vos applications et flux de travail. | |
| </p> | |
| </div> | |
| <div className="flex items-center space-x-3"> | |
| <Button variant="outline" className="rounded-2xl border-primary/20 hover:bg-primary/5 font-bold"> | |
| <BookOpen className="h-4 w-4 mr-2" /> | |
| Documentation Complète | |
| </Button> | |
| </div> | |
| </motion.div> | |
| {/* Section Token */} | |
| <motion.div | |
| initial={{ opacity: 0, scale: 0.95 }} | |
| animate={{ opacity: 1, scale: 1 }} | |
| className="glass rounded-[3rem] p-10 relative overflow-hidden" | |
| > | |
| <div className="absolute top-0 right-0 p-8 opacity-5 pointer-events-none"> | |
| <Lock className="w-48 h-48 text-primary" /> | |
| </div> | |
| <div className="relative z-10"> | |
| <div className="flex items-center mb-8"> | |
| <div className="w-12 h-12 rounded-2xl bg-primary/10 flex items-center justify-center mr-4"> | |
| <Cpu className="h-6 w-6 text-primary" /> | |
| </div> | |
| <div> | |
| <h2 className="text-2xl font-black text-foreground">Votre Clé API</h2> | |
| <p className="text-muted-foreground font-medium">Utilisez ce token pour authentifier vos requêtes.</p> | |
| </div> | |
| </div> | |
| <div className="flex flex-col md:flex-row items-center gap-4"> | |
| <div className="flex-1 w-full relative group"> | |
| <div className="absolute inset-y-0 left-4 flex items-center pointer-events-none"> | |
| <Terminal className="h-5 w-5 text-muted-foreground" /> | |
| </div> | |
| <input | |
| type="text" | |
| readOnly | |
| value={token || 'Chargement...'} | |
| className="w-full pl-12 pr-12 py-5 bg-background/50 border border-border rounded-2xl font-mono text-sm focus:ring-4 focus:ring-primary/10 outline-none transition-all" | |
| /> | |
| <button | |
| onClick={() => copyToClipboard(token)} | |
| className="absolute inset-y-0 right-4 flex items-center text-muted-foreground hover:text-primary transition-colors" | |
| > | |
| {copied ? <Check className="h-5 w-5 text-green-500" /> : <Copy className="h-5 w-5" />} | |
| </button> | |
| </div> | |
| <Button | |
| onClick={regenerateToken} | |
| disabled={loading} | |
| className="w-full md:w-auto px-8 py-7 rounded-2xl bg-primary text-white font-bold shadow-lg hover:shadow-primary/20 transition-all hover:scale-105" | |
| > | |
| <RefreshCw className={`h-5 w-5 mr-2 ${loading ? 'animate-spin' : ''}`} /> | |
| Régénérer le Token | |
| </Button> | |
| </div> | |
| <div className="mt-6 p-4 bg-yellow-500/10 dark:bg-yellow-500/5 border border-yellow-500/20 dark:border-yellow-500/10 rounded-2xl flex items-start"> | |
| <div className="p-2 bg-yellow-500/20 rounded-lg mr-4 mt-1"> | |
| <Lock className="h-4 w-4 text-yellow-500" /> | |
| </div> | |
| <p className="text-sm text-yellow-600 dark:text-yellow-400 font-medium"> | |
| Gardez votre token secret. Ne le partagez jamais dans des environnements publics ou côté client (frontend). | |
| </p> | |
| </div> | |
| </div> | |
| </motion.div> | |
| <div className="grid grid-cols-1 lg:grid-cols-2 gap-10"> | |
| {/* Documentation des Endpoints */} | |
| <motion.div | |
| initial={{ opacity: 0, x: -20 }} | |
| animate={{ opacity: 1, x: 0 }} | |
| className="space-y-6" | |
| > | |
| <div className="flex items-center mb-4"> | |
| <Globe className="h-6 w-6 text-primary mr-3" /> | |
| <h2 className="text-2xl font-black text-foreground">Endpoints Publics</h2> | |
| </div> | |
| {endpoints.map((ep, i) => ( | |
| <div key={i} className="glass rounded-3xl p-6 border border-border hover:border-primary/30 transition-all group"> | |
| <div className="flex items-center justify-between mb-4"> | |
| <div className="flex items-center space-x-3"> | |
| <span className="px-3 py-1 bg-primary text-white text-xs font-black rounded-lg uppercase tracking-widest"> | |
| {ep.method} | |
| </span> | |
| <code className="text-sm font-bold text-foreground/80">{ep.path}</code> | |
| </div> | |
| <ExternalLink className="h-4 w-4 text-muted-foreground group-hover:text-primary transition-colors" /> | |
| </div> | |
| <p className="text-muted-foreground text-sm font-medium mb-4">{ep.description}</p> | |
| <div className="space-y-2"> | |
| <p className="text-[10px] font-black uppercase tracking-widest text-muted-foreground/60">Paramètres</p> | |
| {ep.params.map((param, pi) => ( | |
| <div key={pi} className="flex items-start text-xs"> | |
| <span className="font-bold text-primary mr-2">{param.name}</span> | |
| <span className="text-muted-foreground/60 mr-2">({param.type})</span> | |
| <span className="text-foreground/70">{param.desc}</span> | |
| </div> | |
| ))} | |
| </div> | |
| </div> | |
| ))} | |
| </motion.div> | |
| {/* Exemples de Code */} | |
| <motion.div | |
| initial={{ opacity: 0, x: 20 }} | |
| animate={{ opacity: 1, x: 0 }} | |
| className="space-y-6" | |
| > | |
| <div className="flex items-center mb-4"> | |
| <Code className="h-6 w-6 text-secondary mr-3" /> | |
| <h2 className="text-2xl font-black text-foreground">Exemples d'Intégration</h2> | |
| </div> | |
| <div className="glass rounded-[2rem] md:rounded-[3rem] p-6 md:p-10 border border-border relative overflow-hidden shadow-2xl"> | |
| <div className="flex border-b border-border bg-muted/30"> | |
| {['python', 'javascript', 'curl'].map((tab) => ( | |
| <button | |
| key={tab} | |
| onClick={() => setActiveTab(tab)} | |
| className={`px-8 py-4 text-xs font-black uppercase tracking-widest transition-all ${activeTab === tab | |
| ? 'bg-primary text-white' | |
| : 'text-muted-foreground hover:bg-white/10 dark:hover:bg-black/20' | |
| }`} | |
| > | |
| {tab} | |
| </button> | |
| ))} | |
| </div> | |
| <div className="p-8 bg-black/40 relative group"> | |
| <button | |
| onClick={() => copyToClipboard(codeExamples[activeTab])} | |
| className="absolute top-6 right-6 p-2 bg-white/10 hover:bg-white/20 rounded-xl text-white transition-all opacity-0 group-hover:opacity-100" | |
| > | |
| {copied ? <Check className="h-4 w-4" /> : <Copy className="h-4 w-4" />} | |
| </button> | |
| <pre className="font-mono text-sm text-blue-100 overflow-x-auto"> | |
| <code>{codeExamples[activeTab]}</code> | |
| </pre> | |
| </div> | |
| <div className="p-8 bg-muted/20 border-t border-border"> | |
| <div className="flex items-center justify-between"> | |
| <div className="flex items-center"> | |
| <div className="w-10 h-10 rounded-xl bg-secondary/10 flex items-center justify-center mr-4"> | |
| <Database className="h-5 w-5 text-secondary" /> | |
| </div> | |
| <div> | |
| <p className="text-sm font-bold text-foreground">SDK AfriDataHub</p> | |
| <p className="text-xs text-muted-foreground">Bientôt disponible sur PyPI et NPM.</p> | |
| </div> | |
| </div> | |
| <Button variant="ghost" className="text-xs font-bold text-secondary hover:bg-secondary/10"> | |
| S'inscrire à la Beta | |
| </Button> | |
| </div> | |
| </div> | |
| </div> | |
| </motion.div> | |
| </div> | |
| </div> | |
| ) | |
| } | |
| export default APIDocumentation | |