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:
+19
-5
@@ -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();
|
||||
|
||||
+5
-5
@@ -98,15 +98,15 @@
|
||||
<div id="onboardModal" class="modal-back" hidden>
|
||||
<div class="modal-card">
|
||||
<p class="eyebrow">Welcome to InstantAdPay</p>
|
||||
<h3 style="margin:.2em 0 .5em">Pick your username</h3>
|
||||
<p class="muted small">This is how your team sees you, and it turns your invite link into a clean vanity link.
|
||||
You can change it later in Profile.</p>
|
||||
<h3 style="margin:.2em 0 .5em">Pick your username to continue</h3>
|
||||
<p class="muted small">This is how your team sees you, and it becomes your invite link and your public page.
|
||||
It is permanent once saved, so pick the one you want to be known by. We suggested one from your email; keep it or change it.</p>
|
||||
<p><input id="obUsername" placeholder="Username (3-20 letters, numbers, _)" autocomplete="off" style="width:100%"></p>
|
||||
<p class="muted small" style="margin:12px 0 4px">Optional — a short bio for your public page (you can skip this).</p>
|
||||
<p><textarea id="obBio" rows="2" maxlength="600" placeholder="A short bio (optional)" style="width:100%"></textarea></p>
|
||||
<p id="obErr" class="small" style="color:var(--bad)" hidden></p>
|
||||
<p style="margin-top:14px;display:flex;gap:10px;justify-content:flex-end">
|
||||
<button class="btn sec small" id="obSkip" type="button">Skip for now</button>
|
||||
<button class="btn sec small" id="obSkip" type="button" hidden>Skip for now</button>
|
||||
<button class="btn" id="obSave" type="button">Save & continue</button>
|
||||
</p>
|
||||
</div>
|
||||
@@ -915,7 +915,7 @@
|
||||
<script src="/assets/common.js?v=20260912b"></script>
|
||||
<script src="/assets/wallet.js?v=20260911a"></script>
|
||||
<script src="/assets/promo.js?v=20260911a"></script>
|
||||
<script src="/assets/my.js?v=20260912c"></script>
|
||||
<script src="/assets/my.js?v=20260912e"></script>
|
||||
<script src="/assets/chat.js?v=20260907l"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1108,6 +1108,17 @@ const server = http.createServer(async (req, res) => {
|
||||
const r = await promos.setActive(b.code, !!b.active);
|
||||
return json(res, r.error ? 400 : 200, r);
|
||||
}
|
||||
// -- username suggestion for the required first step (Marty, 2026-09-12): email prefix, cleaned, unique
|
||||
if (p === '/api/my/username-suggest' && req.method === 'GET') {
|
||||
const s = await auth.fromRequest(req);
|
||||
if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' });
|
||||
let base = String(s.email).split('@')[0].toLowerCase().replace(/[^a-z0-9_]/g, '').slice(0, 18);
|
||||
if (!/[a-z]/.test(base)) base = 'member' + base;
|
||||
if (base.length < 3) base = (base + 'xyz').slice(0, 3);
|
||||
let pick = base;
|
||||
for (let i = 2; i < 100 && await accounts.byUsername(pick); i++) pick = base.slice(0, 20 - String(i).length) + i;
|
||||
return json(res, 200, { suggest: pick });
|
||||
}
|
||||
if (p === '/api/my/tank' && req.method === 'GET') {
|
||||
const s = await auth.fromRequest(req);
|
||||
if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' });
|
||||
|
||||
Reference in New Issue
Block a user