diff --git a/public/index.html b/public/index.html index 328daff..b46e6a5 100644 --- a/public/index.html +++ b/public/index.html @@ -34,15 +34,15 @@ - - - - - - - - - - + + + + + + + + + + diff --git a/public/js/api.js b/public/js/api.js index 2713351..3b80112 100644 --- a/public/js/api.js +++ b/public/js/api.js @@ -55,15 +55,41 @@ const Store = { access: null, refresh: null, user: null, + _key: 'buhapp_session_v1', save: function (tokens) { if (!tokens) return; this.access = tokens.Access || tokens.access; this.refresh = tokens.Refresh || tokens.refresh; + try { + localStorage.setItem(this._key, JSON.stringify({ + access: this.access, + refresh: this.refresh, + })); + } catch (e) { /* localStorage недоступен (Safari private mode) — игнорим */ } + }, + load: function () { + try { + const raw = localStorage.getItem(this._key); + if (!raw) return null; + const data = JSON.parse(raw); + this.access = data.access || null; + this.refresh = data.refresh || null; + return this.access; + } catch (e) { + return null; + } + }, + clear: function () { + this.access = null; + this.refresh = null; + this.user = null; + try { localStorage.removeItem(this._key); } catch (e) {} }, - load: function () { return this.access; }, - clear: function () { this.access = null; this.refresh = null; this.user = null; }, }; +// сразу восстановим сессию при загрузке скрипта (нужно для refresh страницы) +Store.load(); + const Auth = { loginEmail: function (email, password, remember) { return apiPost('/api/v1/auth/login', { email: email, password: password, remember: !!remember }); }, registerEmail: function (data) { return apiPost('/api/v1/auth/register', data); }, diff --git a/public/js/app.js b/public/js/app.js index c096a7c..04b620a 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -231,7 +231,7 @@ const App = { `; document.getElementById('el-login').onclick = () => this.emailLogin(); - document.getElementById('el-register').onclick = () => this.emailRegisterPre(); + document.getElementById('el-register').onclick = () => this.showEmailRegister(); const update = () => { const email = document.getElementById('el-email').value.trim(); @@ -260,6 +260,127 @@ const App = { this.loadRecentUsers(); }, + // === ОТДЕЛЬНЫЙ ЭКРАН РЕГИСТРАЦИИ === + showEmailRegister() { + document.getElementById('app').innerHTML = ` +
+

Регистрация

