diff --git a/server/src/index.ts b/server/src/index.ts index ac9d9c7..d1d3c19 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -451,12 +451,16 @@ function handleCreatePairCode(client: Client, msg: ClientMessage & { type: "crea } function handleResolvePairCode(client: Client, msg: ClientMessage & { type: "resolve-pair-code" }): void { - if (typeof msg.code !== "string") return; + if (typeof msg.code !== "string" || msg.code.length !== 6) { + send(client.ws, { type: "error", code: "pair-code-not-found", message: "Code d'appairage invalide" }); + return; + } const entry = pairCodes.get(msg.code.toUpperCase()); if (!entry || Date.now() > entry.expiresAt) { send(client.ws, { type: "error", code: "pair-code-not-found", message: "Code d'appairage invalide ou expiré" }); return; } + pairCodes.delete(msg.code.toUpperCase()); console.log(`[pair] resolved code ${msg.code} → group ${entry.groupId.slice(0, 8)}...`); send(client.ws, { type: "pair-code-resolved", groupId: entry.groupId }); } diff --git a/web/src/components/DevicePairingPanel.tsx b/web/src/components/DevicePairingPanel.tsx index da7aab7..c2bbe12 100644 --- a/web/src/components/DevicePairingPanel.tsx +++ b/web/src/components/DevicePairingPanel.tsx @@ -1,4 +1,4 @@ -import { useState } from "react"; +import { useState, useEffect } from "react"; import { useProfileStore } from "../stores/useProfileStore"; import { useStore } from "../stores/useStore"; @@ -13,38 +13,60 @@ export default function DevicePairingPanel({ }: DevicePairingPanelProps) { const { groupId, setGroupId } = useProfileStore(); const pairCode = useStore((s) => s.pairCode); + const error = useStore((s) => s.error); const [showModal, setShowModal] = useState(false); const [mode, setMode] = useState<"show" | "enter">("show"); const [inputCode, setInputCode] = useState(""); + const [pairError, setPairError] = useState(null); - const handleShow = () => { - let gid = groupId; - if (!gid) { - gid = crypto.randomUUID(); - setGroupId(gid); + // When modal opens in "show" mode, always request a fresh code + useEffect(() => { + if (showModal && mode === "show") { + let gid = groupId; + if (!gid) { + gid = crypto.randomUUID(); + setGroupId(gid); + } + useStore.getState().setPairCode(null); + onRequestCode(gid); } - onRequestCode(gid); - setMode("show"); - setShowModal(true); - }; + }, [showModal, mode]); - const handleEnter = () => { - setMode("enter"); + // Detect pair-code-not-found errors + useEffect(() => { + if (error && error.includes("appairage")) { + setPairError(error); + useStore.getState().setError(null); + } + }, [error]); + + const handleClose = () => { + setShowModal(false); + useStore.getState().setPairCode(null); + setPairError(null); setInputCode(""); - setShowModal(true); }; const handleSubmitCode = () => { if (inputCode.length >= 6) { + setPairError(null); onResolveCode(inputCode); - setShowModal(false); + // Don't close — wait for response or error } }; + // Auto-close on successful pairing (groupId changed after resolve) + const currentGroupId = useProfileStore((s) => s.groupId); + useEffect(() => { + if (mode === "enter" && showModal && currentGroupId && currentGroupId !== groupId) { + handleClose(); + } + }, [currentGroupId]); + return ( <>