diff --git a/shine-UI/js/pages/profile-view.js b/shine-UI/js/pages/profile-view.js index eb32136..bf65422 100644 --- a/shine-UI/js/pages/profile-view.js +++ b/shine-UI/js/pages/profile-view.js @@ -30,28 +30,11 @@ function genderLabel(value) { 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 ''; -} +const GENDER_OPTIONS = Object.freeze([ + { value: PROFILE_GENDER_MALE, label: 'Мужской' }, + { value: PROFILE_GENDER_FEMALE, label: 'Женский' }, + { value: PROFILE_GENDER_UNKNOWN, label: 'Не указан' }, +]); function escapeHtml(text) { return String(text || '') @@ -104,27 +87,70 @@ 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 openGenderPickerModal(initialGender) { + const root = document.getElementById('modal-root'); + if (!root) return Promise.resolve(null); + root.innerHTML = ''; + + const selected = GENDER_OPTIONS.some((item) => item.value === initialGender) + ? initialGender + : PROFILE_GENDER_UNKNOWN; + + root.innerHTML = ` + + `; + + return new Promise((resolve) => { + const modal = root.querySelector('#profile-gender-modal'); + const selectEl = root.querySelector('#profile-gender-select'); + const saveEl = root.querySelector('#profile-gender-save'); + const cancelEl = root.querySelector('#profile-gender-cancel'); + if (!(modal instanceof HTMLElement) || !(selectEl instanceof HTMLSelectElement)) { + root.innerHTML = ''; + resolve(null); + return; + } + + const close = (value = null) => { + root.innerHTML = ''; + resolve(value); + }; + + modal.addEventListener('click', (event) => { + if (event.target === modal) close(null); + }); + cancelEl?.addEventListener('click', () => close(null)); + saveEl?.addEventListener('click', () => close(selectEl.value || PROFILE_GENDER_UNKNOWN)); + window.setTimeout(() => selectEl.focus(), 0); + }); + } + function syncIdentity() { if (!identityEl) return; const firstName = currentFields.find((field) => field.key === 'first_name')?.value || ''; @@ -159,10 +185,69 @@ export function render({ navigate }) { } function updateGenderUi() { + const genderValueEl = listWrap.querySelector('[data-gender-value]'); if (!genderValueEl) return; genderValueEl.textContent = genderLabel(currentGender); } + function openFieldEditModal({ label, value, placeholder = '' }) { + const root = document.getElementById('modal-root'); + if (!root) return Promise.resolve(null); + root.innerHTML = ` + + `; + + return new Promise((resolve) => { + const modal = root.querySelector('#profile-field-edit-modal'); + const inputEl = root.querySelector('#profile-field-edit-input'); + const saveEl = root.querySelector('#profile-field-edit-save'); + const cancelEl = root.querySelector('#profile-field-edit-cancel'); + if (!(modal instanceof HTMLElement) || !(inputEl instanceof HTMLInputElement)) { + root.innerHTML = ''; + resolve(null); + return; + } + + const close = (nextValue = null) => { + root.innerHTML = ''; + resolve(nextValue); + }; + + modal.addEventListener('click', (event) => { + if (event.target === modal) close(null); + }); + cancelEl?.addEventListener('click', () => close(null)); + saveEl?.addEventListener('click', () => close(inputEl.value)); + inputEl.addEventListener('keydown', (event) => { + if (event.key === 'Enter') { + event.preventDefault(); + close(inputEl.value); + } + }); + window.setTimeout(() => { + inputEl.focus(); + inputEl.selectionStart = inputEl.value.length; + inputEl.selectionEnd = inputEl.value.length; + }, 0); + }); + } + function renderFields(fields) { listWrap.innerHTML = ''; fields.forEach((field) => { @@ -172,10 +257,20 @@ export function render({ navigate }) { const isNameField = field.key === 'first_name' || field.key === 'last_name'; const valueClass = isNameField ? 'profile-param-value profile-param-value-small' : 'profile-param-value'; row.innerHTML = ` -
${field.label}: ${value}
+
${field.label}: ${escapeHtml(value)}
`; listWrap.append(row); + + if (field.key === 'last_name') { + const genderRow = document.createElement('div'); + genderRow.className = 'card profile-param-item row'; + genderRow.innerHTML = ` +
Пол: ${escapeHtml(genderLabel(currentGender))}
+ + `; + listWrap.append(genderRow); + } }); } @@ -185,7 +280,10 @@ export function render({ navigate }) { reloadBtn.disabled = true; officialBtn.disabled = true; shineBtn.disabled = true; - genderBtn.disabled = true; + const genderActionBtn = listWrap.querySelector('[data-edit-gender="true"]'); + if (genderActionBtn instanceof HTMLButtonElement) { + genderActionBtn.disabled = true; + } try { const snapshot = await loadProfileSnapshot(login); @@ -208,7 +306,10 @@ export function render({ navigate }) { reloadBtn.disabled = false; officialBtn.disabled = false; shineBtn.disabled = false; - genderBtn.disabled = false; + const genderActionBtnAfter = listWrap.querySelector('[data-edit-gender="true"]'); + if (genderActionBtnAfter instanceof HTMLButtonElement) { + genderActionBtnAfter.disabled = false; + } } } @@ -240,14 +341,13 @@ export function render({ navigate }) { const field = currentFields.find((item) => item.key === fieldKey); if (!field) return; - const entered = window.prompt(`Введите новое значение для «${field.label}»:`, field.value || ''); + const entered = await openFieldEditModal({ + label: field.label, + value: field.value || '', + placeholder: field.placeholder || '', + }); if (entered === null) return; - const confirmed = window.confirm( - `Записать новое значение параметра «${field.label}» в блокчейн?`, - ); - if (!confirmed) return; - status.className = 'status-line'; status.textContent = 'Сохранение в блокчейн...'; @@ -262,21 +362,8 @@ 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; + const nextGender = await openGenderPickerModal(currentGender); + if (!nextGender) return; status.className = 'status-line'; status.textContent = 'Сохранение в блокчейн...'; @@ -294,6 +381,10 @@ export function render({ navigate }) { listWrap.addEventListener('click', (event) => { const target = event.target; if (!(target instanceof HTMLElement)) return; + if (target.dataset.editGender === 'true') { + onGenderClick(); + return; + } const fieldKey = target.dataset.editField; if (!fieldKey) return; onEditFieldClick(fieldKey); @@ -302,9 +393,8 @@ 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, genderWrap, listWrap); + card.append(topRow, badgesRow, status, listWrap); screen.append(card); refreshProfileSnapshot(); diff --git a/shine-UI/styles/components.css b/shine-UI/styles/components.css index 59b5ce3..a996d38 100644 --- a/shine-UI/styles/components.css +++ b/shine-UI/styles/components.css @@ -253,6 +253,22 @@ font-size: 13px; } +.profile-gender-select { + min-height: 46px; + border-radius: 12px; + border: 1px solid rgba(157, 185, 238, 0.35); + background: + linear-gradient(150deg, rgba(23, 43, 79, 0.9), rgba(10, 22, 44, 0.92)); + color: #eef4ff; + font-weight: 600; + letter-spacing: 0.01em; +} + +.profile-gender-select:focus { + border-color: rgba(120, 211, 255, 0.9); + box-shadow: 0 0 0 3px rgba(65, 174, 255, 0.2); +} + .profile-param-time { font-size: 12px; } @@ -1468,7 +1484,8 @@ textarea.input { } .thread-node-level { - margin-left: calc(min(var(--depth, 0), 4) * 12px); + --depth: 0; + margin-left: calc(var(--depth) * 12px); } .thread-block {