feat: add demo device top-up flow
This commit is contained in:
@@ -72,8 +72,11 @@ const routeFromParams = () => {
|
|||||||
return "/home";
|
return "/home";
|
||||||
};
|
};
|
||||||
|
|
||||||
const initialMock = normalizeRouteMock(params.get("mock"));
|
|
||||||
let initialRoute = routeFromParams();
|
let initialRoute = routeFromParams();
|
||||||
|
const mockForRoute = (route) => (normalizePath(route) === "/devices" ? "devices" : "");
|
||||||
|
const initialMock = params.has("mock")
|
||||||
|
? normalizeRouteMock(params.get("mock"))
|
||||||
|
: normalizeRouteMock(mockForRoute(initialRoute) || defaultMock);
|
||||||
if (initialMock === "trial" && initialRoute === "/trial") {
|
if (initialMock === "trial" && initialRoute === "/trial") {
|
||||||
initialRoute = "/home";
|
initialRoute = "/home";
|
||||||
const normalizedUrl = new URL(window.location.href);
|
const normalizedUrl = new URL(window.location.href);
|
||||||
|
|||||||
@@ -293,7 +293,7 @@ const docsHref = '/getting-started/demo/';
|
|||||||
<option value="depleted">Трафик закончился</option>
|
<option value="depleted">Трафик закончился</option>
|
||||||
<option value="no-subscription">Нет подписки</option>
|
<option value="no-subscription">Нет подписки</option>
|
||||||
<option value="trial">Доступна пробная подписка</option>
|
<option value="trial">Доступна пробная подписка</option>
|
||||||
<option value="devices">Лимит устройств</option>
|
<option value="devices">Лимит и докупка устройств</option>
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
<div class="demo-topbar__actions">
|
<div class="demo-topbar__actions">
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
- [Админка: пользователи](/demo/admin/users)
|
- [Админка: пользователи](/demo/admin/users)
|
||||||
- [Админка: бэкапы](/demo/admin/backups)
|
- [Админка: бэкапы](/demo/admin/backups)
|
||||||
- [Пробный период](/demo/home?mock=trial)
|
- [Пробный период](/demo/home?mock=trial)
|
||||||
- [Устройства](/demo/devices?mock=devices)
|
- [Докупка устройств](/demo/devices?mock=devices)
|
||||||
|
|
||||||
## Как собирается
|
## Как собирается
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,10 @@ let demoAdsState = null;
|
|||||||
let demoSupportTicketsState = null;
|
let demoSupportTicketsState = null;
|
||||||
let demoSupportMessagesState = null;
|
let demoSupportMessagesState = null;
|
||||||
let demoTariffsState = null;
|
let demoTariffsState = null;
|
||||||
|
let demoPaymentSequence = 20000;
|
||||||
const demoSettingsChanges = new Map();
|
const demoSettingsChanges = new Map();
|
||||||
|
const demoPaymentStatuses = new Map();
|
||||||
|
const deviceTopupSaleModes = new Set(["hwid_device", "hwid_devices", "hwid_devices_renewal"]);
|
||||||
|
|
||||||
function demoPromos() {
|
function demoPromos() {
|
||||||
if (!demoPromosState) demoPromosState = defaultClone(DEMO_DATASET.promos || []);
|
if (!demoPromosState) demoPromosState = defaultClone(DEMO_DATASET.promos || []);
|
||||||
@@ -68,6 +71,79 @@ function jsonBody(options) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isDeviceTopupSaleMode(value) {
|
||||||
|
return deviceTopupSaleModes.has(String(value || "").toLowerCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
function demoDeviceTopupPlan(body) {
|
||||||
|
const deviceCount = Number(body.device_count || body.months || 0);
|
||||||
|
const plans = DEV_MOCK.data.device_topup_options?.plans || [];
|
||||||
|
return (
|
||||||
|
plans.find(
|
||||||
|
(plan) =>
|
||||||
|
String(plan.tariff_key || "") === String(body.tariff_key || plan.tariff_key || "") &&
|
||||||
|
Number(plan.device_count || plan.purchased_hwid_devices || plan.months || 0) === deviceCount
|
||||||
|
) ||
|
||||||
|
plans.find(
|
||||||
|
(plan) =>
|
||||||
|
Number(plan.device_count || plan.purchased_hwid_devices || plan.months || 0) === deviceCount
|
||||||
|
) ||
|
||||||
|
null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyDemoDeviceTopup(deviceCount) {
|
||||||
|
const count = Math.max(1, Number(deviceCount || 0));
|
||||||
|
const subscription = DEV_MOCK.data.subscription || {};
|
||||||
|
const devicesPayload = DEV_MOCK.data.devices || {};
|
||||||
|
const topupOptions = DEV_MOCK.data.device_topup_options || {};
|
||||||
|
const devices = Array.isArray(devicesPayload.devices) ? devicesPayload.devices : [];
|
||||||
|
const currentMax = Number(
|
||||||
|
subscription.max_devices ||
|
||||||
|
devicesPayload.max_devices ||
|
||||||
|
topupOptions.max_devices ||
|
||||||
|
topupOptions.current_limit ||
|
||||||
|
0
|
||||||
|
);
|
||||||
|
const nextMax = currentMax > 0 ? currentMax + count : currentMax;
|
||||||
|
const currentExtra = Number(
|
||||||
|
subscription.extra_hwid_devices || topupOptions.extra_hwid_devices || 0
|
||||||
|
);
|
||||||
|
const nextExtra = currentExtra + count;
|
||||||
|
const validUntil =
|
||||||
|
subscription.extra_hwid_devices_valid_until_text ||
|
||||||
|
topupOptions.extra_hwid_devices_valid_until_text ||
|
||||||
|
subscription.end_date_text ||
|
||||||
|
"28.06.2026 12:00";
|
||||||
|
|
||||||
|
DEV_MOCK.data.subscription = {
|
||||||
|
...subscription,
|
||||||
|
active: true,
|
||||||
|
can_topup_devices: true,
|
||||||
|
max_devices: nextMax,
|
||||||
|
extra_hwid_devices: nextExtra,
|
||||||
|
extra_hwid_devices_valid_until_text: validUntil,
|
||||||
|
};
|
||||||
|
DEV_MOCK.data.devices = {
|
||||||
|
...devicesPayload,
|
||||||
|
ok: true,
|
||||||
|
enabled: true,
|
||||||
|
current_devices: devices.length || Number(devicesPayload.current_devices || 0),
|
||||||
|
max_devices: nextMax,
|
||||||
|
max_devices_label: nextMax > 0 ? String(nextMax) : devicesPayload.max_devices_label || "∞",
|
||||||
|
};
|
||||||
|
DEV_MOCK.data.device_topup_options = {
|
||||||
|
...topupOptions,
|
||||||
|
ok: true,
|
||||||
|
enabled: true,
|
||||||
|
current_devices: devices.length || Number(topupOptions.current_devices || 0),
|
||||||
|
max_devices: nextMax,
|
||||||
|
current_limit: nextMax,
|
||||||
|
extra_hwid_devices: nextExtra,
|
||||||
|
extra_hwid_devices_valid_until_text: validUntil,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function writeDemoLanguage(language) {
|
function writeDemoLanguage(language) {
|
||||||
if (typeof window === "undefined") return;
|
if (typeof window === "undefined") return;
|
||||||
try {
|
try {
|
||||||
@@ -1681,6 +1757,26 @@ export async function mockApi(path, options = {}, context = {}) {
|
|||||||
return { ok: true, csrf_token: "local-preview-csrf" };
|
return { ok: true, csrf_token: "local-preview-csrf" };
|
||||||
}
|
}
|
||||||
if (path === "/payments" && String(options.method || "").toUpperCase() === "POST") {
|
if (path === "/payments" && String(options.method || "").toUpperCase() === "POST") {
|
||||||
|
const body = jsonBody(options);
|
||||||
|
if (isDeviceTopupSaleMode(body.sale_mode)) {
|
||||||
|
const plan = demoDeviceTopupPlan(body);
|
||||||
|
const deviceCount = Number(
|
||||||
|
body.device_count || plan?.device_count || plan?.purchased_hwid_devices || body.months || 1
|
||||||
|
);
|
||||||
|
const paymentId = ++demoPaymentSequence;
|
||||||
|
demoPaymentStatuses.set(String(paymentId), {
|
||||||
|
status: "pending_yookassa",
|
||||||
|
paid: false,
|
||||||
|
sale_mode: body.sale_mode || "hwid_devices",
|
||||||
|
device_count: deviceCount,
|
||||||
|
applied: false,
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
ok: true,
|
||||||
|
action: "invoice_sent",
|
||||||
|
payment_id: paymentId,
|
||||||
|
};
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
ok: true,
|
ok: true,
|
||||||
action: "open_link",
|
action: "open_link",
|
||||||
@@ -1689,6 +1785,22 @@ export async function mockApi(path, options = {}, context = {}) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (/^\/payments\/\d+$/.test(path) && String(options.method || "GET").toUpperCase() === "GET") {
|
if (/^\/payments\/\d+$/.test(path) && String(options.method || "GET").toUpperCase() === "GET") {
|
||||||
|
const paymentId = String(path.split("/").pop());
|
||||||
|
const status = demoPaymentStatuses.get(paymentId);
|
||||||
|
if (status) {
|
||||||
|
if (!status.applied && isDeviceTopupSaleMode(status.sale_mode)) {
|
||||||
|
applyDemoDeviceTopup(status.device_count);
|
||||||
|
status.applied = true;
|
||||||
|
}
|
||||||
|
status.status = "succeeded";
|
||||||
|
status.paid = true;
|
||||||
|
return {
|
||||||
|
ok: true,
|
||||||
|
payment_id: Number(paymentId),
|
||||||
|
status: status.status,
|
||||||
|
paid: status.paid,
|
||||||
|
};
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
ok: true,
|
ok: true,
|
||||||
payment_id: Number(path.split("/").pop()),
|
payment_id: Number(path.split("/").pop()),
|
||||||
|
|||||||
@@ -778,31 +778,40 @@ export function applyPreviewMock(kind) {
|
|||||||
};
|
};
|
||||||
DEV_MOCK.data.device_topup_options = {
|
DEV_MOCK.data.device_topup_options = {
|
||||||
ok: true,
|
ok: true,
|
||||||
|
enabled: true,
|
||||||
|
tariff_key: "standard",
|
||||||
|
tariff_name: "Стандарт",
|
||||||
|
current_limit: 5,
|
||||||
current_devices: 5,
|
current_devices: 5,
|
||||||
max_devices: 5,
|
max_devices: 5,
|
||||||
available_extra_devices: 3,
|
available_extra_devices: 3,
|
||||||
extra_hwid_devices: 0,
|
extra_hwid_devices: 0,
|
||||||
|
extra_hwid_devices_valid_until_text: "",
|
||||||
|
renewal_available: false,
|
||||||
|
renewal_recommended_count: 0,
|
||||||
plans: [
|
plans: [
|
||||||
{
|
{
|
||||||
id: "standard:hwid:1",
|
id: "standard:hwid:1",
|
||||||
tariff_key: "standard",
|
tariff_key: "standard",
|
||||||
tariff_name: "Стандарт",
|
tariff_name: "Стандарт",
|
||||||
sale_mode: "hwid_device",
|
sale_mode: "hwid_devices",
|
||||||
purchased_hwid_devices: 1,
|
purchased_hwid_devices: 1,
|
||||||
price: 120,
|
price: 120,
|
||||||
currency: "RUB",
|
currency: "RUB",
|
||||||
title: "+1 устройство",
|
title: "+1 устройство",
|
||||||
|
subtitle: "Стандарт",
|
||||||
device_count: 1,
|
device_count: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "standard:hwid:3",
|
id: "standard:hwid:3",
|
||||||
tariff_key: "standard",
|
tariff_key: "standard",
|
||||||
tariff_name: "Стандарт",
|
tariff_name: "Стандарт",
|
||||||
sale_mode: "hwid_device",
|
sale_mode: "hwid_devices",
|
||||||
purchased_hwid_devices: 3,
|
purchased_hwid_devices: 3,
|
||||||
price: 290,
|
price: 290,
|
||||||
currency: "RUB",
|
currency: "RUB",
|
||||||
title: "+3 устройства",
|
title: "+3 устройства",
|
||||||
|
subtitle: "Стандарт",
|
||||||
device_count: 3,
|
device_count: 3,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
Reference in New Issue
Block a user