diff --git a/packages/ui/src/components/connect-prompt.tsx b/packages/ui/src/components/connect-prompt.tsx index decfbb81..d9716c28 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, useEffect, useCallback, lazy, Suspense, useRef } from "react"; +import { useState, useCallback } 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"; @@ -9,40 +9,17 @@ import { parseConnectionCode, saveConnection, } from "@multica/store"; +import { useIsMobile } from "@multica/ui/hooks/use-mobile"; import { HugeiconsIcon } from "@hugeicons/react"; -import { Camera01Icon, TextIcon } from "@hugeicons/core-free-icons"; - -const LazyQrScannerView = lazy(() => - import("@multica/ui/components/qr-scanner-view").then((m) => ({ - default: m.QrScannerView, - })), -); - -type Mode = "scan" | "paste"; +import { Camera01Icon } from "@hugeicons/core-free-icons"; +import { QrScannerSheet } from "@multica/ui/components/qr-scanner-sheet"; export function ConnectPrompt() { const gwState = useConnectionStore((s) => s.connectionState); const [codeInput, setCodeInput] = useState(""); - const [mode, setMode] = useState("paste"); // SSR-safe default - const [canScan, setCanScan] = useState(false); - const scannedRef = useRef(false); + const [scanOpen, setScanOpen] = useState(false); + const isMobile = useIsMobile(); - // Detect mobile + camera capability, auto-switch to scan mode - useEffect(() => { - const isTouchDevice = - "ontouchstart" in window || navigator.maxTouchPoints > 0; - const isNarrow = window.innerWidth < 768; - const hasGetUserMedia = !!navigator.mediaDevices?.getUserMedia; - - if (hasGetUserMedia) { - setCanScan(true); - if (isTouchDevice && isNarrow) { - setMode("scan"); - } - } - }, []); - - // Handle paste-mode connect const handleConnect = useCallback(() => { const trimmed = codeInput.trim(); if (!trimmed) return; @@ -56,26 +33,11 @@ export function ConnectPrompt() { } }, [codeInput]); - // Handle QR scan result — auto-connect, no button needed - const handleQrScan = useCallback((data: string) => { - // Prevent duplicate connects from rapid successive scans - if (scannedRef.current) return; - scannedRef.current = true; - - try { - const info = parseConnectionCode(data); - saveConnection(info); - useConnectionStore.getState().connect(info); - } catch (e) { - toast.error((e as Error).message); - // Allow re-scan on error (invalid/expired code) - scannedRef.current = false; - } - }, []); - - const handleScanError = useCallback((msg: string) => { - toast.error(msg); - setMode("paste"); + // Promise-based handler for QrScannerView — resolve = success, reject = error + const handleScanResult = useCallback(async (data: string) => { + const info = parseConnectionCode(data); + saveConnection(info); + useConnectionStore.getState().connect(info); }, []); const isConnecting = gwState === "connecting" || gwState === "connected"; @@ -84,8 +46,8 @@ export function ConnectPrompt() {

- {mode === "scan" - ? "Scan QR code to connect" + {isMobile + ? "Scan or paste a connection code" : "Paste a connection code to start"}

{isConnecting && ( @@ -95,70 +57,54 @@ export function ConnectPrompt() { )}
- {/* Mode toggle — only show if camera is available */} - {canScan && ( -
- - -
- )} - - {/* Content */}
- {mode === "scan" ? ( - - } - > - - - ) : ( + {/* Mobile: scan button + sheet */} + {isMobile && ( <> -