Fix: Connexion des pages de détails aux APIs backend avec authentification cookies

- 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 <noreply@anthropic.com>
This commit is contained in:
DahoudG
2025-10-31 12:04:35 +00:00
parent a91a34dbf8
commit be04ef16d9
5 changed files with 32 additions and 55 deletions

View File

@@ -9,6 +9,7 @@ import { Column } from 'primereact/column';
import { ProgressBar } from 'primereact/progressbar'; import { ProgressBar } from 'primereact/progressbar';
import { Chart } from 'primereact/chart'; import { Chart } from 'primereact/chart';
import { Tag } from 'primereact/tag'; import { Tag } from 'primereact/tag';
import { apiService } from '@/services/api';
interface BudgetChantier { interface BudgetChantier {
id: number; id: number;
@@ -45,19 +46,11 @@ export default function ChantierBudgetPage() {
const loadBudget = async () => { const loadBudget = async () => {
try { try {
setLoading(true); setLoading(true);
const API_URL = process.env.NEXT_PUBLIC_API_URL || 'https://api.lions.dev/btpxpress'; const data = await apiService.budgets.getByChantier(Number(id));
// 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();
setBudget(data); setBudget(data);
} catch (error) { } 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 { } finally {
setLoading(false); setLoading(false);
} }

View File

@@ -9,6 +9,7 @@ import { Tag } from 'primereact/tag';
import { ProgressBar } from 'primereact/progressbar'; import { ProgressBar } from 'primereact/progressbar';
import { Divider } from 'primereact/divider'; import { Divider } from 'primereact/divider';
import { Skeleton } from 'primereact/skeleton'; import { Skeleton } from 'primereact/skeleton';
import { apiService } from '@/services/api';
interface Chantier { interface Chantier {
id: number; id: number;
@@ -54,18 +55,12 @@ export default function ChantierDetailsPage() {
const loadChantier = async () => { const loadChantier = async () => {
try { try {
setLoading(true); setLoading(true);
const API_URL = process.env.NEXT_PUBLIC_API_URL || 'https://api.lions.dev/btpxpress'; const data = await apiService.chantiers.getById(Number(id));
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();
setChantier(data); setChantier(data);
} catch (error) { } catch (error) {
console.error('Erreur:', error); console.error('Erreur lors du chargement du chantier:', error);
// TODO: Afficher un toast d'erreur // L'intercepteur API gérera automatiquement la redirection si 401
// TODO: Afficher un toast d'erreur pour les autres erreurs
} finally { } finally {
setLoading(false); setLoading(false);
} }

View File

@@ -10,6 +10,7 @@ import { Divider } from 'primereact/divider';
import { DataTable } from 'primereact/datatable'; import { DataTable } from 'primereact/datatable';
import { Column } from 'primereact/column'; import { Column } from 'primereact/column';
import { Tag } from 'primereact/tag'; import { Tag } from 'primereact/tag';
import { apiService } from '@/services/api';
interface Client { interface Client {
id: number; id: number;
@@ -58,15 +59,11 @@ export default function ClientDetailsPage() {
const loadClient = async () => { const loadClient = async () => {
try { try {
setLoading(true); setLoading(true);
const API_URL = process.env.NEXT_PUBLIC_API_URL || 'https://api.lions.dev/btpxpress'; const data = await apiService.clients.getById(Number(id));
const response = await fetch(`${API_URL}/api/v1/clients/${id}`); setClient(data);
if (response.ok) {
const data = await response.json();
setClient(data);
}
} catch (error) { } 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 { } finally {
setLoading(false); setLoading(false);
} }

View File

@@ -10,6 +10,7 @@ import { Calendar } from 'primereact/calendar';
import { DataTable } from 'primereact/datatable'; import { DataTable } from 'primereact/datatable';
import { Column } from 'primereact/column'; import { Column } from 'primereact/column';
import { Timeline } from 'primereact/timeline'; import { Timeline } from 'primereact/timeline';
import { apiService } from '@/services/api';
interface Materiel { interface Materiel {
id: number; id: number;
@@ -60,15 +61,11 @@ export default function MaterielDetailsPage() {
const loadMateriel = async () => { const loadMateriel = async () => {
try { try {
setLoading(true); setLoading(true);
const API_URL = process.env.NEXT_PUBLIC_API_URL || 'https://api.lions.dev/btpxpress'; const data = await apiService.materiels.getById(Number(id));
const response = await fetch(`${API_URL}/api/v1/materiels/${id}`); setMateriel(data);
if (response.ok) {
const data = await response.json();
setMateriel(data);
}
} catch (error) { } 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 { } finally {
setLoading(false); setLoading(false);
} }

View File

@@ -39,24 +39,21 @@ class ApiService {
headers: API_CONFIG.headers, headers: API_CONFIG.headers,
}); });
// Interceptor pour les requêtes - ajouter le token d'authentification // Interceptor pour les requêtes
this.api.interceptors.request.use( this.api.interceptors.request.use(
async (config) => { async (config) => {
// Utiliser le token stocké dans localStorage (nouveau système) // Les tokens sont dans des cookies HttpOnly, automatiquement envoyés par le navigateur
if (typeof window !== 'undefined') { // Pas besoin de les ajouter manuellement dans les headers
const accessToken = localStorage.getItem('accessToken'); // Le header Authorization sera ajouté par le serveur en lisant les cookies
if (accessToken) { console.log('🔐 API Request:', config.url);
config.headers['Authorization'] = `Bearer ${accessToken}`;
console.log('🔐 API Request avec token:', config.url);
} else {
console.log('⚠️ API Request sans token:', config.url);
}
}
// Ajouter des en-têtes par défaut // Ajouter des en-têtes par défaut
config.headers['X-Requested-With'] = 'XMLHttpRequest'; config.headers['X-Requested-With'] = 'XMLHttpRequest';
// Assurer que les cookies sont envoyés avec les requêtes CORS
config.withCredentials = true;
return config; return config;
}, },
(error) => { (error) => {
@@ -100,21 +97,19 @@ class ApiService {
this.notifyServerStatus(true); this.notifyServerStatus(true);
if (error.response?.status === 401) { if (error.response?.status === 401) {
// Gestion des erreurs d'authentification // Gestion des erreurs d'authentification (token expiré ou absent)
if (typeof window !== 'undefined') { if (typeof window !== 'undefined') {
const currentUrl = window.location.href; const currentUrl = window.location.href;
const hasAuthCode = currentUrl.includes('code=') && currentUrl.includes('/dashboard'); const hasAuthCode = currentUrl.includes('code=') && currentUrl.includes('/dashboard');
if (!hasAuthCode) { 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 // Sauvegarder la page actuelle pour y revenir après reconnexion
const currentPath = window.location.pathname + window.location.search; const currentPath = window.location.pathname + window.location.search;
localStorage.setItem('returnUrl', currentPath); sessionStorage.setItem('returnUrl', currentPath);
// Nettoyer les tokens expirés // Les cookies HttpOnly seront automatiquement nettoyés par l'expiration
localStorage.removeItem('accessToken'); // ou lors de la reconnexion. Pas besoin de manipulation côté client.
localStorage.removeItem('refreshToken');
localStorage.removeItem('idToken');
// Rediriger vers la page de connexion // Rediriger vers la page de connexion
window.location.href = '/api/auth/login'; window.location.href = '/api/auth/login';