summaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/src/meshbay_hub/static/profile-page.js
diff options
context:
space:
mode:
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.js71
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>