@gem-sdk/components 2.1.20 → 2.1.21

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 (24) hide show
  1. package/dist/cjs/marquee/components/MarqueeBase.liquid.js +7 -26
  2. package/dist/cjs/post-purchase/product/components/ProductDiscountTag.js +5 -2
  3. package/dist/cjs/post-purchase/product/components/ProductOffer.js +16 -3
  4. package/dist/cjs/post-purchase/product/components/ProductPrice.js +3 -1
  5. package/dist/cjs/post-purchase/product/components/ProductPriceBreakdown.js +9 -21
  6. package/dist/cjs/post-purchase/product/constants/ProductOfferPlaceholder.js +127 -0
  7. package/dist/cjs/post-purchase/product/hooks/useDynamicProduct.js +28 -0
  8. package/dist/cjs/post-purchase/product/settings/ProductOffer.js +26 -0
  9. package/dist/cjs/product/components/ProductImagesV2/jsx/components/ProductFeaturedImageCarousel.js +12 -4
  10. package/dist/cjs/third-party/components/NotifyBackInStockPreOrder.js +1 -1
  11. package/dist/cjs/third-party/setting/NotifyBackInStockPreOrder.js +2 -2
  12. package/dist/esm/marquee/components/MarqueeBase.liquid.js +7 -26
  13. package/dist/esm/post-purchase/product/components/ProductDiscountTag.js +5 -2
  14. package/dist/esm/post-purchase/product/components/ProductOffer.js +16 -3
  15. package/dist/esm/post-purchase/product/components/ProductPrice.js +3 -1
  16. package/dist/esm/post-purchase/product/components/ProductPriceBreakdown.js +9 -21
  17. package/dist/esm/post-purchase/product/constants/ProductOfferPlaceholder.js +125 -0
  18. package/dist/esm/post-purchase/product/hooks/useDynamicProduct.js +26 -0
  19. package/dist/esm/post-purchase/product/settings/ProductOffer.js +26 -0
  20. package/dist/esm/product/components/ProductImagesV2/jsx/components/ProductFeaturedImageCarousel.js +13 -5
  21. package/dist/esm/third-party/components/NotifyBackInStockPreOrder.js +1 -1
  22. package/dist/esm/third-party/setting/NotifyBackInStockPreOrder.js +2 -2
  23. package/dist/types/index.d.ts +1 -0
  24. package/package.json +2 -2
@@ -46,7 +46,6 @@ const MarqueeBase = ({ style = {}, autoFill = false, play = true, pauseOnHover =
46
46
  };
47
47
  return core.template`
48
48
  <div
49
-
50
49
  style="${containerStyle()}"
51
50
  class="rfm-marquee-container"
52
51
  >
@@ -55,35 +54,17 @@ const MarqueeBase = ({ style = {}, autoFill = false, play = true, pauseOnHover =
55
54
  style="${marqueeStyle()}"
56
55
  >
57
56
  <div class="rfm-initial-child-container">
58
-
59
- ${core.template`
60
- <div style="${childStyle()}" class="${`rfm-child`}">
61
- ${children}
62
- </div>
63
- `}
64
- </div>
65
- <div class="rfm-child-placeholder">
66
- ${core.template`
67
- <div style="${childStyle()}" class="${`rfm-child`}">
68
- ${children}
69
- </div>
70
- `}
71
- </div>
72
- <div class="rfm-child-placeholder">
73
-
74
- ${core.template`
75
- <div style="${childStyle()}" class="${`rfm-child`}">
76
- ${children}
77
- </div>
78
- `}
57
+ ${core.template`
58
+ <div style="${childStyle()}" class="${`rfm-child`}">
59
+ ${children}
60
+ </div>
61
+ `}
79
62
  </div>
80
63
  </div>
81
- <div
64
+ <div
82
65
  class="rfm-marquee placeholder-marquee"
83
66
  style="${marqueeStyle()}"
84
- >
85
-
86
- </div>
67
+ ></div>
87
68
  </div>
88
69
  `;
89
70
  };
@@ -5,26 +5,29 @@ Object.defineProperty(exports, '__esModule', { value: true });
5
5
  var jsxRuntime = require('react/jsx-runtime');
6
6
  var core = require('@gem-sdk/core');
7
7
  var Text = require('../../text/Text.js');
8
+ var useDynamicProduct = require('../hooks/useDynamicProduct.js');
8
9
 
