"use client"; import { useEffect, useRef, useState } from "react"; import { useRouter } from "next/navigation"; import Link from "next/link"; import { CheckCircle2Icon, XCircleIcon, ScanLineIcon, DownloadIcon } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Skeleton } from "@/components/ui/skeleton"; import { useEvents } from "@/hooks/use-events"; import { countdownRender } from "@/lib/qr-dedupe"; type PairingState = | { phase: "waiting" } | { phase: "qr"; qrUrl: string } | { phase: "connected"; phoneNumber: string } | { phase: "timeout" }; interface PairLiveProps { accountId: string; label: string; } function CountdownBar({ seconds, total }: { seconds: number; total: number }) { const { pct, danger, expired } = countdownRender(seconds, total); const mm = Math.floor(seconds / 60); const ss = String(seconds % 60).padStart(2, "0"); return (
{expired ? "Pairing window expired" : "Pairing expires in"} {!expired && ( {mm}:{ss} )}
); } // 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 */} WhatsApp QR code

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.

)} {pairingState.phase === "connected" && (

Account connected!

{pairingState.phoneNumber && (

Connected as{" "} +{pairingState.phoneNumber.replace(/^\+/, "")}

)}

Redirecting in 3 seconds…

)} {pairingState.phase === "timeout" && (

Pairing timed out

The QR window closed before a device was linked.

)}
); }