@zenky/storefront-vue 0.9.46 → 0.10.0

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.
Files changed (93) hide show
  1. package/LICENSE.md +2 -2
  2. package/README.md +163 -6
  3. package/dist/cart/index.d.ts +559 -0
  4. package/dist/cart/index.js +13 -0
  5. package/dist/cart/index.js.map +1 -0
  6. package/dist/catalog/index.d.ts +73 -0
  7. package/dist/catalog/index.js +4 -0
  8. package/dist/catalog/index.js.map +1 -0
  9. package/dist/chunk-2OD7CLKD.js +102 -0
  10. package/dist/chunk-2OD7CLKD.js.map +1 -0
  11. package/dist/chunk-4MJKN7PL.js +111 -0
  12. package/dist/chunk-4MJKN7PL.js.map +1 -0
  13. package/dist/chunk-5QCUH3FF.js +15 -0
  14. package/dist/chunk-5QCUH3FF.js.map +1 -0
  15. package/dist/chunk-AL5W5JSD.js +41 -0
  16. package/dist/chunk-AL5W5JSD.js.map +1 -0
  17. package/dist/chunk-BIGTKVC6.js +38 -0
  18. package/dist/chunk-BIGTKVC6.js.map +1 -0
  19. package/dist/chunk-BWUKMWHL.js +45 -0
  20. package/dist/chunk-BWUKMWHL.js.map +1 -0
  21. package/dist/chunk-DKWDKVXQ.js +91 -0
  22. package/dist/chunk-DKWDKVXQ.js.map +1 -0
  23. package/dist/chunk-EMGQXBU3.js +3 -0
  24. package/dist/chunk-EMGQXBU3.js.map +1 -0
  25. package/dist/chunk-EYRCAUUP.js +45 -0
  26. package/dist/chunk-EYRCAUUP.js.map +1 -0
  27. package/dist/chunk-GW2QNTAD.js +327 -0
  28. package/dist/chunk-GW2QNTAD.js.map +1 -0
  29. package/dist/chunk-IGDDWGS4.js +557 -0
  30. package/dist/chunk-IGDDWGS4.js.map +1 -0
  31. package/dist/chunk-OQLU7FSR.js +1841 -0
  32. package/dist/chunk-OQLU7FSR.js.map +1 -0
  33. package/dist/chunk-PJ2LEKW6.js +229 -0
  34. package/dist/chunk-PJ2LEKW6.js.map +1 -0
  35. package/dist/chunk-PWHIDGKW.js +97 -0
  36. package/dist/chunk-PWHIDGKW.js.map +1 -0
  37. package/dist/chunk-RO25KNM5.js +36 -0
  38. package/dist/chunk-RO25KNM5.js.map +1 -0
  39. package/dist/chunk-V3VVDQIR.js +27 -0
  40. package/dist/chunk-V3VVDQIR.js.map +1 -0
  41. package/dist/chunk-VL3T36VM.js +386 -0
  42. package/dist/chunk-VL3T36VM.js.map +1 -0
  43. package/dist/chunk-XUGXLJZB.js +14 -0
  44. package/dist/chunk-XUGXLJZB.js.map +1 -0
  45. package/dist/chunk-Z3IW4ODZ.js +36 -0
  46. package/dist/chunk-Z3IW4ODZ.js.map +1 -0
  47. package/dist/index.d.ts +26 -0
  48. package/dist/index.js +21 -0
  49. package/dist/index.js.map +1 -0
  50. package/dist/layout/index.d.ts +54 -0
  51. package/dist/layout/index.js +102 -0
  52. package/dist/layout/index.js.map +1 -0
  53. package/dist/money/index.d.ts +26 -0
  54. package/dist/money/index.js +3 -0
  55. package/dist/money/index.js.map +1 -0
  56. package/dist/order/index.d.ts +34 -0
  57. package/dist/order/index.js +209 -0
  58. package/dist/order/index.js.map +1 -0
  59. package/dist/pagination/index.d.ts +87 -0
  60. package/dist/pagination/index.js +97 -0
  61. package/dist/pagination/index.js.map +1 -0
  62. package/dist/ports/index.d.ts +124 -0
  63. package/dist/ports/index.js +3 -0
  64. package/dist/ports/index.js.map +1 -0
  65. package/dist/products/index.d.ts +105 -0
  66. package/dist/products/index.js +7 -0
  67. package/dist/products/index.js.map +1 -0
  68. package/dist/promotions/index.d.ts +10 -0
  69. package/dist/promotions/index.js +7 -0
  70. package/dist/promotions/index.js.map +1 -0
  71. package/dist/schedule-Dd3zwHEb.d.ts +27 -0
  72. package/dist/seo/index.d.ts +91 -0
  73. package/dist/seo/index.js +289 -0
  74. package/dist/seo/index.js.map +1 -0
  75. package/dist/snippets/index.d.ts +17 -0
  76. package/dist/snippets/index.js +3 -0
  77. package/dist/snippets/index.js.map +1 -0
  78. package/dist/snippets-BC5bkSjm.d.ts +23 -0
  79. package/dist/stores/index.d.ts +196 -0
  80. package/dist/stores/index.js +8 -0
  81. package/dist/stores/index.js.map +1 -0
  82. package/dist/theme/index.d.ts +60 -0
  83. package/dist/theme/index.js +3 -0
  84. package/dist/theme/index.js.map +1 -0
  85. package/dist/utils/index.d.ts +66 -0
  86. package/dist/utils/index.js +4 -0
  87. package/dist/utils/index.js.map +1 -0
  88. package/dist/website-settings/index.d.ts +38 -0
  89. package/dist/website-settings/index.js +3 -0
  90. package/dist/website-settings/index.js.map +1 -0
  91. package/package.json +122 -41
  92. package/dist/esm/index.js +0 -3642
  93. package/dist/zenky.d.ts +0 -10377
