From be04ef16d9b20217fa64c0406d7452a491345008 Mon Sep 17 00:00:00 2001 From: DahoudG Date: Fri, 31 Oct 2025 12:04:35 +0000 Subject: [PATCH] =?UTF-8?q?Fix:=20Connexion=20des=20pages=20de=20d=C3=A9ta?= =?UTF-8?q?ils=20aux=20APIs=20backend=20avec=20authentification=20cookies?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Mise à jour de services/api.ts pour supporter l'authentification par cookies HttpOnly * Ajout de withCredentials: true dans l'intercepteur de requêtes * Modification de l'intercepteur de réponse pour gérer les 401 sans localStorage * Utilisation de sessionStorage pour returnUrl au lieu de localStorage * Suppression des tentatives de nettoyage de tokens localStorage (gérés par cookies) - Connexion des pages de détails à apiService au lieu de fetch direct: * app/(main)/chantiers/[id]/page.tsx → apiService.chantiers.getById() * app/(main)/chantiers/[id]/budget/page.tsx → apiService.budgets.getByChantier() * app/(main)/clients/[id]/page.tsx → apiService.clients.getById() * app/(main)/materiels/[id]/page.tsx → apiService.materiels.getById() Avantages: - Gestion automatique de l'authentification via cookies HttpOnly (plus sécurisé) - Redirection automatique vers /api/auth/login en cas de 401 - Code plus propre et maintenable - Gestion d'erreurs cohérente dans toute l'application 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- app/(main)/chantiers/[id]/budget/page.tsx | 15 +++-------- app/(main)/chantiers/[id]/page.tsx | 15 ++++------- app/(main)/clients/[id]/page.tsx | 13 ++++------ app/(main)/materiels/[id]/page.tsx | 13 ++++------ services/api.ts | 31 ++++++++++------------- 5 files changed, 32 insertions(+), 55 deletions(-) diff --git a/app/(main)/chantiers/[id]/budget/page.tsx b/app/(main)/chantiers/[id]/budget/page.tsx index 8993b84..cb47353 100644 --- a/app/(main)/chantiers/[id]/budget/page.tsx +++ b/app/(main)/chantiers/[id]/budget/page.tsx @@ -9,6 +9,7 @@ import { Column } from 'primereact/column'; import { ProgressBar } from 'primereact/progressbar'; import { Chart } from 'primereact/chart'; import { Tag } from 'primereact/tag'; +import { apiService } from '@/services/api'; interface BudgetChantier { id: number; @@ -45,19 +46,11 @@ export default function ChantierBudgetPage() { const loadBudget = async () => { try { setLoading(true); - const API_URL = process.env.NEXT_PUBLIC_API_URL || 'https://api.lions.dev/btpxpress'; - - // Charger le budget du chantier - const response = await fetch(`${API_URL}/api/v1/budgets/chantier/${id}`); - - if (!response.ok) { - throw new Error('Erreur lors du chargement du budget'); - } - - const data = await response.json(); + const data = await apiService.budgets.getByChantier(Number(id)); setBudget(data); } catch (error) { - console.error('Erreur:', error); + console.error('Erreur lors du chargement du budget:', error); + // L'intercepteur API gérera automatiquement la redirection si 401 } finally { setLoading(false); } diff --git a/app/(main)/chantiers/[id]/page.tsx b/app/(main)/chantiers/[id]/page.tsx index f38f29c..ab803db 100644 --- a/app/(main)/chantiers/[id]/page.tsx +++ b/app/(main)/chantiers/[id]/page.tsx @@ -9,6 +9,7 @@ import { Tag } from 'primereact/tag'; import { ProgressBar } from 'primereact/progressbar'; import { Divider } from 'primereact/divider'; import { Skeleton } from 'primereact/skeleton'; +import { apiService } from '@/services/api'; interface Chantier { id: number; @@ -54,18 +55,12 @@ export default function ChantierDetailsPage() { const loadChantier = async () => { try { setLoading(true); - const API_URL = process.env.NEXT_PUBLIC_API_URL || 'https://api.lions.dev/btpxpress'; - const response = await fetch(`${API_URL}/api/v1/chantiers/${id}`); - - if (!response.ok) { - throw new Error('Erreur lors du chargement du chantier'); - } - - const data = await response.json(); + const data = await apiService.chantiers.getById(Number(id)); setChantier(data); } catch (error) { - console.error('Erreur:', error); - // TODO: Afficher un toast d'erreur + console.error('Erreur lors du chargement du chantier:', error); + // L'intercepteur API gérera automatiquement la redirection si 401 + // TODO: Afficher un toast d'erreur pour les autres erreurs } finally { setLoading(false); } diff --git a/app/(main)/clients/[id]/page.tsx b/app/(main)/clients/[id]/page.tsx index 58e9359..b7ad2bc 100644 --- a/app/(main)/clients/[id]/page.tsx +++ b/app/(main)/clients/[id]/page.tsx @@ -10,6 +10,7 @@ import { Divider } from 'primereact/divider'; import { DataTable } from 'primereact/datatable'; import { Column } from 'primereact/column'; import { Tag } from 'primereact/tag'; +import { apiService } from '@/services/api'; interface Client { id: number; @@ -58,15 +59,11 @@ export default function ClientDetailsPage() { const loadClient = async () => { try { setLoading(true); - const API_URL = process.env.NEXT_PUBLIC_API_URL || 'https://api.lions.dev/btpxpress'; - const response = await fetch(`${API_URL}/api/v1/clients/${id}`); - - if (response.ok) { - const data = await response.json(); - setClient(data); - } + const data = await apiService.clients.getById(Number(id)); + setClient(data); } catch (error) { - console.error('Erreur:', error); + console.error('Erreur lors du chargement du client:', error); + // L'intercepteur API gérera automatiquement la redirection si 401 } finally { setLoading(false); } diff --git a/app/(main)/materiels/[id]/page.tsx b/app/(main)/materiels/[id]/page.tsx index 7682439..b704864 100644 --- a/app/(main)/materiels/[id]/page.tsx +++ b/app/(main)/materiels/[id]/page.tsx @@ -10,6 +10,7 @@ import { Calendar } from 'primereact/calendar'; import { DataTable } from 'primereact/datatable'; import { Column } from 'primereact/column'; import { Timeline } from 'primereact/timeline'; +import { apiService } from '@/services/api'; interface Materiel { id: number; @@ -60,15 +61,11 @@ export default function MaterielDetailsPage() { const loadMateriel = async () => { try { setLoading(true); - const API_URL = process.env.NEXT_PUBLIC_API_URL || 'https://api.lions.dev/btpxpress'; - const response = await fetch(`${API_URL}/api/v1/materiels/${id}`); - - if (response.ok) { - const data = await response.json(); - setMateriel(data); - } + const data = await apiService.materiels.getById(Number(id)); + setMateriel(data); } catch (error) { - console.error('Erreur:', error); + console.error('Erreur lors du chargement du matériel:', error); + // L'intercepteur API gérera automatiquement la redirection si 401 } finally { setLoading(false); } diff --git a/services/api.ts b/services/api.ts index b234f69..2070ef2 100644 --- a/services/api.ts +++ b/services/api.ts @@ -39,24 +39,21 @@ class ApiService { headers: API_CONFIG.headers, }); - // Interceptor pour les requêtes - ajouter le token d'authentification + // Interceptor pour les requêtes this.api.interceptors.request.use( async (config) => { - // Utiliser le token stocké dans localStorage (nouveau système) - if (typeof window !== 'undefined') { - const accessToken = localStorage.getItem('accessToken'); + // Les tokens sont dans des cookies HttpOnly, automatiquement envoyés par le navigateur + // Pas besoin de les ajouter manuellement dans les headers + // Le header Authorization sera ajouté par le serveur en lisant les cookies - if (accessToken) { - config.headers['Authorization'] = `Bearer ${accessToken}`; - console.log('🔐 API Request avec token:', config.url); - } else { - console.log('⚠️ API Request sans token:', config.url); - } - } + console.log('🔐 API Request:', config.url); // Ajouter des en-têtes par défaut config.headers['X-Requested-With'] = 'XMLHttpRequest'; + // Assurer que les cookies sont envoyés avec les requêtes CORS + config.withCredentials = true; + return config; }, (error) => { @@ -100,21 +97,19 @@ class ApiService { this.notifyServerStatus(true); if (error.response?.status === 401) { - // Gestion des erreurs d'authentification + // Gestion des erreurs d'authentification (token expiré ou absent) if (typeof window !== 'undefined') { const currentUrl = window.location.href; const hasAuthCode = currentUrl.includes('code=') && currentUrl.includes('/dashboard'); if (!hasAuthCode) { - console.log('🔄 Token expiré, redirection vers la connexion...'); + console.log('🔄 Token expiré ou absent, redirection vers la connexion...'); // Sauvegarder la page actuelle pour y revenir après reconnexion const currentPath = window.location.pathname + window.location.search; - localStorage.setItem('returnUrl', currentPath); + sessionStorage.setItem('returnUrl', currentPath); - // Nettoyer les tokens expirés - localStorage.removeItem('accessToken'); - localStorage.removeItem('refreshToken'); - localStorage.removeItem('idToken'); + // Les cookies HttpOnly seront automatiquement nettoyés par l'expiration + // ou lors de la reconnexion. Pas besoin de manipulation côté client. // Rediriger vers la page de connexion window.location.href = '/api/auth/login';