AfriDataHubFrontend / src /components /APIDocumentation.jsx
rinogeek's picture
Update for deployment
62fe6d4
/**
* 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