9
10
  const PostPurchaseProductDiscountTag = ({ setting = {}, styles = {}, style, builderAttrs, builderProps, children })=>{
10
11
  const currentProduct = core.useProduct();
11
12
  const currentProductId = currentProduct?.baseID?.replace('gid://shopify/Product/', '');
12
13
  const currentOfferDiscount = core.useProductOfferDiscount();
14
+ const { isDynamicProduct, isEnableDynamicDiscount } = useDynamicProduct.useDynamicProduct();
13
15
  const { typo } = styles;
14
16
  const { message } = setting;
17
+ const hasDiscount = !!currentOfferDiscount || isDynamicProduct && isEnableDynamicDiscount;
15
18
  return /*#__PURE__*/ jsxRuntime.jsxs("div", {
16
19
  ...builderAttrs,
17
20
  className: core.cls(builderProps?.uid, 'gp-flex gp-w-full'),
18
21
  style: {
19
22
  ...style,
20
- opacity: currentOfferDiscount ? 1 : 0.2
23
+ opacity: hasDiscount ? 1 : 0.2
21
24
  },
22
25
  id: `shopify-text-element-${builderProps?.uid}`,
23
26
  "data-product-id": currentProductId,
24
27
  children: [
25
28
  /*#__PURE__*/ jsxRuntime.jsx(Text.default, {
26
29
  setting: {
27
- text: currentOfferDiscount ? message : message?.replace(new RegExp(/\{\{Discount\}\}/, 'gi'), '0%')
30
+ text: hasDiscount ? message : message?.replace(new RegExp(/\{\{Discount\}\}/, 'gi'), '0%')
28
31
  },
29
32
  styles: {
30
33
  typo
@@ -11,14 +11,20 @@ var Loading = require('../../../common/components/Loading.js');
11
11
  var ProductVariant = require('./ProductVariant.js');
12
12
  var helper = require('../../utils/helper.js');
13
13
  var index = require('../helpers/index.js');
14
+ var ProductOfferPlaceholder = require('../constants/ProductOfferPlaceholder.js');
15
+ var useDynamicProduct = require('../hooks/useDynamicProduct.js');
14
16
 
15
17
  const PostPurchaseProductOffer = ({ styles, builderAttrs, builderProps, children, setting })=>{
16
18
  const mode = core.useEditorMode();
17
- const { productSetting, productOfferLayout, inlineAlignment, ...rowSetting } = setting || {};
19
+ const { productSetting, productOfferLayout, inlineAlignment, discountAndShippingFee, ...rowSetting } = setting || {};
18
20
  const { productOfferColumnGap, productOfferVerticalGutter, productOfferWidth, ...rowStyles } = styles || {};
19
21
  const productOffers = core.usePageStore((s)=>s.productOffers);
20
22
  const publicStoreFrontData = core.usePageStore((s)=>s.publicStoreFrontData);
21
- const totalProduct = React.useMemo(()=>productOffers?.length, [
23
+ const { isDynamicProduct } = useDynamicProduct.useDynamicProduct();
24
+ const dynamicDiscountOffer = core.usePageStore((s)=>s.dynamicDiscountOffer);
25
+ const totalProduct = React.useMemo(()=>isDynamicProduct ? dynamicDiscountOffer?.appliedProductCount : productOffers?.length, [
26
+ dynamicDiscountOffer?.appliedProductCount,
27
+ isDynamicProduct,
22
28
  productOffers?.length
23
29
  ]);
24
30
  const sortedProductBaseIDs = React.useMemo(()=>{
@@ -44,7 +50,14 @@ const PostPurchaseProductOffer = ({ styles, builderAttrs, builderProps, children
44
50
  const products = React.useMemo(()=>core.flattenConnection(productData.data?.products), [
45
51
  productData.data?.products
46
52
  ]);
47
- const productSortedById = sortedProductBaseIDs?.map((id)=>products?.find((el)=>el?.baseID === id)).filter(Boolean);
53
+ const productSortedById = React.useMemo(()=>{
54
+ return isDynamicProduct && dynamicDiscountOffer?.appliedProductCount ? Array(dynamicDiscountOffer.appliedProductCount).fill(ProductOfferPlaceholder.productOfferPlaceholder) : sortedProductBaseIDs?.map((id)=>products?.find((el)=>el?.baseID === id)).filter(Boolean);
55
+ }, [
56
+ isDynamicProduct,
57
+ dynamicDiscountOffer?.appliedProductCount,
58
+ sortedProductBaseIDs,
59
+ products
60
+ ]);
48
61
  const getStyles = ()=>{
49
62
  return {
50
63
  ...core.makeStyleResponsive('cg', productOfferColumnGap),
@@ -6,6 +6,7 @@ var jsxRuntime = require('react/jsx-runtime');
6
6
  var core = require('@gem-sdk/core');
7
7
  var React = require('react');
8
8
  var Text = require('../../text/Text.js');
9
+ var useDynamicProduct = require('../hooks/useDynamicProduct.js');
9
10
 
10
11
  const PostPurchaseProductPrice = ({ styles, style, builderAttrs, builderProps, children })=>{
11
12
  const currentVariant = core.useCurrentVariant();
@@ -13,6 +14,7 @@ const PostPurchaseProductPrice = ({ styles, style, builderAttrs, builderProps, c
13
14
  const price = currentVariant?.price ?? 0;
14
15
  const salePrice = currentVariant?.salePrice ?? 0;
15
16
  const discount = parseFloat(core.shopifyPriceRounding(core.useProductOfferDiscount(), 2));
17
+ const { isDynamicProduct } = useDynamicProduct.useDynamicProduct();
16
18
  const priceShow = React.useMemo(()=>{
17
19
  if (!discount) {
18
20
  if (styles?.priceType === 'compare') {
@@ -53,7 +55,7 @@ const PostPurchaseProductPrice = ({ styles, style, builderAttrs, builderProps, c
53
55
  /*#__PURE__*/ jsxRuntime.jsx(Text.default, {
54
56
  styles: styles,
55
57
  setting: {
56
- text: moneyFormatted
58
+ text: isDynamicProduct ? '$???' : moneyFormatted
57
59
  },
58
60
  className: core.cls({
59
61
  'gp-money': styles?.priceType === 'regular',
@@ -7,6 +7,7 @@ var core = require('@gem-sdk/core');
7
7
  var React = require('react');
8
8
  var helper = require('../../utils/helper.js');
9
9
  var MoneyLine = require('./priceBreakdown/MoneyLine.js');
10
+ var useDynamicProduct = require('../hooks/useDynamicProduct.js');
10
11
 
11
12
  const PostPurchaseProductPriceBreakdown = ({ setting = {}, styles = {}, style, builderAttrs, builderProps, children })=>{
12
13
  const currentVariant = core.useCurrentVariant();
@@ -21,6 +22,7 @@ const PostPurchaseProductPriceBreakdown = ({ setting = {}, styles = {}, style, b
21
22
  const discountShopifyFormat = core.shopifyPriceRounding(discountWithQuantity, 2);
22
23
  const totalMoneyFormatted = core.useFormatMoney(subTotal - parseFloat(discountShopifyFormat), false);
23
24
  const currentProductId = currentProduct?.baseID?.replace('gid://shopify/Product/', '');
25
+ const { isDynamicProduct, dynamicPrice, dynamicDiscount, isEnableDynamicDiscount } = useDynamicProduct.useDynamicProduct();
24
26
  const { labelStyle, moneyStyle, spacing, width, align, isShowPrice } = styles;
25
27
  const itemGap = React.useMemo(()=>{
26
28
  return helper.postPurchaseSpacing[spacing || 'none'];
@@ -33,19 +35,19 @@ const PostPurchaseProductPriceBreakdown = ({ setting = {}, styles = {}, style, b
33
35
  }, [
34
36
  isShowPrice
35
37
  ]);
36
- const moneyLines = React.useMemo(()=>{
38
+ const getMoneyLines = ()=>{
37
39
  const data = [];
38
40
  if (styles.isShowSubTotalLabel !== false) {
39
41
  data.push({
40
42
  title: setting.subTotalLabel,
41
- money: subTotalMoneyFormatted,
43
+ money: isDynamicProduct ? dynamicPrice : subTotalMoneyFormatted,
42
44
  uniqueId: 'subTotalLabel'
43
45
  });
44
46
  }
45
47
  if (styles.isShowDiscountLabel !== false) {
46
48
  data.push({
47
49
  title: setting.discountLabel,
48
- money: discount > 0 ? discountMoneyFormatted : 'No discount',
50
+ money: isDynamicProduct && dynamicDiscount && isEnableDynamicDiscount ? dynamicPrice : discount > 0 ? discountMoneyFormatted : 'No discount',
49
51
  uniqueId: 'discountLabel'
50
52
  });
51
53
  }
@@ -71,25 +73,11 @@ const PostPurchaseProductPriceBreakdown = ({ setting = {}, styles = {}, style, b
71
73
  }
72
74
  data.push({
73
75
  title: setting.totalLabel,
74
- money: totalMoneyFormatted,
76
+ money: isDynamicProduct ? dynamicPrice : totalMoneyFormatted,
75
77
  uniqueId: 'totalLabel'
76
78
  });
77
79
  return data;
78
- }, [
79
- discount,
80
- discountMoneyFormatted,
81
- setting.discountLabel,
82
- setting.shippingLabel,
83
- setting.subTotalLabel,
84
- setting.taxesLabel,
85
- setting.totalLabel,
86
- styles.isShowSubTotalLabel,
87
- styles.isShowDiscountLabel,
88
- styles.isShowShippingLabel,
89
- styles.isShowTaxesLabel,
90
- subTotalMoneyFormatted,
91
- totalMoneyFormatted
92
- ]);
80
+ };
93
81
  return /*#__PURE__*/ jsxRuntime.jsxs(jsxRuntime.Fragment, {
94
82
  children: [
95
83
  /*#__PURE__*/ jsxRuntime.jsxs("div", {
@@ -140,9 +128,9 @@ const PostPurchaseProductPriceBreakdown = ({ setting = {}, styles = {}, style, b
140
128
  ...core.makeStyleResponsive('w', width),
141
129
  gap: itemGap
142
130
  },
143
- children: moneyLines.map((item)=>/*#__PURE__*/ jsxRuntime.jsxs(React.Fragment, {
131
+ children: getMoneyLines().map((item)=>/*#__PURE__*/ jsxRuntime.jsxs(React.Fragment, {
144
132
  children: [
145
- item.uniqueId === 'totalLabel' && moneyLines.length > 1 && /*#__PURE__*/ jsxRuntime.jsx("hr", {}),
133
+ item.uniqueId === 'totalLabel' && getMoneyLines().length > 1 && /*#__PURE__*/ jsxRuntime.jsx("hr", {}),
146
134
  /*#__PURE__*/ jsxRuntime.jsx("div", {
147
135
  children: /*#__PURE__*/ jsxRuntime.jsx(MoneyLine.default, {
148
136
  uniqueId: item.uniqueId,
@@ -0,0 +1,127 @@
1
+ 'use strict';
2
+
3
+ const productOfferPlaceholder = {
4
+ id: '',
5
+ title: 'Dynamic product',
6
+ description: 'This is a dynamic product. The actual product will appear here based on your setup',
7
+ descriptionHtml: '<p class="QN2lPu">This is a dynamic product. The actual product will</p>\n<p class="QN2lPu">appear here based on your setup</p>',
8
+ createdAt: '2024-07-01T04:19:51.286753Z',
9
+ handle: 'dynamic-product',
10
+ averageRating: 0,
11
+ isStorefront: true,
12
+ isSample: false,
13
+ baseID: '',
14
+ sku: '',
15
+ options: [
16
+ {
17
+ id: '1',
18
+ name: 'Variant',
19
+ optionType: '',
20
+ values: [
21
+ {
22
+ id: '1',
23
+ label: 'Variant option',
24
+ isDefault: true,
25
+ baseID: '1'
26
+ }
27
+ ]
28
+ }
29
+ ],
30
+ vendor: '',
31
+ featuredImage: {
32
+ id: '542178560626918105',
33
+ contentType: 'IMAGE',
34
+ previewImage: '',
35
+ src: 'https://cdn.shopify.com/s/files/1/0858/3168/0279/files/gempages_502328832959710176-312cf60c-d923-4913-9873-66545effe9cc.png',
36
+ width: 449,
37
+ height: 449,
38
+ alt: ''
39
+ },
40
+ collections: {
41
+ edges: [
42
+ {
43
+ node: {
44
+ id: '502321959132988135',
45
+ title: 'Home page'
46
+ }
47
+ },
48
+ {
49
+ node: {
50
+ id: '539286505089204764',
51
+ title: 'b'
52
+ }
53
+ },
54
+ {
55
+ node: {
56
+ id: '539286505089401372',
57
+ title: 'e'
58
+ }
59
+ }
60
+ ]
61
+ },
62
+ tags: [],
63
+ medias: {
64
+ edges: [
65
+ {
66
+ cursor: 'gqFpzweGNIc/AALZoXYB',
67
+ node: {
68
+ id: '542178560626918105',
69
+ contentType: 'IMAGE',
70
+ previewImage: '',
71
+ src: 'https://cdn.shopify.com/s/files/1/0858/3168/0279/files/gempages_502328832959710176-312cf60c-d923-4913-9873-66545effe9cc.png',
72
+ width: 449,
73
+ height: 449,
74
+ alt: ''
75
+ }
76
+ }
77
+ ],
78
+ pageInfo: {
79
+ endCursor: 'gqFpzweGNIc/AALZoXYB',
80
+ hasNextPage: true
81
+ }
82
+ },
83
+ variants: {
84
+ edges: [
85
+ {
86
+ cursor: 'gqFpzwc5ZqRtDAMIoXYC',
87
+ node: {
88
+ id: '520560088328176392',
89
+ title: 'Default Title',
90
+ barcode: '',
91
+ baseID: 'gid://shopify/ProductVariant/49298248925463',
92
+ costPrice: 88,
93
+ height: 0,
94
+ inventoryPolicy: 'DENY',
95
+ inventoryQuantity: 211,
96
+ inventoryStatus: '',
97
+ isDigital: true,
98
+ length: 0,
99
+ lowInventoryAmount: 0,
100
+ manageInventory: false,
101
+ mediaId: '0',
102
+ platform: 'SHOPIFY',
103
+ price: 88,
104
+ salePrice: 88,
105
+ sku: '',
106
+ soldIndividually: false,
107
+ weight: 0,
108
+ width: 0,
109
+ selectedOptions: [
110
+ {
111
+ name: 'Variant',
112
+ value: 'Variant option',
113
+ optionType: ''
114
+ }
115
+ ],
116
+ media: undefined
117
+ }
118
+ }
119
+ ],
120
+ pageInfo: {
121
+ hasNextPage: false,
122
+ endCursor: 'gaFpzwc5ZqRtDAMI'
123
+ }
124
+ }
125
+ };
126
+
127
+ exports.productOfferPlaceholder = productOfferPlaceholder;
@@ -0,0 +1,28 @@
1
+ 'use strict';
2
+
3
+ var core = require('@gem-sdk/core');
4
+ var React = require('react');
5
+
6
+ const useDynamicProduct = ()=>{
7
+ const dynamicDiscountOffer = core.usePageStore((s)=>s.dynamicDiscountOffer);
8
+ const isDynamicProduct = React.useMemo(()=>{
9
+ return !!dynamicDiscountOffer;
10
+ }, [
11
+ dynamicDiscountOffer
12
+ ]);
13
+ // false = No discount
14
+ const isEnableDynamicDiscount = React.useMemo(()=>{
15
+ return dynamicDiscountOffer?.isEnabled;
16
+ }, [
17
+ dynamicDiscountOffer
18
+ ]);
19
+ const dynamicPrice = '$???';
20
+ return {
21
+ isDynamicProduct,
22
+ dynamicPrice,
23
+ dynamicDiscount: dynamicDiscountOffer,
24
+ isEnableDynamicDiscount
25
+ };
26
+ };
27
+
28
+ exports.useDynamicProduct = useDynamicProduct;
@@ -184,6 +184,14 @@ const config = {
184
184
  }
185
185
  }
186
186
  },
187
+ {
188
+ id: 'dynamicOfferQuantity',
189
+ type: 'dynamic-offer-quantity',
190
+ min: 1,
191
+ max: 4,
192
+ default: 2,
193
+ isOnlyAcceptInteger: true
194
+ },
187
195
  {
188
196
  id: 'discountAndShippingFee',
189
197
  type: 'discount-and-shipping-fee-product-offer',
@@ -377,6 +385,24 @@ const config = {
377
385
  },
378
386
  layout: 'vertical'
379
387
  },
388
+ {
389
+ type: 'group',
390
+ label: {
391
+ en: 'Product offers'
392
+ },
393
+ controls: [
394
+ {
395
+ type: 'control',
396
+ label: {
397
+ en: 'Quantity'
398
+ },
399
+ setting: {
400
+ id: 'dynamicOfferQuantity'
401
+ }
402
+ }
403
+ ],
404
+ condition: 'false'
405
+ },
380
406
  {
381
407
  type: 'control',
382
408
  label: {
@@ -20,12 +20,20 @@ const ProductFeaturedImageCarousel = ({ productImages, imgRef, onHandleClick, on
20
20
  const { onListener, ref } = core.useInteraction();
21
21
  let delayTimeout;
22
22
  const mode = core.useEditorMode();
23
- const imageActiveIndex = React.useMemo(()=>{
23
+ const getImageActiveIndex = React.useCallback(()=>{
24
24
  const index = productImages?.findIndex((item)=>item.id === featuredImageGlobal?.id);
25
- return index;
25
+ return index ?? 0;
26
26
  }, [
27
- productImages,
28
- featuredImageGlobal
27
+ featuredImageGlobal?.id,
28
+ productImages
29
+ ]);
30
+ const [imageActiveIndex, setImageActiveIndex] = React.useState(getImageActiveIndex());
31
+ React.useEffect(()=>{
32
+ setImageActiveIndex(getImageActiveIndex());
33
+ }, [
34
+ featuredImageGlobal,
35
+ getImageActiveIndex,
36
+ productImages
29
37
  ]);
30
38
  const imageActive = React.useMemo(()=>{
31
39
  if (productImages?.length) return featuredImageGlobal || productImages?.[0];
@@ -9,7 +9,7 @@ var ThirdPartyPreview = require('./ThirdPartyPreview.js');
9
9
  const AppConfig = {
10
10
  id: 'notify-back-in-stockpreorder',
11
11
  label: 'Notify! Back in Stock|PreOrder',
12
- logoUrl: 'https://cdn.shopify.com/app-store/listing_images/9216cd5dd17ab1a38de2641bae109159/icon/CNWHjbfSt4MDEAE=.png',
12
+ logoUrl: 'https://cdn.shopify.com/s/files/1/0643/0497/1949/files/Notify_Me_Logo.png?v=1736234550',
13
13
  tag: 'NotifyBackInStockPreOrder'
14
14
  };
15
15
  const NotifyBackInStockPreOrderContent = ()=>{
@@ -7,7 +7,7 @@ var NotifyBackInStockPreOrder = require('../components/NotifyBackInStockPreOrder
7
7
  const config = {
8
8
  tag: NotifyBackInStockPreOrder.AppConfig.tag,
9
9
  label: NotifyBackInStockPreOrder.AppConfig.label,
10
- icon: '<img class="gp-w-[24px] gp-border gp-border-[#494949] gp-rounded-[3px]" src="https://cdn.shopify.com/app-store/listing_images/9216cd5dd17ab1a38de2641bae109159/icon/CNWHjbfSt4MDEAE=.png">',
10
+ icon: `<img class="gp-w-[24px] gp-border gp-border-[#494949] gp-rounded-[3px]" src="${NotifyBackInStockPreOrder.AppConfig.logoUrl}">`,
11
11
  editorConfigs: {
12
12
  placeholder: {
13
13
  flowTag: [
@@ -27,7 +27,7 @@ const config = {
27
27
  hideTextContent: true,
28
28
  icon: {
29
29
  desktop: `<div class="w-full flex flex-col items-center">
30
- <img class="w-24 border border-dark-200 rounded-medium" src="https://cdn.shopify.com/app-store/listing_images/9216cd5dd17ab1a38de2641bae109159/icon/CNWHjbfSt4MDEAE=.png">
30
+ <img class="w-24 border border-dark-200 rounded-medium" src="${NotifyBackInStockPreOrder.AppConfig.logoUrl}">
31
31
  <span class="preset-item-title">Notify! Back in Stock|PreOrder</span>
32
32
  </div>`
33
33
  },
@@ -42,7 +42,6 @@ const MarqueeBase = ({ style = {}, autoFill = false, play = true, pauseOnHover =
42
42
  };
43
43
  return template`
44
44
  <div
45
-
46
45
  style="${containerStyle()}"
47
46
  class="rfm-marquee-container"
48
47
  >
@@ -51,35 +50,17 @@ const MarqueeBase = ({ style = {}, autoFill = false, play = true, pauseOnHover =
51
50
  style="${marqueeStyle()}"
52
51
  >
53
52
  <div class="rfm-initial-child-container">
54
-
55
- ${template`
56
- <div style="${childStyle()}" class="${`rfm-child`}">
57
- ${children}
58
- </div>
59
- `}
60
- </div>
61
- <div class="rfm-child-placeholder">
62
- ${template`
63
- <div style="${childStyle()}" class="${`rfm-child`}">
64
- ${children}
65
- </div>
66
- `}
67
- </div>
68
- <div class="rfm-child-placeholder">
69
-
70
- ${template`
71
- <div style="${childStyle()}" class="${`rfm-child`}">
72
- ${children}
73
- </div>
74
- `}
53
+ ${template`
54
+ <div style="${childStyle()}" class="${`rfm-child`}">
55
+ ${children}
56
+ </div>
57
+ `}
75
58
  </div>
76
59
  </div>
77
- <div
60
+ <div
78
61
  class="rfm-marquee placeholder-marquee"
79
62
  style="${marqueeStyle()}"
80
- >
81
-
82
- </div>
63
+ ></div>
83
64
  </div>
84
65
  `;
85
66
  };
@@ -1,26 +1,29 @@
1
1
  import { jsxs, jsx } from 'react/jsx-runtime';
2
2
  import { useProduct, useProductOfferDiscount, cls } from '@gem-sdk/core';
3
3
  import Text from '../../text/Text.js';
4
+ import { useDynamicProduct } from '../hooks/useDynamicProduct.js';
4
5
 
5
6
  const PostPurchaseProductDiscountTag = ({ setting = {}, styles = {}, style, builderAttrs, builderProps, children })=>{
6
7
  const currentProduct = useProduct();
7
8
  const currentProductId = currentProduct?.baseID?.replace('gid://shopify/Product/', '');
8
9
  const currentOfferDiscount = useProductOfferDiscount();
10
+ const { isDynamicProduct, isEnableDynamicDiscount } = useDynamicProduct();
9
11
  const { typo } = styles;
10
12
  const { message } = setting;
13
+ const hasDiscount = !!currentOfferDiscount || isDynamicProduct && isEnableDynamicDiscount;
11
14
  return /*#__PURE__*/ jsxs("div", {
12
15
  ...builderAttrs,
13
16
  className: cls(builderProps?.uid, 'gp-flex gp-w-full'),
14
17
  style: {
15
18
  ...style,
16
- opacity: currentOfferDiscount ? 1 : 0.2
19
+ opacity: hasDiscount ? 1 : 0.2
17
20
  },
18
21
  id: `shopify-text-element-${builderProps?.uid}`,
19
22
  "data-product-id": currentProductId,
20
23
  children: [
21
24
  /*#__PURE__*/ jsx(Text, {
22
25
  setting: {
23
- text: currentOfferDiscount ? message : message?.replace(new RegExp(/\{\{Discount\}\}/, 'gi'), '0%')
26
+ text: hasDiscount ? message : message?.replace(new RegExp(/\{\{Discount\}\}/, 'gi'), '0%')
24
27
  },
25
28
  styles: {
26
29
  typo
@@ -7,14 +7,20 @@ import Loading from '../../../common/components/Loading.js';
7
7
  import { DEVICES } from './ProductVariant.js';
8
8
  import { postPurchaseViewSpacing } from '../../utils/helper.js';
9
9
  import { hasPublicStoreFrontData, createStoreFrontFetcher } from '../helpers/index.js';
10
+ import { productOfferPlaceholder } from '../constants/ProductOfferPlaceholder.js';
11
+ import { useDynamicProduct } from '../hooks/useDynamicProduct.js';
10
12
 
11
13
  const PostPurchaseProductOffer = ({ styles, builderAttrs, builderProps, children, setting })=>{
12
14
  const mode = useEditorMode();
13
- const { productSetting, productOfferLayout, inlineAlignment, ...rowSetting } = setting || {};
15
+ const { productSetting, productOfferLayout, inlineAlignment, discountAndShippingFee, ...rowSetting } = setting || {};
14
16
  const { productOfferColumnGap, productOfferVerticalGutter, productOfferWidth, ...rowStyles } = styles || {};
15
17
  const productOffers = usePageStore((s)=>s.productOffers);
16
18
  const publicStoreFrontData = usePageStore((s)=>s.publicStoreFrontData);
17
- const totalProduct = useMemo(()=>productOffers?.length, [
19
+ const { isDynamicProduct } = useDynamicProduct();
20
+ const dynamicDiscountOffer = usePageStore((s)=>s.dynamicDiscountOffer);
21
+ const totalProduct = useMemo(()=>isDynamicProduct ? dynamicDiscountOffer?.appliedProductCount : productOffers?.length, [
22
+ dynamicDiscountOffer?.appliedProductCount,
23
+ isDynamicProduct,
18
24
  productOffers?.length
19
25
  ]);
20
26
  const sortedProductBaseIDs = useMemo(()=>{
@@ -40,7 +46,14 @@ const PostPurchaseProductOffer = ({ styles, builderAttrs, builderProps, children
40
46
  const products = useMemo(()=>flattenConnection(productData.data?.products), [
41
47
  productData.data?.products
42
48
  ]);
43
- const productSortedById = sortedProductBaseIDs?.map((id)=>products?.find((el)=>el?.baseID === id)).filter(Boolean);
49
+ const productSortedById = useMemo(()=>{
50
+ return isDynamicProduct && dynamicDiscountOffer?.appliedProductCount ? Array(dynamicDiscountOffer.appliedProductCount).fill(productOfferPlaceholder) : sortedProductBaseIDs?.map((id)=>products?.find((el)=>el?.baseID === id)).filter(Boolean);
51
+ }, [
52
+ isDynamicProduct,
53
+ dynamicDiscountOffer?.appliedProductCount,
54
+ sortedProductBaseIDs,
55
+ products
56
+ ]);
44
57
  const getStyles = ()=>{
45
58
  return {
46
59
  ...makeStyleResponsive('cg', productOfferColumnGap),
@@ -2,6 +2,7 @@ import { jsxs, jsx } from 'react/jsx-runtime';
2
2
  import { useCurrentVariant, useProduct, shopifyPriceRounding, useProductOfferDiscount, useFormatMoney, cls } from '@gem-sdk/core';
3
3
  import { useMemo } from 'react';
4
4
  import Text from '../../text/Text.js';
5
+ import { useDynamicProduct } from '../hooks/useDynamicProduct.js';
5
6
 
6
7
  const PostPurchaseProductPrice = ({ styles, style, builderAttrs, builderProps, children })=>{
7
8
  const currentVariant = useCurrentVariant();
@@ -9,6 +10,7 @@ const PostPurchaseProductPrice = ({ styles, style, builderAttrs, builderProps, c
9
10
  const price = currentVariant?.price ?? 0;
10
11
  const salePrice = currentVariant?.salePrice ?? 0;
11
12
  const discount = parseFloat(shopifyPriceRounding(useProductOfferDiscount(), 2));
13
+ const { isDynamicProduct } = useDynamicProduct();
12
14
  const priceShow = useMemo(()=>{
13
15
  if (!discount) {
14
16
  if (styles?.priceType === 'compare') {
@@ -49,7 +51,7 @@ const PostPurchaseProductPrice = ({ styles, style, builderAttrs, builderProps, c
49
51
  /*#__PURE__*/ jsx(Text, {
50
52
  styles: styles,
51
53
  setting: {
52
- text: moneyFormatted
54
+ text: isDynamicProduct ? '$???' : moneyFormatted
53
55
  },
54
56
  className: cls({
55
57
  'gp-money': styles?.priceType === 'regular',
@@ -3,6 +3,7 @@ import { useCurrentVariant, useProduct, useQuantity, useProductOfferDiscount, us
3
3
  import { useMemo, useState, useEffect, Fragment as Fragment$1 } from 'react';
4
4
  import { postPurchaseSpacing } from '../../utils/helper.js';
5
5
  import MoneyLine from './priceBreakdown/MoneyLine.js';
6
+ import { useDynamicProduct } from '../hooks/useDynamicProduct.js';
6
7
 
7
8
  const PostPurchaseProductPriceBreakdown = ({ setting = {}, styles = {}, style, builderAttrs, builderProps, children })=>{
8
9
  const currentVariant = useCurrentVariant();
@@ -17,6 +18,7 @@ const PostPurchaseProductPriceBreakdown = ({ setting = {}, styles = {}, style, b
17
18
  const discountShopifyFormat = shopifyPriceRounding(discountWithQuantity, 2);
18
19
  const totalMoneyFormatted = useFormatMoney(subTotal - parseFloat(discountShopifyFormat), false);
19
20
  const currentProductId = currentProduct?.baseID?.replace('gid://shopify/Product/', '');
21
+ const { isDynamicProduct, dynamicPrice, dynamicDiscount, isEnableDynamicDiscount } = useDynamicProduct();
20
22
  const { labelStyle, moneyStyle, spacing, width, align, isShowPrice } = styles;
21
23
  const itemGap = useMemo(()=>{
22
24
  return postPurchaseSpacing[spacing || 'none'];
@@ -29,19 +31,19 @@ const PostPurchaseProductPriceBreakdown = ({ setting = {}, styles = {}, style, b
29
31
  }, [
30
32
  isShowPrice
31
33
  ]);
32
- const moneyLines = useMemo(()=>{
34
+ const getMoneyLines = ()=>{
33
35
  const data = [];
34
36
  if (styles.isShowSubTotalLabel !== false) {
35
37
  data.push({
36
38
  title: setting.subTotalLabel,
37
- money: subTotalMoneyFormatted,
39
+ money: isDynamicProduct ? dynamicPrice : subTotalMoneyFormatted,
38
40
  uniqueId: 'subTotalLabel'
39
41
  });
40
42
  }
41
43
  if (styles.isShowDiscountLabel !== false) {
42
44
  data.push({
43
45
  title: setting.discountLabel,
44
- money: discount > 0 ? discountMoneyFormatted : 'No discount',
46
+ money: isDynamicProduct && dynamicDiscount && isEnableDynamicDiscount ? dynamicPrice : discount > 0 ? discountMoneyFormatted : 'No discount',
45
47
  uniqueId: 'discountLabel'
46
48
  });
47
49
  }
@@ -67,25 +69,11 @@ const PostPurchaseProductPriceBreakdown = ({ setting = {}, styles = {}, style, b
67
69
  }
68
70
  data.push({
69
71
  title: setting.totalLabel,
70
- money: totalMoneyFormatted,
72
+ money: isDynamicProduct ? dynamicPrice : totalMoneyFormatted,
71
73
  uniqueId: 'totalLabel'
72
74
  });
73
75
  return data;
74
- }, [
75
- discount,
76
- discountMoneyFormatted,
77
- setting.discountLabel,
78
- setting.shippingLabel,
79
- setting.subTotalLabel,
80
- setting.taxesLabel,
81
- setting.totalLabel,
82
- styles.isShowSubTotalLabel,
83
- styles.isShowDiscountLabel,
84
- styles.isShowShippingLabel,
85
- styles.isShowTaxesLabel,
86
- subTotalMoneyFormatted,
87
- totalMoneyFormatted
88
- ]);
76
+ };
89
77
  return /*#__PURE__*/ jsxs(Fragment, {
90
78
  children: [
91
79
  /*#__PURE__*/ jsxs("div", {
@@ -136,9 +124,9 @@ const PostPurchaseProductPriceBreakdown = ({ setting = {}, styles = {}, style, b
136
124
  ...makeStyleResponsive('w', width),
137
125
  gap: itemGap
138
126
  },
139
- children: moneyLines.map((item)=>/*#__PURE__*/ jsxs(Fragment$1, {
127
+ children: getMoneyLines().map((item)=>/*#__PURE__*/ jsxs(Fragment$1, {
140
128
  children: [
141
- item.uniqueId === 'totalLabel' && moneyLines.length > 1 && /*#__PURE__*/ jsx("hr", {}),
129
+ item.uniqueId === 'totalLabel' && getMoneyLines().length > 1 && /*#__PURE__*/ jsx("hr", {}),
142
130
  /*#__PURE__*/ jsx("div", {
143
131
  children: /*#__PURE__*/ jsx(MoneyLine, {
144
132
  uniqueId: item.uniqueId,
@@ -0,0 +1,125 @@
1
+ const productOfferPlaceholder = {
2
+ id: '',
3
+ title: 'Dynamic product',
4
+ description: 'This is a dynamic product. The actual product will appear here based on your setup',
5
+ descriptionHtml: '<p class="QN2lPu">This is a dynamic product. The actual product will</p>\n<p class="QN2lPu">appear here based on your setup</p>',
6
+ createdAt: '2024-07-01T04:19:51.286753Z',
7
+ handle: 'dynamic-product',
8
+ averageRating: 0,
9
+ isStorefront: true,
10
+ isSample: false,
11
+ baseID: '',
12
+ sku: '',
13
+ options: [
14
+ {
15
+ id: '1',
16
+ name: 'Variant',
17
+ optionType: '',
18
+ values: [
19
+ {
20
+ id: '1',
21
+ label: 'Variant option',
22
+ isDefault: true,
23
+ baseID: '1'
24
+ }
25
+ ]
26
+ }
27
+ ],
28
+ vendor: '',
29
+ featuredImage: {
30
+ id: '542178560626918105',
31
+ contentType: 'IMAGE',
32
+ previewImage: '',
33
+ src: 'https://cdn.shopify.com/s/files/1/0858/3168/0279/files/gempages_502328832959710176-312cf60c-d923-4913-9873-66545effe9cc.png',
34
+ width: 449,
35
+ height: 449,
36
+ alt: ''
37
+ },
38
+ collections: {
39
+ edges: [
40
+ {
41
+ node: {
42
+ id: '502321959132988135',
43
+ title: 'Home page'
44
+ }
45
+ },
46
+ {
47
+ node: {
48
+ id: '539286505089204764',
49
+ title: 'b'
50
+ }
51
+ },
52
+ {
53
+ node: {
54
+ id: '539286505089401372',
55
+ title: 'e'
56
+ }
57
+ }
58
+ ]
59
+ },
60
+ tags: [],
61
+ medias: {
62
+ edges: [
63
+ {
64
+ cursor: 'gqFpzweGNIc/AALZoXYB',
65
+ node: {
66
+ id: '542178560626918105',
67
+ contentType: 'IMAGE',
68
+ previewImage: '',
69
+ src: 'https://cdn.shopify.com/s/files/1/0858/3168/0279/files/gempages_502328832959710176-312cf60c-d923-4913-9873-66545effe9cc.png',
70
+ width: 449,
71
+ height: 449,
72
+ alt: ''
73
+ }
74
+ }
75
+ ],
76
+ pageInfo: {
77
+ endCursor: 'gqFpzweGNIc/AALZoXYB',
78
+ hasNextPage: true
79
+ }
80
+ },
81
+ variants: {
82
+ edges: [
83
+ {
84
+ cursor: 'gqFpzwc5ZqRtDAMIoXYC',
85
+ node: {
86
+ id: '520560088328176392',
87
+ title: 'Default Title',
88
+ barcode: '',
89
+ baseID: 'gid://shopify/ProductVariant/49298248925463',
90
+ costPrice: 88,
91
+ height: 0,
92
+ inventoryPolicy: 'DENY',
93
+ inventoryQuantity: 211,
94
+ inventoryStatus: '',
95
+ isDigital: true,
96
+ length: 0,
97
+ lowInventoryAmount: 0,
98
+ manageInventory: false,
99
+ mediaId: '0',
100
+ platform: 'SHOPIFY',
101
+ price: 88,
102
+ salePrice: 88,
103
+ sku: '',
104
+ soldIndividually: false,
105
+ weight: 0,
106
+ width: 0,
107
+ selectedOptions: [
108
+ {
109
+ name: 'Variant',
110
+ value: 'Variant option',
111
+ optionType: ''
112
+ }
113
+ ],
114
+ media: undefined
115
+ }
116
+ }
117
+ ],
118
+ pageInfo: {
119
+ hasNextPage: false,
120
+ endCursor: 'gaFpzwc5ZqRtDAMI'
121
+ }
122
+ }
123
+ };
124
+
125
+ export { productOfferPlaceholder };
@@ -0,0 +1,26 @@
1
+ import { usePageStore } from '@gem-sdk/core';
2
+ import { useMemo } from 'react';
3
+
4
+ const useDynamicProduct = ()=>{
5
+ const dynamicDiscountOffer = usePageStore((s)=>s.dynamicDiscountOffer);
6
+ const isDynamicProduct = useMemo(()=>{
7
+ return !!dynamicDiscountOffer;
8
+ }, [
9
+ dynamicDiscountOffer
10
+ ]);
11
+ // false = No discount
12
+ const isEnableDynamicDiscount = useMemo(()=>{
13
+ return dynamicDiscountOffer?.isEnabled;
14
+ }, [
15
+ dynamicDiscountOffer
16
+ ]);
17
+ const dynamicPrice = '$???';
18
+ return {
19
+ isDynamicProduct,
20
+ dynamicPrice,
21
+ dynamicDiscount: dynamicDiscountOffer,
22
+ isEnableDynamicDiscount
23
+ };
24
+ };
25
+
26
+ export { useDynamicProduct };
@@ -180,6 +180,14 @@ const config = {
180
180
  }
181
181
  }
182
182
  },
183
+ {
184
+ id: 'dynamicOfferQuantity',
185
+ type: 'dynamic-offer-quantity',
186
+ min: 1,
187
+ max: 4,
188
+ default: 2,
189
+ isOnlyAcceptInteger: true
190
+ },
183
191
  {
184
192
  id: 'discountAndShippingFee',
185
193
  type: 'discount-and-shipping-fee-product-offer',
@@ -373,6 +381,24 @@ const config = {
373
381
  },
374
382
  layout: 'vertical'
375
383
  },
384
+ {
385
+ type: 'group',
386
+ label: {
387
+ en: 'Product offers'
388
+ },
389
+ controls: [
390
+ {
391
+ type: 'control',
392
+ label: {
393
+ en: 'Quantity'
394
+ },
395
+ setting: {
396
+ id: 'dynamicOfferQuantity'
397
+ }
398
+ }
399
+ ],
400
+ condition: 'false'
401
+ },
376
402
  {
377
403
  type: 'control',
378
404
  label: {
@@ -6,7 +6,7 @@ import { getAspectRatio } from '../../common/common.js';
6
6
  import { checkRenderFeaturedCarousel, getDotSettingByLayout } from '../../common/productFeaturedImage.js';
7
7
  import { handleMouseMove, handleMouseOut } from '../../common/hoverAction.js';
8
8
  import ProductFeaturedImageOnly from './ProductFeaturedImageOnly.js';
9
- import { memo, useMemo, useCallback, useEffect, createRef } from 'react';
9
+ import { memo, useCallback, useState, useEffect, useMemo, createRef } from 'react';
10
10
  import { handleSetSrcForFeatureProductImage } from '../../../../../image/helpers/getSrcSet.js';
11
11
 
12
12
  const ProductFeaturedImageCarousel = ({ productImages, imgRef, onHandleClick, onClickArrow, onHandleLoaded, onHandleUpdateActiveIndex, builderPropUID, builderPropsClass, children, parentUid, ...setting })=>{
@@ -16,12 +16,20 @@ const ProductFeaturedImageCarousel = ({ productImages, imgRef, onHandleClick, on
16
16
  const { onListener, ref } = useInteraction();
17
17
  let delayTimeout;
18
18
  const mode = useEditorMode();
19
- const imageActiveIndex = useMemo(()=>{
19
+ const getImageActiveIndex = useCallback(()=>{
20
20
  const index = productImages?.findIndex((item)=>item.id === featuredImageGlobal?.id);
21
- return index;
21
+ return index ?? 0;
22
22
  }, [
23
- productImages,
24
- featuredImageGlobal
23
+ featuredImageGlobal?.id,
24
+ productImages
25
+ ]);
26
+ const [imageActiveIndex, setImageActiveIndex] = useState(getImageActiveIndex());
27
+ useEffect(()=>{
28
+ setImageActiveIndex(getImageActiveIndex());
29
+ }, [
30
+ featuredImageGlobal,
31
+ getImageActiveIndex,
32
+ productImages
25
33
  ]);
26
34
  const imageActive = useMemo(()=>{
27
35
  if (productImages?.length) return featuredImageGlobal || productImages?.[0];
@@ -5,7 +5,7 @@ import ThirdPartyPreview from './ThirdPartyPreview.js';
5
5
  const AppConfig = {
6
6
  id: 'notify-back-in-stockpreorder',
7
7
  label: 'Notify! Back in Stock|PreOrder',
8
- logoUrl: 'https://cdn.shopify.com/app-store/listing_images/9216cd5dd17ab1a38de2641bae109159/icon/CNWHjbfSt4MDEAE=.png',
8
+ logoUrl: 'https://cdn.shopify.com/s/files/1/0643/0497/1949/files/Notify_Me_Logo.png?v=1736234550',
9
9
  tag: 'NotifyBackInStockPreOrder'
10
10
  };
11
11
  const NotifyBackInStockPreOrderContent = ()=>{
@@ -3,7 +3,7 @@ import { AppConfig } from '../components/NotifyBackInStockPreOrder.js';
3
3
  const config = {
4
4
  tag: AppConfig.tag,
5
5
  label: AppConfig.label,
6
- icon: '<img class="gp-w-[24px] gp-border gp-border-[#494949] gp-rounded-[3px]" src="https://cdn.shopify.com/app-store/listing_images/9216cd5dd17ab1a38de2641bae109159/icon/CNWHjbfSt4MDEAE=.png">',
6
+ icon: `<img class="gp-w-[24px] gp-border gp-border-[#494949] gp-rounded-[3px]" src="${AppConfig.logoUrl}">`,
7
7
  editorConfigs: {
8
8
  placeholder: {
9
9
  flowTag: [
@@ -23,7 +23,7 @@ const config = {
23
23
  hideTextContent: true,
24
24
  icon: {
25
25
  desktop: `<div class="w-full flex flex-col items-center">
26
- <img class="w-24 border border-dark-200 rounded-medium" src="https://cdn.shopify.com/app-store/listing_images/9216cd5dd17ab1a38de2641bae109159/icon/CNWHjbfSt4MDEAE=.png">
26
+ <img class="w-24 border border-dark-200 rounded-medium" src="${AppConfig.logoUrl}">
27
27
  <span class="preset-item-title">Notify! Back in Stock|PreOrder</span>
28
28
  </div>`
29
29
  },
@@ -4589,6 +4589,7 @@ type ProductOfferSettings = Omit<ProductSetting, 'productSetting'> & {
4589
4589
  productIds?: string[];
4590
4590
  };
4591
4591
  productOfferLayout: ObjectDevices<'1col' | '2col'>;
4592
+ dynamicOfferQuantity?: number;
4592
4593
  discountAndShippingFee?: boolean;
4593
4594
  };
4594
4595
  type ProductOfferStyles = RowProps$1['styles'] & {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gem-sdk/components",
3
- "version": "2.1.20",
3
+ "version": "2.1.21",
4
4
  "license": "MIT",
5
5
  "sideEffects": false,
6
6
  "main": "dist/cjs/index.js",
@@ -21,7 +21,7 @@
21
21
  "format": "prettier --write \"./src/**/*.{ts,tsx}\""
22
22
  },
23
23
  "devDependencies": {
24
- "@gem-sdk/core": "2.1.20",
24
+ "@gem-sdk/core": "2.1.21",
25
25
  "@gem-sdk/styles": "2.1.0",
26
26
  "@types/react-transition-group": "^4.4.5"
27
27
  },