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:
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|||||||
Reference in New Issue
Block a user