[FE-COMP-005] fe-comp: Add toast notifications for all user actions

This commit is contained in:
senke 2025-12-25 11:32:53 +01:00
parent 4be1925173
commit d4f4e12fb3
3 changed files with 144 additions and 3 deletions

View file

@ -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",

View file

@ -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

View 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: '⚠️' });
}