// Members pay before approval (Jay, Oct 30 2026: "members must pay before they can be approved into the group"). // The body of the pending "Waiting for approval" lock page in app.jsx. Each member pays for their own Bragwire app // subscription ($4.99 a month, Stripe Checkout, full-page redirect); paying never approves anyone, a group admin // still does. State comes from GET /auth/member/subscription (utils/member-billing.js pendingGate): // unpaid -> explain + Pay button (POST /auth/member/checkout) // paid -> "Paid. Waiting for your group admin to approve you." // unavailable -> must pay, but payments are off right now: no button // waiting -> covered / not required / older API / read error: the existing waiting copy // Back from Stripe (#checkout=success|cancel, caught by utils/member-billing.js): success polls until // subscription.current; cancel says so and keeps the Pay button. (function () { const { useState, useEffect } = React; function PendingPayLock({ me, billing, checkoutReturn, onReturnHandled, apiBase, token, withCode, onAuthExpired, onChanged }) { const B = window.BBMemberBilling; const GM = window.BBGroupMembers; const T = B.PENDING_TEXT; const [ret] = useState(() => checkoutReturn || ""); const [phase, setPhase] = useState(ret === "success" ? "confirming" : ""); const [note, setNote] = useState(ret === "cancel" ? T.cancelled : ""); const [busy, setBusy] = useState(false); const opts = { apiBase, token, withCode }; const apply = (st) => { if (st && (st.state === "ready" || st.reason === "off")) onChanged(st); }; useEffect(() => { if (ret && onReturnHandled) onReturnHandled(); }, []); // eslint-disable-line react-hooks/exhaustive-deps useEffect(() => { if (ret !== "success") return undefined; let live = true; B.pollUntilCurrent({ read: () => B.fetchSubscription(opts), alive: () => live }).then((res) => { if (!live) return; if (res.outcome === "auth") { onAuthExpired(); return; } apply(res.st); if (res.outcome === "timeout") { setPhase("pending"); setNote(B.TEXT.pending); } else setPhase(""); }); return () => { live = false; }; }, []); // eslint-disable-line react-hooks/exhaustive-deps const pay = async () => { if (busy) return; setBusy(true); setNote(""); setPhase(""); const r = await B.startCheckout(opts); if (r.ok) { window.location.assign(r.url); return; } // full-page redirect to Stripe; stays busy until the page leaves setBusy(false); if (r.kind === "auth") { onAuthExpired(); return; } if (r.kind === "refresh") { // 409 already_subscribed: show what the server has now const st = await B.fetchSubscription(opts); if (st.state === "hidden" && st.reason === "auth") { onAuthExpired(); return; } apply(st); return; } setNote(r.message); }; // After a success return that timed out: re-read once; still not current -> the Pay button comes back. const recheck = async () => { setBusy(true); const st = await B.fetchSubscription(opts); setBusy(false); if (st.state === "hidden" && st.reason === "auth") { onAuthExpired(); return; } apply(st); setPhase(""); setNote(""); }; const gate = phase === "confirming" ? "confirming" : B.pendingGate(billing); const signedIn =

Signed in as {me.handle}. This page updates by itself once an admin acts.

; if (gate === "confirming") { return (

{T.unpaidTitle}

{B.TEXT.confirming}

); } if (gate === "paid") { return (

{T.paidTitle}

{T.paidBody}

{signedIn}
); } if (gate === "unpaid" || gate === "unavailable") { return (

{T.unpaidTitle}

{T.unpaidBody}

{T.unpaidFee}

{note &&
{note}
} {phase === "pending" ? ( ) : gate === "unavailable" ? (

{T.unavailable}

) : ( <>

{T.payHint}

)}

Signed in as {me.handle}.

); } const lock = GM.lockScreen(me); return (

{lock.title}

{lock.message}

{gate === "loading" ?

{T.loading}

: signedIn}
); } window.BBPendingPay = { Lock: PendingPayLock }; })();