// Link a board (#/pair) and Enter a code (#/pair/enter). Pages only, no modals. Rules, copy and the API client live in // utils/board-pair.js (window.BBBoardPair); this file only renders the steps: // #/pair: account check -> preview (board label) -> confirm (group, picker for 2+) -> "Board linked to " // #/pair/enter: the short code field, then the same flow. // Every error page has "Enter a code instead". Long board and group names wrap (overflow-wrap: anywhere). // Read-only for everything else: no grading, ledger, balances, picks or wagers. (function () { const { useState, useEffect, useRef } = React; const P = () => window.BBBoardPair; const WRAP = { overflowWrap: "anywhere", wordBreak: "break-word", minWidth: 0 }; const BTN = { maxWidth: "100%", whiteSpace: "normal" }; function Actions({ children }) { return
{children}
; } function EnterCodePage({ navigate, initialError }) { const [code, setCode] = useState(""); const [err, setErr] = useState(initialError || ""); const submit = (e) => { e.preventDefault(); const c = P().normalizeCode(code); if (!c) { setErr("Enter the code shown on your board."); return; } P().setPending(sessionStorage, c); navigate("/pair"); }; return (

Type the short code shown on your Bragwire Board to link it to one of your groups.

{ setCode(e.target.value); setErr(""); }} autoComplete="one-time-code" autoCapitalize="characters" spellCheck={false} maxLength={64} inputMode="text" aria-invalid={err ? "true" : "false"} aria-describedby={err ? "bb-pair-code-err" : undefined} style={{ boxSizing: "border-box", maxWidth: "100%", textTransform: "uppercase", letterSpacing: "0.08em" }} /> {err && }
); } function ErrorPage({ kind, onEnterCode, onRetry, onPickGroup, onSignIn, onAccount }) { const c = P().copyFor(kind); return (

{c.title}

{c.body}

{onPickGroup && } {onSignIn && } {onAccount && } {onRetry && }
); } // mode: "link" (#/pair) | "enter" (#/pair/enter). accountAvailable: null while checking, then true / false. // token: the app (handle) session, the same one My boards sends. accountAvailable picks which sign-in page to open. function BoardPairPage({ mode, apiBase, token, navigate, accountAvailable, currentGroupCode }) { const [st, setSt] = useState({ step: "loading" }); // loading | confirm | claiming | success | error const [pick, setPick] = useState(""); const [nonce, setNonce] = useState(0); const live = useRef(true); useEffect(() => { live.current = true; return () => { live.current = false; }; }, []); const client = () => P().createClient({ apiBase, token: () => token }); const enterCode = () => { P().clearPending(sessionStorage); navigate("/pair/enter"); }; // My account sign-in (its Enter opens the app session and lands back on #/pair); without accounts, the // handle + PIN sign-in with next=/pair. const signIn = (auto) => { P().markResume(sessionStorage); const to = accountAvailable === true ? "/account/signin" : window.BBRouter.build("signin", null, { next: "/pair" }); // From the first render's effect the App's route listener may not be subscribed yet: hop on the next tick. if (auto === true) setTimeout(() => { if (live.current) navigate(to, { replace: true }); }, 0); else navigate(to); }; useEffect(() => { if (mode !== "link" || !P()) return undefined; // #/pair?c= opened inside the app (hashchange): keep the code, strip c. P().capture(window); const c = P().peekPending(sessionStorage); if (!c) { navigate("/pair/enter", { replace: true }); return undefined; } if (!P().appToken(token)) { // Came back from sign-in without an app session: show Sign in instead of redirecting again. if (P().resuming(sessionStorage)) { setSt({ step: "error", kind: "signed_out" }); return undefined; } if (accountAvailable == null) { setSt({ step: "loading" }); return undefined; } signIn(true); return undefined; } P().takeResume(sessionStorage); let on = true; setSt({ step: "loading" }); (async () => { const pv = await client().preview(c); if (!on) return; if (!pv.ok) { setSt({ step: "error", kind: pv.kind }); return; } const gs = await client().groups(currentGroupCode); if (!on) return; if (!gs.ok) { setSt({ step: "error", kind: gs.kind }); return; } const groups = gs.groups; if (!groups.length) { setSt({ step: "error", kind: "no_groups" }); return; } setPick((p) => (groups.some((g) => g.key === p) ? p : groups[0].key)); setSt({ step: "confirm", board: pv.board, groups }); })(); return () => { on = false; }; }, [mode, apiBase, token, accountAvailable, nonce]); // eslint-disable-line react-hooks/exhaustive-deps if (!P()) return
Board linking failed to load. Please refresh.
; if (mode === "enter") return ; const link = async () => { if (st.step !== "confirm") return; const group = st.groups.find((g) => g.key === pick) || st.groups[0]; const prev = st; setSt({ ...st, step: "claiming" }); const r = await client().claim(P().peekPending(sessionStorage), group.groupCode, group.sendHandle ? group.handle : ""); if (!live.current) return; if (r.ok) { P().clearPending(sessionStorage); const L = r.linked || {}; setSt({ step: "success", groupName: L.groupName || group.groupName, boardLabel: L.boardLabel || prev.board.label, handle: L.handle || group.handle }); return; } setSt({ step: "error", kind: r.kind, back: prev }); }; if (st.step === "loading") return
Loading…
; if (st.step === "error") { const k = st.kind; const back = st.back; return ( 1 ? () => setSt(back) : null} onSignIn={k === "signed_out" ? () => signIn(false) : null} onAccount={k === "no_groups" ? () => navigate("/account") : null} onRetry={k === "rate_limited" || k === "network" || k === "generic" || k === "unavailable" ? () => (back ? setSt(back) : setNonce((n) => n + 1)) : null} /> ); } if (st.step === "success") { return (

Board linked to {st.groupName}

{st.handle &&

Owner: {st.handle}

}

{st.boardLabel ? st.boardLabel + " switches" : "Your board switches"} over in a few seconds.

); } const busy = st.step === "claiming"; const many = st.groups.length > 1; const one = st.groups[0]; return (
Board
{st.board.label}
{many ? (
Link it to {st.groups.map((g) => ( ))}
) : (
Link it to
{one.groupName}
)}
); } window.BBBoardPairUI = { BoardPairPage }; })();