// Public profile view (/users/{id}): full bio, contact, links and custom // details from the user's profile card. import { api } from '/static/js/api.js'; const root = document.getElementById('profile-root'); const errorBox = document.getElementById('error'); const uid = root.dataset.userId; const HIDDEN_EXTRA = new Set(['ticketingIdentities', 'savedBoardFilters']); const esc = (s) => String(s ?? '').replace(/[&<>"']/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''', }[c])); // only allow safe link schemes; bare domains get https://, others are blocked export function safeUrl(u) { const s = String(u ?? '').trim(); if (/^(https?:\/\/|mailto:)/i.test(s)) return s; if (/^[a-z][a-z0-9+.-]*:/i.test(s)) return '#'; return s ? 'https://' + s : '#'; } async function load() { try { const res = await api('GET', `/api/v1/users/${uid}/card`); const c = res.card; const roles = ['admin', 'consultant', 'developer'].filter((r) => c.roles && c.roles[r]); const links = ((c.contact && c.contact.links) || []).filter(Boolean); const extra = Object.entries(c.extra || {}) .filter(([k, v]) => !HIDDEN_EXTRA.has(k) && v !== null && typeof v !== 'object'); const hasContact = (c.contact && (c.contact.phone || c.contact.location)) || links.length; root.innerHTML = `
${roles.map((r) => `${r}`).join(' ')}
${esc(c.bio)}
` : ''} ${hasContact ? '📍 ${esc(c.contact.location)}
` : ''} ${c.contact && c.contact.phone ? `📞 ${esc(c.contact.phone)}
` : ''} ${links.length ? `${esc(k)}: ${esc(String(v))}
`).join('')}` : ''}