diff --git a/src/hooks/useImages.ts b/src/hooks/useImages.ts index c22f685..689b0b8 100644 --- a/src/hooks/useImages.ts +++ b/src/hooks/useImages.ts @@ -249,8 +249,13 @@ export function useImages() { [items, update], ) - const uploadAll = useCallback(() => { - items.filter((i) => i.status === 'ready').forEach((i) => uploadOne(i.id)) + const uploadAll = useCallback(async () => { + const ready = items.filter((i) => i.status === 'ready') + for (const item of ready) { + await uploadOne(item.id) + // Pause de 500ms entre chaque upload pour éviter le rate limit Shopify + await new Promise(r => setTimeout(r, 500)) + } }, [items, uploadOne]) const removeItem = useCallback((id: string) => { diff --git a/src/lib/shopify.ts b/src/lib/shopify.ts index 10dad03..23cbed7 100644 --- a/src/lib/shopify.ts +++ b/src/lib/shopify.ts @@ -118,20 +118,41 @@ export async function uploadProductImage( const existingUrl = await productImageExists(shopifyProductId, filename) if (existingUrl) return { url: existingUrl, skipped: true } - const res = await fetch(`${baseUrl}/products/${shopifyProductId}/images.json`, { - method: 'POST', - headers, - body: JSON.stringify({ image: { attachment: imageBase64, filename } }), - }) + // Retry avec backoff exponentiel (gère le 429 rate limit) + const MAX_RETRIES = 4 + let delay = 1000 - if (!res.ok) { - const body = await res.text() - throw new Error(`Shopify upload error: ${res.status} — ${body}`) + for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) { + const res = await fetch(`${baseUrl}/products/${shopifyProductId}/images.json`, { + method: 'POST', + headers, + body: JSON.stringify({ image: { attachment: imageBase64, filename } }), + }) + + // 409 Conflict = image déjà présente → traiter comme skipped + if (res.status === 409) { + return { url: `https://placeholder/already-exists/${filename}`, skipped: true } + } + + // 429 Too Many Requests → attendre et réessayer + if (res.status === 429 && attempt < MAX_RETRIES) { + const retryAfter = parseInt(res.headers.get('Retry-After') ?? '0', 10) + await new Promise(r => setTimeout(r, retryAfter > 0 ? retryAfter * 1000 : delay)) + delay *= 2 + continue + } + + if (!res.ok) { + const body = await res.text() + throw new Error(`Shopify upload error: ${res.status} — ${body}`) + } + + const data = await res.json() + if (!data.image?.src) { + throw new Error('Shopify response invalide : champ image.src manquant') + } + return { url: data.image.src as string } } - const data = await res.json() - if (!data.image?.src) { - throw new Error('Shopify response invalide : champ image.src manquant') - } - return { url: data.image.src as string } + throw new Error('Shopify upload error: trop de tentatives (rate limit persistant)') }