import { profile } from '../mock-data.js'; import { authService, state } from '../state.js'; import { loadProfileSnapshot, } from '../services/user-profile-params.js'; import { renderUserAvatar } from '../components/avatar-image.js'; import { createOverflowDots } from '../components/overflow-dots.js'; import { createDropdownMenu } from '../components/dropdown-menu.js'; import { userDisplayName } from '../services/user-display.js'; export const pageMeta = { id: 'profile-view', title: 'Профиль' }; function escapeHtml(text) { return String(text || '') .replaceAll('&', '&') .replaceAll('<', '<') .replaceAll('>', '>') .replaceAll('"', '"') .replaceAll("'", '''); } function fieldMap(snapshot) { const out = {}; (Array.isArray(snapshot?.fields) ? snapshot.fields : []).forEach((field) => { out[String(field?.key || '').trim()] = String(field?.value || '').trim(); }); return out; } function openTextModal(title, text) { const root = document.getElementById('modal-root'); if (!root) return; root.innerHTML = ` `; const close = () => { root.innerHTML = ''; }; root.querySelector('#profile-text-close')?.addEventListener('click', close); root.querySelector('#profile-text-modal')?.addEventListener('click', (event) => { if (event.target?.id === 'profile-text-modal') close(); }); } function statusBadges(accountRole, shineStatus) { const role = accountRole === 'primary' ? 'Основной аккаунт' : accountRole === 'non_voting' ? 'Голос не учитывать' : ''; const shine = shineStatus === 'shining' ? 'Сияющий' : ''; return `
${role ? `${escapeHtml(role)}` : ''} ${shine ? 'Сияющий' : ''} ${shineStatus === 'not_interested' ? 'Сияние неинтересно' : ''}
`; } function statsRows(stats = {}) { return [ ['friends', 'Друзья', stats.friendsCount], ['close_friends', 'Близкие друзья', stats.closeFriendsCount], ['primary_received', 'Подтвердили основной аккаунт', stats.primaryReceivedCount], ['primary_given', 'Подтверждённые аккаунты', stats.primaryGivenCount], ['shine_received', 'Считают сияющим', stats.shineReceivedCount], ['shine_given', 'Подтверждённые сияющие', stats.shineGivenCount], ['channels_following', 'Подписки на каналы', stats.followingChannelsCount], ['channels_owned', 'Каналы', stats.ownedPublicChannelsCount], ]; } export function render({ navigate, chrome }) { const login = String(state.session.login || profile.login || '').trim(); const screen = document.createElement('section'); screen.className = 'stack profile-screen'; const topbar = document.createElement('header'); topbar.className = 'page-header app-topbar-shell app-topbar-shell--profile'; topbar.innerHTML = `

Профиль

`; const menuButton = topbar.querySelector('.profile-head-menu-btn'); menuButton?.append(createOverflowDots()); const profileMenu = createDropdownMenu({ anchorEl: menuButton, className: 'profile-head-menu', minWidth: 230, items: [ { label: 'Редактировать профиль', iconSrc: '/assets/profile-icon-profile.svg', action: () => navigate('profile-edit-view') }, { label: 'Кошелёк', iconSrc: '/assets/profile-icon-wallet.svg', action: () => navigate('wallet-view') }, { label: 'Настройки', iconSrc: '/assets/profile-icon-settings.svg', action: () => navigate('settings-view') }, { label: 'Сменить профиль', action: () => navigate('profiles-view') }, ], }); chrome?.setTopbar(topbar); const status = document.createElement('div'); status.className = 'status-line'; status.textContent = 'Загрузка профиля...'; const body = document.createElement('div'); body.className = 'stack'; screen.append(status, body); let current = null; function renderProfile() { if (!current) return; const { snapshot, user } = current; const fields = fieldMap(snapshot); const firstName = fields.first_name || ''; const lastName = fields.last_name || ''; const displayName = userDisplayName({ login, firstName, lastName }); const avatar = snapshot?.avatar?.txId ? { ar: String(snapshot.avatar.txId).trim(), sha256Hex: String(snapshot?.avatar?.sha256Hex || '').trim().toLowerCase() } : null; const stats = { ownedPublicChannelsCount: Number(user?.ownedPublicChannelsCount || 0), followingChannelsCount: Number(user?.followingChannelsCount || 0), friendsCount: Number(user?.friendsCount || 0), closeFriendsCount: Number(user?.closeFriendsCount || 0), primaryReceivedCount: Number(user?.primaryConfirmationsReceivedCount || 0), primaryGivenCount: Number(user?.primaryConfirmationsGivenCount || 0), shineReceivedCount: Number(user?.shineConfirmationsReceivedCount || 0), shineGivenCount: Number(user?.shineConfirmationsGivenCount || 0), }; body.innerHTML = ''; const identity = document.createElement('div'); identity.className = 'card row'; identity.style.gap = '12px'; identity.style.alignItems = 'center'; identity.append(renderUserAvatar({ login, firstName, lastName, avatar, size: 'xl', className: 'profile-avatar' })); const identityText = document.createElement('div'); identityText.innerHTML = `
${escapeHtml(displayName)}
${escapeHtml(login)}
`; identity.append(identityText); body.append(identity); const badges = document.createElement('div'); badges.innerHTML = statusBadges(String(snapshot?.accountRole || '').trim().toLowerCase(), String(snapshot?.shineStatus || '').trim().toLowerCase()); body.append(...badges.children); if (fields.about) { const about = document.createElement('div'); about.className = 'card profile-about'; about.style.whiteSpace = 'pre-wrap'; about.textContent = fields.about; body.append(about); } const statsGrid = document.createElement('div'); statsGrid.className = 'profile-stats-grid'; statsRows(stats).forEach(([kind, label, value]) => { const button = document.createElement('button'); button.type = 'button'; button.className = 'card profile-stat-card'; button.dataset.profileList = kind; button.innerHTML = `${Number(value || 0)}${escapeHtml(label)}`; statsGrid.append(button); }); body.append(statsGrid); const detailRow = document.createElement('div'); detailRow.className = 'row wrap-row'; detailRow.innerHTML = ''; body.append(detailRow); } body.addEventListener('click', (event) => { if (!current) return; const el = event.target.closest('[data-profile-list],[data-profile-detail]'); if (!el) return; const kind = el.dataset.profileList; if (kind) { navigate(`SHiNE/${encodeURIComponent(login)}/list/${encodeURIComponent(kind)}`); return; } const fields = fieldMap(current.snapshot); if (el.dataset.profileDetail === 'contacts') { openTextModal('Контакты', [ fields.web ? `Links: ${fields.web}` : '', fields.phone ? `Телефон: ${fields.phone}` : '', fields.address ? `Адрес: ${fields.address}` : '', ].filter(Boolean).join('\n') || 'Не заполнено'); } if (el.dataset.profileDetail === 'spiritual') openTextModal('Духовный путь', fields.spiritual_path); }); async function refresh() { if (state.session.isLocalDemo) { status.textContent = 'Локальный тестовый режим.'; return; } const [snapshot, user] = await Promise.all([loadProfileSnapshot(login), authService.getUser(login)]); current = { snapshot, user }; renderProfile(); status.textContent = ''; } refresh().catch((error) => { status.className = 'status-line is-unavailable'; status.textContent = `Ошибка: ${error?.message || 'unknown'}`; }); screen.cleanup = () => profileMenu.destroy(); return screen; }