cloudcommerce 0.8.7 → 0.9.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 (158) hide show
  1. package/.github/renovate.json +1 -2
  2. package/CHANGELOG.md +44 -0
  3. package/ecomplus-stores/monocard/functions/core/package.json +1 -1
  4. package/ecomplus-stores/monocard/functions/events/package.json +2 -2
  5. package/ecomplus-stores/monocard/functions/modules/package.json +2 -2
  6. package/ecomplus-stores/monocard/functions/passport/package.json +2 -2
  7. package/ecomplus-stores/monocard/functions/ssr/content/layout.json +37 -0
  8. package/ecomplus-stores/monocard/functions/ssr/package.json +6 -6
  9. package/ecomplus-stores/monocard/functions/ssr/src/components/FeatureTabs.astro +1 -1
  10. package/ecomplus-stores/monocard/functions/ssr/src/components/FeatureTabs.vue +23 -10
  11. package/ecomplus-stores/monocard/functions/ssr/src/components/PitchBar.vue +2 -2
  12. package/ecomplus-stores/monocard/functions/ssr/src/components/ShopHeader.vue +11 -10
  13. package/ecomplus-stores/monocard/functions/ssr/src/components/ShopSidenavCategory.vue +2 -2
  14. package/ecomplus-stores/monocard/functions/ssr/src/layouts/Pages.astro +28 -14
  15. package/ecomplus-stores/monocard/functions/ssr/src/main/Home.astro +3 -3
  16. package/ecomplus-stores/monocard/functions/ssr/src/main/Wildcard.astro +9 -7
  17. package/ecomplus-stores/monocard/functions/ssr/src/pages/[...slug].astro +2 -2
  18. package/ecomplus-stores/monocard/functions/ssr/src/pages/fallback.astro +1 -1
  19. package/ecomplus-stores/monocard/functions/ssr/src/pages/index.astro +2 -2
  20. package/ecomplus-stores/monocard/package.json +1 -1
  21. package/package.json +10 -10
  22. package/packages/api/lib/api.d.ts +27 -11
  23. package/packages/api/package.json +1 -1
  24. package/packages/apps/correios/package.json +3 -3
  25. package/packages/apps/custom-payment/package.json +1 -1
  26. package/packages/apps/custom-shipping/package.json +1 -1
  27. package/packages/apps/datafrete/package.json +3 -3
  28. package/packages/apps/discounts/package.json +1 -1
  29. package/packages/apps/emails/lib/functios-lib/abandoned-carts.js +2 -1
  30. package/packages/apps/emails/lib/functios-lib/abandoned-carts.js.map +1 -1
  31. package/packages/apps/emails/package.json +2 -2
  32. package/packages/apps/emails/src/functios-lib/abandoned-carts.ts +2 -1
  33. package/packages/apps/fb-conversions/package.json +2 -2
  34. package/packages/apps/frenet/lib/functions-lib/tracking-codes.js +2 -1
  35. package/packages/apps/frenet/lib/functions-lib/tracking-codes.js.map +1 -1
  36. package/packages/apps/frenet/package.json +3 -3
  37. package/packages/apps/frenet/src/functions-lib/tracking-codes.ts +2 -1
  38. package/packages/apps/galaxpay/lib/functions-lib/ecom/events-to-galaxpay.js +1 -1
  39. package/packages/apps/galaxpay/lib/functions-lib/ecom/events-to-galaxpay.js.map +1 -1
  40. package/packages/apps/galaxpay/lib/functions-lib/galaxpay/webhook.js +101 -37
  41. package/packages/apps/galaxpay/lib/functions-lib/galaxpay/webhook.js.map +1 -1
  42. package/packages/apps/galaxpay/package.json +3 -3
  43. package/packages/apps/galaxpay/src/functions-lib/ecom/events-to-galaxpay.ts +1 -1
  44. package/packages/apps/galaxpay/src/functions-lib/galaxpay/webhook.ts +123 -39
  45. package/packages/apps/google-analytics/package.json +3 -3
  46. package/packages/apps/infinitepay/package.json +3 -3
  47. package/packages/apps/jadlog/package.json +2 -2
  48. package/packages/apps/loyalty-points/package.json +1 -1
  49. package/packages/apps/melhor-envio/lib/functions-lib/tracking-codes.js +1 -1
  50. package/packages/apps/melhor-envio/lib/functions-lib/tracking-codes.js.map +1 -1
  51. package/packages/apps/melhor-envio/package.json +3 -3
  52. package/packages/apps/melhor-envio/src/functions-lib/tracking-codes.ts +1 -1
  53. package/packages/apps/mercadopago/package.json +3 -3
  54. package/packages/apps/pagarme/package.json +3 -3
  55. package/packages/apps/paghiper/package.json +3 -3
  56. package/packages/apps/pix/package.json +3 -3
  57. package/packages/apps/tiny-erp/package.json +3 -3
  58. package/packages/apps/webhooks/package.json +3 -3
  59. package/packages/cli/package.json +1 -1
  60. package/packages/config/package.json +1 -1
  61. package/packages/emails/package.json +2 -2
  62. package/packages/events/package.json +2 -2
  63. package/packages/firebase/package.json +2 -2
  64. package/packages/i18n/package.json +1 -1
  65. package/packages/modules/package.json +3 -3
  66. package/packages/passport/package.json +2 -2
  67. package/packages/ssr/package.json +5 -5
  68. package/packages/storefront/client.d.ts +1 -1
  69. package/packages/storefront/config/storefront.cms.cjs +17 -12
  70. package/packages/storefront/config/storefront.cms.mjs +17 -12
  71. package/packages/storefront/dist/client/_astro/Carousel.dfb3f26a.js +1 -0
  72. package/packages/storefront/dist/client/_astro/HeroSlider.34436243.js +1 -0
  73. package/packages/storefront/dist/client/_astro/PitchBar.1c993792.js +1 -0
  74. package/packages/storefront/dist/client/_astro/Prices.70885f17.js +1 -0
  75. package/packages/storefront/dist/client/_astro/ShopHeader.e901fad5.js +4 -0
  76. package/packages/storefront/dist/client/_astro/_...slug_.9a31c59e.css +1 -0
  77. package/packages/storefront/dist/client/_astro/_plugin-vue_export-helper.0c2b7f88.js +1 -0
  78. package/packages/storefront/dist/client/_astro/client.4e825332.js +1 -0
  79. package/packages/storefront/dist/client/_astro/{firebase-app.d090c84e.js → firebase-app.247497b8.js} +22 -22
  80. package/packages/storefront/dist/client/_astro/{format-money.ab6b71eb.js → format-money.f09b89ed.js} +1 -1
  81. package/packages/storefront/dist/client/_astro/{hoisted.541b5c05.js → hoisted.56fa2eff.js} +1 -1
  82. package/packages/storefront/dist/client/_astro/index.1eaf97c3.css +1 -0
  83. package/packages/storefront/dist/client/_astro/index.9018a8da.js +1 -0
  84. package/packages/storefront/dist/client/_astro/{modules-info.06ac0727.js → modules-info.2a72e536.js} +1 -1
  85. package/packages/storefront/dist/client/_astro/runtime-dom.esm-bundler.1fd80976.js +1 -0
  86. package/packages/storefront/dist/client/_astro/session-utm.ac492493.js +1 -0
  87. package/packages/storefront/dist/client/img/uploads/ecom-icon.png +0 -0
  88. package/packages/storefront/dist/client/img/uploads/logo.png +0 -0
  89. package/packages/storefront/dist/client/manifest.webmanifest +1 -1
  90. package/packages/storefront/dist/client/sw.js +1 -1
  91. package/packages/storefront/dist/client/workbox-e0d788d4.js +1 -1
  92. package/packages/storefront/dist/server/chunks/{astro.9781c0a7.mjs → astro.39f7f387.mjs} +910 -295
  93. package/packages/storefront/dist/server/chunks/pages/{all.b355675e.mjs → all.b15cad2d.mjs} +408 -246
  94. package/packages/storefront/dist/server/entry.mjs +32 -19
  95. package/packages/storefront/dist/server/manifest.webmanifest +1 -1
  96. package/packages/storefront/package.json +15 -15
  97. package/packages/storefront/server.d.ts +1 -1
  98. package/packages/storefront/src/images/use-ssr-picture.ts +30 -10
  99. package/packages/storefront/src/lib/assets/base.css +4 -2
  100. package/packages/storefront/src/lib/cms.d.ts +14 -18
  101. package/packages/storefront/src/lib/components/Carousel.vue +16 -10
  102. package/packages/storefront/src/lib/components/CarouselControl.vue +2 -2
  103. package/packages/storefront/src/lib/components/Drawer.vue +5 -5
  104. package/packages/storefront/src/lib/components/HeroPicture.astro +54 -0
  105. package/packages/storefront/src/lib/components/LoginForm.vue +3 -2
  106. package/packages/storefront/src/lib/components/SocialNetworkLink.vue +2 -2
  107. package/packages/storefront/src/lib/components/_injection-keys.ts +5 -3
  108. package/packages/storefront/src/lib/components/globals/AImg.vue +4 -5
  109. package/packages/storefront/src/lib/components/globals/ALink.vue +2 -2
  110. package/packages/storefront/src/lib/components/globals/Fade.vue +5 -5
  111. package/packages/storefront/src/lib/composables/use-hero-slider.ts +38 -0
  112. package/packages/storefront/src/lib/composables/use-prices.ts +6 -3
  113. package/packages/storefront/src/lib/composables/use-shop-header.ts +30 -14
  114. package/packages/storefront/src/lib/composables/use-sticky-header.ts +15 -12
  115. package/packages/storefront/src/lib/layouts/Base.astro +1 -1
  116. package/packages/storefront/src/lib/layouts/BaseBody.astro +2 -2
  117. package/packages/storefront/src/lib/layouts/BaseHead.astro +2 -2
  118. package/packages/storefront/src/lib/layouts/sections/use-hero-section.ts +48 -0
  119. package/packages/storefront/src/lib/layouts/use-home-main.ts +16 -0
  120. package/packages/storefront/src/lib/layouts/use-page-layout.ts +41 -0
  121. package/packages/storefront/src/lib/pages/_vue.ts +1 -1
  122. package/packages/storefront/src/lib/scripts/modules-info-preset.ts +5 -2
  123. package/packages/storefront/src/lib/scripts/session-utm.ts +6 -1
  124. package/packages/storefront/src/lib/ssr-context.ts +24 -15
  125. package/packages/storefront/src/lib/state/customer-session.ts +14 -2
  126. package/packages/storefront/src/lib/state/shopping-cart/add-cart-item.ts +8 -10
  127. package/packages/storefront/src/lib/state/shopping-cart.ts +4 -3
  128. package/packages/storefront/src/vue-globals.d.ts +3 -2
  129. package/packages/storefront/tailwind.config.cjs +10 -3
  130. package/packages/storefront/tsconfig.json +3 -1
  131. package/packages/storefront/uno.config.cjs +8 -4
  132. package/packages/types/package.json +1 -1
  133. package/ecomplus-stores/monocard/functions/ssr/content/code.json +0 -5
  134. package/ecomplus-stores/monocard/functions/ssr/content/footer.json +0 -46
  135. package/ecomplus-stores/monocard/functions/ssr/content/header.json +0 -23
  136. package/ecomplus-stores/monocard/functions/ssr/content/menu.json +0 -6
  137. package/ecomplus-stores/monocard/functions/ssr/content/metatags.json +0 -5
  138. package/ecomplus-stores/monocard/functions/ssr/src/components/header/HeaderButtonLink.vue +0 -5
  139. package/ecomplus-stores/monocard/functions/ssr/src/components/header/HeaderButtons.vue +0 -39
  140. package/ecomplus-stores/monocard/functions/ssr/src/components/header/HeaderNav.vue +0 -61
  141. package/ecomplus-stores/monocard/functions/ssr/src/components/header/HeaderNavLink.vue +0 -5
  142. package/packages/storefront/dist/client/_astro/PitchBar.5d55c359.js +0 -1
  143. package/packages/storefront/dist/client/_astro/Prices.55399c72.js +0 -1
  144. package/packages/storefront/dist/client/_astro/ProductCard.7e891c08.js +0 -1
  145. package/packages/storefront/dist/client/_astro/ShopHeader.6bbe1a17.js +0 -4
  146. package/packages/storefront/dist/client/_astro/_...slug_.45f0edaf.css +0 -1
  147. package/packages/storefront/dist/client/_astro/_plugin-vue_export-helper.77ed7c18.js +0 -1
  148. package/packages/storefront/dist/client/_astro/client.0de2f274.js +0 -1
  149. package/packages/storefront/dist/client/_astro/index.ff4f0b30.js +0 -1
  150. package/packages/storefront/dist/client/_astro/runtime-dom.esm-bundler.d2f39f33.js +0 -1
  151. package/packages/storefront/dist/client/_astro/session-utm.72684b84.js +0 -1
  152. package/packages/storefront/dist/client/img/uploads/icon.png +0 -0
  153. package/packages/storefront/dist/client/img/uploads/large-icon.png +0 -0
  154. package/packages/storefront/dist/client/img/uploads/logo.webp +0 -0
  155. package/packages/storefront/src/lib/components/ProductCard.vue +0 -36
  156. package/packages/storefront/src/lib/layouts/PagesHeader.astro +0 -51
  157. /package/packages/storefront/dist/client/_astro/{index.0c833781.css → index.e56fc6b3.css} +0 -0
  158. /package/packages/storefront/dist/client/_astro/{server.1bc2fa51.css → server.1dabec03.css} +0 -0
