@ordergroove/offers 2.29.0 → 2.29.1-alpha-PR-707-2.113

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 (37) hide show
  1. package/README.md +22 -1
  2. package/dist/bundle-report.html +72 -59
  3. package/dist/examples.js +262 -105
  4. package/dist/examples.js.map +2 -2
  5. package/dist/offers.js +82 -32
  6. package/dist/offers.js.map +3 -3
  7. package/examples/index.js +230 -218
  8. package/package.json +2 -2
  9. package/src/components/PrepaidData.js +110 -0
  10. package/src/components/PrepaidToggle.js +108 -0
  11. package/src/components/Price.js +6 -6
  12. package/src/components/Select.js +6 -1
  13. package/src/components/__tests__/PrepaidData.spec.js +173 -0
  14. package/src/components/__tests__/PrepaidToggle.spec.js +115 -0
  15. package/src/components/__tests__/Price.spec.js +96 -0
  16. package/src/core/__tests__/adapters.spec.js +232 -1
  17. package/src/core/__tests__/descriptors.spec.js +56 -0
  18. package/src/core/__tests__/reducer.spec.js +153 -1
  19. package/src/core/__tests__/selectors.spec.js +34 -1
  20. package/src/core/actions.js +5 -0
  21. package/src/core/adapters.js +48 -2
  22. package/src/core/constants.js +1 -0
  23. package/src/core/descriptors.js +7 -1
  24. package/src/core/reducer.js +35 -14
  25. package/src/core/selectors.js +32 -0
  26. package/src/core/utils.ts +16 -0
  27. package/src/make-api.js +4 -0
  28. package/src/shopify/__tests__/productPlan.spec.js +513 -0
  29. package/src/shopify/__tests__/shopifyReducer.spec.js +630 -19
  30. package/src/shopify/__tests__/utils.spec.js +25 -0
  31. package/src/shopify/reducers/productPlans.ts +134 -0
  32. package/src/shopify/shopifyMiddleware.ts +3 -0
  33. package/src/shopify/shopifyReducer.js +96 -47
  34. package/src/shopify/shopifyTrackingMiddleware.ts +1 -1
  35. package/src/shopify/types/productPlan.ts +11 -0
  36. package/src/shopify/types/shopify.ts +98 -0
  37. package/src/shopify/utils.ts +3 -0
