import { useCallback, useEffect, useRef, useState } from "react"; import { openUrl } from "@tauri-apps/plugin-opener"; import { useInstallHelper } from "../hooks/useInstallHelper"; import { useDocker } from "../hooks/useDocker"; interface Props { onClose: () => void; } type Phase = "idle" | "installing" | "done" | "error"; export default function DockerInstallDialog({ onClose }: Props) { const { options, loadOptions, runInstall } = useInstallHelper(); const { checkDocker } = useDocker(); const [showManual, setShowManual] = useState(false); const [phase, setPhase] = useState("idle"); const [log, setLog] = useState([]); const [error, setError] = useState(null); const overlayRef = useRef(null); useEffect(() => { loadOptions(); }, [loadOptions]); useEffect(() => { const onKey = (e: KeyboardEvent) => { if (e.key === "Escape" && phase !== "installing") onClose(); }; document.addEventListener("keydown", onKey); return () => document.removeEventListener("keydown", onKey); }, [onClose, phase]); const handleOverlayClick = useCallback( (e: React.MouseEvent) => { if (e.target === overlayRef.current && phase !== "installing") onClose(); }, [onClose, phase], ); const handleInstall = async () => { setPhase("installing"); setLog([]); setError(null); try { await runInstall((line) => setLog((prev) => [...prev, line])); setPhase("done"); // Re-check Docker so the rest of the app can proceed without a reload. await checkDocker(); } catch (e) { setError(String(e)); setPhase("error"); } }; const handleOpenDocs = async () => { if (!options) return; try { await openUrl(options.docs_url); } catch (e) { console.error("Failed to open docs URL:", e); } }; const handleRecheck = async () => { const available = await checkDocker(); if (available) onClose(); }; if (!options) { return null; } const installVerb = phase === "installing" ? "Installing…" : `Install ${options.product_name}`; return (

Docker not detected

Triple-C needs a Docker-compatible runtime to manage sandboxed project containers. We can install {options.product_name}{" "} for you, or you can follow the official instructions.

{phase === "idle" && (
{options.can_auto_install ? ( ) : (
One-click install unavailable:{" "} {options.auto_install_blocker ?? "required tooling missing."}
)}
)} {phase === "installing" && (
Installing… a system password prompt may appear. Do not close this window.
)} {phase === "done" && (
Install finished.
{options.post_install_notes.length > 0 && (
    {options.post_install_notes.map((note, i) => (
  • {note}
  • ))}
)}
)} {phase === "error" && (
Install failed.
{error &&
{error}
}
)} {(showManual || phase === "error") && (
Manual install steps
    {options.manual_steps.map((step, i) => (
  1. {step}
  2. ))}
)} {log.length > 0 && (
{log.map((line, i) => (
{line}
))}
)} {phase === "idle" && (
)}
); }