From c7bf8051b9d65aed67b98154ff54276ae66a593d9eeb76ed57c8fe68175b8971 Mon Sep 17 00:00:00 2001 From: AidarKC Date: Fri, 17 Apr 2026 17:39:04 +0300 Subject: [PATCH] =?UTF-8?q?17-04-2026=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=B8=D0=BB=20=D0=B2=D0=BA=D0=BB=D0=B0=D0=B4=D0=BA=D1=83=20?= =?UTF-8?q?=D0=BF=D0=BE=D0=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- shine-UI/js/pages/profile-view.js | 85 ++++++++++++++++++++- shine-UI/js/pages/user-profile-view.js | 8 ++ shine-UI/js/services/user-connections.js | 1 + shine-UI/js/services/user-profile-params.js | 37 ++++++++- 4 files changed, 129 insertions(+), 2 deletions(-) diff --git a/shine-UI/js/pages/profile-view.js b/shine-UI/js/pages/profile-view.js index 5a96063..eb32136 100644 --- a/shine-UI/js/pages/profile-view.js +++ b/shine-UI/js/pages/profile-view.js @@ -2,7 +2,11 @@ import { renderHeader } from '../components/header.js'; import { profile } from '../mock-data.js'; import { state } from '../state.js'; import { + PROFILE_GENDER_FEMALE, + PROFILE_GENDER_MALE, + PROFILE_GENDER_UNKNOWN, loadProfileSnapshot, + saveProfileGender, saveProfileParamBlock, saveProfileToggle, } from '../services/user-profile-params.js'; @@ -20,6 +24,35 @@ function showLocalErrorAlert(prefix, error) { window.alert(`${prefix}: ${message}${stack}`); } +function genderLabel(value) { + if (value === PROFILE_GENDER_MALE) return 'Мужской'; + if (value === PROFILE_GENDER_FEMALE) return 'Женский'; + return 'Не указан'; +} + +function parseGenderChoice(value) { + const normalized = String(value || '').trim().toLowerCase(); + if (!normalized) return ''; + if (normalized === '1' || normalized === 'м' || normalized === 'муж' || normalized === 'мужской' || normalized === PROFILE_GENDER_MALE) { + return PROFILE_GENDER_MALE; + } + if (normalized === '2' || normalized === 'ж' || normalized === 'жен' || normalized === 'женский' || normalized === PROFILE_GENDER_FEMALE) { + return PROFILE_GENDER_FEMALE; + } + if ( + normalized === '3' || + normalized === 'н' || + normalized === 'не указан' || + normalized === 'неуказан' || + normalized === 'не указано' || + normalized === 'неизвестно' || + normalized === PROFILE_GENDER_UNKNOWN + ) { + return PROFILE_GENDER_UNKNOWN; + } + return ''; +} + function escapeHtml(text) { return String(text || '') .replaceAll('&', '&') @@ -71,15 +104,25 @@ export function render({ navigate }) { status.className = 'status-line'; status.textContent = 'Загрузка параметров...'; + const genderWrap = document.createElement('div'); + genderWrap.className = 'card row profile-param-item'; + genderWrap.innerHTML = ` +
Пол: Не указан
+ + `; + const listWrap = document.createElement('div'); listWrap.className = 'stack profile-param-list'; const reloadBtn = topRow.querySelector('[data-reload="true"]'); const officialBtn = badgesRow.querySelector('[data-toggle="official"]'); const shineBtn = badgesRow.querySelector('[data-toggle="shine"]'); + const genderValueEl = genderWrap.querySelector('[data-gender-value]'); + const genderBtn = genderWrap.querySelector('[data-edit-gender="true"]'); let currentFields = []; let currentToggles = []; + let currentGender = PROFILE_GENDER_UNKNOWN; const identityEl = topRow.querySelector('[data-profile-identity="true"]'); function syncIdentity() { @@ -115,6 +158,11 @@ export function render({ navigate }) { updateToggleButton(shineBtn, 'Сияющий', shine.enabled); } + function updateGenderUi() { + if (!genderValueEl) return; + genderValueEl.textContent = genderLabel(currentGender); + } + function renderFields(fields) { listWrap.innerHTML = ''; fields.forEach((field) => { @@ -137,15 +185,18 @@ export function render({ navigate }) { reloadBtn.disabled = true; officialBtn.disabled = true; shineBtn.disabled = true; + genderBtn.disabled = true; try { const snapshot = await loadProfileSnapshot(login); currentFields = snapshot.fields; currentToggles = snapshot.toggles; + currentGender = snapshot.gender || PROFILE_GENDER_UNKNOWN; syncIdentity(); renderFields(currentFields); updateTogglesUi(); + updateGenderUi(); status.className = 'status-line is-available'; status.textContent = 'Актуальные параметры загружены.'; @@ -157,6 +208,7 @@ export function render({ navigate }) { reloadBtn.disabled = false; officialBtn.disabled = false; shineBtn.disabled = false; + genderBtn.disabled = false; } } @@ -209,6 +261,36 @@ export function render({ navigate }) { } } + async function onGenderClick() { + const entered = window.prompt( + 'Выберите пол:\n1 — Мужской\n2 — Женский\n3 — Не указан\nМожно ввести номер или значение (male/female/unknown).', + currentGender, + ); + if (entered === null) return; + const nextGender = parseGenderChoice(entered); + if (!nextGender) { + window.alert('Некорректный выбор пола. Доступно: male, female, unknown.'); + return; + } + + const confirmed = window.confirm( + `Установить пол: «${genderLabel(nextGender)}»?\nБудет создана запись в блокчейне.`, + ); + if (!confirmed) return; + + status.className = 'status-line'; + status.textContent = 'Сохранение в блокчейн...'; + + try { + await saveProfileGender(login, nextGender); + await refreshProfileSnapshot(); + } catch (error) { + status.className = 'status-line is-unavailable'; + status.textContent = `Не удалось изменить пол: ${error.message || 'ошибка сети'}`; + showLocalErrorAlert('Ошибка изменения пола', error); + } + } + listWrap.addEventListener('click', (event) => { const target = event.target; if (!(target instanceof HTMLElement)) return; @@ -220,8 +302,9 @@ export function render({ navigate }) { reloadBtn.addEventListener('click', refreshProfileSnapshot); officialBtn.addEventListener('click', () => onToggleClick('official')); shineBtn.addEventListener('click', () => onToggleClick('shine')); + genderBtn.addEventListener('click', onGenderClick); - card.append(topRow, badgesRow, status, listWrap); + card.append(topRow, badgesRow, status, genderWrap, listWrap); screen.append(card); refreshProfileSnapshot(); diff --git a/shine-UI/js/pages/user-profile-view.js b/shine-UI/js/pages/user-profile-view.js index 15c5663..9573ff7 100644 --- a/shine-UI/js/pages/user-profile-view.js +++ b/shine-UI/js/pages/user-profile-view.js @@ -22,6 +22,13 @@ function boolText(flag) { return flag ? 'Да' : 'Нет'; } +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 ? 'Убрать из друзей' : 'Добавить в друзья'; @@ -85,6 +92,7 @@ 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 }, diff --git a/shine-UI/js/services/user-connections.js b/shine-UI/js/services/user-connections.js index c0c5fef..453a962 100644 --- a/shine-UI/js/services/user-connections.js +++ b/shine-UI/js/services/user-connections.js @@ -183,6 +183,7 @@ export async function loadUserProfileCard(login) { address: fields.address || '', web: fields.web || '', phone: fields.phone || '', + gender: String(snapshot?.gender || 'unknown').trim().toLowerCase() || 'unknown', official: Boolean(toggles.official), shine: Boolean(toggles.shine), }; diff --git a/shine-UI/js/services/user-profile-params.js b/shine-UI/js/services/user-profile-params.js index ffa1ce1..228932f 100644 --- a/shine-UI/js/services/user-profile-params.js +++ b/shine-UI/js/services/user-profile-params.js @@ -13,6 +13,15 @@ export const profileToggleDefs = [ { key: 'shine', label: 'Сияющий' }, ]; +export const PROFILE_GENDER_MALE = 'male'; +export const PROFILE_GENDER_FEMALE = 'female'; +export const PROFILE_GENDER_UNKNOWN = 'unknown'; +export const PROFILE_GENDER_VALUES = Object.freeze([ + PROFILE_GENDER_MALE, + PROFILE_GENDER_FEMALE, + PROFILE_GENDER_UNKNOWN, +]); + function normalizeItem(param, payload) { if (!param) return null; @@ -31,6 +40,13 @@ function parseToggleValue(value) { return normalized === 'true' || normalized === 'yes' || normalized === '1'; } +function normalizeGenderValue(value) { + const normalized = String(value || '').trim().toLowerCase(); + if (normalized === PROFILE_GENDER_MALE) return PROFILE_GENDER_MALE; + if (normalized === PROFILE_GENDER_FEMALE) return PROFILE_GENDER_FEMALE; + return PROFILE_GENDER_UNKNOWN; +} + async function getStoragePwd() { const storagePwd = state.session.storagePwdInMemory; if (!storagePwd) { @@ -100,7 +116,15 @@ export async function loadProfileSnapshot(login) { }); } - return { fields, toggles }; + const latestGender = loadLatestByAliasesFromItems(items, ['gender']); + const gender = normalizeGenderValue(latestGender?.value || PROFILE_GENDER_UNKNOWN); + + return { + fields, + toggles, + gender, + genderTimeMs: latestGender?.timeMs || 0, + }; } export async function saveProfileParamBlock(login, key, value) { @@ -122,3 +146,14 @@ export async function saveProfileToggle(login, key, enabled) { storagePwd, }); } + +export async function saveProfileGender(login, gender) { + const normalized = normalizeGenderValue(gender); + const storagePwd = await getStoragePwd(); + await authService.addBlockUserParam({ + login, + param: 'gender', + value: normalized, + storagePwd, + }); +}