work-license-server 1.0.7 → 1.0.8
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 +259 -263
- package/package.json +1 -1
- package/src/landing.js +111 -198
- package/src/server.js +86 -1
- package/src/store.js +109 -15
package/assets/landing.js
CHANGED
|
@@ -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,49 +157,38 @@
|
|
|
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
|
-
async function performLogin() {
|
|
170
|
+
async function performLogin(provider = 'email') {
|
|
163
171
|
const errBox = $('#authErrorMsg');
|
|
164
172
|
if (errBox) errBox.textContent = '';
|
|
165
|
-
|
|
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);
|
|
173
|
+
let email = $('#authEmail')?.value.trim() || '';
|
|
174
|
+
const ofertaChecked = $('#authOfertaCheck') ? Boolean($('#authOfertaCheck').checked) : true;
|
|
171
175
|
|
|
172
|
-
if (!
|
|
173
|
-
if (errBox) errBox.textContent = '
|
|
176
|
+
if (!ofertaChecked) {
|
|
177
|
+
if (errBox) errBox.textContent = 'Подтвердите согласие с условиями Публичного договора-оферты.';
|
|
174
178
|
return;
|
|
175
179
|
}
|
|
176
|
-
if (!
|
|
177
|
-
if (errBox) errBox.textContent = 'Укажите
|
|
180
|
+
if (provider === 'email' && (!email || !email.includes('@'))) {
|
|
181
|
+
if (errBox) errBox.textContent = 'Укажите корректный email.';
|
|
178
182
|
return;
|
|
179
183
|
}
|
|
180
|
-
if (!
|
|
181
|
-
|
|
182
|
-
return;
|
|
184
|
+
if (provider !== 'email' && (!email || !email.includes('@'))) {
|
|
185
|
+
email = `user.${provider}@${provider}.id.odant.org`;
|
|
183
186
|
}
|
|
184
187
|
|
|
185
188
|
try {
|
|
186
189
|
const data = await api('/api/auth/register', {
|
|
187
190
|
email,
|
|
188
|
-
|
|
189
|
-
inn,
|
|
190
|
-
name: signer || companyName,
|
|
191
|
-
licenseId,
|
|
191
|
+
provider,
|
|
192
192
|
ofertaAccepted: true,
|
|
193
193
|
});
|
|
194
194
|
currentUser = data;
|
|
@@ -198,7 +198,7 @@
|
|
|
198
198
|
const nextAction = pendingAuthAction;
|
|
199
199
|
pendingAuthAction = null;
|
|
200
200
|
if (typeof nextAction === 'function') {
|
|
201
|
-
nextAction();
|
|
201
|
+
await nextAction();
|
|
202
202
|
}
|
|
203
203
|
else {
|
|
204
204
|
switchStage('addons');
|
|
@@ -213,11 +213,9 @@
|
|
|
213
213
|
currentUser = null;
|
|
214
214
|
pendingAuthAction = null;
|
|
215
215
|
try { localStorage.removeItem('odant_work_session'); } catch { /* ignore */ }
|
|
216
|
-
$('#tileAddons').style.display = 'none';
|
|
217
|
-
$('#btnAuthText').textContent = 'Войти / Регистрация';
|
|
216
|
+
if ($('#tileAddons')) $('#tileAddons').style.display = 'none';
|
|
217
|
+
if ($('#btnAuthText')) $('#btnAuthText').textContent = 'Войти / Регистрация';
|
|
218
218
|
if ($('#btnLogoutModal')) $('#btnLogoutModal').style.display = 'none';
|
|
219
|
-
updateOfertaBanners();
|
|
220
|
-
updateClientSupportCard();
|
|
221
219
|
closeAuth();
|
|
222
220
|
if (currentStage === 'addons') switchStage('product');
|
|
223
221
|
moveIndicator();
|
|
@@ -226,11 +224,7 @@
|
|
|
226
224
|
async function restoreSession() {
|
|
227
225
|
let key = '';
|
|
228
226
|
try { key = localStorage.getItem('odant_work_session') || ''; } catch { /* приватный режим */ }
|
|
229
|
-
if (!key)
|
|
230
|
-
updateOfertaBanners();
|
|
231
|
-
updateClientSupportCard();
|
|
232
|
-
return;
|
|
233
|
-
}
|
|
227
|
+
if (!key) return;
|
|
234
228
|
try {
|
|
235
229
|
const data = await api('/api/auth/me?sessionKey=' + encodeURIComponent(key));
|
|
236
230
|
if (data.authenticated && data.user) {
|
|
@@ -240,98 +234,74 @@
|
|
|
240
234
|
}
|
|
241
235
|
}
|
|
242
236
|
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
237
|
}
|
|
300
238
|
|
|
301
239
|
function onAuthenticated() {
|
|
302
240
|
if (!currentUser) return;
|
|
303
|
-
$('#tileAddons').style.display = isUserReadyForOrders() ? '' : 'none';
|
|
304
|
-
$('#btnAuthText').textContent = currentUser.companyName || currentUser.email;
|
|
305
|
-
if ($('#inpLicId')) $('#inpLicId').value = currentUser.licenseId || '';
|
|
241
|
+
if ($('#tileAddons')) $('#tileAddons').style.display = isUserReadyForOrders() ? '' : 'none';
|
|
242
|
+
if ($('#btnAuthText')) $('#btnAuthText').textContent = currentUser.companyName || currentUser.email;
|
|
306
243
|
if ($('#inpCabinetHubToken')) $('#inpCabinetHubToken').value = currentUser.hubToken || '';
|
|
307
244
|
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
245
|
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
|
-
}
|
|
246
|
+
if ($('#userBadgeBox')) {
|
|
247
|
+
const orgInfo = currentUser.companyName ? `${esc(currentUser.companyName)}${currentUser.inn ? ' (ИНН ' + esc(currentUser.inn) + ')' : ''} · ` : '';
|
|
248
|
+
$('#userBadgeBox').innerHTML = `${orgInfo}Аккаунт: <b>${esc(currentUser.email)}</b> · <a href="#" data-action="logout" style="color:var(--muted)">Выйти</a>`;
|
|
329
249
|
}
|
|
250
|
+
renderCabinetOrders();
|
|
330
251
|
renderCabinetLicenses();
|
|
331
|
-
updateOfertaBanners();
|
|
332
|
-
updateClientSupportCard();
|
|
333
252
|
moveIndicator();
|
|
334
|
-
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function renderCabinetOrders() {
|
|
256
|
+
const box = $('#cabinetOrdersBox');
|
|
257
|
+
if (!box) return;
|
|
258
|
+
const orders = currentUser?.orders || [];
|
|
259
|
+
if (!orders.length) {
|
|
260
|
+
box.innerHTML = `<div style="padding:18px;border-radius:12px;border:1px dashed var(--border);color:var(--muted);text-align:center;">
|
|
261
|
+
В вашем Личном кабинете пока нет заказов. Перейдите в раздел <a href="#clients" data-stage="clients"><b>«Клиентам»</b></a> (чтобы купить лицензию или ПАК) или <a href="#franchise" data-stage="franchise"><b>«Франшиза»</b></a> (чтобы зарезервировать регион).
|
|
262
|
+
</div>`;
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
box.innerHTML = orders.map(o => {
|
|
266
|
+
const isPaid = o.status === 'paid';
|
|
267
|
+
const title = (o.items && o.items.length)
|
|
268
|
+
? o.items.map(it => esc(it.title)).join(' + ')
|
|
269
|
+
: esc(o.tierName || o.orderId);
|
|
270
|
+
const dateStr = o.createdAt ? new Date(o.createdAt).toLocaleString('ru-RU') : '';
|
|
271
|
+
const statusBadge = isPaid
|
|
272
|
+
? `<span class="tag ok">✓ Оплачен</span>`
|
|
273
|
+
: `<span class="tag">Ожидает оплаты</span>`;
|
|
274
|
+
const actionsHtml = isPaid
|
|
275
|
+
? `<div style="display:flex;gap:8px;flex-wrap:wrap;margin-top:10px">
|
|
276
|
+
${o.invoiceUrl ? `<a class="btn btn-ghost btn-sm" href="${esc(o.invoiceUrl)}" target="_blank" rel="noopener">Открыть счёт-договор (${esc(o.invoiceNumber || o.orderId)}) ↗</a>` : ''}
|
|
277
|
+
</div>`
|
|
278
|
+
: `<div style="display:flex;gap:10px;flex-wrap:wrap;margin-top:12px;align-items:center">
|
|
279
|
+
<button class="btn btn-primary btn-sm" data-pay-order="${esc(o.orderId)}" data-pay-method="yookassa">Оплатить через ЮKassa (${rub(o.amountRub)})</button>
|
|
280
|
+
<button class="btn btn-ghost btn-sm" data-invoice-toggle="${esc(o.orderId)}">Счёт-договор (для юр. лиц)</button>
|
|
281
|
+
<button class="btn btn-ghost btn-sm" data-cancel-order="${esc(o.orderId)}" style="color:var(--danger)">Удалить заказ</button>
|
|
282
|
+
</div>
|
|
283
|
+
<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)">
|
|
284
|
+
<div class="small" style="margin-bottom:8px;font-weight:600">Реквизиты организации для выставления счёта-договора:</div>
|
|
285
|
+
<div class="row2">
|
|
286
|
+
<div class="field"><label>Наименование организации *</label><input type="text" id="invCompany-${esc(o.orderId)}" value="${esc(o.customer?.name || currentUser?.companyName || '')}" placeholder="ООО «Маслоторг»"></div>
|
|
287
|
+
<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>
|
|
288
|
+
</div>
|
|
289
|
+
<button class="btn btn-primary btn-sm" style="margin-top:8px" data-pay-order="${esc(o.orderId)}" data-pay-method="invoice">Сформировать и открыть счёт-договор →</button>
|
|
290
|
+
</div>`;
|
|
291
|
+
return `<div style="padding:14px 16px;border-radius:12px;border:1px solid var(--border);margin-bottom:10px;">
|
|
292
|
+
<div style="display:flex;justify-content:space-between;gap:12px;flex-wrap:wrap;align-items:center">
|
|
293
|
+
<div>
|
|
294
|
+
<div style="font-size:12px;color:var(--muted)">Заказ <code>${esc(o.orderId)}</code> · ${esc(dateStr)}</div>
|
|
295
|
+
<div style="font-weight:700;font-size:15px;margin-top:2px">${title}</div>
|
|
296
|
+
</div>
|
|
297
|
+
<div style="text-align:right">
|
|
298
|
+
<div style="font-size:18px;font-weight:800">${rub(o.amountRub)}</div>
|
|
299
|
+
${statusBadge}
|
|
300
|
+
</div>
|
|
301
|
+
</div>
|
|
302
|
+
${actionsHtml}
|
|
303
|
+
</div>`;
|
|
304
|
+
}).join('');
|
|
335
305
|
}
|
|
336
306
|
|
|
337
307
|
function renderCabinetLicenses() {
|
|
@@ -339,9 +309,10 @@
|
|
|
339
309
|
if (!box) return;
|
|
340
310
|
const lic = currentUser?.license;
|
|
341
311
|
const files = lic?.licenseFiles || [];
|
|
342
|
-
|
|
312
|
+
const reservations = (currentUser?.reservations || []).filter(r => r.depositPaid);
|
|
313
|
+
if (!files.length && !reservations.length) {
|
|
343
314
|
box.innerHTML = `<div style="padding:12px 14px;border-radius:10px;border:1px dashed var(--border);color:var(--muted);">
|
|
344
|
-
|
|
315
|
+
Активных оплаченных лицензий пока нет. После оплаты заказа выше подписанные файлы лицензий (<code>.lic.json</code>) сразу появятся здесь и автоматически загрузятся в ваш локальный WORK по токену владельца.
|
|
345
316
|
</div>`;
|
|
346
317
|
return;
|
|
347
318
|
}
|
|
@@ -355,7 +326,11 @@
|
|
|
355
326
|
<div style="text-align:right"><span class="pill" style="padding:3px 10px;font-size:11.5px">${esc(expText)}</span></div>
|
|
356
327
|
</div>`;
|
|
357
328
|
}).join('');
|
|
358
|
-
|
|
329
|
+
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;">
|
|
330
|
+
<div><b>Региональная франшиза: ${esc(r.regionName)}</b><br><code style="font-size:11.5px;color:var(--muted)">${esc(r.reservationId)}</code></div>
|
|
331
|
+
<div style="text-align:right"><span class="pill" style="padding:3px 10px;font-size:11.5px">Залог оплачен</span></div>
|
|
332
|
+
</div>`).join('');
|
|
333
|
+
box.innerHTML = (files.length ? `<div>Всего доступно одновременных сессий: <b style="color:var(--text)">${lic.totalSeats}</b> (${lic.baseSeats} вечных + ${lic.addonSeats} временных)</div>` : '') + rows + resRows;
|
|
359
334
|
}
|
|
360
335
|
|
|
361
336
|
async function refreshCabinetUser() {
|
|
@@ -381,7 +356,7 @@
|
|
|
381
356
|
}
|
|
382
357
|
}
|
|
383
358
|
|
|
384
|
-
/* ================= Калькуляторы ================= */
|
|
359
|
+
/* ================= Калькуляторы раздела «Клиентам» ================= */
|
|
385
360
|
function volumeDiscount(seats) {
|
|
386
361
|
const s = Math.max(0, Math.floor(Number(seats) || 0));
|
|
387
362
|
return (D.discounts || []).find(d => s >= d.minSeats) || { discountPercent: 0, minSeats: 1 };
|
|
@@ -400,136 +375,147 @@
|
|
|
400
375
|
const tierById = (id) => (D.tiers || []).find(t => t.id === id) || D.tiers[1];
|
|
401
376
|
const lines = (rows) => rows.map(([k, v]) => `<div><span>${k}</span><b>${v}</b></div>`).join('');
|
|
402
377
|
|
|
403
|
-
function
|
|
404
|
-
const
|
|
405
|
-
const
|
|
406
|
-
const
|
|
378
|
+
function syncClientSeats(v) {
|
|
379
|
+
const variant = $('#segPakVariant button.on')?.dataset.variant || 'pak';
|
|
380
|
+
const minSeats = variant === 'addons_only' ? 1 : 0;
|
|
381
|
+
const n = Math.max(minSeats, Math.min(2000, Number(v) || 0));
|
|
382
|
+
if ($('#inpPakExtraSeats')) $('#inpPakExtraSeats').value = Math.min(150, n);
|
|
383
|
+
if ($('#inpPakExtraSeatsNum')) $('#inpPakExtraSeatsNum').value = n;
|
|
384
|
+
recalcBaseOrder();
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
function recalcBaseOrder() {
|
|
388
|
+
if (!$('#lblPakTotal')) return;
|
|
389
|
+
const variant = $('#segPakVariant button.on')?.dataset.variant || 'pak';
|
|
390
|
+
const includeBasePak = variant === 'pak';
|
|
391
|
+
const includeBaseLicenseOnly = variant === 'license_only';
|
|
407
392
|
const basePrice = includeBasePak
|
|
408
393
|
? (D.product?.basePriceRub || 5_000_000)
|
|
409
394
|
: (includeBaseLicenseOnly ? (D.product?.baseLicensePriceRub || 4_000_000) : 0);
|
|
410
395
|
|
|
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)})`]);
|
|
396
|
+
const minSeats = variant === 'addons_only' ? 1 : 0;
|
|
397
|
+
const extra = Math.max(minSeats, Number($('#inpPakExtraSeatsNum')?.value ?? $('#inpPakExtraSeats')?.value) || 0);
|
|
398
|
+
if ($('#inpPakExtraSeatsNum') && Number($('#inpPakExtraSeatsNum').value) !== extra) {
|
|
399
|
+
$('#inpPakExtraSeatsNum').value = extra;
|
|
432
400
|
}
|
|
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);
|
|
401
|
+
const years = Math.min(5, Math.max(1, Number($('#inpPakYears')?.value) || 1));
|
|
450
402
|
const tier = tierById(segValue('segPakTier'));
|
|
451
403
|
const disc = volumeDiscount(extra);
|
|
452
|
-
const gross = extra * tier.pricePerUserYearRub;
|
|
453
|
-
const
|
|
454
|
-
|
|
404
|
+
const gross = extra * tier.pricePerUserYearRub * years;
|
|
405
|
+
const saved = Math.round(gross * disc.discountPercent / 100);
|
|
406
|
+
const net = gross - saved;
|
|
407
|
+
|
|
455
408
|
$('#lblPakTotal').textContent = rub(basePrice + net);
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
409
|
+
if ($('#lblPakDiscount')) {
|
|
410
|
+
$('#lblPakDiscount').textContent = extra > 0 && disc.discountPercent
|
|
411
|
+
? `Скидка за количество −${disc.discountPercent}% · экономия ${rub(saved)}`
|
|
412
|
+
: (includeBasePak ? 'Включает сервер, 10 вечных мест и 1 год поддержки Enterprise' : (includeBaseLicenseOnly ? 'Включает 10 вечных мест и активацию ИИ-агентов' : 'Скидка от количества начинается с 5 мест'));
|
|
413
|
+
}
|
|
414
|
+
const rows = [];
|
|
415
|
+
if (includeBasePak) rows.push(['Комплекс ODANT-WORK (с машиной)', rub(basePrice)]);
|
|
416
|
+
if (includeBaseLicenseOnly) rows.push(['Базовая лицензия (без железа)', rub(basePrice)]);
|
|
417
|
+
if (includeBasePak || includeBaseLicenseOnly) rows.push(['Вечные рабочие места', String(D.product.baseSeats)]);
|
|
418
|
+
if (extra > 0) {
|
|
419
|
+
rows.push([`Доп. места · ${esc(tier.name)} (${years} г.)`, `${extra} мест · ${rub(net)}`]);
|
|
420
|
+
}
|
|
462
421
|
$('#lblPakBreakdown').innerHTML = lines(rows);
|
|
422
|
+
markLadder('ladderPricing', extra);
|
|
463
423
|
}
|
|
464
|
-
|
|
424
|
+
|
|
425
|
+
async function buyClientConfig() {
|
|
426
|
+
const variant = $('#segPakVariant button.on')?.dataset.variant || 'pak';
|
|
427
|
+
const includeBasePak = variant === 'pak';
|
|
428
|
+
const includeBaseLicenseOnly = variant === 'license_only';
|
|
429
|
+
const minSeats = variant === 'addons_only' ? 1 : 0;
|
|
430
|
+
const addonSeats = Math.max(minSeats, Number($('#inpPakExtraSeatsNum')?.value ?? $('#inpPakExtraSeats')?.value) || 0);
|
|
431
|
+
const years = Math.min(5, Math.max(1, Number($('#inpPakYears')?.value) || 1));
|
|
432
|
+
const tier = segValue('segPakTier');
|
|
433
|
+
|
|
434
|
+
const intentTitle = includeBasePak
|
|
435
|
+
? `Комплекс ODANT-WORK с машиной${addonSeats > 0 ? ` + ${addonSeats} доп. мест` : ''}`
|
|
436
|
+
: (includeBaseLicenseOnly
|
|
437
|
+
? `Базовая лицензия без железа (10 вечных мест)${addonSeats > 0 ? ` + ${addonSeats} доп. мест` : ''}`
|
|
438
|
+
: `Дополнительные временные лицензии (${addonSeats} мест на ${years} г.)`);
|
|
439
|
+
|
|
465
440
|
if (!isUserReadyForOrders()) {
|
|
466
|
-
requireAuthAndOferta(() =>
|
|
467
|
-
companyName: $('#inpPakOrg')?.value.trim(),
|
|
468
|
-
inn: $('#inpPakInn')?.value.trim(),
|
|
469
|
-
email: $('#inpPakEmail')?.value.trim(),
|
|
470
|
-
});
|
|
441
|
+
requireAuthAndOferta(() => buyClientConfig(), { intentTitle });
|
|
471
442
|
return;
|
|
472
443
|
}
|
|
473
|
-
const variant = $('#segPakVariant button.on')?.dataset.variant || 'pak';
|
|
474
|
-
const isLicenseOnly = variant === 'license_only';
|
|
475
444
|
const st = $('#pakOrderStatus');
|
|
476
|
-
st.textContent =
|
|
445
|
+
if (st) st.textContent = 'Добавляем заказ в ваш Личный кабинет…';
|
|
477
446
|
try {
|
|
478
|
-
const data = await api('/api/orders/
|
|
447
|
+
const data = await api('/api/cabinet/orders/draft', {
|
|
479
448
|
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() },
|
|
449
|
+
includeBasePak,
|
|
450
|
+
includeBaseLicenseOnly,
|
|
451
|
+
tier,
|
|
452
|
+
addonSeats,
|
|
453
|
+
years,
|
|
487
454
|
});
|
|
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>`;
|
|
455
|
+
if (data.user) {
|
|
456
|
+
currentUser = { ...data.user, sessionKey: currentUser.sessionKey };
|
|
457
|
+
onAuthenticated();
|
|
493
458
|
}
|
|
494
|
-
|
|
459
|
+
if (st) st.textContent = '';
|
|
460
|
+
switchStage('addons');
|
|
495
461
|
}
|
|
496
|
-
catch (err) {
|
|
497
|
-
|
|
498
|
-
async function submitAddonOrder(paymentMethod = 'yookassa') {
|
|
499
|
-
if (!isUserReadyForOrders()) {
|
|
500
|
-
requireAuthAndOferta(() => submitAddonOrder(paymentMethod));
|
|
501
|
-
return;
|
|
462
|
+
catch (err) {
|
|
463
|
+
if (st) st.textContent = err.message;
|
|
502
464
|
}
|
|
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);
|
|
465
|
+
}
|
|
508
466
|
|
|
509
|
-
|
|
510
|
-
|
|
467
|
+
async function payCabinetOrder(orderId, paymentMethod = 'yookassa') {
|
|
468
|
+
if (!orderId) return;
|
|
469
|
+
let companyName = '';
|
|
470
|
+
let inn = '';
|
|
471
|
+
if (paymentMethod === 'invoice') {
|
|
472
|
+
companyName = $(`#invCompany-${orderId}`)?.value.trim() || currentUser?.companyName || '';
|
|
473
|
+
inn = $(`#invInn-${orderId}`)?.value.trim() || currentUser?.inn || '';
|
|
474
|
+
if (!companyName || !inn) {
|
|
475
|
+
const invBox = $(`#invBox-${orderId}`);
|
|
476
|
+
if (invBox) invBox.style.display = '';
|
|
477
|
+
$(`#invCompany-${orderId}`)?.focus();
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
511
481
|
try {
|
|
512
|
-
const data = await api('/api/orders/
|
|
482
|
+
const data = await api('/api/cabinet/orders/pay', {
|
|
513
483
|
sessionKey: currentUser?.sessionKey || '',
|
|
514
|
-
|
|
515
|
-
includeBaseLicenseOnly,
|
|
484
|
+
orderId,
|
|
516
485
|
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 || '' },
|
|
486
|
+
companyName,
|
|
487
|
+
inn,
|
|
522
488
|
});
|
|
523
|
-
if (
|
|
524
|
-
|
|
489
|
+
if (data.user) {
|
|
490
|
+
currentUser = { ...data.user, sessionKey: currentUser.sessionKey };
|
|
491
|
+
onAuthenticated();
|
|
525
492
|
}
|
|
526
|
-
|
|
527
|
-
|
|
493
|
+
const url = data.invoiceUrl || data.confirmationUrl;
|
|
494
|
+
if (url) window.open(url, '_blank', 'noopener');
|
|
495
|
+
}
|
|
496
|
+
catch (err) {
|
|
497
|
+
alert(err.message);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
async function cancelCabinetOrder(orderId) {
|
|
502
|
+
if (!orderId) return;
|
|
503
|
+
try {
|
|
504
|
+
const data = await api('/api/cabinet/orders/cancel', {
|
|
505
|
+
sessionKey: currentUser?.sessionKey || '',
|
|
506
|
+
orderId,
|
|
507
|
+
});
|
|
508
|
+
if (data.user) {
|
|
509
|
+
currentUser = { ...data.user, sessionKey: currentUser.sessionKey };
|
|
510
|
+
onAuthenticated();
|
|
528
511
|
}
|
|
529
512
|
refreshCatalogRegions();
|
|
530
513
|
}
|
|
531
|
-
catch (err) {
|
|
514
|
+
catch (err) {
|
|
515
|
+
alert(err.message);
|
|
516
|
+
}
|
|
532
517
|
}
|
|
518
|
+
|
|
533
519
|
async function generateCabinetToken() {
|
|
534
520
|
if (!currentUser?.sessionKey) return;
|
|
535
521
|
try {
|
|
@@ -570,7 +556,7 @@
|
|
|
570
556
|
];
|
|
571
557
|
let i = 0;
|
|
572
558
|
const next = () => {
|
|
573
|
-
if (currentStage !== '
|
|
559
|
+
if (currentStage !== 'product' || i >= script.length) return;
|
|
574
560
|
const [who, text, tools] = script[i++];
|
|
575
561
|
const el = document.createElement('div');
|
|
576
562
|
if (who === 'v') {
|
|
@@ -919,32 +905,33 @@
|
|
|
919
905
|
code = code || selectedCode;
|
|
920
906
|
if (!code) return;
|
|
921
907
|
selectRegion(code);
|
|
908
|
+
const reg = REGIONS.find(r => codeOf(r) === code);
|
|
909
|
+
const regTitle = reg ? nameOf(reg) : code;
|
|
922
910
|
if (!isUserReadyForOrders()) {
|
|
923
911
|
requireAuthAndOferta(() => reserveRegionFranchise(code), {
|
|
924
|
-
|
|
925
|
-
inn: $('#inpPartnerInn')?.value.trim(),
|
|
926
|
-
email: $('#inpPartnerEmail')?.value.trim(),
|
|
912
|
+
intentTitle: `Резервирование франшизы (${regTitle})`,
|
|
927
913
|
});
|
|
928
914
|
return;
|
|
929
915
|
}
|
|
930
916
|
const msg = $('#partnerActionMsg');
|
|
931
|
-
msg.textContent = '
|
|
917
|
+
if (msg) msg.textContent = 'Добавляем резерв франшизы в ваш Личный кабинет…';
|
|
932
918
|
try {
|
|
933
|
-
const data = await api('/api/
|
|
919
|
+
const data = await api('/api/cabinet/orders/draft', {
|
|
934
920
|
sessionKey: currentUser?.sessionKey || '',
|
|
921
|
+
kind: 'franchise_reservation',
|
|
935
922
|
regionCode: code,
|
|
936
|
-
companyName: $('#inpPartnerCompany').value.trim(),
|
|
937
|
-
inn: $('#inpPartnerInn').value.trim(),
|
|
938
|
-
email: $('#inpPartnerEmail').value.trim(),
|
|
939
923
|
});
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
924
|
+
if (data.user) {
|
|
925
|
+
currentUser = { ...data.user, sessionKey: currentUser.sessionKey };
|
|
926
|
+
onAuthenticated();
|
|
927
|
+
}
|
|
928
|
+
if (msg) msg.textContent = '';
|
|
945
929
|
refreshCatalogRegions();
|
|
930
|
+
switchStage('addons');
|
|
931
|
+
}
|
|
932
|
+
catch (err) {
|
|
933
|
+
if (msg) msg.textContent = err.message;
|
|
946
934
|
}
|
|
947
|
-
catch (err) { msg.textContent = err.message; }
|
|
948
935
|
}
|
|
949
936
|
window.reserveRegionFranchise = reserveRegionFranchise;
|
|
950
937
|
|
|
@@ -1200,9 +1187,17 @@
|
|
|
1200
1187
|
/* ================= Привязка событий ================= */
|
|
1201
1188
|
function bind() {
|
|
1202
1189
|
document.addEventListener('click', (e) => {
|
|
1203
|
-
const el = e.target.closest('[data-stage],[data-action],[data-reserve],[data-
|
|
1190
|
+
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
1191
|
if (!el) return;
|
|
1205
1192
|
if (el.matches('[data-reserve]')) { e.stopPropagation(); reserveRegionFranchise(el.dataset.reserve); return; }
|
|
1193
|
+
if (el.matches('[data-pay-order]')) { e.stopPropagation(); payCabinetOrder(el.dataset.payOrder, el.dataset.payMethod || 'yookassa'); return; }
|
|
1194
|
+
if (el.matches('[data-invoice-toggle]')) {
|
|
1195
|
+
e.stopPropagation();
|
|
1196
|
+
const box = $(`#invBox-${el.dataset.invoiceToggle}`);
|
|
1197
|
+
if (box) box.style.display = box.style.display === 'none' ? '' : 'none';
|
|
1198
|
+
return;
|
|
1199
|
+
}
|
|
1200
|
+
if (el.matches('[data-cancel-order]')) { e.stopPropagation(); cancelCabinetOrder(el.dataset.cancelOrder); return; }
|
|
1206
1201
|
if (el.matches('#regionsTableBody tr')) { selectRegion(el.dataset.code, { zoom: true }); $('#mapBox').scrollIntoView({ block: 'nearest', behavior: 'smooth' }); return; }
|
|
1207
1202
|
if (el.matches('th[data-sort]')) {
|
|
1208
1203
|
SORT.dir = SORT.key === el.dataset.sort ? -SORT.dir : (el.dataset.sort === 'name' || el.dataset.sort === 'district' ? 1 : -1);
|
|
@@ -1211,14 +1206,22 @@
|
|
|
1211
1206
|
return;
|
|
1212
1207
|
}
|
|
1213
1208
|
if (el.matches('[data-district]')) { setDistrict(el.dataset.district); return; }
|
|
1214
|
-
if (el.matches('[data-bot]')) { $('#botText').value = el.dataset.bot; sendBot(); return; }
|
|
1215
1209
|
if (el.matches('[data-ask]')) { sendAi(el.dataset.ask); return; }
|
|
1216
1210
|
if (el.dataset.stage) { e.preventDefault(); switchStage(el.dataset.stage); return; }
|
|
1217
1211
|
switch (el.dataset.action) {
|
|
1218
|
-
case 'auth':
|
|
1212
|
+
case 'auth':
|
|
1213
|
+
if (isUserReadyForOrders() && currentStage !== 'addons') {
|
|
1214
|
+
switchStage('addons');
|
|
1215
|
+
}
|
|
1216
|
+
else {
|
|
1217
|
+
pendingAuthAction = null;
|
|
1218
|
+
openAuth();
|
|
1219
|
+
}
|
|
1220
|
+
break;
|
|
1219
1221
|
case 'auth-close': closeAuth(); break;
|
|
1220
|
-
case 'login': performLogin(); break;
|
|
1221
|
-
case '
|
|
1222
|
+
case 'login': performLogin('email'); break;
|
|
1223
|
+
case 'oauth-login': performLogin(el.dataset.provider || 'email'); break;
|
|
1224
|
+
case 'logout': e.preventDefault(); performLogout(); break;
|
|
1222
1225
|
case 'open-oferta': e.preventDefault(); openOfertaModal(); break;
|
|
1223
1226
|
case 'oferta-close': closeOfertaModal(); break;
|
|
1224
1227
|
case 'accept-from-oferta':
|
|
@@ -1232,20 +1235,18 @@
|
|
|
1232
1235
|
else requireAuthAndOferta(() => switchStage('addons'));
|
|
1233
1236
|
break;
|
|
1234
1237
|
case 'pick-tier':
|
|
1235
|
-
$$('#
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1238
|
+
$$('#segPakTier button').forEach(b => b.classList.toggle('on', b.dataset.tier === el.dataset.tier));
|
|
1239
|
+
if ($('#segPakVariant')) {
|
|
1240
|
+
$$('#segPakVariant button').forEach(b => b.classList.toggle('on', b.dataset.variant === 'addons_only'));
|
|
1241
|
+
}
|
|
1242
|
+
syncClientSeats(5);
|
|
1243
|
+
buyClientConfig();
|
|
1239
1244
|
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;
|
|
1245
|
+
case 'buy-client-config': buyClientConfig(); break;
|
|
1244
1246
|
case 'gen-hub-token': generateCabinetToken(); break;
|
|
1245
1247
|
case 'copy-hub-token': copyCabinetToken(); break;
|
|
1246
1248
|
case 'refresh-cabinet': refreshCabinetUser(); break;
|
|
1247
1249
|
case 'reserve-selected': reserveRegionFranchise(selectedCode); break;
|
|
1248
|
-
case 'bot-send': sendBot(); break;
|
|
1249
1250
|
case 'cert-close': $('#certModal')?.classList.remove('open'); break;
|
|
1250
1251
|
case 'cert-copy':
|
|
1251
1252
|
navigator.clipboard?.writeText($('#certJsonBox')?.textContent || '');
|
|
@@ -1260,19 +1261,15 @@
|
|
|
1260
1261
|
document.addEventListener('keydown', (e) => {
|
|
1261
1262
|
if (e.key === 'Escape') { closeAuth(); closeOfertaModal(); $('#certModal')?.classList.remove('open'); toggleAi(false); }
|
|
1262
1263
|
});
|
|
1263
|
-
$('#authModal')?.addEventListener('keydown', (e) => { if (e.key === 'Enter' && e.target.tagName !== 'BUTTON') performLogin(); });
|
|
1264
|
+
$('#authModal')?.addEventListener('keydown', (e) => { if (e.key === 'Enter' && e.target.tagName !== 'BUTTON') performLogin('email'); });
|
|
1264
1265
|
$('#aiInput')?.addEventListener('keydown', (e) => { if (e.key === 'Enter') sendAi(); });
|
|
1265
|
-
$('#
|
|
1266
|
-
$('#
|
|
1267
|
-
$('#
|
|
1268
|
-
$('#inpYears')?.addEventListener('input', recalcAddons);
|
|
1269
|
-
$('#inpPakExtraSeats')?.addEventListener('input', recalcBaseOrder);
|
|
1266
|
+
$('#inpPakExtraSeats')?.addEventListener('input', (e) => syncClientSeats(e.target.value));
|
|
1267
|
+
$('#inpPakExtraSeatsNum')?.addEventListener('input', (e) => syncClientSeats(e.target.value));
|
|
1268
|
+
$('#inpPakYears')?.addEventListener('input', recalcBaseOrder);
|
|
1270
1269
|
$('#rngPartnerSales')?.addEventListener('input', recalcPartner);
|
|
1271
1270
|
$('#inpRegionFilter')?.addEventListener('input', renderRegionsTable);
|
|
1272
|
-
bindSeg('segTier', recalcAddons);
|
|
1273
|
-
bindSeg('segCabinetBase', () => { syncSeats($('#inpSeats')?.value || 0); });
|
|
1274
1271
|
bindSeg('segPakTier', recalcBaseOrder);
|
|
1275
|
-
bindSeg('segPakVariant',
|
|
1272
|
+
bindSeg('segPakVariant', () => syncClientSeats($('#inpPakExtraSeatsNum')?.value || 0));
|
|
1276
1273
|
window.addEventListener('message', (e) => {
|
|
1277
1274
|
if (e.data?.type === 'odant:order-paid') {
|
|
1278
1275
|
refreshCabinetUser();
|
|
@@ -1286,7 +1283,6 @@
|
|
|
1286
1283
|
applyTheme(document.documentElement.getAttribute('data-theme') || 'dark');
|
|
1287
1284
|
bind();
|
|
1288
1285
|
renderOrbit();
|
|
1289
|
-
recalcAddons();
|
|
1290
1286
|
recalcBaseOrder();
|
|
1291
1287
|
recalcPartner();
|
|
1292
1288
|
renderRegionsTable();
|