// Komoot import page — shows live progress for background bulk imports // and lets the user trigger a new import run. import { useEffect, useRef } from "react"; import { data, redirect, useFetcher, useRevalidator } from "react-router"; import { useTranslation } from "react-i18next"; import type { Route } from "./+types/sync.import.komoot"; import { getSessionUser } from "~/lib/auth/session.server"; import { getService } from "~/lib/connected-services"; import { getDb } from "~/lib/db"; import { importBatches } from "@trails-cool/db/schema/journal"; import { desc, eq, and } from "drizzle-orm"; export function meta() { return [{ title: "Import from Komoot — trails.cool" }]; } export async function loader({ request }: Route.LoaderArgs) { const user = await getSessionUser(request); if (!user) return redirect("/auth/login"); const service = await getService(user.id, "komoot"); if (!service) return redirect("/settings/connections/komoot"); const db = getDb(); const [batch] = await db .select() .from(importBatches) .where(and(eq(importBatches.userId, user.id), eq(importBatches.connectionId, service.id))) .orderBy(desc(importBatches.startedAt)) .limit(1); return data({ batch: batch ? { id: batch.id, status: batch.status, totalFound: batch.totalFound, importedCount: batch.importedCount, duplicateCount: batch.duplicateCount, errorMessage: batch.errorMessage, startedAt: batch.startedAt.toISOString(), completedAt: batch.completedAt?.toISOString() ?? null, } : null, }); } export async function action({ request }: Route.ActionArgs) { const user = await getSessionUser(request); if (!user) return redirect("/auth/login"); const resp = await fetch( new URL("/api/sync/komoot/import", new URL(request.url).origin), { method: "POST", headers: { cookie: request.headers.get("cookie") ?? "" } }, ); if (!resp.ok) { const body = (await resp.json()) as { error?: string }; return data({ error: body.error ?? "failed" }, { status: resp.status }); } return redirect("/sync/import/komoot"); } function formatDuration(seconds: number): string { const h = Math.floor(seconds / 3600); const m = Math.floor((seconds % 3600) / 60); if (h > 0) return `${h}h ${m}m`; return `${m}m`; } export default function KomootImportPage({ loaderData }: Route.ComponentProps) { const { batch } = loaderData; const { t } = useTranslation("journal"); const revalidator = useRevalidator(); const triggerFetcher = useFetcher<{ error?: string }>(); 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); } return () => { if (pollingRef.current) clearInterval(pollingRef.current); }; }, [isActive]); const elapsedSeconds = batch ? Math.floor( ((batch.completedAt ? new Date(batch.completedAt) : new Date()).getTime() - new Date(batch.startedAt).getTime()) / 1000, ) : 0; return (
{/* Header row — mirrors sync.import.$provider layout */}

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

{isActive ? ( {batch.totalFound > 0 ? t("sync.importingProgress", { current: batch.importedCount + batch.duplicateCount, total: batch.totalFound, }) : t("komoot.import.status.running")} ) : ( )}
{/* 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")}

)}
); } function StatusBadge({ status, t }: { status: string; t: (k: string) => string }) { const styles: Record = { pending: "bg-gray-100 text-gray-600", running: "bg-blue-100 text-blue-700", completed: "bg-green-100 text-green-700", failed: "bg-red-100 text-red-700", }; return ( {t(`komoot.import.status.${status}`)} ); } function StatBox({ label, value }: { label: string; value: number }) { return (

{value}

{label}

); }