diff --git a/src/app/(dashboard)/creation/page.tsx b/src/app/(dashboard)/creation/page.tsx index d0d528d..5d5bb9c 100644 --- a/src/app/(dashboard)/creation/page.tsx +++ b/src/app/(dashboard)/creation/page.tsx @@ -1,5 +1,5 @@ 'use client' -import { useState, useCallback, useEffect } from 'react' +import { useState, useCallback, useEffect, useRef } from 'react' import { Header } from '@/components/layout/Header' import { PendingRowsTable } from '@/components/creation/PendingRowsTable' import { MetafieldResolver } from '@/components/creation/MetafieldResolver' @@ -24,6 +24,8 @@ export default function CreationPage() { const [resolutions, setResolutions] = useState([]) const [events, setEvents] = useState([]) const [done, setDone] = useState(false) + const [stopped, setStopped] = useState(false) + const abortRef = useRef(null) // Collection const [collections, setCollections] = useState([]) @@ -83,32 +85,48 @@ export default function CreationPage() { const handleResolved = useCallback((r: MetafieldResolution[]) => { setResolutions(r) }, []) + const stop = useCallback(() => { + abortRef.current?.abort() + setStopped(true) + setDone(true) + }, []) + const execute = async () => { + const controller = new AbortController() + abortRef.current = controller setStep('execution') setEvents([]) setDone(false) - const res = await fetch('/api/creation/execute', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ resolutions, tab: selectedTab, collectionOverride: collectionOverride || undefined }), - }) - if (!res.body) return - const reader = res.body.getReader() - const decoder = new TextDecoder() - let buffer = '' - while (true) { - const { done: readerDone, value } = await reader.read() - if (readerDone) break - buffer += decoder.decode(value, { stream: true }) - const lines = buffer.split('\n') - buffer = lines.pop() ?? '' - for (const line of lines) { - if (!line.trim()) continue - try { - const event = JSON.parse(line) as CreationStreamEvent - setEvents(prev => [...prev, event]) - if (event.type === 'done') setDone(true) - } catch { /* ligne incomplète */ } + setStopped(false) + try { + const res = await fetch('/api/creation/execute', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ resolutions, tab: selectedTab, collectionOverride: collectionOverride || undefined }), + signal: controller.signal, + }) + if (!res.body) return + const reader = res.body.getReader() + const decoder = new TextDecoder() + let buffer = '' + while (true) { + const { done: readerDone, value } = await reader.read() + if (readerDone) break + buffer += decoder.decode(value, { stream: true }) + const lines = buffer.split('\n') + buffer = lines.pop() ?? '' + for (const line of lines) { + if (!line.trim()) continue + try { + const event = JSON.parse(line) as CreationStreamEvent + setEvents(prev => [...prev, event]) + if (event.type === 'done') setDone(true) + } catch { /* ligne incomplète */ } + } + } + } catch (e) { + if ((e as Error).name !== 'AbortError') { + setEvents(prev => [...prev, { type: 'error', title: '', message: (e as Error).message }]) } } setDone(true) @@ -140,10 +158,13 @@ export default function CreationPage() { } const reset = () => { + abortRef.current?.abort() + abortRef.current = null setStep('famille') setSelectedTab(null) setEvents([]) setDone(false) + setStopped(false) setByTab({}) setTotal(0) setDetectedCollectionId('') @@ -305,12 +326,23 @@ export default function CreationPage() { {step === 'execution' && (
- {done && ( - - )} +
+ {!done && ( + + )} + {done && ( + <> + {stopped &&

⚠ Création interrompue — les produits déjà créés sont conservés dans Shopify.

} + + + )} +
)}