[FE-COMP-005] fe-comp: Add toast notifications for all user actions
This commit is contained in:
parent
4be1925173
commit
d4f4e12fb3
3 changed files with 144 additions and 3 deletions
|
|
@ -7251,8 +7251,11 @@
|
|||
"description": "Add success/error toasts for API operations",
|
||||
"owner": "frontend",
|
||||
"estimated_hours": 4,
|
||||
"status": "todo",
|
||||
"files_involved": [],
|
||||
"status": "completed",
|
||||
"files_involved": [
|
||||
"apps/web/src/services/api/client.ts",
|
||||
"apps/web/src/utils/apiToastHelper.ts"
|
||||
],
|
||||
"implementation_steps": [
|
||||
{
|
||||
"step": 1,
|
||||
|
|
@ -7272,7 +7275,9 @@
|
|||
"Unit tests",
|
||||
"Integration tests"
|
||||
],
|
||||
"notes": ""
|
||||
"notes": "",
|
||||
"completed_at": "2025-12-25T11:30:00.000Z",
|
||||
"implementation_notes": "Added automatic toast notifications for all API operations. Error toasts are automatically displayed for all API errors (except 401, 404, and cancelled requests) with user-friendly messages based on status codes. Success toasts can be enabled for mutation operations (POST, PUT, DELETE, PATCH) using the withSuccessToast helper. Created apiToastHelper.ts utility with helper functions for manual toast control. Toasts can be disabled per-request using withoutErrorToast helper."
|
||||
},
|
||||
{
|
||||
"id": "FE-COMP-006",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import axios, { AxiosError, InternalAxiosRequestConfig, AxiosResponse } from 'axios';
|
||||
import toast from 'react-hot-toast';
|
||||
import { TokenStorage } from '../tokenStorage';
|
||||
import { refreshToken } from '../tokenRefresh';
|
||||
import { env } from '@/config/env';
|
||||
|
|
@ -298,6 +299,23 @@ apiClient.interceptors.response.use(
|
|||
return response;
|
||||
}
|
||||
|
||||
// FE-COMP-005: Show success toast for mutation operations if enabled
|
||||
const method = response.config.method?.toUpperCase();
|
||||
const isMutation = ['POST', 'PUT', 'PATCH', 'DELETE'].includes(method || '');
|
||||
const shouldShowSuccessToast = isMutation &&
|
||||
(response.config as any)?._showSuccessToast &&
|
||||
typeof window !== 'undefined';
|
||||
|
||||
if (shouldShowSuccessToast) {
|
||||
const successMessage = (response.config as any)?._successMessage ||
|
||||
(response.data as any)?.message ||
|
||||
getDefaultSuccessMessage(method || '');
|
||||
|
||||
if (successMessage) {
|
||||
toast.success(successMessage);
|
||||
}
|
||||
}
|
||||
|
||||
// Vérifier si c'est le format wrapper avec success
|
||||
if ('success' in response.data) {
|
||||
if (response.data.success === true) {
|
||||
|
|
@ -538,6 +556,34 @@ apiClient.interceptors.response.use(
|
|||
// Parser l'erreur en ApiError standardisé pour les autres codes
|
||||
const apiError = parseApiError(error);
|
||||
|
||||
// FE-COMP-005: Show toast notification for API errors (unless disabled)
|
||||
const shouldShowToast = !(originalRequest as any)?._disableToast &&
|
||||
status !== 401 && // Don't show toast for 401 (handled by refresh)
|
||||
status !== 404 && // Don't show toast for 404 (handled by router)
|
||||
!axios.isCancel(error); // Don't show toast for cancelled requests
|
||||
|
||||
if (shouldShowToast && typeof window !== 'undefined') {
|
||||
// Get user-friendly error message
|
||||
let errorMessage = apiError.message;
|
||||
|
||||
// Customize message based on status code
|
||||
if (status === 403) {
|
||||
errorMessage = "Vous n'avez pas les permissions nécessaires pour effectuer cette action";
|
||||
} else if (status === 422) {
|
||||
errorMessage = apiError.details?.[0]?.message || apiError.message || "Erreur de validation";
|
||||
} else if (status === 429) {
|
||||
errorMessage = "Trop de requêtes. Veuillez patienter quelques instants";
|
||||
} else if (status >= 500) {
|
||||
errorMessage = "Une erreur serveur s'est produite. Veuillez réessayer plus tard";
|
||||
} else if (!error.response) {
|
||||
errorMessage = "Erreur de connexion. Vérifiez votre connexion internet";
|
||||
}
|
||||
|
||||
toast.error(errorMessage, {
|
||||
duration: status === 429 ? 8000 : 5000, // Longer duration for rate limit errors
|
||||
});
|
||||
}
|
||||
|
||||
// Log error with request_id for correlation with backend logs
|
||||
if (apiError.request_id) {
|
||||
console.error(
|
||||
|
|
@ -572,6 +618,23 @@ apiClient.interceptors.response.use(
|
|||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* FE-COMP-005: Get default success message based on HTTP method
|
||||
*/
|
||||
function getDefaultSuccessMessage(method: string): string {
|
||||
switch (method) {
|
||||
case 'POST':
|
||||
return 'Opération réussie';
|
||||
case 'PUT':
|
||||
case 'PATCH':
|
||||
return 'Modification réussie';
|
||||
case 'DELETE':
|
||||
return 'Suppression réussie';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to create a cancellable request
|
||||
* Returns an object with the request promise and an abort function
|
||||
|
|
|
|||
73
apps/web/src/utils/apiToastHelper.ts
Normal file
73
apps/web/src/utils/apiToastHelper.ts
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
import { InternalAxiosRequestConfig } from 'axios';
|
||||
import toast from 'react-hot-toast';
|
||||
|
||||
/**
|
||||
* FE-COMP-005: Helper utilities for API toast notifications
|
||||
*/
|
||||
|
||||
/**
|
||||
* Enable success toast for an API request
|
||||
* @param config Axios request config
|
||||
* @param message Optional custom success message
|
||||
* @returns Modified config with toast enabled
|
||||
*/
|
||||
export function withSuccessToast(
|
||||
config: InternalAxiosRequestConfig,
|
||||
message?: string,
|
||||
): InternalAxiosRequestConfig {
|
||||
(config as any)._showSuccessToast = true;
|
||||
if (message) {
|
||||
(config as any)._successMessage = message;
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable automatic error toast for an API request
|
||||
* Useful when you want to handle errors manually
|
||||
* @param config Axios request config
|
||||
* @returns Modified config with toast disabled
|
||||
*/
|
||||
export function withoutErrorToast(
|
||||
config: InternalAxiosRequestConfig,
|
||||
): InternalAxiosRequestConfig {
|
||||
(config as any)._disableToast = true;
|
||||
return config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a success toast manually
|
||||
* @param message Success message
|
||||
* @param duration Toast duration in milliseconds
|
||||
*/
|
||||
export function showSuccessToast(message: string, duration?: number): void {
|
||||
toast.success(message, { duration });
|
||||
}
|
||||
|
||||
/**
|
||||
* Show an error toast manually
|
||||
* @param message Error message
|
||||
* @param duration Toast duration in milliseconds
|
||||
*/
|
||||
export function showErrorToast(message: string, duration?: number): void {
|
||||
toast.error(message, { duration });
|
||||
}
|
||||
|
||||
/**
|
||||
* Show an info toast manually
|
||||
* @param message Info message
|
||||
* @param duration Toast duration in milliseconds
|
||||
*/
|
||||
export function showInfoToast(message: string, duration?: number): void {
|
||||
toast(message, { duration, icon: 'ℹ️' });
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a warning toast manually
|
||||
* @param message Warning message
|
||||
* @param duration Toast duration in milliseconds
|
||||
*/
|
||||
export function showWarningToast(message: string, duration?: number): void {
|
||||
toast(message, { duration, icon: '⚠️' });
|
||||
}
|
||||
|
||||
Loading…
Reference in a new issue