leLab / src /components /ui /PortDetectionModal.tsx
jurmy24's picture
mega big updates pre-demo
9a9d18a
import React, { useState } from "react";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogDescription,
} from "@/components/ui/dialog";
import { Loader2, Search, CheckCircle, AlertCircle } from "lucide-react";
import { useToast } from "@/hooks/use-toast";
import { useApi } from "@/contexts/ApiContext";
interface PortDetectionModalProps {
open: boolean;
onOpenChange: (open: boolean) => void;
robotType: "leader" | "follower";
onPortDetected: (port: string) => void;
}
const PortDetectionModal: React.FC<PortDetectionModalProps> = ({
open,
onOpenChange,
robotType,
onPortDetected,
}) => {
const [step, setStep] = useState<
"instructions" | "detecting" | "success" | "error"
>("instructions");
const [detectedPort, setDetectedPort] = useState<string>("");
const [error, setError] = useState<string>("");
const [portsBeforeDisconnect, setPortsBeforeDisconnect] = useState<string[]>(
[]
);
const { toast } = useToast();
const { baseUrl, fetchWithHeaders } = useApi();
const handleStartDetection = async () => {
try {
setStep("detecting");
setError("");
// Start port detection process
const response = await fetchWithHeaders(
`${baseUrl}/start-port-detection`,
{
method: "POST",
body: JSON.stringify({
robot_type: robotType,
}),
}
);
const data = await response.json();
if (data.status === "success") {
setPortsBeforeDisconnect(data.data.ports_before);
// Give user time to disconnect
setTimeout(() => {
detectPortAfterDisconnect(data.data.ports_before);
}, 3000); // 3 second delay to allow disconnection
} else {
throw new Error(data.message || "Failed to start port detection");
}
} catch (error) {
console.error("Error starting port detection:", error);
setError(
`Error starting detection: ${
error instanceof Error ? error.message : "Unknown error"
}`
);
setStep("error");
}
};
const detectPortAfterDisconnect = async (portsBefore: string[]) => {
try {
const response = await fetchWithHeaders(
`${baseUrl}/detect-port-after-disconnect`,
{
method: "POST",
body: JSON.stringify({
ports_before: portsBefore,
}),
}
);
const data = await response.json();
if (data.status === "success") {
setDetectedPort(data.port);
// Save the port for future use
await savePort(data.port);
setStep("success");
toast({
title: "Port Detected Successfully",
description: `${robotType} port detected: ${data.port}`,
});
} else {
throw new Error(data.message || "Failed to detect port");
}
} catch (error) {
console.error("Error detecting port:", error);
setError(
`Error detecting port: ${
error instanceof Error ? error.message : "Unknown error"
}`
);
setStep("error");
}
};
const savePort = async (port: string) => {
try {
await fetchWithHeaders(`${baseUrl}/save-robot-port`, {
method: "POST",
body: JSON.stringify({
robot_type: robotType,
port: port,
}),
});
} catch (error) {
console.error("Error saving port:", error);
// Don't throw here, as the main detection was successful
}
};
const handleUsePort = () => {
onPortDetected(detectedPort);
onOpenChange(false);
resetModal();
};
const handleClose = () => {
onOpenChange(false);
resetModal();
};
const resetModal = () => {
setStep("instructions");
setDetectedPort("");
setError("");
setPortsBeforeDisconnect([]);
};
const renderStepContent = () => {
switch (step) {
case "instructions":
return (
<div className="space-y-6">
<div className="text-center space-y-4">
<Search className="w-16 h-16 text-blue-500 mx-auto" />
<div className="space-y-2">
<h3 className="text-lg font-semibold text-white">
Detect {robotType === "leader" ? "Leader" : "Follower"} Port
</h3>
<p className="text-gray-400">
Follow these steps to automatically detect the robot port:
</p>
</div>
</div>
<div className="bg-gray-800 rounded-lg p-4 space-y-3">
<div className="flex items-start gap-3">
<div className="w-6 h-6 bg-blue-500 rounded-full flex items-center justify-center text-white text-sm font-bold">
1
</div>
<p className="text-gray-300 text-sm">
Make sure your {robotType} robot arm is currently connected
</p>
</div>
<div className="flex items-start gap-3">
<div className="w-6 h-6 bg-blue-500 rounded-full flex items-center justify-center text-white text-sm font-bold">
2
</div>
<p className="text-gray-300 text-sm">
Click "Start Detection" below
</p>
</div>
<div className="flex items-start gap-3">
<div className="w-6 h-6 bg-blue-500 rounded-full flex items-center justify-center text-white text-sm font-bold">
3
</div>
<p className="text-gray-300 text-sm">
When prompted,{" "}
<strong>unplug the {robotType} robot arm</strong> from USB
</p>
</div>
<div className="flex items-start gap-3">
<div className="w-6 h-6 bg-blue-500 rounded-full flex items-center justify-center text-white text-sm font-bold">
4
</div>
<p className="text-gray-300 text-sm">
The system will automatically detect which port was
disconnected
</p>
</div>
</div>
<div className="flex gap-4 justify-center">
<Button
onClick={handleStartDetection}
className="bg-blue-500 hover:bg-blue-600 text-white px-8 py-2"
>
Start Detection
</Button>
<Button
onClick={handleClose}
variant="outline"
className="border-gray-500 hover:border-gray-200 text-gray-300 hover:text-white px-8 py-2"
>
Cancel
</Button>
</div>
</div>
);
case "detecting":
return (
<div className="space-y-6 text-center">
<Loader2 className="w-16 h-16 text-blue-500 mx-auto animate-spin" />
<div className="space-y-2">
<h3 className="text-lg font-semibold text-white">
Detecting Port...
</h3>
<p className="text-gray-400">
Please <strong>unplug the {robotType} robot arm</strong> from
USB now
</p>
<p className="text-sm text-gray-500">
Detection will complete automatically in a few seconds
</p>
</div>
</div>
);
case "success":
return (
<div className="space-y-6 text-center">
<CheckCircle className="w-16 h-16 text-green-500 mx-auto" />
<div className="space-y-2">
<h3 className="text-lg font-semibold text-white">
Port Detected Successfully!
</h3>
<p className="text-gray-400">
{robotType === "leader" ? "Leader" : "Follower"} port detected:
</p>
<p className="text-xl font-mono text-green-400 bg-gray-800 px-4 py-2 rounded">
{detectedPort}
</p>
<p className="text-sm text-gray-500">
This port has been saved as the default for future use.
<br />
You can now reconnect your robot arm.
</p>
</div>
<div className="flex gap-4 justify-center">
<Button
onClick={handleUsePort}
className="bg-green-500 hover:bg-green-600 text-white px-8 py-2"
>
Use This Port
</Button>
<Button
onClick={handleClose}
variant="outline"
className="border-gray-500 hover:border-gray-200 text-gray-300 hover:text-white px-8 py-2"
>
Cancel
</Button>
</div>
</div>
);
case "error":
return (
<div className="space-y-6 text-center">
<AlertCircle className="w-16 h-16 text-red-500 mx-auto" />
<div className="space-y-2">
<h3 className="text-lg font-semibold text-white">
Detection Failed
</h3>
<p className="text-gray-400">Unable to detect the robot port.</p>
<div className="bg-red-900/20 border border-red-800 rounded-lg p-3">
<p className="text-red-400 text-sm">{error}</p>
</div>
</div>
<div className="flex gap-4 justify-center">
<Button
onClick={() => setStep("instructions")}
className="bg-blue-500 hover:bg-blue-600 text-white px-8 py-2"
>
Try Again
</Button>
<Button
onClick={handleClose}
variant="outline"
className="border-gray-500 hover:border-gray-200 text-gray-300 hover:text-white px-8 py-2"
>
Cancel
</Button>
</div>
</div>
);
default:
return null;
}
};
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="bg-gray-900 border-gray-800 text-white sm:max-w-[500px] p-8">
<DialogHeader>
<DialogTitle className="text-white text-center text-xl font-bold">
Port Detection
</DialogTitle>
<DialogDescription className="text-gray-400 text-center">
Automatically detect the USB port for your robot arm
</DialogDescription>
</DialogHeader>
<div className="py-4">{renderStepContent()}</div>
</DialogContent>
</Dialog>
);
};
export default PortDetectionModal;