const { useEffect, useMemo, useRef, useState } = React; window.__SHOP_BOOT_STAGE__ = "app-js-loaded"; const RUNTIME_VERSION_KEY = "shop-runtime-version"; const CURRENT_RUNTIME_VERSION = window.__SHOP_RUNTIME_VERSION__ || "dev"; function clearShopClientState() { try { const keysToDelete = []; for (let index = 0; index < localStorage.length; index += 1) { const key = localStorage.key(index); if (key && key.startsWith("shop-")) { keysToDelete.push(key); } } keysToDelete.forEach((key) => localStorage.removeItem(key)); sessionStorage.clear(); } catch (err) { console.warn("Failed to clear cached shop state.", err); } } function enforceRuntimeVersion() { try { const storedVersion = localStorage.getItem(RUNTIME_VERSION_KEY); if (storedVersion !== CURRENT_RUNTIME_VERSION) { clearShopClientState(); localStorage.setItem(RUNTIME_VERSION_KEY, CURRENT_RUNTIME_VERSION); } } catch (err) { console.warn("Failed to enforce runtime version.", err); } } enforceRuntimeVersion(); const API_BASE = window.__SHOP_API_BASE__ || localStorage.getItem("shop-api-base") || (window.location.protocol === "file:" ? "http://127.0.0.1:8000/api" : "/api"); const SESSION_TOKEN_KEY = "shop-session-token"; const ADMIN_UI_VERSION_KEY = "shop-admin-ui-version"; const API_REQUEST_TIMEOUT_MS = 12000; const HEALTHCHECK_TIMEOUT_MS = 5000; const GET_RETRY_COUNT = 1; const navItems = [ { id: "home", label: "Главная", icon: "icon-home-fill" }, { id: "favorites", label: "Избранное", icon: "icon-heart-fill" }, { id: "cart", label: "Корзина", icon: "icon-bag-fill" }, { id: "profile", label: "Профиль", icon: "icon-user-fill" }, ]; const sortModes = [ { id: "featured", label: "По популярности" }, { id: "priceAsc", label: "Цена: ниже" }, { id: "priceDesc", label: "Цена: выше" }, ]; const emptyPromoForm = { code: "", discount_type: "percent", amount: "10", description: "", usage_limit: "", is_active: true, }; const emptyProductForm = { title: "", price: "2990", category: "ФУТБОЛКИ", sort_order: "0", is_active: true, images: [], imageUrlInput: "", sizesText: "S\nM\nL\nXL", descriptionText: "", }; const emptyBroadcastForm = { text: "", mediaUrl: "", mediaType: "", mediaName: "", testChatIds: "", }; const emptyChannelPostForm = { text: "", channelUsername: "", }; function createEmptyReminderStep(delaySeconds = "30") { return { id: null, delay_minutes: delaySeconds, text: "", is_active: true, }; } const emptyReminderPoolForm = { title: "", is_active: true, is_default: true, steps: [createEmptyReminderStep("30"), createEmptyReminderStep("90")], }; function makeReminderPoolForm(pool) { if (!pool) { return { ...emptyReminderPoolForm, steps: emptyReminderPoolForm.steps.map((step) => ({ ...step })), }; } return { title: pool.title || "", is_active: Boolean(pool.is_active), is_default: Boolean(pool.is_default), steps: (pool.steps || []).length ? pool.steps.map((step) => ({ id: step.id || null, delay_minutes: String(step.delay_minutes || 30), text: step.text || "", is_active: step.is_active !== false, })) : [createEmptyReminderStep("30")], }; } const CDEK_DEFAULT_LOCATION = [37.622513, 55.75322]; let cdekWidgetScriptPromise = null; function ensureCdekWidgetScript() { if (window.CDEKWidget) { return Promise.resolve(window.CDEKWidget); } if (cdekWidgetScriptPromise) { return cdekWidgetScriptPromise; } cdekWidgetScriptPromise = new Promise((resolve, reject) => { const existing = document.querySelector('script[data-cdek-widget="1"]'); if (existing) { existing.addEventListener("load", () => resolve(window.CDEKWidget)); existing.addEventListener("error", () => reject(new Error("Не удалось загрузить виджет СДЭК"))); return; } const script = document.createElement("script"); script.src = "https://cdn.jsdelivr.net/npm/@cdek-it/widget@3.11.1"; script.async = true; script.dataset.cdekWidget = "1"; script.onload = () => { if (window.CDEKWidget) { resolve(window.CDEKWidget); } else { reject(new Error("Виджет СДЭК загрузился некорректно")); } }; script.onerror = () => reject(new Error("Не удалось загрузить виджет СДЭК")); document.head.appendChild(script); }); return cdekWidgetScriptPromise; } function rub(value) { return `${new Intl.NumberFormat("ru-RU").format(value)} \u20bd`; } function getStoredToken() { return localStorage.getItem(SESSION_TOKEN_KEY) || ""; } function setStoredToken(token) { localStorage.setItem(SESSION_TOKEN_KEY, token); } function clearStoredToken() { localStorage.removeItem(SESSION_TOKEN_KEY); } function getTelegramInitData() { return window.Telegram?.WebApp?.initData || ""; } function getTelegramUserFromInitData(initData) { if (!initData) { return null; } try { const params = new URLSearchParams(initData); const rawUser = params.get("user"); if (!rawUser) { return null; } const user = JSON.parse(rawUser); return user && typeof user === "object" ? user : null; } catch (err) { console.warn("Failed to parse Telegram initData user.", err); return null; } } function setBootStage(stage) { window.__SHOP_BOOT_STAGE__ = stage; } function reportFrontendDiagnostic(source, error, extras = {}) { try { const payload = { source, message: error?.message || String(error || ""), url: window.location.href, stack: error?.stack || "", userAgent: navigator.userAgent, bootStage: window.__SHOP_BOOT_STAGE__ || "unknown", ...extras, }; fetch(`${API_BASE}/frontend/log`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload), keepalive: true, }).catch(() => {}); } catch (reportError) { console.warn("Failed to send frontend diagnostic.", reportError); } } function reportBootstrapMetric(metric, durationMs, status = "ok", extras = {}) { try { fetch(`${API_BASE}/frontend/bootstrap-metrics`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ metric, status, durationMs, bootStage: window.__SHOP_BOOT_STAGE__ || "unknown", userAgent: navigator.userAgent, ...extras, }), keepalive: true, }).catch(() => {}); } catch (reportError) { console.warn("Failed to send bootstrap metric.", reportError); } } async function measureBootstrapStep(metric, task) { const startedAt = performance.now(); try { const result = await task(); reportBootstrapMetric(metric, performance.now() - startedAt, "ok"); return result; } catch (err) { reportBootstrapMetric(metric, performance.now() - startedAt, "error", { message: err?.message || String(err || ""), }); throw err; } } async function checkApiHealth() { try { const startedAt = performance.now(); await apiRequest("/health", { skipAuth: true, retries: 0, timeoutMs: HEALTHCHECK_TIMEOUT_MS, }); reportBootstrapMetric("bootstrap.health", performance.now() - startedAt, "ok"); return true; } catch (err) { reportBootstrapMetric("bootstrap.health", 0, "error", { message: err?.message || "health check failed", }); return false; } } function buildBootstrapErrorMessage(error, apiHealthy) { if (error?.isTimeout) { return apiHealthy ? "Сервер отвечает слишком долго. Попробуйте еще раз или временно отключите VPN." : "Не удается достучаться до сервера. Похоже, сеть или VPN режет соединение."; } if (error?.isNetworkError) { return apiHealthy ? "Сеть нестабильна. Попробуйте еще раз или смените соединение." : "Сервер сейчас недоступен из вашей сети. Попробуйте без VPN или позже."; } return error?.message || "Не удалось загрузить данные"; } function isLocalPreview() { return ( window.location.protocol === "file:" || window.location.hostname === "127.0.0.1" || window.location.hostname === "localhost" ); } function parseTimestamp(value) { if (!value) { return null; } if (typeof value === "number") { return new Date(value * 1000); } return new Date(`${value.replace(" ", "T")}Z`); } function formatOrderDate(value) { const date = parseTimestamp(value); if (!date || Number.isNaN(date.getTime())) { return "\u2014"; } return new Intl.DateTimeFormat("ru-RU", { day: "2-digit", month: "short", hour: "2-digit", minute: "2-digit", }).format(date); } function getDisplayName(user) { if (!user) { return "Покупатель"; } return ( user.display_name || [user.first_name, user.last_name].filter(Boolean).join(" ").trim() || (user.username ? `@${user.username}` : "Покупатель") ); } function getInitials(user) { const name = getDisplayName(user).replace("@", "").trim(); const parts = name.split(/\s+/).filter(Boolean); const letters = parts.slice(0, 2).map((part) => part[0]?.toUpperCase() || ""); return letters.join("") || "TG"; } async function copyText(text) { if (navigator.clipboard?.writeText) { await navigator.clipboard.writeText(text); return; } const input = document.createElement("textarea"); input.value = text; input.setAttribute("readonly", ""); input.style.position = "absolute"; input.style.left = "-9999px"; document.body.appendChild(input); input.select(); document.execCommand("copy"); document.body.removeChild(input); } function sleep(ms) { return new Promise((resolve) => window.setTimeout(resolve, ms)); } function isRetryableRequest(method, error) { const normalizedMethod = (method || "GET").toUpperCase(); if (!["GET", "HEAD"].includes(normalizedMethod)) { return false; } if (error?.isTimeout || error?.isNetworkError) { return true; } const status = Number(error?.status || 0); return status === 408 || status === 429 || status >= 500; } async function apiRequest(path, options = {}) { const headers = { ...(options.headers || {}) }; const isFormData = typeof FormData !== "undefined" && options.body instanceof FormData; const method = (options.method || "GET").toUpperCase(); const retries = Number.isInteger(options.retries) ? options.retries : GET_RETRY_COUNT; const timeoutMs = options.timeoutMs || API_REQUEST_TIMEOUT_MS; if (!isFormData && !headers["Content-Type"]) { headers["Content-Type"] = "application/json"; } if (!options.skipAuth) { const token = getStoredToken(); if (token) { headers.Authorization = `Bearer ${token}`; } } let attempt = 0; while (true) { const controller = new AbortController(); const timeoutId = window.setTimeout(() => controller.abort("timeout"), timeoutMs); try { const response = await fetch(`${API_BASE}${path}`, { ...options, method, headers, signal: controller.signal, }); if (!response.ok) { const payload = await response.json().catch(() => ({})); if (response.status === 401 && !options.skipAuth) { clearStoredToken(); } const detail = payload.detail; const error = new Error( typeof detail === "string" ? detail : payload.message || `Ошибка API (${response.status})` ); error.status = response.status; error.payload = payload; error.path = path; throw error; } return response.json(); } catch (err) { const isAbortError = err?.name === "AbortError" || err === "timeout"; if (isAbortError) { const timeoutError = new Error("Сервер долго не отвечает"); timeoutError.isTimeout = true; timeoutError.path = path; err = timeoutError; } else if (!err?.status) { err.isNetworkError = true; err.path = path; if (!err.message) { err.message = "Не удалось связаться с сервером"; } } if (attempt < retries && isRetryableRequest(method, err)) { attempt += 1; await sleep(350 * attempt); continue; } throw err; } finally { window.clearTimeout(timeoutId); } } } function makeCartKey(productId, size = "") { return `${productId}::${size || ""}`; } function buildCartIndex(items) { return Object.fromEntries(items.map((item) => [item.key, item])); } function normalizeState(payload) { const favorites = payload.favorites || []; const cartItems = (payload.cart || []).map((item) => ({ ...item, size: item.size || "", key: item.key || makeCartKey(item.product_id, item.size || ""), })); return { user: payload.user || null, favorites, cartItems, cartIndex: buildCartIndex(cartItems), orders: payload.orders || [], supportUrl: payload.support_url || null, supportUsername: payload.support_username || null, }; } function Icon({ id }) { return ( ); } function CatalogHeader({ activeCategory, searchOpen, searchQuery, onSearchToggle, onSearchChange, onOpenCategories, onCycleSort, onOpenProfile, categoryButtons, }) { return ( <>

BLISSCOMMUNITY

{categoryButtons}
{searchOpen ? (
) : null}
); } function ProductCard({ product, isFavorite, onToggleFavorite, onOpen }) { return (
{product.images.map((_, index) => ( ))}
); } function FavoritesEmpty({ onBackToShop }) { return (

В избранном пока пусто

Сохраняйте понравившиеся вещи, чтобы быстро вернуться к ним позже.

); } const checkoutSteps = [ { id: "contact", label: "Контакты" }, { id: "delivery", label: "Доставка" }, { id: "shipping", label: "Способ доставки" }, { id: "payment", label: "Оплата" }, { id: "review", label: "Подтверждение" }, ]; function formatCdekEta(tariff) { if (!tariff) { return ""; } if (tariff.period_min && tariff.period_max) { if (tariff.period_min === tariff.period_max) { return `${tariff.period_max} дн.`; } return `${tariff.period_min}-${tariff.period_max} дн.`; } if (tariff.period_max) { return `${tariff.period_max} дн.`; } return ""; } function getOrderStatusLabel(status, paymentProvider) { const normalized = (status || "").toLowerCase(); if (normalized === "paid") { return "Оплачен"; } if (normalized === "pending_payment") { return "Ожидает оплаты"; } if (normalized === "new" || normalized === "manual_review") { return paymentProvider === "manual" ? "Нужно подтвердить" : "Новый заказ"; } if (normalized === "processing") { return "В обработке"; } if (normalized === "completed") { return "Завершен"; } return "Заказ создан"; } function CheckoutFlow({ open, step, draft, options, cartItems, productMap, couponCode, promoPreview, applyingPromo, submitting, onClose, onBack, onAdvance, onSubmit, onChange, onDeliveryPicked, onCouponChange, onApplyPromo, }) { const widgetRef = useRef(null); const widgetHostRef = useRef(null); const deliveryPickedRef = useRef(onDeliveryPicked); const [widgetLoading, setWidgetLoading] = useState(false); const [widgetError, setWidgetError] = useState(""); const widgetConfig = options?.cdek_widget || {}; const deliveryChosen = Boolean(draft.delivery_code && draft.delivery_method_id && draft.delivery_address); const totalItems = cartItems.reduce((sum, item) => sum + item.quantity, 0); const itemsAmount = cartItems.reduce((sum, item) => { const product = productMap[item.product_id]; return sum + (product ? product.price * item.quantity : 0); }, 0); const discountAmount = promoPreview?.coupon_applied ? promoPreview.discount_amount || 0 : 0; const deliveryAmount = draft.delivery_amount || 0; const finalTotal = (promoPreview?.coupon_applied ? promoPreview.total_amount || Math.max(0, itemsAmount - discountAmount) : itemsAmount) + deliveryAmount; const activeStepIndex = checkoutSteps.findIndex((item) => item.id === step); const reviewDeliveryAddress = draft.delivery_address || ""; useEffect(() => { deliveryPickedRef.current = onDeliveryPicked; }, [onDeliveryPicked]); useEffect(() => { let cancelled = false; async function preloadWidgetScript() { if (!open || !widgetConfig.configured) { return; } setWidgetLoading(true); try { await ensureCdekWidgetScript(); } catch (err) { if (!cancelled) { setWidgetError(err.message || "Не удалось инициализировать виджет СДЭК"); } } finally { if (!cancelled) { setWidgetLoading(false); } } } preloadWidgetScript(); return () => { cancelled = true; closeDeliveryWidget(); }; }, [ open, widgetConfig.configured, ]); if (!open || !options) { return null; } function destroyWidgetInstance() { if (widgetRef.current?.destroy) { widgetRef.current.destroy(); } widgetRef.current = null; } function closeDeliveryWidget() { destroyWidgetInstance(); if (widgetHostRef.current) { widgetHostRef.current.remove(); widgetHostRef.current = null; } document.body.classList.remove("cdek-modal-open"); } function ensureWidgetHost() { closeDeliveryWidget(); const host = document.createElement("div"); host.className = "cdek-modal-shell"; host.innerHTML = `
`; host.querySelector(".cdek-modal-backdrop")?.addEventListener("click", closeDeliveryWidget); host.querySelector(".cdek-modal-close")?.addEventListener("click", closeDeliveryWidget); document.body.appendChild(host); document.body.classList.add("cdek-modal-open"); widgetHostRef.current = host; return "cdek-modal-root"; } function handleDeliveryChoose(type, tariff, address) { const mode = type === "door" ? "courier" : "pickup"; const pointLabel = address?.name || address?.office_name || (mode === "courier" ? "Адрес доставки" : "Пункт выдачи СДЭК"); const label = tariff?.tariff_name || tariff?.name || (mode === "courier" ? "Курьерская доставка" : "Посылка склад-склад"); const normalizedAddress = mode === "courier" ? address?.formatted || address?.address || draft.delivery_address || "" : address?.address || address?.formatted || ""; const normalizedCode = address?.code || address?.office_code || address?.city_code || normalizedAddress || `${mode}-${Date.now()}`; const cityCode = address?.city_code || address?.location?.city_code || address?.city?.code || ""; const officeCode = address?.code || address?.office_code || ""; deliveryPickedRef.current?.({ delivery_mode: mode, delivery_code: String(normalizedCode), delivery_point_label: pointLabel, delivery_address: normalizedAddress, delivery_city_code: cityCode ? String(cityCode) : "", delivery_office_code: officeCode ? String(officeCode) : "", delivery_method_id: String(tariff?.tariff_code || tariff?.id || mode), delivery_label: label, delivery_eta: formatCdekEta(tariff), delivery_amount: Number(tariff?.delivery_sum || 0), }); closeDeliveryWidget(); } async function openDeliveryWidget() { if (!widgetConfig.configured) { setWidgetError( "Добавьте на сервере YANDEX_MAPS_API_KEY, CDEK_INTEGRATION_ACCOUNT и CDEK_INTEGRATION_PASSWORD, чтобы включить выбор доставки." ); return; } setWidgetError(""); setWidgetLoading(true); try { const CDEKWidget = await ensureCdekWidgetScript(); const rootId = ensureWidgetHost(); widgetRef.current = new CDEKWidget({ root: rootId, apiKey: widgetConfig.api_key, servicePath: widgetConfig.service_path, canChoose: true, defaultLocation: CDEK_DEFAULT_LOCATION, from: widgetConfig.from, goods: widgetConfig.goods || [], lang: "rus", currency: "RUB", hideDeliveryOptions: draft.delivery_mode === "pickup" ? { door: true } : { office: true }, onReady() { setWidgetLoading(false); }, onChoose(type, tariff, address) { handleDeliveryChoose(type, tariff, address); }, }); } catch (err) { closeDeliveryWidget(); setWidgetLoading(false); setWidgetError(err.message || "Не удалось открыть виджет СДЭК"); } } return (
BLISSCOMMUNITY
{checkoutSteps.map((item, index) => ( ))}
{step === "contact" ? (

Контактные данные

Ваши данные
🇷🇺
Комментарий к заказу

Эти данные нужны, чтобы мы могли оформить и доставить ваш заказ.

) : null} {step === "delivery" ? (
{draft.delivery_mode === "pickup" ? "СДЭК • ПВЗ" : "СДЭК • КУРЬЕР"}

{draft.delivery_mode === "pickup" ? "Выберите удобный пункт выдачи" : "Выберите адрес и способ доставки"}

Официальный виджет СДЭК откроется поверх экрана и вернет готовый вариант доставки в оформление.

{!deliveryChosen && !widgetError ? (
После выбора мы автоматически подставим адрес, срок и стоимость на следующий шаг.
) : null} {widgetError ?
{widgetError}
: null} {deliveryChosen ? (
{draft.delivery_label} {rub(deliveryAmount)}
{reviewDeliveryAddress}
{draft.delivery_mode === "pickup" ? "Пункт выдачи" : "Курьер"} {draft.delivery_eta ? ( {draft.delivery_eta} ) : null}
) : null}
) : null} {step === "shipping" ? (

Способ доставки

Выбранная доставка
{draft.delivery_label || "Способ доставки не выбран"} {draft.delivery_eta || "Срок появится после выбора в виджете"} {rub(deliveryAmount)}
{reviewDeliveryAddress || "Откройте предыдущий шаг и выберите ПВЗ или адрес"}
) : null} {step === "payment" ? (

Оплата

Способ оплаты
) : null} {step === "review" ? (

Подтверждение заказа

Контактные данные
{draft.contact_first_name} {draft.contact_last_name} {draft.contact_phone} {draft.contact_email}
Доставка {draft.delivery_label || (draft.delivery_mode === "pickup" ? "СДЭК" : "Курьер")}: {reviewDeliveryAddress}
{draft.delivery_eta || "Доставка"}
{cartItems.map((item) => { const product = productMap[item.product_id]; if (!product) { return null; } return (
{product.title}
); })}
{draft.delivery_label || "Доставка"} {rub(deliveryAmount)}
Способ оплаты
Онлайн-оплата
Комментарий к заказу
{draft.order_note || "Без комментария"}
onCouponChange(event.target.value.toUpperCase())} />
{promoPreview?.promo_error ? (
{promoPreview.promo_error}
) : null} {promoPreview?.coupon_applied ? (
Промокод {promoPreview.coupon_code} применен, скидка {rub(discountAmount)}
) : null}
Товары ({totalItems}) {rub(itemsAmount)}
Доставка {rub(deliveryAmount)}
{discountAmount > 0 ? (
Скидка -{rub(discountAmount)}
) : null}
Итого {rub(finalTotal)}

Нажимая кнопку, вы соглашаетесь на обработку персональных данных

) : null}
{step === "contact" ? ( ) : null} {step === "delivery" ? ( ) : null} {step === "shipping" ? ( ) : null} {step === "payment" ? ( ) : null} {step === "review" ? ( ) : null}
); } function CartScreen({ cartItems, productMap, couponCode, promoPreview, applyingPromo, onCouponChange, onApplyPromo, onClearCart, onIncrement, onDecrement, onCheckout, }) { if (!cartItems.length) { return (

Корзина пока пустая

Добавьте товары в корзину, чтобы оформить заказ.

); } const totalItems = cartItems.reduce((sum, item) => sum + item.quantity, 0); const subtotalAmount = cartItems.reduce((sum, item) => { const product = productMap[item.product_id]; return sum + (product ? product.price * item.quantity : 0); }, 0); const discountAmount = promoPreview?.coupon_applied ? promoPreview.discount_amount || 0 : 0; const totalAmount = promoPreview?.coupon_applied ? promoPreview.total_amount || Math.max(0, subtotalAmount - discountAmount) : subtotalAmount; const appliedCouponCode = promoPreview?.coupon_applied ? promoPreview.coupon_code : ""; return (

Корзина

{cartItems.map((item) => { const product = productMap[item.product_id]; if (!product) { return null; } return (
{product.title}

{product.title}

Размер одежды {item.size || "Единый"}
{item.quantity}
{rub(product.price * item.quantity)}
); })}
onCouponChange(event.target.value.toUpperCase())} />
{promoPreview?.promo_error ? (
{promoPreview.promo_error}
) : null} {promoPreview?.coupon_applied ? (
Промокод {appliedCouponCode} применен, скидка {rub(discountAmount)}
) : null}
Товары ({totalItems}) {rub(subtotalAmount)}
{discountAmount > 0 ? (
Скидка -{rub(discountAmount)}
) : null}
Итого {rub(totalAmount)}
); } function ProfileScreen({ user, orders, favoritesCount, cartCount, productMap, supportUrl, supportUsername, onOpenAdmin, onResumeOrderPayment, }) { const displayName = getDisplayName(user); const username = user?.username ? `@${user.username}` : "не указан"; const authDate = user?.last_auth_date ? formatOrderDate(user.last_auth_date) : "\u2014"; return (
{user?.photo_url ? ( {displayName} ) : (
{getInitials(user)}
)}

{displayName}

{username}
Заказы {orders.length}
Избранное {favoritesCount}
Товары в корзине {cartCount}
Профиль
Имя пользователя {username}
Язык {user?.language_code || "ru"}
Последний вход {authDate}
{supportUsername ? (
Поддержка @{supportUsername}
) : null} {user?.is_admin ? ( ) : null}
История заказов
{orders.length ? (
{orders.map((order) => (
Заказ #{order.id}
{formatOrderDate(order.created_at)}
{rub(order.total_amount)}
{getOrderStatusLabel(order.status, order.payment_provider)}
{order.items.map((item, index) => { const product = productMap[item.product_id]; return (
{product?.title || `Товар #${item.product_id}`} {item.quantity} × {rub(item.unit_price)}
); })}
{order.coupon_code ?
Промокод: {order.coupon_code}
: null} {order.status !== "paid" && order.payment_provider === "robokassa" ? ( ) : null}
))}
) : (
У вас пока нет заказов. Когда оформите первую покупку, она появится здесь.
)}
); } function AdminOrdersSection({ orders, selectedOrderIds, deletingOrders, onToggleOrderSelection, onSelectAllOrders, onClearOrderSelection, onDeleteSelectedOrders, }) { return (
Все заказы
Выбрано: {selectedOrderIds.length} из {orders.length}
{orders.length ? ( orders.map((order) => { const isSelected = selectedOrderIds.includes(order.id); return (
{rub(order.total_amount)}
{order.customer.display_name}
{order.customer.username ? `@${order.customer.username} • ` : ""} {order.status}
{formatOrderDate(order.created_at)}
{order.coupon_code ? (
Промокод: {order.coupon_code} • скидка {rub(order.discount_amount || 0)}
) : null}
{order.items.map((item, index) => (
{item.product_title || `Товар #${item.product_id}`} {item.quantity} × {rub(item.unit_price)} {item.size ? `• ${item.size}` : ""}
))}
); }) ) : (
Заказов пока нет.
)}
); } function AdminPromoSection({ dashboard, promoForm, editingPromoId, onPromoFormChange, onSavePromo, onEditPromo, onResetPromo, }) { return (
Промокоды
{editingPromoId ? ( ) : null}
{dashboard.promoCodes.map((promo) => (
{promo.code}
{promo.discount_type === "percent" ? "процент" : "фикс"} • {promo.amount} {promo.usage_limit ? ` • ${promo.uses_count}/${promo.usage_limit}` : ` • использован ${promo.uses_count} раз`}
{promo.is_active ? "активен" : "выключен"}
))}
); } function AdminBotSection({ dashboard, botStartMessage, botSaving, broadcastForm, broadcastUploading, broadcastSending, channelPostForm, channelPostSending, reminderPoolForm, reminderSaving, editingReminderPoolId, onBotStartMessageChange, onSaveBotStartMessage, onBroadcastFormChange, onBroadcastMediaUpload, onClearBroadcastMedia, onSendTestBroadcast, onSendBroadcast, onChannelPostFormChange, onSendChannelPost, onReminderPoolFormChange, onReminderStepChange, onAddReminderStep, onRemoveReminderStep, onSaveReminderPool, onEditReminderPool, onResetReminderPool, }) { return (
Бот и рассылки