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,
+ });
+}