@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.
- package/components/{buy-item/select-category-item-card.tsx → buy/_to_deprecate_select-category-item-card.tsx} +22 -32
- package/components/{select-category-item-panel.tsx → buy/_to_deprecate_select-category-item-panel.tsx} +16 -22
- package/components/{buy-item/buy-item-popup.tsx → buy/_unused_buy-popup.tsx_} +4 -4
- package/components/{add-to-cart-widget.tsx → buy/add-to-cart-widget.tsx} +10 -6
- package/components/buy/buy-button.tsx +31 -0
- package/components/buy/buy-card.tsx +267 -0
- package/components/buy/buy-drawer.tsx +89 -0
- package/components/buy/buy-trigger-wrapper.tsx +20 -0
- package/components/{cart-accordian.tsx → cart/cart-accordian.tsx} +2 -1
- package/components/{cart-panel → cart/cart-panel}/cart-line-item.tsx +27 -14
- package/components/{cart-panel → cart/cart-panel}/index.tsx +12 -3
- package/components/{cart-panel → cart/cart-panel}/promo-code.tsx +3 -3
- package/components/{payment-step-form → checkout/payment-step-form}/contact-form.tsx +1 -1
- package/components/{payment-step-form → checkout/payment-step-form}/index.tsx +3 -3
- package/components/{payment-step-form → checkout/payment-step-form}/methods/bank-transfer.tsx +1 -1
- package/components/{payment-step-form → checkout/payment-step-form}/methods/card.tsx +4 -4
- package/components/{payment-step-form → checkout/payment-step-form}/methods/crypto.tsx +3 -3
- package/components/{payment-step-form → checkout/payment-step-form}/methods/index.ts +1 -1
- package/components/{shipping-step-form.tsx → checkout/shipping-step-form.tsx} +4 -4
- package/components/index.ts +20 -18
- package/components/item/item-media.tsx +51 -0
- package/components/item/product-card.tsx +48 -0
- package/components/{facet-values-widget → level-nodes-widget}/index.tsx +20 -19
- package/components/level-nodes-widget/node-image.tsx +31 -0
- package/components/select/carousel-selector.tsx +112 -0
- package/components/select/image-selector.tsx +162 -0
- package/components/select/radio-selector.tsx +125 -0
- package/package.json +3 -2
- package/service/context.tsx +4 -4
- package/service/impls/standalone/actual-line-item.ts +30 -16
- package/service/impls/standalone/index.ts +4 -4
- package/service/impls/standalone/standalone-service.ts +94 -64
- package/types/buy-ui-spec.ts +8 -0
- package/types/category.ts +6 -4
- package/types/commerce-service.ts +21 -16
- package/types/index.ts +3 -2
- package/types/item-selector.ts +34 -6
- package/types/line-item.ts +2 -0
- package/types/product.ts +5 -5
- package/types/tree-node.ts +24 -0
- package/util/buy-ui-conf.ts +34 -0
- package/util/index.ts +14 -11
- package/util/obs-string-mutator.ts +22 -0
- package/util/use-sync-sku-param-w-current-item.ts +5 -5
- package/components/buy-item/buy-item-button-wrapper.tsx +0 -27
- package/components/buy-item/buy-item-button.tsx +0 -34
- package/components/buy-item/buy-item-card.tsx +0 -92
- package/components/buy-item/buy-item-mobile-drawer.tsx +0 -54
- package/components/buy-item/select-category-and-item-widget.tsx +0 -83
- package/components/category-item-radio-selector.tsx +0 -55
- package/components/category-item-scroll-selector.tsx +0 -40
- package/components/facet-values-widget/facet-image.tsx +0 -41
- package/components/item-carousel.tsx +0 -80
- package/components/product-card.tsx +0 -79
- package/types/facet.ts +0 -35
- /package/components/{cart-icon.tsx → cart/cart-icon.tsx} +0 -0
- /package/components/{payment-step-form → checkout/payment-step-form}/card-icon-row.tsx +0 -0
- /package/components/{payment-step-form → checkout/payment-step-form}/card-icons/amex.tsx +0 -0
- /package/components/{payment-step-form → checkout/payment-step-form}/card-icons/diners-club.tsx +0 -0
- /package/components/{payment-step-form → checkout/payment-step-form}/card-icons/discover.tsx +0 -0
- /package/components/{payment-step-form → checkout/payment-step-form}/card-icons/jcb.tsx +0 -0
- /package/components/{payment-step-form → checkout/payment-step-form}/card-icons/mastercard.tsx +0 -0
- /package/components/{payment-step-form → checkout/payment-step-form}/card-icons/visa.tsx +0 -0
- /package/components/{payment-step-form → checkout/payment-step-form}/cc-button.tsx +0 -0
- /package/components/{payment-step-form → checkout/payment-step-form}/crypto-icons/btc.tsx +0 -0
- /package/components/{payment-step-form → checkout/payment-step-form}/crypto-icons/eth.tsx +0 -0
- /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
|
-
|
|
17
|
-
|
|
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
|
|
46
|
-
private
|
|
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
|
-
|
|
54
|
+
rootNode: ProductTreeNode,
|
|
55
55
|
options: StandaloneServiceOptions,
|
|
56
56
|
serviceSnapshot?: StandaloneServiceSnapshot,
|
|
57
57
|
) {
|
|
58
58
|
|
|
59
|
-
this.
|
|
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
|
-
'
|
|
77
|
+
'_selectedPaths' |
|
|
78
78
|
'_currentItem' |
|
|
79
79
|
'_promo'
|
|
80
80
|
>(this, {
|
|
81
|
-
|
|
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
|
-
|
|
93
|
-
|
|
92
|
+
selectedItems: computed,
|
|
93
|
+
selectedCategories: computed,
|
|
94
|
+
hasSelection: computed,
|
|
94
95
|
setCurrentItem: action,
|
|
95
96
|
currentItem: computed,
|
|
96
97
|
item: computed,
|
|
97
|
-
|
|
98
|
+
selectedPaths: computed,
|
|
98
99
|
appliedPromo: computed,
|
|
99
100
|
setAppliedPromo: action,
|
|
100
|
-
/* NOT
|
|
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
|
-
|
|
109
|
+
getNodeAtPath(skuPath: string): ProductTreeNode | undefined {
|
|
110
110
|
const toks = skuPath.split(SEP)
|
|
111
111
|
let level = 1
|
|
112
|
-
let
|
|
112
|
+
let desc: ProductTreeNode | undefined = this._rootNode
|
|
113
113
|
do {
|
|
114
|
-
|
|
115
|
-
valuesAtLevel = fvalue ? fvalue.sub : undefined
|
|
114
|
+
desc = desc!.subNodes?.find((vf) => (vf.skuToken === toks[level]))
|
|
116
115
|
level++
|
|
117
116
|
}
|
|
118
|
-
while (
|
|
119
|
-
return level === toks.length ?
|
|
117
|
+
while (desc && (level < toks.length))
|
|
118
|
+
return level === toks.length ? desc : undefined
|
|
120
119
|
}
|
|
121
120
|
|
|
122
|
-
|
|
121
|
+
getSelectedNodesAtLevel = computedFn((level: number): ProductTreeNode[] | undefined => {
|
|
123
122
|
|
|
124
123
|
let lvl = 1
|
|
125
|
-
let valuesAtLevel:
|
|
124
|
+
let valuesAtLevel: ProductTreeNode[] | undefined = this._rootNode.subNodes
|
|
126
125
|
|
|
127
126
|
do {
|
|
128
|
-
let selectedAtLevel:
|
|
127
|
+
let selectedAtLevel: ProductTreeNode[] | undefined = undefined
|
|
129
128
|
// If not specified, assume all
|
|
130
|
-
if (lvl in this.
|
|
131
|
-
selectedAtLevel = valuesAtLevel!.filter((fv) => (this.
|
|
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:
|
|
137
|
-
selectedAtLevel?.forEach((fvd:
|
|
138
|
-
if (fvd.
|
|
139
|
-
allSubsOfSelected = [...allSubsOfSelected, ...fvd.
|
|
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.
|
|
254
|
-
for (let category of this.
|
|
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
|
-
|
|
293
|
+
selectPaths(sel: SelectedPaths): Category[] {
|
|
285
294
|
runInAction (() => {
|
|
286
|
-
this.
|
|
295
|
+
this._selectedPaths = this._processAndValidate(sel)
|
|
287
296
|
})
|
|
288
|
-
return this.
|
|
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
|
|
292
|
-
const result:
|
|
293
|
-
for( let level in this.
|
|
294
|
-
result[level] = [...this.
|
|
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
|
-
|
|
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.
|
|
307
|
-
(acc: Category[], subFacet:
|
|
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.
|
|
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:
|
|
316
|
-
const path = [...parentPath, node.
|
|
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.
|
|
340
|
+
!this._selectedPaths[level]
|
|
323
341
|
||
|
|
324
|
-
this.
|
|
342
|
+
this._selectedPaths[level].includes(node.skuToken)
|
|
325
343
|
)
|
|
326
344
|
if (specified) {
|
|
327
345
|
// Process subnodes
|
|
328
|
-
if (node.
|
|
329
|
-
return node.
|
|
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("
|
|
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:
|
|
362
|
+
private _processAndValidate(partial: SelectedPaths): SelectedPaths {
|
|
345
363
|
|
|
346
|
-
const result:
|
|
364
|
+
const result: SelectedPaths = {}
|
|
347
365
|
|
|
348
366
|
let level = 1
|
|
349
|
-
let currentSet = this.
|
|
367
|
+
let currentSet = this._rootNode.subNodes!
|
|
350
368
|
|
|
351
369
|
while (true) {
|
|
352
|
-
let possibleCurrent = currentSet.map((el) => (el.
|
|
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.
|
|
360
|
-
return (fd && fd.
|
|
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
|
|
369
|
-
if (Object.keys(toJS(this.
|
|
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.
|
|
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 {
|
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
|
|
5
|
-
title: string
|
|
6
|
-
|
|
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?:
|
|
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 {
|
|
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
|
|
33
|
-
* If a level is missing (undefined), everything at that level is
|
|
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
|
|
35
|
+
* This selects one or more Category's, and all the LineItem's in them.
|
|
36
36
|
*
|
|
37
|
-
* An empty value object
|
|
37
|
+
* An empty value object selects all Category's and all LineItem's,
|
|
38
38
|
* */
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
get
|
|
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
|
-
|
|
49
|
+
getNodeAtPath(skuPath: string): ProductTreeNode | undefined
|
|
46
50
|
|
|
47
|
-
/**
|
|
48
|
-
*
|
|
49
|
-
*
|
|
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
|
-
|
|
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 "
|
|
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 "
|
|
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 './
|
|
11
|
+
export * from './tree-node'
|
|
11
12
|
export * from './string-mutator'
|
package/types/item-selector.ts
CHANGED
|
@@ -1,14 +1,42 @@
|
|
|
1
|
-
import type
|
|
2
|
-
import type { ObsLineItemRef } from './line-item'
|
|
1
|
+
import type { LineItem, ObsLineItemRef } from './line-item'
|
|
3
2
|
|
|
4
3
|
interface ItemSelector {
|
|
5
|
-
|
|
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
|
|
39
|
+
type ItemSelector,
|
|
40
|
+
type ItemSelectorCompProps,
|
|
41
|
+
type ItemSelectorProps
|
|
14
42
|
}
|
package/types/line-item.ts
CHANGED
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
|
-
|
|
7
|
-
|
|
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?:
|
|
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
|
|
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.
|
|
35
|
+
const facets = cmmc.selectedPaths
|
|
36
36
|
facets[level] = [value]
|
|
37
|
-
cmmc.
|
|
38
|
-
const subFacets = cmmc.
|
|
37
|
+
cmmc.selectPaths(facets)
|
|
38
|
+
const subFacets = cmmc.getSelectedNodesAtLevel(level)
|
|
39
39
|
if (subFacets) {
|
|
40
|
-
const facets = cmmc.
|
|
41
|
-
facets[level + 1] = [subFacets[0].
|
|
42
|
-
cmmc.
|
|
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.
|
|
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'
|