import { escapeHtml } from '../util.js'; import { logoLockup } from '../logo.js'; import { renderCornerRail } from '../cornerRail.js'; import { attachTooltips } from '../tooltip.js'; import { renderTopRail } from '../topRail.js'; import { api, ApiError } from '../api.js'; import { FLAIR_ICONS, CLOSE_ICON } from './icons.js'; // modeKey format is set in app.js: "words-25", "time-30", "quote-". function modeLabel(modeKey) { const [kind, arg] = modeKey.split('-'); if (kind === 'words') return `Words · ${arg}`; if (kind === 'time') return `Time · ${arg}s`; if (kind === 'quote') return arg === 'random' ? 'Random' : arg.charAt(0).toUpperCase() + arg.slice(1); return modeKey; } function formatDate(iso) { const t = Date.parse(iso); return Number.isNaN(t) ? '' : new Date(t).toLocaleDateString(); } // Read-only - no settings, no actions on someone else's data, just what // /api/users/:username/public hands back (the same aggregate shape // /api/stats/me uses for yourself, minus anything private). export function renderPublicProfileScreen(root, { username, onBack, onShowStats, onShowPlaceholder, onShowAccount, onShowLeaderboard, onShowFriends, onShowMultiplayer, onShowStore }) { function bodyMarkup(state) { if (state.status === 'loading') return `
Loading...
`; if (state.status === 'error') return `
${escapeHtml(state.message)}
`; const p = state.profile; return `
Joined ${formatDate(p.joined_at)}

Personal bests

${p.personal_bests.length > 0 ? `
${p.personal_bests.map(b => `
${escapeHtml(modeLabel(b.mode_key))}
${Math.round(b.wpm)} wpm
${formatDate(b.date)}
`).join('')}
` : `
No personal bests recorded yet.
`} `; } root.innerHTML = `
${logoLockup()}

${escapeHtml(username)}

`; root.querySelectorAll('[data-action="menu"]').forEach(el => el.addEventListener('click', onBack)); attachTooltips(root); const cleanupTheme = renderTopRail(root, { onShowAccount, onShowFriends }); const bodyEl = root.querySelector('.public-profile-body'); const headingEl = root.querySelector('.public-profile-heading'); bodyEl.innerHTML = bodyMarkup({ status: 'loading' }); api.get(`/api/users/${encodeURIComponent(username)}/public`) .then(profile => { bodyEl.innerHTML = bodyMarkup({ status: 'ok', profile }); if (profile.flair && FLAIR_ICONS[profile.flair]) { headingEl.innerHTML = `${escapeHtml(username)}${FLAIR_ICONS[profile.flair]}`; // Scoped to headingEl, not the whole root - see the same // note in leaderboardScreen.js's load(). attachTooltips(headingEl); } }) .catch(err => { const message = err instanceof ApiError && err.status === 404 ? `No profile found for "${username}".` : 'Could not load this profile - try again.'; bodyEl.innerHTML = bodyMarkup({ status: 'error', message }); }); const cleanupRail = renderCornerRail(root, { onShowStats, onShowPlaceholder, onShowAccount, onShowLeaderboard, onShowFriends, onShowMultiplayer, onShowStore }); return () => { cleanupTheme(); cleanupRail(); }; }