diff --git a/VERSION.properties b/VERSION.properties index 36360fb..f0ad64a 100644 --- a/VERSION.properties +++ b/VERSION.properties @@ -1,2 +1,2 @@ -client.version=1.2.324 -server.version=1.2.296 +client.version=1.2.325 +server.version=1.2.297 diff --git a/shine-UI/js/pages/registration-payment-view.js b/shine-UI/js/pages/registration-payment-view.js index 9cd3f1d..02ccdb0 100644 --- a/shine-UI/js/pages/registration-payment-view.js +++ b/shine-UI/js/pages/registration-payment-view.js @@ -24,10 +24,29 @@ import { defaultServerLogin } from '../deploy-config.js'; export const pageMeta = { id: 'registration-payment-view', title: 'Оплата регистрации', showAppChrome: false }; const MIN_REQUIRED_SOL = 0.01; -const AUTO_LOGIN_INITIAL_DELAY_MS = 5000; -const AUTO_LOGIN_RETRY_MS = 1000; +const AUTO_LOGIN_INITIAL_DELAY_MS = 10000; +const AUTO_LOGIN_RETRY_MS = 2000; const EMPTY_PASSWORD_WORDS = Array.from({ length: 12 }, () => ''); +function getExplorerClusterName(endpoint) { + const source = String(endpoint || '').trim().toLowerCase(); + if (!source) return 'mainnet-beta'; + if (source.includes('devnet')) return 'devnet'; + if (source.includes('testnet')) return 'testnet'; + return 'mainnet-beta'; +} + +function makeSolanaExplorerTxUrl(signature, endpoint) { + const cleanSignature = String(signature || '').trim(); + if (!cleanSignature) return ''; + const cluster = getExplorerClusterName(endpoint); + const url = new URL(`https://explorer.solana.com/tx/${encodeURIComponent(cleanSignature)}`); + if (cluster !== 'mainnet-beta') { + url.searchParams.set('cluster', cluster); + } + return url.toString(); +} + function parseBalanceSol(value) { const parsed = Number.parseFloat(String(value || '').replace(',', '.')); return Number.isFinite(parsed) ? parsed : 0; @@ -262,14 +281,16 @@ export function render({ navigate }) { // Регистрация на Solana (смарт контракт) submitButton.textContent = 'Регистрация в Solana...'; + let registrationTxId = ''; try { - await registerUserOnSolana({ + const registrationResult = await registerUserOnSolana({ login: state.registrationDraft.login, keyBundle, solanaEndpoint: state.entrySettings.solanaServer, accessServers: [state.entrySettings.shineServerLogin || defaultServerLogin], promoCode: '', }); + registrationTxId = String(registrationResult?.signature || '').trim(); } catch (solanaError) { const solanaMsg = formatSolanaErrorDetails(solanaError); // Пользователь уже зарегистрирован в Solana — продолжаем @@ -278,7 +299,7 @@ export function render({ navigate }) { } } - renderSolanaDoneStage({ navigate, status, keyBundle }); + renderSolanaDoneStage({ navigate, status, keyBundle, registrationTxId }); } catch (error) { const message = toUserMessage(error, 'Не удалось завершить регистрацию.'); setAuthError(message); @@ -324,37 +345,47 @@ export function render({ navigate }) { return screen; } -function renderSolanaDoneStage({ navigate, status, keyBundle }) { +function renderSolanaDoneStage({ navigate, status, keyBundle, registrationTxId = '' }) { const screen = document.querySelector('section.stack'); if (!screen) return; + const headerBackButton = screen.querySelector('.page-header .header-left .icon-btn'); const card = screen.querySelector('.card.stack'); if (!card) return; + card.classList.add('registration-finish-card'); let initialDelayId = null; let retryTimerId = null; let loginInFlight = false; let loginCompleted = false; + const txExplorerUrl = makeSolanaExplorerTxUrl(registrationTxId, state.entrySettings.solanaServer); const title = document.createElement('h2'); + title.className = 'registration-finish-title'; title.textContent = 'Поздравляем, регистрация завершена'; - title.style.margin = '0'; const hint = document.createElement('p'); - hint.className = 'auth-copy'; - hint.textContent = 'Осталось подождать несколько секунд, пока обновится запись в блокчейне. После этого приложение автоматически войдёт в ваш аккаунт.'; + hint.className = 'auth-copy registration-finish-text'; + hint.textContent = 'Подождите 10 секунд, пока обновится транзакция вашей регистрации в блокчейне Solana. После этого вход в аккаунт произойдёт автоматически.'; + + const txIdLine = document.createElement('p'); + txIdLine.className = 'meta-muted registration-finish-tx'; + if (registrationTxId && txExplorerUrl) { + const txLabel = document.createElement('span'); + txLabel.textContent = 'Tx ID регистрации: '; + const txLink = document.createElement('a'); + txLink.className = 'registration-finish-tx-link'; + txLink.href = txExplorerUrl; + txLink.target = '_blank'; + txLink.rel = 'noopener noreferrer'; + txLink.textContent = registrationTxId; + txIdLine.append(txLabel, txLink); + } else { + txIdLine.textContent = 'Tx ID регистрации: ожидаем подтверждённую подпись'; + } const progress = document.createElement('p'); - progress.className = 'meta-muted'; - progress.textContent = 'Подготавливаем автоматический вход...'; - - const altHint = document.createElement('p'); - altHint.className = 'meta-muted'; - altHint.textContent = 'Или вы можете'; - - const backBtn = document.createElement('button'); - backBtn.className = 'ghost-btn'; - backBtn.type = 'button'; - backBtn.textContent = 'Вернуться в главное меню'; + progress.className = 'meta-muted registration-finish-progress'; + progress.textContent = 'Подготавливаем проверку регистрации...'; const stopAutoLogin = () => { if (initialDelayId) { @@ -370,7 +401,7 @@ function renderSolanaDoneStage({ navigate, status, keyBundle }) { const tryAutoLogin = async () => { if (loginCompleted || loginInFlight) return; loginInFlight = true; - progress.textContent = 'Пробуем автоматически войти в аккаунт...'; + progress.textContent = 'Проверяем, прошла ли регистрация...'; status.style.display = 'none'; try { await completeRegistrationLogin({ navigate, keyBundle }); @@ -378,7 +409,7 @@ function renderSolanaDoneStage({ navigate, status, keyBundle }) { stopAutoLogin(); } catch (error) { if (!loginCompleted) { - progress.textContent = 'Ждём обновления записи и скоро попробуем снова...'; + progress.textContent = 'Пока ещё не прошла, подождите ещё секундочку...'; console.warn('Auto login after registration is not ready yet', toUserMessage(error, 'auto-login-wait')); } } finally { @@ -386,14 +417,18 @@ function renderSolanaDoneStage({ navigate, status, keyBundle }) { } }; - backBtn.addEventListener('click', () => { - loginCompleted = true; - stopAutoLogin(); - navigate('start-view'); - }); + if (headerBackButton) { + const replacement = headerBackButton.cloneNode(true); + replacement.addEventListener('click', () => { + loginCompleted = true; + stopAutoLogin(); + navigate('start-view'); + }); + headerBackButton.replaceWith(replacement); + } card.innerHTML = ''; - card.append(title, hint, progress, altHint, backBtn, status); + card.append(title, hint, txIdLine, progress, status); initialDelayId = window.setTimeout(() => { if (loginCompleted) return; diff --git a/shine-UI/styles/components.css b/shine-UI/styles/components.css index 8caa4e9..340f5b3 100644 --- a/shine-UI/styles/components.css +++ b/shine-UI/styles/components.css @@ -755,6 +755,39 @@ font-size: 13px; } +.registration-finish-card { + width: 100%; + max-width: 100%; + overflow: hidden; +} + +.registration-finish-title { + margin: 0; + font-size: clamp(1.7rem, 6vw, 2.15rem); + line-height: 1.18; + text-wrap: balance; +} + +.registration-finish-text, +.registration-finish-tx, +.registration-finish-progress { + max-width: 100%; + overflow-wrap: anywhere; + word-break: break-word; +} + +.registration-finish-tx-link { + color: #9fd8ff; + text-decoration: underline; + overflow-wrap: anywhere; + word-break: break-all; +} + +.registration-finish-tx-link:hover, +.registration-finish-tx-link:focus-visible { + color: #c9ebff; +} + .session-status { color: var(--text-muted); font-size: 13px;