diff --git a/packages/ui/src/components/connect-prompt.tsx b/packages/ui/src/components/connect-prompt.tsx index d9716c28..1e40e7f7 100644 --- a/packages/ui/src/components/connect-prompt.tsx +++ b/packages/ui/src/components/connect-prompt.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState, useCallback } from "react"; +import { useState, useCallback, useRef } from "react"; import { Button } from "@multica/ui/components/ui/button"; import { Textarea } from "@multica/ui/components/ui/textarea"; import { toast } from "@multica/ui/components/ui/sonner"; @@ -11,29 +11,45 @@ import { } from "@multica/store"; import { useIsMobile } from "@multica/ui/hooks/use-mobile"; import { HugeiconsIcon } from "@hugeicons/react"; -import { Camera01Icon } from "@hugeicons/core-free-icons"; -import { QrScannerSheet } from "@multica/ui/components/qr-scanner-sheet"; +import { Camera01Icon, TextIcon } from "@hugeicons/core-free-icons"; +import { QrScannerView } from "@multica/ui/components/qr-scanner-view"; + +type Mode = "scan" | "paste"; export function ConnectPrompt() { const gwState = useConnectionStore((s) => s.connectionState); + const [mode, setMode] = useState("scan"); const [codeInput, setCodeInput] = useState(""); - const [scanOpen, setScanOpen] = useState(false); const isMobile = useIsMobile(); + const validatingRef = useRef(false); - const handleConnect = useCallback(() => { - const trimmed = codeInput.trim(); - if (!trimmed) return; + const tryConnect = useCallback((raw: string) => { + const trimmed = raw.trim(); + if (!trimmed || validatingRef.current) return; + validatingRef.current = true; try { const info = parseConnectionCode(trimmed); saveConnection(info); useConnectionStore.getState().connect(info); - setCodeInput(""); } catch (e) { toast.error((e as Error).message); + } finally { + validatingRef.current = false; } - }, [codeInput]); + }, []); - // Promise-based handler for QrScannerView — resolve = success, reject = error + // Auto-validate on paste + const handlePaste = useCallback( + (e: React.ClipboardEvent) => { + const text = e.clipboardData.getData("text"); + if (!text.trim()) return; + // Let the textarea update visually first, then validate + setTimeout(() => tryConnect(text), 50); + }, + [tryConnect], + ); + + // Promise-based handler for QrScannerView const handleScanResult = useCallback(async (data: string) => { const info = parseConnectionCode(data); saveConnection(info); @@ -42,68 +58,87 @@ export function ConnectPrompt() { const isConnecting = gwState === "connecting" || gwState === "connected"; + // Mobile: scanner only, no tabs, no paste + if (isMobile) { + return ( +
+
+

Scan to start

+

+ Scan a QR code to use an Agent +

+ {isConnecting && ( +

+ Connecting to Agent... +

+ )} +
+ +
+ ); + } + + // Desktop: tab toggle (scan / paste), same-size panels return (
-

- {isMobile - ? "Scan or paste a connection code" - : "Paste a connection code to start"} +

+ {mode === "scan" ? "Scan to start" : "Paste to start"} +

+

+ {mode === "scan" + ? "Scan a QR code to use an Agent" + : "Paste a connection code to use an Agent"}

{isConnecting && (

- Connecting... + Connecting to Agent...

)}
-
- {/* Mobile: scan button + sheet */} - {isMobile && ( - <> - - - - )} - - {/* Paste UI (always shown) */} -