cloudcommerce 0.39.0 → 0.40.1

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 (206) hide show
  1. package/CHANGELOG.md +67 -0
  2. package/ecomplus-stores/barra-doce/functions/many/package.json +3 -3
  3. package/ecomplus-stores/barra-doce/functions/ssr/package.json +6 -6
  4. package/ecomplus-stores/barra-doce/functions/with-apps/package.json +3 -3
  5. package/ecomplus-stores/barra-doce/package.json +2 -2
  6. package/package.json +6 -6
  7. package/packages/api/lib/api.d.ts +2 -2
  8. package/packages/api/package.json +1 -1
  9. package/packages/api/types/applications.d.ts +15 -0
  10. package/packages/api/types/authentications.d.ts +14 -0
  11. package/packages/api/types/brands.d.ts +17 -0
  12. package/packages/api/types/carts.d.ts +12 -0
  13. package/packages/api/types/categories.d.ts +17 -0
  14. package/packages/api/types/collections.d.ts +15 -0
  15. package/packages/api/types/customers.d.ts +12 -0
  16. package/packages/api/types/grids.d.ts +15 -0
  17. package/packages/api/types/orders.d.ts +15 -0
  18. package/packages/api/types/products.d.ts +17 -0
  19. package/packages/api/types/stores.d.ts +15 -0
  20. package/packages/api/types.d.ts +34 -8
  21. package/packages/apps/affilate-program/package.json +1 -1
  22. package/packages/apps/correios/package.json +2 -2
  23. package/packages/apps/custom-payment/package.json +1 -1
  24. package/packages/apps/custom-shipping/package.json +1 -1
  25. package/packages/apps/datafrete/package.json +2 -2
  26. package/packages/apps/discounts/package.json +2 -2
  27. package/packages/apps/emails/package.json +1 -1
  28. package/packages/apps/fb-conversions/package.json +1 -1
  29. package/packages/apps/flash-courier/package.json +1 -1
  30. package/packages/apps/frenet/package.json +1 -1
  31. package/packages/apps/galaxpay/package.json +1 -1
  32. package/packages/apps/google-analytics/package.json +1 -1
  33. package/packages/apps/jadlog/package.json +1 -1
  34. package/packages/apps/loyalty-points/package.json +2 -2
  35. package/packages/apps/mandae/package.json +2 -2
  36. package/packages/apps/melhor-envio/package.json +1 -1
  37. package/packages/apps/mercadopago/package.json +1 -1
  38. package/packages/apps/pagarme/package.json +1 -1
  39. package/packages/apps/pagarme-v5/package.json +2 -2
  40. package/packages/apps/paghiper/package.json +1 -1
  41. package/packages/apps/pix/package.json +1 -1
  42. package/packages/apps/tiny-erp/package.json +2 -2
  43. package/packages/apps/webhooks/package.json +1 -1
  44. package/packages/cli/config/firebase.json +1 -1
  45. package/packages/cli/package.json +1 -1
  46. package/packages/config/package.json +1 -1
  47. package/packages/emails/package.json +1 -1
  48. package/packages/eslint/package.json +5 -5
  49. package/packages/eslint/storefront.eslintrc.cjs +1 -0
  50. package/packages/events/package.json +1 -1
  51. package/packages/feeds/package.json +1 -1
  52. package/packages/firebase/package.json +2 -2
  53. package/packages/i18n/lib/en_us/i19anyPrice.txt +1 -0
  54. package/packages/i18n/lib/en_us/i19filterOut.txt +1 -0
  55. package/packages/i18n/lib/en_us/i19loadMoreProducts.txt +1 -0
  56. package/packages/i18n/lib/en_us/i19noProductsFound.txt +1 -0
  57. package/packages/i18n/lib/en_us/i19noProductsFoundFor$1.txt +1 -0
  58. package/packages/i18n/lib/en_us/i19noProductsInBrandMsg.txt +1 -0
  59. package/packages/i18n/lib/en_us/i19noProductsInCategoryMsg.txt +1 -0
  60. package/packages/i18n/lib/en_us.d.ts +8 -3
  61. package/packages/i18n/lib/en_us.js +8 -3
  62. package/packages/i18n/lib/en_us.js.map +1 -1
  63. package/packages/i18n/lib/pt_br/i19anyPrice.txt +1 -0
  64. package/packages/i18n/lib/pt_br/i19filterOut.txt +1 -0
  65. package/packages/i18n/lib/pt_br/i19loadMoreProducts.txt +1 -0
  66. package/packages/i18n/lib/pt_br/i19noItemSelected.txt +1 -1
  67. package/packages/i18n/lib/pt_br/i19noProductsFoundFor$1.txt +1 -0
  68. package/packages/i18n/lib/pt_br/i19noProductsInBrandMsg.txt +1 -0
  69. package/packages/i18n/lib/pt_br/i19noProductsInCategoryMsg.txt +1 -0
  70. package/packages/i18n/lib/pt_br.d.ts +9 -4
  71. package/packages/i18n/lib/pt_br.js +9 -4
  72. package/packages/i18n/lib/pt_br.js.map +1 -1
  73. package/packages/i18n/package.json +1 -1
  74. package/packages/i18n/src/en_us.ts +8 -3
  75. package/packages/i18n/src/pt_br.ts +9 -4
  76. package/packages/modules/package.json +2 -2
  77. package/packages/passport/package.json +1 -1
  78. package/packages/ssr/events.js +0 -0
  79. package/packages/ssr/package.json +6 -6
  80. package/packages/storefront/dist/client/_astro/AccountPage.Osc6ffV0.js +1 -0
  81. package/packages/storefront/dist/client/_astro/{CartSidebar.1kUFk9Xn.js → CartSidebar.LrfpXG2B.js} +1 -1
  82. package/packages/storefront/dist/client/_astro/HeroSlider.Zf6kCaqD.js +1 -0
  83. package/packages/storefront/dist/client/_astro/PitchBar.N4d9fV8H.js +1 -0
  84. package/packages/storefront/dist/client/_astro/{Prices.PuQc6C7F.js → Prices.eQ9Vd255.js} +1 -1
  85. package/packages/storefront/dist/client/_astro/ProductDetails.Ln_jf57U.js +7 -0
  86. package/packages/storefront/dist/client/_astro/ProductShelf.X389bv9S.js +1 -0
  87. package/packages/storefront/dist/client/_astro/ProductShelf.vj6lVDCr.js +1 -0
  88. package/packages/storefront/dist/client/_astro/{QuantitySelector.YNmWjN8y.js → QuantitySelector.qSJQlvTe.js} +1 -1
  89. package/packages/storefront/dist/client/_astro/SearchModal.BrVoDBvg.js +1 -0
  90. package/packages/storefront/dist/client/_astro/SearchShowcase.KAz3nwco.js +1 -0
  91. package/packages/storefront/dist/client/_astro/ShopHeader.mFKGzhIu.js +7 -0
  92. package/packages/storefront/dist/client/_astro/_plugin-vue_export-helper._suIQ7WC.js +1 -0
  93. package/packages/storefront/dist/client/_astro/_slug_.cfRx-rdA.css +1 -0
  94. package/packages/storefront/dist/client/_astro/client.PdegUP6S.js +1 -0
  95. package/packages/storefront/dist/client/_astro/{customer-session._VkzXnXT.js → customer-session.KkTHFOXl.js} +1 -1
  96. package/packages/storefront/dist/client/_astro/ecom-icon_12falx.png +0 -0
  97. package/packages/storefront/dist/client/_astro/ecom-icon_15pqnO.png +0 -0
  98. package/packages/storefront/dist/client/_astro/ecom-icon_t3guw.png +0 -0
  99. package/packages/storefront/dist/client/_astro/ecom-utils.Ld2zf-Ve.js +1 -0
  100. package/packages/storefront/dist/client/_astro/{format-money.FMQXgKHB.js → format-money.Axn0YFLJ.js} +1 -1
  101. package/packages/storefront/dist/client/_astro/grid-title.GntHOijE.js +1 -0
  102. package/packages/storefront/dist/client/_astro/headphone_T2Jjc.avif +0 -0
  103. package/packages/storefront/dist/client/_astro/headphone_Z1CG18r.webp +0 -0
  104. package/packages/storefront/dist/client/_astro/{hoisted._FbzheVm.js → hoisted.3ec9NcvP.js} +1 -1
  105. package/packages/storefront/dist/client/_astro/{hoisted.B6fKrLPR.js → hoisted.UnTlypMd.js} +1 -1
  106. package/packages/storefront/dist/client/_astro/{i18n.m7SpISxy.js → i18n.3-NUtxYq.js} +1 -1
  107. package/packages/storefront/dist/client/_astro/{img.zh-Drf-O.js → img.pozlYdd3.js} +1 -1
  108. package/packages/storefront/dist/client/_astro/{index.XrHFaN2F.js → index.TkCbiY_p.js} +1 -1
  109. package/packages/storefront/dist/client/_astro/{index.5PN-EYMS.js → index.m6fK0JXC.js} +1 -1
  110. package/packages/storefront/dist/client/_astro/logo_1UBsBq.webp +0 -0
  111. package/packages/storefront/dist/client/_astro/logo_Z1K5PE9.png +0 -0
  112. package/packages/storefront/dist/client/_astro/logo_Z1KIIl1.avif +0 -0
  113. package/packages/storefront/dist/client/_astro/{modules-info.sD0tdb2b.js → modules-info.mmwB1qIJ.js} +1 -1
  114. package/packages/storefront/dist/client/_astro/name.49dZlpDx.js +1 -0
  115. package/packages/storefront/dist/client/_astro/passion_LHbe9.webp +0 -0
  116. package/packages/storefront/dist/client/_astro/passion_Z23MeUb.avif +0 -0
  117. package/packages/storefront/dist/client/_astro/rect8589_1TtOHY.png +0 -0
  118. package/packages/storefront/dist/client/_astro/rect8589_1f5opX.webp +0 -0
  119. package/packages/storefront/dist/client/_astro/rect8589_IUskt.webp +0 -0
  120. package/packages/storefront/dist/client/_astro/rect8589_Uxfdf.png +0 -0
  121. package/packages/storefront/dist/client/_astro/rect8589_Z15uApA.png +0 -0
  122. package/packages/storefront/dist/client/_astro/rect8589_Z1Ap1Im.avif +0 -0
  123. package/packages/storefront/dist/client/_astro/rect8589_Z1JT1HB.webp +0 -0
  124. package/packages/storefront/dist/client/_astro/rect8589_ZY9mtN.avif +0 -0
  125. package/packages/storefront/dist/client/_astro/rect8589_tMFW0.avif +0 -0
  126. package/packages/storefront/dist/client/_astro/rect859_1TgQXS.avif +0 -0
  127. package/packages/storefront/dist/client/_astro/rect859_2Nm1z.avif +0 -0
  128. package/packages/storefront/dist/client/_astro/rect859_DIqwR.png +0 -0
  129. package/packages/storefront/dist/client/_astro/rect859_Z1IKDb2.png +0 -0
  130. package/packages/storefront/dist/client/_astro/rect859_Z29FI4V.webp +0 -0
  131. package/packages/storefront/dist/client/_astro/rect859_Z2kFHGk.avif +0 -0
  132. package/packages/storefront/dist/client/_astro/rect859_ZkpPFI.webp +0 -0
  133. package/packages/storefront/dist/client/_astro/rect859_jXzBi.png +0 -0
  134. package/packages/storefront/dist/client/_astro/rect859_x1l16.webp +0 -0
  135. package/packages/storefront/dist/client/_astro/rect89_1TSfW7.avif +0 -0
  136. package/packages/storefront/dist/client/_astro/rect89_Z1re32x.webp +0 -0
  137. package/packages/storefront/dist/client/_astro/sf-utils.yjuG8NjM.js +1 -0
  138. package/packages/storefront/dist/client/_astro/{shopping-cart.A-1jhlKi.js → shopping-cart.Ean2uW2R.js} +1 -1
  139. package/packages/storefront/dist/client/_astro/use-analytics.Y7amTmnr.js +1 -0
  140. package/packages/storefront/dist/client/_astro/{use-product-card.erbOV6Fv.js → use-product-card.zB4z32bC.js} +1 -1
  141. package/packages/storefront/dist/client/_astro/use-text-value.f9pDzruv.js +4 -0
  142. package/packages/storefront/dist/client/app/account/index.html +56 -0
  143. package/packages/storefront/dist/client/app/index.html +96 -0
  144. package/packages/storefront/dist/client/headset-gamer-corsair-hs40-raptor-ca-9011122-naap-vinho/index.html +107 -0
  145. package/packages/storefront/dist/client/index.html +120 -0
  146. package/packages/storefront/dist/client/macbook-apple-pro-15.4-intel-core-i7-2.6ghz-ram-16gb-ssd-512gb-mr972ll/a-prata/index.html +107 -0
  147. package/packages/storefront/dist/client/mouse-gamer-corsair-optico-harpoon-rgb-ch-9301011-na/index.html +107 -0
  148. package/packages/storefront/dist/client/teclado-gamer-fortrek-mecanico-k5-rgb-outemu-blue-preto/index.html +107 -0
  149. package/packages/storefront/dist/client/~fallback/index.html +74 -0
  150. package/packages/storefront/dist/server/chunks/{CartSidebar_hAgJQJgm.mjs → CartSidebar_gCUGFy65.mjs} +1 -1
  151. package/packages/storefront/dist/server/chunks/{SearchModal_eWb5SdQM.mjs → SearchModal_2uYZ9tHy.mjs} +4 -5
  152. package/packages/storefront/dist/server/chunks/{_.._S7DDBn_b.mjs → _.._FnNLvTNv.mjs} +1 -1
  153. package/packages/storefront/dist/server/chunks/{account_3ySmGzMc.mjs → account_iZ2QmK5E.mjs} +1 -1
  154. package/packages/storefront/dist/server/chunks/astro/{assets-service_QlOZG8ov.mjs → assets-service_TpUb271h.mjs} +58 -136
  155. package/packages/storefront/dist/server/chunks/{astro_zcC1GStV.mjs → astro_PpArQAsY.mjs} +4 -4
  156. package/packages/storefront/dist/server/chunks/{index_nIwq11oA.mjs → index_1q5IpD39.mjs} +1 -1
  157. package/packages/storefront/dist/server/chunks/{index_uAR5ZV4d.mjs → index_JEU6B2DI.mjs} +1 -1
  158. package/packages/storefront/dist/server/chunks/{node_2VvC7trl.mjs → node_wOa5hJmt.mjs} +1 -1
  159. package/packages/storefront/dist/server/chunks/pages/{__MSibDuuV.mjs → __cEcxuEWe.mjs} +1674 -422
  160. package/packages/storefront/dist/server/chunks/pages/{account_iG-YqJ5q.mjs → account_WliDuQOB.mjs} +2 -2
  161. package/packages/storefront/dist/server/chunks/pages/{index_BtDyKPh_.mjs → index_2bQLkQf-.mjs} +3 -3
  162. package/packages/storefront/dist/server/chunks/pages/{node_bKqL47eZ.mjs → node_ZaY4t9qW.mjs} +21 -2
  163. package/packages/storefront/dist/server/chunks/pages/{~fallback_73R5VA6j.mjs → ~fallback_TL5hPXF_.mjs} +2 -2
  164. package/packages/storefront/dist/server/chunks/{~fallback_7q1dqY4e.mjs → ~fallback_wxuqpDXG.mjs} +1 -1
  165. package/packages/storefront/dist/server/entry.mjs +34 -16
  166. package/packages/storefront/dist/server/{manifest_dSwvaOdW.mjs → manifest_YgAgEwvF.mjs} +17 -16
  167. package/packages/storefront/dist/server/renderers.mjs +14 -3
  168. package/packages/storefront/package.json +12 -12
  169. package/packages/storefront/src/helpers/sf-utils.ts +11 -0
  170. package/packages/storefront/src/lib/$storefront.d.ts +7 -1
  171. package/packages/storefront/src/lib/components/Drawer.vue +13 -8
  172. package/packages/storefront/src/lib/components/globals/ALink.vue +1 -1
  173. package/packages/storefront/src/lib/composables/use-pagination.ts +61 -0
  174. package/packages/storefront/src/lib/composables/use-product-shelf.ts +29 -19
  175. package/packages/storefront/src/lib/composables/use-search-filters.ts +271 -0
  176. package/packages/storefront/src/lib/composables/use-search-modal.ts +2 -11
  177. package/packages/storefront/src/lib/composables/use-search-showcase.ts +168 -22
  178. package/packages/storefront/src/lib/composables/use-sku-selector.ts +3 -8
  179. package/packages/storefront/src/lib/layouts/use-page-main.ts +20 -8
  180. package/packages/storefront/src/lib/state/search-engine.ts +19 -6
  181. package/packages/storefront/tsconfig.json +1 -1
  182. package/packages/test-base/package.json +1 -1
  183. package/packages/types/package.json +1 -1
  184. package/pnpm-workspace.yaml +0 -2
  185. package/packages/i18n/lib/en_us/i19noProduct.txt +0 -1
  186. package/packages/i18n/lib/en_us/i19noResultsFor.txt +0 -1
  187. package/packages/i18n/lib/pt_br/i19noResultsFor.txt +0 -1
  188. package/packages/storefront/dist/client/_astro/AccountPage.j0C5JBLY.js +0 -1
  189. package/packages/storefront/dist/client/_astro/HeroSlider.tR1dVXyu.js +0 -1
  190. package/packages/storefront/dist/client/_astro/PitchBar.FelC04wE.js +0 -1
  191. package/packages/storefront/dist/client/_astro/ProductCard.ephJafAE.js +0 -1
  192. package/packages/storefront/dist/client/_astro/ProductDetails.B6Ih5MGf.js +0 -7
  193. package/packages/storefront/dist/client/_astro/ProductShelf.GESxuPZ_.js +0 -1
  194. package/packages/storefront/dist/client/_astro/SearchContainer.BzixfU3R.js +0 -1
  195. package/packages/storefront/dist/client/_astro/SearchModal.F7vbwxIv.js +0 -1
  196. package/packages/storefront/dist/client/_astro/ShopHeader.5vR1LgSW.js +0 -10
  197. package/packages/storefront/dist/client/_astro/_plugin-vue_export-helper.sk5AFsEV.js +0 -1
  198. package/packages/storefront/dist/client/_astro/_slug_.r8QHNfdw.css +0 -1
  199. package/packages/storefront/dist/client/_astro/client.RF8UxjZd.js +0 -1
  200. package/packages/storefront/dist/client/_astro/ecom-utils.gJYgRPRz.js +0 -1
  201. package/packages/storefront/dist/client/_astro/name.HU5l7TJo.js +0 -1
  202. package/packages/storefront/dist/client/_astro/sf-utils.5t7r9A2G.js +0 -1
  203. package/packages/storefront/dist/client/_astro/use-analytics.1EVxbrS7.js +0 -1
  204. /package/packages/i18n/lib/en_us/{i19relatedProduct.txt → i19relatedProducts.txt} +0 -0
  205. /package/packages/i18n/lib/pt_br/{i19noProduct.txt → i19noProductsFound.txt} +0 -0
  206. /package/packages/i18n/lib/pt_br/{i19relatedProduct.txt → i19relatedProducts.txt} +0 -0
