@hanzo/commerce 5.1.3 → 6.1.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 (67) hide show
  1. package/components/{buy-item/select-category-item-card.tsx → buy/_to_deprecate_select-category-item-card.tsx} +22 -32
  2. package/components/{select-category-item-panel.tsx → buy/_to_deprecate_select-category-item-panel.tsx} +16 -22
  3. package/components/{buy-item/buy-item-popup.tsx → buy/_unused_buy-popup.tsx_} +4 -4
  4. package/components/{add-to-cart-widget.tsx → buy/add-to-cart-widget.tsx} +10 -6
  5. package/components/buy/buy-button.tsx +31 -0
  6. package/components/buy/buy-card.tsx +267 -0
  7. package/components/buy/buy-drawer.tsx +89 -0
  8. package/components/buy/buy-trigger-wrapper.tsx +20 -0
  9. package/components/{cart-accordian.tsx → cart/cart-accordian.tsx} +2 -1
  10. package/components/{cart-panel → cart/cart-panel}/cart-line-item.tsx +27 -14
  11. package/components/{cart-panel → cart/cart-panel}/index.tsx +12 -3
  12. package/components/{cart-panel → cart/cart-panel}/promo-code.tsx +3 -3
  13. package/components/{payment-step-form → checkout/payment-step-form}/contact-form.tsx +1 -1
  14. package/components/{payment-step-form → checkout/payment-step-form}/index.tsx +3 -3
  15. package/components/{payment-step-form → checkout/payment-step-form}/methods/bank-transfer.tsx +1 -1
  16. package/components/{payment-step-form → checkout/payment-step-form}/methods/card.tsx +4 -4
  17. package/components/{payment-step-form → checkout/payment-step-form}/methods/crypto.tsx +3 -3
  18. package/components/{payment-step-form → checkout/payment-step-form}/methods/index.ts +1 -1
  19. package/components/{shipping-step-form.tsx → checkout/shipping-step-form.tsx} +4 -4
  20. package/components/index.ts +20 -18
  21. package/components/item/item-media.tsx +51 -0
  22. package/components/item/product-card.tsx +48 -0
  23. package/components/{facet-values-widget → level-nodes-widget}/index.tsx +20 -19
  24. package/components/level-nodes-widget/node-image.tsx +31 -0
  25. package/components/select/carousel-selector.tsx +112 -0
  26. package/components/select/image-selector.tsx +162 -0
  27. package/components/select/radio-selector.tsx +125 -0
  28. package/package.json +3 -2
  29. package/service/context.tsx +4 -4
  30. package/service/impls/standalone/actual-line-item.ts +30 -16
  31. package/service/impls/standalone/index.ts +4 -4
  32. package/service/impls/standalone/standalone-service.ts +94 -64
  33. package/types/buy-ui-spec.ts +8 -0
  34. package/types/category.ts +6 -4
  35. package/types/commerce-service.ts +21 -16
  36. package/types/index.ts +3 -2
  37. package/types/item-selector.ts +34 -6
  38. package/types/line-item.ts +2 -0
  39. package/types/product.ts +5 -5
  40. package/types/tree-node.ts +24 -0
  41. package/util/buy-ui-conf.ts +34 -0
  42. package/util/index.ts +14 -11
  43. package/util/obs-string-mutator.ts +22 -0
  44. package/util/use-sync-sku-param-w-current-item.ts +5 -5
  45. package/components/buy-item/buy-item-button-wrapper.tsx +0 -27
  46. package/components/buy-item/buy-item-button.tsx +0 -34
  47. package/components/buy-item/buy-item-card.tsx +0 -92
  48. package/components/buy-item/buy-item-mobile-drawer.tsx +0 -54
  49. package/components/buy-item/select-category-and-item-widget.tsx +0 -83
  50. package/components/category-item-radio-selector.tsx +0 -55
  51. package/components/category-item-scroll-selector.tsx +0 -40
  52. package/components/facet-values-widget/facet-image.tsx +0 -41
  53. package/components/item-carousel.tsx +0 -80
  54. package/components/product-card.tsx +0 -79
  55. package/types/facet.ts +0 -35
  56. /package/components/{cart-icon.tsx → cart/cart-icon.tsx} +0 -0
  57. /package/components/{payment-step-form → checkout/payment-step-form}/card-icon-row.tsx +0 -0
  58. /package/components/{payment-step-form → checkout/payment-step-form}/card-icons/amex.tsx +0 -0
  59. /package/components/{payment-step-form → checkout/payment-step-form}/card-icons/diners-club.tsx +0 -0
  60. /package/components/{payment-step-form → checkout/payment-step-form}/card-icons/discover.tsx +0 -0
  61. /package/components/{payment-step-form → checkout/payment-step-form}/card-icons/jcb.tsx +0 -0
  62. /package/components/{payment-step-form → checkout/payment-step-form}/card-icons/mastercard.tsx +0 -0
  63. /package/components/{payment-step-form → checkout/payment-step-form}/card-icons/visa.tsx +0 -0
  64. /package/components/{payment-step-form → checkout/payment-step-form}/cc-button.tsx +0 -0
  65. /package/components/{payment-step-form → checkout/payment-step-form}/crypto-icons/btc.tsx +0 -0
  66. /package/components/{payment-step-form → checkout/payment-step-form}/crypto-icons/eth.tsx +0 -0
  67. /package/components/{payment-step-form → checkout/payment-step-form}/crypto-icons/usdt.tsx +0 -0
