/**
* Profile Page — User settings, identity management, theme
*
* SSR view with inline scripts for interactive elements.
*/
import { renderPage } from '../page-layout.js'
function esc (s) {
return (s || '').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"')
}
/**
* Render the profile/settings page
*
* @param {Object} profile - User profile from /api/user/profile
* @param {Object} whoami - Auth info from /api/whoami
* @param {Object} opts - { pathPrefix }
* @returns {string} Full HTML page
*/
export function renderProfilePage (profile, whoami, opts = {}) {
const pathPrefix = opts.pathPrefix || ''
const username = profile.username || ''
const displayName = profile.name || 'Anonymous'
const pubkey = profile.publicKey || ''
const method = profile.method || whoami.method || 'web-identity'
const isAdmin = whoami.permissions && whoami.permissions.admin
const role = isAdmin ? 'admin' : 'writer'
const currentTheme = profile.theme || 'dark'
const identities = profile.identities || []
let content = '<div class="settings-card">'
// User status card
content += `<div class="profile-header">
<div class="profile-info">
<div class="profile-name">${esc(displayName)}</div>
${username ? `<div class="meta">@${esc(username)}</div>` : '<div class="meta">No username set</div>'}
</div>
<span class="badge badge-${role === 'admin' ? 'success' : 'secondary'}">${esc(role)}</span>
</div>`
// Username section
content += '<div class="settings-section">'
content += '<div class="settings-row"><label>Username</label><div class="settings-value">'
if (username) {
content += `<span>@${esc(username)}</span> <button class="btn btn-sm" id="btn-change-username">Change</button>`
} else {
content += `<span class="meta">Not set</span> <button class="btn btn-sm btn-primary" id="btn-set-username">Set username</button>`
}
content += '</div></div>'
// Display name
content += `<div class="settings-row"><label>Display Name</label><div class="settings-value">
<span id="profile-display-name">${esc(displayName)}</span>
<button class="btn btn-sm" id="btn-edit-name">Edit</button>
</div></div>`
// Theme picker
content += '<div class="settings-row"><label>Theme</label><div class="settings-value"><div class="theme-options">'
for (const theme of ['light', 'dark', 'dark-blue']) {
const active = currentTheme === theme ? ' theme-active' : ''
const label = theme === 'dark-blue' ? 'Dark Blue' : theme.charAt(0).toUpperCase() + theme.slice(1)
content += `<button class="theme-option${active}" data-theme="${theme}">
<div class="theme-swatch swatch-${theme}"></div>${label}
</button>`
}
content += '</div></div></div>'
// Auth method
content += `<div class="settings-row"><label>Auth Method</label><div class="settings-value">
<span>${esc(method)}</span>
</div></div>`
content += '</div>'
// Identity section
content += '<div class="settings-section"><h3>Identity</h3>'
if (identities.length > 0) {
for (const ident of identities) {
content += `<div class="settings-row"><label>${esc(ident.method || 'identity')}</label>
<div class="settings-value"><code class="identity-code">${esc(ident.identityId || ident.publicKey || '')}</code></div>
</div>`
}
}
// Keypair management buttons
content += `<div class="keypair-actions">
<button class="btn btn-sm" id="btn-copy-pubkey">Copy Public Key</button>
<button class="btn btn-sm" id="btn-copy-keypair">Copy Keypair</button>
<button class="btn btn-sm" id="btn-show-keypair">Show Keypair</button>
<button class="btn btn-sm" id="btn-export-keypair">Export JSON</button>
</div>`
content += '<div id="keypair-display" style="display:none"></div>'
content += '</div>'
// Device linking
content += `<div class="settings-section"><h3>Link Another Device</h3>
<p class="meta" style="margin-bottom:8px">Import your keypair on another device to access your account there.</p>
<button class="btn btn-sm" id="btn-import-keypair">Import Keypair</button>
<div id="import-form" style="display:none;margin-top:12px">
<input type="text" id="import-input" placeholder="Paste 132-char keypair string" style="margin-bottom:8px">
<button class="btn btn-sm btn-primary" id="btn-do-import">Import</button>
</div>
</div>`
// Danger zone
content += `<div class="settings-section"><h3 style="color:var(--danger)">Danger Zone</h3>
<button class="btn btn-sm btn-danger" id="btn-logout">Clear Identity & Logout</button>
</div>`
content += '</div>'
const css = `
.settings-card { max-width: 600px; }
.profile-header { display:flex; align-items:center; justify-content:space-between; margin-bottom:24px; padding-bottom:16px; border-bottom:1px solid var(--border); }
.profile-info { flex:1; }
.profile-name { font-size:1.3rem; font-weight:bold; }
.settings-section { margin-bottom:24px; padding-bottom:16px; border-bottom:1px solid var(--border); }
.settings-section h3 { font-size:1rem; margin-bottom:12px; }
.settings-row { display:flex; align-items:center; padding:8px 0; gap:12px; }
.settings-row label { min-width:120px; font-size:0.85rem; color:var(--text-muted); }
.settings-value { flex:1; display:flex; align-items:center; gap:8px; flex-wrap:wrap; }
.btn-sm { padding:4px 12px; font-size:0.8rem; }
.identity-code { font-size:0.7rem; word-break:break-all; color:var(--text-muted); }
.keypair-actions { display:flex; gap:8px; flex-wrap:wrap; margin-top:12px; }
.theme-options { display:flex; gap:8px; }
.theme-option { display:flex; align-items:center; gap:6px; padding:6px 12px; border:1px solid var(--border); border-radius:6px; background:var(--bg-secondary); color:var(--text); cursor:pointer; font-size:0.85rem; }
.theme-option:hover { border-color:var(--accent); }
.theme-active { border-color:var(--accent); background:var(--bg-hover); }
.theme-swatch { width:16px; height:16px; border-radius:50%; border:1px solid var(--border); }
.swatch-light { background:#fff; }
.swatch-dark { background:#0d1117; }
.swatch-dark-blue { background:#1a2332; }
`
const scripts = `
(function () {
var basePath = ${JSON.stringify(pathPrefix)};
// Theme picker
document.querySelectorAll('.theme-option').forEach(function (btn) {
btn.addEventListener('click', function () {
var theme = btn.getAttribute('data-theme');
document.documentElement.setAttribute('data-theme', theme);
localStorage.setItem('hn-theme', theme);
document.querySelectorAll('.theme-option').forEach(function (b) { b.classList.remove('theme-active'); });
btn.classList.add('theme-active');
window.fetch(basePath + '/api/user/profile', {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ theme: theme })
}).catch(function () {});
});
});
// Copy public key
var btnPub = document.getElementById('btn-copy-pubkey');
if (btnPub) btnPub.addEventListener('click', function () {
var pk = window.HnIdentity && window.HnIdentity.getPublicKey();
if (pk) { navigator.clipboard.writeText(pk); btnPub.textContent = 'Copied!'; setTimeout(function () { btnPub.textContent = 'Copy Public Key'; }, 2000); }
});
// Copy keypair
var btnKp = document.getElementById('btn-copy-keypair');
if (btnKp) btnKp.addEventListener('click', function () {
var kp = window.HnIdentity && window.HnIdentity.getKeypair();
if (kp) { navigator.clipboard.writeText(kp); btnKp.textContent = 'Copied!'; setTimeout(function () { btnKp.textContent = 'Copy Keypair'; }, 2000); }
});
// Show keypair
var btnShow = document.getElementById('btn-show-keypair');
var kpDisplay = document.getElementById('keypair-display');
if (btnShow && kpDisplay) btnShow.addEventListener('click', function () {
if (kpDisplay.style.display === 'none') {
var kp = window.HnIdentity && window.HnIdentity.getKeypair();
var pk = window.HnIdentity && window.HnIdentity.getPublicKey();
kpDisplay.innerHTML = '<div style="background:var(--bg-secondary);padding:12px;border-radius:6px;font-family:monospace;font-size:0.7rem;word-break:break-all">'
+ '<div><strong>Public Key:</strong></div><div style="margin:4px 0">' + (pk || 'none') + '</div>'
+ '<div style="margin-top:8px"><strong style="color:var(--danger)">Full Keypair (KEEP SECRET):</strong></div>'
+ '<div style="margin:4px 0">' + (kp || 'none') + '</div></div>';
kpDisplay.style.display = 'block';
btnShow.textContent = 'Hide Keypair';
} else {
kpDisplay.style.display = 'none';
btnShow.textContent = 'Show Keypair';
}
});
// Export keypair
var btnExport = document.getElementById('btn-export-keypair');
if (btnExport) btnExport.addEventListener('click', function () {
var data = window.HnIdentity && window.HnIdentity.exportKeypair();
if (!data) return;
var blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
var a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = 'hypernote-identity.json';
a.click();
});
// Import keypair
var btnImport = document.getElementById('btn-import-keypair');
var importForm = document.getElementById('import-form');
if (btnImport && importForm) btnImport.addEventListener('click', function () {
importForm.style.display = importForm.style.display === 'none' ? 'block' : 'none';
});
var btnDoImport = document.getElementById('btn-do-import');
if (btnDoImport) btnDoImport.addEventListener('click', function () {
var val = document.getElementById('import-input').value.trim();
if (val.length !== 132) { alert('Keypair must be 132 characters'); return; }
try {
window.HnIdentity.importKeypair(val);
window.location.reload();
} catch (e) { alert('Invalid keypair: ' + e.message); }
});
// Username set/change
function usernameAction () {
var current = ${JSON.stringify(username)};
var newName = prompt(current ? 'Change username (current: @' + current + '):' : 'Set your username:', current || '');
if (newName === null) return;
newName = newName.trim().toLowerCase();
if (!newName) return;
window.fetch(basePath + '/api/user/username', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username: newName })
}).then(function (r) {
if (r.ok) window.location.reload();
else r.json().then(function (d) { alert(d.error || 'Failed'); });
}).catch(function (e) { alert('Error: ' + e.message); });
}
var btnSetUser = document.getElementById('btn-set-username');
var btnChangeUser = document.getElementById('btn-change-username');
if (btnSetUser) btnSetUser.addEventListener('click', usernameAction);
if (btnChangeUser) btnChangeUser.addEventListener('click', usernameAction);
// Edit name
var btnEditName = document.getElementById('btn-edit-name');
if (btnEditName) btnEditName.addEventListener('click', function () {
var newName = prompt('Display name:', ${JSON.stringify(displayName)});
if (newName === null) return;
window.fetch(basePath + '/api/user/profile', {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: newName.trim() })
}).then(function (r) { if (r.ok) window.location.reload(); })
.catch(function () {});
});
// Logout
var btnLogout = document.getElementById('btn-logout');
if (btnLogout) btnLogout.addEventListener('click', function () {
if (confirm('Clear your identity? You will lose access unless you have exported your keypair.')) {
window.HnIdentity.clear();
window.location.href = basePath + '/';
}
});
})();
`
return renderPage('Profile', content, { pathPrefix, activePage: 'profile', css, scripts })
}