Le hook useApi simplifie les appels API avec gestion automatique de l'authentification JWT, du rafraîchissement de token et de la gestion des erreurs.
L'API est configurée dans appConfig.js :
// src/appConfig.js export const config = { api: { prefixUrl: import.meta.env.VITE_API_URL, timeout: 30000, debug: import.meta.env.DEV, paths: { login: "login", logout: "logout", refresh: "refresh" } } };
import { useApi } from '@cap-rel/smartcommon'; const api = useApi();
| Méthode | Description |
|---|---|
| api.login(body) | Connexion utilisateur |
| api.logout() | Déconnexion |
| api.public | Instance pour requêtes publiques |
| api.private | Instance pour requêtes authentifiées |
import { useApi, useNavigation } from '@cap-rel/smartcommon'; function LoginPage() { const api = useApi(); const navigate = useNavigation(); const handleLogin = async (email, password) => { try { const user = await api.login({ login: email, password: password, rememberMe: true }); console.log('Connecté :', user); navigate('/'); } catch (error) { console.error('Erreur de connexion:', error); alert('Identifiants incorrects'); } }; return ( <form onSubmit={(e) => { e.preventDefault(); const fd = new FormData(e.target); handleLogin(fd.get('email'), fd.get('password')); }}> <input name="email" type="email" placeholder="Email" /> <input name="password" type="password" placeholder="Mot de passe" /> <button type="submit">Connexion</button> </form> ); }
function LogoutButton() { const api = useApi(); const navigate = useNavigation(); const handleLogout = async () => { await api.logout(); navigate('/login'); }; return ( <button onClick={handleLogout}>Déconnexion</button> ); }
Pour les endpoints sans authentification :
const api = useApi(); // GET const info = await api.public.get('public/info').json(); // POST const result = await api.public.post('public/contact', { json: { name: 'Jean', message: 'Bonjour' } }).json();
Pour les endpoints protégés par JWT :
const api = useApi(); // GET - Liste const items = await api.private.get('items').json(); // GET - Détail const item = await api.private.get(`items/${id}`).json(); // POST - Création const newItem = await api.private.post('items', { json: { label: 'Nouveau produit', price: 29.99 } }).json(); // PUT - Modification const updated = await api.private.put(`items/${id}`, { json: { label: 'Produit modifié' } }).json(); // DELETE - Suppression await api.private.delete(`items/${id}`);
import { useEffect } from 'react'; import { Page, Block, List, ListItem, Button, Spinner } from '@cap-rel/smartcommon'; import { useApi, useStates, useNavigation } from '@cap-rel/smartcommon'; export const ProductsPage = () => { const api = useApi(); const navigate = useNavigation(); const st = useStates({ initialStates: { products: [], loading: true, error: null } }); // Charger les produits useEffect(() => { loadProducts(); }, []); const loadProducts = async () => { st.set('loading', true); st.set('error', null); try { const data = await api.private.get('products').json(); st.set('products', data.products); } catch (err) { st.set('error', err.message); } finally { st.set('loading', false); } }; // Supprimer un produit const deleteProduct = async (id) => { if (!confirm('Supprimer ce produit ?')) return; try { await api.private.delete(`products/${id}`); // Retirer de la liste locale st.set('products', st.get('products').filter(p => p.id !== id)); } catch (err) { alert('Erreur : ' + err.message); } }; if (st.get('loading')) { return <Page title="Produits"><Spinner /></Page>; } if (st.get('error')) { return ( <Page title="Produits"> <Block> <p>Erreur : {st.get('error')}</p> <Button onClick={loadProducts}>Réessayer</Button> </Block> </Page> ); } return ( <Page title="Produits" onRefresh={loadProducts}> <Block> <Button onClick={() => navigate('/products/new')}> Nouveau produit </Button> </Block> <Block> <List> {st.get('products').map(product => ( <ListItem key={product.id} title={product.label} subtitle={`${product.price} €`} onClick={() => navigate(`/products/${product.id}`)} actions={ <Button size="sm" variant="danger" onClick={(e) => { e.stopPropagation(); deleteProduct(product.id); }} > Supprimer </Button> } /> ))} </List> </Block> </Page> ); };
const api = useApi(); const createProduct = async (data) => { try { const result = await api.private.post('products', { json: data }).json(); return { success: true, data: result }; } catch (error) { // Erreur HTTP if (error.response) { const status = error.response.status; const body = await error.response.json().catch(() => ({})); if (status === 400) { return { success: false, error: 'Données invalides', details: body }; } if (status === 401) { return { success: false, error: 'Session expirée' }; } if (status === 403) { return { success: false, error: 'Accès interdit' }; } if (status === 404) { return { success: false, error: 'Ressource non trouvée' }; } if (status >= 500) { return { success: false, error: 'Erreur serveur' }; } } // Erreur réseau return { success: false, error: 'Erreur de connexion' }; } };
api.privateChaque requête inclut :
Authorization: Bearer <accessToken> (requêtes private)X-DEVICEID: <uuid> (identification de l'appareil)Content-Type: application/jsonProtection contre les requêtes en cascade :
// GET /products?category=electronics&limit=10 const products = await api.private.get('products', { searchParams: { category: 'electronics', limit: 10 } }).json();
const data = await api.private.get('items', { headers: { 'X-Custom-Header': 'value' } }).json();
const data = await api.private.get('slow-endpoint', { timeout: 60000 // 60 secondes }).json();