From 0c2754d2d4cb1905d5e324f56fbc64e4afae526f Mon Sep 17 00:00:00 2001 From: Christophe Besson Date: Mon, 31 Aug 2026 01:06:06 +0200 Subject: refactor(ui): extract Explore, Login, Register and CreateGroup from app.js MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ExplorePage → explore-page.js (static import), LoginPage/RegisterPage/ FirstRunPage → auth-page.js (static import, LoginPage receives onLogin as a prop), CreateGroupPage/wizard → create-group-page.js (lazy-loaded via dynamic import(), same pattern as AdminPage/NodePage). app.js goes from 1803 to 914 lines. webapp.py _ASSETS extended with the three new files and the previously missing extracted pages. Test fixtures updated to follow the moved components. Co-Authored-By: Claude Opus 4.6 --- .../src/meshbay_hub/static/auth-page.js | 201 +++++++++++++++++++++ 1 file changed, 201 insertions(+) create mode 100644 packages/meshbay-hub/src/meshbay_hub/static/auth-page.js (limited to 'packages/meshbay-hub/src/meshbay_hub/static/auth-page.js') diff --git a/packages/meshbay-hub/src/meshbay_hub/static/auth-page.js b/packages/meshbay-hub/src/meshbay_hub/static/auth-page.js new file mode 100644 index 0000000..1cca56f --- /dev/null +++ b/packages/meshbay-hub/src/meshbay_hub/static/auth-page.js @@ -0,0 +1,201 @@ +import { + html, useState, +} from './vendor/htm-preact.js'; +import { t } from './i18n.js'; +import { hubFetch, navigate } from './hub-client.js'; +import * as platform from './platform.js'; + +const PASSWORD_MIN_BITS = 60; +const PASSWORD_MIN_LEN = 12; + +function passwordBits(pw) { + if (!pw) return 0; + let pool = 0; + if (/[a-z]/.test(pw)) pool += 26; + if (/[A-Z]/.test(pw)) pool += 26; + if (/[0-9]/.test(pw)) pool += 10; + if (/[^A-Za-z0-9]/.test(pw)) pool += 32; + let bits = pw.length * Math.log2(pool || 1); + + const unique = new Set(pw).size; + if (unique < pw.length / 2) bits *= 0.6; + if (/^[0-9]+$/.test(pw)) bits *= 0.5; + if (/(password|motdepasse|azerty|qwerty|123456|meshbay)/i.test(pw)) bits *= 0.3; + return Math.round(bits); +} + +export function FirstRunPage({ onSet }) { + const [url, setUrl] = useState(''); + const [error, setError] = useState(''); + const [busy, setBusy] = useState(false); + + const submit = async (e) => { + e.preventDefault(); + setError(''); + setBusy(true); + try { + await window.meshbay.setHubBase(url.trim()); + onSet(); + } catch (err) { + setError(platform.bridgeMessage(err)); + setBusy(false); + } + }; + + return html` +
+ +
+ `; +} + +export function LoginPage({ onLogin }) { + const [username, setUsername] = useState(''); + const [password, setPassword] = useState(''); + const [error, setError] = useState(''); + const [loading, setLoading] = useState(false); + + const onSubmit = async (e) => { + e.preventDefault(); + if (!username || !password) return; + setError(''); + setLoading(true); + try { + await onLogin(username, password); + navigate('/'); + } catch (err) { + setError(err.message); + } finally { + setLoading(false); + } + }; + + return html` +
+ +
+ `; +} + +export function RegisterPage() { + const [username, setUsername] = useState(''); + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + const [confirm, setConfirm] = useState(''); + const [error, setError] = useState(''); + const [success, setSuccess] = useState(false); + const [loading, setLoading] = useState(false); + + const onSubmit = async (e) => { + e.preventDefault(); + if (password !== confirm) { setError(t('register.err_mismatch')); return; } + if (password.length < PASSWORD_MIN_LEN) { + setError(t('register.err_min_len', { n: PASSWORD_MIN_LEN })); return; + } + if (passwordBits(password) < PASSWORD_MIN_BITS) { + setError(t('register.err_too_weak')); return; + } + setError(''); + setLoading(true); + try { + if (window.MeshBayKeys) { + await window.MeshBayKeys.registerUser(username, email, password); + } else { + await hubFetch('/v1/users/register', { + method: 'POST', + body: { username, email, password, pk_user_ed25519: '', pk_user_x25519: '' }, + }); + } + setSuccess(true); + } catch (err) { + setError(err.message); + } finally { + setLoading(false); + } + }; + + if (success) { + return html` +
+ +
+ `; + } + + return html` +
+ +
+ `; +} -- cgit v1.2.3