@@ -95,10 +95,10 @@ const onEnter = (el: HTMLElement) => {
95
95
  el.style.visibility = 'hidden';
96
96
  // eslint-disable-next-line no-shadow
97
97
  const { width, height } = getComputedStyle(el);
98
- el.style.width = isSlideY.value ? null : '0';
99
- el.style.height = isSlideY.value ? '0' : null;
100
- el.style.position = null;
101
- el.style.visibility = null;
98
+ el.style.width = isSlideY.value ? '' : '0';
99
+ el.style.height = isSlideY.value ? '0' : '';
100
+ el.style.position = '';
101
+ el.style.visibility = '';
102
102
  // Force repaint to make sure the animation is triggered correctly
103
103
  // eslint-disable-next-line no-unused-expressions
104
104
  getComputedStyle(el)[isSlideY.value ? 'height' : 'width'];
@@ -113,7 +113,7 @@ const onEnter = (el: HTMLElement) => {
113
113
  };
114
114
  const onAfterEnter = (el: HTMLElement) => {
115
115
  if (props.slide && !props.isFloating && props.isEnterFrom) {
116
- el.style[isSlideY.value ? 'height' : 'width'] = null;
116
+ el.style[isSlideY.value ? 'height' : 'width'] = '';
117
117
  }
118
118
  };
119
119
  const onLeave = (el: HTMLElement) => {
@@ -0,0 +1,38 @@
1
+ import { computed } from 'vue';
2
+ import { parseShippingPhrase } from '@@sf/state/modules-info';
3
+
4
+ export interface Props {
5
+ autoplay?: number;
6
+ slides: Array<{
7
+ img?: string;
8
+ alt?: string;
9
+ mobileImg?: string;
10
+ href?: string;
11
+ title?: string;
12
+ subtitle?: string;
13
+ buttonLink?: string;
14
+ buttonText?: string;
15
+ }>;
16
+ }
17
+
18
+ const useHeroSlider = (props: Props) => {
19
+ const parsedSlides = computed(() => {
20
+ return props.slides.map((slide) => {
21
+ const title = slide.title
22
+ ? parseShippingPhrase(slide.title).value : '';
23
+ const subtitle = slide.subtitle
24
+ ? parseShippingPhrase(slide.subtitle).value : '';
25
+ const buttonText = slide.buttonText
26
+ ? parseShippingPhrase(slide.buttonText).value : '';
27
+ return {
28
+ ...slide,
29
+ hasHeader: Boolean(title || subtitle || buttonText),
30
+ };
31
+ });
32
+ });
33
+ return {
34
+ parsedSlides,
35
+ };
36
+ };
37
+
38
+ export default useHeroSlider;
@@ -10,10 +10,13 @@ export interface Props {
10
10
  isAmountTotal?: boolean;
11
11
  installmentsOption?: ListPaymentsResponse['installments_option'];
12
12
  discountOption?: ListPaymentsResponse['discount_option'];
13
- loyaltyPointsProgram?: ListPaymentsResponse['loyalty_points_programs']['k'];
13
+ loyaltyPointsProgram?: Exclude<ListPaymentsResponse['loyalty_points_programs'], undefined>['k'];
14
14
  }
15
15
 
16
- const getPriceWithDiscount = (price: number, discount: Props['discountOption']) => {
16
+ const getPriceWithDiscount = (
17
+ price: number,
18
+ discount: Exclude<Props['discountOption'], undefined>,
19
+ ) => {
17
20
  const { type, value } = discount;
18
21
  let priceWithDiscount: number;
19
22
  if (value) {
@@ -134,7 +137,7 @@ const usePrices = (props: Props) => {
134
137
  const programIds = Object.keys(pointsPrograms);
135
138
  for (let i = 0; i < programIds.length; i++) {
136
139
  const program = pointsPrograms[programIds[i]];
137
- if (program && program.earn_percentage > 0) {
140
+ if (program?.earn_percentage && program.earn_percentage > 0) {
138
141
  return program;
139
142
  }
140
143
  }
@@ -1,27 +1,44 @@
1
1
  import type { Ref } from 'vue';
2
- import type { CategoriesList } from '@cloudcommerce/api/types';
2
+ import type { ResourceId, Categories, CategoriesList } from '@cloudcommerce/api/types';
3
3
  import { computed } from 'vue';
4
4
  import useStickyHeader from '@@sf/composables/use-sticky-header';
5
5
 
6
6
  export interface Props {
7
- header: Ref<HTMLElement>;
7
+ header: Ref<HTMLElement | null>;
8
8
  categories: CategoriesList;
9
9
  menuCategorySlugs?: string[];
10
10
  menuRandomCategories?: number;
11
11
  isAlphabeticalSortSubmenu?: boolean;
12
12
  }
13
13
 
14
- type CategoryTree = CategoriesList[0] & {
15
- subcategories: Array<CategoryTree>,
14
+ type MainCategory = Partial<Categories> & {
15
+ _id: ResourceId,
16
+ name: string,
17
+ slug: string,
18
+ parent: undefined,
19
+ };
20
+ type Subcategory = Partial<Categories> & {
21
+ _id: ResourceId,
22
+ name: string,
23
+ slug: string,
24
+ parent: Exclude<Categories['parent'], undefined>,
25
+ };
26
+
27
+ export type SubcategoryTree = Subcategory & {
28
+ subcategories: Array<SubcategoryTree>,
29
+ };
30
+
31
+ export type CategoryTree = MainCategory & {
32
+ subcategories: Array<SubcategoryTree>,
16
33
  };
17
34
 
18
35
  const filterMainCategories = (
19
36
  categories: CategoriesList,
20
37
  featuredSlugs?: string[],
21
38
  ) => {
22
- const mainCategories = categories.filter(({ slug, parent }) => {
23
- return slug && !parent;
24
- });
39
+ const mainCategories = categories.filter(({ name, slug, parent }) => {
40
+ return name && slug && !parent;
41
+ }) as Array<MainCategory>;
25
42
  if (featuredSlugs?.length) {
26
43
  mainCategories.sort((a, b) => {
27
44
  const indexA = featuredSlugs.indexOf(a.slug);
@@ -42,13 +59,13 @@ const filterSubcategories = (
42
59
  parentCategory: CategoriesList[0],
43
60
  isAlphabeticalSort = false,
44
61
  ) => {
45
- const subcategories = categories.filter(({ slug, parent }) => {
46
- if (slug && parent) {
62
+ const subcategories = categories.filter(({ name, slug, parent }) => {
63
+ if (name && slug && parent) {
47
64
  return parent._id === parentCategory._id
48
65
  || (parent.slug && parent.slug === parentCategory.slug);
49
66
  }
50
67
  return false;
51
- });
68
+ }) as Array<Subcategory>;
52
69
  if (isAlphabeticalSort) {
53
70
  subcategories.sort((a, b) => {
54
71
  if (a.name < b.name) return -1;
@@ -71,7 +88,7 @@ const useShopHeader = ({
71
88
  staticY,
72
89
  } = useStickyHeader({ header });
73
90
  const positionY = computed(() => {
74
- return isSticky.value ? header.value.offsetHeight : staticY.value;
91
+ return isSticky.value ? header.value?.offsetHeight : staticY.value;
75
92
  });
76
93
  const mainCategories = filterMainCategories(categories, menuCategorySlugs);
77
94
  const getSubcategories = (parentCategory: CategoriesList[0]) => {
@@ -81,7 +98,8 @@ const useShopHeader = ({
81
98
  !!isAlphabeticalSortSubmenu,
82
99
  );
83
100
  };
84
- const getCategoryTree = (parentCategory: CategoriesList[0]): CategoryTree => {
101
+ const getCategoryTree = <T extends CategoriesList[0]>(parentCategory: T):
102
+ T & { subcategories: SubcategoryTree[] } => {
85
103
  return {
86
104
  ...parentCategory,
87
105
  subcategories: getSubcategories(parentCategory).map((subcategory) => {
@@ -121,5 +139,3 @@ export {
121
139
  filterMainCategories,
122
140
  filterSubcategories,
123
141
  };
124
-
125
- export type { CategoryTree };
@@ -13,7 +13,7 @@ import {
13
13
  } from '@vueuse/core';
14
14
 
15
15
  export interface Props {
16
- header: Ref<HTMLElement>;
16
+ header: Ref<HTMLElement | null>;
17
17
  canSetStyles?: boolean;
18
18
  canCreateHeightDiv?: boolean;
19
19
  isShownOnScrollDown?: boolean;
@@ -38,11 +38,11 @@ const useStickyHeader = (props: Props) => {
38
38
  const isSticky = computed(() => ready.value && y.value > staticY.value * 1.2);
39
39
  const transition = ref('none');
40
40
  watch(isSticky, async (_isSticky) => {
41
- if (canSetStyles) {
42
- header.value.style.position = _isSticky ? 'sticky' : null;
41
+ if (canSetStyles && header.value) {
42
+ header.value.style.position = _isSticky ? 'sticky' : '';
43
43
  }
44
44
  if (heightDiv) {
45
- heightDiv.style.height = _isSticky ? `${staticHeight.value}px` : null;
45
+ heightDiv.style.height = _isSticky ? `${staticHeight.value}px` : '';
46
46
  }
47
47
  if (!_isSticky) {
48
48
  start();
@@ -58,10 +58,12 @@ const useStickyHeader = (props: Props) => {
58
58
  });
59
59
  if (!import.meta.env.SSR) {
60
60
  onMounted(() => {
61
+ if (!header.value) throw new Error('<header> (props.header) not set');
61
62
  const fixHeight = () => {
62
- staticHeight.value = header.value.offsetHeight;
63
+ const headerElm = header.value as HTMLElement;
64
+ staticHeight.value = headerElm.offsetHeight;
63
65
  staticY.value = staticHeight.value
64
- + window.pageYOffset + header.value.getBoundingClientRect().top;
66
+ + window.pageYOffset + headerElm.getBoundingClientRect().top;
65
67
  start();
66
68
  };
67
69
  const imgs = header.value.getElementsByTagName('IMG');
@@ -80,22 +82,23 @@ const useStickyHeader = (props: Props) => {
80
82
  if (canSetStyles) {
81
83
  header.value.style.willChange = 'transform';
82
84
  watch([isSticky, isScrollUp], ([_isSticky, _isScrollUp]) => {
83
- let opacity: string | null = null;
84
- let transform: string | null = null;
85
+ let opacity: string = '';
86
+ let transform: string = '';
85
87
  if (_isSticky && (!_isScrollUp || isShownOnScrollDown)) {
86
88
  opacity = '0';
87
89
  transform = 'translateY(-100%)';
88
90
  }
89
- header.value.style.opacity = opacity;
90
- header.value.style.transform = transform;
91
+ const headerElm = header.value as HTMLElement;
92
+ headerElm.style.opacity = opacity;
93
+ headerElm.style.transform = transform;
91
94
  });
92
95
  watch(transition, (_transition) => {
93
- header.value.style.transition = _transition;
96
+ (header.value as HTMLElement).style.transition = _transition;
94
97
  });
95
98
  }
96
99
  if (canCreateHeightDiv) {
97
100
  heightDiv = document.createElement('div');
98
- header.value.parentElement.insertBefore(heightDiv, header.value);
101
+ header.value.parentElement?.insertBefore(heightDiv, header.value);
99
102
  }
100
103
  });
101
104
  }
@@ -11,7 +11,7 @@ export interface Props {
11
11
 
12
12
  const { pageContext, title } = Astro.props as Props;
13
13
  const { cms } = pageContext;
14
- const cmsCustomCode = await cms('code');
14
+ const { custom_code: cmsCustomCode } = await cms('layout');
15
15
  ---
16
16
 
17
17
  <head>
@@ -16,10 +16,10 @@ const { settings } = Astro.props.pageContext;
16
16
 
17
17
  <body>
18
18
  <div id="teleported-overlap" class="relative z-50"></div>
19
- <div id="teleported-top" class="relative z-0"></div>
19
+ <div id="teleported-top" class="relative z-10"></div>
20
20
  <slot />
21
21
  <slot name="before-body-end" />
22
- <div id="teleported-bottom" class="relative z-0"></div>
22
+ <div id="teleported-bottom" class="relative z-10"></div>
23
23
  {settings.icon &&
24
24
  <Picture
25
25
  src={settings.icon}
@@ -31,11 +31,11 @@ const description = state.meta_description || state.short_description || setting
31
31
  const favicon = settings.icon ? getIconUrl(32) : '/favicon.ico';
32
32
  const shortcutIcon = settings.icon ? getIconUrl(192) : null;
33
33
  const canonicalUrl = new URL(Astro.url.pathname, Astro.site || `https://${domain}`);
34
- const cmsMetatags = await cms('metatags');
34
+ const { metatags: cmsMetatags } = await cms('layout');
35
35
  const ogLocale = lang.length === 2 ? lang : lang.substring(0, 2) + lang.slice(3).toUpperCase();
36
36
  let ogImage: string | undefined;
37
37
  if (apiDoc) {
38
- const picture = getImg(state, null, 'zoom');
38
+ const picture = getImg(state, undefined, 'zoom');
39
39
  ogImage = picture && picture.url;
40
40
  }
41
41
  if (!ogImage) {
@@ -0,0 +1,48 @@
1
+ import type { PageContext } from '@@sf/ssr-context';
2
+ import type { CmsHome } from '@@sf/cms';
3
+ import type { Props as UseHeroSliderProps } from '@@sf/composables/use-hero-slider';
4
+
5
+ export type HeroSliderProps = Omit<UseHeroSliderProps, 'slides'> & {
6
+ slides: Array<Omit<UseHeroSliderProps['slides'][0], 'img'> & { img: string }>,
7
+ };
8
+
9
+ export interface Props {
10
+ pageContext: PageContext;
11
+ }
12
+
13
+ const useHeroSection = async ({ pageContext }: Props) => {
14
+ const { cmsContent } = pageContext;
15
+ const heroSlider: HeroSliderProps = { slides: [] };
16
+ const cmsHero: CmsHome['hero'] | undefined = cmsContent?.hero;
17
+ if (cmsHero) {
18
+ heroSlider.autoplay = cmsHero.autoplay;
19
+ const now = Date.now();
20
+ cmsHero.slides?.forEach(({
21
+ img,
22
+ start,
23
+ end,
24
+ mobile_img: mobileImg,
25
+ button_link: buttonLink,
26
+ button_text: buttonText,
27
+ ...rest
28
+ }) => {
29
+ if (!img) return;
30
+ if (start && new Date(start).getTime() < now) return;
31
+ if (end && new Date(end).getTime() > now) return;
32
+ heroSlider.slides.push({
33
+ ...rest,
34
+ img,
35
+ mobileImg,
36
+ buttonLink,
37
+ buttonText,
38
+ });
39
+ });
40
+ }
41
+ return {
42
+ heroSlider,
43
+ };
44
+ };
45
+
46
+ export default useHeroSection;
47
+
48
+ export { useHeroSection };
@@ -0,0 +1,16 @@
1
+ import type { PageContext } from '@@sf/ssr-context';
2
+ import useHeroSection from '@@sf/layouts/sections/use-hero-section';
3
+
4
+ export interface Props {
5
+ pageContext: PageContext;
6
+ }
7
+
8
+ const useHomeMain = async ({ pageContext }: Props) => {
9
+ return {
10
+ ...(await useHeroSection({ pageContext })),
11
+ };
12
+ };
13
+
14
+ export default useHomeMain;
15
+
16
+ export { useHomeMain };
@@ -0,0 +1,41 @@
1
+ import type { PageContext } from '@@sf/ssr-context';
2
+ import type { CmsLayout } from '@@sf/cms';
3
+ import type { Props as PitchBarProps } from '@@sf/composables/use-pitch-bar';
4
+ import type { Props as UseShopHeaderProps } from '@@sf/composables/use-shop-header';
5
+
6
+ type ShopHeaderProps = Omit<UseShopHeaderProps, 'header'> & {
7
+ serviceLinks?: CmsLayout['service_links'],
8
+ };
9
+
10
+ export interface Props {
11
+ pageContext: PageContext;
12
+ }
13
+
14
+ const usePageLayout = async ({ pageContext }: Props) => {
15
+ const { apiState, cms } = pageContext;
16
+ const {
17
+ header: cmsHeader,
18
+ service_links: cmsServiceLinks,
19
+ } = await cms('layout');
20
+ const pitchBar: PitchBarProps = { slides: [] };
21
+ if (cmsHeader?.pitch_bar) {
22
+ pitchBar.slides = cmsHeader.pitch_bar;
23
+ }
24
+ const shopHeader: ShopHeaderProps = {
25
+ categories: apiState.categories || [],
26
+ menuCategorySlugs: cmsHeader.inline_menu_categories?.featured,
27
+ menuRandomCategories: cmsHeader.inline_menu_categories?.random,
28
+ isAlphabeticalSortSubmenu: cmsHeader.alphabetical_sort_submenu,
29
+ serviceLinks: cmsServiceLinks,
30
+ };
31
+ return {
32
+ pitchBar,
33
+ shopHeader,
34
+ };
35
+ };
36
+
37
+ export default usePageLayout;
38
+
39
+ export { usePageLayout };
40
+
41
+ export type { PitchBarProps, ShopHeaderProps };
@@ -11,7 +11,7 @@ const formatPercentage = (value: number, digits = 1) => {
11
11
  const createApp = (app: App) => {
12
12
  app.use({
13
13
  // eslint-disable-next-line no-shadow
14
- install: (app: App, options: Record<string, any>) => {
14
+ install: (app: App, options?: Record<string, any>) => {
15
15
  // @ts-ignore
16
16
  app.config.globalProperties.$t = (dict, lang) => {
17
17
  // @ts-ignore
@@ -37,8 +37,11 @@ const getModulesInfoPreset = (settingsModules = globalThis.storefront.settings.m
37
37
  }
38
38
 
39
39
  const settingsShipping = settingsModules.calculate_shipping;
40
- if (settingsShipping && settingsShipping.free_shipping_from_value) {
41
- modulesInfoPreset.calculate_shipping = settingsShipping;
40
+ const freeShippingFromValue = settingsShipping?.free_shipping_from_value;
41
+ if (typeof freeShippingFromValue === 'number') {
42
+ modulesInfoPreset.calculate_shipping = {
43
+ free_shipping_from_value: freeShippingFromValue,
44
+ };
42
45
  }
43
46
  }
44
47
  return modulesInfoPreset;
@@ -3,7 +3,12 @@ let utm: { [k: string]: string } = {};
3
3
 
4
4
  if (!import.meta.env.SSR) {
5
5
  const storageKey = 'ecomUtm';
6
- utm = JSON.parse(sessionStorage.getItem(storageKey)) || {};
6
+ const storedValue = sessionStorage.getItem(storageKey);
7
+ try {
8
+ utm = (storedValue && JSON.parse(storedValue)) || {};
9
+ } catch {
10
+ utm = {};
11
+ }
7
12
 
8
13
  let isCurrentUrl;
9
14
  const urlParams = new URLSearchParams(window.location.search);
@@ -2,9 +2,10 @@ import type { AstroGlobal } from 'astro';
2
2
  import type { BaseConfig } from '@cloudcommerce/config';
3
3
  import type { ApiError, ApiEndpoint } from '@cloudcommerce/api';
4
4
  import type { CategoriesList, BrandsList } from '@cloudcommerce/api/types';
5
- import type { CMS, CmsSettings } from './cms';
5
+ import type { CMS, CmsSettings, CmsHome } from './cms';
6
6
  import { EventEmitter } from 'node:events';
7
7
  import api from '@cloudcommerce/api';
8
+ // @ts-ignore
8
9
  import _getConfig from '../../storefront.config.mjs';
9
10
 
10
11
  type StorefrontConfig = {
@@ -17,8 +18,6 @@ type StorefrontConfig = {
17
18
  primaryColor: CmsSettings['primary_color'],
18
19
  secondaryColor: CmsSettings['secondary_color'],
19
20
  settings: CmsSettings,
20
- dirContent: string,
21
- // eslint-disable-next-line no-unused-vars
22
21
  cms: CMS,
23
22
  };
24
23
 
@@ -60,16 +59,15 @@ const loadPageContext = async (Astro: Readonly<AstroGlobal>, {
60
59
  const startedAt = Date.now();
61
60
  const urlPath = Astro.url.pathname;
62
61
  const isHomepage = urlPath === '/';
63
- const { slug } = Astro.params;
64
62
  const config = getConfig();
65
63
  globalThis.storefront.settings = config.settings;
66
- let cmsContent: Record<string, any> | undefined;
64
+ let cmsContent: CmsHome | Record<string, any> | null | undefined;
67
65
  let apiResource: 'products' | 'categories' | 'brands' | 'collections' | undefined;
68
66
  let apiDoc: Record<string, any> | undefined;
69
67
  const apiState: {
70
68
  categories?: CategoriesList,
71
69
  brands?: BrandsList,
72
- [k: string]: Record<string, any>,
70
+ [k: string]: Record<string, any> | undefined,
73
71
  } = {};
74
72
  const apiOptions = {
75
73
  fetch,
@@ -79,11 +77,16 @@ const loadPageContext = async (Astro: Readonly<AstroGlobal>, {
79
77
  null, // fetch by slug
80
78
  ...apiPrefetchEndpoints.map((endpoint) => api.get(endpoint, apiOptions)),
81
79
  ];
82
- if (slug) {
83
- if (cmsCollection) {
84
- cmsContent = await config.cms(`${cmsCollection}/${slug}`);
85
- } else {
86
- apiFetchings[0] = api.get(`slugs/${slug}`, apiOptions);
80
+ if (isHomepage) {
81
+ cmsContent = await config.cms('home');
82
+ } else {
83
+ const { slug } = Astro.params;
84
+ if (slug) {
85
+ if (cmsCollection) {
86
+ cmsContent = await config.cms(`${cmsCollection}/${slug}`);
87
+ } else {
88
+ apiFetchings[0] = api.get(`slugs/${slug}`, apiOptions);
89
+ }
87
90
  }
88
91
  }
89
92
  try {
@@ -91,10 +94,15 @@ const loadPageContext = async (Astro: Readonly<AstroGlobal>, {
91
94
  if (slugResponse) {
92
95
  apiResource = slugResponse.data.resource;
93
96
  apiDoc = slugResponse.data.doc;
94
- apiState[`${apiResource}/${apiDoc._id}`] = apiDoc;
97
+ if (apiDoc) {
98
+ apiState[`${apiResource}/${apiDoc._id}`] = apiDoc;
99
+ }
95
100
  }
96
- prefetchResponses.forEach(({ config: { endpoint }, data }) => {
97
- apiState[endpoint.replace(/\?.*$/, '')] = data.result || data;
101
+ prefetchResponses.forEach((response) => {
102
+ if (response) {
103
+ const { config: { endpoint }, data } = response;
104
+ apiState[endpoint.replace(/\?.*$/, '')] = data.result || data;
105
+ }
98
106
  });
99
107
  } catch (err: any) {
100
108
  const error: ApiError = err;
@@ -129,7 +137,7 @@ const loadPageContext = async (Astro: Readonly<AstroGlobal>, {
129
137
  }
130
138
  if (apiDoc) {
131
139
  globalThis.storefront.context = {
132
- resource: apiResource,
140
+ resource: apiResource as Exclude<typeof apiResource, undefined>,
133
141
  doc: apiDoc as any,
134
142
  timestamp: Date.now(),
135
143
  };
@@ -141,6 +149,7 @@ const loadPageContext = async (Astro: Readonly<AstroGlobal>, {
141
149
  apiResource,
142
150
  apiDoc,
143
151
  apiState,
152
+ getContent: config.cms,
144
153
  };
145
154
  emitter.emit('load', pageContext);
146
155
  return pageContext;
@@ -49,8 +49,18 @@ const logout = () => {
49
49
  firebaseAuth.signOut();
50
50
  };
51
51
 
52
+ const throwNoAuth = (msg = 'Not authenticated') => {
53
+ const err: any = new Error(msg);
54
+ err.isNoAuth = true;
55
+ throw err;
56
+ };
57
+
52
58
  const authenticate = async () => {
53
- const authToken = await firebaseAuth.currentUser.getIdToken();
59
+ const authToken = await firebaseAuth.currentUser?.getIdToken();
60
+ if (!authToken) {
61
+ throwNoAuth('Can\'t get Firebase user ID token');
62
+ return;
63
+ }
54
64
  const { domain } = window.storefront.settings;
55
65
  try {
56
66
  const resAuth = await fetch(`https://${domain}/api/passport/token`, {
@@ -70,12 +80,14 @@ const getAccessToken = async () => {
70
80
  if (!isAuthenticated.value) {
71
81
  await authenticate();
72
82
  }
83
+ if (!session.auth) return throwNoAuth();
73
84
  return session.auth.access_token;
74
85
  };
75
86
 
76
87
  const fetchCustomer = async () => {
77
88
  const accessToken = await getAccessToken();
78
- const { data } = await api.get(`customers/${session.auth.customer_id}`, {
89
+ const auth = session.auth as Exclude<typeof session.auth, null>;
90
+ const { data } = await api.get(`customers/${auth.customer_id}`, {
79
91
  accessToken,
80
92
  });
81
93
  session.customer = data;
@@ -3,18 +3,14 @@ import { randomObjectId } from '@ecomplus/utils';
3
3
 
4
4
  type CartItem = CartSet['items'][0];
5
5
 
6
- const matchItemsFlags = (item: CartItem, newItem: CartItem) => {
7
- if (!item.flags && !newItem.flags) {
6
+ const matchItemsFlags = (item: CartItem, { flags: newItemFlags }: CartItem) => {
7
+ if (!item.flags && !newItemFlags) {
8
8
  return true;
9
9
  }
10
- if (
11
- !item.flags
12
- || !newItem.flags
13
- || item.flags.length !== newItem.flags.length
14
- ) {
10
+ if (!item.flags || !newItemFlags || item.flags.length !== newItemFlags.length) {
15
11
  return false;
16
12
  }
17
- return item.flags.every((flag) => newItem.flags.includes(flag));
13
+ return item.flags.every((flag) => newItemFlags.includes(flag));
18
14
  };
19
15
 
20
16
  const addCartItem = (cart: CartSet, newItem: CartItem): null | CartItem => {
@@ -47,7 +43,7 @@ const addCartItem = (cart: CartSet, newItem: CartItem): null | CartItem => {
47
43
  }
48
44
  }
49
45
  }
50
- const itemCopy = { ...newItem };
46
+ const itemCopy: CartItem = { ...newItem };
51
47
  if (
52
48
  !newItem._id
53
49
  || newItem._id === newItem.variation_id
@@ -56,9 +52,11 @@ const addCartItem = (cart: CartSet, newItem: CartItem): null | CartItem => {
56
52
  itemCopy._id = randomObjectId();
57
53
  }
58
54
  if (newItem.customizations) {
55
+ const customizationsCopy: Exclude<CartItem['customizations'], undefined> = [];
59
56
  newItem.customizations.forEach((customization, i) => {
60
- itemCopy.customizations[i] = { ...customization };
57
+ customizationsCopy[i] = { ...customization };
61
58
  });
59
+ itemCopy.customizations = customizationsCopy;
62
60
  }
63
61
  items.push(itemCopy);
64
62
  return itemCopy;
@@ -12,19 +12,20 @@ const cart = useStorage<CartSet>(storageKey, emptyCart);
12
12
 
13
13
  const cartItems = computed(() => {
14
14
  return cart.items.map((item) => {
15
- item.final_price = item.kit_product?.price && item.kit_product.pack_quantity
15
+ let finalPrice = item.kit_product?.price && item.kit_product.pack_quantity
16
16
  ? item.kit_product.price / item.kit_product.pack_quantity
17
17
  : item.price;
18
18
  if (Array.isArray(item.customizations)) {
19
19
  item.customizations.forEach((customization) => {
20
20
  if (customization.add_to_price) {
21
21
  const { type, addition } = customization.add_to_price;
22
- item.final_price += type === 'fixed'
22
+ finalPrice += type === 'fixed'
23
23
  ? addition
24
24
  : item.price * (addition / 100);
25
25
  }
26
26
  });
27
27
  }
28
+ item.final_price = finalPrice;
28
29
  const min = item.min_quantity || 1;
29
30
  const max = item.max_quantity;
30
31
  if (
@@ -41,7 +42,7 @@ const cartItems = computed(() => {
41
42
  });
42
43
  const subtotal = computed(() => {
43
44
  return cartItems.value.reduce((acc, item) => {
44
- return acc + (item.quantity * item.final_price);
45
+ return acc + (item.quantity * (item.final_price || item.price));
45
46
  }, 0);
46
47
  });
47
48
  const shoppingCart = computed({
@@ -2,6 +2,7 @@
2
2
  // eslint-disable-next-line import/no-extraneous-dependencies
3
3
  import '@vue/runtime-core';
4
4
  import type { FormatPercentage } from '@@sf/pages/_vue';
5
+ import type { CmsSettings } from '@@sf/cms';
5
6
 
6
7
  type Dictionary = Omit<typeof import('@@i18n'),
7
8
  'i19StoreApiResources' | 'i19ApiActions' | 'i19TransactionsType' | 'i19StateRegister' |
@@ -18,8 +19,8 @@ declare module '@vue/runtime-core' {
18
19
  };
19
20
  $money: typeof import('@ecomplus/utils')['formatMoney'];
20
21
  $percentage: FormatPercentage;
21
- $settings: typeof globalThis.storefront.settings;
22
- $context: typeof globalThis.storefront.context;
22
+ $settings: Partial<CmsSettings>;
23
+ $context: typeof window.storefront.context;
23
24
  }
24
25
 
25
26
  export interface GlobalComponents {