Username is required: blocking first step on every sign-in until one is saved

showOnboard(required) has no skip and no dismiss; a suggestion from the email
prefix is prefilled (GET /api/my/username-suggest, made unique); the member
area does not render until the username exists. Welcome tour and login ad still
run once for invite arrivals afterwards. Wallet-only sessions are unaffected.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
martbost
2026-09-12 15:32:42 -05:00
parent 6996186061
commit a385fbf4c2
3 changed files with 35 additions and 10 deletions
+19 -5
View File
@@ -523,12 +523,18 @@
if ($('mcFinish')) $('mcFinish').hidden = !walletOnly;
if (!signedIn) return;
if ($('adminLink')) $('adminLink').hidden = !me.isAdmin; // admin portal link, only for ADMIN_EMAIL
// arrived from an invite page: run onboarding once (username, welcome tour, login ad)
// REQUIRED first step (Marty, 2026-09-12): no username, nothing else. The modal cannot be
// skipped or dismissed; it resolves only when a username is saved, then the area renders.
if (!me.username) {
if (!render.gating) { render.gating = true; showOnboard(true).then(() => { render.gating = false; render(); }); }
return;
}
// arrived from an invite page: run the welcome tour and login ad once
if (/[?&]welcome=1/.test(location.search) && !render.welcomed) {
render.welcomed = true;
history.replaceState(null, '', '/my' + (location.hash || ''));
(async () => {
try { if (!me.username) await showOnboard(); if (!(await showGauntlet())) await showLoginAd(); } catch (e) {}
try { if (!(await showGauntlet())) await showLoginAd(); } catch (e) {}
await render();
})();
}
@@ -1771,15 +1777,23 @@
}));
})();
// new-member onboarding: choose a username, optionally a bio (both skippable)
function showOnboard() {
// new-member onboarding: choose a username (required), optionally a bio.
// required=true: no skip, no backdrop dismiss, prefilled suggestion, resolves only after a save.
function showOnboard(required) {
return new Promise(resolve => {
const m = $('onboardModal'); if (!m) return resolve();
m.hidden = false; $('obErr').hidden = true;
$('obSkip').hidden = !!required;
if (required && !$('obUsername').value) {
fetch('/api/my/username-suggest').then(r => r.json()).then(r => { if (r.suggest && !$('obUsername').value) { $('obUsername').value = r.suggest; $('obUsername').select(); } }).catch(() => {});
}
setTimeout(() => $('obUsername').focus(), 50);
const done = () => { m.hidden = true; resolve(); };
$('obSkip').onclick = done;
$('obSkip').onclick = required ? null : done;
$('obUsername').onkeydown = e => { if (e.key === 'Enter') { e.preventDefault(); $('obSave').click(); } };
$('obSave').onclick = async () => {
const u = $('obUsername').value.trim();
if (required && !u) { $('obErr').hidden = false; $('obErr').textContent = 'Pick a username to continue.'; return; }
try {
if (u) await api('/api/my/profile', { username: u });
const bio = $('obBio').value.trim();