@@ -0,0 +1,102 @@
1
+ // src/catalog/findCategory.ts
2
+ function findCategoryByShortId(tree, shortId) {
3
+ for (const cat of tree) {
4
+ if (cat.short_id === shortId) return cat;
5
+ if (cat.children && cat.children.length > 0) {
6
+ const found = findCategoryByShortId(cat.children, shortId);
7
+ if (found) return found;
8
+ }
9
+ }
10
+ return null;
11
+ }
12
+ function findRootAncestor(tree, category) {
13
+ if (!category.parent_id) {
14
+ return tree.find((c) => c.id === category.id) ?? category;
15
+ }
16
+ const parent = findCategoryById(tree, category.parent_id);
17
+ if (!parent) return null;
18
+ return findRootAncestor(tree, parent);
19
+ }
20
+ function findCategoryById(tree, id) {
21
+ for (const cat of tree) {
22
+ if (cat.id === id) return cat;
23
+ if (cat.children && cat.children.length > 0) {
24
+ const found = findCategoryById(cat.children, id);
25
+ if (found) return found;
26
+ }
27
+ }
28
+ return null;
29
+ }
30
+ function getCategoryPath(tree, target) {
31
+ const path = [];
32
+ let current = target;
33
+ while (current) {
34
+ path.unshift(current);
35
+ if (!current.parent_id) break;
36
+ current = findCategoryById(tree, current.parent_id);
37
+ }
38
+ return path;
39
+ }
40
+
41
+ // src/catalog/groupBySubcategory.ts
42
+ function collectDescendantIds(category) {
43
+ const ids = [category.id];
44
+ for (const child of category.children ?? []) ids.push(...collectDescendantIds(child));
45
+ return ids;
46
+ }
47
+ function productCategoryIds(product) {
48
+ const categories = product.categories;
49
+ return categories?.map((c) => c.id) ?? [];
50
+ }
51
+ function collectSubtreeProducts(category, products, order) {
52
+ const seen = /* @__PURE__ */ new Set();
53
+ const out = [];
54
+ const pushUnique = (list) => {
55
+ for (const p of list) {
56
+ if (!seen.has(p.id)) {
57
+ seen.add(p.id);
58
+ out.push(p);
59
+ }
60
+ }
61
+ };
62
+ pushUnique(
63
+ products.filter((p) => productCategoryIds(p).includes(category.id)).sort((a, b) => (order.get(a.id) ?? 0) - (order.get(b.id) ?? 0))
64
+ );
65
+ for (const child of category.children ?? []) {
66
+ pushUnique(collectSubtreeProducts(child, products, order));
67
+ }
68
+ return out;
69
+ }
70
+ function groupProductsBySubcategory(rootCategory, products) {
71
+ const order = new Map(products.map((p, i) => [p.id, i]));
72
+ const rootDirect = products.filter((p) => productCategoryIds(p).includes(rootCategory.id));
73
+ const groups = [];
74
+ for (const child of rootCategory.children ?? []) {
75
+ const groupProducts = collectSubtreeProducts(child, products, order);
76
+ if (groupProducts.length > 0) groups.push({ category: child, products: groupProducts });
77
+ }
78
+ return { rootDirect, groups };
79
+ }
80
+
81
+ // src/catalog/useCollections.ts
82
+ function useCollections(sdk) {
83
+ async function loadCollections(storeId, request) {
84
+ const response = await sdk.collections.getCollections(storeId, request);
85
+ return response.items;
86
+ }
87
+ return { loadCollections };
88
+ }
89
+
90
+ // src/catalog/routes.ts
91
+ var catalogRoutes = {
92
+ /** Корневая страница каталога: список категорий + коллекций */
93
+ index: () => "/catalog",
94
+ /** Страница конкретной категории */
95
+ category: (shortId, slug) => `/catalog/${shortId}/${slug || shortId}`,
96
+ /** Страница конкретной коллекции */
97
+ collection: (id) => `/collections/${id}`
98
+ };
99
+
100
+ export { catalogRoutes, collectDescendantIds, findCategoryById, findCategoryByShortId, findRootAncestor, getCategoryPath, groupProductsBySubcategory, useCollections };
101
+ //# sourceMappingURL=chunk-2OD7CLKD.js.map
102
+ //# sourceMappingURL=chunk-2OD7CLKD.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/catalog/findCategory.ts","../src/catalog/groupBySubcategory.ts","../src/catalog/useCollections.ts","../src/catalog/routes.ts"],"names":[],"mappings":"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","file":"chunk-2OD7CLKD.js","sourcesContent":["import type { NestedCategory } from '@zenky/storefront-api'\n\n/**\n * Рекурсивный поиск категории в дереве по `short_id`.\n *\n * `NestedCategory[]` — это дерево: корневые категории на верхнем уровне,\n * вложенные — внутри `children[]`. Простой `tree.find()` найдёт только\n * корневые. Для вложенных (например «Куртки» внутри «Мужская» внутри «Одежда»)\n * нужно рекурсивно обойти все ветки.\n *\n * Возвращает первое совпадение (DFS pre-order).\n */\nexport function findCategoryByShortId(\n tree: NestedCategory[],\n shortId: string\n): NestedCategory | null {\n for (const cat of tree) {\n if (cat.short_id === shortId) return cat\n if (cat.children && cat.children.length > 0) {\n const found = findCategoryByShortId(cat.children, shortId)\n if (found) return found\n }\n }\n return null\n}\n\n/**\n * Находит корневого предка категории по `parent_id`.\n *\n * Для вложенной «Куртки» (parent: «Мужская») вернёт «Одежда» (корень ветки).\n * Для корневой «Одежда» вернёт саму себя.\n *\n * Используется в `CategoryTabs`, чтобы подсветить корневой таб когда юзер\n * находится на вложенной категории.\n */\nexport function findRootAncestor(\n tree: NestedCategory[],\n category: NestedCategory\n): NestedCategory | null {\n if (!category.parent_id) {\n // Уже корневая — возвращаем её саму если она есть в tree на верхнем уровне\n return tree.find((c) => c.id === category.id) ?? category\n }\n\n // Ищем родителя\n const parent = findCategoryById(tree, category.parent_id)\n if (!parent) return null\n return findRootAncestor(tree, parent)\n}\n\n/** Рекурсивный поиск по `id` (не `short_id`). Используется в findRootAncestor. */\nexport function findCategoryById(tree: NestedCategory[], id: string): NestedCategory | null {\n for (const cat of tree) {\n if (cat.id === id) return cat\n if (cat.children && cat.children.length > 0) {\n const found = findCategoryById(cat.children, id)\n if (found) return found\n }\n }\n return null\n}\n\n/**\n * Возвращает путь от корневой категории до указанной (включительно).\n *\n * Пример для «Куртки» в ветке «Одежда → Мужская → Куртки»:\n * → [Одежда, Мужская, Куртки]\n *\n * Используется для построения хлебных крошек.\n */\nexport function getCategoryPath(\n tree: NestedCategory[],\n target: NestedCategory\n): NestedCategory[] {\n const path: NestedCategory[] = []\n let current: NestedCategory | null = target\n\n while (current) {\n path.unshift(current)\n if (!current.parent_id) break\n current = findCategoryById(tree, current.parent_id)\n }\n\n return path\n}\n","import type { NestedCategory, Product } from '@zenky/storefront-api'\n\nexport interface SubcategoryGroup {\n category: NestedCategory\n products: Product[]\n}\n\nexport interface GroupedProducts {\n rootDirect: Product[]\n groups: SubcategoryGroup[]\n}\n\nexport function collectDescendantIds(category: NestedCategory): string[] {\n const ids = [category.id]\n for (const child of category.children ?? []) ids.push(...collectDescendantIds(child))\n return ids\n}\n\nfunction productCategoryIds(product: Product): string[] {\n const categories = (product as unknown as { categories?: Array<{ id: string }> }).categories\n return categories?.map((c) => c.id) ?? []\n}\n\n/**\n * Товары поддерева в порядке дерева (DFS): сперва товары самой категории\n * (в порядке каталога), затем рекурсивно товары дочерних категорий.\n * Дубли (товар в нескольких категориях) отбрасываем, оставляя первое вхождение.\n */\nfunction collectSubtreeProducts(\n category: NestedCategory,\n products: Product[],\n order: Map<string, number>\n): Product[] {\n const seen = new Set<string>()\n const out: Product[] = []\n\n const pushUnique = (list: Product[]): void => {\n for (const p of list) {\n if (!seen.has(p.id)) {\n seen.add(p.id)\n out.push(p)\n }\n }\n }\n\n pushUnique(\n products\n .filter((p) => productCategoryIds(p).includes(category.id))\n .sort((a, b) => (order.get(a.id) ?? 0) - (order.get(b.id) ?? 0))\n )\n for (const child of category.children ?? []) {\n pushUnique(collectSubtreeProducts(child, products, order))\n }\n\n return out\n}\n\nexport function groupProductsBySubcategory(\n rootCategory: NestedCategory,\n products: Product[]\n): GroupedProducts {\n const order = new Map(products.map((p, i) => [p.id, i]))\n\n const rootDirect = products.filter((p) => productCategoryIds(p).includes(rootCategory.id))\n\n const groups: SubcategoryGroup[] = []\n for (const child of rootCategory.children ?? []) {\n const groupProducts = collectSubtreeProducts(child, products, order)\n if (groupProducts.length > 0) groups.push({ category: child, products: groupProducts })\n }\n\n return { rootDirect, groups }\n}\n","import type { CollectionsListRequest, ProductsCollection, ZenkyStorefront } from '@zenky/storefront-api'\n\n/**\n * Загрузка списка коллекций товаров магазина.\n * Pure composable — SDK передаётся параметром (DI), Nuxt не нужен.\n *\n * NOTE: одиночный `getCollection(id)` в storefront API не предусмотрен,\n * для получения товаров коллекции используется `GET /products?collection_id=...`.\n * Чтобы получить метаданные коллекции — ищем в списке через `loadCollections`.\n */\nexport function useCollections(sdk: ZenkyStorefront): {\n loadCollections: (\n storeId: string,\n request?: CollectionsListRequest\n ) => Promise<ProductsCollection[]>\n} {\n async function loadCollections(\n storeId: string,\n request?: CollectionsListRequest\n ): Promise<ProductsCollection[]> {\n const response = await sdk.collections.getCollections(storeId, request)\n return response.items\n }\n\n return { loadCollections }\n}\n","/** URL-хелперы каталога — единая точка правды для путей категорий/коллекций. */\nexport const catalogRoutes = {\n /** Корневая страница каталога: список категорий + коллекций */\n index: (): string => '/catalog',\n\n /** Страница конкретной категории */\n category: (shortId: string, slug: string | null): string =>\n `/catalog/${shortId}/${slug || shortId}`,\n\n /** Страница конкретной коллекции */\n collection: (id: string): string => `/collections/${id}`,\n} as const\n"]}
@@ -0,0 +1,111 @@
1
+ import { usePromotionsStore } from './chunk-BWUKMWHL.js';
2
+ import { useAuthStore, useStoreProfileStore } from './chunk-PWHIDGKW.js';
3
+ import { useCartStore } from './chunk-VL3T36VM.js';
4
+ import { useRuntime } from './chunk-V3VVDQIR.js';
5
+ import { ref } from 'vue';
6
+
7
+ var KEY_ORDER_ID = "zenky_order_id";
8
+ var KEY_ORDER_TOKEN = "zenky_order_token";
9
+ var WEEK_SECONDS = 60 * 60 * 24 * 7;
10
+ var ORDER_STATUS_PENDING = "pending";
11
+ function useCartCredentials() {
12
+ const { storage } = useRuntime();
13
+ const cartStore = useCartStore();
14
+ const authStore = useAuthStore();
15
+ const profileStore = useStoreProfileStore();
16
+ const orderIdRef = ref(storage.get(KEY_ORDER_ID));
17
+ const orderTokenRef = ref(storage.get(KEY_ORDER_TOKEN));
18
+ function getStoreId() {
19
+ if (!profileStore.store) {
20
+ throw new Error("Store is not initialized.");
21
+ }
22
+ return profileStore.store.id;
23
+ }
24
+ function getCityId() {
25
+ if (!profileStore.city) {
26
+ throw new Error("City is not initialized.");
27
+ }
28
+ return profileStore.city.id;
29
+ }
30
+ function getApiToken() {
31
+ return authStore.isAuthenticated ? authStore.token ?? void 0 : void 0;
32
+ }
33
+ function buildCredentials(orderId, orderToken) {
34
+ const credentials = {
35
+ id: orderId,
36
+ token: orderToken
37
+ };
38
+ const apiToken = getApiToken();
39
+ if (apiToken) {
40
+ credentials.api_token = apiToken;
41
+ }
42
+ return credentials;
43
+ }
44
+ function getCredentials() {
45
+ if (!cartStore.credentials) {
46
+ throw new Error("Cart has no credentials.");
47
+ }
48
+ return buildCredentials(cartStore.credentials.id, cartStore.credentials.token);
49
+ }
50
+ function persistCredentials(savedOrder) {
51
+ orderIdRef.value = savedOrder.id;
52
+ orderTokenRef.value = savedOrder.token;
53
+ storage.set(KEY_ORDER_ID, savedOrder.id, { maxAge: WEEK_SECONDS });
54
+ storage.set(KEY_ORDER_TOKEN, savedOrder.token, { maxAge: WEEK_SECONDS });
55
+ }
56
+ function clearPersistedCredentials() {
57
+ orderIdRef.value = null;
58
+ orderTokenRef.value = null;
59
+ storage.remove(KEY_ORDER_ID);
60
+ storage.remove(KEY_ORDER_TOKEN);
61
+ }
62
+ function isOrderActive(existingOrder) {
63
+ if (!existingOrder.status) return true;
64
+ return String(existingOrder.status.kind) === ORDER_STATUS_PENDING;
65
+ }
66
+ return {
67
+ orderIdCookie: orderIdRef,
68
+ orderTokenCookie: orderTokenRef,
69
+ getStoreId,
70
+ getCityId,
71
+ getApiToken,
72
+ buildCredentials,
73
+ getCredentials,
74
+ persistCredentials,
75
+ clearPersistedCredentials,
76
+ isOrderActive
77
+ };
78
+ }
79
+
80
+ // src/promotions/usePromotionsChecker.ts
81
+ var WS_RESPONSE_TIMEOUT_MS = 5e3;
82
+ function usePromotionsChecker() {
83
+ const { sdk, events } = useRuntime();
84
+ const cartStore = useCartStore();
85
+ const profileStore = useStoreProfileStore();
86
+ const promotionsStore = usePromotionsStore();
87
+ const credentials = useCartCredentials();
88
+ async function dispatchCheck() {
89
+ if (!cartStore.hasOrder || promotionsStore.checking) return;
90
+ const storeId = profileStore.store?.id;
91
+ if (!storeId) return;
92
+ promotionsStore.setChecking(true);
93
+ promotionsStore.clearRewards();
94
+ events?.get()?.promotionsCheckerStarted({ order: cartStore.order });
95
+ try {
96
+ const creds = credentials.getCredentials();
97
+ await sdk.orders.dispatchPromotionsChecker(storeId, creds);
98
+ setTimeout(() => {
99
+ if (promotionsStore.checking) promotionsStore.setChecking(false);
100
+ }, WS_RESPONSE_TIMEOUT_MS);
101
+ } catch (e) {
102
+ console.error("[promotions] check failed", e);
103
+ promotionsStore.setChecking(false);
104
+ }
105
+ }
106
+ return { dispatchCheck };
107
+ }
108
+
109
+ export { useCartCredentials, usePromotionsChecker };
110
+ //# sourceMappingURL=chunk-4MJKN7PL.js.map
111
+ //# sourceMappingURL=chunk-4MJKN7PL.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cart/useCartCredentials.ts","../src/promotions/usePromotionsChecker.ts"],"names":[],"mappings":"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","file":"chunk-4MJKN7PL.js","sourcesContent":["import { ref, type Ref } from 'vue'\n\nimport type { Order, OrderCredentials } from '@zenky/storefront-api'\n\nimport { useRuntime } from '../ports/runtime'\nimport { useAuthStore } from '../stores/auth'\nimport { useCartStore } from '../stores/cart'\nimport { useStoreProfileStore } from '../stores/store-profile'\n\nconst KEY_ORDER_ID = 'zenky_order_id'\nconst KEY_ORDER_TOKEN = 'zenky_order_token'\nconst WEEK_SECONDS = 60 * 60 * 24 * 7\n\n// app-энум OrderStatusKind.Pending = 'pending'.\nconst ORDER_STATUS_PENDING = 'pending'\n\nexport interface CartCredentials {\n orderIdCookie: Ref<string | null>\n orderTokenCookie: Ref<string | null>\n getStoreId: () => string\n getCityId: () => string\n getApiToken: () => string | undefined\n buildCredentials: (orderId: string, orderToken: string) => OrderCredentials\n getCredentials: () => OrderCredentials\n persistCredentials: (savedOrder: Order) => void\n clearPersistedCredentials: () => void\n isOrderActive: (order: Order) => boolean\n}\n\n/**\n * Управление credentials заказа. Персистенс order-id/token — через порт `storage`\n * (ядро не знает про useCookie). Рефы инициализируются из storage, persist пишет в оба.\n */\nexport function useCartCredentials(): CartCredentials {\n const { storage } = useRuntime()\n const cartStore = useCartStore()\n const authStore = useAuthStore()\n const profileStore = useStoreProfileStore()\n\n const orderIdRef = ref<string | null>(storage.get(KEY_ORDER_ID))\n const orderTokenRef = ref<string | null>(storage.get(KEY_ORDER_TOKEN))\n\n function getStoreId(): string {\n if (!profileStore.store) {\n throw new Error('Store is not initialized.')\n }\n\n return profileStore.store.id\n }\n\n function getCityId(): string {\n if (!profileStore.city) {\n throw new Error('City is not initialized.')\n }\n\n return profileStore.city.id\n }\n\n function getApiToken(): string | undefined {\n return authStore.isAuthenticated ? (authStore.token ?? undefined) : undefined\n }\n\n function buildCredentials(orderId: string, orderToken: string): OrderCredentials {\n const credentials: OrderCredentials = {\n id: orderId,\n token: orderToken,\n }\n\n const apiToken = getApiToken()\n\n if (apiToken) {\n credentials.api_token = apiToken\n }\n\n return credentials\n }\n\n function getCredentials(): OrderCredentials {\n if (!cartStore.credentials) {\n throw new Error('Cart has no credentials.')\n }\n\n return buildCredentials(cartStore.credentials.id, cartStore.credentials.token)\n }\n\n function persistCredentials(savedOrder: Order): void {\n orderIdRef.value = savedOrder.id\n orderTokenRef.value = savedOrder.token\n storage.set(KEY_ORDER_ID, savedOrder.id, { maxAge: WEEK_SECONDS })\n storage.set(KEY_ORDER_TOKEN, savedOrder.token, { maxAge: WEEK_SECONDS })\n }\n\n function clearPersistedCredentials(): void {\n orderIdRef.value = null\n orderTokenRef.value = null\n storage.remove(KEY_ORDER_ID)\n storage.remove(KEY_ORDER_TOKEN)\n }\n\n function isOrderActive(existingOrder: Order): boolean {\n if (!existingOrder.status) return true\n\n return String(existingOrder.status.kind) === ORDER_STATUS_PENDING\n }\n\n return {\n orderIdCookie: orderIdRef,\n orderTokenCookie: orderTokenRef,\n getStoreId,\n getCityId,\n getApiToken,\n buildCredentials,\n getCredentials,\n persistCredentials,\n clearPersistedCredentials,\n isOrderActive,\n }\n}\n","import { useRuntime } from '../ports/runtime'\nimport { useCartCredentials } from '../cart/useCartCredentials'\nimport { useCartStore } from '../stores/cart'\nimport { usePromotionsStore } from '../stores/promotions'\nimport { useStoreProfileStore } from '../stores/store-profile'\n\nexport interface PromotionsChecker {\n dispatchCheck: () => Promise<void>\n}\n\n/** Таймаут — если WS не ответил, сбросить флаг checking. */\nconst WS_RESPONSE_TIMEOUT_MS = 5000\n\n/**\n * Проверка промо-акций. Вызывает dispatchPromotionsChecker после изменения корзины.\n * Результат приходит через WebSocket (обрабатывается в useOrderChannel).\n */\nexport function usePromotionsChecker(): PromotionsChecker {\n const { sdk, events } = useRuntime()\n const cartStore = useCartStore()\n const profileStore = useStoreProfileStore()\n const promotionsStore = usePromotionsStore()\n const credentials = useCartCredentials()\n\n async function dispatchCheck(): Promise<void> {\n if (!cartStore.hasOrder || promotionsStore.checking) return\n\n const storeId = profileStore.store?.id\n if (!storeId) return\n\n promotionsStore.setChecking(true)\n promotionsStore.clearRewards() // Очистить старые — WS заново наполнит актуальные\n events?.get()?.promotionsCheckerStarted({ order: cartStore.order })\n\n try {\n const creds = credentials.getCredentials()\n await sdk.orders.dispatchPromotionsChecker(storeId, creds)\n\n setTimeout(() => {\n if (promotionsStore.checking) promotionsStore.setChecking(false)\n }, WS_RESPONSE_TIMEOUT_MS)\n } catch (e: unknown) {\n console.error('[promotions] check failed', e)\n promotionsStore.setChecking(false)\n }\n }\n\n return { dispatchCheck }\n}\n"]}
@@ -0,0 +1,15 @@
1
+ // src/ports/runtimeState.ts
2
+ function createRuntimeState(create) {
3
+ const states = /* @__PURE__ */ new WeakMap();
4
+ return (runtime) => {
5
+ const existing = states.get(runtime);
6
+ if (existing !== void 0) return existing;
7
+ const state = create();
8
+ states.set(runtime, state);
9
+ return state;
10
+ };
11
+ }
12
+
13
+ export { createRuntimeState };
14
+ //# sourceMappingURL=chunk-5QCUH3FF.js.map
15
+ //# sourceMappingURL=chunk-5QCUH3FF.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/ports/runtimeState.ts"],"names":[],"mappings":";AAEO,SAAS,mBAAsB,MAAA,EAAoD;AACtF,EAAA,MAAM,MAAA,uBAAa,OAAA,EAA8B;AAEjD,EAAA,OAAO,CAAC,OAAA,KAAkC;AACtC,IAAA,MAAM,QAAA,GAAW,MAAA,CAAO,GAAA,CAAI,OAAO,CAAA;AACnC,IAAA,IAAI,QAAA,KAAa,QAAW,OAAO,QAAA;AAEnC,IAAA,MAAM,QAAQ,MAAA,EAAO;AACrB,IAAA,MAAA,CAAO,GAAA,CAAI,SAAS,KAAK,CAAA;AACzB,IAAA,OAAO,KAAA;AAAA,EACX,CAAA;AACJ","file":"chunk-5QCUH3FF.js","sourcesContent":["import type { StorefrontRuntime } from './index'\n\nexport function createRuntimeState<T>(create: () => T): (runtime: StorefrontRuntime) => T {\n const states = new WeakMap<StorefrontRuntime, T>()\n\n return (runtime: StorefrontRuntime): T => {\n const existing = states.get(runtime)\n if (existing !== undefined) return existing\n\n const state = create()\n states.set(runtime, state)\n return state\n }\n}\n"]}
@@ -0,0 +1,41 @@
1
+ // src/ports/index.ts
2
+ var ok = (value) => ({ ok: true, value });
3
+ var err = (error) => ({ ok: false, error });
4
+ var NOOP_NOTIFIER = {
5
+ error: () => {
6
+ },
7
+ success: () => {
8
+ },
9
+ warning: () => {
10
+ },
11
+ message: () => {
12
+ }
13
+ };
14
+ var NOOP_ACTIVITY_REPORTER = {
15
+ begin: () => {
16
+ },
17
+ end: () => {
18
+ }
19
+ };
20
+ var NOOP_RUM = {
21
+ mark: () => {
22
+ },
23
+ measure: () => {
24
+ }
25
+ };
26
+ function createMemoryStorage() {
27
+ const map = /* @__PURE__ */ new Map();
28
+ return {
29
+ get: (key) => map.get(key) ?? null,
30
+ set: (key, value) => {
31
+ map.set(key, value);
32
+ },
33
+ remove: (key) => {
34
+ map.delete(key);
35
+ }
36
+ };
37
+ }
38
+
39
+ export { NOOP_ACTIVITY_REPORTER, NOOP_NOTIFIER, NOOP_RUM, createMemoryStorage, err, ok };
40
+ //# sourceMappingURL=chunk-AL5W5JSD.js.map
41
+ //# sourceMappingURL=chunk-AL5W5JSD.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/ports/index.ts"],"names":[],"mappings":";AA+GO,IAAM,KAAK,CAAI,KAAA,MAAgC,EAAE,EAAA,EAAI,MAAM,KAAA,EAAM;AACjE,IAAM,MAAM,CAAI,KAAA,MAAgC,EAAE,EAAA,EAAI,OAAO,KAAA,EAAM;AAwBnE,IAAM,aAAA,GAA0B;AAAA,EACnC,OAAO,MAAM;AAAA,EAAC,CAAA;AAAA,EACd,SAAS,MAAM;AAAA,EAAC,CAAA;AAAA,EAChB,SAAS,MAAM;AAAA,EAAC,CAAA;AAAA,EAChB,SAAS,MAAM;AAAA,EAAC;AACpB;AAEO,IAAM,sBAAA,GAA2C;AAAA,EACpD,OAAO,MAAM;AAAA,EAAC,CAAA;AAAA,EACd,KAAK,MAAM;AAAA,EAAC;AAChB;AAEO,IAAM,QAAA,GAAwB;AAAA,EACjC,MAAM,MAAM;AAAA,EAAC,CAAA;AAAA,EACb,SAAS,MAAM;AAAA,EAAC;AACpB;AAGO,SAAS,mBAAA,GAAsC;AAClD,EAAA,MAAM,GAAA,uBAAU,GAAA,EAAoB;AACpC,EAAA,OAAO;AAAA,IACH,KAAK,CAAC,GAAA,KAAQ,GAAA,CAAI,GAAA,CAAI,GAAG,CAAA,IAAK,IAAA;AAAA,IAC9B,GAAA,EAAK,CAAC,GAAA,EAAK,KAAA,KAAU;AACjB,MAAA,GAAA,CAAI,GAAA,CAAI,KAAK,KAAK,CAAA;AAAA,IACtB,CAAA;AAAA,IACA,MAAA,EAAQ,CAAC,GAAA,KAAQ;AACb,MAAA,GAAA,CAAI,OAAO,GAAG,CAAA;AAAA,IAClB;AAAA,GACJ;AACJ","file":"chunk-AL5W5JSD.js","sourcesContent":["// Ports — интерфейсы того, что ядру нужно от рантайма потребителя.\n// Каждый фронт (Nuxt / QR-меню / шаблон) приносит свои адаптеры.\n// Ядро НЕ знает про useCookie, useRuntimeConfig, vue-router и т.п.\n\nimport type { Ref } from 'vue'\n\nimport type { ZenkyIntegrationsSDK } from '@zenky/integrations-sdk'\nimport type { RemoteCatalog, ZenkyStorefront } from '@zenky/storefront-api'\n\nexport interface StorageOptions {\n /** Время жизни в секундах. */\n maxAge?: number\n sameSite?: 'strict' | 'lax' | 'none'\n secure?: boolean\n path?: string\n}\n\n/** Персистентное key-value хранилище (куки / localStorage / память). */\nexport interface StorageAdapter {\n get(key: string): string | null\n set(key: string, value: string, options?: StorageOptions): void\n remove(key: string, options?: StorageOptions): void\n}\n\n/** Конфиг рантайма, который ядро не должно знать откуда берётся. */\nexport interface StorefrontConfig {\n baseUrl: string\n baseAuthUrl: string\n /** Опциональный sales channel для креативов/каналов. */\n salesChannelId?: string\n}\n\n/** Уведомления пользователю (тосты). Реализация — на стороне потребителя. */\nexport interface Notifier {\n error(message: string): void\n success(message: string): void\n warning(message: string): void\n message(title: string, options?: { description?: string }): void\n}\n\n/** Репортер активности (busy-индикаторы, длительные операции). */\nexport interface ActivityReporter {\n begin(kind: string): void\n end(kind: string): void\n}\n\n/** Realtime-канал: подписка на события (WebSocket). Ядро не знает про pusher/транспорт. */\nexport interface RealtimeChannel {\n bind(event: string, handler: (data?: unknown) => void): void\n}\n\nexport interface RealtimeAdapter {\n subscribe(channelName: string): RealtimeChannel | null\n unsubscribe(channelName: string): void\n isReady: Ref<boolean>\n}\n\nexport interface RealtimePort {\n get(): RealtimeAdapter | null\n}\n\nexport interface EventsPort {\n get(): ZenkyIntegrationsSDK | null\n}\n\n/** Перезагрузка каталога при смене склада. Транспорт (fetch) — на стороне потребителя. */\nexport interface CatalogReloader {\n reload(storeId: string, remoteCatalog: RemoteCatalog): Promise<void>\n}\n\n/** RUM-репортер производительности (performance-marks). Реализация — у потребителя. */\nexport interface RumReporter {\n mark(name: string): void\n measure(metric: string, startMark: string): void\n}\n\n/** Идентификаторы commerce-события (session/anonymous) — попадают в checkout-request. */\nexport interface CommerceEventIdentity {\n session_id?: string\n anonymous_id?: string\n}\n\nexport interface CommerceAddedToCartPayload {\n productId: string\n productVariantId: string\n quantity?: number\n unitPrice?: number\n totalPrice?: number\n}\n\nexport interface CommerceRemovedFromCartPayload {\n productId: string\n productVariantId: string\n quantity?: number\n}\n\n/** Commerce-event трекинг (#29). Реализация/очередь/транспорт — у потребителя. */\nexport interface CommerceEventsPort {\n trackAddedToCart(orderId: string, payload: CommerceAddedToCartPayload): void\n trackRemovedFromCart(orderId: string, payload: CommerceRemovedFromCartPayload): void\n getIdentity(): CommerceEventIdentity\n}\n\n/**\n * Railway-style результат: компилятор не даст прочитать `.value`,\n * пока потребитель не проверил `.ok`. Замена throw на публичной границе.\n */\nexport type Result<T = void, E = unknown> =\n | { ok: true; value: T }\n | { ok: false; error: E }\n\nexport const ok = <T>(value: T): Result<T, never> => ({ ok: true, value })\nexport const err = <E>(error: E): Result<never, E> => ({ ok: false, error })\n\n/** Полный рантайм, который потребитель собирает и отдаёт в createStorefront(). */\nexport interface StorefrontRuntime {\n /** SDK-клиент Zenky. Потребитель создаёт один инстанс и отдаёт сюда. */\n sdk: ZenkyStorefront\n storage: StorageAdapter\n config: StorefrontConfig\n notifier: Notifier\n activity: ActivityReporter\n /** Ленивый провайдер analytics-интеграции, которая может появиться после setup. */\n events?: EventsPort | null\n /** Ленивый провайдер realtime-транспорта (WebSocket). */\n realtime?: RealtimePort | null\n /** Перезагрузка каталога при смене склада. Нет — reload пропускается. */\n catalogReloader?: CatalogReloader | null\n /** RUM-метрики. Нет — инструментирование пропускается. */\n rum?: RumReporter | null\n /** Commerce-event трекинг (#29). Нет — трекинг пропускается. */\n commerceEvents?: CommerceEventsPort | null\n}\n\n// --- NOOP / in-memory заглушки (SSR, тесты, дефолты) ---\n\nexport const NOOP_NOTIFIER: Notifier = {\n error: () => {},\n success: () => {},\n warning: () => {},\n message: () => {},\n}\n\nexport const NOOP_ACTIVITY_REPORTER: ActivityReporter = {\n begin: () => {},\n end: () => {},\n}\n\nexport const NOOP_RUM: RumReporter = {\n mark: () => {},\n measure: () => {},\n}\n\n/** In-memory storage — для SSR/тестов, без побочных эффектов на реальные куки. */\nexport function createMemoryStorage(): StorageAdapter {\n const map = new Map<string, string>()\n return {\n get: (key) => map.get(key) ?? null,\n set: (key, value) => {\n map.set(key, value)\n },\n remove: (key) => {\n map.delete(key)\n },\n }\n}\n"]}
@@ -0,0 +1,38 @@
1
+ import { useStoreProfileStore } from './chunk-PWHIDGKW.js';
2
+ import { defineStore, storeToRefs } from 'pinia';
3
+ import { ref, computed } from 'vue';
4
+
5
+ var useProductsStore = defineStore("zenky-products", () => {
6
+ const products = ref([]);
7
+ function setProducts(list) {
8
+ products.value = list;
9
+ }
10
+ const hasProducts = computed(() => products.value.length > 0);
11
+ return { products, setProducts, hasProducts };
12
+ });
13
+ function useStore() {
14
+ const profileStore = useStoreProfileStore();
15
+ return {
16
+ store: computed(() => profileStore.store),
17
+ city: computed(() => profileStore.city),
18
+ stock: computed(() => profileStore.stock)
19
+ };
20
+ }
21
+ function useStorefrontProfile() {
22
+ const profileStore = useStoreProfileStore();
23
+ const { store, city, stock, categories } = storeToRefs(profileStore);
24
+ const isReady = computed(() => profileStore.isReady);
25
+ const hasStock = computed(() => profileStore.hasStock);
26
+ return {
27
+ store,
28
+ city,
29
+ stock,
30
+ categories,
31
+ isReady,
32
+ hasStock
33
+ };
34
+ }
35
+
36
+ export { useProductsStore, useStore, useStorefrontProfile };
37
+ //# sourceMappingURL=chunk-BIGTKVC6.js.map
38
+ //# sourceMappingURL=chunk-BIGTKVC6.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/stores/products.ts","../src/stores/useStore.ts","../src/stores/useStorefrontProfile.ts"],"names":["computed"],"mappings":";;;;AAaO,IAAM,gBAAA,GAAmB,WAAA,CAAY,gBAAA,EAAkB,MAAqB;AAC/E,EAAA,MAAM,QAAA,GAAW,GAAA,CAAe,EAAE,CAAA;AAElC,EAAA,SAAS,YAAY,IAAA,EAAuB;AACxC,IAAA,QAAA,CAAS,KAAA,GAAQ,IAAA;AAAA,EACrB;AAEA,EAAA,MAAM,cAAc,QAAA,CAAS,MAAM,QAAA,CAAS,KAAA,CAAM,SAAS,CAAC,CAAA;AAE5D,EAAA,OAAO,EAAE,QAAA,EAAU,WAAA,EAAa,WAAA,EAAY;AAChD,CAAC;AChBM,SAAS,QAAA,GAId;AACE,EAAA,MAAM,eAAe,oBAAA,EAAqB;AAE1C,EAAA,OAAO;AAAA,IACH,KAAA,EAAOA,QAAAA,CAAS,MAAM,YAAA,CAAa,KAAM,CAAA;AAAA,IACzC,IAAA,EAAMA,QAAAA,CAAS,MAAM,YAAA,CAAa,IAAK,CAAA;AAAA,IACvC,KAAA,EAAOA,QAAAA,CAAS,MAAM,YAAA,CAAa,KAAK;AAAA,GAC5C;AACJ;ACbO,SAAS,oBAAA,GAAuB;AACnC,EAAA,MAAM,eAAe,oBAAA,EAAqB;AAC1C,EAAA,MAAM,EAAE,KAAA,EAAO,IAAA,EAAM,OAAO,UAAA,EAAW,GAAI,YAAY,YAAY,CAAA;AAEnE,EAAA,MAAM,OAAA,GAAUA,QAAAA,CAAS,MAAM,YAAA,CAAa,OAAO,CAAA;AACnD,EAAA,MAAM,QAAA,GAAWA,QAAAA,CAAS,MAAM,YAAA,CAAa,QAAQ,CAAA;AAErD,EAAA,OAAO;AAAA,IACH,KAAA;AAAA,IACA,IAAA;AAAA,IACA,KAAA;AAAA,IACA,UAAA;AAAA,IACA,OAAA;AAAA,IACA;AAAA,GACJ;AACJ","file":"chunk-BIGTKVC6.js","sourcesContent":["import { defineStore } from 'pinia'\nimport { computed, ref, type ComputedRef, type Ref } from 'vue'\n\nimport type { Product } from '@zenky/storefront-api'\n\n// Явный тип возврата — сохраняет `Product` именованной ссылкой в .d.ts вместо разворачивания.\nexport interface ProductsStore {\n products: Ref<Product[]>\n setProducts: (list: Product[]) => void\n hasProducts: ComputedRef<boolean>\n}\n\n// Товары держим в сторе, но наполняем из каталога (не из отдельного API-вызова).\nexport const useProductsStore = defineStore('zenky-products', (): ProductsStore => {\n const products = ref<Product[]>([])\n\n function setProducts(list: Product[]): void {\n products.value = list\n }\n\n const hasProducts = computed(() => products.value.length > 0)\n\n return { products, setProducts, hasProducts }\n})\n","import { computed, type ComputedRef } from 'vue'\n\nimport type { City, Stock, Store } from '@zenky/storefront-api'\n\nimport { useStoreProfileStore } from './store-profile'\n\n/** Аксессор загруженного стора/города/склада (store/city — гарантированно загружены). */\nexport function useStore(): {\n store: ComputedRef<Store>\n city: ComputedRef<City>\n stock: ComputedRef<Stock | null>\n} {\n const profileStore = useStoreProfileStore()\n\n return {\n store: computed(() => profileStore.store!),\n city: computed(() => profileStore.city!),\n stock: computed(() => profileStore.stock),\n }\n}\n","import { storeToRefs } from 'pinia'\nimport { computed } from 'vue'\n\nimport { useStoreProfileStore } from './store-profile'\n\n/** Реактивный доступ к профилю (store/city/stock/categories + флаги готовности). */\nexport function useStorefrontProfile() {\n const profileStore = useStoreProfileStore()\n const { store, city, stock, categories } = storeToRefs(profileStore)\n\n const isReady = computed(() => profileStore.isReady)\n const hasStock = computed(() => profileStore.hasStock)\n\n return {\n store,\n city,\n stock,\n categories,\n isReady,\n hasStock,\n }\n}\n"]}
@@ -0,0 +1,45 @@
1
+ import { defineStore } from 'pinia';
2
+ import { ref, computed } from 'vue';
3
+
4
+ // src/stores/promotions.ts
5
+ var usePromotionsStore = defineStore("zenky-promotions", () => {
6
+ const rewards = ref([]);
7
+ const checking = ref(false);
8
+ function addOrUpdateReward(selection) {
9
+ const index = rewards.value.findIndex((r) => r.promotionId === selection.promotionId);
10
+ if (index >= 0) {
11
+ rewards.value[index] = selection;
12
+ } else {
13
+ rewards.value.push(selection);
14
+ }
15
+ }
16
+ function removeRewardByPromotion(promotionId) {
17
+ rewards.value = rewards.value.filter((r) => r.promotionId !== promotionId);
18
+ }
19
+ function clearRewards() {
20
+ rewards.value = [];
21
+ }
22
+ function setChecking(value) {
23
+ checking.value = value;
24
+ }
25
+ const hasRewards = computed(() => rewards.value.length > 0);
26
+ const totalEmptySlots = computed(() => rewards.value.reduce((sum, r) => sum + r.limit, 0));
27
+ const rewardSelection = computed(() => rewards.value[0] ?? null);
28
+ const needsSelection = computed(() => rewards.value.some((r) => r.context !== null));
29
+ return {
30
+ rewards,
31
+ checking,
32
+ addOrUpdateReward,
33
+ removeRewardByPromotion,
34
+ clearRewards,
35
+ setChecking,
36
+ hasRewards,
37
+ totalEmptySlots,
38
+ rewardSelection,
39
+ needsSelection
40
+ };
41
+ });
42
+
43
+ export { usePromotionsStore };
44
+ //# sourceMappingURL=chunk-BWUKMWHL.js.map
45
+ //# sourceMappingURL=chunk-BWUKMWHL.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/stores/promotions.ts"],"names":[],"mappings":";;;;AA6BO,IAAM,kBAAA,GAAqB,WAAA,CAAY,kBAAA,EAAoB,MAAuB;AAErF,EAAA,MAAM,OAAA,GAAU,GAAA,CAAuB,EAAE,CAAA;AAEzC,EAAA,MAAM,QAAA,GAAW,IAAI,KAAK,CAAA;AAE1B,EAAA,SAAS,kBAAkB,SAAA,EAAkC;AACzD,IAAA,MAAM,KAAA,GAAQ,QAAQ,KAAA,CAAM,SAAA,CAAU,CAAC,CAAA,KAAM,CAAA,CAAE,WAAA,KAAgB,SAAA,CAAU,WAAW,CAAA;AACpF,IAAA,IAAI,SAAS,CAAA,EAAG;AACZ,MAAA,OAAA,CAAQ,KAAA,CAAM,KAAK,CAAA,GAAI,SAAA;AAAA,IAC3B,CAAA,MAAO;AACH,MAAA,OAAA,CAAQ,KAAA,CAAM,KAAK,SAAS,CAAA;AAAA,IAChC;AAAA,EACJ;AAEA,EAAA,SAAS,wBAAwB,WAAA,EAA2B;AACxD,IAAA,OAAA,CAAQ,KAAA,GAAQ,QAAQ,KAAA,CAAM,MAAA,CAAO,CAAC,CAAA,KAAM,CAAA,CAAE,gBAAgB,WAAW,CAAA;AAAA,EAC7E;AAEA,EAAA,SAAS,YAAA,GAAqB;AAC1B,IAAA,OAAA,CAAQ,QAAQ,EAAC;AAAA,EACrB;AAEA,EAAA,SAAS,YAAY,KAAA,EAAsB;AACvC,IAAA,QAAA,CAAS,KAAA,GAAQ,KAAA;AAAA,EACrB;AAEA,EAAA,MAAM,aAAa,QAAA,CAAS,MAAM,OAAA,CAAQ,KAAA,CAAM,SAAS,CAAC,CAAA;AAE1D,EAAA,MAAM,eAAA,GAAkB,QAAA,CAAS,MAAM,OAAA,CAAQ,KAAA,CAAM,MAAA,CAAO,CAAC,GAAA,EAAK,CAAA,KAAM,GAAA,GAAM,CAAA,CAAE,KAAA,EAAO,CAAC,CAAC,CAAA;AAEzF,EAAA,MAAM,kBAAkB,QAAA,CAAiC,MAAM,QAAQ,KAAA,CAAM,CAAC,KAAK,IAAI,CAAA;AACvF,EAAA,MAAM,cAAA,GAAiB,QAAA,CAAS,MAAM,OAAA,CAAQ,KAAA,CAAM,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,OAAA,KAAY,IAAI,CAAC,CAAA;AAEnF,EAAA,OAAO;AAAA,IACH,OAAA;AAAA,IACA,QAAA;AAAA,IACA,iBAAA;AAAA,IACA,uBAAA;AAAA,IACA,YAAA;AAAA,IACA,WAAA;AAAA,IACA,UAAA;AAAA,IACA,eAAA;AAAA,IACA,eAAA;AAAA,IACA;AAAA,GACJ;AACJ,CAAC","file":"chunk-BWUKMWHL.js","sourcesContent":["import { defineStore } from 'pinia'\nimport { computed, ref, type ComputedRef, type Ref } from 'vue'\n\nexport interface RewardSelectionContext {\n type: string\n id: string\n}\n\nexport interface RewardSelection {\n offerId: string\n promotionId: string\n rewardId: string\n context: RewardSelectionContext | null\n limit: number\n}\n\nexport interface PromotionsStore {\n rewards: Ref<RewardSelection[]>\n checking: Ref<boolean>\n addOrUpdateReward: (selection: RewardSelection) => void\n removeRewardByPromotion: (promotionId: string) => void\n clearRewards: () => void\n setChecking: (value: boolean) => void\n hasRewards: ComputedRef<boolean>\n totalEmptySlots: ComputedRef<number>\n rewardSelection: ComputedRef<RewardSelection | null>\n needsSelection: ComputedRef<boolean>\n}\n\nexport const usePromotionsStore = defineStore('zenky-promotions', (): PromotionsStore => {\n /** Доступные награды (может быть несколько акций одновременно) */\n const rewards = ref<RewardSelection[]>([])\n /** Идёт проверка промо-акций */\n const checking = ref(false)\n\n function addOrUpdateReward(selection: RewardSelection): void {\n const index = rewards.value.findIndex((r) => r.promotionId === selection.promotionId)\n if (index >= 0) {\n rewards.value[index] = selection\n } else {\n rewards.value.push(selection)\n }\n }\n\n function removeRewardByPromotion(promotionId: string): void {\n rewards.value = rewards.value.filter((r) => r.promotionId !== promotionId)\n }\n\n function clearRewards(): void {\n rewards.value = []\n }\n\n function setChecking(value: boolean): void {\n checking.value = value\n }\n\n const hasRewards = computed(() => rewards.value.length > 0)\n /** Общее количество пустых слотов по всем акциям */\n const totalEmptySlots = computed(() => rewards.value.reduce((sum, r) => sum + r.limit, 0))\n /** Для обратной совместимости — первая награда */\n const rewardSelection = computed<RewardSelection | null>(() => rewards.value[0] ?? null)\n const needsSelection = computed(() => rewards.value.some((r) => r.context !== null))\n\n return {\n rewards,\n checking,\n addOrUpdateReward,\n removeRewardByPromotion,\n clearRewards,\n setChecking,\n hasRewards,\n totalEmptySlots,\n rewardSelection,\n needsSelection,\n }\n})\n"]}
@@ -0,0 +1,91 @@
1
+ // src/snippets/snippets.ts
2
+ function normalizeSnippets(groups) {
3
+ if (!Array.isArray(groups)) return [];
4
+ return groups.flatMap(
5
+ (group, groupIndex) => (group.snippets ?? []).filter((item) => !!item.content?.trim()).map((item, itemIndex) => {
6
+ const snippet = {
7
+ id: `${group.region}-${groupIndex}-${itemIndex}`,
8
+ position: group.region,
9
+ language: item.language,
10
+ code: item.content,
11
+ enabled: item.enabled ?? true
12
+ };
13
+ if (item.name !== void 0) snippet.name = item.name;
14
+ return snippet;
15
+ })
16
+ );
17
+ }
18
+
19
+ // src/snippets/buildSnippetHead.ts
20
+ function wrapSnippetJs(snippet) {
21
+ const id = JSON.stringify(snippet.id);
22
+ return `;(() => {
23
+ try {
24
+ ${snippet.code}
25
+ } catch (error) {
26
+ console.error('[snippet:error]', { id: ${id}, error });
27
+ window.dispatchEvent(new CustomEvent('zenky:snippet-error', {
28
+ detail: { id: ${id}, message: error instanceof Error ? error.message : String(error) },
29
+ }));
30
+ }
31
+ })();`;
32
+ }
33
+ function parseTagAttributes(raw) {
34
+ const attrs = {};
35
+ const re = /([a-zA-Z][\w:-]*)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+)))?/g;
36
+ let match;
37
+ while ((match = re.exec(raw)) !== null) {
38
+ const name = match[1];
39
+ if (name) attrs[name] = match[2] ?? match[3] ?? match[4] ?? true;
40
+ }
41
+ return attrs;
42
+ }
43
+ function parseHeadHtml(html) {
44
+ const result = { meta: [], link: [], script: [] };
45
+ let m;
46
+ const metaRe = /<meta\b([^>]*)>/gi;
47
+ while ((m = metaRe.exec(html)) !== null) result.meta.push(parseTagAttributes(m[1] ?? ""));
48
+ const linkRe = /<link\b([^>]*)>/gi;
49
+ while ((m = linkRe.exec(html)) !== null) result.link.push(parseTagAttributes(m[1] ?? ""));
50
+ const scriptRe = /<script\b([^>]*)>([\s\S]*?)<\/script>/gi;
51
+ while ((m = scriptRe.exec(html)) !== null) {
52
+ const attrs = parseTagAttributes(m[1] ?? "");
53
+ const inner = (m[2] ?? "").trim();
54
+ result.script.push(inner ? { ...attrs, innerHTML: inner } : attrs);
55
+ }
56
+ return result;
57
+ }
58
+ function buildSnippetHead(snippets) {
59
+ const enabled = snippets.filter((s) => s.enabled);
60
+ const jsSnippets = enabled.filter((s) => s.language === "javascript");
61
+ const cssSnippets = enabled.filter((s) => s.language === "css");
62
+ const headHtmlSnippets = enabled.filter(
63
+ (s) => s.language === "html" && s.position.startsWith("head")
64
+ );
65
+ const parsedHead = headHtmlSnippets.map((s) => parseHeadHtml(s.code));
66
+ const headMeta = parsedHead.flatMap((p) => p.meta);
67
+ const headLink = parsedHead.flatMap((p) => p.link);
68
+ const headScripts = parsedHead.flatMap((p) => p.script);
69
+ return {
70
+ meta: headMeta,
71
+ link: headLink,
72
+ script: [
73
+ ...jsSnippets.map((snippet) => ({
74
+ key: `snippet-js-${snippet.id}`,
75
+ innerHTML: wrapSnippetJs(snippet),
76
+ body: snippet.position.startsWith("body"),
77
+ "data-snippet-id": snippet.id
78
+ })),
79
+ ...headScripts
80
+ ],
81
+ style: cssSnippets.map((snippet) => ({
82
+ key: `snippet-css-${snippet.id}`,
83
+ innerHTML: snippet.code,
84
+ "data-snippet-id": snippet.id
85
+ }))
86
+ };
87
+ }
88
+
89
+ export { buildSnippetHead, normalizeSnippets };
90
+ //# sourceMappingURL=chunk-DKWDKVXQ.js.map
91
+ //# sourceMappingURL=chunk-DKWDKVXQ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/snippets/snippets.ts","../src/snippets/buildSnippetHead.ts"],"names":[],"mappings":";AAwBO,SAAS,kBAAkB,MAAA,EAAsD;AACpF,EAAA,IAAI,CAAC,KAAA,CAAM,OAAA,CAAQ,MAAM,CAAA,SAAU,EAAC;AAEpC,EAAA,OAAO,MAAA,CAAO,OAAA;AAAA,IAAQ,CAAC,OAAO,UAAA,KAAA,CACzB,KAAA,CAAM,YAAY,EAAC,EACf,OAAO,CAAC,IAAA,KAAS,CAAC,CAAC,IAAA,CAAK,SAAS,IAAA,EAAM,EACvC,GAAA,CAAI,CAAC,MAAM,SAAA,KAAuB;AAC/B,MAAA,MAAM,OAAA,GAAmB;AAAA,QACrB,IAAI,CAAA,EAAG,KAAA,CAAM,MAAM,CAAA,CAAA,EAAI,UAAU,IAAI,SAAS,CAAA,CAAA;AAAA,QAC9C,UAAU,KAAA,CAAM,MAAA;AAAA,QAChB,UAAU,IAAA,CAAK,QAAA;AAAA,QACf,MAAM,IAAA,CAAK,OAAA;AAAA,QACX,OAAA,EAAS,KAAK,OAAA,IAAW;AAAA,OAC7B;AAEA,MAAA,IAAI,IAAA,CAAK,IAAA,KAAS,MAAA,EAAW,OAAA,CAAQ,OAAO,IAAA,CAAK,IAAA;AACjD,MAAA,OAAO,OAAA;AAAA,IACX,CAAC;AAAA,GACT;AACJ;;;AChCA,SAAS,cAAc,OAAA,EAA0B;AAC7C,EAAA,MAAM,EAAA,GAAK,IAAA,CAAK,SAAA,CAAU,OAAA,CAAQ,EAAE,CAAA;AACpC,EAAA,OAAO,CAAA;AAAA;AAAA,QAAA,EAED,QAAQ,IAAI;AAAA;AAAA,+CAAA,EAE2B,EAAE,CAAA;AAAA;AAAA,0BAAA,EAEvB,EAAE,CAAA;AAAA;AAAA;AAAA,KAAA,CAAA;AAI9B;AAUA,SAAS,mBAAmB,GAAA,EAAwB;AAChD,EAAA,MAAM,QAAmB,EAAC;AAC1B,EAAA,MAAM,EAAA,GAAK,sEAAA;AACX,EAAA,IAAI,KAAA;AACJ,EAAA,OAAA,CAAQ,KAAA,GAAQ,EAAA,CAAG,IAAA,CAAK,GAAG,OAAO,IAAA,EAAM;AACpC,IAAA,MAAM,IAAA,GAAO,MAAM,CAAC,CAAA;AACpB,IAAA,IAAI,IAAA,EAAM,KAAA,CAAM,IAAI,CAAA,GAAI,KAAA,CAAM,CAAC,CAAA,IAAK,KAAA,CAAM,CAAC,CAAA,IAAK,KAAA,CAAM,CAAC,CAAA,IAAK,IAAA;AAAA,EAChE;AACA,EAAA,OAAO,KAAA;AACX;AAEA,SAAS,cAAc,IAAA,EAA8B;AACjD,EAAA,MAAM,MAAA,GAAyB,EAAE,IAAA,EAAM,EAAC,EAAG,MAAM,EAAC,EAAG,MAAA,EAAQ,EAAC,EAAE;AAChE,EAAA,IAAI,CAAA;AAEJ,EAAA,MAAM,MAAA,GAAS,mBAAA;AACf,EAAA,OAAA,CAAQ,CAAA,GAAI,MAAA,CAAO,IAAA,CAAK,IAAI,OAAO,IAAA,EAAM,MAAA,CAAO,IAAA,CAAK,IAAA,CAAK,kBAAA,CAAmB,CAAA,CAAE,CAAC,CAAA,IAAK,EAAE,CAAC,CAAA;AAExF,EAAA,MAAM,MAAA,GAAS,mBAAA;AACf,EAAA,OAAA,CAAQ,CAAA,GAAI,MAAA,CAAO,IAAA,CAAK,IAAI,OAAO,IAAA,EAAM,MAAA,CAAO,IAAA,CAAK,IAAA,CAAK,kBAAA,CAAmB,CAAA,CAAE,CAAC,CAAA,IAAK,EAAE,CAAC,CAAA;AAExF,EAAA,MAAM,QAAA,GAAW,yCAAA;AACjB,EAAA,OAAA,CAAQ,CAAA,GAAI,QAAA,CAAS,IAAA,CAAK,IAAI,OAAO,IAAA,EAAM;AACvC,IAAA,MAAM,KAAA,GAAQ,kBAAA,CAAmB,CAAA,CAAE,CAAC,KAAK,EAAE,CAAA;AAC3C,IAAA,MAAM,KAAA,GAAA,CAAS,CAAA,CAAE,CAAC,CAAA,IAAK,IAAI,IAAA,EAAK;AAChC,IAAA,MAAA,CAAO,MAAA,CAAO,KAAK,KAAA,GAAQ,EAAE,GAAG,KAAA,EAAO,SAAA,EAAW,KAAA,EAAM,GAAI,KAAK,CAAA;AAAA,EACrE;AAEA,EAAA,OAAO,MAAA;AACX;AAMO,SAAS,iBAAiB,QAAA,EAAkC;AAC/D,EAAA,MAAM,UAAU,QAAA,CAAS,MAAA,CAAO,CAAC,CAAA,KAAM,EAAE,OAAO,CAAA;AAEhD,EAAA,MAAM,aAAa,OAAA,CAAQ,MAAA,CAAO,CAAC,CAAA,KAAM,CAAA,CAAE,aAAa,YAAY,CAAA;AACpE,EAAA,MAAM,cAAc,OAAA,CAAQ,MAAA,CAAO,CAAC,CAAA,KAAM,CAAA,CAAE,aAAa,KAAK,CAAA;AAC9D,EAAA,MAAM,mBAAmB,OAAA,CAAQ,MAAA;AAAA,IAC7B,CAAC,MAAM,CAAA,CAAE,QAAA,KAAa,UAAU,CAAA,CAAE,QAAA,CAAS,WAAW,MAAM;AAAA,GAChE;AAEA,EAAA,MAAM,UAAA,GAAa,iBAAiB,GAAA,CAAI,CAAC,MAAM,aAAA,CAAc,CAAA,CAAE,IAAI,CAAC,CAAA;AACpE,EAAA,MAAM,WAAW,UAAA,CAAW,OAAA,CAAQ,CAAC,CAAA,KAAM,EAAE,IAAI,CAAA;AACjD,EAAA,MAAM,WAAW,UAAA,CAAW,OAAA,CAAQ,CAAC,CAAA,KAAM,EAAE,IAAI,CAAA;AACjD,EAAA,MAAM,cAAc,UAAA,CAAW,OAAA,CAAQ,CAAC,CAAA,KAAM,EAAE,MAAM,CAAA;AAEtD,EAAA,OAAO;AAAA,IACH,IAAA,EAAM,QAAA;AAAA,IACN,IAAA,EAAM,QAAA;AAAA,IACN,MAAA,EAAQ;AAAA,MACJ,GAAG,UAAA,CAAW,GAAA,CAAI,CAAC,OAAA,MAAa;AAAA,QAC5B,GAAA,EAAK,CAAA,WAAA,EAAc,OAAA,CAAQ,EAAE,CAAA,CAAA;AAAA,QAC7B,SAAA,EAAW,cAAc,OAAO,CAAA;AAAA,QAChC,IAAA,EAAM,OAAA,CAAQ,QAAA,CAAS,UAAA,CAAW,MAAM,CAAA;AAAA,QACxC,mBAAmB,OAAA,CAAQ;AAAA,OAC/B,CAAE,CAAA;AAAA,MACF,GAAG;AAAA,KACP;AAAA,IACA,KAAA,EAAO,WAAA,CAAY,GAAA,CAAI,CAAC,OAAA,MAAa;AAAA,MACjC,GAAA,EAAK,CAAA,YAAA,EAAe,OAAA,CAAQ,EAAE,CAAA,CAAA;AAAA,MAC9B,WAAW,OAAA,CAAQ,IAAA;AAAA,MACnB,mBAAmB,OAAA,CAAQ;AAAA,KAC/B,CAAE;AAAA,GACN;AACJ","file":"chunk-DKWDKVXQ.js","sourcesContent":["export type SnippetPosition = 'head_start' | 'head_end' | 'body_start' | 'body_end'\nexport type SnippetLanguage = 'html' | 'css' | 'javascript'\n\nexport interface Snippet {\n id: string\n name?: string\n position: SnippetPosition\n language: SnippetLanguage\n code: string\n enabled: boolean\n}\n\nexport interface RawSnippetItem {\n language: SnippetLanguage\n content: string\n enabled?: boolean\n name?: string\n}\n\nexport interface SnippetGroup {\n region: SnippetPosition\n snippets: RawSnippetItem[]\n}\n\nexport function normalizeSnippets(groups: SnippetGroup[] | null | undefined): Snippet[] {\n if (!Array.isArray(groups)) return []\n\n return groups.flatMap((group, groupIndex) =>\n (group.snippets ?? [])\n .filter((item) => !!item.content?.trim())\n .map((item, itemIndex): Snippet => {\n const snippet: Snippet = {\n id: `${group.region}-${groupIndex}-${itemIndex}`,\n position: group.region,\n language: item.language,\n code: item.content,\n enabled: item.enabled ?? true,\n }\n // exactOptionalPropertyTypes: name задаём только если оно есть\n if (item.name !== undefined) snippet.name = item.name\n return snippet\n })\n )\n}\n","import type { Snippet } from './snippets'\n\n/** Head-нагрузка для инъекции сниппетов (потребитель отдаёт её в useHead). */\nexport interface SnippetHead {\n meta: Record<string, string | boolean>[]\n link: Record<string, string | boolean>[]\n script: Record<string, unknown>[]\n style: Record<string, string | boolean>[]\n}\n\n/** JS-сниппет в try/catch с dispatch `zenky:snippet-error` при падении. */\nfunction wrapSnippetJs(snippet: Snippet): string {\n const id = JSON.stringify(snippet.id)\n return `;(() => {\n try {\n ${snippet.code}\n } catch (error) {\n console.error('[snippet:error]', { id: ${id}, error });\n window.dispatchEvent(new CustomEvent('zenky:snippet-error', {\n detail: { id: ${id}, message: error instanceof Error ? error.message : String(error) },\n }));\n }\n})();`\n}\n\ntype HeadAttrs = Record<string, string | boolean>\n\ninterface ParsedHeadHtml {\n meta: HeadAttrs[]\n link: HeadAttrs[]\n script: Array<HeadAttrs & { innerHTML?: string }>\n}\n\nfunction parseTagAttributes(raw: string): HeadAttrs {\n const attrs: HeadAttrs = {}\n const re = /([a-zA-Z][\\w:-]*)(?:\\s*=\\s*(?:\"([^\"]*)\"|'([^']*)'|([^\\s\"'=<>`]+)))?/g\n let match: RegExpExecArray | null\n while ((match = re.exec(raw)) !== null) {\n const name = match[1]\n if (name) attrs[name] = match[2] ?? match[3] ?? match[4] ?? true\n }\n return attrs\n}\n\nfunction parseHeadHtml(html: string): ParsedHeadHtml {\n const result: ParsedHeadHtml = { meta: [], link: [], script: [] }\n let m: RegExpExecArray | null\n\n const metaRe = /<meta\\b([^>]*)>/gi\n while ((m = metaRe.exec(html)) !== null) result.meta.push(parseTagAttributes(m[1] ?? ''))\n\n const linkRe = /<link\\b([^>]*)>/gi\n while ((m = linkRe.exec(html)) !== null) result.link.push(parseTagAttributes(m[1] ?? ''))\n\n const scriptRe = /<script\\b([^>]*)>([\\s\\S]*?)<\\/script>/gi\n while ((m = scriptRe.exec(html)) !== null) {\n const attrs = parseTagAttributes(m[1] ?? '')\n const inner = (m[2] ?? '').trim()\n result.script.push(inner ? { ...attrs, innerHTML: inner } : attrs)\n }\n\n return result\n}\n\n/**\n * Раскладывает сниппеты магазина в head-нагрузку: JS (обёрнут в try/catch),\n * CSS (style), head-HTML (распарсенные meta/link/script). Инъекция — у потребителя.\n */\nexport function buildSnippetHead(snippets: Snippet[]): SnippetHead {\n const enabled = snippets.filter((s) => s.enabled)\n\n const jsSnippets = enabled.filter((s) => s.language === 'javascript')\n const cssSnippets = enabled.filter((s) => s.language === 'css')\n const headHtmlSnippets = enabled.filter(\n (s) => s.language === 'html' && s.position.startsWith('head')\n )\n\n const parsedHead = headHtmlSnippets.map((s) => parseHeadHtml(s.code))\n const headMeta = parsedHead.flatMap((p) => p.meta)\n const headLink = parsedHead.flatMap((p) => p.link)\n const headScripts = parsedHead.flatMap((p) => p.script)\n\n return {\n meta: headMeta,\n link: headLink,\n script: [\n ...jsSnippets.map((snippet) => ({\n key: `snippet-js-${snippet.id}`,\n innerHTML: wrapSnippetJs(snippet),\n body: snippet.position.startsWith('body'),\n 'data-snippet-id': snippet.id,\n })),\n ...headScripts,\n ],\n style: cssSnippets.map((snippet) => ({\n key: `snippet-css-${snippet.id}`,\n innerHTML: snippet.code,\n 'data-snippet-id': snippet.id,\n })),\n }\n}\n"]}
@@ -0,0 +1,3 @@
1
+
2
+ //# sourceMappingURL=chunk-EMGQXBU3.js.map
3
+ //# sourceMappingURL=chunk-EMGQXBU3.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"chunk-EMGQXBU3.js"}
@@ -0,0 +1,45 @@
1
+ // src/catalog/catalogResolver.ts
2
+ function getCity(store, cityId) {
3
+ if (!store.cities.length) return null;
4
+ const defaultCity = store.cities.find((c) => c.is_default) ?? store.cities[0] ?? null;
5
+ if (!cityId) return defaultCity;
6
+ return store.cities.find((c) => c.id === cityId) ?? defaultCity;
7
+ }
8
+ function getStock(city, stockId) {
9
+ if (!city.stocks?.length) return null;
10
+ const defaultStock = city.stocks[0] ?? null;
11
+ if (!stockId) return defaultStock;
12
+ return city.stocks.find((s) => s.id === stockId) ?? defaultStock;
13
+ }
14
+ function getRemoteCatalog(store, city, stock) {
15
+ if (!store.catalogs.length) {
16
+ console.warn("[catalog-resolver] store.catalogs is empty", {
17
+ storeId: store.id,
18
+ cityId: city.id,
19
+ stockId: stock?.id ?? null
20
+ });
21
+ return null;
22
+ }
23
+ if (stock) {
24
+ const exact = store.catalogs.find((c) => c.city_id === city.id && c.stock_id === stock.id);
25
+ if (exact) return exact;
26
+ }
27
+ const cityOnly = store.catalogs.find((c) => c.city_id === city.id && c.stock_id === null);
28
+ if (cityOnly) return cityOnly;
29
+ const global = store.catalogs.find((c) => c.city_id === null && c.stock_id === null);
30
+ if (global) return global;
31
+ console.warn("[catalog-resolver] no catalog matched (exact/city-only/global)", {
32
+ storeId: store.id,
33
+ cityId: city.id,
34
+ stockId: stock?.id ?? null,
35
+ availableCatalogs: store.catalogs.map((c) => ({
36
+ city_id: c.city_id,
37
+ stock_id: c.stock_id
38
+ }))
39
+ });
40
+ return null;
41
+ }
42
+
43
+ export { getCity, getRemoteCatalog, getStock };
44
+ //# sourceMappingURL=chunk-EYRCAUUP.js.map
45
+ //# sourceMappingURL=chunk-EYRCAUUP.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/catalog/catalogResolver.ts"],"names":[],"mappings":";AAEO,SAAS,OAAA,CAAQ,OAAc,MAAA,EAAqC;AACvE,EAAA,IAAI,CAAC,KAAA,CAAM,MAAA,CAAO,MAAA,EAAQ,OAAO,IAAA;AAEjC,EAAA,MAAM,WAAA,GAAc,KAAA,CAAM,MAAA,CAAO,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,UAAU,CAAA,IAAK,KAAA,CAAM,MAAA,CAAO,CAAC,CAAA,IAAK,IAAA;AAEjF,EAAA,IAAI,CAAC,QAAQ,OAAO,WAAA;AAEpB,EAAA,OAAO,KAAA,CAAM,OAAO,IAAA,CAAK,CAAC,MAAM,CAAA,CAAE,EAAA,KAAO,MAAM,CAAA,IAAK,WAAA;AACxD;AAEO,SAAS,QAAA,CAAS,MAAY,OAAA,EAAuC;AACxE,EAAA,IAAI,CAAC,IAAA,CAAK,MAAA,EAAQ,MAAA,EAAQ,OAAO,IAAA;AAEjC,EAAA,MAAM,YAAA,GAAe,IAAA,CAAK,MAAA,CAAO,CAAC,CAAA,IAAK,IAAA;AAEvC,EAAA,IAAI,CAAC,SAAS,OAAO,YAAA;AAErB,EAAA,OAAO,IAAA,CAAK,OAAO,IAAA,CAAK,CAAC,MAAM,CAAA,CAAE,EAAA,KAAO,OAAO,CAAA,IAAK,YAAA;AACxD;AAEO,SAAS,gBAAA,CAAiB,KAAA,EAAc,IAAA,EAAY,KAAA,EAA2C;AAClG,EAAA,IAAI,CAAC,KAAA,CAAM,QAAA,CAAS,MAAA,EAAQ;AACxB,IAAA,OAAA,CAAQ,KAAK,4CAAA,EAA8C;AAAA,MACvD,SAAS,KAAA,CAAM,EAAA;AAAA,MACf,QAAQ,IAAA,CAAK,EAAA;AAAA,MACb,OAAA,EAAS,OAAO,EAAA,IAAM;AAAA,KACzB,CAAA;AACD,IAAA,OAAO,IAAA;AAAA,EACX;AAGA,EAAA,IAAI,KAAA,EAAO;AACP,IAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,QAAA,CAAS,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,OAAA,KAAY,IAAA,CAAK,EAAA,IAAM,CAAA,CAAE,QAAA,KAAa,MAAM,EAAE,CAAA;AACzF,IAAA,IAAI,OAAO,OAAO,KAAA;AAAA,EACtB;AAEA,EAAA,MAAM,QAAA,GAAW,KAAA,CAAM,QAAA,CAAS,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,OAAA,KAAY,IAAA,CAAK,EAAA,IAAM,CAAA,CAAE,QAAA,KAAa,IAAI,CAAA;AACxF,EAAA,IAAI,UAAU,OAAO,QAAA;AAErB,EAAA,MAAM,MAAA,GAAS,KAAA,CAAM,QAAA,CAAS,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,OAAA,KAAY,IAAA,IAAQ,CAAA,CAAE,QAAA,KAAa,IAAI,CAAA;AACnF,EAAA,IAAI,QAAQ,OAAO,MAAA;AAEnB,EAAA,OAAA,CAAQ,KAAK,gEAAA,EAAkE;AAAA,IAC3E,SAAS,KAAA,CAAM,EAAA;AAAA,IACf,QAAQ,IAAA,CAAK,EAAA;AAAA,IACb,OAAA,EAAS,OAAO,EAAA,IAAM,IAAA;AAAA,IACtB,iBAAA,EAAmB,KAAA,CAAM,QAAA,CAAS,GAAA,CAAI,CAAC,CAAA,MAAO;AAAA,MAC1C,SAAS,CAAA,CAAE,OAAA;AAAA,MACX,UAAU,CAAA,CAAE;AAAA,KAChB,CAAE;AAAA,GACL,CAAA;AAED,EAAA,OAAO,IAAA;AACX","file":"chunk-EYRCAUUP.js","sourcesContent":["import type { City, RemoteCatalog, Stock, Store } from '@zenky/storefront-api'\n\nexport function getCity(store: Store, cityId?: string | null): City | null {\n if (!store.cities.length) return null\n\n const defaultCity = store.cities.find((c) => c.is_default) ?? store.cities[0] ?? null\n\n if (!cityId) return defaultCity\n\n return store.cities.find((c) => c.id === cityId) ?? defaultCity\n}\n\nexport function getStock(city: City, stockId?: string | null): Stock | null {\n if (!city.stocks?.length) return null\n\n const defaultStock = city.stocks[0] ?? null\n\n if (!stockId) return defaultStock\n\n return city.stocks.find((s) => s.id === stockId) ?? defaultStock\n}\n\nexport function getRemoteCatalog(store: Store, city: City, stock: Stock | null): RemoteCatalog | null {\n if (!store.catalogs.length) {\n console.warn('[catalog-resolver] store.catalogs is empty', {\n storeId: store.id,\n cityId: city.id,\n stockId: stock?.id ?? null,\n })\n return null\n }\n\n // Приоритет: точное совпадение city+stock → city-only → глобальный.\n if (stock) {\n const exact = store.catalogs.find((c) => c.city_id === city.id && c.stock_id === stock.id)\n if (exact) return exact\n }\n\n const cityOnly = store.catalogs.find((c) => c.city_id === city.id && c.stock_id === null)\n if (cityOnly) return cityOnly\n\n const global = store.catalogs.find((c) => c.city_id === null && c.stock_id === null)\n if (global) return global\n\n console.warn('[catalog-resolver] no catalog matched (exact/city-only/global)', {\n storeId: store.id,\n cityId: city.id,\n stockId: stock?.id ?? null,\n availableCatalogs: store.catalogs.map((c) => ({\n city_id: c.city_id,\n stock_id: c.stock_id,\n })),\n })\n\n return null\n}\n"]}