LeLab / src /components /training /InstallProgress.tsx
GitHub CI
Sync from leLab @ fa88de1790b1c0b6d5dbd19b602a8b498aabd3ba
0163c2d
Raw
History Blame Contribute Delete
4.74 kB
import React from "react";
import { Button } from "@/components/ui/button";
import { useToast } from "@/hooks/use-toast";
import {
AlertTriangle,
CheckCircle2,
Copy,
Loader2,
XCircle,
} from "lucide-react";
import type { InstallState, LogEntry } from "@/hooks/useInstallExtra";
interface InstallProgressProps {
state: InstallState;
error: string | null;
logs: LogEntry[];
logBoxRef: React.RefObject<HTMLDivElement>;
onInstall: () => void;
onRetry: () => void;
installHint: string;
packageName: string;
idleTitle: string;
idleDescription: React.ReactNode;
doneDescription: React.ReactNode;
}
export function installTitle(state: InstallState, idleTitle: string): string {
switch (state) {
case "done":
return "Install Complete";
case "error":
return "Install Failed";
case "installing":
return "Installing…";
default:
return idleTitle;
}
}
export function InstallTitleIcon({ state }: { state: InstallState }) {
if (state === "done") return <CheckCircle2 className="w-6 h-6 text-green-400" />;
if (state === "error") return <XCircle className="w-6 h-6 text-red-400" />;
if (state === "installing")
return <Loader2 className="w-6 h-6 text-sky-400 animate-spin" />;
return <AlertTriangle className="w-6 h-6 text-amber-400" />;
}
export const InstallProgress: React.FC<InstallProgressProps> = ({
state,
error,
logs,
logBoxRef,
onInstall,
onRetry,
installHint,
packageName,
idleDescription,
doneDescription,
}) => {
const { toast } = useToast();
const handleCopy = async () => {
try {
await navigator.clipboard.writeText(installHint);
toast({ title: "Copied", description: installHint });
} catch {
toast({
title: "Copy failed",
description: "Select the command and copy manually.",
variant: "destructive",
});
}
};
return (
<>
{state === "idle" && (
<>
<p className="text-slate-300">{idleDescription}</p>
<div className="flex items-center gap-2">
<code className="flex-1 bg-slate-900 border border-slate-700 rounded-lg px-3 py-2 text-sm text-slate-200 font-mono">
{installHint}
</code>
<Button
variant="ghost"
size="icon"
onClick={handleCopy}
className="text-slate-400 hover:text-white"
aria-label="Copy install command"
>
<Copy className="w-4 h-4" />
</Button>
</div>
<Button
onClick={onInstall}
className="bg-green-500 hover:bg-green-600 text-white font-semibold"
>
Install Now
</Button>
</>
)}
{state === "installing" && (
<p className="text-slate-300">
Installing{" "}
<code className="px-1 py-0.5 rounded bg-slate-900 text-sky-300">
{packageName}
</code>
. This usually takes about 10 seconds.
</p>
)}
{state === "done" && (
<div className="space-y-3 text-slate-300">{doneDescription}</div>
)}
{state === "error" && (
<>
<p className="text-red-300">{error || "Install failed."}</p>
<Button
onClick={onRetry}
className="bg-slate-700 hover:bg-slate-600 text-white"
>
Try again
</Button>
</>
)}
{state === "error" && logs.length > 0 && (
<div
ref={logBoxRef}
className="bg-slate-900 rounded-lg p-3 h-48 overflow-y-auto font-mono text-xs border border-slate-700 text-slate-300 whitespace-pre-wrap break-words"
>
{logs.map((log, idx) => (
<div key={idx}>{log.message}</div>
))}
</div>
)}
</>
);
};
export const RestartInstructions: React.FC<{ purpose: string }> = ({
purpose,
}) => (
<>
<p>
Install complete. Restart{" "}
<code className="px-1 py-0.5 rounded bg-slate-900 text-sky-300">
lelab
</code>{" "}
to enable {purpose}:
</p>
<ol className="list-decimal list-inside space-y-2 pl-1">
<li>
Press{" "}
<kbd className="px-1.5 py-0.5 rounded bg-slate-900 border border-slate-600 text-xs font-mono text-slate-200">
Ctrl+C
</kbd>{" "}
in the terminal running{" "}
<code className="px-1 py-0.5 rounded bg-slate-900 text-sky-300">
lelab
</code>
.
</li>
<li>
Run{" "}
<code className="px-1 py-0.5 rounded bg-slate-900 text-sky-300">
lelab
</code>{" "}
again.
</li>
</ol>
</>
);