Добавить promo-регистрацию и оффлайн генератор промокодов

This commit is contained in:
AidarKC
2026-06-30 17:39:40 +04:00
parent b0887648f7
commit 0a416a2f5c
12 changed files with 1154 additions and 42 deletions
+81 -1
View File
@@ -71,6 +71,7 @@ export function render({ navigate }) {
let passwordMode = String(state.registrationDraft.passwordMode || 'single') === 'words' ? 'words' : 'single';
let passwordWords = normalizePasswordWords(state.registrationDraft.passwordWords);
let usePromoCode = Boolean(state.registrationDraft.usePromoCode);
const loginInput = document.createElement('input');
loginInput.className = 'input';
@@ -117,6 +118,40 @@ export function render({ navigate }) {
passwordModeToggle.append(passwordModeCheckbox, passwordModeLabel);
const promoToggle = document.createElement('label');
promoToggle.className = 'registration-toggle';
const promoCheckbox = document.createElement('input');
promoCheckbox.type = 'checkbox';
promoCheckbox.checked = usePromoCode;
const promoToggleLabel = document.createElement('span');
promoToggleLabel.textContent = 'У меня есть промокод';
promoToggle.append(promoCheckbox, promoToggleLabel);
const promoField = document.createElement('label');
promoField.className = 'stack';
const promoFieldLabel = document.createElement('span');
promoFieldLabel.className = 'field-label';
promoFieldLabel.textContent = 'Промокод';
const promoInput = document.createElement('input');
promoInput.className = 'input';
promoInput.type = 'text';
promoInput.autocomplete = 'off';
promoInput.autocapitalize = 'off';
promoInput.spellcheck = false;
promoInput.value = String(state.registrationDraft.promoCode || '');
promoInput.placeholder = 'Вставьте промокод';
const promoHint = document.createElement('p');
promoHint.className = 'meta-muted';
promoHint.textContent = 'Если промокод включён, обычная проверка premium/trademark логина пропускается.';
promoField.append(promoFieldLabel, promoInput, promoHint);
const statusText = document.createElement('p');
statusText.className = 'meta-muted';
statusText.textContent = 'Проверка логина: не выполнена';
@@ -202,6 +237,12 @@ export function render({ navigate }) {
state.registrationDraft.passwordMode = passwordMode;
state.registrationDraft.passwordWords = normalizePasswordWords(passwordWords);
state.registrationDraft.password = getCurrentPassword();
state.registrationDraft.usePromoCode = usePromoCode;
state.registrationDraft.promoCode = String(promoInput.value || '').trim();
}
function updatePromoVisibility() {
promoField.style.display = usePromoCode ? 'grid' : 'none';
}
async function runAvailabilityCheck() {
@@ -212,6 +253,16 @@ export function render({ navigate }) {
return false;
}
if (usePromoCode) {
lastCheckedLogin = login;
lastCheckedFree = true;
lastCheckedClassName = 'promo';
statusText.textContent = 'Промокод включён: обычная проверка premium/trademark логина пропущена';
statusText.className = 'status-line is-available';
formError.style.display = 'none';
return true;
}
if (login === lastCheckedLogin) {
if (!lastCheckedFree) {
statusText.textContent = 'Логин уже занят ❌';
@@ -320,8 +371,34 @@ export function render({ navigate }) {
syncDraftState();
});
promoCheckbox.addEventListener('change', () => {
usePromoCode = promoCheckbox.checked;
lastCheckedLogin = '';
lastCheckedFree = false;
lastCheckedClassName = '';
updatePromoVisibility();
syncDraftState();
if (usePromoCode) {
statusText.textContent = 'Промокод включён: проверка логина будет пропущена';
statusText.className = 'status-line is-available';
} else {
statusText.textContent = 'Проверка логина: не выполнена';
statusText.className = 'meta-muted';
}
});
promoInput.addEventListener('input', () => {
syncDraftState();
});
nextButton.addEventListener('click', async () => {
formError.style.display = 'none';
const promoCode = String(promoInput.value || '').trim();
if (usePromoCode && !promoCode) {
formError.textContent = 'Если включён промокод, поле промокода должно быть заполнено.';
formError.style.display = '';
return;
}
const isFree = await runAvailabilityCheck();
if (!isFree) return;
@@ -345,6 +422,8 @@ export function render({ navigate }) {
state.registrationDraft.password = nextPassword;
state.registrationDraft.passwordMode = passwordMode;
state.registrationDraft.passwordWords = normalizePasswordWords(passwordWords);
state.registrationDraft.usePromoCode = usePromoCode;
state.registrationDraft.promoCode = promoCode;
if (credsChanged) {
state.registrationDraft.preGeneratedKeyBundle = null;
}
@@ -363,10 +442,11 @@ export function render({ navigate }) {
passwordField = form.children[1];
loginField.append(loginInput);
passwordField.append(passwordInput);
form.append(passwordModeToggle, passwordLengthText, wordsSection, statusText, checkButton, formError);
form.append(passwordModeToggle, promoToggle, promoField, passwordLengthText, wordsSection, statusText, checkButton, formError);
actions.innerHTML = '';
actions.append(backButton, nextButton);
updatePasswordModeVisibility();
updatePromoVisibility();
syncDraftState();
}