@hanzo/commerce 6.0.0 → 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/_to_deprecate_select-category-item-card.tsx +2 -1
- package/components/buy/_to_deprecate_select-category-item-panel.tsx +4 -2
- package/components/buy/add-to-cart-widget.tsx +5 -3
- package/components/buy/buy-card.tsx +184 -86
- package/components/buy/buy-drawer.tsx +18 -7
- package/components/cart/cart-panel/cart-line-item.tsx +22 -32
- package/components/cart/cart-panel/index.tsx +9 -0
- package/components/index.ts +1 -1
- package/components/item/item-media.tsx +11 -18
- package/components/item/product-card.tsx +20 -49
- package/components/{facet-values-widget → level-nodes-widget}/index.tsx +13 -13
- package/components/level-nodes-widget/node-image.tsx +31 -0
- package/components/select/carousel-selector.tsx +71 -23
- package/components/select/image-selector.tsx +93 -56
- package/components/select/radio-selector.tsx +86 -27
- package/package.json +2 -2
- package/service/impls/standalone/actual-line-item.ts +30 -16
- package/service/impls/standalone/standalone-service.ts +21 -3
- package/types/buy-ui-spec.ts +8 -0
- package/types/category.ts +6 -4
- package/types/commerce-service.ts +1 -0
- package/types/index.ts +1 -0
- package/types/item-selector.ts +25 -4
- package/types/line-item.ts +2 -0
- package/types/product.ts +5 -6
- package/util/buy-ui-conf.ts +34 -0
- package/util/index.ts +5 -2
- package/util/obs-string-mutator.ts +22 -0
- package/components/facet-values-widget/facet-image.tsx +0 -41
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hanzo/commerce",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.1.0",
|
|
4
4
|
"description": "Library with shopping cart components.",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"registry": "https://registry.npmjs.org/",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
},
|
|
41
41
|
"peerDependencies": {
|
|
42
42
|
"@hanzo/auth": "^2.3.3",
|
|
43
|
-
"@hanzo/ui": "^3.
|
|
43
|
+
"@hanzo/ui": "^3.2.0",
|
|
44
44
|
"@hookform/resolvers": "^3.3.4",
|
|
45
45
|
"@radix-ui/react-radio-group": "^1.1.3",
|
|
46
46
|
"firebase": "^10.8.0",
|
|
@@ -5,9 +5,9 @@ import {
|
|
|
5
5
|
observable,
|
|
6
6
|
} from 'mobx'
|
|
7
7
|
|
|
8
|
-
import type { VideoDef } from '@hanzo/ui/types'
|
|
8
|
+
import type { ImageDef, VideoDef } from '@hanzo/ui/types'
|
|
9
9
|
|
|
10
|
-
import type { Product, LineItem } from '../../../types'
|
|
10
|
+
import type { Product, LineItem, CommerceService } from '../../../types'
|
|
11
11
|
|
|
12
12
|
interface ActualLineItemSnapshot {
|
|
13
13
|
sku: string
|
|
@@ -26,12 +26,13 @@ class ActualLineItem
|
|
|
26
26
|
|
|
27
27
|
id: string
|
|
28
28
|
sku: string
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
fullTitle?: string
|
|
30
|
+
optionLabel: string
|
|
31
|
+
categoryTitle: string
|
|
31
32
|
categoryId: string
|
|
32
33
|
desc?: string
|
|
33
34
|
price: number
|
|
34
|
-
img?:
|
|
35
|
+
img?: ImageDef
|
|
35
36
|
video?: VideoDef
|
|
36
37
|
animation?: string
|
|
37
38
|
timeAdded: number = 0 // timeAdded of being added to cart
|
|
@@ -39,8 +40,9 @@ class ActualLineItem
|
|
|
39
40
|
constructor(prod: Product, snap?: ActualLineItemSnapshot) {
|
|
40
41
|
this.id = prod.id
|
|
41
42
|
this.sku = prod.sku
|
|
42
|
-
this.
|
|
43
|
-
this.
|
|
43
|
+
this.fullTitle = prod.fullTitle
|
|
44
|
+
this.optionLabel = prod.optionLabel
|
|
45
|
+
this.categoryTitle = prod.categoryTitle
|
|
44
46
|
this.categoryId = prod.categoryId
|
|
45
47
|
this.desc = prod.desc
|
|
46
48
|
this.price = prod.price
|
|
@@ -58,20 +60,32 @@ class ActualLineItem
|
|
|
58
60
|
timeAdded: observable,
|
|
59
61
|
canDecrement: computed,
|
|
60
62
|
isInCart: computed,
|
|
61
|
-
|
|
63
|
+
title: computed,
|
|
62
64
|
increment: action,
|
|
63
65
|
decrement: action,
|
|
64
66
|
})
|
|
65
67
|
}
|
|
66
68
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
69
|
+
get title(): string {
|
|
70
|
+
return this.fullTitle ? this.fullTitle : (this.categoryTitle + ', ' + this.optionLabel)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
takeSnapshot = (cmmc: CommerceService): ActualLineItemSnapshot => {
|
|
74
|
+
|
|
75
|
+
const title = this.fullTitle ?
|
|
76
|
+
this.fullTitle
|
|
77
|
+
:
|
|
78
|
+
((cmmc.getCategory(this.categoryId)?.title ?? this.categoryTitle) + ', ' + this.optionLabel)
|
|
79
|
+
|
|
80
|
+
return({
|
|
81
|
+
sku: this.sku,
|
|
82
|
+
categoryId: this.categoryId,
|
|
83
|
+
title,
|
|
84
|
+
price: this.price,
|
|
85
|
+
quantity: this.qu,
|
|
86
|
+
timeAdded: this.timeAdded
|
|
87
|
+
} satisfies ActualLineItemSnapshot)
|
|
88
|
+
}
|
|
75
89
|
|
|
76
90
|
get canDecrement(): boolean { return this.qu > 0 }
|
|
77
91
|
get quantity(): number {return this.qu}
|
|
@@ -78,8 +78,8 @@ class StandaloneService
|
|
|
78
78
|
'_currentItem' |
|
|
79
79
|
'_promo'
|
|
80
80
|
>(this, {
|
|
81
|
-
_selectedPaths :
|
|
82
|
-
_currentItem: observable,
|
|
81
|
+
_selectedPaths : observable.deep,
|
|
82
|
+
_currentItem: observable.shallow,
|
|
83
83
|
_promo: observable,
|
|
84
84
|
})
|
|
85
85
|
|
|
@@ -91,6 +91,7 @@ class StandaloneService
|
|
|
91
91
|
cartEmpty: computed,
|
|
92
92
|
selectedItems: computed,
|
|
93
93
|
selectedCategories: computed,
|
|
94
|
+
hasSelection: computed,
|
|
94
95
|
setCurrentItem: action,
|
|
95
96
|
currentItem: computed,
|
|
96
97
|
item: computed,
|
|
@@ -164,7 +165,7 @@ class StandaloneService
|
|
|
164
165
|
}
|
|
165
166
|
|
|
166
167
|
takeSnapshot = (): StandaloneServiceSnapshot => ({
|
|
167
|
-
items : (this.cartItems as ActualLineItem[]).map((it) => (it.takeSnapshot()))
|
|
168
|
+
items : (this.cartItems as ActualLineItem[]).map((it) => (it.takeSnapshot(this)))
|
|
168
169
|
})
|
|
169
170
|
|
|
170
171
|
get cartItems(): LineItem[] {
|
|
@@ -237,10 +238,19 @@ class StandaloneService
|
|
|
237
238
|
|
|
238
239
|
setCurrentItem(skuToFind: string | undefined): boolean {
|
|
239
240
|
|
|
241
|
+
const logMe = (s: string) => {
|
|
242
|
+
//if (skuToFind?.startsWith('LXM-CR-E')) {
|
|
243
|
+
//console.log(s)
|
|
244
|
+
//}
|
|
245
|
+
}
|
|
246
|
+
|
|
240
247
|
if (skuToFind === undefined || skuToFind.length === 0) {
|
|
241
248
|
this._currentItem = undefined
|
|
242
249
|
return true
|
|
243
250
|
}
|
|
251
|
+
|
|
252
|
+
logMe("SETTING ITEM: " + skuToFind)
|
|
253
|
+
|
|
244
254
|
// self calling function
|
|
245
255
|
this._currentItem = ((): ActualLineItem | undefined => {
|
|
246
256
|
|
|
@@ -250,6 +260,7 @@ class StandaloneService
|
|
|
250
260
|
categoriesTried.push(category.id)
|
|
251
261
|
const foundItem = category.products.find((p) => (p.sku === skuToFind))
|
|
252
262
|
if (foundItem) {
|
|
263
|
+
logMe("FOUND 1st LOOP")
|
|
253
264
|
return foundItem as ActualLineItem
|
|
254
265
|
}
|
|
255
266
|
}
|
|
@@ -258,12 +269,15 @@ class StandaloneService
|
|
|
258
269
|
if (categoriesTried.includes(categoryId)) continue
|
|
259
270
|
const foundItem = category.products.find((p) => (p.sku === skuToFind)) as ActualLineItem | undefined
|
|
260
271
|
if (foundItem) {
|
|
272
|
+
logMe("FOUND 2nd LOOP")
|
|
261
273
|
return foundItem as ActualLineItem
|
|
262
274
|
}
|
|
263
275
|
}
|
|
276
|
+
logMe("NOT FOUND")
|
|
264
277
|
return undefined
|
|
265
278
|
})();
|
|
266
279
|
|
|
280
|
+
logMe("CURRENT ITEM SET TO: " + this._currentItem?.sku)
|
|
267
281
|
return !!this._currentItem
|
|
268
282
|
}
|
|
269
283
|
|
|
@@ -379,6 +393,10 @@ class StandaloneService
|
|
|
379
393
|
(allProducts, cat) => ([...allProducts, ...(cat.products as LineItem[])]), [] as LineItem[])
|
|
380
394
|
}
|
|
381
395
|
|
|
396
|
+
get hasSelection(): boolean {
|
|
397
|
+
return this.selectedCategories.length > 0
|
|
398
|
+
}
|
|
399
|
+
|
|
382
400
|
getCartCategorySubtotal(categoryId: string): number {
|
|
383
401
|
const c = this._categoryMap.get(categoryId)!
|
|
384
402
|
return (c.products as LineItem[]).reduce(
|
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
|
}
|
|
@@ -43,6 +43,7 @@ interface CommerceService extends ObsLineItemRef {
|
|
|
43
43
|
|
|
44
44
|
get selectedItems(): LineItem[]
|
|
45
45
|
get selectedCategories(): Category[]
|
|
46
|
+
get hasSelection(): boolean
|
|
46
47
|
|
|
47
48
|
/** Whether this path defines a Category, or if it has further levels */
|
|
48
49
|
getNodeAtPath(skuPath: string): ProductTreeNode | undefined
|
package/types/index.ts
CHANGED
|
@@ -3,6 +3,7 @@ export type { default as Category } from './category'
|
|
|
3
3
|
export type { default as CommerceService } from './commerce-service'
|
|
4
4
|
export type { default as Product } from './product'
|
|
5
5
|
export type { default as Promo } from './promo'
|
|
6
|
+
export type { default as BuyUISpec } from './buy-ui-spec'
|
|
6
7
|
|
|
7
8
|
export * from './checkout'
|
|
8
9
|
export * from './item-selector'
|
package/types/item-selector.ts
CHANGED
|
@@ -4,18 +4,39 @@ interface ItemSelector {
|
|
|
4
4
|
items: LineItem[]
|
|
5
5
|
selectedItemRef: ObsLineItemRef
|
|
6
6
|
selectSku: (sku: string) => void
|
|
7
|
-
showPrice?: boolean // true by default
|
|
8
|
-
showQuantity?: boolean // false by default (not impl)
|
|
9
7
|
}
|
|
10
8
|
|
|
11
|
-
interface
|
|
9
|
+
interface _ItemSelectorCompProps {
|
|
12
10
|
clx?: string
|
|
13
|
-
soleItemClx?: string
|
|
14
11
|
itemClx?: string
|
|
12
|
+
soleItemClx?: string
|
|
13
|
+
/** type-specific props for ItemSelector's.
|
|
14
|
+
* eg, Carousel options.
|
|
15
|
+
*/
|
|
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
|
|
16
31
|
}
|
|
17
32
|
|
|
33
|
+
type ItemSelectorCompProps = Omit<_ItemSelectorCompProps, 'scrollList'>
|
|
34
|
+
|
|
35
|
+
interface ItemSelectorProps extends
|
|
36
|
+
ItemSelector, _ItemSelectorCompProps {}
|
|
37
|
+
|
|
18
38
|
export {
|
|
19
39
|
type ItemSelector,
|
|
40
|
+
type ItemSelectorCompProps,
|
|
20
41
|
type ItemSelectorProps
|
|
21
42
|
}
|
package/types/line-item.ts
CHANGED
package/types/product.ts
CHANGED
|
@@ -1,16 +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?:
|
|
13
|
-
imgAR?: number
|
|
12
|
+
img?: ImageDef // if undefined: (category's img exists) ? (use it) : (use generic placeholder)
|
|
14
13
|
animation?: string // spline scene url
|
|
15
14
|
video?: VideoDef
|
|
16
15
|
}
|
|
@@ -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(
|
|
@@ -59,4 +59,7 @@ export const getFacetValuesMutator = (level: number, cmmc: CommerceService): Str
|
|
|
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'
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
|
|
2
|
+
import { makeObservable, observable, action } from 'mobx'
|
|
3
|
+
import type { StringMutator } from '../types'
|
|
4
|
+
|
|
5
|
+
class ObsStringMutator implements StringMutator {
|
|
6
|
+
|
|
7
|
+
s: string | null
|
|
8
|
+
constructor(_s: string) {
|
|
9
|
+
this.s = _s
|
|
10
|
+
makeObservable(this, {
|
|
11
|
+
s: observable,
|
|
12
|
+
set: action,
|
|
13
|
+
//get: computed
|
|
14
|
+
})
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
set(v: string | null): void { this.s = v }
|
|
18
|
+
get(): string | null { return this.s }
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export default ObsStringMutator
|
|
22
|
+
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import Image from 'next/image'
|
|
3
|
-
|
|
4
|
-
import type { ProductTreeNode } from '../../types'
|
|
5
|
-
|
|
6
|
-
const ICON_SIZE = 20
|
|
7
|
-
|
|
8
|
-
const FacetImage: React.FC<{
|
|
9
|
-
facetValueDesc: ProductTreeNode
|
|
10
|
-
}> = ({
|
|
11
|
-
facetValueDesc
|
|
12
|
-
}) => {
|
|
13
|
-
|
|
14
|
-
const {
|
|
15
|
-
img,
|
|
16
|
-
imgAR: ar,
|
|
17
|
-
label
|
|
18
|
-
} = facetValueDesc
|
|
19
|
-
|
|
20
|
-
if (!img) {
|
|
21
|
-
return null
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
// url
|
|
25
|
-
if (typeof img === 'string') {
|
|
26
|
-
return (
|
|
27
|
-
<Image
|
|
28
|
-
src={img as string}
|
|
29
|
-
alt={`Toggle ${label}`}
|
|
30
|
-
className={'block mr-1 '}
|
|
31
|
-
width={ar ? ar * ICON_SIZE : ICON_SIZE}
|
|
32
|
-
height={ICON_SIZE}
|
|
33
|
-
/>
|
|
34
|
-
)
|
|
35
|
-
}
|
|
36
|
-
// ReactNode
|
|
37
|
-
return img as React.ReactNode
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export default FacetImage
|
|
41
|
-
|