// My account in the app: a thin React wrapper around the shared, framework-free module (shared/account/). // #/account and #/account/signin are pages (PageBar + Back), never modals. The module owns the bbsu_ account // session (30-day rolling app session via POST /auth/account/refresh {client:"app"}, localStorage bb_signup_token); // the app's handle session (bb_token, bb_group_code, bb_handle) is written when the person opens a group they just // joined or created, or picks "Use this handle" (POST /auth/account/switch, the /auth/join shape), or when the // handle in use is left and the page switches to another one (onActiveHandle). // useAccountAvailable() fails closed: unless /api/config has {"accountApi": true} AND the account API answers 2xx, // the app hides every My account entry point and #/account shows the handle + PIN Profile page (as before #686). (function () { const { useEffect, useRef, useState } = React; function mockOn() { try { const prodHost = /(^|\.)bragwire\.com$/i.test(window.location.hostname || ""); return !prodHost && /[?&]mock=1(&|$)/.test(window.location.search || ""); } catch (e) { return false; } } // Store a handle session for the app (same keys JoinModal / signup-open write), then reload into Games so every // per-group cache starts clean. Shared (signed-out) theme keys are cleared like any sign-in; per-user theme kept. // path: where to land after the reload (default Games; "/account" after a leave-and-switch keeps the page). function openAppSession(s, path) { if (!s || !s.token) return; try { ["bb_appearance", "bb_team_theme", "bb_theme"].forEach((k) => localStorage.removeItem(k)); localStorage.setItem("bb_theme_per_user", "1"); localStorage.setItem("bb_token", s.token); localStorage.setItem("bb_group_code", s.groupCode); localStorage.setItem("bb_handle", s.handle); } catch (e) {} try { window.history.replaceState(null, "", window.location.pathname + window.location.search + "#" + (path || "/games")); } catch (e) {} window.location.reload(); } function sameHandle(groupCode, handle) { const g = String(localStorage.getItem("bb_group_code") || "").trim().toLowerCase(); const h = String(localStorage.getItem("bb_handle") || "").trim().toLowerCase(); return !!g && g === String(groupCode || "").trim().toLowerCase() && h === String(handle || "").trim().toLowerCase(); } function activeHandle() { try { const token = localStorage.getItem("bb_token") || ""; const groupCode = localStorage.getItem("bb_group_code") || ""; const handle = localStorage.getItem("bb_handle") || ""; return token || (groupCode && handle) ? { token, groupCode, handle } : null; } catch (e) { return null; } } function AccountPage({ view, apiBase, onHandleRenamed, onHandleLeft, onSignedIn, onSignedOut, onSessionExpired }) { const ref = useRef(null); useEffect(() => { if (!ref.current || !window.BWAccountUI) return undefined; const ctl = window.BWAccountUI.mount(ref.current, { host: "app", apiBase, appUrl: window.location.origin, ssoReturnTo: window.location.origin + "/", checkoutReturnTo: "https://app.bragwire.com/", pinSigninHref: "#/signin", transferAdminHref: "#/admin/members", view: view === "signin" ? "signin" : "account", mock: mockOn() ? true : null, onAppSession: (s) => openAppSession(s), activeHandle, onSignedIn: () => { if (view === "signin" && onSignedIn) onSignedIn(); }, // Account token refused for good (401, e.g. the 90-day cap): the module cleared it; go to #/account/signin, // which shows "Please sign in again." The handle session (bb_token) is separate and untouched. onSessionExpired: () => { if (view !== "signin" && onSessionExpired) onSessionExpired(); }, onRenamed: (r) => { if (sameHandle(r.groupCode, r.previous)) { try { localStorage.setItem("bb_handle", r.handle); } catch (e) {} if (onHandleRenamed) onHandleRenamed(r.handle); } }, // Leave keeps the link and the sessions, but a removed handle's session gets 403 everywhere. If it was the one // this app uses, the module switches to another handle (session) or reports none left (null): then this app // drops its handle session and stays on My account (signed in, no group). onActiveHandle: (s) => { if (s && s.token) { openAppSession(s, "/account"); return; } if (onHandleLeft) onHandleLeft(); }, // Account sign-out: the module already called POST /auth/account/signout and POST /auth/logout (this handle's // app session) in parallel. Drop the handle token first so the app's own sign-out doesn't call /auth/logout again. onSignedOut: () => { try { localStorage.removeItem("bb_token"); } catch (e) {} if (onSignedOut) onSignedOut(); }, }); return () => ctl.destroy(); }, [view, apiBase]); // eslint-disable-line react-hooks/exhaustive-deps return
; } // null while checking, then true/false. Fail closed: only true when /api/config has {"accountApi": true} AND // GET /auth/account/handles answers 2xx (BWAccountCore.checkAccountLive). Callers treat anything but true as hidden. function useAccountAvailable(apiBase) { const [ok, setOk] = useState(() => (mockOn() ? true : null)); useEffect(() => { if (mockOn()) return undefined; if (!window.BWAccountCore || !window.BWAccountCore.checkAccountLive) { setOk(false); return undefined; } let live = true; let token = ""; try { token = window.BWAccountCore.session(localStorage).token() || ""; } catch (e) { token = ""; } window.BWAccountCore.checkAccountLive({ apiBase, token }) .then((v) => { if (live) setOk(v === true); }, () => { if (live) setOk(false); }); return () => { live = false; }; }, [apiBase]); return ok; } window.BBMyAccount = { AccountPage, useAccountAvailable, openAppSession }; })();