diff options
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/static/profile-page.js')
| -rw-r--r-- | packages/meshbay-hub/src/meshbay_hub/static/profile-page.js | 71 |
1 files changed, 60 insertions, 11 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/profile-page.js b/packages/meshbay-hub/src/meshbay_hub/static/profile-page.js index 3d3e578..ce22116 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/profile-page.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/profile-page.js @@ -15,6 +15,9 @@ export function ProfilePage({ user, onLogout }) { const [emailEditing, setEmailEditing] = useState(false); const [emailSaving, setEmailSaving] = useState(false); const [emailStatus, setEmailStatus] = useState(''); + const [emailVerifyPending, setEmailVerifyPending] = useState(false); + const [emailCode, setEmailCode] = useState(''); + const [emailVerifying, setEmailVerifying] = useState(false); const [pinCount, setPinCount] = useState( () => (window.MeshBayTransport?.pinnedNodeCount?.() ?? 0)); const [delOpen, setDelOpen] = useState(false); @@ -63,13 +66,18 @@ export function ProfilePage({ user, onLogout }) { setEmailSaving(true); setEmailStatus(''); try { - await hubFetch('/v1/users/me', { + const resp = await hubFetch('/v1/users/me', { method: 'PATCH', token: user.token, body: { email: val }, }); - setEmail(val); - setEmailEditing(false); - setEmailStatus(t('settings.email_saved')); - setTimeout(() => setEmailStatus(''), 3000); + if (resp.email_verification_required) { + setEmailVerifyPending(true); + setEmailStatus(t('settings.email_code_sent')); + } else { + setEmail(val); + setEmailEditing(false); + setEmailStatus(t('settings.email_saved')); + setTimeout(() => setEmailStatus(''), 3000); + } } catch (e) { setEmailStatus(e.message); } finally { @@ -77,6 +85,27 @@ export function ProfilePage({ user, onLogout }) { } }, [emailDraft, email, user.token]); + const verifyEmailChange = useCallback(async () => { + if (!emailCode.trim()) return; + setEmailVerifying(true); + setEmailStatus(''); + try { + const resp = await hubFetch('/v1/users/verify-email-change', { + method: 'POST', token: user.token, body: { code: emailCode.trim() }, + }); + setEmail(resp.email || emailDraft.trim()); + setEmailEditing(false); + setEmailVerifyPending(false); + setEmailCode(''); + setEmailStatus(t('settings.email_verified')); + setTimeout(() => setEmailStatus(''), 3000); + } catch (e) { + setEmailStatus(e.message); + } finally { + setEmailVerifying(false); + } + }, [emailCode, emailDraft, user.token]); + const submitNodeKey = useCallback(async () => { const key = nodeKey.trim(); if (!key) return; @@ -110,16 +139,19 @@ export function ProfilePage({ user, onLogout }) { <div class="settings-row"> <span class="settings-label">${t('settings.email')}</span> ${emailEditing - ? html`<span style="display:flex;gap:8px;align-items:center"> + ? html`<span style="display:flex;gap:8px;align-items:center;flex-wrap:wrap"> <input type="email" value=${emailDraft} onInput=${e => setEmailDraft(e.target.value)} onKeyDown=${e => e.key === 'Enter' && saveEmail()} + disabled=${emailVerifyPending} style="font-size:0.9em;padding:4px 8px;border:1px solid var(--border);border-radius:4px" /> - <button class="admin-btn" onClick=${saveEmail} - disabled=${emailSaving}>${t('settings.email_save')}</button> - <button class="btn-secondary" onClick=${() => { - setEmailEditing(false); setEmailDraft(email); - }}>${t('settings.cancel')}</button> + ${!emailVerifyPending && html` + <button class="admin-btn" onClick=${saveEmail} + disabled=${emailSaving}>${t('settings.email_save')}</button> + <button class="btn-secondary" onClick=${() => { + setEmailEditing(false); setEmailDraft(email); + }}>${t('settings.cancel')}</button> + `} </span>` : html`<span style="display:flex;gap:8px;align-items:center"> <span class="settings-value">${email || '—'}</span> @@ -128,6 +160,23 @@ export function ProfilePage({ user, onLogout }) { </span>` } </div> + ${emailVerifyPending && html` + <div style="display:flex;gap:8px;align-items:center;margin-top:8px"> + <input type="text" placeholder="${t('settings.email_code_placeholder')}" + value=${emailCode} onInput=${e => setEmailCode(e.target.value)} + onKeyDown=${e => e.key === 'Enter' && verifyEmailChange()} + inputmode="numeric" maxlength="6" + style="width:120px;text-align:center;font-size:1.1em;letter-spacing:0.2em" /> + <button class="admin-btn" onClick=${verifyEmailChange} + disabled=${emailVerifying}> + ${emailVerifying ? t('settings.email_verifying') : t('settings.email_verify_btn')} + </button> + <button class="btn-secondary" onClick=${() => { + setEmailVerifyPending(false); setEmailEditing(false); + setEmailDraft(email); setEmailCode(''); setEmailStatus(''); + }}>${t('settings.cancel')}</button> + </div> + `} ${emailStatus && html`<p class="settings-hint" style="margin-top:4px">${emailStatus}</p>`} </div> |