@hanzo/commerce 7.0.1 → 7.0.3
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/add-to-cart-widget.tsx +2 -19
- package/components/buy/buy-card.tsx +2 -3
- package/components/buy/carousel-buy-card.tsx +0 -1
- package/components/cart/cart-panel/cart-line-item.tsx +1 -1
- package/components/index.ts +0 -1
- package/context/index.tsx +4 -28
- package/package.json +10 -12
- package/service/debug.ts +7 -7
- package/service/impls/standalone/actual-line-item.ts +1 -1
- package/service/impls/standalone/standalone-service.ts +17 -7
- package/tsconfig.json +2 -2
- package/types/commerce-service.ts +2 -1
- package/components/buy/buy-button.tsx +0 -35
- package/context/commerce-ui.ts +0 -101
|
@@ -9,11 +9,9 @@ import { cn, type VariantProps } from '@hanzo/ui/util'
|
|
|
9
9
|
import Icons from './Icons'
|
|
10
10
|
import type { LineItem } from '../types'
|
|
11
11
|
import { sendFBEvent, sendGAEvent } from '../util/analytics'
|
|
12
|
-
import { useCommerceUI } from '..'
|
|
13
12
|
|
|
14
13
|
const AddToCartWidget: React.FC<{
|
|
15
14
|
item: LineItem
|
|
16
|
-
registerAdd?: boolean
|
|
17
15
|
disabled?: boolean
|
|
18
16
|
className?: string
|
|
19
17
|
buttonClx?: string
|
|
@@ -22,26 +20,19 @@ const AddToCartWidget: React.FC<{
|
|
|
22
20
|
}> = observer(({
|
|
23
21
|
item,
|
|
24
22
|
variant='primary',
|
|
25
|
-
registerAdd=true,
|
|
26
23
|
disabled=false,
|
|
27
24
|
className='',
|
|
28
25
|
buttonClx='',
|
|
29
26
|
onQuantityChanged
|
|
30
27
|
}) => {
|
|
31
28
|
|
|
32
|
-
const ui = useCommerceUI()
|
|
33
|
-
|
|
34
29
|
const reactionDisposer = useRef<IReactionDisposer | undefined>(undefined)
|
|
35
|
-
|
|
36
30
|
useEffect(() => {
|
|
37
|
-
|
|
38
|
-
// if we're not part of the cart ui,
|
|
39
|
-
// or part of the main drawer.
|
|
40
|
-
if (registerAdd && variant !== 'minimal') {
|
|
31
|
+
if (onQuantityChanged) {
|
|
41
32
|
reactionDisposer.current = reaction(
|
|
42
33
|
() => (item.quantity),
|
|
43
34
|
(quantity: number, previous: number) => {
|
|
44
|
-
|
|
35
|
+
onQuantityChanged(item.sku, quantity, previous)
|
|
45
36
|
}
|
|
46
37
|
)
|
|
47
38
|
}
|
|
@@ -75,11 +66,7 @@ const AddToCartWidget: React.FC<{
|
|
|
75
66
|
}
|
|
76
67
|
|
|
77
68
|
const inc = () => {
|
|
78
|
-
const old = item.quantity
|
|
79
69
|
item.increment()
|
|
80
|
-
if (onQuantityChanged) {
|
|
81
|
-
onQuantityChanged(item.sku, old, old + 1)
|
|
82
|
-
}
|
|
83
70
|
sendGAEvent('add_to_cart', {
|
|
84
71
|
items: [{
|
|
85
72
|
item_id: item.sku,
|
|
@@ -104,11 +91,7 @@ const AddToCartWidget: React.FC<{
|
|
|
104
91
|
}
|
|
105
92
|
|
|
106
93
|
const dec = () => {
|
|
107
|
-
const old = item.quantity
|
|
108
94
|
item.decrement()
|
|
109
|
-
if (onQuantityChanged) {
|
|
110
|
-
onQuantityChanged(item.sku, old, old - 1)
|
|
111
|
-
}
|
|
112
95
|
sendGAEvent('remove_from_cart', {
|
|
113
96
|
items: [{
|
|
114
97
|
item_id: item.sku,
|
|
@@ -86,7 +86,7 @@ const BuyCard: React.FC<{
|
|
|
86
86
|
|
|
87
87
|
useEffect(() => {
|
|
88
88
|
|
|
89
|
-
let requestedFamily = cmmc.
|
|
89
|
+
let requestedFamily = cmmc.getFamilyById(skuPath)
|
|
90
90
|
let requestedNode = requestedFamily ? undefined : cmmc.getNodeAtPath(skuPath)
|
|
91
91
|
let initialFamily = undefined
|
|
92
92
|
|
|
@@ -137,7 +137,7 @@ const BuyCard: React.FC<{
|
|
|
137
137
|
},
|
|
138
138
|
(famId) => {
|
|
139
139
|
if (famId && famId !== cmmc.currentItem?.familyId) {
|
|
140
|
-
const fam = cmmc.
|
|
140
|
+
const fam = cmmc.getFamilyById(famId)
|
|
141
141
|
if (fam) {
|
|
142
142
|
cmmc.setCurrentItem(fam.products[0].sku)
|
|
143
143
|
}
|
|
@@ -248,7 +248,6 @@ const BuyCard: React.FC<{
|
|
|
248
248
|
{(cmmc.currentItem) && (
|
|
249
249
|
<AddToCartWidget
|
|
250
250
|
item={cmmc.currentItem}
|
|
251
|
-
registerAdd={false}
|
|
252
251
|
onQuantityChanged={onQuantityChanged}
|
|
253
252
|
className={cn('min-w-[160px] mx-auto mt-4', (scroll ? 'shrink-0' : ''), addWidgetClx)}
|
|
254
253
|
/>
|
|
@@ -61,7 +61,7 @@ const CartLineItem: React.FC<{
|
|
|
61
61
|
</div>
|
|
62
62
|
<div className='flex flex-row items-center justify-between w-full'>
|
|
63
63
|
<div className='flex flex-row items-center'>
|
|
64
|
-
<AddToCartWidget variant='minimal'
|
|
64
|
+
<AddToCartWidget variant='minimal' item={item} buttonClx='!h-8 md:!h-6' />
|
|
65
65
|
{item.quantity > 1 && (<span className='pl-2.5'>{'@' + formatCurrencyValue(item.price)}</span>)}
|
|
66
66
|
</div>
|
|
67
67
|
<div className='flex flex-row gap-1 items-center justify-end'>
|
package/components/index.ts
CHANGED
package/context/index.tsx
CHANGED
|
@@ -4,7 +4,6 @@ import React, {
|
|
|
4
4
|
useContext,
|
|
5
5
|
useRef,
|
|
6
6
|
type PropsWithChildren,
|
|
7
|
-
useEffect
|
|
8
7
|
} from 'react'
|
|
9
8
|
|
|
10
9
|
// https://dev.to/ivandotv/mobx-server-side-rendering-with-next-js-4m18
|
|
@@ -15,21 +14,11 @@ import type { ServiceOptions } from '..'
|
|
|
15
14
|
import type CommerceService from '../types/commerce-service'
|
|
16
15
|
import getInstance from '../service/get-instance'
|
|
17
16
|
import type { Family, CategoryNode, SelectionUISpecifier } from '../types'
|
|
18
|
-
import { type CommerceUI, CommerceUIStore } from './commerce-ui'
|
|
19
17
|
|
|
20
|
-
|
|
21
|
-
service: CommerceService
|
|
22
|
-
ui: CommerceUI
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const CommerceContext = createContext<CommerceContextValue | undefined>(undefined)
|
|
18
|
+
const CommerceContext = createContext<CommerceService | undefined>(undefined)
|
|
26
19
|
|
|
27
20
|
const useCommerce = (): CommerceService => {
|
|
28
|
-
return
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const useCommerceUI = (): CommerceUI => {
|
|
32
|
-
return (useContext(CommerceContext) as CommerceContextValue)?.ui
|
|
21
|
+
return useContext(CommerceContext) as CommerceService
|
|
33
22
|
}
|
|
34
23
|
|
|
35
24
|
const CommerceProvider: React.FC<PropsWithChildren & {
|
|
@@ -37,29 +26,17 @@ const CommerceProvider: React.FC<PropsWithChildren & {
|
|
|
37
26
|
rootNode: CategoryNode
|
|
38
27
|
options?: ServiceOptions
|
|
39
28
|
uiSpecs?: Record<string, SelectionUISpecifier>
|
|
40
|
-
DEBUG_NO_TICK?: boolean
|
|
41
29
|
}> = ({
|
|
42
30
|
children,
|
|
43
31
|
families,
|
|
44
32
|
rootNode,
|
|
45
33
|
options,
|
|
46
|
-
uiSpecs
|
|
47
|
-
DEBUG_NO_TICK=false
|
|
34
|
+
uiSpecs
|
|
48
35
|
}) => {
|
|
49
36
|
|
|
50
|
-
useEffect(() => {
|
|
51
|
-
if (DEBUG_NO_TICK) return
|
|
52
|
-
const intervalId = setInterval(() => {
|
|
53
|
-
(valueRef.current.ui as CommerceUIStore).tick()
|
|
54
|
-
}, 250)
|
|
55
|
-
return () => { clearInterval(intervalId) }
|
|
56
|
-
}, [])
|
|
57
|
-
|
|
58
37
|
// TODO: Inject Promo fixture here from siteDef
|
|
59
38
|
const service = getInstance(families, rootNode, options, uiSpecs)
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
const valueRef = useRef<CommerceContextValue>({service, ui})
|
|
39
|
+
const valueRef = useRef<CommerceService>(service)
|
|
63
40
|
|
|
64
41
|
return (
|
|
65
42
|
<CommerceContext.Provider value={valueRef.current}>
|
|
@@ -70,7 +47,6 @@ const CommerceProvider: React.FC<PropsWithChildren & {
|
|
|
70
47
|
|
|
71
48
|
export {
|
|
72
49
|
useCommerce,
|
|
73
|
-
useCommerceUI,
|
|
74
50
|
CommerceProvider
|
|
75
51
|
}
|
|
76
52
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hanzo/commerce",
|
|
3
|
-
"version": "7.0.
|
|
3
|
+
"version": "7.0.3",
|
|
4
4
|
"description": "e-commerce framework.",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"registry": "https://registry.npmjs.org/",
|
|
@@ -26,9 +26,7 @@
|
|
|
26
26
|
},
|
|
27
27
|
"exports": {
|
|
28
28
|
".": "./index.ts",
|
|
29
|
-
"./
|
|
30
|
-
"./types": "./types/index.ts",
|
|
31
|
-
"./debug": "./service/debug.ts"
|
|
29
|
+
"./types": "./types/index.ts"
|
|
32
30
|
},
|
|
33
31
|
"dependencies": {
|
|
34
32
|
"ethers": "^6.12.0",
|
|
@@ -37,8 +35,8 @@
|
|
|
37
35
|
"square": "^35.1.0"
|
|
38
36
|
},
|
|
39
37
|
"peerDependencies": {
|
|
40
|
-
"@hanzo/auth": "^2.4.
|
|
41
|
-
"@hanzo/ui": "^3.7.
|
|
38
|
+
"@hanzo/auth": "^2.4.6",
|
|
39
|
+
"@hanzo/ui": "^3.7.22",
|
|
42
40
|
"@hookform/resolvers": "^3.3.4",
|
|
43
41
|
"@radix-ui/react-radio-group": "^1.1.3",
|
|
44
42
|
"firebase": "^10.8.0",
|
|
@@ -47,14 +45,14 @@
|
|
|
47
45
|
"mobx-react-lite": "^4.0.5",
|
|
48
46
|
"mobx-utils": "^6.0.8",
|
|
49
47
|
"next": "14.1.3",
|
|
50
|
-
"react": "^18.
|
|
51
|
-
"react-dom": "^18.
|
|
52
|
-
"react-hook-form": "^7.51.
|
|
53
|
-
"zod": "3.
|
|
48
|
+
"react": "^18.3.1",
|
|
49
|
+
"react-dom": "^18.3.1",
|
|
50
|
+
"react-hook-form": "^7.51.4",
|
|
51
|
+
"zod": "3.23.8"
|
|
54
52
|
},
|
|
55
53
|
"devDependencies": {
|
|
56
|
-
"@types/react": "^18.
|
|
57
|
-
"@types/react-dom": "^18.
|
|
54
|
+
"@types/react": "^18.3.1",
|
|
55
|
+
"@types/react-dom": "^18.3.0",
|
|
58
56
|
"cross-fetch": "^4.0.0",
|
|
59
57
|
"typescript": "^5.4.5"
|
|
60
58
|
}
|
package/service/debug.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { CategoryNode, CategoryNodeRole, Family, LineItem } from '../types'
|
|
2
2
|
|
|
3
|
-
//
|
|
3
|
+
// Node recursion
|
|
4
4
|
export const categoryNodeDump = (node: CategoryNode, level: number = 0): void => {
|
|
5
5
|
const spacers: string[] = []
|
|
6
6
|
for (let i = 0; i < level; i++) {
|
|
@@ -9,9 +9,9 @@ export const categoryNodeDump = (node: CategoryNode, level: number = 0): void =>
|
|
|
9
9
|
console.log("NODE:" + spacers.join(''), node.skuToken)
|
|
10
10
|
node.subNodes?.forEach((sn: CategoryNode) => {categoryNodeDump(sn, level + 1)})
|
|
11
11
|
}
|
|
12
|
-
|
|
12
|
+
|
|
13
13
|
export const selectedFamiliesDump = (result: Family[]): string => {
|
|
14
|
-
const toDisplay = result.map((c) => (c.id))
|
|
14
|
+
const toDisplay = result.map((c) => (c.id))
|
|
15
15
|
return JSON.stringify(toDisplay, null, 2)
|
|
16
16
|
}
|
|
17
17
|
|
|
@@ -32,10 +32,10 @@ export const peekDump = (result : {
|
|
|
32
32
|
item: result.item ? result.item.sku : 'UNDEF',
|
|
33
33
|
family: result.family ? result.family.id : 'UNDEF',
|
|
34
34
|
families: result.families ? result.families.map((f) => (f.id)) : 'UNDEF',
|
|
35
|
-
node: result.node ?
|
|
36
|
-
(result.node.skuToken + (result.node.label ? (': ' + result.node.label) : ''))
|
|
37
|
-
:
|
|
35
|
+
node: result.node ?
|
|
36
|
+
(result.node.skuToken + (result.node.label ? (': ' + result.node.label) : ''))
|
|
37
|
+
:
|
|
38
38
|
'UNDEF'
|
|
39
39
|
}
|
|
40
40
|
return JSON.stringify(toDisplay, null, 2)
|
|
41
|
-
}
|
|
41
|
+
}
|
|
@@ -81,7 +81,7 @@ class ActualLineItem
|
|
|
81
81
|
const title = this.fullTitle ?
|
|
82
82
|
this.fullTitle
|
|
83
83
|
:
|
|
84
|
-
((cmmc.
|
|
84
|
+
((cmmc.getFamilyById(this.familyId)?.title ?? this.familyTitle) + ', ' + this.optionLabel)
|
|
85
85
|
|
|
86
86
|
return({
|
|
87
87
|
sku: this.sku,
|
|
@@ -110,7 +110,7 @@ class StandaloneService
|
|
|
110
110
|
})
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
-
|
|
113
|
+
getFamilyById(id: string): Family | undefined {
|
|
114
114
|
return this._familyMap.get(id)
|
|
115
115
|
}
|
|
116
116
|
|
|
@@ -343,14 +343,13 @@ class StandaloneService
|
|
|
343
343
|
this._promo = promo
|
|
344
344
|
}
|
|
345
345
|
|
|
346
|
-
|
|
346
|
+
getItemBySku = (skuToFind: string | undefined): LineItem | undefined => {
|
|
347
347
|
|
|
348
348
|
if (skuToFind === undefined || skuToFind.length === 0) {
|
|
349
|
-
|
|
350
|
-
return true
|
|
349
|
+
return undefined
|
|
351
350
|
}
|
|
352
|
-
//
|
|
353
|
-
|
|
351
|
+
// Self-calling
|
|
352
|
+
const found = ((): ActualLineItem | undefined => {
|
|
354
353
|
|
|
355
354
|
const familiesTried: string[] = []
|
|
356
355
|
if (this.selectedFamilies && this.selectedFamilies.length > 0) {
|
|
@@ -370,8 +369,19 @@ class StandaloneService
|
|
|
370
369
|
}
|
|
371
370
|
}
|
|
372
371
|
return undefined
|
|
373
|
-
})(); // necessary semi
|
|
372
|
+
})(); // Self-calling, necessary semi
|
|
373
|
+
|
|
374
|
+
return found
|
|
375
|
+
}
|
|
374
376
|
|
|
377
|
+
setCurrentItem = (skuToFind: string | undefined): boolean => {
|
|
378
|
+
|
|
379
|
+
if (skuToFind === undefined || skuToFind.length === 0) {
|
|
380
|
+
this._currentItem = undefined
|
|
381
|
+
return true
|
|
382
|
+
}
|
|
383
|
+
// self calling function
|
|
384
|
+
this._currentItem = this.getItemBySku(skuToFind) as ActualLineItem | undefined
|
|
375
385
|
this.setCurrentFamily(this._currentItem ? this._currentItem.familyId : undefined)
|
|
376
386
|
return !!this._currentItem
|
|
377
387
|
}
|
package/tsconfig.json
CHANGED
|
@@ -112,7 +112,8 @@ interface CommerceService extends ObsLineItemRef, ObsFamilyRef {
|
|
|
112
112
|
get currentFamily(): Family | undefined
|
|
113
113
|
|
|
114
114
|
// utility
|
|
115
|
-
|
|
115
|
+
getFamilyById(id: string): Family | undefined
|
|
116
|
+
getItemBySku(sku: string): LineItem | undefined
|
|
116
117
|
}
|
|
117
118
|
|
|
118
119
|
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
import React, {type PropsWithChildren} from 'react'
|
|
3
|
-
|
|
4
|
-
import { Button, buttonVariants } from '@hanzo/ui/primitives'
|
|
5
|
-
import { type VariantProps } from '@hanzo/ui/util'
|
|
6
|
-
|
|
7
|
-
import { cn } from '@hanzo/ui/util'
|
|
8
|
-
import { useCommerceUI } from '../..'
|
|
9
|
-
|
|
10
|
-
const BuyButton: React.FC<
|
|
11
|
-
PropsWithChildren &
|
|
12
|
-
VariantProps<typeof buttonVariants> &
|
|
13
|
-
{
|
|
14
|
-
skuPath: string
|
|
15
|
-
className?: string
|
|
16
|
-
}
|
|
17
|
-
> = ({
|
|
18
|
-
skuPath,
|
|
19
|
-
children,
|
|
20
|
-
className='',
|
|
21
|
-
...rest
|
|
22
|
-
}) => {
|
|
23
|
-
|
|
24
|
-
const ui = useCommerceUI()
|
|
25
|
-
const handleClick = () => { ui.showBuyOptions(skuPath) }
|
|
26
|
-
|
|
27
|
-
return (
|
|
28
|
-
<Button onClick={handleClick} {...rest} className={cn(className, '')}>
|
|
29
|
-
{children}
|
|
30
|
-
</Button>
|
|
31
|
-
)
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
export default BuyButton
|
package/context/commerce-ui.ts
DELETED
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
action,
|
|
3
|
-
computed,
|
|
4
|
-
makeObservable,
|
|
5
|
-
observable,
|
|
6
|
-
} from 'mobx'
|
|
7
|
-
import type { CommerceService, LineItem, ObsLineItemRef } from '../types'
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
interface CommerceUI extends ObsLineItemRef {
|
|
11
|
-
showBuyOptions: (skuPath: string) => void
|
|
12
|
-
hideBuyOptions: () => void
|
|
13
|
-
get buyOptionsSkuPath(): string | undefined
|
|
14
|
-
|
|
15
|
-
itemQuantityChanged(item: LineItem, val: number, prevVal: number): void
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
class CommerceUIStore implements CommerceUI {
|
|
19
|
-
|
|
20
|
-
static readonly TIMEOUT = 1500
|
|
21
|
-
_buyOptionsSkuPath: string | undefined = undefined
|
|
22
|
-
_paused: boolean = false
|
|
23
|
-
_activeItem: LineItem | undefined = undefined
|
|
24
|
-
_lastActivity: number | undefined = undefined
|
|
25
|
-
_service: CommerceService
|
|
26
|
-
|
|
27
|
-
constructor(s: CommerceService) {
|
|
28
|
-
this._service = s
|
|
29
|
-
makeObservable(this, {
|
|
30
|
-
_buyOptionsSkuPath: observable,
|
|
31
|
-
_activeItem: observable.shallow,
|
|
32
|
-
showBuyOptions: action,
|
|
33
|
-
hideBuyOptions: action,
|
|
34
|
-
buyOptionsSkuPath: computed,
|
|
35
|
-
itemQuantityChanged: action,
|
|
36
|
-
tick: action,
|
|
37
|
-
item: computed
|
|
38
|
-
})
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
showBuyOptions = (skuPath: string): void => {
|
|
42
|
-
this._service.setCurrentItem(undefined)
|
|
43
|
-
this._buyOptionsSkuPath = skuPath
|
|
44
|
-
this._paused = true
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
hideBuyOptions = (): void => {
|
|
48
|
-
this._buyOptionsSkuPath = undefined
|
|
49
|
-
this._paused = false
|
|
50
|
-
if (this._lastActivity) {
|
|
51
|
-
this._lastActivity = Date.now()
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
get buyOptionsSkuPath(): string | undefined {
|
|
56
|
-
return this._buyOptionsSkuPath
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
tick = () => {
|
|
60
|
-
if (
|
|
61
|
-
!this._paused
|
|
62
|
-
&&
|
|
63
|
-
this._lastActivity
|
|
64
|
-
&&
|
|
65
|
-
(Date.now() - this._lastActivity >= CommerceUIStore.TIMEOUT)
|
|
66
|
-
) {
|
|
67
|
-
this._activeItem = undefined
|
|
68
|
-
this._lastActivity = undefined
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
itemQuantityChanged = (item: LineItem, val: number, oldVal: number, ): void => {
|
|
73
|
-
|
|
74
|
-
if (val === 0) {
|
|
75
|
-
if (this._activeItem?.sku === item.sku) {
|
|
76
|
-
this._activeItem = undefined
|
|
77
|
-
this._lastActivity = undefined
|
|
78
|
-
}
|
|
79
|
-
// otherwise ignore
|
|
80
|
-
}
|
|
81
|
-
else if (val < oldVal) {
|
|
82
|
-
if (this._activeItem?.sku === item.sku) {
|
|
83
|
-
this._lastActivity = Date.now()
|
|
84
|
-
}
|
|
85
|
-
// otherwise ignore
|
|
86
|
-
}
|
|
87
|
-
else {
|
|
88
|
-
this._activeItem = item
|
|
89
|
-
this._lastActivity = Date.now()
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
get item(): LineItem | undefined {
|
|
94
|
-
return this._activeItem
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
export {
|
|
99
|
-
CommerceUIStore,
|
|
100
|
-
type CommerceUI
|
|
101
|
-
}
|