SHiNE-server/shine-UI/js/pages/user-profile-view.js

286 lines
10 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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';
import { navigateBack } from '../router.js';
export const pageMeta = { id: 'user', title: 'Чужой профиль' };
function escapeHtml(text) {
return String(text || '')
.replaceAll('&', '&')
.replaceAll('<', '&lt;')
.replaceAll('>', '&gt;')
.replaceAll('"', '&quot;')
.replaceAll("'", '&#39;');
}
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 `
<div class="row wrap-row">
<span class="badge ${card.official ? 'is-yes-official' : 'is-no'}">Официальный: ${card.official ? 'Yes' : 'No'}</span>
<span class="badge ${card.shine ? 'is-yes-shine' : 'is-no'}">Сияющий: ${card.shine ? 'Yes' : 'No'}</span>
</div>
`;
}
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 `
<div class="card stack user-relations-list">
${rows.map((row) => `
<div class="user-rel-row ${row.text ? '' : 'is-empty'}">
<span class="user-rel-text">${escapeHtml(row.text)}</span>
<button class="ghost-btn user-rel-action" type="button" data-relation-action="${row.kind}">${escapeHtml(row.button)}</button>
</div>
`).join('')}
</div>
`;
}
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 `
<div class="card stack profile-param-list">
${rows.map((row) => `
<div class="card profile-param-item row">
<div class="profile-param-value"><b>${row.label}</b>: ${escapeHtml(String(row.value || '').trim() || 'не заполнено')}</div>
</div>
`).join('')}
</div>
`;
}
export function render({ navigate, route }) {
const requestedLogin = String(route.params.login || '').trim();
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: () => navigateBack() },
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;
}