);
}
// Match the bot's PAIR_TIMEOUT_MS — 5 minutes — so the pairing-window
// timer is the single source of truth for "you have to scan by then".
// Individual QR rotations (~5 s each from Baileys) refresh the displayed
// QR but do NOT reset this timer — that would punish the user with a
// constantly-resetting countdown.
const PAIRING_WINDOW_SEC = 5 * 60;
export function PairLive({ accountId, label }: PairLiveProps) {
const router = useRouter();
const [pairingState, setPairingState] = useState({ phase: "waiting" });
const [countdown, setCountdown] = useState(PAIRING_WINDOW_SEC);
const timerRef = useRef | null>(null);
const startedRef = useRef(false);
/** Start the pairing-window countdown once. Subsequent QR refreshes do
* not restart this — only mount/connect/timeout/unmount touches it. */
const startCountdown = () => {
if (startedRef.current) return;
startedRef.current = true;
if (timerRef.current) clearInterval(timerRef.current);
setCountdown(PAIRING_WINDOW_SEC);
timerRef.current = setInterval(() => {
setCountdown((c) => {
if (c <= 1) {
if (timerRef.current) clearInterval(timerRef.current);
return 0;
}
return c - 1;
});
}, 1000);
};
useEvents({
"session.qr": (data) => {
if (data.accountId !== accountId) return;
// Bust the URL with the timestamp so the browser refetches each time.
setPairingState({ phase: "qr", qrUrl: `/api/qr/${accountId}?t=${data.ts}` });
// Idempotent — only the first QR starts the global pairing-window
// timer; later QR rotations leave it ticking.
startCountdown();
},
"session.connected": (data) => {
if (data.accountId !== accountId) return;
if (timerRef.current) clearInterval(timerRef.current);
setPairingState({
phase: "connected",
phoneNumber: data.phoneNumber ?? "",
});
},
"session.timeout": (data) => {
if (data.accountId !== accountId) return;
if (timerRef.current) clearInterval(timerRef.current);
setPairingState({ phase: "timeout" });
},
});
// Auto-redirect on connected
useEffect(() => {
if (pairingState.phase !== "connected") return;
const t = setTimeout(() => {
router.push(`/accounts/${accountId}` as never);
}, 3000);
return () => clearTimeout(t);
}, [pairingState.phase, accountId, router]);
// Cleanup interval on unmount
useEffect(() => {
return () => {
if (timerRef.current) clearInterval(timerRef.current);
};
}, []);
return (
{/* Label chip */}
{label}
{/* State display */}
{pairingState.phase === "waiting" && (
Generating QR…
)}
{pairingState.phase === "qr" && (
{/* Pairing-window countdown — single source of truth.
The QR image rotates every ~5 s but this timer keeps ticking
against the bot's PAIR_TIMEOUT (5 min). */}
{/* QR image */}
{/* eslint-disable-next-line @next/next/no-img-element */}
Scan with WhatsApp → Linked Devices
Open WhatsApp → tap ⋮ → Linked Devices → Link a device
The QR rotates automatically every few seconds — scan whichever one is showing.