@@ -2,6 +2,7 @@ import type { ResourceId, Collections, SearchItem } from '@cloudcommerce/types';
2
2
  import { ref, shallowReactive } from 'vue';
3
3
  import api from '@cloudcommerce/api';
4
4
  import { inStock as checkInStock } from '@ecomplus/utils';
5
+ import { i19relatedProducts } from '@@i18n';
5
6
 
6
7
  export interface Props {
7
8
  collectionId?: ResourceId | null;
@@ -13,6 +14,7 @@ export interface Props {
13
14
  limit?: number;
14
15
  page?: number;
15
16
  products?: SearchItem[];
17
+ isRelatedProducts?: boolean;
16
18
  }
17
19
 
18
20
  const useProductShelf = (props: Props) => {
@@ -26,31 +28,39 @@ const useProductShelf = (props: Props) => {
26
28
  if (!props.products) {
27
29
  isFetching.value = true;
28
30
  fetching = (async () => {
29
- let searchQuery = props.searchQuery || '';
30
- let collection: Collections | undefined;
31
- if (props.collectionId) {
32
- try {
33
- const { data } = await api.get(`collections/${props.collectionId}`);
34
- collection = data;
35
- } catch (err: any) {
36
- console.error(err);
37
- fetchError.value = err;
38
- }
39
- const productIds = collection?.products;
40
- if (Array.isArray(productIds) && productIds.length) {
41
- searchQuery += `&_id=${productIds.slice(0, 60).join(',')}`;
42
- }
43
- if (!title.value && title.value !== null && collection?.name) {
44
- title.value = collection?.name;
45
- }
46
- }
47
31
  const limit = props.limit || 24;
48
32
  const offset = props.page ? (props.page - 1) * limit : 0;
49
33
  let endpointQuery = `offset=${offset}&limit=${limit}`;
50
34
  if (props.sort) {
51
35
  endpointQuery += `&sort=${props.sort}`;
52
36
  }
53
- endpointQuery += searchQuery;
37
+ if (props.isRelatedProducts) {
38
+ const { apiContext } = globalThis.$storefront;
39
+ if (apiContext?.resource === 'products') {
40
+ endpointQuery = `like=${apiContext.doc._id}`;
41
+ title.value = i19relatedProducts;
42
+ }
43
+ } else {
44
+ let searchQuery = props.searchQuery || '';
45
+ let collection: Collections | undefined;
46
+ if (props.collectionId) {
47
+ try {
48
+ const { data } = await api.get(`collections/${props.collectionId}`);
49
+ collection = data;
50
+ } catch (err: any) {
51
+ console.error(err);
52
+ fetchError.value = err;
53
+ }
54
+ const productIds = collection?.products;
55
+ if (Array.isArray(productIds) && productIds.length) {
56
+ searchQuery += `&_id=${productIds.slice(0, 60).join(',')}`;
57
+ }
58
+ if (!title.value && title.value !== null && collection?.name) {
59
+ title.value = collection?.name;
60
+ }
61
+ }
62
+ endpointQuery += searchQuery;
63
+ }
54
64
  try {
55
65
  const { data } = await api.get(`search/v1?${endpointQuery}`);
56
66
  if (props.isShuffle) {
@@ -0,0 +1,271 @@
1
+ import type SearchEngineInstance from '@@sf/state/search-engine';
2
+ import { ref, computed, watch } from 'vue';
3
+ import { useDebounceFn } from '@vueuse/core';
4
+ import { formatMoney, gridTitle as getGridTitle } from '@ecomplus/utils';
5
+ import {
6
+ i19aboveOf,
7
+ i19brands,
8
+ i19categories,
9
+ i19upTo,
10
+ } from '@@i18n';
11
+
12
+ export interface Props {
13
+ searchEngine: SearchEngineInstance;
14
+ fixedParams?: SearchEngineInstance['params'];
15
+ }
16
+
17
+ type PriceRange = Exclude<
18
+ Exclude<SearchEngineInstance['meta']['buckets'], undefined>['prices'],
19
+ undefined
20
+ >[number];
21
+
22
+ const useSearchActiveFilters = ({ searchEngine, fixedParams }: Props) => {
23
+ const activeFilters = computed<SearchEngineInstance['params']>(() => {
24
+ const filters = {};
25
+ Object.keys(searchEngine.params).forEach((param) => {
26
+ if (fixedParams?.[param]) return;
27
+ const val = searchEngine.params[param];
28
+ if (val === undefined) return;
29
+ switch (param) {
30
+ case 'sort':
31
+ case 'term':
32
+ case 'limit':
33
+ case 'offset':
34
+ case 'count':
35
+ case 'buckets':
36
+ case 'fields':
37
+ return;
38
+ default:
39
+ }
40
+ filters[param] = val;
41
+ });
42
+ return filters;
43
+ });
44
+ const filtersCount = computed(() => {
45
+ const paramKeys = Object.keys(activeFilters.value);
46
+ const fields: string[] = [];
47
+ paramKeys.forEach((key) => {
48
+ const field = key.replace(/[^\w.]/g, '');
49
+ if (field === 'specs' && Array.isArray(activeFilters.value[key])) {
50
+ (activeFilters.value[key] as string[]).forEach((specAndVal) => {
51
+ const [spec] = specAndVal.split(':');
52
+ const specField = `specs.${spec}`;
53
+ if (!fields.includes(specField)) {
54
+ fields.push(specField);
55
+ }
56
+ });
57
+ return;
58
+ }
59
+ if (!fields.includes(field)) {
60
+ fields.push(field);
61
+ }
62
+ });
63
+ return fields.length;
64
+ });
65
+ return { activeFilters, filtersCount };
66
+ };
67
+
68
+ const useSearchFilters = (props: Props) => {
69
+ const { searchEngine, fixedParams } = props;
70
+ const resultMeta = computed(() => searchEngine.meta);
71
+ const resultBuckets = computed(() => searchEngine.meta.buckets);
72
+ const { activeFilters, filtersCount } = useSearchActiveFilters(props);
73
+ watch(searchEngine.params, () => {
74
+ searchEngine.fetch();
75
+ });
76
+ let _lastParamChanged: null | string = null;
77
+ const clearFilters = () => {
78
+ Object.keys(searchEngine.params).forEach((param) => {
79
+ if (fixedParams?.[param]) return;
80
+ delete searchEngine.params[param];
81
+ });
82
+ _lastParamChanged = null;
83
+ };
84
+
85
+ const getPriceRangeKey = ({ min, max }: Partial<PriceRange>) => {
86
+ return `${min || null}/${max || null}`;
87
+ };
88
+ const currentPriceRange = computed(() => {
89
+ const { params } = searchEngine;
90
+ return {
91
+ min: Number(params['price>']),
92
+ max: Number(params['price<']),
93
+ };
94
+ });
95
+ const priceRanges = ref<Array<{ range: PriceRange, key: string }>>([]);
96
+ const _updatePriceRanges = useDebounceFn(() => {
97
+ priceRanges.value.splice(0);
98
+ resultBuckets.value?.prices?.forEach((priceRange) => {
99
+ if (priceRange.min) priceRange.min = Math.round(priceRange.min * 100) / 100;
100
+ if (priceRange.max) priceRange.max = Math.round(priceRange.max * 100) / 100;
101
+ priceRanges.value.push({
102
+ range: priceRange,
103
+ key: getPriceRangeKey(priceRange),
104
+ });
105
+ });
106
+ if (!Number.isNaN(currentPriceRange.value.min)) {
107
+ const checkedPriceRange = priceRanges.value.find(({ range }) => {
108
+ return range.min === currentPriceRange.value.min
109
+ && range.max === currentPriceRange.value.max;
110
+ });
111
+ if (!checkedPriceRange) {
112
+ priceRanges.value.unshift({
113
+ range: {
114
+ ...currentPriceRange.value,
115
+ count: resultMeta.value.count || 0,
116
+ avg: null,
117
+ },
118
+ key: getPriceRangeKey(currentPriceRange.value),
119
+ });
120
+ }
121
+ }
122
+ }, 50);
123
+ _updatePriceRanges();
124
+ const priceRangeKey = ref<string | null>(getPriceRangeKey(currentPriceRange.value));
125
+ watch(priceRangeKey, () => {
126
+ _lastParamChanged = 'price';
127
+ const priceRange = priceRanges.value.find(({ range }) => {
128
+ return getPriceRangeKey(range) === priceRangeKey.value;
129
+ });
130
+ if (priceRange) {
131
+ const { min, max } = priceRange.range;
132
+ if (min) {
133
+ searchEngine.params['price>'] = min;
134
+ } else {
135
+ delete searchEngine.params['price>'];
136
+ }
137
+ if (max) {
138
+ searchEngine.params['price<'] = max;
139
+ } else {
140
+ delete searchEngine.params['price<'];
141
+ }
142
+ return;
143
+ }
144
+ delete searchEngine.params['price>'];
145
+ delete searchEngine.params['price<'];
146
+ });
147
+ const getPriceRangeLabel = ({ min, max }: PriceRange) => {
148
+ if (min && max) {
149
+ if (max === min) {
150
+ return formatMoney(max);
151
+ }
152
+ return `${formatMoney(min)} ${i19upTo.toLowerCase()} ${formatMoney(max)}`;
153
+ }
154
+ if (!min && max) {
155
+ return `${i19upTo} ${formatMoney(max)}`;
156
+ }
157
+ return `${i19aboveOf} ${formatMoney(min || 0)}`;
158
+ };
159
+
160
+ const filterOptions = ref<Array<{
161
+ title: string,
162
+ options: { [value: string]: number },
163
+ field: string,
164
+ }>>([]);
165
+ const _updateFilterOptions = useDebounceFn(() => {
166
+ for (let i = 0; i < filterOptions.value.length; i++) {
167
+ const { field } = filterOptions.value[i];
168
+ if (field !== _lastParamChanged) {
169
+ filterOptions.value.splice(i, 1);
170
+ i -= 1;
171
+ }
172
+ }
173
+ const buckets = resultBuckets.value;
174
+ if (buckets) {
175
+ [['brands', i19brands], ['categories', i19categories]]
176
+ .forEach(([resource, title]) => {
177
+ const field = `${resource}.name`;
178
+ if (buckets[field] && _lastParamChanged !== field) {
179
+ filterOptions.value.push({
180
+ title,
181
+ options: buckets[field],
182
+ field,
183
+ });
184
+ }
185
+ });
186
+ if (buckets.specs) {
187
+ const { grids } = globalThis.$storefront.data;
188
+ Object.keys(buckets.specs).forEach((specAndVal) => {
189
+ const [spec, value] = specAndVal.split(':');
190
+ if (value) {
191
+ const field = `specs.${spec}`;
192
+ if (_lastParamChanged === field) return;
193
+ const title = getGridTitle(spec, grids || []);
194
+ let filterOption = filterOptions.value.find((_filterOption) => {
195
+ return _filterOption.field === field;
196
+ });
197
+ if (!filterOption) {
198
+ filterOption = { title, options: {}, field };
199
+ filterOptions.value.push(filterOption);
200
+ }
201
+ filterOption.options[value] = buckets.specs![specAndVal];
202
+ }
203
+ });
204
+ }
205
+ }
206
+ }, 50);
207
+ _updateFilterOptions();
208
+ const parseSpecsField = (field: string, value: string | number) => {
209
+ const [, spec] = field.split('.');
210
+ return ['specs,', `${spec}:${value}`];
211
+ };
212
+ const checkFilterOption = (field: string, value: string | number) => {
213
+ if (field.startsWith('specs.')) {
214
+ [field, value] = parseSpecsField(field, value);
215
+ }
216
+ const fieldParams = activeFilters.value[field];
217
+ if (fieldParams === value) return true;
218
+ // @ts-ignore
219
+ if (Array.isArray(fieldParams) && fieldParams.includes(value)) return true;
220
+ return false;
221
+ };
222
+ const toggleFilterOption = (field: string, value: string | number) => {
223
+ _lastParamChanged = field;
224
+ console.log({ _lastParamChanged });
225
+ if (field.startsWith('specs.')) {
226
+ [field, value] = parseSpecsField(field, value);
227
+ }
228
+ const isToActivate = !checkFilterOption(field, value);
229
+ let fieldParams = searchEngine.params[field];
230
+ if (!Array.isArray(fieldParams)) {
231
+ fieldParams = [];
232
+ searchEngine.params[field] = fieldParams;
233
+ }
234
+ if (isToActivate) {
235
+ // @ts-ignore
236
+ fieldParams.push(value);
237
+ } else {
238
+ for (let i = 0; i < fieldParams.length; i++) {
239
+ if (fieldParams[i] === value) {
240
+ fieldParams.splice(i, 1);
241
+ break;
242
+ }
243
+ }
244
+ }
245
+ };
246
+ watch(resultBuckets, () => {
247
+ if (_lastParamChanged !== 'price') {
248
+ _updatePriceRanges();
249
+ }
250
+ _updateFilterOptions();
251
+ });
252
+
253
+ return {
254
+ resultMeta,
255
+ resultBuckets,
256
+ activeFilters,
257
+ filtersCount,
258
+ clearFilters,
259
+ getPriceRangeKey,
260
+ priceRanges,
261
+ priceRangeKey,
262
+ getPriceRangeLabel,
263
+ filterOptions,
264
+ checkFilterOption,
265
+ toggleFilterOption,
266
+ };
267
+ };
268
+
269
+ export default useSearchFilters;
270
+
271
+ export { useSearchFilters, useSearchActiveFilters };
@@ -1,8 +1,4 @@
1
- import type {
2
- Categories,
3
- Brands,
4
- Collections,
5
- } from '@cloudcommerce/api/types';
1
+ import type { Categories } from '@cloudcommerce/api/types';
6
2
  import { ref, watch, toRef } from 'vue';
7
3
  import Wade from 'wade';
8
4
  import { clearAccents } from '@@sf/sf-lib';
@@ -14,18 +10,13 @@ export interface Props {
14
10
  productsLimit?: number;
15
11
  }
16
12
 
17
- const storefrontData = globalThis.$storefront.data as {
18
- categories?: Array<Partial<Categories>>,
19
- brands?: Array<Partial<Brands>>,
20
- collections?: Array<Partial<Collections>>,
21
- };
22
13
  const wadeDocs: Array<{
23
14
  text: string,
24
15
  type: 'categories' | 'brands' | 'collections' | 'blog',
25
16
  data: Record<string, any> & { name: string, slug: string },
26
17
  }> = [];
27
18
  (['categories', 'brands', 'collections'] as const).forEach((resource) => {
28
- const docsList = storefrontData[resource];
19
+ const docsList = globalThis.$storefront.data[resource];
29
20
  if (docsList) {
30
21
  for (let i = 0; i < docsList.length; i++) {
31
22
  const doc = docsList[i];
@@ -1,18 +1,40 @@
1
+ import type { Ref } from 'vue';
1
2
  import type { SearchItem } from '@cloudcommerce/types';
2
- import { watch, shallowReactive } from 'vue';
3
+ import type { SearchEngineInstance } from '@@sf/state/search-engine';
4
+ import {
5
+ ref,
6
+ computed,
7
+ watch,
8
+ shallowReactive,
9
+ } from 'vue';
10
+ import { useUrlSearchParams, watchOnce } from '@vueuse/core';
11
+ import { isScreenLg, scrollToEl } from '@@sf/sf-lib';
12
+ import {
13
+ i19discount,
14
+ i19highestPrice,
15
+ i19lowestPrice,
16
+ i19name,
17
+ i19releases,
18
+ i19relevance,
19
+ i19sales,
20
+ } from '@@i18n';
3
21
  import { SearchEngine } from '@@sf/state/search-engine';
22
+ import { useSearchActiveFilters } from '@@sf/composables/use-search-filters';
4
23
 
5
24
  export interface Props {
6
- searchEngine?: InstanceType<typeof SearchEngine>;
7
25
  term?: string | null;
8
- params?: Record<string, any>;
9
- sort?: '-sales' | '-created_at' | 'price' | '-price' | '-price_discount' | string;
10
- products?: SearchItem[];
26
+ fixedParams?: SearchEngineInstance['params'];
27
+ products?: SearchItem<null>[];
28
+ resultMeta?: SearchEngineInstance['meta'];
11
29
  ssrError?: string | null;
30
+ canUseUrlParams?: boolean;
31
+ showcase?: Ref<HTMLElement | null>;
12
32
  }
13
33
 
14
34
  const useSearchShowcase = (props: Props) => {
15
- let { term, searchEngine } = props;
35
+ let { term } = props;
36
+ const canUseUrlParams = props.canUseUrlParams !== false;
37
+ const urlParams = canUseUrlParams ? useUrlSearchParams('history') : null;
16
38
  if (props.ssrError && !import.meta.env.SSR) {
17
39
  console.error(new Error(`SSR search error: ${props.ssrError}`));
18
40
  if (window.location.pathname.startsWith('/s/')) {
@@ -20,33 +42,157 @@ const useSearchShowcase = (props: Props) => {
20
42
  }
21
43
  }
22
44
  const products = shallowReactive<SearchItem[]>(props.products || []);
23
- if (!searchEngine) {
24
- searchEngine = new SearchEngine({ debounce: 50 });
25
- if (term === undefined && !import.meta.env.SSR) {
26
- term = new URLSearchParams(window.location.search).get('q') || null;
27
- }
28
- if (term !== undefined) {
29
- searchEngine.term.value = term;
30
- }
31
- if (props.params) {
32
- Object.assign(searchEngine.params, props.params);
45
+ const searchEngine = new SearchEngine({ debounce: 50 });
46
+ if (term === undefined && !import.meta.env.SSR) {
47
+ term = new URLSearchParams(window.location.search).get('q') || null;
48
+ }
49
+ if (term !== undefined) {
50
+ searchEngine.term.value = term;
51
+ }
52
+ Object.assign(searchEngine.params, props.fixedParams);
53
+ if (urlParams) {
54
+ Object.keys(urlParams).forEach((param) => {
55
+ if (param === 'sort') {
56
+ searchEngine.params.sort = urlParams.sort;
57
+ return;
58
+ }
59
+ if (param === 'p') {
60
+ const pageNumber = parseInt(String(urlParams.p), 10);
61
+ if (pageNumber >= 1) searchEngine.pageNumber.value = pageNumber;
62
+ return;
63
+ }
64
+ if (param.startsWith('f\\')) {
65
+ const field = param.substring(2);
66
+ searchEngine.params[field] = urlParams[param];
67
+ }
68
+ });
69
+ }
70
+ if (!searchEngine.wasFetched.value) {
71
+ if (props.products || props.resultMeta) {
72
+ searchEngine.setResult({
73
+ result: props.products,
74
+ meta: props.resultMeta,
75
+ });
33
76
  }
34
- if (props.sort) {
35
- searchEngine.params.sort = props.sort;
77
+ if (!props.products) {
78
+ searchEngine.fetch().catch(console.error);
36
79
  }
37
80
  }
38
- if (!searchEngine.wasFetched.value && !props.products) {
39
- searchEngine.fetch().catch(console.error);
40
- }
81
+ searchEngine.isWithCount.value = true;
82
+ searchEngine.isWithBuckets.value = true;
83
+ const resultMeta = ref({
84
+ count: 0,
85
+ ...(props.resultMeta || searchEngine.meta),
86
+ });
41
87
  watch(searchEngine.products, () => {
42
88
  products.splice(0);
43
- searchEngine?.products.forEach((item) => products.push(item));
89
+ searchEngine.products.forEach((item) => products.push(item));
90
+ resultMeta.value = {
91
+ count: 0,
92
+ ...searchEngine.meta,
93
+ };
94
+ });
95
+
96
+ const totalPages = computed(() => {
97
+ const { count } = searchEngine.meta;
98
+ if (!count || products.length < 2) return 1;
99
+ return Math.ceil(count / searchEngine.pageSize.value);
100
+ });
101
+ watch(searchEngine.pageNumber, (pageNumber) => {
102
+ if (urlParams) {
103
+ urlParams.p = `${pageNumber}`;
104
+ }
105
+ searchEngine.fetch();
44
106
  });
107
+ const startWatchingFetch = () => {
108
+ watch(searchEngine.isFetching, (isFetching) => {
109
+ const el = props.showcase?.value;
110
+ if (!isFetching && el) {
111
+ setTimeout(() => {
112
+ scrollToEl(el, isScreenLg ? -25 : 0);
113
+ }, 50);
114
+ }
115
+ });
116
+ };
117
+ if (searchEngine.wasFetched.value) {
118
+ startWatchingFetch();
119
+ } else {
120
+ watchOnce(searchEngine.wasFetched, startWatchingFetch);
121
+ }
122
+
123
+ const { activeFilters, filtersCount } = useSearchActiveFilters({
124
+ searchEngine,
125
+ fixedParams: props.fixedParams,
126
+ });
127
+ if (urlParams) {
128
+ watch(activeFilters, (params) => {
129
+ if (urlParams) {
130
+ Object.keys(urlParams).forEach((param) => {
131
+ if (param.startsWith('f\\')) delete urlParams[param];
132
+ });
133
+ }
134
+ Object.keys(params).forEach((param) => {
135
+ const val = params[param];
136
+ if (typeof val === 'string' || typeof val === 'number') {
137
+ urlParams[`f\\${param}`] = `${val}`;
138
+ return;
139
+ }
140
+ if (Array.isArray(val) && typeof val[0] === 'string') {
141
+ urlParams[`f\\${param}`] = val as string[];
142
+ }
143
+ });
144
+ });
145
+ }
146
+ const sortOptions = [
147
+ {
148
+ value: null,
149
+ label: i19relevance,
150
+ }, {
151
+ value: '-sales',
152
+ label: i19sales,
153
+ }, {
154
+ value: 'price',
155
+ label: i19lowestPrice,
156
+ }, {
157
+ value: '-price',
158
+ label: i19highestPrice,
159
+ }, {
160
+ value: '-price_discount',
161
+ label: i19discount,
162
+ }, {
163
+ value: '-created_at',
164
+ label: i19releases,
165
+ }, {
166
+ value: 'name',
167
+ label: i19name,
168
+ },
169
+ ];
170
+ const sortOption = ref<string | null>(null);
171
+ watch(sortOption, () => {
172
+ searchEngine.params.sort = sortOption.value || undefined;
173
+ searchEngine.fetch();
174
+ });
175
+ if (urlParams) {
176
+ if (typeof urlParams.sort === 'string' && urlParams.sort) {
177
+ sortOption.value = urlParams.sort;
178
+ }
179
+ watch(searchEngine.params, (params) => {
180
+ delete urlParams.sort;
181
+ if (params.sort) urlParams.sort = String(params.sort);
182
+ });
183
+ }
184
+
45
185
  return {
46
186
  searchEngine,
47
187
  fetching: searchEngine.fetching.value,
48
188
  isFetching: searchEngine.isFetching,
49
189
  products,
190
+ resultMeta,
191
+ totalPages,
192
+ activeFilters,
193
+ filtersCount,
194
+ sortOptions,
195
+ sortOption,
50
196
  };
51
197
  };
52
198
 
@@ -1,8 +1,4 @@
1
- import type {
2
- ResourceId,
3
- Products,
4
- GridsList,
5
- } from '@cloudcommerce/api/types';
1
+ import type { ResourceId, Products } from '@cloudcommerce/api/types';
6
2
  import {
7
3
  ref,
8
4
  computed,
@@ -25,9 +21,8 @@ export interface Props {
25
21
  }
26
22
 
27
23
  const useSkuSelector = (props: Props) => {
28
- let grids = shallowReactive<GridsList<null>>(globalThis.$storefront.data.grids);
29
- if (!grids) {
30
- grids = shallowReactive([]);
24
+ const grids = shallowReactive(globalThis.$storefront.data.grids || []);
25
+ if (!grids.length) {
31
26
  api.get('grids').then(({ data: { result } }) => {
32
27
  result.forEach((grid) => grids.push(grid));
33
28
  });
@@ -56,10 +56,13 @@ export const usePageSections = async <T extends CustomSection = CustomSection>
56
56
  | { type: 'banners-grid', props: { banners: UseBannerProps[] } }
57
57
  | { type: 'product-details', props: ProductDetailsProps }
58
58
  | { type: 'breadcrumbs', props: {} }
59
- | { type: 'related-products', props: {} }
59
+ | { type: 'related-products', props: UseProductShelfProps }
60
60
  | { type: 'doc-description', props: {} }
61
+ | { type: 'doc-banners', props: {} }
61
62
  | { type: 'product-specifications', props: {} }
62
63
  | { type: 'search-showcase' | 'context-showcase', props: UseSearchShowcaseProps }
64
+ | { type: 'page-title', props: {} }
65
+ | { type: 'custom-html', props: { html: string } }
63
66
  > = [];
64
67
  if (sectionsContent) {
65
68
  await Promise.all(sectionsContent.map(async ({ type, ...sectionContent }, index) => {
@@ -134,32 +137,33 @@ export const usePageSections = async <T extends CustomSection = CustomSection>
134
137
  }
135
138
  const { resource, doc } = routeContext.apiContext;
136
139
  if (resource === 'categories' || resource === 'brands') {
137
- const params = { [`${resource}.slug`]: [doc!.slug] };
140
+ const params = { [`${resource}.name`]: [doc!.name] };
138
141
  if (resource === 'categories') {
139
142
  const { value: categories } = await useSharedData({ field: 'categories' });
140
- categories?.forEach(({ slug, parent }) => {
143
+ categories?.forEach(({ name, parent }) => {
141
144
  if (
142
- slug && parent
145
+ name && parent
143
146
  && (parent._id === doc!._id || parent.slug === doc!.slug)
144
147
  ) {
145
- params[`categories.slug`].push(slug);
148
+ params[`categories.name`].push(name);
146
149
  }
147
150
  });
148
151
  }
149
- props.params = params;
152
+ props.fixedParams = params;
150
153
  } else if (resource === 'collections') {
151
154
  const { products } = (doc as Collections);
152
155
  if (products?.length) {
153
- props.params = { _id: products };
156
+ props.fixedParams = { _id: products };
154
157
  }
155
158
  }
156
159
  } else if (routeContext.searchPageTerm !== undefined) {
157
160
  props.term = routeContext.searchPageTerm || null;
158
161
  }
159
- if (props.term !== undefined || props.params) {
162
+ if (props.term !== undefined || props.fixedParams) {
160
163
  const { searchEngine, fetching } = useSearchShowcase(props);
161
164
  await fetching;
162
165
  props.products = searchEngine.products;
166
+ props.resultMeta = searchEngine.meta;
163
167
  props.ssrError = searchEngine.fetchError.value?.message;
164
168
  }
165
169
  sections[index] = { type, props };
@@ -180,13 +184,21 @@ export const usePageSections = async <T extends CustomSection = CustomSection>
180
184
  case 'product-details':
181
185
  case 'related-products':
182
186
  case 'doc-description':
187
+ case 'doc-banners':
183
188
  case 'product-specifications':
189
+ case 'page-title':
184
190
  // Bypassed sections
185
191
  sections[index] = {
186
192
  type,
187
193
  props: sectionContent,
188
194
  };
189
195
  return;
196
+ case 'custom-html':
197
+ sections[index] = {
198
+ type,
199
+ props: { html: sectionContent.html || '' },
200
+ };
201
+ return;
190
202
  default:
191
203
  }
192
204
  if (typeof handleCustomSection === 'function') {