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

229 lines
9.6 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 { profile } from '../mock-data.js';
import { state } from '../state.js';
import {
PROFILE_GENDER_FEMALE,
PROFILE_GENDER_MALE,
loadProfileSnapshot,
} from '../services/user-profile-params.js';
import { buildIdentityLines } from '../services/user-connections.js';
import { renderUserAvatar } from '../components/avatar-image.js';
import { makeProfileLinksRoute } from '../services/shine-routes.js';
export const pageMeta = { id: 'profile-view', title: 'Профиль' };
function toggleText(enabled) {
return enabled ? 'Yes' : 'No';
}
function genderLabel(value) {
if (value === PROFILE_GENDER_MALE) return 'Мужской';
if (value === PROFILE_GENDER_FEMALE) return 'Женский';
return 'Не указан';
}
function escapeHtml(text) {
return String(text || '')
.replaceAll('&', '&')
.replaceAll('<', '&lt;')
.replaceAll('>', '&gt;')
.replaceAll('"', '&quot;')
.replaceAll("'", '&#39;');
}
function openProfileInfoModal({ title, text }) {
const root = document.getElementById('modal-root');
if (!root) return;
root.innerHTML = `
<div class="modal" id="profile-info-modal">
<div class="modal-card stack">
<h3 class="modal-title">${escapeHtml(title)}</h3>
<p class="meta-muted" style="white-space: pre-wrap; line-height: 1.45;">${escapeHtml(text)}</p>
<button class="secondary-btn" type="button" id="profile-info-close">Закрыть</button>
</div>
</div>
`;
const close = () => { root.innerHTML = ''; };
root.querySelector('#profile-info-close')?.addEventListener('click', close);
root.querySelector('#profile-info-modal')?.addEventListener('click', (event) => {
if (event.target?.id === 'profile-info-modal') close();
});
}
function officialInfoText() {
return 'Можно создавать несколько альтернативных или анонимных каналов. '
+ 'Но для корректного учёта голосов на одного реального человека используется только один официальный канал.';
}
function shineInfoText() {
return 'Сияющие — это те, от кого идёт внутреннее сияние на тонком плане.\n\n'
+ 'Пять принципов сияющих:\n'
+ '1) сияющие не обманывают;\n'
+ '2) сияющие чувствуют, что человек — это не только физическое тело, а нечто большее;\n'
+ '3) сияющие развиваются и в духовной, и в материальной плоскости;\n'
+ '4) у сияющих есть близкие друзья, с которыми им по-настоящему хорошо;\n'
+ '5) сияющие заботятся о мире: о людях, гармонии и общем благе.';
}
export function render({ navigate }) {
const login = state.session.login || profile.login;
const screen = document.createElement('section');
screen.className = 'stack profile-screen';
const topActions = document.createElement('div');
topActions.className = 'profile-top-actions';
topActions.innerHTML = `
<button class="ghost-btn profile-top-action-btn" type="button" data-top-action="edit">Редактировать профиль</button>
<button class="ghost-btn profile-top-action-btn" type="button" data-top-action="settings">Настройки</button>
`;
topActions.querySelector('[data-top-action="edit"]')?.addEventListener('click', () => navigate('profile-edit-view'));
topActions.querySelector('[data-top-action="settings"]')?.addEventListener('click', () => navigate('settings-view'));
screen.append(topActions);
const bottomActions = document.createElement('div');
bottomActions.className = 'profile-bottom-actions';
bottomActions.innerHTML = `
<button class="ghost-btn profile-top-action-btn" type="button" data-bottom-action="wallet">Кошелёк</button>
<button class="ghost-btn profile-top-action-btn profile-links-two-line" type="button" data-bottom-action="links">Показать\nсвязи</button>
`;
bottomActions.querySelector('[data-bottom-action="wallet"]')?.addEventListener('click', () => navigate('wallet-view'));
bottomActions.querySelector('[data-bottom-action="links"]')?.addEventListener('click', () => navigate(makeProfileLinksRoute(login)));
screen.append(bottomActions);
const card = document.createElement('div');
card.className = 'card stack profile-main-card';
const topRow = document.createElement('div');
topRow.className = 'row';
topRow.innerHTML = `
<div class="row" style="gap:12px; align-items:center;">
<div data-profile-avatar-slot="true"></div>
<div class="profile-identity-lines" data-profile-identity="true">
<div class="profile-identity-line profile-identity-login">${String(login || '').trim() || 'unknown'}</div>
</div>
</div>
`;
const badgesRow = document.createElement('div');
badgesRow.className = 'row';
badgesRow.innerHTML = `
<button class="badge profile-toggle-btn is-no" type="button" data-toggle="official">Официальный: No</button>
<button class="badge profile-toggle-btn is-no" type="button" data-toggle="shine">Сияющий: No</button>
`;
const listWrap = document.createElement('div');
listWrap.className = 'stack profile-param-list';
const officialBtn = badgesRow.querySelector('[data-toggle="official"]');
const shineBtn = badgesRow.querySelector('[data-toggle="shine"]');
const identityEl = topRow.querySelector('[data-profile-identity="true"]');
const avatarSlotEl = topRow.querySelector('[data-profile-avatar-slot="true"]');
let currentFields = [];
let currentToggles = [];
let currentGender = 'unknown';
let currentAvatar = { value: '', source: '', txId: '', sha256Hex: '', timeMs: 0 };
function syncIdentity() {
if (!identityEl) return;
const firstName = currentFields.find((field) => field.key === 'first_name')?.value || '';
const lastName = currentFields.find((field) => field.key === 'last_name')?.value || '';
const lines = buildIdentityLines({ login, firstName, lastName });
identityEl.innerHTML = lines.map((line, idx) => (
`<div class="profile-identity-line${idx === lines.length - 1 ? ' profile-identity-login' : ''}">${escapeHtml(line)}</div>`
)).join('');
}
function updateAvatarUi() {
if (!(avatarSlotEl instanceof HTMLElement)) return;
const firstName = String(currentFields.find((field) => field.key === 'first_name')?.value || '').trim();
const lastName = String(currentFields.find((field) => field.key === 'last_name')?.value || '').trim();
avatarSlotEl.innerHTML = '';
avatarSlotEl.append(renderUserAvatar({
login,
firstName,
lastName,
avatar: currentAvatar?.txId
? { ar: currentAvatar.txId, sha256Hex: String(currentAvatar?.sha256Hex || '').trim().toLowerCase() }
: null,
size: 'large',
className: 'profile-avatar',
}));
}
function updateToggleButton(button, prefix, enabled) {
button.textContent = `${prefix}: ${toggleText(enabled)}`;
button.classList.remove('is-no', 'is-yes-official', 'is-yes-shine');
if (!enabled) {
button.classList.add('is-no');
return;
}
if (prefix === 'Официальный') button.classList.add('is-yes-official');
else button.classList.add('is-yes-shine');
}
function updateTogglesUi() {
const official = currentToggles.find((item) => item.key === 'official') || { enabled: false };
const shine = currentToggles.find((item) => item.key === 'shine') || { enabled: false };
updateToggleButton(officialBtn, 'Официальный', official.enabled);
updateToggleButton(shineBtn, 'Сияющий', shine.enabled);
}
officialBtn?.classList.add('profile-badge-trigger');
shineBtn?.classList.add('profile-badge-trigger');
officialBtn?.addEventListener('click', () => {
openProfileInfoModal({
title: 'Официальный канал',
text: officialInfoText(),
});
});
shineBtn?.addEventListener('click', () => {
openProfileInfoModal({
title: 'Справка о сияющих',
text: shineInfoText(),
});
});
function renderFields(fields) {
listWrap.innerHTML = '';
fields.forEach((field) => {
const row = document.createElement('div');
row.className = 'card profile-param-item row';
const value = String(field.value || '').trim() || 'не заполнено';
row.innerHTML = `<div class="profile-param-value"><b>${field.label}</b>: ${escapeHtml(value)}</div>`;
listWrap.append(row);
if (field.key === 'last_name') {
const genderRow = document.createElement('div');
genderRow.className = 'card profile-param-item row';
genderRow.innerHTML = `<div class="profile-param-value"><b>Пол</b>: ${escapeHtml(genderLabel(currentGender))}</div>`;
listWrap.append(genderRow);
}
});
}
async function refreshProfileSnapshot() {
try {
const snapshot = await loadProfileSnapshot(login);
currentFields = Array.isArray(snapshot.fields) ? snapshot.fields : [];
currentToggles = Array.isArray(snapshot.toggles) ? snapshot.toggles : [];
currentGender = snapshot.gender || 'unknown';
currentAvatar = snapshot.avatar || { value: '', source: '', txId: '', sha256Hex: '', timeMs: 0 };
syncIdentity();
updateAvatarUi();
updateTogglesUi();
renderFields(currentFields);
} catch (error) {
// ignore status row in profile-view
}
}
card.append(topRow, badgesRow, listWrap);
screen.append(card);
updateAvatarUi();
refreshProfileSnapshot();
return screen;
}