@@ -0,0 +1,25 @@
1
+ import { money, percentage } from '../utils';
2
+
3
+ describe('Shopify Utils', () => {
4
+ describe('Money', () => {
5
+ it('Should return formatted greater than $1 money price', () => {
6
+ const price = 1000;
7
+ const formattedPrice = money(price);
8
+ expect(formattedPrice).toBe('$10.00');
9
+ });
10
+
11
+ it('Should return formatted lower than $1 money price', () => {
12
+ const price = 50;
13
+ const formattedPrice = money(price);
14
+ expect(formattedPrice).toBe('$.50');
15
+ });
16
+ });
17
+
18
+ describe('Percentage', () => {
19
+ it('Should return percentage', () => {
20
+ const price = 10;
21
+ const formattedPrice = percentage(price);
22
+ expect(formattedPrice).toBe('10%');
23
+ });
24
+ });
25
+ });
@@ -0,0 +1,134 @@
1
+ import { money, percentage } from '../utils';
2
+ import { ShopifyProductEntity, ShopifySellingPlanAllocationsEntity, ShopifySellingPlansEntity } from '../types/shopify';
3
+ import { ProductPlanEntity } from '../types/productPlan';
4
+
5
+ export const isPrepaidAllocation = (allocation: ShopifySellingPlanAllocationsEntity) =>
6
+ Array.isArray(allocation.selling_plan?.options) &&
7
+ allocation.selling_plan?.options.some(option => option?.name === 'Shipment amount');
8
+
9
+ export const getPrepaidShipmentsNumberFromOptions = options => {
10
+ if (options && options.length > 1) {
11
+ const shipmentAmountArray = options.find(option => option?.name === 'Shipment amount').value.split(' ');
12
+ return shipmentAmountArray.length > 0 ? +shipmentAmountArray[0] : null;
13
+ }
14
+
15
+ return null;
16
+ };
17
+
18
+ export const getAllocationFrequency = (allocation: ShopifySellingPlanAllocationsEntity) => {
19
+ // now frequency every_period will match with selling_plan_id so no need to convert it
20
+ return (allocation.selling_plan_id || allocation.selling_plan?.id).toString();
21
+ };
22
+
23
+ export const getAllocationRegularPrice = (allocation: ShopifySellingPlanAllocationsEntity) => {
24
+ return money(allocation.compare_at_price);
25
+ };
26
+
27
+ export const getAllocationSubscriptionPrice = (allocation: ShopifySellingPlanAllocationsEntity) => {
28
+ if (isPrepaidAllocation(allocation)) {
29
+ const prepaidShipmentsPerBilling = getPrepaidShipmentsNumberFromOptions(allocation.selling_plan?.options);
30
+ const pricePerShipment = allocation.price / prepaidShipmentsPerBilling;
31
+ return money(pricePerShipment);
32
+ }
33
+
34
+ return money(allocation.price);
35
+ };
36
+
37
+ const getPrepaidPercentage = (allocation: ShopifySellingPlanAllocationsEntity, pricePerShipment: number) => {
38
+ return ((allocation.compare_at_price - pricePerShipment) * 100) / allocation.compare_at_price;
39
+ };
40
+
41
+ export const getAllocationDiscountRate = (allocation: ShopifySellingPlanAllocationsEntity) => {
42
+ if (isPrepaidAllocation(allocation)) {
43
+ const prepaidShipmentsPerBilling = getPrepaidShipmentsNumberFromOptions(allocation.selling_plan?.options);
44
+ const pricePerShipment = allocation.price / prepaidShipmentsPerBilling;
45
+ const prepaidPercentageSavings = getPrepaidPercentage(allocation, pricePerShipment);
46
+
47
+ return percentage(prepaidPercentageSavings);
48
+ }
49
+
50
+ let formatted_discount = '';
51
+
52
+ if (allocation.price_adjustments[0]?.value_type === 'percentage') {
53
+ formatted_discount = percentage(allocation.price_adjustments[0].value);
54
+ } else if (allocation.price_adjustments[0]?.value) {
55
+ formatted_discount = money(allocation.price_adjustments[0].value);
56
+ } else if (allocation.compare_at_price) {
57
+ formatted_discount = money(allocation.compare_at_price - allocation.price);
58
+ }
59
+
60
+ return formatted_discount;
61
+ };
62
+
63
+ export const getAllocationNumberOfShipments = (allocation: ShopifySellingPlanAllocationsEntity) => {
64
+ if (isPrepaidAllocation(allocation)) {
65
+ return getPrepaidShipmentsNumberFromOptions(allocation.selling_plan?.options);
66
+ }
67
+ return null;
68
+ };
69
+
70
+ export const addPrepaidPriceAndSavings = (
71
+ allocation: ShopifySellingPlanAllocationsEntity,
72
+ productPlan: ProductPlanEntity,
73
+ payAsYouGoPlan: ShopifySellingPlansEntity
74
+ ) => {
75
+ const prepaidShipmentsPerBilling = getPrepaidShipmentsNumberFromOptions(allocation.selling_plan?.options);
76
+ const pricePerShipment = allocation.price / prepaidShipmentsPerBilling;
77
+ const prepaidSaving = allocation.compare_at_price - pricePerShipment;
78
+ const prepaidPercentageSavings = getPrepaidPercentage(allocation, pricePerShipment);
79
+ const payAsYouGoAdjustment = payAsYouGoPlan?.price_adjustments?.[0];
80
+ const payAsYouGoPercentage =
81
+ payAsYouGoAdjustment && payAsYouGoAdjustment.value_type === 'percentage' ? payAsYouGoAdjustment.value : null;
82
+
83
+ productPlan['regularPrepaidPrice'] = money(allocation.price);
84
+ productPlan['prepaidSavingsPerShipment'] = money(prepaidSaving);
85
+ productPlan['prepaidSavingsTotal'] = money(prepaidSaving * prepaidShipmentsPerBilling);
86
+
87
+ if (payAsYouGoPercentage && prepaidPercentageSavings) {
88
+ productPlan['prepaidExtraSavingsPercentage'] = percentage(prepaidPercentageSavings - payAsYouGoPercentage);
89
+ }
90
+
91
+ return productPlan;
92
+ };
93
+
94
+ export const DEFAULT_PAY_AS_YOU_GO_GROUP_NAME = 'Subscribe and Save';
95
+
96
+ export const mapSellingPlanToDiscount = (
97
+ allocation: ShopifySellingPlanAllocationsEntity,
98
+ sellingPlans: ShopifySellingPlansEntity[] = []
99
+ ) => {
100
+ if (!allocation.selling_plan) {
101
+ allocation.selling_plan = sellingPlans.find(plan => plan.id === allocation.selling_plan_id);
102
+ }
103
+
104
+ const productPlan: ProductPlanEntity = {
105
+ frequency: getAllocationFrequency(allocation),
106
+ regularPrice: getAllocationRegularPrice(allocation),
107
+ subscriptionPrice: getAllocationSubscriptionPrice(allocation),
108
+ discountRate: getAllocationDiscountRate(allocation),
109
+ prepaidShipments: getAllocationNumberOfShipments(allocation)
110
+ };
111
+
112
+ if (isPrepaidAllocation(allocation)) {
113
+ const payAsYouGoPlan = sellingPlans.find(
114
+ plan => plan.group_name === DEFAULT_PAY_AS_YOU_GO_GROUP_NAME && plan.options.length === 1
115
+ );
116
+ return addPrepaidPriceAndSavings(allocation, productPlan, payAsYouGoPlan);
117
+ }
118
+
119
+ return productPlan;
120
+ };
121
+
122
+ export const sellingPlanAllocationsReducer = (acc, cur, sellingPlans = []) => [
123
+ ...acc,
124
+ mapSellingPlanToDiscount(cur, sellingPlans)
125
+ ];
126
+
127
+ export const getSellingPlans = (product: ShopifyProductEntity) =>
128
+ product.selling_plan_groups.reduce(
129
+ (allGroups, group) => [
130
+ ...allGroups,
131
+ ...group.selling_plans.map(selling_plan => ({ ...selling_plan, group_name: group.name }))
132
+ ],
133
+ []
134
+ );
@@ -6,6 +6,7 @@ import {
6
6
  OPTIN_PRODUCT,
7
7
  OPTOUT_PRODUCT,
8
8
  PRODUCT_CHANGE_FREQUENCY,
9
+ PRODUCT_CHANGE_PREPAID_SHIPMENTS,
9
10
  RECEIVE_OFFER,
10
11
  REQUEST_OFFER,
11
12
  SETUP_CART,
@@ -345,6 +346,8 @@ export default function shopifyMiddleware(store) {
345
346
  case OPTOUT_PRODUCT:
346
347
  case PRODUCT_CHANGE_FREQUENCY:
347
348
  synchronizeCartOptin(action, store);
349
+ // eslint-disable-next-line no-fallthrough
350
+ case PRODUCT_CHANGE_PREPAID_SHIPMENTS:
348
351
  case REQUEST_OFFER:
349
352
  case RECEIVE_OFFER:
350
353
  case SETUP_PRODUCT:
@@ -21,36 +21,24 @@ import baseReducer, {
21
21
  previewUpsellOffer,
22
22
  productToSubscribe,
23
23
  sessionId,
24
- templates
24
+ templates,
25
+ prepaidShipmentsSelected
25
26
  } from '../core/reducer';
26
27
  import {
27
28
  getFirstSellingPlan,
28
29
  hasShopifySellingPlans,
29
30
  isOgFrequency,
30
31
  mapFrequencyToSellingPlan,
31
- safeProductId
32
+ safeProductId,
33
+ getMatchingProductIfExists
32
34
  } from '../core/utils';
33
35
 
34
- const money = val => (val === null ? '' : `$${val.toString().replace(/(\d\d)$/, '.$1')}`);
35
-
36
- const percentage = val => `${val}%`;
37
-
38
- const mapSellingPlanToDiscount = allocation => {
39
- let formatted_discount = '';
40
- if (allocation.price_adjustments[0]?.value_type === 'percentage') {
41
- formatted_discount = percentage(allocation.price_adjustments[0].value);
42
- } else if (allocation.price_adjustments[0]?.value) {
43
- formatted_discount = money(allocation.price_adjustments[0].value);
44
- } else if (allocation.compare_at_price) {
45
- formatted_discount = money(allocation.compare_at_price - allocation.price);
46
- }
47
-
48
- if (formatted_discount) {
49
- return [money(allocation.compare_at_price), formatted_discount, money(allocation.price)];
50
- }
51
-
52
- return [money(allocation.price), '', money(allocation.price)];
53
- };
36
+ import { getObjectStructuredProductPlans } from '../core/adapters';
37
+ import {
38
+ sellingPlanAllocationsReducer,
39
+ getSellingPlans,
40
+ DEFAULT_PAY_AS_YOU_GO_GROUP_NAME
41
+ } from './reducers/productPlans';
54
42
 
55
43
  const overrideLineKey = (state, productId, newValue) => {
56
44
  const keys = Object.keys(state).filter(it => it.startsWith(productId.toString()));
@@ -140,7 +128,7 @@ export const reduceNewOptinsFromOfferResponse = (
140
128
 
141
129
  const getOGSellingPlanGroup = product => {
142
130
  const sellingPlanGroup = product?.selling_plan_groups.find(group => {
143
- return group.name === 'Subscribe and Save';
131
+ return group.name === DEFAULT_PAY_AS_YOU_GO_GROUP_NAME;
144
132
  });
145
133
  return sellingPlanGroup;
146
134
  };
@@ -152,12 +140,6 @@ const productOrVariantInStockReducer = (acc, cur) => ({
152
140
 
153
141
  const productTrue = (acc, [id]) => ({ ...acc, [id]: true });
154
142
 
155
- const sellingPlanAllocationsReducer = (acc, cur) => ({
156
- ...acc,
157
- // now frequency every_period will match with selling_plan_id so no need to convert it
158
- [cur.selling_plan_id || cur.selling_plan?.id]: mapSellingPlanToDiscount(cur)
159
- });
160
-
161
143
  const reduceProductCartLine = (acc, cur) => {
162
144
  const productId = safeProductId(cur.key);
163
145
  return { ...acc, [cur.key]: acc[productId] || null };
@@ -210,6 +192,26 @@ export function sellingPlansToFrequencies(sellingPlanGroup) {
210
192
  return sellingPlanGroup?.selling_plans?.map(({ id }) => `${id}`);
211
193
  }
212
194
 
195
+ function getPrepaidShipments(sellingPlan) {
196
+ const shipments = sellingPlan?.options.find(({ name }) => name === 'Shipment amount')?.value.split(' ')[0];
197
+ return shipments ? Number(shipments) : undefined;
198
+ }
199
+
200
+ function getPrepaidSellingPlans(prepaidSellingPlanGroups) {
201
+ return prepaidSellingPlanGroups.reduce((acc, cur) => {
202
+ const variant = cur.name.split('-')[1];
203
+
204
+ const sellingPlanInfo = cur.selling_plans.map(sellingPlanObject => {
205
+ return {
206
+ numberShipments: getPrepaidShipments(sellingPlanObject),
207
+ sellingPlan: String(sellingPlanObject.id)
208
+ };
209
+ });
210
+
211
+ return { ...acc, [variant]: sellingPlanInfo };
212
+ }, {});
213
+ }
214
+
213
215
  export const config = (
214
216
  state = {
215
217
  frequencies: [],
@@ -236,8 +238,10 @@ export const config = (
236
238
  const {
237
239
  payload: { offer: offerEl, product }
238
240
  } = action;
241
+ let configToAdd = {};
242
+ // pay as you go selling plans
239
243
  const sellingPlanGroup = getOGSellingPlanGroup(product);
240
- const frequencies = sellingPlanGroup?.selling_plans?.map(({ id }) => `${id}`);
244
+ const frequencies = sellingPlansToFrequencies(sellingPlanGroup);
241
245
  if (frequencies?.length) {
242
246
  const frequenciesEveryPeriod = sellingPlansToEveryPeriod(sellingPlanGroup);
243
247
  const frequenciesText = sellingPlanGroup.options?.[0]?.values || frequencies;
@@ -249,15 +253,26 @@ export const config = (
249
253
  getFirstSellingPlan(frequencies) ||
250
254
  defaultFrequency;
251
255
  }
252
-
253
- return {
254
- ...state,
255
- frequenciesEveryPeriod,
256
+ configToAdd = {
256
257
  frequencies,
258
+ frequenciesEveryPeriod,
257
259
  frequenciesText,
258
260
  ...(defaultFrequency ? { defaultFrequency } : {})
259
261
  };
260
262
  }
263
+
264
+ // prepaid selling plans
265
+ const prepaidSellingPlanGroups = product?.selling_plan_groups.filter(group => /^Prepaid-.*/.test(group.name));
266
+ if (prepaidSellingPlanGroups.length) {
267
+ configToAdd = {
268
+ ...configToAdd,
269
+ prepaidSellingPlans: getPrepaidSellingPlans(prepaidSellingPlanGroups)
270
+ };
271
+ }
272
+ return {
273
+ ...state,
274
+ ...configToAdd
275
+ };
261
276
  }
262
277
 
263
278
  if (constants.RECEIVE_OFFER === action.type) {
@@ -310,20 +325,33 @@ export const inStock = (state = {}, action) => {
310
325
 
311
326
  export const offer = (state = {}, action) => state;
312
327
 
328
+ function getFrequencyForPrepaidShipments({ prepaidShipments, offer: offerEl, product }) {
329
+ if (prepaidShipments) {
330
+ const productId = product.id;
331
+ const plan = offerEl.config.prepaidSellingPlans[productId]?.find(p => p.numberShipments === prepaidShipments);
332
+ return plan ? plan.sellingPlan : null;
333
+ }
334
+ return offerEl.config.frequencies[0];
335
+ }
336
+
337
+ function getOptedInItem(cartItem) {
338
+ const prepaidShipments = getPrepaidShipments(cartItem.selling_plan_allocation.selling_plan);
339
+ const item = {
340
+ id: cartItem.key,
341
+ frequency: `${cartItem.selling_plan_allocation.selling_plan.id}`
342
+ };
343
+ if (prepaidShipments) {
344
+ item.prepaidShipments = prepaidShipments;
345
+ }
346
+ return item;
347
+ }
348
+
313
349
  export const optedin = (state = [], action) => {
314
350
  if (constants.SETUP_CART === action.type) {
315
351
  const cart = action.payload;
316
352
  return state
317
353
  .filter(it => !it.id.includes(':'))
318
- .concat(
319
- cart.items.reduce(
320
- (acc, cur) =>
321
- cur.selling_plan_allocation
322
- ? [...acc, { id: cur.key, frequency: `${cur.selling_plan_allocation.selling_plan.id}` }]
323
- : acc,
324
- []
325
- )
326
- );
354
+ .concat(cart.items.reduce((acc, cur) => (cur.selling_plan_allocation ? [...acc, getOptedInItem(cur)] : acc), []));
327
355
  }
328
356
 
329
357
  if (constants.RECEIVE_OFFER === action.type) {
@@ -354,6 +382,19 @@ export const optedin = (state = [], action) => {
354
382
  });
355
383
  }
356
384
 
385
+ if (constants.PRODUCT_CHANGE_PREPAID_SHIPMENTS === action.type) {
386
+ const { payload } = action;
387
+ // core reducer sets prepaid shipments
388
+ const newState = coreOptedin(state, action);
389
+ // get the new frequency (selling plan) that matches the prepaid shipments
390
+ const [oldone, rest] = getMatchingProductIfExists(newState, payload.product);
391
+ return rest.concat({
392
+ ...oldone,
393
+ ...payload.product,
394
+ frequency: getFrequencyForPrepaidShipments(payload)
395
+ });
396
+ }
397
+
357
398
  return coreOptedin(state, action);
358
399
  };
359
400
 
@@ -364,11 +405,18 @@ export const productPlans = (state = {}, action) => {
364
405
  const {
365
406
  payload: { product }
366
407
  } = action;
408
+
409
+ const sellingPlans = getSellingPlans(product);
410
+
367
411
  return (
412
+ // We consider the product here as well for cases where we don't have any variants
368
413
  [product, ...product?.variants]?.reduce(
369
414
  (acc, cur) => ({
370
415
  ...acc,
371
- [cur.id]: cur.selling_plan_allocations?.reduce(sellingPlanAllocationsReducer, {})
416
+ [cur.id]: cur.selling_plan_allocations?.reduce(
417
+ (accumulator, current) => sellingPlanAllocationsReducer(accumulator, current, sellingPlans),
418
+ []
419
+ )
372
420
  }),
373
421
  state
374
422
  ) || state
@@ -382,7 +430,7 @@ export const productPlans = (state = {}, action) => {
382
430
  cur.selling_plan_allocation
383
431
  ? {
384
432
  ...acc,
385
- [cur.key]: sellingPlanAllocationsReducer({}, cur.selling_plan_allocation)
433
+ [cur.key]: sellingPlanAllocationsReducer([], cur.selling_plan_allocation)
386
434
  }
387
435
  : acc,
388
436
  state
@@ -390,7 +438,7 @@ export const productPlans = (state = {}, action) => {
390
438
  );
391
439
  }
392
440
  if (constants.RECEIVE_PRODUCT_PLANS === action.type) {
393
- return { ...action.payload };
441
+ return getObjectStructuredProductPlans(action.payload);
394
442
  }
395
443
  return state;
396
444
  };
@@ -420,7 +468,8 @@ const reducer = combineReducers({
420
468
  productPlans,
421
469
  productToSubscribe,
422
470
  sessionId,
423
- templates
471
+ templates,
472
+ prepaidShipmentsSelected
424
473
  });
425
474
 
426
475
  export default function shopifyReducer(state, action) {
@@ -1,4 +1,4 @@
1
- import { OPTIN_PRODUCT, OPTOUT_PRODUCT, PRODUCT_CHANGE_FREQUENCY, RECEIVE_OFFER, SETUP_PRODUCT } from '../core/constants';
1
+ import { OPTIN_PRODUCT, OPTOUT_PRODUCT, PRODUCT_CHANGE_FREQUENCY, RECEIVE_OFFER } from '../core/constants';
2
2
  import { getTrackingEvent, getSubscribedFrequency } from './shopifyMiddleware';
3
3
 
4
4
  /**
@@ -0,0 +1,11 @@
1
+ export interface ProductPlanEntity {
2
+ frequency: string;
3
+ regularPrice: string;
4
+ subscriptionPrice: string;
5
+ discountRate: string;
6
+ prepaidShipments: number;
7
+ regularPrepaidPrice?: string;
8
+ prepaidSavingsPerShipment?: string;
9
+ prepaidSavingsTotal?: string;
10
+ prepaidExtraSavingsPercentage?: string;
11
+ }
@@ -0,0 +1,98 @@
1
+ export interface ShopifyPriceAdjustmentsEntity {
2
+ position: number;
3
+ price?: number;
4
+ order_count?: null;
5
+ value_type?: string;
6
+ value?: number;
7
+ }
8
+
9
+ export interface ShopifyAllOptionsEntity {
10
+ name: string;
11
+ position: number;
12
+ values?: string[] | null;
13
+ }
14
+
15
+ export interface ShopifyOptionsEntity {
16
+ name: string;
17
+ position: number;
18
+ value: string;
19
+ }
20
+
21
+ export interface ShopifySellingPlansEntity {
22
+ id: number;
23
+ name: string;
24
+ description?: null;
25
+ options?: ShopifyOptionsEntity[] | null;
26
+ recurring_deliveries: boolean;
27
+ price_adjustments?: ShopifyPriceAdjustmentsEntity[] | null;
28
+ group_name?: string;
29
+ }
30
+
31
+ export interface ShopifySellingPlanGroupsEntity {
32
+ id: string;
33
+ name: string;
34
+ options?: ShopifyAllOptionsEntity[] | null;
35
+ selling_plans?: ShopifySellingPlansEntity[] | null;
36
+ app_id?: null;
37
+ }
38
+
39
+ export interface ShopifySellingPlanAllocationsEntity {
40
+ price_adjustments?: ShopifyPriceAdjustmentsEntity[] | null;
41
+ price: number;
42
+ compare_at_price: number;
43
+ per_delivery_price: number;
44
+ selling_plan_id: number;
45
+ selling_plan_group_id: string;
46
+ selling_plan?: ShopifySellingPlansEntity;
47
+ }
48
+
49
+ export interface ShopifyVariantsEntity {
50
+ id: number;
51
+ title: string;
52
+ option1: string;
53
+ option2?: null;
54
+ option3?: null;
55
+ sku: string;
56
+ requires_shipping: boolean;
57
+ taxable: boolean;
58
+ featured_image?: null;
59
+ available: boolean;
60
+ name: string;
61
+ public_title: string;
62
+ options?: string[] | null;
63
+ price: number;
64
+ weight: number;
65
+ compare_at_price?: null;
66
+ inventory_management: string;
67
+ barcode: string;
68
+ requires_selling_plan: boolean;
69
+ selling_plan_allocations?: ShopifySellingPlanAllocationsEntity[] | null;
70
+ }
71
+
72
+ export interface ShopifyProductEntity {
73
+ id: number;
74
+ title: string;
75
+ handle: string;
76
+ description: string;
77
+ published_at: string;
78
+ created_at: string;
79
+ vendor: string;
80
+ type: string;
81
+ tags?: null[] | null;
82
+ price: number;
83
+ price_min: number;
84
+ price_max: number;
85
+ available: boolean;
86
+ price_varies: boolean;
87
+ compare_at_price?: null;
88
+ compare_at_price_min: number;
89
+ compare_at_price_max: number;
90
+ compare_at_price_varies: boolean;
91
+ variants?: ShopifyVariantsEntity[] | null;
92
+ images?: null[] | null;
93
+ featured_image?: null;
94
+ options?: ShopifyAllOptionsEntity[] | null;
95
+ url: string;
96
+ requires_selling_plan: boolean;
97
+ selling_plan_groups?: ShopifySellingPlanGroupsEntity[] | null;
98
+ }
@@ -0,0 +1,3 @@
1
+ export const money = val => (val === null ? '' : `$${val.toString().replace(/(\d\d)$/, '.$1')}`);
2
+
3
+ export const percentage = val => `${val}%`;