17-04-2026
Добавил вкладку пол
This commit is contained in:
parent
7591fbdace
commit
c7bf8051b9
@ -2,7 +2,11 @@ import { renderHeader } from '../components/header.js';
|
|||||||
import { profile } from '../mock-data.js';
|
import { profile } from '../mock-data.js';
|
||||||
import { state } from '../state.js';
|
import { state } from '../state.js';
|
||||||
import {
|
import {
|
||||||
|
PROFILE_GENDER_FEMALE,
|
||||||
|
PROFILE_GENDER_MALE,
|
||||||
|
PROFILE_GENDER_UNKNOWN,
|
||||||
loadProfileSnapshot,
|
loadProfileSnapshot,
|
||||||
|
saveProfileGender,
|
||||||
saveProfileParamBlock,
|
saveProfileParamBlock,
|
||||||
saveProfileToggle,
|
saveProfileToggle,
|
||||||
} from '../services/user-profile-params.js';
|
} from '../services/user-profile-params.js';
|
||||||
@ -20,6 +24,35 @@ function showLocalErrorAlert(prefix, error) {
|
|||||||
window.alert(`${prefix}: ${message}${stack}`);
|
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) {
|
function escapeHtml(text) {
|
||||||
return String(text || '')
|
return String(text || '')
|
||||||
.replaceAll('&', '&')
|
.replaceAll('&', '&')
|
||||||
@ -71,15 +104,25 @@ export function render({ navigate }) {
|
|||||||
status.className = 'status-line';
|
status.className = 'status-line';
|
||||||
status.textContent = 'Загрузка параметров...';
|
status.textContent = 'Загрузка параметров...';
|
||||||
|
|
||||||
|
const genderWrap = document.createElement('div');
|
||||||
|
genderWrap.className = 'card row profile-param-item';
|
||||||
|
genderWrap.innerHTML = `
|
||||||
|
<div class="profile-param-value"><b>Пол</b>: <span data-gender-value>Не указан</span></div>
|
||||||
|
<button class="ghost-btn" type="button" data-edit-gender="true">Выбрать</button>
|
||||||
|
`;
|
||||||
|
|
||||||
const listWrap = document.createElement('div');
|
const listWrap = document.createElement('div');
|
||||||
listWrap.className = 'stack profile-param-list';
|
listWrap.className = 'stack profile-param-list';
|
||||||
|
|
||||||
const reloadBtn = topRow.querySelector('[data-reload="true"]');
|
const reloadBtn = topRow.querySelector('[data-reload="true"]');
|
||||||
const officialBtn = badgesRow.querySelector('[data-toggle="official"]');
|
const officialBtn = badgesRow.querySelector('[data-toggle="official"]');
|
||||||
const shineBtn = badgesRow.querySelector('[data-toggle="shine"]');
|
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 currentFields = [];
|
||||||
let currentToggles = [];
|
let currentToggles = [];
|
||||||
|
let currentGender = PROFILE_GENDER_UNKNOWN;
|
||||||
const identityEl = topRow.querySelector('[data-profile-identity="true"]');
|
const identityEl = topRow.querySelector('[data-profile-identity="true"]');
|
||||||
|
|
||||||
function syncIdentity() {
|
function syncIdentity() {
|
||||||
@ -115,6 +158,11 @@ export function render({ navigate }) {
|
|||||||
updateToggleButton(shineBtn, 'Сияющий', shine.enabled);
|
updateToggleButton(shineBtn, 'Сияющий', shine.enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updateGenderUi() {
|
||||||
|
if (!genderValueEl) return;
|
||||||
|
genderValueEl.textContent = genderLabel(currentGender);
|
||||||
|
}
|
||||||
|
|
||||||
function renderFields(fields) {
|
function renderFields(fields) {
|
||||||
listWrap.innerHTML = '';
|
listWrap.innerHTML = '';
|
||||||
fields.forEach((field) => {
|
fields.forEach((field) => {
|
||||||
@ -137,15 +185,18 @@ export function render({ navigate }) {
|
|||||||
reloadBtn.disabled = true;
|
reloadBtn.disabled = true;
|
||||||
officialBtn.disabled = true;
|
officialBtn.disabled = true;
|
||||||
shineBtn.disabled = true;
|
shineBtn.disabled = true;
|
||||||
|
genderBtn.disabled = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const snapshot = await loadProfileSnapshot(login);
|
const snapshot = await loadProfileSnapshot(login);
|
||||||
currentFields = snapshot.fields;
|
currentFields = snapshot.fields;
|
||||||
currentToggles = snapshot.toggles;
|
currentToggles = snapshot.toggles;
|
||||||
|
currentGender = snapshot.gender || PROFILE_GENDER_UNKNOWN;
|
||||||
|
|
||||||
syncIdentity();
|
syncIdentity();
|
||||||
renderFields(currentFields);
|
renderFields(currentFields);
|
||||||
updateTogglesUi();
|
updateTogglesUi();
|
||||||
|
updateGenderUi();
|
||||||
|
|
||||||
status.className = 'status-line is-available';
|
status.className = 'status-line is-available';
|
||||||
status.textContent = 'Актуальные параметры загружены.';
|
status.textContent = 'Актуальные параметры загружены.';
|
||||||
@ -157,6 +208,7 @@ export function render({ navigate }) {
|
|||||||
reloadBtn.disabled = false;
|
reloadBtn.disabled = false;
|
||||||
officialBtn.disabled = false;
|
officialBtn.disabled = false;
|
||||||
shineBtn.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) => {
|
listWrap.addEventListener('click', (event) => {
|
||||||
const target = event.target;
|
const target = event.target;
|
||||||
if (!(target instanceof HTMLElement)) return;
|
if (!(target instanceof HTMLElement)) return;
|
||||||
@ -220,8 +302,9 @@ export function render({ navigate }) {
|
|||||||
reloadBtn.addEventListener('click', refreshProfileSnapshot);
|
reloadBtn.addEventListener('click', refreshProfileSnapshot);
|
||||||
officialBtn.addEventListener('click', () => onToggleClick('official'));
|
officialBtn.addEventListener('click', () => onToggleClick('official'));
|
||||||
shineBtn.addEventListener('click', () => onToggleClick('shine'));
|
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);
|
screen.append(card);
|
||||||
|
|
||||||
refreshProfileSnapshot();
|
refreshProfileSnapshot();
|
||||||
|
|||||||
@ -22,6 +22,13 @@ function boolText(flag) {
|
|||||||
return 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) {
|
function relationButtonLabel(kind, flags) {
|
||||||
if (kind === 'follow') return flags.outFollow ? 'Отписаться' : 'Подписаться';
|
if (kind === 'follow') return flags.outFollow ? 'Отписаться' : 'Подписаться';
|
||||||
if (kind === 'friend') return flags.outFriend ? 'Убрать из друзей' : 'Добавить в друзья';
|
if (kind === 'friend') return flags.outFriend ? 'Убрать из друзей' : 'Добавить в друзья';
|
||||||
@ -85,6 +92,7 @@ function renderReadOnlyParams(card) {
|
|||||||
const rows = [
|
const rows = [
|
||||||
{ label: 'Имя', value: card.firstName },
|
{ label: 'Имя', value: card.firstName },
|
||||||
{ label: 'Фамилия', value: card.lastName },
|
{ label: 'Фамилия', value: card.lastName },
|
||||||
|
{ label: 'Пол', value: genderText(card.gender) },
|
||||||
{ label: 'Адрес', value: card.address },
|
{ label: 'Адрес', value: card.address },
|
||||||
{ label: 'Web', value: card.web },
|
{ label: 'Web', value: card.web },
|
||||||
{ label: 'Телефон', value: card.phone },
|
{ label: 'Телефон', value: card.phone },
|
||||||
|
|||||||
@ -183,6 +183,7 @@ export async function loadUserProfileCard(login) {
|
|||||||
address: fields.address || '',
|
address: fields.address || '',
|
||||||
web: fields.web || '',
|
web: fields.web || '',
|
||||||
phone: fields.phone || '',
|
phone: fields.phone || '',
|
||||||
|
gender: String(snapshot?.gender || 'unknown').trim().toLowerCase() || 'unknown',
|
||||||
official: Boolean(toggles.official),
|
official: Boolean(toggles.official),
|
||||||
shine: Boolean(toggles.shine),
|
shine: Boolean(toggles.shine),
|
||||||
};
|
};
|
||||||
|
|||||||
@ -13,6 +13,15 @@ export const profileToggleDefs = [
|
|||||||
{ key: 'shine', label: 'Сияющий' },
|
{ 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) {
|
function normalizeItem(param, payload) {
|
||||||
if (!param) return null;
|
if (!param) return null;
|
||||||
|
|
||||||
@ -31,6 +40,13 @@ function parseToggleValue(value) {
|
|||||||
return normalized === 'true' || normalized === 'yes' || normalized === '1';
|
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() {
|
async function getStoragePwd() {
|
||||||
const storagePwd = state.session.storagePwdInMemory;
|
const storagePwd = state.session.storagePwdInMemory;
|
||||||
if (!storagePwd) {
|
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) {
|
export async function saveProfileParamBlock(login, key, value) {
|
||||||
@ -122,3 +146,14 @@ export async function saveProfileToggle(login, key, enabled) {
|
|||||||
storagePwd,
|
storagePwd,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function saveProfileGender(login, gender) {
|
||||||
|
const normalized = normalizeGenderValue(gender);
|
||||||
|
const storagePwd = await getStoragePwd();
|
||||||
|
await authService.addBlockUserParam({
|
||||||
|
login,
|
||||||
|
param: 'gender',
|
||||||
|
value: normalized,
|
||||||
|
storagePwd,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user