diff --git a/app/routes/app._index.jsx b/app/routes/app._index.jsx index 0bda272..673c2fd 100644 --- a/app/routes/app._index.jsx +++ b/app/routes/app._index.jsx @@ -90,6 +90,25 @@ export const action = async ({ request }) => { const formData = await request.formData(); const backendApiUrl = getBackendApiUrl(); const shop = session.shop; + const _action = String(formData.get("_action") || "run"); + + // Cancel a running job + if (_action === "cancel") { + const jobId = String(formData.get("jobId") || "").trim(); + if (!jobId) return { ok: false, error: "Missing jobId." }; + try { + const response = await fetch( + `${backendApiUrl}/pipeline/cancel/${encodeURIComponent(jobId)}`, + { method: "POST" } + ); + const payload = await readJsonSafe(response); + return { ok: response.ok, cancelled: true, jobId, ...payload }; + } catch (error) { + return { ok: false, error: error.message }; + } + } + + // Start a new import job const limitValue = String(formData.get("limit") || "").trim(); const source = String(formData.get("source") || "kyt").trim() || "kyt"; const limit = limitValue ? Number(limitValue) : null; @@ -97,9 +116,7 @@ export const action = async ({ request }) => { try { const response = await fetch(`${backendApiUrl}/pipeline/run`, { method: "POST", - headers: { - "Content-Type": "application/json", - }, + headers: { "Content-Type": "application/json" }, body: JSON.stringify({ shop, source, @@ -109,10 +126,7 @@ export const action = async ({ request }) => { const payload = await readJsonSafe(response); if (!response.ok) { - return { - ok: false, - error: payload?.error || "Failed to start import job.", - }; + return { ok: false, error: payload?.error || "Failed to start import job." }; } return { @@ -124,10 +138,7 @@ export const action = async ({ request }) => { backendApiUrl, }; } catch (error) { - return { - ok: false, - error: error.message, - }; + return { ok: false, error: error.message }; } }; @@ -741,7 +752,7 @@ export default function Index() { const runningSources = useMemo(() => { return new Set( Object.entries(sourceJobs || {}) - .filter(([, sourceJob]) => sourceJob && !["done", "error"].includes(sourceJob.status)) + .filter(([, sourceJob]) => sourceJob && !["done", "error", "cancelled"].includes(sourceJob.status)) .map(([sourceKey]) => sourceKey), ); }, [sourceJobs]); @@ -780,6 +791,11 @@ export default function Index() { return; } + if (fetcher.data.ok && fetcher.data.cancelled) { + shopify.toast.show("Import cancellation requested — stopping after current step."); + return; + } + if (fetcher.data.ok && fetcher.data.jobId) { const sourceKey = fetcher.data.source || activeSourceConfig.sourceKey; setActiveSource(sourceKey); @@ -955,6 +971,7 @@ export default function Index() { Launch this source independently and track its live sync below.

+
@@ -962,6 +979,7 @@ export default function Index() { type="submit" variant="primary" {...(connection?.status !== 1 ? { disabled: true } : {})} + {...(isSubmitting || runningSources.has(activeSourceConfig.sourceKey) ? { disabled: true } : {})} {...(isSubmitting ? { loading: true } : {})} > Start {activeSourceConfig.label} @@ -969,6 +987,23 @@ export default function Index() {
+ + {runningSources.has(activeSourceConfig.sourceKey) && activeJobId && ( + + + +
+ + Cancel import + +
+
+ )}