import { renderHeader } from '../components/header.js'; import { authService, state } from '../state.js'; import { buildIdentityLines, loadRelationsForPair, loadUserProfileCard, } from '../services/user-connections.js'; import { renderUserAvatar } from '../components/avatar-image.js'; export const pageMeta = { id: 'user-profile-view', title: 'Чужой профиль' }; function escapeHtml(text) { return String(text || '') .replaceAll('&', '&') .replaceAll('<', '<') .replaceAll('>', '>') .replaceAll('"', '"') .replaceAll("'", '''); } function genderText(value) { const normalized = String(value || '').trim().toLowerCase(); if (normalized === 'male') return 'Мужской'; if (normalized === 'female') return 'Женский'; return 'Не указан'; } function relationButtonLabel(kind, flags) { if (kind === 'follow') return flags.outFollow ? 'Отписаться' : 'Подписаться'; if (kind === 'friend') return flags.outFriend ? 'Убрать из близких друзей' : 'Добавить в близкие друзья'; return flags.outContact ? 'Убрать из контактов' : 'Добавить в контакты'; } function relationNextState(kind, flags) { if (kind === 'follow') return !flags.outFollow; if (kind === 'friend') return !flags.outFriend; return !flags.outContact; } function relationConfirmLabel(kind) { if (kind === 'follow') return 'подписку'; if (kind === 'friend') return 'статус близкого друга'; return 'контакт'; } function relationStateText(kind, flags) { if (kind === 'follow') { if (flags.outFollow && flags.inFollow) return 'Вы взаимно подписаны.'; if (flags.outFollow) return 'Вы подписаны на этот профиль.'; if (flags.inFollow) return 'Этот профиль подписан на вас.'; return ''; } if (kind === 'friend') { if (flags.outFriend && flags.inFriend) return 'Вы взаимно близкие друзья.'; if (flags.outFriend) return 'Вы считаете этот профиль близким другом.'; if (flags.inFriend) return 'Этот профиль считает вас близким другом.'; return ''; } if (flags.outContact && flags.inContact) return 'Вы обменялись контактами.'; if (flags.outContact) return 'Вы добавили этот профиль в контакты.'; if (flags.inContact) return 'Этот профиль добавил вас в контакты.'; return ''; } function renderIdentity(card) { const lines = buildIdentityLines({ login: card.login, firstName: card.firstName, lastName: card.lastName, }); const row = document.createElement('div'); row.className = 'row'; row.style.gap = '12px'; row.style.alignItems = 'center'; row.append(renderUserAvatar({ login: card.login, firstName: card.firstName, lastName: card.lastName, avatar: card.avatar, size: 'large', className: 'profile-avatar', })); const identityLines = document.createElement('div'); identityLines.className = 'profile-identity-lines'; lines.forEach((line, idx) => { const lineEl = document.createElement('div'); lineEl.className = `profile-identity-line${idx === lines.length - 1 ? ' profile-identity-login' : ''}`; lineEl.textContent = line; identityLines.append(lineEl); }); row.append(identityLines); return row; } function renderReadOnlyBadges(card) { return `
Официальный: ${card.official ? 'Yes' : 'No'} Сияющий: ${card.shine ? 'Yes' : 'No'}
`; } function renderRelations(flags) { const rows = [ { kind: 'follow', text: relationStateText('follow', flags), button: relationButtonLabel('follow', flags) }, { kind: 'friend', text: relationStateText('friend', flags), button: relationButtonLabel('friend', flags) }, { kind: 'contact', text: relationStateText('contact', flags), button: relationButtonLabel('contact', flags) }, ]; return `
${rows.map((row) => `
${escapeHtml(row.text)}
`).join('')}
`; } function renderReadOnlyParams(card) { const rows = [ { label: 'Имя', value: card.firstName }, { label: 'Фамилия', value: card.lastName }, { label: 'Пол', value: genderText(card.gender) }, { label: 'Адрес', value: card.address }, { label: 'Web', value: card.web }, { label: 'Телефон', value: card.phone }, ]; return `
${rows.map((row) => `
${row.label}: ${escapeHtml(String(row.value || '').trim() || 'не заполнено')}
`).join('')}
`; } export function render({ navigate, route }) { const requestedLogin = String(route.params.login || '').trim(); const fromPage = String(route.params.fromPage || 'messages-list').trim() || 'messages-list'; const sessionLogin = String(state.session.login || '').trim(); const screen = document.createElement('section'); screen.className = 'stack'; const status = document.createElement('div'); status.className = 'status-line'; status.textContent = 'Загрузка профиля...'; const body = document.createElement('div'); body.className = 'stack'; screen.append( renderHeader({ title: 'Профиль пользователя', leftAction: { label: '←', onClick: () => navigate(fromPage) }, rightActions: [{ label: 'Обновить', onClick: () => refresh() }], }), status, body, ); let currentCard = null; let currentFlags = null; let isBusy = false; function syncActionButtons() { const followBtn = body.querySelector('[data-relation-action="follow"]'); const friendBtn = body.querySelector('[data-relation-action="friend"]'); const contactBtn = body.querySelector('[data-relation-action="contact"]'); if (!followBtn || !friendBtn || !contactBtn || !currentFlags) return; const isSelf = currentCard && currentCard.login.toLowerCase() === sessionLogin.toLowerCase(); followBtn.textContent = relationButtonLabel('follow', currentFlags); friendBtn.textContent = relationButtonLabel('friend', currentFlags); contactBtn.textContent = relationButtonLabel('contact', currentFlags); followBtn.disabled = Boolean(isSelf); friendBtn.disabled = Boolean(isSelf); contactBtn.disabled = Boolean(isSelf); } async function refresh() { if (!requestedLogin) { status.className = 'status-line is-unavailable'; status.textContent = 'Не передан login пользователя.'; return; } isBusy = true; status.className = 'status-line'; status.textContent = 'Загрузка профиля...'; try { const card = await loadUserProfileCard(requestedLogin); const flags = await loadRelationsForPair({ currentLogin: sessionLogin, targetLogin: card.login, }); currentCard = card; currentFlags = flags; body.innerHTML = ` ${renderReadOnlyBadges(card)} ${renderRelations(flags)} ${renderReadOnlyParams(card)} `; const identityCard = document.createElement('div'); identityCard.className = 'card stack'; identityCard.append(renderIdentity(card)); body.prepend(identityCard); syncActionButtons(); status.className = 'status-line is-available'; status.textContent = 'Профиль обновлён.'; } catch (error) { status.className = 'status-line is-unavailable'; status.textContent = `Ошибка загрузки профиля: ${error.message || 'unknown'}`; window.alert(`Не удалось загрузить профиль: ${error.message || 'unknown'}`); } finally { isBusy = false; } } async function onRelationAction(kind) { if (isBusy || !currentCard || !currentFlags) return; if (!sessionLogin) { window.alert('Для изменения связей нужен активный вход.'); return; } if (!state.session.storagePwdInMemory) { window.alert('Нет storagePwd в памяти сессии. Выполните вход заново.'); return; } const nextEnabled = relationNextState(kind, currentFlags); const confirmed = window.confirm( `Изменить ${relationConfirmLabel(kind)} с пользователем ${currentCard.login}?\n` + 'Будет отправлен AddBlock CONNECTION.', ); if (!confirmed) return; isBusy = true; status.className = 'status-line'; status.textContent = 'Сохранение отношения в блокчейн...'; try { await authService.setUserRelation({ login: sessionLogin, toLogin: currentCard.login, kind, enabled: nextEnabled, storagePwd: state.session.storagePwdInMemory, }); await refresh(); } catch (error) { status.className = 'status-line is-unavailable'; status.textContent = `Ошибка изменения связи: ${error.message || 'unknown'}`; window.alert(`Не удалось изменить связь: ${error.message || 'unknown'}`); isBusy = false; } } body.addEventListener('click', (event) => { const target = event.target; if (!(target instanceof HTMLElement)) return; const actionBtn = target.closest('[data-relation-action]'); const kind = String(actionBtn?.getAttribute('data-relation-action') || ''); if (!kind) return; onRelationAction(kind); }); refresh(); return screen; }