import { api, toast } from '/static/js/api.js'; const form = document.getElementById('profile-form'); const errorBox = document.getElementById('error'); const okBox = document.getElementById('ok'); const extraRows = document.getElementById('extra-rows'); let version = parseInt(form.dataset.version, 10); function addExtraRow(key, value) { const row = document.createElement('div'); row.className = 'kv-row'; const k = document.createElement('input'); k.type = 'text'; k.placeholder = 'Field name'; k.value = key || ''; k.setAttribute('aria-label', 'Field name'); const v = document.createElement('input'); v.type = 'text'; v.placeholder = 'Value'; v.value = value || ''; v.setAttribute('aria-label', 'Field value'); const rm = document.createElement('button'); rm.type = 'button'; rm.className = 'btn small'; rm.textContent = '×'; rm.setAttribute('aria-label', 'Remove field'); rm.addEventListener('click', () => row.remove()); row.append(k, v, rm); extraRows.appendChild(row); } async function loadExtra() { const me = await api('GET', '/api/v1/auth/me'); version = me.user.version; const extra = me.user.extra || {}; Object.keys(extra).forEach((k) => addExtraRow(k, String(extra[k]))); } loadExtra().catch((e) => { errorBox.textContent = e.message; }); document.getElementById('extra-add').addEventListener('click', () => addExtraRow('', '')); form.addEventListener('submit', async (e) => { e.preventDefault(); errorBox.textContent = ''; okBox.textContent = ''; const extra = {}; extraRows.querySelectorAll('.kv-row').forEach((row) => { const [k, v] = row.querySelectorAll('input'); if (k.value.trim()) extra[k.value.trim()] = v.value; }); const links = document.getElementById('p-links').value .split('\n').map((s) => s.trim()).filter(Boolean); try { const res = await api('PATCH', '/api/v1/profile', { version, name: document.getElementById('p-name').value.trim(), bio: document.getElementById('p-bio').value, contact: { phone: document.getElementById('p-phone').value.trim(), location: document.getElementById('p-location').value.trim(), links, }, extra, settings: { theme: document.getElementById('p-theme').value }, }); version = res.user.version; const theme = res.user.settings.theme; document.documentElement.dataset.theme = theme; try { localStorage.setItem('theme', theme); } catch (err) { /* ignore */ } okBox.textContent = 'Profile saved.'; } catch (err) { if (err.status === 409) { errorBox.textContent = 'This profile changed elsewhere — reload the page and try again.'; } else { errorBox.textContent = err.message; } } }); document.getElementById('avatar-file').addEventListener('change', async (e) => { const file = e.target.files[0]; if (!file) return; const fd = new FormData(); fd.append('file', file); try { const res = await api('POST', '/api/v1/profile/avatar', fd); const img = document.createElement('img'); img.className = 'avatar large'; img.alt = 'Current avatar'; img.src = '/files/' + res.fileId + '?v=' + Date.now(); const preview = document.getElementById('avatar-preview'); preview.replaceChildren(img); toast('Avatar updated.', 'ok'); } catch (err) { toast('Avatar upload failed: ' + err.message, 'err'); } }); document.getElementById('logout-all').addEventListener('click', async () => { try { await api('POST', '/api/v1/auth/logout-all', {}); } catch (e) { /* session is gone either way */ } window.location.href = '/login'; });