+
Создай аккаунт за 30 секунд
+
+ + + + + + + + + + + + + +
+
+
+ `; + + const update = () => { + const name = document.getElementById('rg-name').value.trim(); + const email = document.getElementById('rg-email').value.trim(); + const pass = document.getElementById('rg-pass').value; + const pass2 = document.getElementById('rg-pass2').value; + const adult = document.getElementById('rg-adult').checked; + const terms = document.getElementById('rg-terms').checked; + const btn = document.getElementById('rg-submit'); + const hint = document.getElementById('rg-hint'); + let msg = ''; + let ready = true; + if (name.length < 1) { msg = 'Введите имя'; ready = false; } + else if (!email.includes('@') || !email.includes('.')) { msg = 'Некорректный email'; ready = false; } + else if (pass.length < 8) { msg = 'Пароль минимум 8 символов (' + pass.length + '/8)'; ready = false; } + else if (pass !== pass2) { msg = 'Пароли не совпадают'; ready = false; } + else if (!adult) { msg = 'Только для лиц 18+'; ready = false; } + else if (!terms) { msg = 'Примите соглашение'; ready = false; } + else { msg = '✓ Всё готово'; } + btn.disabled = !ready; + hint.textContent = msg; + hint.style.color = ready ? 'var(--success)' : (msg.startsWith('✓') ? 'var(--success)' : 'var(--text-dim)'); + }; + ['rg-name','rg-email','rg-pass','rg-pass2','rg-adult','rg-terms'].forEach((id) => { + document.getElementById(id).addEventListener('input', update); + document.getElementById(id).addEventListener('change', update); + }); + update(); + + document.getElementById('rg-submit').onclick = () => this.emailRegister(); + document.getElementById('rg-back').onclick = () => this.showEmailLogin(); + }, + + async emailRegister() { + const name = document.getElementById('rg-name').value.trim(); + const email = document.getElementById('rg-email').value.trim(); + const pass = document.getElementById('rg-pass').value; + const pass2 = document.getElementById('rg-pass2').value; + const adult = document.getElementById('rg-adult').checked; + const terms = document.getElementById('rg-terms').checked; + const remember = document.getElementById('rg-remember').checked; + const btn = document.getElementById('rg-submit'); + const hint = document.getElementById('rg-hint'); + + // повторная проверка на стороне клиента + if (!name || !email.includes('@') || pass.length < 8 || pass !== pass2 || !adult || !terms) { + hint.textContent = 'Проверьте поля'; + hint.style.color = 'var(--danger)'; + return; + } + + btn.disabled = true; + btn.textContent = '⏳ Создаём аккаунт…'; + hint.textContent = 'Регистрация на сервере…'; + hint.style.color = 'var(--text-dim)'; + + try { + const r = await API.Auth.registerEmail({ + email, + password: pass, + name, + consents: { adult: true, terms: true, privacy: true, disclaimer: true }, + remember: remember, + }); + if (r && r.tokens) API.Store.save(r.tokens); + const me = await API.Auth.me(); + API.Store.user = me.user || me; + btn.textContent = '✓ Готово'; + hint.textContent = 'Добро пожаловать, ' + name + '!'; + hint.style.color = 'var(--success)'; + this.onLogin(); + await this.maybeOnboard(); + this.showView('map'); + } catch (e) { + btn.disabled = false; + btn.textContent = 'Создать аккаунт'; + const msg = (e && e.message) || 'Неизвестная ошибка'; + hint.textContent = '⚠️ ' + msg; + hint.style.color = 'var(--danger)'; + if (msg.includes('email')) { + // подсветить поле email красным + const el = document.getElementById('rg-email'); + if (el) el.style.borderColor = 'var(--danger)'; + } + console.error('register failed:', e); + } + }, + async loadRecentUsers() { try { const r = await api('/api/v1/users/recent?limit=8'); @@ -288,19 +409,6 @@ const App = { } }, - // Сначала consent overlay, потом реальная регистрация - emailRegisterPre() { - const email = document.getElementById('el-email').value.trim(); - const pass = document.getElementById('el-pass').value; - if (!email || pass.length < 8) { - TG_APP.showAlert('Email и пароль (≥8 символов) обязательны'); - return; - } - // проверим дубликат email - this._preReg = () => this.emailRegister(); - this.showConsent(); - }, - async emailLogin() { const email = document.getElementById('el-email').value.trim(); const pass = document.getElementById('el-pass').value; @@ -318,43 +426,6 @@ const App = { } }, - async emailRegister() { - const email = document.getElementById('el-email').value.trim(); - const pass = document.getElementById('el-pass').value; - // показываем сообщение о процессе - const ov = document.createElement('div'); - ov.id = 'consent-overlay'; - ov.innerHTML = ` -
-
⏳
-
Регистрация...
-
- `; - const target = document.querySelector('body.mobile-mode #mobile-frame') || document.body; - target.appendChild(ov); - - try { - const remember = document.getElementById('el-remember')?.checked ?? true; - const r = await API.Auth.registerEmail({ - email, - password: pass, - name: email.split('@')[0], - consents: { adult: true, terms: true, privacy: true, disclaimer: true }, - remember: remember, - }); - if (r && r.tokens) API.Store.save(r.tokens); - await API.Auth.me(); - ov.remove(); - await this.maybeOnboard(); - this.showView('map'); - } catch (e) { - ov.remove(); - TG_APP.showAlert('Ошибка регистрации: ' + (e?.message || 'unknown')); - // вернёмся к форме - this.showEmailLogin(); - } - }, - async maybeOnboard() { if (this._onboardRunning) return; this._onboardRunning = true;