Fix session persistence + полный рерайт регистрации
FIX SESSION:
- Store.save() теперь сохраняет в localStorage ('buhapp_session_v1')
- Store.load() восстанавливает access+refresh
- Store.clear() чистит и память и localStorage
- При загрузке api.js сразу Store.load() — сессия восстанавливается автоматически
- При refresh страницы init() видит Store.access и сразу делает me()
REGISTRATION REDO:
- Отдельный экран showEmailRegister() (не на том же экране что login)
- Поля: имя, email, пароль, пароль2, 18+, соглашения, запомнить
- Полная валидация в реальном времени (имя не пустое, email валидный,
пароль ≥8, пароли совпадают, чекбоксы отмечены)
- Кнопка disabled пока форма не готова, hint показывает что не так
- После успешной регистрации: сразу показ 'Добро пожаловать!' + переход на карту
- Ошибка от сервера — показывается в hint + подсветка поля email красным
- Удалён старый emailRegisterPre и дублирующий emailRegister
This commit is contained in:
parent
1f02bf04e5
commit
a2cc623756
@ -34,15 +34,15 @@
|
||||
</button>
|
||||
</nav>
|
||||
</div>
|
||||
<script src="/js/tg.js?v=21"></script>
|
||||
<script src="/js/api.js?v=21"></script>
|
||||
<script src="/js/views/map.js?v=21"></script>
|
||||
<script src="/js/views/chats.js?v=21"></script>
|
||||
<script src="/js/views/chat.js?v=21"></script>
|
||||
<script src="/js/views/profile.js?v=21"></script>
|
||||
<script src="/js/views/settings.js?v=21"></script>
|
||||
<script src="/js/views/onboarding.js?v=21"></script>
|
||||
<script src="/js/views/review.js?v=21"></script>
|
||||
<script src="/js/app.js?v=21"></script>
|
||||
<script src="/js/tg.js?v=22"></script>
|
||||
<script src="/js/api.js?v=22"></script>
|
||||
<script src="/js/views/map.js?v=22"></script>
|
||||
<script src="/js/views/chats.js?v=22"></script>
|
||||
<script src="/js/views/chat.js?v=22"></script>
|
||||
<script src="/js/views/profile.js?v=22"></script>
|
||||
<script src="/js/views/settings.js?v=22"></script>
|
||||
<script src="/js/views/onboarding.js?v=22"></script>
|
||||
<script src="/js/views/review.js?v=22"></script>
|
||||
<script src="/js/app.js?v=22"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -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); },
|
||||
|
||||
173
public/js/app.js
173
public/js/app.js
@ -231,7 +231,7 @@ const App = {
|
||||
</div>
|
||||
`;
|
||||
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 = `
|
||||
<div class="view">
|
||||
<h1>Регистрация</h1>
|
||||
<div class="sub">Создай аккаунт за 30 секунд</div>
|
||||
<div class="card">
|
||||
<label>Имя (как к тебе обращаться)</label>
|
||||
<input class="input" id="rg-name" type="text" placeholder="Саша" autocomplete="given-name">
|
||||
<label>Email</label>
|
||||
<input class="input" id="rg-email" type="email" placeholder="you@example.com" autocomplete="email">
|
||||
<label>Пароль (≥8 символов)</label>
|
||||
<input class="input" id="rg-pass" type="password" placeholder="минимум 8 символов" autocomplete="new-password">
|
||||
<label>Пароль ещё раз</label>
|
||||
<input class="input" id="rg-pass2" type="password" placeholder="повтори пароль" autocomplete="new-password">
|
||||
<label class="checkrow">
|
||||
<input type="checkbox" id="rg-adult" checked>
|
||||
<span>Мне уже есть 18 лет</span>
|
||||
</label>
|
||||
<label class="checkrow">
|
||||
<input type="checkbox" id="rg-terms" checked>
|
||||
<span>Принимаю <a href="/legal/TERMS.html" target="_blank">соглашение</a></span>
|
||||
</label>
|
||||
<label class="checkrow">
|
||||
<input type="checkbox" id="rg-remember" checked>
|
||||
<span>Запомнить меня (30 дней)</span>
|
||||
</label>
|
||||
<button class="btn" id="rg-submit">Создать аккаунт</button>
|
||||
<button class="btn ghost" id="rg-back">← Назад ко входу</button>
|
||||
<div class="sub" id="rg-hint" style="margin-top:8px;text-align:center;"></div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
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 = `
|
||||
<div style="text-align:center;padding:48px 16px;">
|
||||
<div style="font-size:32px;margin-bottom:12px;">⏳</div>
|
||||
<div>Регистрация...</div>
|
||||
</div>
|
||||
`;
|
||||
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;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user