From 1c07ae23f3694b972e307c50c9c09f1c429f2ea2 Mon Sep 17 00:00:00 2001 From: Christophe Besson Date: Sat, 26 Sep 2026 10:26:51 +0200 Subject: feat(hub): default email opt-outs and match Register to Login styling - Invitation-by-email and recovery-key-by-email boxes now start unchecked; mailing a code/key is opt-in. The invite choice still remembers itself per account once set. - Align the two invite-email checkboxes with their label (center, not flex-start). - Register (and its verify/recovery/done steps) now sits on the same dark gradient backdrop and frosted card as Login, via a shared AuthShell. - Make the gradient's top-left corner very slightly less bright (#86a3c4 -> #809cbc), on both auth pages. Co-Authored-By: Claude Opus 4.8 --- .../src/meshbay_hub/static/auth-page.js | 37 ++++++++++++++++------ .../src/meshbay_hub/static/group-settings.js | 9 +++--- .../meshbay-hub/src/meshbay_hub/static/style.css | 2 +- .../meshbay-hub/tests/test_invite_email_choice.py | 5 +-- 4 files changed, 37 insertions(+), 16 deletions(-) (limited to 'packages/meshbay-hub') diff --git a/packages/meshbay-hub/src/meshbay_hub/static/auth-page.js b/packages/meshbay-hub/src/meshbay_hub/static/auth-page.js index 2164eae..09c4acf 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/auth-page.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/auth-page.js @@ -344,6 +344,23 @@ function WelcomePitch() { `; } +// The sign-in page's dark gradient backdrop and frosted card, without the +// pitch beside it — so Register (and its verify/recovery/done steps) sits on +// the same background and reads in the same dark theme as Login. A lone +// `.welcome-side` is centred by `.welcome`'s `justify-content`. +function AuthShell({ children }) { + return html` +
+ +
+
+ ${children} +
+
+
+ `; +} + export function RegisterPage() { const [username, setUsername] = useState(''); const [email, setEmail] = useState(''); @@ -358,7 +375,9 @@ export function RegisterPage() { const [recoveryMnemonic, setRecoveryMnemonic] = useState(''); const [recoverySaved, setRecoverySaved] = useState(false); const [recoveryCopied, setRecoveryCopied] = useState(false); - const [emailRecovery, setEmailRecovery] = useState(true); + // Off by default: mailing the recovery key is opt-in — the key is shown on + // screen to save, and sending a copy is the user's own choice to make. + const [emailRecovery, setEmailRecovery] = useState(false); const captcha = useCaptcha(); const onSubmit = async (e) => { @@ -457,7 +476,7 @@ export function RegisterPage() { if (phase === 'done') { return html` -
+ <${AuthShell}> -
+ `; } @@ -480,7 +499,7 @@ export function RegisterPage() { } catch { /* clipboard blocked — the text is on screen to copy by hand */ } }; return html` -
+ <${AuthShell}> -
+ `; } if (phase === 'verify') { return html` -
+ <${AuthShell}>
- + `; } return html` -
+ <${AuthShell}>
- + `; } diff --git a/packages/meshbay-hub/src/meshbay_hub/static/group-settings.js b/packages/meshbay-hub/src/meshbay_hub/static/group-settings.js index a510e63..fba8a0e 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/group-settings.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/group-settings.js @@ -417,7 +417,8 @@ function GroupSettingsPanel({ groupId, group, token, transportRef, gekRef, // Whether the hub mails the invitation. Checked, the hub is handed the code // to write it into the mail — so it is the inviter's choice, remembered per // account (docs/MESHBAY_DESIGN.md §3.4). Unchecked, the hub never sees it. - const [inviteByEmail, setInviteByEmail] = useState(true); + // Off by default: mailing the code is opt-in, not something to do unasked. + const [inviteByEmail, setInviteByEmail] = useState(false); const [error, setError] = useState(''); // Node loopback state (Electron-only) @@ -811,7 +812,7 @@ function GroupSettingsPanel({ groupId, group, token, transportRef, gekRef, useEffect(() => { hubFetch('/v1/users/me/preferences', { token }) - .then(prefs => setInviteByEmail(prefs[INVITE_EMAIL_PREF] !== 'false')) + .then(prefs => setInviteByEmail(prefs[INVITE_EMAIL_PREF] === 'true')) .catch(() => {}); }, [token]); @@ -1059,7 +1060,7 @@ function GroupSettingsPanel({ groupId, group, token, transportRef, gekRef, ${inviting ? '...' : t('members.invite_btn')} -