work-license-server 1.0.7 → 1.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/landing.js +307 -268
- package/package.json +1 -1
- package/src/landing.js +155 -292
- package/src/server.js +238 -2
- package/src/store.js +109 -15
package/assets/landing.js
CHANGED
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
}
|
|
68
68
|
if (!$('#stage-' + id)) id = 'product';
|
|
69
69
|
currentStage = id;
|
|
70
|
-
document.body.setAttribute('data-stage', id);
|
|
70
|
+
document.body.setAttribute('data-active-stage', id);
|
|
71
71
|
$$('.tab').forEach(t => t.classList.toggle('active', t.dataset.stage === id));
|
|
72
72
|
$$('.stage').forEach(s => s.classList.toggle('active', s.id === 'stage-' + id));
|
|
73
73
|
moveIndicator();
|
|
@@ -80,7 +80,7 @@
|
|
|
80
80
|
if (scroll) window.scrollTo({ top: 0, behavior: reduceMotion ? 'auto' : 'smooth' });
|
|
81
81
|
if (window.setBackdropStage) window.setBackdropStage(id);
|
|
82
82
|
observeReveals();
|
|
83
|
-
if (id === '
|
|
83
|
+
if (id === 'product') playConsultantMock();
|
|
84
84
|
if (id === 'franchise') requestAnimationFrame(() => MAP.ready && applyView());
|
|
85
85
|
}
|
|
86
86
|
window.switchStage = switchStage;
|
|
@@ -129,15 +129,26 @@
|
|
|
129
129
|
card.style.setProperty('--my', (e.clientY - r.top) + 'px');
|
|
130
130
|
}, { passive: true });
|
|
131
131
|
|
|
132
|
-
/* =================
|
|
133
|
-
function openAuth(hintMsg = '') {
|
|
132
|
+
/* ================= Вход / Регистрация (Email, Мессенджеры, OAuth) ================= */
|
|
133
|
+
function openAuth(hintMsg = '', intentTitle = '') {
|
|
134
134
|
if ($('#authErrorMsg')) $('#authErrorMsg').textContent = hintMsg || '';
|
|
135
|
+
const intentBox = $('#authIntentNotice');
|
|
136
|
+
if (intentBox) {
|
|
137
|
+
if (intentTitle) {
|
|
138
|
+
intentBox.style.display = '';
|
|
139
|
+
intentBox.innerHTML = `Выбранная позиция: <b>${esc(intentTitle)}</b>.<br>После входа она автоматически появится как заказ в вашем Личном кабинете.`;
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
intentBox.style.display = 'none';
|
|
143
|
+
intentBox.textContent = '';
|
|
144
|
+
}
|
|
145
|
+
}
|
|
135
146
|
if ($('#btnLogoutModal')) $('#btnLogoutModal').style.display = currentUser ? '' : 'none';
|
|
136
|
-
$('#authModal')
|
|
147
|
+
$('#authModal')?.classList.add('open');
|
|
137
148
|
setTimeout(() => $('#authEmail')?.focus(), 50);
|
|
138
149
|
}
|
|
139
150
|
function closeAuth() {
|
|
140
|
-
$('#authModal')
|
|
151
|
+
$('#authModal')?.classList.remove('open');
|
|
141
152
|
if ($('#authErrorMsg')) $('#authErrorMsg').textContent = '';
|
|
142
153
|
}
|
|
143
154
|
function openOfertaModal() {
|
|
@@ -146,59 +157,81 @@
|
|
|
146
157
|
function closeOfertaModal() {
|
|
147
158
|
$('#ofertaModal')?.classList.remove('open');
|
|
148
159
|
}
|
|
149
|
-
function requireAuthAndOferta(actionFn,
|
|
160
|
+
function requireAuthAndOferta(actionFn, opts = {}) {
|
|
150
161
|
if (isUserReadyForOrders()) {
|
|
151
162
|
if (typeof actionFn === 'function') actionFn();
|
|
152
163
|
return true;
|
|
153
164
|
}
|
|
154
165
|
pendingAuthAction = typeof actionFn === 'function' ? actionFn : null;
|
|
155
|
-
|
|
156
|
-
if (prefill.inn && $('#authInn')) $('#authInn').value = prefill.inn;
|
|
157
|
-
if (prefill.email && $('#authEmail')) $('#authEmail').value = prefill.email;
|
|
158
|
-
openAuth('Для оформления заказа, оплаты или резервирования франшизы зарегистрируйтесь и подпишите договор-оферту (ПЭП).');
|
|
166
|
+
openAuth('', opts.intentTitle || '');
|
|
159
167
|
return false;
|
|
160
168
|
}
|
|
161
169
|
|
|
162
|
-
|
|
170
|
+
function resetAuthStep() {
|
|
171
|
+
if ($('#authStepEmail')) $('#authStepEmail').style.display = '';
|
|
172
|
+
if ($('#authStepCode')) $('#authStepCode').style.display = 'none';
|
|
173
|
+
if ($('#authCodeInput')) $('#authCodeInput').value = '';
|
|
174
|
+
if ($('#authErrorMsg')) $('#authErrorMsg').textContent = '';
|
|
175
|
+
setTimeout(() => $('#authEmail')?.focus(), 50);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async function sendAuthCode() {
|
|
163
179
|
const errBox = $('#authErrorMsg');
|
|
164
180
|
if (errBox) errBox.textContent = '';
|
|
165
181
|
const email = $('#authEmail')?.value.trim() || '';
|
|
166
|
-
const
|
|
167
|
-
const inn = $('#authInn')?.value.trim() || '';
|
|
168
|
-
const signer = $('#authSigner')?.value.trim() || '';
|
|
169
|
-
const licenseId = $('#authLicId')?.value.trim() || '';
|
|
170
|
-
const ofertaChecked = Boolean($('#authOfertaCheck')?.checked);
|
|
171
|
-
|
|
182
|
+
const ofertaChecked = $('#authOfertaCheck') ? Boolean($('#authOfertaCheck').checked) : true;
|
|
172
183
|
if (!email || !email.includes('@')) {
|
|
173
|
-
if (errBox) errBox.textContent = '
|
|
174
|
-
return;
|
|
175
|
-
}
|
|
176
|
-
if (!companyName || !inn) {
|
|
177
|
-
if (errBox) errBox.textContent = 'Укажите наименование организации и ИНН.';
|
|
184
|
+
if (errBox) errBox.textContent = 'Введите ваш корректный email адрес.';
|
|
178
185
|
return;
|
|
179
186
|
}
|
|
180
187
|
if (!ofertaChecked) {
|
|
181
|
-
if (errBox) errBox.textContent = '
|
|
188
|
+
if (errBox) errBox.textContent = 'Подтвердите согласие с условиями Публичного договора-оферты.';
|
|
182
189
|
return;
|
|
183
190
|
}
|
|
191
|
+
const btn = $('#btnSendCode');
|
|
192
|
+
if (btn) btn.disabled = true;
|
|
193
|
+
try {
|
|
194
|
+
const res = await api('/api/auth/send-code', { email });
|
|
195
|
+
if ($('#authStepEmail')) $('#authStepEmail').style.display = 'none';
|
|
196
|
+
if ($('#authStepCode')) $('#authStepCode').style.display = '';
|
|
197
|
+
if ($('#authCodeSentNotice')) {
|
|
198
|
+
$('#authCodeSentNotice').textContent = res.message || `Код отправлен на ${email}. Введите его ниже.`;
|
|
199
|
+
}
|
|
200
|
+
if ($('#authCodeInput')) {
|
|
201
|
+
$('#authCodeInput').value = '';
|
|
202
|
+
setTimeout(() => $('#authCodeInput')?.focus(), 50);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
catch (err) {
|
|
206
|
+
if (errBox) errBox.textContent = err.message;
|
|
207
|
+
}
|
|
208
|
+
finally {
|
|
209
|
+
if (btn) btn.disabled = false;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
184
212
|
|
|
213
|
+
async function verifyAuthCode() {
|
|
214
|
+
const errBox = $('#authErrorMsg');
|
|
215
|
+
if (errBox) errBox.textContent = '';
|
|
216
|
+
const email = $('#authEmail')?.value.trim() || '';
|
|
217
|
+
const code = $('#authCodeInput')?.value.trim() || '';
|
|
218
|
+
if (!code || code.length < 6) {
|
|
219
|
+
if (errBox) errBox.textContent = 'Введите 6-значный код из письма.';
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
const btn = $('#btnVerifyCode');
|
|
223
|
+
if (btn) btn.disabled = true;
|
|
185
224
|
try {
|
|
186
|
-
const data = await api('/api/auth/
|
|
187
|
-
email,
|
|
188
|
-
companyName,
|
|
189
|
-
inn,
|
|
190
|
-
name: signer || companyName,
|
|
191
|
-
licenseId,
|
|
192
|
-
ofertaAccepted: true,
|
|
193
|
-
});
|
|
225
|
+
const data = await api('/api/auth/verify-code', { email, code });
|
|
194
226
|
currentUser = data;
|
|
195
227
|
try { localStorage.setItem('odant_work_session', data.sessionKey); } catch { /* приватный режим */ }
|
|
228
|
+
resetAuthStep();
|
|
196
229
|
onAuthenticated();
|
|
197
230
|
closeAuth();
|
|
198
231
|
const nextAction = pendingAuthAction;
|
|
199
232
|
pendingAuthAction = null;
|
|
200
233
|
if (typeof nextAction === 'function') {
|
|
201
|
-
nextAction();
|
|
234
|
+
await nextAction();
|
|
202
235
|
}
|
|
203
236
|
else {
|
|
204
237
|
switchStage('addons');
|
|
@@ -207,17 +240,18 @@
|
|
|
207
240
|
catch (err) {
|
|
208
241
|
if (errBox) errBox.textContent = err.message;
|
|
209
242
|
}
|
|
243
|
+
finally {
|
|
244
|
+
if (btn) btn.disabled = false;
|
|
245
|
+
}
|
|
210
246
|
}
|
|
211
247
|
|
|
212
248
|
function performLogout() {
|
|
213
249
|
currentUser = null;
|
|
214
250
|
pendingAuthAction = null;
|
|
215
251
|
try { localStorage.removeItem('odant_work_session'); } catch { /* ignore */ }
|
|
216
|
-
$('#tileAddons').style.display = 'none';
|
|
217
|
-
$('#btnAuthText').textContent = 'Войти / Регистрация';
|
|
252
|
+
if ($('#tileAddons')) $('#tileAddons').style.display = 'none';
|
|
253
|
+
if ($('#btnAuthText')) $('#btnAuthText').textContent = 'Войти / Регистрация';
|
|
218
254
|
if ($('#btnLogoutModal')) $('#btnLogoutModal').style.display = 'none';
|
|
219
|
-
updateOfertaBanners();
|
|
220
|
-
updateClientSupportCard();
|
|
221
255
|
closeAuth();
|
|
222
256
|
if (currentStage === 'addons') switchStage('product');
|
|
223
257
|
moveIndicator();
|
|
@@ -226,11 +260,7 @@
|
|
|
226
260
|
async function restoreSession() {
|
|
227
261
|
let key = '';
|
|
228
262
|
try { key = localStorage.getItem('odant_work_session') || ''; } catch { /* приватный режим */ }
|
|
229
|
-
if (!key)
|
|
230
|
-
updateOfertaBanners();
|
|
231
|
-
updateClientSupportCard();
|
|
232
|
-
return;
|
|
233
|
-
}
|
|
263
|
+
if (!key) return;
|
|
234
264
|
try {
|
|
235
265
|
const data = await api('/api/auth/me?sessionKey=' + encodeURIComponent(key));
|
|
236
266
|
if (data.authenticated && data.user) {
|
|
@@ -240,98 +270,74 @@
|
|
|
240
270
|
}
|
|
241
271
|
}
|
|
242
272
|
catch { /* сессия устарела */ }
|
|
243
|
-
updateOfertaBanners();
|
|
244
|
-
updateClientSupportCard();
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
function updateOfertaBanners() {
|
|
248
|
-
const ready = isUserReadyForOrders();
|
|
249
|
-
$$('[data-oferta-banner]').forEach(box => {
|
|
250
|
-
box.classList.toggle('signed', ready);
|
|
251
|
-
if (ready) {
|
|
252
|
-
box.innerHTML = `
|
|
253
|
-
<div>
|
|
254
|
-
<span class="tag ok">✓ Договор-оферта подписан ПЭП</span>
|
|
255
|
-
<div style="margin-top:6px;font-size:13.5px">
|
|
256
|
-
Организация: <b>${esc(currentUser.companyName)}</b> (ИНН ${esc(currentUser.inn || '—')}) · Подписант: <b>${esc(currentUser.ofertaSignerName || currentUser.name || '—')}</b> · Штамп ПЭП: <code>${esc(currentUser.ofertaSignatureId || 'PEP')}</code>
|
|
257
|
-
</div>
|
|
258
|
-
</div>
|
|
259
|
-
<div style="display:flex;gap:8px;flex-wrap:wrap">
|
|
260
|
-
<button class="btn btn-ghost btn-sm" data-action="open-oferta">Текст оферты</button>
|
|
261
|
-
<button class="btn btn-ghost btn-sm" data-action="auth">Профиль</button>
|
|
262
|
-
</div>`;
|
|
263
|
-
}
|
|
264
|
-
else {
|
|
265
|
-
box.innerHTML = `
|
|
266
|
-
<div>
|
|
267
|
-
<span class="tag">Требуется регистрация и акцепт оферты</span>
|
|
268
|
-
<div style="margin-top:6px;font-size:13.5px;color:var(--text-2)">
|
|
269
|
-
Любые действия по заказу комплекса, докупке лицензий, оплате через ЮKassa и резервированию региональной франшизы выполняются после регистрации и подписания Публичного договора-оферты (ПЭП).
|
|
270
|
-
</div>
|
|
271
|
-
</div>
|
|
272
|
-
<div style="display:flex;gap:8px;flex-wrap:wrap">
|
|
273
|
-
<button class="btn btn-ghost btn-sm" data-action="open-oferta">Читать оферту</button>
|
|
274
|
-
<button class="btn btn-primary btn-sm" data-action="auth">Войти и подписать оферту</button>
|
|
275
|
-
</div>`;
|
|
276
|
-
}
|
|
277
|
-
});
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
function updateClientSupportCard() {
|
|
281
|
-
const box = $('#clientLicenseBox');
|
|
282
|
-
if (!box) return;
|
|
283
|
-
if (!currentUser || !currentUser.license) {
|
|
284
|
-
box.textContent = 'Войдите в личный кабинет и подпишите договор-оферту, чтобы получить код привязки вашего комплекса к ботам MAX, Telegram и VK.';
|
|
285
|
-
return;
|
|
286
|
-
}
|
|
287
|
-
const lic = currentUser.license;
|
|
288
|
-
const bindCode = lic.support?.bindCode || lic.licenseId;
|
|
289
|
-
box.textContent = [
|
|
290
|
-
`Организация: ${currentUser.companyName} (ИНН ${currentUser.inn || '—'})`,
|
|
291
|
-
`Оферта ПЭП: ${currentUser.ofertaAccepted ? ('подписана · ' + (currentUser.ofertaSignatureId || '')) : 'ожидает подписания'}`,
|
|
292
|
-
`Лицензия ПАК: ${lic.licenseId}`,
|
|
293
|
-
`Код привязки мессенджер-бота: ${bindCode}`,
|
|
294
|
-
`Команда для чата MAX / Telegram / VK: /bind ${bindCode}`,
|
|
295
|
-
`Рабочие места: ${lic.totalSeats} (${lic.baseSeats} вечных + ${lic.addonSeats} годовых)`,
|
|
296
|
-
`Уровень поддержки SLA: ${lic.support?.tierName || 'Enterprise'} (${lic.support?.reactionLabel || 'до 15 мин'})`,
|
|
297
|
-
`Остаток обращений: ${lic.support?.ticketsRemaining ?? 0} из ${lic.support?.ticketsQuota ?? 0}`,
|
|
298
|
-
].join('\n');
|
|
299
273
|
}
|
|
300
274
|
|
|
301
275
|
function onAuthenticated() {
|
|
302
276
|
if (!currentUser) return;
|
|
303
|
-
$('#tileAddons').style.display = isUserReadyForOrders() ? '' : 'none';
|
|
304
|
-
$('#btnAuthText').textContent = currentUser.companyName || currentUser.email;
|
|
305
|
-
if ($('#inpLicId')) $('#inpLicId').value = currentUser.licenseId || '';
|
|
277
|
+
if ($('#tileAddons')) $('#tileAddons').style.display = isUserReadyForOrders() ? '' : 'none';
|
|
278
|
+
if ($('#btnAuthText')) $('#btnAuthText').textContent = currentUser.companyName || currentUser.email;
|
|
306
279
|
if ($('#inpCabinetHubToken')) $('#inpCabinetHubToken').value = currentUser.hubToken || '';
|
|
307
280
|
if ($('#authEmail')) $('#authEmail').value = currentUser.email || '';
|
|
308
|
-
if ($('#authCompany')) $('#authCompany').value = currentUser.companyName || '';
|
|
309
|
-
if ($('#authInn') && currentUser.inn) $('#authInn').value = currentUser.inn;
|
|
310
|
-
if ($('#authSigner') && (currentUser.ofertaSignerName || currentUser.name)) $('#authSigner').value = currentUser.ofertaSignerName || currentUser.name;
|
|
311
|
-
if ($('#authLicId')) $('#authLicId').value = currentUser.licenseId || '';
|
|
312
281
|
if ($('#authOfertaCheck')) $('#authOfertaCheck').checked = Boolean(currentUser.ofertaAccepted);
|
|
313
|
-
if ($('#
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
if ($('#inpPartnerCompany') && currentUser.companyName) $('#inpPartnerCompany').value = currentUser.companyName;
|
|
317
|
-
if ($('#inpPartnerInn') && currentUser.inn) $('#inpPartnerInn').value = currentUser.inn;
|
|
318
|
-
if ($('#inpPartnerEmail') && currentUser.email) $('#inpPartnerEmail').value = currentUser.email;
|
|
319
|
-
if ($('#botText') && currentUser.license?.support?.bindCode && $('#botText').value === '/start') {
|
|
320
|
-
$('#botText').value = '/bind ' + currentUser.license.support.bindCode;
|
|
321
|
-
}
|
|
322
|
-
$('#userBadgeBox').innerHTML = `${esc(currentUser.companyName)} (ИНН ${esc(currentUser.inn || '—')}) · лицензия <b>${esc(currentUser.licenseId)}</b> · ПЭП <code>${esc(currentUser.ofertaSignatureId || '—')}</code>`;
|
|
323
|
-
if (currentUser.license?.baseActivated && $('#segCabinetBase')) {
|
|
324
|
-
$$('#segCabinetBase button').forEach(b => b.classList.toggle('on', b.dataset.base === 'none'));
|
|
325
|
-
if (Number($('#inpSeats')?.value) === 0 && $('#inpSeats')) {
|
|
326
|
-
$('#inpSeats').value = 5;
|
|
327
|
-
if ($('#rngAddonSeats')) $('#rngAddonSeats').value = 5;
|
|
328
|
-
}
|
|
282
|
+
if ($('#userBadgeBox')) {
|
|
283
|
+
const orgInfo = currentUser.companyName ? `${esc(currentUser.companyName)}${currentUser.inn ? ' (ИНН ' + esc(currentUser.inn) + ')' : ''} · ` : '';
|
|
284
|
+
$('#userBadgeBox').innerHTML = `${orgInfo}Аккаунт: <b>${esc(currentUser.email)}</b> · <a href="#" data-action="logout" style="color:var(--muted)">Выйти</a>`;
|
|
329
285
|
}
|
|
286
|
+
renderCabinetOrders();
|
|
330
287
|
renderCabinetLicenses();
|
|
331
|
-
updateOfertaBanners();
|
|
332
|
-
updateClientSupportCard();
|
|
333
288
|
moveIndicator();
|
|
334
|
-
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function renderCabinetOrders() {
|
|
292
|
+
const box = $('#cabinetOrdersBox');
|
|
293
|
+
if (!box) return;
|
|
294
|
+
const orders = currentUser?.orders || [];
|
|
295
|
+
if (!orders.length) {
|
|
296
|
+
box.innerHTML = `<div style="padding:18px;border-radius:12px;border:1px dashed var(--border);color:var(--muted);text-align:center;">
|
|
297
|
+
В вашем Личном кабинете пока нет заказов. Перейдите в раздел <a href="#clients" data-stage="clients"><b>«Клиентам»</b></a> (чтобы купить лицензию или ПАК) или <a href="#franchise" data-stage="franchise"><b>«Франшиза»</b></a> (чтобы зарезервировать регион).
|
|
298
|
+
</div>`;
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
box.innerHTML = orders.map(o => {
|
|
302
|
+
const isPaid = o.status === 'paid';
|
|
303
|
+
const title = (o.items && o.items.length)
|
|
304
|
+
? o.items.map(it => esc(it.title)).join(' + ')
|
|
305
|
+
: esc(o.tierName || o.orderId);
|
|
306
|
+
const dateStr = o.createdAt ? new Date(o.createdAt).toLocaleString('ru-RU') : '';
|
|
307
|
+
const statusBadge = isPaid
|
|
308
|
+
? `<span class="tag ok">✓ Оплачен</span>`
|
|
309
|
+
: `<span class="tag">Ожидает оплаты</span>`;
|
|
310
|
+
const actionsHtml = isPaid
|
|
311
|
+
? `<div style="display:flex;gap:8px;flex-wrap:wrap;margin-top:10px">
|
|
312
|
+
${o.invoiceUrl ? `<a class="btn btn-ghost btn-sm" href="${esc(o.invoiceUrl)}" target="_blank" rel="noopener">Открыть счёт-договор (${esc(o.invoiceNumber || o.orderId)}) ↗</a>` : ''}
|
|
313
|
+
</div>`
|
|
314
|
+
: `<div style="display:flex;gap:10px;flex-wrap:wrap;margin-top:12px;align-items:center">
|
|
315
|
+
<button class="btn btn-primary btn-sm" data-pay-order="${esc(o.orderId)}" data-pay-method="yookassa">Оплатить через ЮKassa (${rub(o.amountRub)})</button>
|
|
316
|
+
<button class="btn btn-ghost btn-sm" data-invoice-toggle="${esc(o.orderId)}">Счёт-договор (для юр. лиц)</button>
|
|
317
|
+
<button class="btn btn-ghost btn-sm" data-cancel-order="${esc(o.orderId)}" style="color:var(--danger)">Удалить заказ</button>
|
|
318
|
+
</div>
|
|
319
|
+
<div id="invBox-${esc(o.orderId)}" style="display:none;margin-top:12px;padding:12px;border-radius:10px;border:1px solid var(--border);background:rgba(255,255,255,.02)">
|
|
320
|
+
<div class="small" style="margin-bottom:8px;font-weight:600">Реквизиты организации для выставления счёта-договора:</div>
|
|
321
|
+
<div class="row2">
|
|
322
|
+
<div class="field"><label>Наименование организации *</label><input type="text" id="invCompany-${esc(o.orderId)}" value="${esc(o.customer?.name || currentUser?.companyName || '')}" placeholder="ООО «Маслоторг»"></div>
|
|
323
|
+
<div class="field"><label>ИНН *</label><input type="text" id="invInn-${esc(o.orderId)}" value="${esc(o.customer?.inn || currentUser?.inn || '')}" placeholder="7701234567" inputmode="numeric"></div>
|
|
324
|
+
</div>
|
|
325
|
+
<button class="btn btn-primary btn-sm" style="margin-top:8px" data-pay-order="${esc(o.orderId)}" data-pay-method="invoice">Сформировать и открыть счёт-договор →</button>
|
|
326
|
+
</div>`;
|
|
327
|
+
return `<div style="padding:14px 16px;border-radius:12px;border:1px solid var(--border);margin-bottom:10px;">
|
|
328
|
+
<div style="display:flex;justify-content:space-between;gap:12px;flex-wrap:wrap;align-items:center">
|
|
329
|
+
<div>
|
|
330
|
+
<div style="font-size:12px;color:var(--muted)">Заказ <code>${esc(o.orderId)}</code> · ${esc(dateStr)}</div>
|
|
331
|
+
<div style="font-weight:700;font-size:15px;margin-top:2px">${title}</div>
|
|
332
|
+
</div>
|
|
333
|
+
<div style="text-align:right">
|
|
334
|
+
<div style="font-size:18px;font-weight:800">${rub(o.amountRub)}</div>
|
|
335
|
+
${statusBadge}
|
|
336
|
+
</div>
|
|
337
|
+
</div>
|
|
338
|
+
${actionsHtml}
|
|
339
|
+
</div>`;
|
|
340
|
+
}).join('');
|
|
335
341
|
}
|
|
336
342
|
|
|
337
343
|
function renderCabinetLicenses() {
|
|
@@ -339,9 +345,10 @@
|
|
|
339
345
|
if (!box) return;
|
|
340
346
|
const lic = currentUser?.license;
|
|
341
347
|
const files = lic?.licenseFiles || [];
|
|
342
|
-
|
|
348
|
+
const reservations = (currentUser?.reservations || []).filter(r => r.depositPaid);
|
|
349
|
+
if (!files.length && !reservations.length) {
|
|
343
350
|
box.innerHTML = `<div style="padding:12px 14px;border-radius:10px;border:1px dashed var(--border);color:var(--muted);">
|
|
344
|
-
|
|
351
|
+
Активных оплаченных лицензий пока нет. После оплаты заказа выше подписанные файлы лицензий (<code>.lic.json</code>) сразу появятся здесь и автоматически загрузятся в ваш локальный WORK по токену владельца.
|
|
345
352
|
</div>`;
|
|
346
353
|
return;
|
|
347
354
|
}
|
|
@@ -355,7 +362,11 @@
|
|
|
355
362
|
<div style="text-align:right"><span class="pill" style="padding:3px 10px;font-size:11.5px">${esc(expText)}</span></div>
|
|
356
363
|
</div>`;
|
|
357
364
|
}).join('');
|
|
358
|
-
|
|
365
|
+
const resRows = reservations.map(r => `<div style="display:flex;justify-content:space-between;gap:12px;padding:8px 0;border-bottom:1px solid var(--border);align-items:center;">
|
|
366
|
+
<div><b>Региональная франшиза: ${esc(r.regionName)}</b><br><code style="font-size:11.5px;color:var(--muted)">${esc(r.reservationId)}</code></div>
|
|
367
|
+
<div style="text-align:right"><span class="pill" style="padding:3px 10px;font-size:11.5px">Залог оплачен</span></div>
|
|
368
|
+
</div>`).join('');
|
|
369
|
+
box.innerHTML = (files.length ? `<div>Всего доступно одновременных сессий: <b style="color:var(--text)">${lic.totalSeats}</b> (${lic.baseSeats} вечных + ${lic.addonSeats} временных)</div>` : '') + rows + resRows;
|
|
359
370
|
}
|
|
360
371
|
|
|
361
372
|
async function refreshCabinetUser() {
|
|
@@ -381,7 +392,7 @@
|
|
|
381
392
|
}
|
|
382
393
|
}
|
|
383
394
|
|
|
384
|
-
/* ================= Калькуляторы ================= */
|
|
395
|
+
/* ================= Калькуляторы раздела «Клиентам» ================= */
|
|
385
396
|
function volumeDiscount(seats) {
|
|
386
397
|
const s = Math.max(0, Math.floor(Number(seats) || 0));
|
|
387
398
|
return (D.discounts || []).find(d => s >= d.minSeats) || { discountPercent: 0, minSeats: 1 };
|
|
@@ -400,136 +411,147 @@
|
|
|
400
411
|
const tierById = (id) => (D.tiers || []).find(t => t.id === id) || D.tiers[1];
|
|
401
412
|
const lines = (rows) => rows.map(([k, v]) => `<div><span>${k}</span><b>${v}</b></div>`).join('');
|
|
402
413
|
|
|
403
|
-
function
|
|
404
|
-
const
|
|
405
|
-
const
|
|
406
|
-
const
|
|
414
|
+
function syncClientSeats(v) {
|
|
415
|
+
const variant = $('#segPakVariant button.on')?.dataset.variant || 'pak';
|
|
416
|
+
const minSeats = variant === 'addons_only' ? 1 : 0;
|
|
417
|
+
const n = Math.max(minSeats, Math.min(2000, Number(v) || 0));
|
|
418
|
+
if ($('#inpPakExtraSeats')) $('#inpPakExtraSeats').value = Math.min(150, n);
|
|
419
|
+
if ($('#inpPakExtraSeatsNum')) $('#inpPakExtraSeatsNum').value = n;
|
|
420
|
+
recalcBaseOrder();
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
function recalcBaseOrder() {
|
|
424
|
+
if (!$('#lblPakTotal')) return;
|
|
425
|
+
const variant = $('#segPakVariant button.on')?.dataset.variant || 'pak';
|
|
426
|
+
const includeBasePak = variant === 'pak';
|
|
427
|
+
const includeBaseLicenseOnly = variant === 'license_only';
|
|
407
428
|
const basePrice = includeBasePak
|
|
408
429
|
? (D.product?.basePriceRub || 5_000_000)
|
|
409
430
|
: (includeBaseLicenseOnly ? (D.product?.baseLicensePriceRub || 4_000_000) : 0);
|
|
410
431
|
|
|
411
|
-
const minSeats =
|
|
412
|
-
const
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
const disc = volumeDiscount(seats);
|
|
416
|
-
const gross = seats * tier.pricePerUserYearRub * years;
|
|
417
|
-
const saved = Math.round(gross * disc.discountPercent / 100);
|
|
418
|
-
const addonsNet = gross - saved;
|
|
419
|
-
const total = basePrice + addonsNet;
|
|
420
|
-
|
|
421
|
-
$('#lblTotal').textContent = rub(total);
|
|
422
|
-
$('#lblDiscountBadge').textContent = seats > 0 && disc.discountPercent
|
|
423
|
-
? `Скидка за количество −${disc.discountPercent}% · экономия ${rub(saved)}`
|
|
424
|
-
: (includeBasePak || includeBaseLicenseOnly ? 'Включает 10 вечных мест и активацию ИИ-агентов' : 'Скидка от количества начинается с 5 мест');
|
|
425
|
-
|
|
426
|
-
const breakdownRows = [];
|
|
427
|
-
if (includeBasePak) breakdownRows.push(['Базовый комплект (с машиной)', rub(basePrice)]);
|
|
428
|
-
if (includeBaseLicenseOnly) breakdownRows.push(['Базовая лицензия (без железа)', rub(basePrice)]);
|
|
429
|
-
if (seats > 0) {
|
|
430
|
-
breakdownRows.push(['Уровень поддержки доп. мест', `${esc(tier.name)} · ${esc(tier.reactionLabel)}`]);
|
|
431
|
-
breakdownRows.push(['Доп. мест × лет', `${seats} × ${years} (${rub(addonsNet)})`]);
|
|
432
|
+
const minSeats = variant === 'addons_only' ? 1 : 0;
|
|
433
|
+
const extra = Math.max(minSeats, Number($('#inpPakExtraSeatsNum')?.value ?? $('#inpPakExtraSeats')?.value) || 0);
|
|
434
|
+
if ($('#inpPakExtraSeatsNum') && Number($('#inpPakExtraSeatsNum').value) !== extra) {
|
|
435
|
+
$('#inpPakExtraSeatsNum').value = extra;
|
|
432
436
|
}
|
|
433
|
-
|
|
434
|
-
$('#lblBreakdown').innerHTML = lines(breakdownRows);
|
|
435
|
-
markLadder('ladderAddons', seats);
|
|
436
|
-
}
|
|
437
|
-
function syncSeats(v) {
|
|
438
|
-
const baseMode = $('#segCabinetBase button.on')?.dataset.base || 'license_only';
|
|
439
|
-
const minSeats = baseMode === 'none' ? 1 : 0;
|
|
440
|
-
const n = Math.max(minSeats, Math.min(2000, Number(v) || 0));
|
|
441
|
-
$('#inpSeats').value = n;
|
|
442
|
-
$('#rngAddonSeats').value = Math.min(150, n);
|
|
443
|
-
recalcAddons();
|
|
444
|
-
}
|
|
445
|
-
function recalcBaseOrder() {
|
|
446
|
-
const variant = $('#segPakVariant button.on')?.dataset.variant || 'pak';
|
|
447
|
-
const isLicenseOnly = variant === 'license_only';
|
|
448
|
-
const basePrice = isLicenseOnly ? (D.product.baseLicensePriceRub || 4_000_000) : D.product.basePriceRub;
|
|
449
|
-
const extra = Math.max(0, Number($('#inpPakExtraSeats').value) || 0);
|
|
437
|
+
const years = Math.min(5, Math.max(1, Number($('#inpPakYears')?.value) || 1));
|
|
450
438
|
const tier = tierById(segValue('segPakTier'));
|
|
451
439
|
const disc = volumeDiscount(extra);
|
|
452
|
-
const gross = extra * tier.pricePerUserYearRub;
|
|
453
|
-
const
|
|
454
|
-
|
|
440
|
+
const gross = extra * tier.pricePerUserYearRub * years;
|
|
441
|
+
const saved = Math.round(gross * disc.discountPercent / 100);
|
|
442
|
+
const net = gross - saved;
|
|
443
|
+
|
|
455
444
|
$('#lblPakTotal').textContent = rub(basePrice + net);
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
445
|
+
if ($('#lblPakDiscount')) {
|
|
446
|
+
$('#lblPakDiscount').textContent = extra > 0 && disc.discountPercent
|
|
447
|
+
? `Скидка за количество −${disc.discountPercent}% · экономия ${rub(saved)}`
|
|
448
|
+
: (includeBasePak ? 'Включает сервер, 10 вечных мест и 1 год поддержки Enterprise' : (includeBaseLicenseOnly ? 'Включает 10 вечных мест и активацию ИИ-агентов' : 'Скидка от количества начинается с 5 мест'));
|
|
449
|
+
}
|
|
450
|
+
const rows = [];
|
|
451
|
+
if (includeBasePak) rows.push(['Комплекс ODANT-WORK (с машиной)', rub(basePrice)]);
|
|
452
|
+
if (includeBaseLicenseOnly) rows.push(['Базовая лицензия (без железа)', rub(basePrice)]);
|
|
453
|
+
if (includeBasePak || includeBaseLicenseOnly) rows.push(['Вечные рабочие места', String(D.product.baseSeats)]);
|
|
454
|
+
if (extra > 0) {
|
|
455
|
+
rows.push([`Доп. места · ${esc(tier.name)} (${years} г.)`, `${extra} мест · ${rub(net)}`]);
|
|
456
|
+
}
|
|
462
457
|
$('#lblPakBreakdown').innerHTML = lines(rows);
|
|
458
|
+
markLadder('ladderPricing', extra);
|
|
463
459
|
}
|
|
464
|
-
|
|
460
|
+
|
|
461
|
+
async function buyClientConfig() {
|
|
462
|
+
const variant = $('#segPakVariant button.on')?.dataset.variant || 'pak';
|
|
463
|
+
const includeBasePak = variant === 'pak';
|
|
464
|
+
const includeBaseLicenseOnly = variant === 'license_only';
|
|
465
|
+
const minSeats = variant === 'addons_only' ? 1 : 0;
|
|
466
|
+
const addonSeats = Math.max(minSeats, Number($('#inpPakExtraSeatsNum')?.value ?? $('#inpPakExtraSeats')?.value) || 0);
|
|
467
|
+
const years = Math.min(5, Math.max(1, Number($('#inpPakYears')?.value) || 1));
|
|
468
|
+
const tier = segValue('segPakTier');
|
|
469
|
+
|
|
470
|
+
const intentTitle = includeBasePak
|
|
471
|
+
? `Комплекс ODANT-WORK с машиной${addonSeats > 0 ? ` + ${addonSeats} доп. мест` : ''}`
|
|
472
|
+
: (includeBaseLicenseOnly
|
|
473
|
+
? `Базовая лицензия без железа (10 вечных мест)${addonSeats > 0 ? ` + ${addonSeats} доп. мест` : ''}`
|
|
474
|
+
: `Дополнительные временные лицензии (${addonSeats} мест на ${years} г.)`);
|
|
475
|
+
|
|
465
476
|
if (!isUserReadyForOrders()) {
|
|
466
|
-
requireAuthAndOferta(() =>
|
|
467
|
-
companyName: $('#inpPakOrg')?.value.trim(),
|
|
468
|
-
inn: $('#inpPakInn')?.value.trim(),
|
|
469
|
-
email: $('#inpPakEmail')?.value.trim(),
|
|
470
|
-
});
|
|
477
|
+
requireAuthAndOferta(() => buyClientConfig(), { intentTitle });
|
|
471
478
|
return;
|
|
472
479
|
}
|
|
473
|
-
const variant = $('#segPakVariant button.on')?.dataset.variant || 'pak';
|
|
474
|
-
const isLicenseOnly = variant === 'license_only';
|
|
475
480
|
const st = $('#pakOrderStatus');
|
|
476
|
-
st.textContent =
|
|
481
|
+
if (st) st.textContent = 'Добавляем заказ в ваш Личный кабинет…';
|
|
477
482
|
try {
|
|
478
|
-
const data = await api('/api/orders/
|
|
483
|
+
const data = await api('/api/cabinet/orders/draft', {
|
|
479
484
|
sessionKey: currentUser?.sessionKey || '',
|
|
480
|
-
includeBasePak
|
|
481
|
-
includeBaseLicenseOnly
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
years: 1,
|
|
486
|
-
customer: { name: $('#inpPakOrg').value.trim(), inn: $('#inpPakInn').value.trim(), email: $('#inpPakEmail').value.trim() },
|
|
485
|
+
includeBasePak,
|
|
486
|
+
includeBaseLicenseOnly,
|
|
487
|
+
tier,
|
|
488
|
+
addonSeats,
|
|
489
|
+
years,
|
|
487
490
|
});
|
|
488
|
-
if (
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
else {
|
|
492
|
-
st.innerHTML = `Счёт <b>${esc(data.orderId)}</b> сформирован, лицензия <b>${esc(data.licenseId)}</b>. <a href="${esc(data.confirmationUrl)}" target="_blank" rel="noopener">Перейти к оплате →</a>`;
|
|
491
|
+
if (data.user) {
|
|
492
|
+
currentUser = { ...data.user, sessionKey: currentUser.sessionKey };
|
|
493
|
+
onAuthenticated();
|
|
493
494
|
}
|
|
494
|
-
|
|
495
|
+
if (st) st.textContent = '';
|
|
496
|
+
switchStage('addons');
|
|
495
497
|
}
|
|
496
|
-
catch (err) {
|
|
497
|
-
|
|
498
|
-
async function submitAddonOrder(paymentMethod = 'yookassa') {
|
|
499
|
-
if (!isUserReadyForOrders()) {
|
|
500
|
-
requireAuthAndOferta(() => submitAddonOrder(paymentMethod));
|
|
501
|
-
return;
|
|
498
|
+
catch (err) {
|
|
499
|
+
if (st) st.textContent = err.message;
|
|
502
500
|
}
|
|
503
|
-
|
|
504
|
-
const includeBasePak = baseMode === 'pak';
|
|
505
|
-
const includeBaseLicenseOnly = baseMode === 'license_only';
|
|
506
|
-
const minSeats = (includeBasePak || includeBaseLicenseOnly) ? 0 : 1;
|
|
507
|
-
const addonSeats = Math.max(minSeats, Number($('#inpSeats').value) || 0);
|
|
501
|
+
}
|
|
508
502
|
|
|
509
|
-
|
|
510
|
-
|
|
503
|
+
async function payCabinetOrder(orderId, paymentMethod = 'yookassa') {
|
|
504
|
+
if (!orderId) return;
|
|
505
|
+
let companyName = '';
|
|
506
|
+
let inn = '';
|
|
507
|
+
if (paymentMethod === 'invoice') {
|
|
508
|
+
companyName = $(`#invCompany-${orderId}`)?.value.trim() || currentUser?.companyName || '';
|
|
509
|
+
inn = $(`#invInn-${orderId}`)?.value.trim() || currentUser?.inn || '';
|
|
510
|
+
if (!companyName || !inn) {
|
|
511
|
+
const invBox = $(`#invBox-${orderId}`);
|
|
512
|
+
if (invBox) invBox.style.display = '';
|
|
513
|
+
$(`#invCompany-${orderId}`)?.focus();
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
}
|
|
511
517
|
try {
|
|
512
|
-
const data = await api('/api/orders/
|
|
518
|
+
const data = await api('/api/cabinet/orders/pay', {
|
|
513
519
|
sessionKey: currentUser?.sessionKey || '',
|
|
514
|
-
|
|
515
|
-
includeBaseLicenseOnly,
|
|
520
|
+
orderId,
|
|
516
521
|
paymentMethod,
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
years: Number($('#inpYears').value) || 1,
|
|
520
|
-
licenseId: $('#inpLicId').value.trim(),
|
|
521
|
-
customer: { name: currentUser?.companyName || '', inn: currentUser?.inn || '', email: currentUser?.email || '' },
|
|
522
|
+
companyName,
|
|
523
|
+
inn,
|
|
522
524
|
});
|
|
523
|
-
if (
|
|
524
|
-
|
|
525
|
+
if (data.user) {
|
|
526
|
+
currentUser = { ...data.user, sessionKey: currentUser.sessionKey };
|
|
527
|
+
onAuthenticated();
|
|
525
528
|
}
|
|
526
|
-
|
|
527
|
-
|
|
529
|
+
const url = data.invoiceUrl || data.confirmationUrl;
|
|
530
|
+
if (url) window.open(url, '_blank', 'noopener');
|
|
531
|
+
}
|
|
532
|
+
catch (err) {
|
|
533
|
+
alert(err.message);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
async function cancelCabinetOrder(orderId) {
|
|
538
|
+
if (!orderId) return;
|
|
539
|
+
try {
|
|
540
|
+
const data = await api('/api/cabinet/orders/cancel', {
|
|
541
|
+
sessionKey: currentUser?.sessionKey || '',
|
|
542
|
+
orderId,
|
|
543
|
+
});
|
|
544
|
+
if (data.user) {
|
|
545
|
+
currentUser = { ...data.user, sessionKey: currentUser.sessionKey };
|
|
546
|
+
onAuthenticated();
|
|
528
547
|
}
|
|
529
548
|
refreshCatalogRegions();
|
|
530
549
|
}
|
|
531
|
-
catch (err) {
|
|
550
|
+
catch (err) {
|
|
551
|
+
alert(err.message);
|
|
552
|
+
}
|
|
532
553
|
}
|
|
554
|
+
|
|
533
555
|
async function generateCabinetToken() {
|
|
534
556
|
if (!currentUser?.sessionKey) return;
|
|
535
557
|
try {
|
|
@@ -570,7 +592,7 @@
|
|
|
570
592
|
];
|
|
571
593
|
let i = 0;
|
|
572
594
|
const next = () => {
|
|
573
|
-
if (currentStage !== '
|
|
595
|
+
if (currentStage !== 'product' || i >= script.length) return;
|
|
574
596
|
const [who, text, tools] = script[i++];
|
|
575
597
|
const el = document.createElement('div');
|
|
576
598
|
if (who === 'v') {
|
|
@@ -919,32 +941,33 @@
|
|
|
919
941
|
code = code || selectedCode;
|
|
920
942
|
if (!code) return;
|
|
921
943
|
selectRegion(code);
|
|
944
|
+
const reg = REGIONS.find(r => codeOf(r) === code);
|
|
945
|
+
const regTitle = reg ? nameOf(reg) : code;
|
|
922
946
|
if (!isUserReadyForOrders()) {
|
|
923
947
|
requireAuthAndOferta(() => reserveRegionFranchise(code), {
|
|
924
|
-
|
|
925
|
-
inn: $('#inpPartnerInn')?.value.trim(),
|
|
926
|
-
email: $('#inpPartnerEmail')?.value.trim(),
|
|
948
|
+
intentTitle: `Резервирование франшизы (${regTitle})`,
|
|
927
949
|
});
|
|
928
950
|
return;
|
|
929
951
|
}
|
|
930
952
|
const msg = $('#partnerActionMsg');
|
|
931
|
-
msg.textContent = '
|
|
953
|
+
if (msg) msg.textContent = 'Добавляем резерв франшизы в ваш Личный кабинет…';
|
|
932
954
|
try {
|
|
933
|
-
const data = await api('/api/
|
|
955
|
+
const data = await api('/api/cabinet/orders/draft', {
|
|
934
956
|
sessionKey: currentUser?.sessionKey || '',
|
|
957
|
+
kind: 'franchise_reservation',
|
|
935
958
|
regionCode: code,
|
|
936
|
-
companyName: $('#inpPartnerCompany').value.trim(),
|
|
937
|
-
inn: $('#inpPartnerInn').value.trim(),
|
|
938
|
-
email: $('#inpPartnerEmail').value.trim(),
|
|
939
959
|
});
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
960
|
+
if (data.user) {
|
|
961
|
+
currentUser = { ...data.user, sessionKey: currentUser.sessionKey };
|
|
962
|
+
onAuthenticated();
|
|
963
|
+
}
|
|
964
|
+
if (msg) msg.textContent = '';
|
|
945
965
|
refreshCatalogRegions();
|
|
966
|
+
switchStage('addons');
|
|
967
|
+
}
|
|
968
|
+
catch (err) {
|
|
969
|
+
if (msg) msg.textContent = err.message;
|
|
946
970
|
}
|
|
947
|
-
catch (err) { msg.textContent = err.message; }
|
|
948
971
|
}
|
|
949
972
|
window.reserveRegionFranchise = reserveRegionFranchise;
|
|
950
973
|
|
|
@@ -1200,9 +1223,17 @@
|
|
|
1200
1223
|
/* ================= Привязка событий ================= */
|
|
1201
1224
|
function bind() {
|
|
1202
1225
|
document.addEventListener('click', (e) => {
|
|
1203
|
-
const el = e.target.closest('[data-stage],[data-action],[data-reserve],[data-
|
|
1226
|
+
const el = e.target.closest('[data-stage],[data-action],[data-reserve],[data-pay-order],[data-invoice-toggle],[data-cancel-order],[data-ask],[data-district],th[data-sort],#regionsTableBody tr');
|
|
1204
1227
|
if (!el) return;
|
|
1205
1228
|
if (el.matches('[data-reserve]')) { e.stopPropagation(); reserveRegionFranchise(el.dataset.reserve); return; }
|
|
1229
|
+
if (el.matches('[data-pay-order]')) { e.stopPropagation(); payCabinetOrder(el.dataset.payOrder, el.dataset.payMethod || 'yookassa'); return; }
|
|
1230
|
+
if (el.matches('[data-invoice-toggle]')) {
|
|
1231
|
+
e.stopPropagation();
|
|
1232
|
+
const box = $(`#invBox-${el.dataset.invoiceToggle}`);
|
|
1233
|
+
if (box) box.style.display = box.style.display === 'none' ? '' : 'none';
|
|
1234
|
+
return;
|
|
1235
|
+
}
|
|
1236
|
+
if (el.matches('[data-cancel-order]')) { e.stopPropagation(); cancelCabinetOrder(el.dataset.cancelOrder); return; }
|
|
1206
1237
|
if (el.matches('#regionsTableBody tr')) { selectRegion(el.dataset.code, { zoom: true }); $('#mapBox').scrollIntoView({ block: 'nearest', behavior: 'smooth' }); return; }
|
|
1207
1238
|
if (el.matches('th[data-sort]')) {
|
|
1208
1239
|
SORT.dir = SORT.key === el.dataset.sort ? -SORT.dir : (el.dataset.sort === 'name' || el.dataset.sort === 'district' ? 1 : -1);
|
|
@@ -1211,14 +1242,24 @@
|
|
|
1211
1242
|
return;
|
|
1212
1243
|
}
|
|
1213
1244
|
if (el.matches('[data-district]')) { setDistrict(el.dataset.district); return; }
|
|
1214
|
-
if (el.matches('[data-bot]')) { $('#botText').value = el.dataset.bot; sendBot(); return; }
|
|
1215
1245
|
if (el.matches('[data-ask]')) { sendAi(el.dataset.ask); return; }
|
|
1216
|
-
if (el.dataset.stage) { e.preventDefault(); switchStage(el.dataset.stage); return; }
|
|
1246
|
+
if (el.dataset.stage && el !== document.body) { e.preventDefault(); switchStage(el.dataset.stage); return; }
|
|
1217
1247
|
switch (el.dataset.action) {
|
|
1218
|
-
case 'auth':
|
|
1248
|
+
case 'auth':
|
|
1249
|
+
if (isUserReadyForOrders() && currentStage !== 'addons') {
|
|
1250
|
+
switchStage('addons');
|
|
1251
|
+
}
|
|
1252
|
+
else {
|
|
1253
|
+
pendingAuthAction = null;
|
|
1254
|
+
resetAuthStep();
|
|
1255
|
+
openAuth();
|
|
1256
|
+
}
|
|
1257
|
+
break;
|
|
1219
1258
|
case 'auth-close': closeAuth(); break;
|
|
1220
|
-
case '
|
|
1221
|
-
case '
|
|
1259
|
+
case 'send-code': sendAuthCode(); break;
|
|
1260
|
+
case 'verify-code': verifyAuthCode(); break;
|
|
1261
|
+
case 'reset-auth-step': resetAuthStep(); break;
|
|
1262
|
+
case 'logout': e.preventDefault(); performLogout(); break;
|
|
1222
1263
|
case 'open-oferta': e.preventDefault(); openOfertaModal(); break;
|
|
1223
1264
|
case 'oferta-close': closeOfertaModal(); break;
|
|
1224
1265
|
case 'accept-from-oferta':
|
|
@@ -1232,20 +1273,18 @@
|
|
|
1232
1273
|
else requireAuthAndOferta(() => switchStage('addons'));
|
|
1233
1274
|
break;
|
|
1234
1275
|
case 'pick-tier':
|
|
1235
|
-
$$('#
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1276
|
+
$$('#segPakTier button').forEach(b => b.classList.toggle('on', b.dataset.tier === el.dataset.tier));
|
|
1277
|
+
if ($('#segPakVariant')) {
|
|
1278
|
+
$$('#segPakVariant button').forEach(b => b.classList.toggle('on', b.dataset.variant === 'addons_only'));
|
|
1279
|
+
}
|
|
1280
|
+
syncClientSeats(5);
|
|
1281
|
+
buyClientConfig();
|
|
1239
1282
|
break;
|
|
1240
|
-
case '
|
|
1241
|
-
case 'order-pak-invoice': submitBasePakOrder('invoice'); break;
|
|
1242
|
-
case 'order-addons': submitAddonOrder('yookassa'); break;
|
|
1243
|
-
case 'order-addons-invoice': submitAddonOrder('invoice'); break;
|
|
1283
|
+
case 'buy-client-config': buyClientConfig(); break;
|
|
1244
1284
|
case 'gen-hub-token': generateCabinetToken(); break;
|
|
1245
1285
|
case 'copy-hub-token': copyCabinetToken(); break;
|
|
1246
1286
|
case 'refresh-cabinet': refreshCabinetUser(); break;
|
|
1247
1287
|
case 'reserve-selected': reserveRegionFranchise(selectedCode); break;
|
|
1248
|
-
case 'bot-send': sendBot(); break;
|
|
1249
1288
|
case 'cert-close': $('#certModal')?.classList.remove('open'); break;
|
|
1250
1289
|
case 'cert-copy':
|
|
1251
1290
|
navigator.clipboard?.writeText($('#certJsonBox')?.textContent || '');
|
|
@@ -1260,19 +1299,20 @@
|
|
|
1260
1299
|
document.addEventListener('keydown', (e) => {
|
|
1261
1300
|
if (e.key === 'Escape') { closeAuth(); closeOfertaModal(); $('#certModal')?.classList.remove('open'); toggleAi(false); }
|
|
1262
1301
|
});
|
|
1263
|
-
$('#authModal')?.addEventListener('keydown', (e) => {
|
|
1302
|
+
$('#authModal')?.addEventListener('keydown', (e) => {
|
|
1303
|
+
if (e.key === 'Enter' && e.target.tagName !== 'BUTTON') {
|
|
1304
|
+
if ($('#authStepCode')?.style.display !== 'none') verifyAuthCode();
|
|
1305
|
+
else sendAuthCode();
|
|
1306
|
+
}
|
|
1307
|
+
});
|
|
1264
1308
|
$('#aiInput')?.addEventListener('keydown', (e) => { if (e.key === 'Enter') sendAi(); });
|
|
1265
|
-
$('#
|
|
1266
|
-
$('#
|
|
1267
|
-
$('#
|
|
1268
|
-
$('#inpYears')?.addEventListener('input', recalcAddons);
|
|
1269
|
-
$('#inpPakExtraSeats')?.addEventListener('input', recalcBaseOrder);
|
|
1309
|
+
$('#inpPakExtraSeats')?.addEventListener('input', (e) => syncClientSeats(e.target.value));
|
|
1310
|
+
$('#inpPakExtraSeatsNum')?.addEventListener('input', (e) => syncClientSeats(e.target.value));
|
|
1311
|
+
$('#inpPakYears')?.addEventListener('input', recalcBaseOrder);
|
|
1270
1312
|
$('#rngPartnerSales')?.addEventListener('input', recalcPartner);
|
|
1271
1313
|
$('#inpRegionFilter')?.addEventListener('input', renderRegionsTable);
|
|
1272
|
-
bindSeg('segTier', recalcAddons);
|
|
1273
|
-
bindSeg('segCabinetBase', () => { syncSeats($('#inpSeats')?.value || 0); });
|
|
1274
1314
|
bindSeg('segPakTier', recalcBaseOrder);
|
|
1275
|
-
bindSeg('segPakVariant',
|
|
1315
|
+
bindSeg('segPakVariant', () => syncClientSeats($('#inpPakExtraSeatsNum')?.value || 0));
|
|
1276
1316
|
window.addEventListener('message', (e) => {
|
|
1277
1317
|
if (e.data?.type === 'odant:order-paid') {
|
|
1278
1318
|
refreshCabinetUser();
|
|
@@ -1286,7 +1326,6 @@
|
|
|
1286
1326
|
applyTheme(document.documentElement.getAttribute('data-theme') || 'dark');
|
|
1287
1327
|
bind();
|
|
1288
1328
|
renderOrbit();
|
|
1289
|
-
recalcAddons();
|
|
1290
1329
|
recalcBaseOrder();
|
|
1291
1330
|
recalcPartner();
|
|
1292
1331
|
renderRegionsTable();
|