@@ -13,8 +13,8 @@ import type {
13
13
  CommerceService,
14
14
  Category,
15
15
  LineItem,
16
- FacetsValue,
17
- FacetValueDesc,
16
+ SelectedPaths,
17
+ ProductTreeNode,
18
18
  Promo
19
19
  } from '../../../types'
20
20
 
@@ -42,8 +42,8 @@ class StandaloneService
42
42
  implements CommerceService
43
43
  {
44
44
  private _categoryMap = new Map<string, Category>()
45
- private _rootFacet: FacetValueDesc
46
- private _selectedFacets: FacetsValue = {}
45
+ private _rootNode: ProductTreeNode
46
+ private _selectedPaths: SelectedPaths = {}
47
47
  private _promo: Promo | null = null
48
48
 
49
49
  private _options : StandaloneServiceOptions
@@ -51,12 +51,12 @@ class StandaloneService
51
51
 
52
52
  constructor(
53
53
  categories: Category[],
54
- rootFacet: FacetValueDesc,
54
+ rootNode: ProductTreeNode,
55
55
  options: StandaloneServiceOptions,
56
56
  serviceSnapshot?: StandaloneServiceSnapshot,
57
57
  ) {
58
58
 
59
- this._rootFacet = rootFacet
59
+ this._rootNode = rootNode
60
60
  this._options = options
61
61
 
62
62
  categories.forEach((c) => {
@@ -74,12 +74,12 @@ class StandaloneService
74
74
 
75
75
  makeObservable<
76
76
  StandaloneService,
77
- '_selectedFacets' |
77
+ '_selectedPaths' |
78
78
  '_currentItem' |
79
79
  '_promo'
80
80
  >(this, {
81
- _selectedFacets : observable.deep,
82
- _currentItem: observable,
81
+ _selectedPaths : observable.deep,
82
+ _currentItem: observable.shallow,
83
83
  _promo: observable,
84
84
  })
85
85
 
@@ -89,54 +89,53 @@ class StandaloneService
89
89
  cartTotal: computed,
90
90
  promoAppliedCartTotal: computed,
91
91
  cartEmpty: computed,
92
- specifiedItems: computed,
93
- specifiedCategories: computed,
92
+ selectedItems: computed,
93
+ selectedCategories: computed,
94
+ hasSelection: computed,
94
95
  setCurrentItem: action,
95
96
  currentItem: computed,
96
97
  item: computed,
97
- facetsValue: computed,
98
+ selectedPaths: computed,
98
99
  appliedPromo: computed,
99
100
  setAppliedPromo: action,
100
- /* NOT setFacets. It implements it's action mechanism */
101
+ /* NOT selectPaths. It implements it's action mechanism */
101
102
  })
102
103
  }
103
104
 
104
-
105
105
  getCategory(id: string): Category | undefined {
106
106
  return this._categoryMap.get(id)
107
107
  }
108
108
 
109
- getFacetValuesAtSkuPath(skuPath: string): FacetValueDesc[] | undefined {
109
+ getNodeAtPath(skuPath: string): ProductTreeNode | undefined {
110
110
  const toks = skuPath.split(SEP)
111
111
  let level = 1
112
- let valuesAtLevel: FacetValueDesc[] | undefined = this._rootFacet.sub
112
+ let desc: ProductTreeNode | undefined = this._rootNode
113
113
  do {
114
- const fvalue = valuesAtLevel!.find((vf) => (vf.value === toks[level]))
115
- valuesAtLevel = fvalue ? fvalue.sub : undefined
114
+ desc = desc!.subNodes?.find((vf) => (vf.skuToken === toks[level]))
116
115
  level++
117
116
  }
118
- while (valuesAtLevel && (level < toks.length))
119
- return level === toks.length ? valuesAtLevel : undefined
117
+ while (desc && (level < toks.length))
118
+ return level === toks.length ? desc : undefined
120
119
  }
121
120
 
122
- getFacetValuesSpecified = computedFn((level: number): FacetValueDesc[] | undefined => {
121
+ getSelectedNodesAtLevel = computedFn((level: number): ProductTreeNode[] | undefined => {
123
122
 
124
123
  let lvl = 1
125
- let valuesAtLevel: FacetValueDesc[] | undefined = this._rootFacet.sub
124
+ let valuesAtLevel: ProductTreeNode[] | undefined = this._rootNode.subNodes
126
125
 
127
126
  do {
128
- let selectedAtLevel: FacetValueDesc[] | undefined = undefined
127
+ let selectedAtLevel: ProductTreeNode[] | undefined = undefined
129
128
  // If not specified, assume all
130
- if (lvl in this._selectedFacets) {
131
- selectedAtLevel = valuesAtLevel!.filter((fv) => (this._selectedFacets[lvl].includes(fv.value)))
129
+ if (lvl in this._selectedPaths) {
130
+ selectedAtLevel = valuesAtLevel!.filter((fv) => (this._selectedPaths[lvl].includes(fv.skuToken)))
132
131
  }
133
132
  else {
134
133
  selectedAtLevel = valuesAtLevel
135
134
  }
136
- let allSubsOfSelected: FacetValueDesc[] = []
137
- selectedAtLevel?.forEach((fvd: FacetValueDesc) => {
138
- if (fvd.sub) {
139
- allSubsOfSelected = [...allSubsOfSelected, ...fvd.sub]
135
+ let allSubsOfSelected: ProductTreeNode[] = []
136
+ selectedAtLevel?.forEach((fvd: ProductTreeNode) => {
137
+ if (fvd.subNodes) {
138
+ allSubsOfSelected = [...allSubsOfSelected, ...fvd.subNodes]
140
139
  }
141
140
  })
142
141
 
@@ -166,7 +165,7 @@ class StandaloneService
166
165
  }
167
166
 
168
167
  takeSnapshot = (): StandaloneServiceSnapshot => ({
169
- items : (this.cartItems as ActualLineItem[]).map((it) => (it.takeSnapshot()))
168
+ items : (this.cartItems as ActualLineItem[]).map((it) => (it.takeSnapshot(this)))
170
169
  })
171
170
 
172
171
  get cartItems(): LineItem[] {
@@ -199,11 +198,9 @@ class StandaloneService
199
198
  if (!this._promo) {
200
199
  return this.cartTotal
201
200
  }
202
-
203
201
  if (!this._promo.skus) {
204
202
  return this._promoValue_unsafe(this.cartTotal)
205
203
  }
206
-
207
204
  let total = this.cartItems.reduce(
208
205
  (total, item) => {
209
206
  const itemPrice = this._promo!.skus!.includes(item.sku) ?
@@ -214,7 +211,6 @@ class StandaloneService
214
211
  },
215
212
  0
216
213
  )
217
-
218
214
  return total
219
215
  }
220
216
 
@@ -242,19 +238,29 @@ class StandaloneService
242
238
 
243
239
  setCurrentItem(skuToFind: string | undefined): boolean {
244
240
 
241
+ const logMe = (s: string) => {
242
+ //if (skuToFind?.startsWith('LXM-CR-E')) {
243
+ //console.log(s)
244
+ //}
245
+ }
246
+
245
247
  if (skuToFind === undefined || skuToFind.length === 0) {
246
248
  this._currentItem = undefined
247
249
  return true
248
250
  }
251
+
252
+ logMe("SETTING ITEM: " + skuToFind)
253
+
249
254
  // self calling function
250
255
  this._currentItem = ((): ActualLineItem | undefined => {
251
256
 
252
257
  const categoriesTried: string[] = []
253
- if (this.specifiedCategories && this.specifiedCategories.length > 0) {
254
- for (let category of this.specifiedCategories) {
258
+ if (this.selectedCategories && this.selectedCategories.length > 0) {
259
+ for (let category of this.selectedCategories) {
255
260
  categoriesTried.push(category.id)
256
261
  const foundItem = category.products.find((p) => (p.sku === skuToFind))
257
262
  if (foundItem) {
263
+ logMe("FOUND 1st LOOP")
258
264
  return foundItem as ActualLineItem
259
265
  }
260
266
  }
@@ -263,12 +269,15 @@ class StandaloneService
263
269
  if (categoriesTried.includes(categoryId)) continue
264
270
  const foundItem = category.products.find((p) => (p.sku === skuToFind)) as ActualLineItem | undefined
265
271
  if (foundItem) {
272
+ logMe("FOUND 2nd LOOP")
266
273
  return foundItem as ActualLineItem
267
274
  }
268
275
  }
276
+ logMe("NOT FOUND")
269
277
  return undefined
270
278
  })();
271
279
 
280
+ logMe("CURRENT ITEM SET TO: " + this._currentItem?.sku)
272
281
  return !!this._currentItem
273
282
  }
274
283
 
@@ -281,52 +290,61 @@ class StandaloneService
281
290
  return this._currentItem
282
291
  }
283
292
 
284
- setFacets(sel: FacetsValue): Category[] {
293
+ selectPaths(sel: SelectedPaths): Category[] {
285
294
  runInAction (() => {
286
- this._selectedFacets = this._processAndValidate(sel)
295
+ this._selectedPaths = this._processAndValidate(sel)
287
296
  })
288
- return this.specifiedCategories
297
+ return this.selectedCategories
298
+ }
299
+
300
+ selectPath(skuPath: string): Category[] {
301
+ const toks = skuPath.split(SEP)
302
+ const highestLevel = toks.length - 1
303
+ const fsv: SelectedPaths = {}
304
+ for (let level = 1; level <= highestLevel; level++ ) {
305
+ fsv[level] = [toks[level]]
306
+ }
307
+ return this.selectPaths(fsv)
289
308
  }
290
309
 
291
- get facetsValue(): FacetsValue {
292
- const result: FacetsValue = {}
293
- for( let level in this._selectedFacets ) {
294
- result[level] = [...this._selectedFacets[level]]
310
+ get selectedPaths(): SelectedPaths {
311
+ const result: SelectedPaths = {}
312
+ for( let level in this._selectedPaths ) {
313
+ result[level] = [...this._selectedPaths[level]]
295
314
  }
296
315
  return result
297
316
  }
298
317
 
299
-
300
- get specifiedCategories(): Category[] {
301
- if (Object.keys(toJS(this._selectedFacets)).length === 0) {
318
+ get selectedCategories(): Category[] {
319
+ if (Object.keys(toJS(this._selectedPaths)).length === 0) {
302
320
  // FacetsDesc have never been set or unset, so cannot evaluate them
303
321
  return []
304
322
  }
305
323
 
306
- return this._rootFacet.sub!.reduce(
307
- (acc: Category[], subFacet: FacetValueDesc) => (
324
+ return this._rootNode.subNodes!.reduce(
325
+ (acc: Category[], subFacet: ProductTreeNode) => (
308
326
  // Pass the root token as a one member array
309
- this._reduceNode([this._rootFacet.value], acc, subFacet)
327
+ this._reduceNode([this._rootNode.skuToken], acc, subFacet)
310
328
  ),
311
329
  []
312
330
  )
313
331
  }
314
332
 
315
- private _reduceNode(parentPath: string[], acc: Category[], node: FacetValueDesc): Category[] {
316
- const path = [...parentPath, node.value] // Don't mutate original please :)
333
+ private _reduceNode(parentPath: string[], acc: Category[], node: ProductTreeNode): Category[] {
334
+ const path = [...parentPath, node.skuToken] // Don't mutate original please :)
317
335
  const level = path.length - 1
318
336
  // If there is no token array supplied for this level,
319
337
  // assume all are specified. Otherwise, see if the
320
338
  // current node is in the array
321
339
  const specified = (
322
- !this._selectedFacets[level]
340
+ !this._selectedPaths[level]
323
341
  ||
324
- this._selectedFacets[level].includes(node.value)
342
+ this._selectedPaths[level].includes(node.skuToken)
325
343
  )
326
344
  if (specified) {
327
345
  // Process subnodes
328
- if (node.sub && node.sub.length > 0) {
329
- return node.sub.reduce((acc, n) => (
346
+ if (node.subNodes && node.subNodes.length > 0) {
347
+ return node.subNodes.reduce((acc, n) => (
330
348
  this._reduceNode(path, acc, n)
331
349
  )
332
350
  , acc)
@@ -334,30 +352,30 @@ class StandaloneService
334
352
  // Process leaf
335
353
  const cat = this._categoryMap.get(path.join(SEP))
336
354
  if (!cat) {
337
- throw new Error("specifiedCategories WTF?!" + path.join(SEP))
355
+ throw new Error("selectedCategories WTF?!" + path.join(SEP))
338
356
  }
339
357
  acc.push(cat)
340
358
  }
341
359
  return acc
342
360
  }
343
361
 
344
- private _processAndValidate(partial: FacetsValue): FacetsValue {
362
+ private _processAndValidate(partial: SelectedPaths): SelectedPaths {
345
363
 
346
- const result: FacetsValue = {}
364
+ const result: SelectedPaths = {}
347
365
 
348
366
  let level = 1
349
- let currentSet = this._rootFacet.sub!
367
+ let currentSet = this._rootNode.subNodes!
350
368
 
351
369
  while (true) {
352
- let possibleCurrent = currentSet.map((el) => (el.value))
370
+ let possibleCurrent = currentSet.map((el) => (el.skuToken))
353
371
  const validTokens = !partial[level] ? undefined : partial[level].filter((tok) => possibleCurrent.includes(tok))
354
372
  if (!validTokens) {
355
373
  break
356
374
  }
357
375
  result[level] = validTokens
358
376
  currentSet = validTokens.map((tok) => {
359
- const fd = currentSet.find((node) => ( node.value === tok ))
360
- return (fd && fd.sub && fd.sub.length > 0) ? fd.sub : []
377
+ const fd = currentSet.find((node) => ( node.skuToken === tok ))
378
+ return (fd && fd.subNodes && fd.subNodes.length > 0) ? fd.subNodes : []
361
379
  }).flat()
362
380
  level++
363
381
  }
@@ -365,16 +383,20 @@ class StandaloneService
365
383
  return result
366
384
  }
367
385
 
368
- get specifiedItems(): LineItem[] {
369
- if (Object.keys(toJS(this._selectedFacets)).length === 0) {
386
+ get selectedItems(): LineItem[] {
387
+ if (Object.keys(toJS(this._selectedPaths)).length === 0) {
370
388
  // FacetsDesc have never been set or unset, so cannot evaluate them
371
389
  return []
372
390
  }
373
391
 
374
- return this.specifiedCategories.reduce(
392
+ return this.selectedCategories.reduce(
375
393
  (allProducts, cat) => ([...allProducts, ...(cat.products as LineItem[])]), [] as LineItem[])
376
394
  }
377
395
 
396
+ get hasSelection(): boolean {
397
+ return this.selectedCategories.length > 0
398
+ }
399
+
378
400
  getCartCategorySubtotal(categoryId: string): number {
379
401
  const c = this._categoryMap.get(categoryId)!
380
402
  return (c.products as LineItem[]).reduce(
@@ -383,6 +405,14 @@ class StandaloneService
383
405
  0
384
406
  )
385
407
  }
408
+
409
+ debug_getSelectedCategories = () => {
410
+ const spec = this.selectedCategories
411
+ console.log('NUM SPEC CATS: ', spec.length)
412
+ if (spec.length > 0) {
413
+ console.log('IDS: ', (spec.map((c) => (c.id))).join(', '))
414
+ }
415
+ }
386
416
  }
387
417
 
388
418
  export {
@@ -0,0 +1,8 @@
1
+ interface BuyUISpec {
2
+ selector: 'carousel' | 'radio' | 'image'
3
+ allVariants: boolean
4
+ }
5
+
6
+ export {
7
+ type BuyUISpec as default
8
+ }
package/types/category.ts CHANGED
@@ -1,11 +1,13 @@
1
+ import type { ImageDef } from '@hanzo/ui/types'
1
2
  import type Product from './product'
2
3
 
3
4
  interface Category {
4
- id: string // LXB-AU-B
5
- title: string // Minted Bar
6
- parentTitle?: string // Lux Gold
5
+ id: string // LXB-AU-B
6
+ title: string // Minted Bar, Lux Elite Card
7
+ titleShort?: string // Elite
8
+ parentTitle?: string // Lux Gold, Lux Credit
7
9
  desc?: string
8
- img?: string
10
+ img?: ImageDef,
9
11
  // inbound they're Products and then interally they become LineItems
10
12
  products: Product[]
11
13
  }
@@ -1,5 +1,5 @@
1
1
  import type { LineItem, ObsLineItemRef } from './line-item'
2
- import type { FacetValueDesc, FacetsValue } from './facet'
2
+ import type { ProductTreeNode, SelectedPaths } from './tree-node'
3
3
  import type Category from './category'
4
4
  import type Promo from './promo'
5
5
 
@@ -29,36 +29,41 @@ interface CommerceService extends ObsLineItemRef {
29
29
 
30
30
  /**
31
31
  * Sets the tokens at each level supplied.
32
- * If a level is specifed as [], nothing will be specified.
33
- * If a level is missing (undefined), everything at that level is included
32
+ * If a level is selected as [], nothing will be selected.
33
+ * If a level is missing (undefined), everything at that level is selected
34
34
  *
35
- * This specifies one or more Category's, and all the LineItem's in them
35
+ * This selects one or more Category's, and all the LineItem's in them.
36
36
  *
37
- * An empty value object specifies all Category's and all LineItem's,
37
+ * An empty value object selects all Category's and all LineItem's,
38
38
  * */
39
- setFacets(value: FacetsValue): Category[]
40
- get facetsValue(): FacetsValue // returns a copy
41
- get specifiedItems(): LineItem[]
42
- get specifiedCategories(): Category[]
39
+ selectPaths(value: SelectedPaths): Category[]
40
+ selectPath(skuPath: string): Category[]
41
+
42
+ get selectedPaths(): SelectedPaths // returns a copy
43
+
44
+ get selectedItems(): LineItem[]
45
+ get selectedCategories(): Category[]
46
+ get hasSelection(): boolean
43
47
 
44
48
  /** Whether this path defines a Category, or if it has further levels */
45
- getFacetValuesAtSkuPath(skuPath: string): FacetValueDesc[] | undefined
49
+ getNodeAtPath(skuPath: string): ProductTreeNode | undefined
46
50
 
47
- /** Based on current value of 'level', what are the available subfacets?
48
- * If more than one value is specified at 'level' returned FacetValueDesc[]
49
- * may represent multiple sets.
51
+ /**
52
+ * Who are the subnodes selected at 'level'?
53
+ * If more than one value is selected at 'level',
54
+ * the returned ProductTreeNode[] may represent multiple sets.
50
55
  * */
51
- getFacetValuesSpecified(level: number): FacetValueDesc[] | undefined
56
+ getSelectedNodesAtLevel(level: number): ProductTreeNode[] | undefined
52
57
 
53
58
  /**
54
59
  * For convenience, so widgets can share state.
55
- * "current" is unrelated to what is "specified",
60
+ * "current" is unrelated to what is "selected",
56
61
  * ie, facets' values
57
62
  * */
58
63
  setCurrentItem(sku: string | undefined): boolean // valid sku and was set.
59
64
  /**
60
65
  * For convenience, so widgets can share state.
61
- * "current" is unrelated to what is "specified",
66
+ * "current" is unrelated to what is "selected",
62
67
  * ie, facets' values
63
68
  *
64
69
  * note: for ObsLineItemRef, there is also
package/types/index.ts CHANGED
@@ -1,11 +1,12 @@
1
1
 
2
2
  export type { default as Category } from './category'
3
3
  export type { default as CommerceService } from './commerce-service'
4
- export type { default as ItemSelector } from './item-selector'
5
4
  export type { default as Product } from './product'
6
5
  export type { default as Promo } from './promo'
6
+ export type { default as BuyUISpec } from './buy-ui-spec'
7
7
 
8
8
  export * from './checkout'
9
+ export * from './item-selector'
9
10
  export * from './line-item'
10
- export * from './facet'
11
+ export * from './tree-node'
11
12
  export * from './string-mutator'
@@ -1,14 +1,42 @@
1
- import type Category from './category'
2
- import type { ObsLineItemRef } from './line-item'
1
+ import type { LineItem, ObsLineItemRef } from './line-item'
3
2
 
4
3
  interface ItemSelector {
5
- category: Category
4
+ items: LineItem[]
6
5
  selectedItemRef: ObsLineItemRef
7
6
  selectSku: (sku: string) => void
8
- showPrice?: boolean // true by default
9
- showQuantity?: boolean // true by default (not impl)
10
7
  }
11
8
 
9
+ interface _ItemSelectorCompProps {
10
+ clx?: string
11
+ itemClx?: string
12
+ soleItemClx?: string
13
+ /** type-specific props for ItemSelector's.
14
+ * eg, Carousel options.
15
+ */
16
+ ext?: any
17
+ /** List selectors will scroll. Used internally */
18
+ scrollList: boolean
19
+ /**
20
+ * Whether the item label includes the Category name.
21
+ * eg, 'Minted Bar, 1oz' vs '1oz'
22
+ * default: true in 'allVariants' mode and false otherwise.
23
+ * This overrides
24
+ */
25
+ showCategory?: boolean
26
+ /**
27
+ * Show the current item quantity along with title and price.
28
+ * default: false
29
+ */
30
+ showQuantity?: boolean
31
+ }
32
+
33
+ type ItemSelectorCompProps = Omit<_ItemSelectorCompProps, 'scrollList'>
34
+
35
+ interface ItemSelectorProps extends
36
+ ItemSelector, _ItemSelectorCompProps {}
37
+
12
38
  export {
13
- type ItemSelector as default
39
+ type ItemSelector,
40
+ type ItemSelectorCompProps,
41
+ type ItemSelectorProps
14
42
  }
@@ -18,6 +18,8 @@ interface LineItem extends Product {
18
18
 
19
19
  increment(): void
20
20
  decrement(): void
21
+
22
+ get title(): string
21
23
  }
22
24
 
23
25
  interface ObsLineItemRef {
package/types/product.ts CHANGED
@@ -1,15 +1,15 @@
1
- import type { VideoDef } from '@hanzo/ui/types'
1
+ import type { ImageDef, VideoDef } from '@hanzo/ui/types'
2
2
 
3
3
  interface Product {
4
4
  id: string // DB index // not a logical aspect of our domain. may not be necessary at all
5
5
  sku: string // human visible on orders etc.
6
- title: string
7
- shortTitle?: string
8
- titleAsOption: string
6
+ fullTitle?: string
7
+ optionLabel: string
9
8
  categoryId: string // skuPath, eg LXB-AU-B
9
+ categoryTitle: string
10
10
  desc?: string
11
11
  price: number
12
- img?: string // if undefined: (category's img exists) ? (use it) : (use generic placeholder)
12
+ img?: ImageDef // if undefined: (category's img exists) ? (use it) : (use generic placeholder)
13
13
  animation?: string // spline scene url
14
14
  video?: VideoDef
15
15
  }
@@ -0,0 +1,24 @@
1
+ import type { ReactNode } from 'react'
2
+
3
+ interface ProductTreeNode {
4
+ skuToken: string // a token in the sku
5
+ label: string
6
+ img? : string | ReactNode // icon is required
7
+ imgAR? : number // helps with svgs
8
+
9
+ /** Short desc of what subNodes refer to.
10
+ * For example, if this node's label is 'Lux Credit',
11
+ * it's subNodesLabel would be 'Level',
12
+ * refering to membership 'levels' ('black', 'elite', etc)
13
+ */
14
+ subNodesLabel?: string
15
+ subNodes?: ProductTreeNode[]
16
+ }
17
+
18
+ // Which facets tokens are 'on' at each level
19
+ type SelectedPaths = Record<number, string[]>
20
+
21
+ export type {
22
+ ProductTreeNode,
23
+ SelectedPaths
24
+ }
@@ -0,0 +1,34 @@
1
+ import type { BuyUISpec } from '../types'
2
+
3
+ const map = new Map<string, BuyUISpec>([
4
+ ['LXM_AU', {
5
+ selector: 'radio',
6
+ allVariants: false
7
+ }],
8
+ ['LXM_AG', {
9
+ selector: 'radio',
10
+ allVariants: false
11
+ }],
12
+ ['LXM_CN', {
13
+ selector: 'image',
14
+ allVariants: false
15
+ }],
16
+ // only one option so image / radio doesn't have meaning
17
+ ['LXM_PS', {
18
+ selector: 'image',
19
+ allVariants: false
20
+ }],
21
+ ['LXM-VL', {
22
+ selector: 'radio',
23
+ allVariants: false
24
+ }],
25
+ ['LXM-CR', {
26
+ selector: 'carousel',
27
+ allVariants: true
28
+ }],
29
+ ])
30
+
31
+ export default (skuPath: string): BuyUISpec | undefined => {
32
+ const key = skuPath.split('-').slice(0, 2).join('-')
33
+ return map.get(key)
34
+ }
package/util/index.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { StringMutator, CommerceService, Promo } from '../types'
1
+ import type { StringMutator, CommerceService } from '../types'
2
2
 
3
3
  export function toTitleCase(str: string) {
4
4
  return str.replace(
@@ -32,19 +32,19 @@ export function formatCurrencyValue(price: number): string {
32
32
  export const getFacetValuesMutator = (level: number, cmmc: CommerceService): StringMutator => {
33
33
 
34
34
  const setLevel = (value: string, level: number ): void => {
35
- const facets = cmmc.facetsValue
35
+ const facets = cmmc.selectedPaths
36
36
  facets[level] = [value]
37
- cmmc.setFacets(facets)
38
- const subFacets = cmmc.getFacetValuesSpecified(level)
37
+ cmmc.selectPaths(facets)
38
+ const subFacets = cmmc.getSelectedNodesAtLevel(level)
39
39
  if (subFacets) {
40
- const facets = cmmc.facetsValue
41
- facets[level + 1] = [subFacets[0].value]
42
- cmmc.setFacets(facets)
40
+ const facets = cmmc.selectedPaths
41
+ facets[level + 1] = [subFacets[0].skuToken]
42
+ cmmc.selectPaths(facets)
43
43
  }
44
44
  }
45
45
 
46
46
  const getLevelValueSafe = (level: number): string | null => {
47
- const facets = cmmc.facetsValue
47
+ const facets = cmmc.selectedPaths
48
48
  if (!(level in facets) || facets[level].length === 0 ) {
49
49
  return null
50
50
  }
@@ -52,11 +52,14 @@ export const getFacetValuesMutator = (level: number, cmmc: CommerceService): Str
52
52
  }
53
53
 
54
54
  return {
55
- get: () => (getLevelValueSafe(level)),
56
- set: (v: string) => {setLevel(v, level)}
55
+ get: (): string | null => (getLevelValueSafe(level)),
56
+ set: (v: string): void => {setLevel(v, level)}
57
57
  } satisfies StringMutator
58
58
  }
59
59
 
60
60
 
61
61
  export { default as useSyncSkuParamWithCurrentItem } from './use-sync-sku-param-w-current-item'
62
- export { default as processSquareCardPayment } from './square-payment'
62
+ export { default as processSquareCardPayment } from './square-payment'
63
+ export { default as ObsStringMutator } from './obs-string-mutator'
64
+
65
+ export { default as getUISpecFromPath } from './buy-ui-conf'