From 600ecc668532ac45e66d8d470d5bf0fffeabce90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ullrich=20Sch=C3=A4fer?= Date: Sat, 23 May 2026 18:48:14 +0200 Subject: [PATCH] Match Komoot import page layout to Wahoo import page - Switch to max-w-4xl to match the generic import page - Move the trigger button to the header row (title on left, action on right) - Show progress inline as "X / Y" in the header while running - Progress card only appears once a batch exists; empty state is a centered paragraph like the generic page's "no workouts" message - Link to /activities on completion instead of a separate paragraph Co-Authored-By: Claude Sonnet 4.6 --- .../journal/app/routes/sync.import.komoot.tsx | 177 +++++++++--------- 1 file changed, 89 insertions(+), 88 deletions(-) diff --git a/apps/journal/app/routes/sync.import.komoot.tsx b/apps/journal/app/routes/sync.import.komoot.tsx index 07ab181..b8b51f7 100644 --- a/apps/journal/app/routes/sync.import.komoot.tsx +++ b/apps/journal/app/routes/sync.import.komoot.tsx @@ -50,8 +50,6 @@ export async function action({ request }: Route.ActionArgs) { const user = await getSessionUser(request); if (!user) return redirect("/auth/login"); - // Delegate to the API route — just redirect so the page reloads with - // the new batch after the POST. const resp = await fetch( new URL("/api/sync/komoot/import", new URL(request.url).origin), { method: "POST", headers: { cookie: request.headers.get("cookie") ?? "" } }, @@ -78,107 +76,110 @@ export default function KomootImportPage({ loaderData }: Route.ComponentProps) { const pollingRef = useRef | null>(null); const isActive = batch?.status === "pending" || batch?.status === "running"; + const isIdle = triggerFetcher.state === "idle"; useEffect(() => { if (isActive) { - pollingRef.current = setInterval(() => { - revalidator.revalidate(); - }, 2000); + pollingRef.current = setInterval(() => revalidator.revalidate(), 2000); } - return () => { - if (pollingRef.current) clearInterval(pollingRef.current); - }; + return () => { if (pollingRef.current) clearInterval(pollingRef.current); }; }, [isActive]); const elapsedSeconds = batch ? Math.floor( - (batch.completedAt - ? new Date(batch.completedAt).getTime() - : Date.now()) - new Date(batch.startedAt).getTime(), - ) / 1000 + ((batch.completedAt ? new Date(batch.completedAt) : new Date()).getTime() + - new Date(batch.startedAt).getTime()) / 1000, + ) : 0; return ( -
-

- {t("sync.importFrom", { provider: "Komoot" })} -

+
+ {/* Header row — mirrors sync.import.$provider layout */} +
+

+ {t("sync.importFrom", { provider: "Komoot" })} +

-
- {!batch && ( -
-

{t("komoot.import.noImportYet")}

- - - -
- )} - - {batch && ( -
-
- - {(batch.status === "completed" || batch.status === "failed") && ( - - - - )} -
- - {isActive && ( -
-
0 - ? `${Math.round(((batch.importedCount + batch.duplicateCount) / batch.totalFound) * 100)}%` - : "5%", - }} - /> -
- )} - -
- - - -
- - {batch.status === "completed" && ( -

- {t("komoot.import.completedIn", { duration: formatDuration(elapsedSeconds) })} -

- )} - - {batch.status === "failed" && batch.errorMessage && ( -

- {batch.errorMessage} -

- )} -
+ {isActive ? ( + + {batch.totalFound > 0 + ? t("sync.importingProgress", { + current: batch.importedCount + batch.duplicateCount, + total: batch.totalFound, + }) + : t("komoot.import.status.running")} + + ) : ( + + + )}
- {batch?.status === "completed" && ( -

- - {t("komoot.import.viewActivities")} - -

+ {/* Progress card — shown once a batch exists */} + {batch && ( +
+
+ + {(batch.status === "completed" || batch.status === "failed") && ( + + + + )} +
+ + {isActive && ( +
+
0 + ? `${Math.round(((batch.importedCount + batch.duplicateCount) / batch.totalFound) * 100)}%` + : "5%", + }} + /> +
+ )} + +
+ + + +
+ + {batch.status === "completed" && ( +

+ {t("komoot.import.completedIn", { duration: formatDuration(elapsedSeconds) })} + {" · "} + + {t("komoot.import.viewActivities")} + +

+ )} + + {batch.status === "failed" && batch.errorMessage && ( +

+ {batch.errorMessage} +

+ )} +
+ )} + + {/* Empty state — no batch yet */} + {!batch && ( +

{t("komoot.import.noImportYet")}

)}
);