error-propagation: implement retry for failed mutations (MarketplaceHome, RolesPage, SettingsPage)

This commit is contained in:
senke 2026-01-11 17:37:45 +01:00
parent f929e8ebd0
commit 6e16fce1b9
3 changed files with 116 additions and 16 deletions

View file

@ -1,4 +1,4 @@
import { useState, useEffect } from 'react';
import { useState, useEffect, useRef } from 'react';
import { getRoles, deleteRole, updateRole } from '../services/roleService';
import type { Role } from '../types/role';
import { Button } from '@/components/ui/button';
@ -29,6 +29,8 @@ export function RolesPage() {
const [queryError, setQueryError] = useState<Error | null>(null);
const [mutationError, setMutationError] = useState<Error | null>(null);
const [editingRole, setEditingRole] = useState<Role | null>(null);
const [retryCount, setRetryCount] = useState(0);
const lastMutationRef = useRef<(() => Promise<void>) | null>(null);
const [isEditModalOpen, setIsEditModalOpen] = useState(false);
const [isAssignModalOpen, setIsAssignModalOpen] = useState(false);
const [assignUserId, setAssignUserId] = useState<string>('');
@ -54,13 +56,23 @@ export function RolesPage() {
}, []);
const handleToggleActive = async (role: Role) => {
try {
setMutationError(null);
// Action 3.4.1.3: Store mutation for retry
const performMutation = async () => {
await updateRole(role.id, { is_active: !role.is_active });
toast.success(
`Role ${!role.is_active ? 'activated' : 'deactivated'} successfully`,
);
loadRoles();
setMutationError(null);
setRetryCount(0);
lastMutationRef.current = null;
};
lastMutationRef.current = performMutation;
setMutationError(null);
try {
await performMutation();
} catch (err) {
const errorMessage =
err instanceof Error ? err.message : 'Failed to update role';
@ -74,11 +86,21 @@ export function RolesPage() {
return;
}
try {
setMutationError(null);
// Action 3.4.1.3: Store mutation for retry
const performMutation = async () => {
await deleteRole(role.id);
toast.success('Role deleted successfully');
loadRoles();
setMutationError(null);
setRetryCount(0);
lastMutationRef.current = null;
};
lastMutationRef.current = performMutation;
setMutationError(null);
try {
await performMutation();
} catch (err) {
const errorMessage =
err instanceof Error ? err.message : 'Failed to delete role';
@ -86,6 +108,18 @@ export function RolesPage() {
}
};
// Action 3.4.1.3: Retry handler for failed mutations
const handleRetry = async () => {
if (!lastMutationRef.current || retryCount >= 3) return;
setRetryCount((prev) => prev + 1);
try {
await lastMutationRef.current();
} catch (error) {
// Error will be handled by the mutation function
}
};
const handleEdit = (role: Role) => {
setEditingRole(role);
setIsEditModalOpen(true);
@ -135,7 +169,12 @@ export function RolesPage() {
action: 'updating role',
resource: 'roles',
}}
onDismiss={() => setMutationError(null)}
onRetry={retryCount < 3 ? handleRetry : undefined}
onDismiss={() => {
setMutationError(null);
setRetryCount(0);
lastMutationRef.current = null;
}}
/>
)}
<div className="mb-6 flex items-center justify-between">

View file

@ -1,4 +1,4 @@
import { useState, useEffect } from 'react';
import { useState, useEffect, useRef } from 'react';
import { useAuthStore } from '@/features/auth/store/authStore';
import { getSettings, updateSettings } from '../services/settingsService';
import { UserSettings } from '../types/settings';
@ -20,6 +20,8 @@ export function SettingsPage() {
const [isSaving, setIsSaving] = useState(false);
const [queryError, setQueryError] = useState<Error | null>(null);
const [mutationError, setMutationError] = useState<Error | null>(null);
const [retryCount, setRetryCount] = useState(0);
const lastMutationRef = useRef<(() => Promise<void>) | null>(null);
const loadSettings = async () => {
if (!user?.id) {
@ -61,11 +63,21 @@ export function SettingsPage() {
return;
}
try {
setIsSaving(true);
setMutationError(null);
// Action 3.4.1.3: Store mutation for retry
const performMutation = async () => {
await updateSettings(user.id, settings);
toast.success('Paramètres sauvegardés avec succès');
setMutationError(null);
setRetryCount(0);
lastMutationRef.current = null;
};
lastMutationRef.current = performMutation;
setIsSaving(true);
setMutationError(null);
try {
await performMutation();
} catch (err) {
const errorMessage =
err instanceof Error ? err.message : 'Erreur lors de la sauvegarde';
@ -75,6 +87,21 @@ export function SettingsPage() {
}
};
// Action 3.4.1.3: Retry handler for failed mutations
const handleRetry = async () => {
if (!lastMutationRef.current || retryCount >= 3) return;
setRetryCount((prev) => prev + 1);
setIsSaving(true);
try {
await lastMutationRef.current();
} catch (error) {
// Error will be handled by the mutation function
} finally {
setIsSaving(false);
}
};
if (isLoading) {
return (
<div className="flex items-center justify-center min-h-[400px]">
@ -129,7 +156,12 @@ export function SettingsPage() {
action: 'saving settings',
resource: 'settings',
}}
onDismiss={() => setMutationError(null)}
onRetry={retryCount < 3 ? handleRetry : undefined}
onDismiss={() => {
setMutationError(null);
setRetryCount(0);
lastMutationRef.current = null;
}}
/>
)}
<div className="max-w-4xl mx-auto">

View file

@ -1,4 +1,4 @@
import { useState, useEffect } from 'react';
import { useState, useEffect, useRef } from 'react';
import { marketplaceService } from '@/services/marketplaceService';
import { ProductCard } from '@/features/marketplace/components/ProductCard';
import { Product, ProductType } from '@/types/marketplace';
@ -27,6 +27,8 @@ export function MarketplaceHome() {
const [queryError, setQueryError] = useState<Error | null>(null);
const [mutationError, setMutationError] = useState<Error | null>(null);
const [purchasingProductId, setPurchasingProductId] = useState<string | null>(null);
const [retryCount, setRetryCount] = useState(0);
const lastMutationRef = useRef<(() => Promise<void>) | null>(null);
const [isCartOpen, setIsCartOpen] = useState(false);
const [page, setPage] = useState(1);
const [limit] = useState(12);
@ -94,12 +96,22 @@ export function MarketplaceHome() {
};
// CRITIQUE FIX #70: Gestion d'erreur améliorée pour purchaseProduct
// Action 3.4.1.3: Store mutation for retry
const handlePurchase = async (product: Product) => {
try {
setPurchasingProductId(product.id);
setMutationError(null);
const performMutation = async () => {
await marketplaceService.purchaseProduct(product.id);
toast.success(`Successfully purchased ${product.title}`);
setMutationError(null);
setRetryCount(0);
lastMutationRef.current = null;
};
lastMutationRef.current = performMutation;
setPurchasingProductId(product.id);
setMutationError(null);
try {
await performMutation();
} catch (error: unknown) {
// CRITIQUE FIX #70: Gestion d'erreur améliorée avec message détaillé
const apiError = parseApiError(error);
@ -113,6 +125,18 @@ export function MarketplaceHome() {
}
};
// Action 3.4.1.3: Retry handler for failed mutations
const handleRetry = async () => {
if (!lastMutationRef.current || retryCount >= 3) return;
setRetryCount((prev) => prev + 1);
try {
await lastMutationRef.current();
} catch (error) {
// Error will be handled by the mutation function
}
};
const handleClearFilters = () => {
setSearchQuery('');
setProductType('');
@ -159,7 +183,12 @@ export function MarketplaceHome() {
action: 'purchasing product',
resource: 'marketplace',
}}
onDismiss={() => setMutationError(null)}
onRetry={retryCount < 3 ? handleRetry : undefined}
onDismiss={() => {
setMutationError(null);
setRetryCount(0);
lastMutationRef.current = null;
}}
/>
)}
<div className="mb-6 flex items-center justify-between">