| index 7179b7e..fec73e5 100644 |
| --- a/home/web/static/home.js |
| +++ b/home/web/static/home.js |
| @@ -87,6 +87,11 @@ function fmtBytes(n) { |
| // /api/features). The create-a-space controls only appear when it's both |
| // enabled here and you're signed in. |
| let spacesEnabled = false; |
| +// Per-country provisioning (GET /api/features): regions is [{code,name}] and |
| +// regionChoice is the default the picker starts on. Empty/one region → the |
| +// picker stays hidden and creation auto-assigns server-side. |
| +let regions = []; |
| +let regionChoice = ""; |
| // provisioned: host → row from GET /api/spaces — which listed spaces THIS |
| // account created, plus their entitlement. billingEnabled mirrors the |
| // provisioner's flag; both drive the quiet Upgrade affordance + Supporter |
| @@ -1003,6 +1008,29 @@ async function openPortal(host, btn) { |
| |
| // ---- create a new space ------------------------------------------------------- |
| |
| +// populateRegionPicker fills the create screen's region <select> from |
| +// /api/features. Built as DOM nodes (never innerHTML). Shown only for 2+ |
| +// regions — one region, or a single-region home server, auto-assigns and the |
| +// control stays hidden. |
| +function populateRegionPicker() { |
| + const wrap = $("create-region"); |
| + const sel = $("region-select"); |
| + if (!wrap || !sel) return; |
| + sel.textContent = ""; |
| + if (regions.length < 2) { |
| + wrap.classList.add("hidden"); |
| + return; |
| + } |
| + for (const r of regions) { |
| + const opt = document.createElement("option"); |
| + opt.value = r.code; |
| + opt.textContent = r.name || r.code; |
| + if (r.code === regionChoice) opt.selected = true; |
| + sel.appendChild(opt); |
| + } |
| + wrap.classList.remove("hidden"); |
| +} |
| + |
| // requestSpace is the real create call, fired by the fader the moment it snaps |
| // to eleven: POST /api/spaces (link-token auth) — it returns only once the |
| // server has confirmed the space is live and reachable, so the fader's build |
| @@ -1014,10 +1042,15 @@ async function requestSpace() { |
| // and grind it out right here, during the fader's build phase — the CPU |
| // burn IS the "Hold on…" moment. bits 0 = no gate, nothing to solve. |
| const proof = await solveCreateChallenge(); |
| + // The owner's region choice rides alongside the proof (empty = auto-assign |
| + // server-side). The picker only exists for a home server with 2+ regions. |
| + const payload = proof || {}; |
| + const sel = $("region-select"); |
| + if (regions.length > 1 && sel && sel.value) payload.region = sel.value; |
| const res = await fetch("/api/spaces", { |
| method: "POST", |
| headers: { Authorization: "Bearer " + state.token, "Content-Type": "application/json" }, |
| - body: JSON.stringify(proof || {}), |
| + body: JSON.stringify(payload), |
| }); |
| const body = await res.json().catch(() => ({})); |
| if (!res.ok) { |
| @@ -1588,6 +1621,9 @@ $("space-search").addEventListener("input", () => { if (vault) showVault(); }); |
| apiJSON("/api/features") |
| .then((f) => { |
| spacesEnabled = !!f.spaces; |
| + regions = Array.isArray(f.regions) ? f.regions : []; |
| + regionChoice = f.default_region || (regions[0] && regions[0].code) || ""; |
| + populateRegionPicker(); |
| // The no-account demo path (shown signed-out, where it matters). |
| $("demo-offer").classList.toggle("hidden", !f.demo); |
| if (vault) $("open-create").classList.toggle("hidden", !canCreateSpace()); |