Files
SHiNE-server/shine-UI/js/pages/registration-keys-view.js
T
qandClaude Opus 5.5 2e62616b46 UI v4: новый стиль по макету V3/V4 и доводка экранов
Палитра «Сияние» по умолчанию, новые роли цветов. Лента канала: плоские записи, ряд действий
с равным шагом иконок, дата у записи. Обсуждение со строкой ввода, личный чат, шторки.
Вход и регистрация: стартовый экран (надпись «СИЯНИЕ», сияние мандалы), настройки входа
со статусами серверов, статус логина в одну строку, вход с другого устройства (код, таймер, шаги),
сохранение ключей с переключателями. Корневые разделы – заголовок по центру; фильтр чатов –
переключатель; уведомления – список как в чатах; фото – просмотр на весь экран.
Общее: один зазор под шапкой (--page-top-gap), переключатели высотой с поле ввода,
второстепенные кнопки по одной роли (в том числе на экранах устройств и серверов),
иконки только из ui-icon.js, полоса прокрутки скрыта, среднее тире в текстах интерфейса.

Что нужно от сервера и разработчика – docs/UI-Design/UI-v4-for-dev.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 01:36:52 +03:00

205 lines
7.9 KiB
JavaScript

import { createTopBar } from '../components/topbar.js';
import {
authService,
cancelAddProfileLogin,
consumeAuthReturnPage,
authorizeSession,
refreshSessions,
resetRegistrationFlow,
setAuthError,
setAuthInfo,
state,
} from '../state.js';
import { clearStoredMessages } from '../services/message-store.js';
import { toUserMessage } from '../services/ui-error-texts.js';
export const pageMeta = { id: 'registration-keys-view', title: 'Сохранение ключей', showAppChrome: false };
const EMPTY_PASSWORD_WORDS = Array.from({ length: 12 }, () => '');
export function render({ navigate }) {
const screen = document.createElement('section');
screen.className = 'stack keys-screen';
const isLoginFlow = state.registrationDraft.flowType === 'login';
const normalizedLogin = (state.registrationDraft.login || '').trim();
const displayLogin = normalizedLogin ? `@${normalizedLogin.replace(/^@/, '')}` : '@new.user';
const lead = document.createElement('div');
lead.className = 'keys-lead';
const title = document.createElement('h1');
title.className = 'keys-lead__title';
title.textContent = isLoginFlow ? `Вы вошли как ${displayLogin}` : `${displayLogin} зарегистрирован`;
const question = document.createElement('p');
question.className = 'keys-lead__text';
question.textContent = 'Какие ключи хранить на этом устройстве?';
const questionHint = document.createElement('p');
questionHint.className = 'keys-lead__text';
questionHint.textContent = 'Они хранятся в зашифрованном виде.';
lead.append(title, question, questionHint);
const status = document.createElement('p');
status.className = 'status-line is-unavailable';
status.style.display = 'none';
const createKeyRow = (toggle, titleText, descriptionText) => {
const row = document.createElement('label');
row.className = 'keys-row';
const text = document.createElement('span');
text.className = 'keys-row__text';
const name = document.createElement('span');
name.className = 'keys-row__title';
name.textContent = titleText;
const description = document.createElement('span');
description.className = 'keys-row__hint';
description.textContent = descriptionText;
text.append(name, description);
toggle.className = 'switch';
row.append(text, toggle);
return row;
};
const rootToggle = document.createElement('input');
rootToggle.type = 'checkbox';
rootToggle.checked = state.keyStorage.saveRoot;
const blockchainToggle = document.createElement('input');
blockchainToggle.type = 'checkbox';
blockchainToggle.checked = state.keyStorage.saveBlockchain;
const deviceToggle = document.createElement('input');
deviceToggle.type = 'checkbox';
deviceToggle.checked = true;
deviceToggle.disabled = true;
const list = document.createElement('div');
list.className = 'keys-list';
list.append(
createKeyRow(rootToggle, 'Главный ключ', 'Смена пароля, восстановление доступа, важные настройки'),
createKeyRow(blockchainToggle, 'Ключ публикаций', 'Подписывает ваши записи и действия'),
createKeyRow(deviceToggle, 'Ключ устройства', 'Вход с этого устройства – нужен всегда'),
);
const simpleNote = document.createElement('p');
simpleNote.className = 'keys-note';
simpleNote.textContent = 'Не уверены – оставьте все включёнными.';
const actions = document.createElement('div');
actions.className = 'keys-actions';
const cancelButton = document.createElement('button');
cancelButton.className = 'secondary-btn';
cancelButton.type = 'button';
cancelButton.textContent = 'Отмена';
cancelButton.addEventListener('click', async () => {
if (state.authReturnHash === '/profiles') {
await cancelAddProfileLogin();
navigate('profiles-view');
return;
}
resetRegistrationFlow();
navigate('start-view');
});
const okButton = document.createElement('button');
okButton.className = 'primary-btn';
okButton.type = 'button';
okButton.textContent = 'Готово';
okButton.addEventListener('click', async () => {
status.style.display = 'none';
try {
if (!state.registrationDraft.pendingKeyBundle || !state.registrationDraft.pendingSessionMaterial) {
throw new Error('Сначала завершите шаг регистрации на предыдущем экране');
}
state.keyStorage.saveRoot = rootToggle.checked;
state.keyStorage.saveBlockchain = blockchainToggle.checked;
await authService.persistSelectedKeys(
state.registrationDraft.login,
state.registrationDraft.storagePwd,
state.registrationDraft.pendingKeyBundle,
{
saveRoot: state.keyStorage.saveRoot,
saveBlockchain: state.keyStorage.saveBlockchain,
},
);
await authService.persistSessionMaterial(
state.registrationDraft.login,
state.registrationDraft.pendingSessionMaterial,
);
if (!state.keyStorage.saveRoot && state.registrationDraft.pendingKeyBundle) {
state.registrationDraft.pendingKeyBundle.rootPair = null;
}
if (!state.keyStorage.saveBlockchain && state.registrationDraft.pendingKeyBundle) {
state.registrationDraft.pendingKeyBundle.blockchainPair = null;
}
await clearStoredMessages(state.registrationDraft.login).catch(() => {});
authorizeSession({
login: state.registrationDraft.login,
sessionId: state.registrationDraft.sessionId,
storagePwd: state.registrationDraft.storagePwd,
});
state.loginDraft.login = state.registrationDraft.login;
state.loginDraft.password = '';
state.loginDraft.passwordMode = 'single';
state.loginDraft.passwordWords = EMPTY_PASSWORD_WORDS.slice();
state.registrationDraft.flowType = '';
state.registrationDraft.password = '';
state.registrationDraft.passwordMode = 'single';
state.registrationDraft.passwordWords = EMPTY_PASSWORD_WORDS.slice();
state.registrationDraft.passwordWordsLinked = false;
state.registrationDraft.usePromoCode = false;
state.registrationDraft.promoCode = '';
state.registrationDraft.storagePwd = '';
state.registrationDraft.sessionId = '';
state.registrationDraft.pendingKeyBundle = null;
state.registrationDraft.pendingSessionMaterial = null;
state.registrationDraft.preGeneratedKeyBundle = null;
state.registrationPayment.walletAddress = '';
state.registrationPayment.balanceSOL = '0.0000';
await refreshSessions();
setAuthInfo(isLoginFlow
? `Ключи сохранены. Вы вошли как @${state.registrationDraft.login}. Далее откройте вкладку «Каналы».`
: `Ключи сохранены. Регистрация завершена для @${state.registrationDraft.login}. Далее откройте вкладку «Каналы».`);
navigate(consumeAuthReturnPage('profile-view'));
} catch (error) {
const message = toUserMessage(error, 'Не удалось сохранить ключи на устройстве.');
setAuthError(message);
status.textContent = message;
status.style.display = '';
}
});
actions.append(okButton, cancelButton);
screen.append(
createTopBar({
title: 'Сохранение ключей',
back: {
label: '←',
onClick: async () => {
if (state.authReturnHash === '/profiles') {
await cancelAddProfileLogin();
navigate('profiles-view');
return;
}
resetRegistrationFlow();
navigate('start-view');
},
},
}),
lead,
list,
simpleNote,
status,
actions,
);
return screen;
}