@hanzo/commerce 7.3.2 → 7.3.4
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/Icons.tsx +34 -34
- package/components/add-to-cart-widget.tsx +183 -183
- package/components/buy/buy-card.tsx +259 -259
- package/components/buy/carousel-buy-card.tsx +242 -242
- package/components/buy/multi-family/all-variants-carousel.tsx +261 -261
- package/components/buy/multi-family/family-carousel/index.tsx +77 -77
- package/components/buy/multi-family/family-carousel/slide.tsx +83 -83
- package/components/buy/multi-family/family-carousel/state.ts +87 -87
- package/components/buy/multi-family/index.ts +2 -2
- package/components/buy/single-family-selector.tsx +90 -90
- package/components/buy/title-and-byline.tsx +25 -25
- package/components/cart/cart-panel/cart-line-item.tsx +76 -76
- package/components/cart/cart-panel/index.tsx +154 -154
- package/components/cart/cart-panel/promo-code.tsx +106 -106
- package/components/cart/cart-panel/total-area.tsx +60 -60
- package/components/checkout/payment-step-form/card-icon-row.tsx +26 -26
- package/components/checkout/payment-step-form/card-icons/amex.tsx +32 -32
- package/components/checkout/payment-step-form/card-icons/diners-club.tsx +13 -13
- package/components/checkout/payment-step-form/card-icons/discover.tsx +25 -25
- package/components/checkout/payment-step-form/card-icons/jcb.tsx +26 -26
- package/components/checkout/payment-step-form/card-icons/mastercard.tsx +27 -27
- package/components/checkout/payment-step-form/card-icons/visa.tsx +25 -25
- package/components/checkout/payment-step-form/cc-button.tsx +17 -17
- package/components/checkout/payment-step-form/contact-form.tsx +49 -49
- package/components/checkout/payment-step-form/crypto-icons/btc.tsx +11 -11
- package/components/checkout/payment-step-form/crypto-icons/eth.tsx +20 -20
- package/components/checkout/payment-step-form/crypto-icons/usdt.tsx +13 -13
- package/components/checkout/payment-step-form/index.tsx +122 -122
- package/components/checkout/payment-step-form/methods/bank-transfer.tsx +79 -79
- package/components/checkout/payment-step-form/methods/card.tsx +232 -232
- package/components/checkout/payment-step-form/methods/crypto.tsx +227 -227
- package/components/checkout/payment-step-form/methods/index.ts +22 -22
- package/components/checkout/shipping-step-form.tsx +172 -172
- package/components/index.ts +11 -11
- package/components/item/product-card.tsx +48 -48
- package/components/item-selector/button.tsx +188 -188
- package/components/item-selector/carousel/index.tsx +197 -197
- package/components/item-selector/carousel/slider.tsx +40 -40
- package/components/item-selector/index.ts +5 -5
- package/components/item-selector/quantity-indicator.tsx +48 -48
- package/components/node-tabs/index.tsx +91 -91
- package/components/node-tabs/node-image.tsx +31 -31
- package/index.ts +12 -12
- package/package.json +60 -60
- package/service/context.tsx +45 -45
- package/service/debug.ts +41 -41
- package/service/impls/standalone/actual-line-item.ts +135 -135
- package/service/impls/standalone/get-instance.ts +64 -64
- package/service/impls/standalone/index.ts +579 -579
- package/service/impls/standalone/order/firebase.ts +14 -14
- package/service/impls/standalone/order/index.ts +128 -128
- package/service/impls/standalone/persistence.ts +33 -33
- package/service/path-utils.ts +25 -25
- package/service/sep.ts +6 -6
- package/tsconfig.json +10 -10
- package/types/README.md +1 -1
- package/types/category-node.ts +49 -49
- package/types/checkout.ts +46 -46
- package/types/commerce-config.ts +13 -13
- package/types/commerce-service.ts +127 -127
- package/types/family.ts +25 -25
- package/types/index.ts +15 -15
- package/types/item-selector.ts +96 -96
- package/types/line-item.ts +29 -29
- package/types/multi-family-selector-props.ts +19 -19
- package/types/product.ts +20 -20
- package/types/promo.ts +9 -9
- package/types/selection-ui-specifier.ts +51 -51
- package/types/string-mutator.ts +13 -13
- package/types/token-separators.ts +7 -7
- package/util/analytics.ts +20 -20
- package/util/countries.ts +195 -195
- package/util/error.ts +34 -34
- package/util/index.ts +70 -70
- package/util/item-selector-options-accessor.ts +34 -34
- package/util/line-item-ref.ts +23 -23
- package/util/multi-family-selector-options-accessor.ts +14 -14
- package/util/obs-string-mutator.ts +22 -22
- package/util/product-media-accessor.ts +58 -58
- package/util/promo-codes.ts +108 -108
- package/util/selection-ui-specifiers.ts +29 -29
- package/util/square-payment.ts +42 -42
- package/util/use-sync-sku-param-w-current-item.ts +88 -88
|
@@ -1,259 +1,259 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
import React, { useRef, useEffect, type ComponentType } from 'react'
|
|
3
|
-
import { reaction, type IReactionDisposer } from 'mobx'
|
|
4
|
-
import { observer } from 'mobx-react-lite'
|
|
5
|
-
|
|
6
|
-
import { cn } from '@hanzo/ui/util'
|
|
7
|
-
import { ApplyTypography, MediaStack } from '@hanzo/ui/primitives'
|
|
8
|
-
|
|
9
|
-
import type {
|
|
10
|
-
ItemSelectorProps,
|
|
11
|
-
ItemSelectorCompProps,
|
|
12
|
-
LineItem,
|
|
13
|
-
Family,
|
|
14
|
-
CategoryNode,
|
|
15
|
-
} from '../../types'
|
|
16
|
-
|
|
17
|
-
import { useCommerce } from '../../service/context'
|
|
18
|
-
import * as pathUtils from '../../service/path-utils'
|
|
19
|
-
import { getFacetValuesMutator, ObsStringMutator } from '../../util'
|
|
20
|
-
|
|
21
|
-
import NodeTabs from '../node-tabs'
|
|
22
|
-
import AddToCartWidget from '../add-to-cart-widget'
|
|
23
|
-
|
|
24
|
-
const BuyCard: React.FC<{
|
|
25
|
-
skuPath: string
|
|
26
|
-
|
|
27
|
-
/** For any selectors that show a list of choices,
|
|
28
|
-
* starting with 'scrollAfter' elements,
|
|
29
|
-
* the list will scroll, and the card will
|
|
30
|
-
* have 'scrollHeightClx' applied to it to define and fix the height.
|
|
31
|
-
* This is most like something like '80vh'
|
|
32
|
-
* defaults are '5' and 'h-[70vh]'
|
|
33
|
-
* */
|
|
34
|
-
scrollAfter?: number
|
|
35
|
-
scrollHeightClx?: string
|
|
36
|
-
|
|
37
|
-
clx?: string
|
|
38
|
-
famWidgetClx?: string
|
|
39
|
-
addWidgetClx?: string
|
|
40
|
-
|
|
41
|
-
selector: ComponentType<ItemSelectorProps>
|
|
42
|
-
selectorProps?: ItemSelectorCompProps
|
|
43
|
-
|
|
44
|
-
/** Show all items from all sibling families
|
|
45
|
-
* If skuPath defines a Family, the first of it's items will
|
|
46
|
-
* be selected. The facet widget will just select a the first
|
|
47
|
-
* item in the chosen Family without changings the overall set
|
|
48
|
-
* of choices. */
|
|
49
|
-
allVariants?: boolean
|
|
50
|
-
|
|
51
|
-
familyTabAs?: 'image' | 'label' | 'image-and-label'
|
|
52
|
-
showItemMedia?: boolean
|
|
53
|
-
|
|
54
|
-
onQuantityChanged?: (sku: string, oldV: number, newV: number) => void
|
|
55
|
-
mobile?: boolean
|
|
56
|
-
}> = observer(({
|
|
57
|
-
skuPath,
|
|
58
|
-
scrollAfter=5,
|
|
59
|
-
scrollHeightClx='h-[80vh]',
|
|
60
|
-
clx='',
|
|
61
|
-
famWidgetClx='',
|
|
62
|
-
addWidgetClx='',
|
|
63
|
-
selector: Selector,
|
|
64
|
-
selectorProps={
|
|
65
|
-
clx: '',
|
|
66
|
-
soleItemClx: '',
|
|
67
|
-
itemClx: '',
|
|
68
|
-
ext: {}
|
|
69
|
-
},
|
|
70
|
-
familyTabAs='image-and-label',
|
|
71
|
-
showItemMedia=true,
|
|
72
|
-
allVariants=false,
|
|
73
|
-
mobile=false,
|
|
74
|
-
onQuantityChanged,
|
|
75
|
-
}) => {
|
|
76
|
-
|
|
77
|
-
const cmmc = useCommerce()
|
|
78
|
-
const inst = useRef<{
|
|
79
|
-
level: number,
|
|
80
|
-
requestedFamily : Family | undefined
|
|
81
|
-
requestedNode: CategoryNode | undefined
|
|
82
|
-
currentFamTokenMutator: ObsStringMutator
|
|
83
|
-
disposers: IReactionDisposer[]
|
|
84
|
-
} | undefined>(undefined)
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
useEffect(() => {
|
|
88
|
-
|
|
89
|
-
let requestedFamily = cmmc.getFamilyById(skuPath)
|
|
90
|
-
let requestedNode = requestedFamily ? undefined : cmmc.getNodeAtPath(skuPath)
|
|
91
|
-
let initialFamily = undefined
|
|
92
|
-
|
|
93
|
-
const { paths, level } = pathUtils.getSelectedPaths(skuPath)
|
|
94
|
-
|
|
95
|
-
if (level === 0) {
|
|
96
|
-
throw new Error('BuyCard.useEffect(): must specify at least at least one Level in skuPath!')
|
|
97
|
-
}
|
|
98
|
-
if (requestedFamily) {
|
|
99
|
-
if (allVariants && level >= 2) {
|
|
100
|
-
// select all siblings of requestedFamily.
|
|
101
|
-
// ie, go one level back
|
|
102
|
-
delete paths[level]
|
|
103
|
-
initialFamily = requestedFamily
|
|
104
|
-
requestedFamily = undefined
|
|
105
|
-
requestedNode = cmmc.getNodeAtPath(pathUtils.getParentPath(skuPath))
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
else {
|
|
109
|
-
// select siblings paths
|
|
110
|
-
if (allVariants) {
|
|
111
|
-
paths[level + 1] = [...requestedNode!.subNodes!.map((node) => (node.skuToken))]
|
|
112
|
-
}
|
|
113
|
-
// Actually select the the first fam at the fam level,
|
|
114
|
-
else {
|
|
115
|
-
paths[level + 1] = [requestedNode!.subNodes![0].skuToken]
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
const selectedFams = cmmc.selectPaths(paths)
|
|
120
|
-
initialFamily = initialFamily ? initialFamily : (requestedFamily ? requestedFamily : selectedFams[0])
|
|
121
|
-
cmmc.setCurrentItem(initialFamily.products[0].sku)
|
|
122
|
-
const currentFamTokenMutator = new ObsStringMutator(pathUtils.lastToken(initialFamily!.id))
|
|
123
|
-
|
|
124
|
-
inst.current = {
|
|
125
|
-
level,
|
|
126
|
-
requestedFamily,
|
|
127
|
-
requestedNode,
|
|
128
|
-
currentFamTokenMutator,
|
|
129
|
-
disposers: []
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
if (!allVariants) {
|
|
133
|
-
inst.current?.disposers.push(reaction(
|
|
134
|
-
() => {
|
|
135
|
-
const fams = cmmc.selectedFamilies
|
|
136
|
-
return (fams.length > 0) ? fams[0].id : undefined
|
|
137
|
-
},
|
|
138
|
-
(famId) => {
|
|
139
|
-
if (famId && famId !== cmmc.currentItem?.familyId) {
|
|
140
|
-
const fam = cmmc.getFamilyById(famId)
|
|
141
|
-
if (fam) {
|
|
142
|
-
cmmc.setCurrentItem(fam.products[0].sku)
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
))
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
return () => {
|
|
150
|
-
inst.current?.disposers.forEach((d) => {d()})
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
}, [])
|
|
154
|
-
|
|
155
|
-
const setFamilyPath = (pathValue: string | null) => {
|
|
156
|
-
if (inst.current?.currentFamTokenMutator.get() !== pathValue) {
|
|
157
|
-
inst.current?.currentFamTokenMutator.set(pathValue)
|
|
158
|
-
}
|
|
159
|
-
const found = cmmc.selectedItems.find((item) => (item.familyId.endsWith(pathValue!)))
|
|
160
|
-
if (found) {
|
|
161
|
-
cmmc.setCurrentItem(found.sku)
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
const selectSku = (sku: string) => {
|
|
166
|
-
cmmc.setCurrentItem(sku)
|
|
167
|
-
const pathValue = pathUtils.lastToken(cmmc.currentItem!.familyId)
|
|
168
|
-
if (pathValue !== inst.current?.currentFamTokenMutator.get()) {
|
|
169
|
-
inst.current?.currentFamTokenMutator.set(pathValue)
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
const famTitle = inst.current?.requestedFamily ? inst.current.requestedFamily.title : cmmc.selectedFamilies[0].title
|
|
174
|
-
|
|
175
|
-
const itemsToShow = (!cmmc.hasSelection) ? undefined :
|
|
176
|
-
allVariants ? cmmc.selectedItems : cmmc.selectedFamilies[0].products as LineItem[]
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
const TitleArea: React.FC<{
|
|
180
|
-
title: string
|
|
181
|
-
byline?: string
|
|
182
|
-
clx?: string
|
|
183
|
-
}> = ({
|
|
184
|
-
title,
|
|
185
|
-
byline,
|
|
186
|
-
clx=''
|
|
187
|
-
}) => (
|
|
188
|
-
<ApplyTypography className={clx}>
|
|
189
|
-
<h3>{title}</h3>
|
|
190
|
-
{byline && (<h6>{byline}</h6>)}
|
|
191
|
-
</ApplyTypography>
|
|
192
|
-
)
|
|
193
|
-
|
|
194
|
-
const scroll = !!(itemsToShow && itemsToShow.length > scrollAfter)
|
|
195
|
-
return (
|
|
196
|
-
<div className={cn(
|
|
197
|
-
'px-4 md:px-6 pt-3 pb-4 flex flex-col items-center min-h-[60vh]',
|
|
198
|
-
clx,
|
|
199
|
-
scroll ? scrollHeightClx : 'h-auto'
|
|
200
|
-
)} >
|
|
201
|
-
{inst.current?.requestedNode && (<>
|
|
202
|
-
<ApplyTypography className={'mb-2 ' + (scroll ? 'shrink-0' : '')}>
|
|
203
|
-
<h3>{inst.current!.requestedNode.label}</h3>
|
|
204
|
-
{inst.current!.requestedNode.subNodesLabel && (
|
|
205
|
-
<h6 className='!text-center font-bold text-muted mt-3'>{inst.current!.requestedNode.subNodesLabel}</h6>
|
|
206
|
-
)}
|
|
207
|
-
</ApplyTypography>
|
|
208
|
-
<NodeTabs
|
|
209
|
-
className={cn(
|
|
210
|
-
'grid gap-0 align-stretch justify-normal ' + `grid-cols-${inst.current.requestedNode.subNodes!.length}`,
|
|
211
|
-
'border-b-2 rounded-lg border-level-3 mb-4 -mr-2 -ml-2 max-w-[460px] h-10', // height is needed for iPhone bug
|
|
212
|
-
(scroll ? 'shrink-0' : ''),
|
|
213
|
-
famWidgetClx
|
|
214
|
-
)}
|
|
215
|
-
mobile={mobile}
|
|
216
|
-
mutator={allVariants ?
|
|
217
|
-
{
|
|
218
|
-
get: () => (inst.current!.currentFamTokenMutator.get()),
|
|
219
|
-
set: setFamilyPath
|
|
220
|
-
}
|
|
221
|
-
:
|
|
222
|
-
getFacetValuesMutator(inst.current.level + 1, cmmc)
|
|
223
|
-
}
|
|
224
|
-
itemClx='flex-col h-auto gap-0 py-1 px-3'
|
|
225
|
-
buttonClx={
|
|
226
|
-
'h-full ' +
|
|
227
|
-
'!border-level-3'
|
|
228
|
-
}
|
|
229
|
-
levelNodes={inst.current.requestedNode.subNodes!}
|
|
230
|
-
show={familyTabAs}
|
|
231
|
-
/>
|
|
232
|
-
</>)}
|
|
233
|
-
{!inst.current?.requestedNode && famTitle && (
|
|
234
|
-
<TitleArea title={famTitle} clx=''/>
|
|
235
|
-
)}
|
|
236
|
-
{(cmmc.currentItem && showItemMedia) && (
|
|
237
|
-
<MediaStack media={cmmc.currentItem} constrainTo={{w: 200, h: 200}} clx={(scroll ? 'shrink-0' : '')}/>
|
|
238
|
-
)}
|
|
239
|
-
{itemsToShow && (
|
|
240
|
-
<Selector
|
|
241
|
-
items={itemsToShow}
|
|
242
|
-
selectedItemRef={cmmc}
|
|
243
|
-
selectSku={selectSku}
|
|
244
|
-
{...selectorProps}
|
|
245
|
-
scrollable={scroll}
|
|
246
|
-
/>
|
|
247
|
-
)}
|
|
248
|
-
{(cmmc.currentItem) && (
|
|
249
|
-
<AddToCartWidget
|
|
250
|
-
item={cmmc.currentItem}
|
|
251
|
-
onQuantityChanged={onQuantityChanged}
|
|
252
|
-
className={cn('min-w-[160px] mx-auto mt-4', (scroll ? 'shrink-0' : ''), addWidgetClx)}
|
|
253
|
-
/>
|
|
254
|
-
)}
|
|
255
|
-
</div >
|
|
256
|
-
)
|
|
257
|
-
})
|
|
258
|
-
|
|
259
|
-
export default BuyCard
|
|
1
|
+
'use client'
|
|
2
|
+
import React, { useRef, useEffect, type ComponentType } from 'react'
|
|
3
|
+
import { reaction, type IReactionDisposer } from 'mobx'
|
|
4
|
+
import { observer } from 'mobx-react-lite'
|
|
5
|
+
|
|
6
|
+
import { cn } from '@hanzo/ui/util'
|
|
7
|
+
import { ApplyTypography, MediaStack } from '@hanzo/ui/primitives'
|
|
8
|
+
|
|
9
|
+
import type {
|
|
10
|
+
ItemSelectorProps,
|
|
11
|
+
ItemSelectorCompProps,
|
|
12
|
+
LineItem,
|
|
13
|
+
Family,
|
|
14
|
+
CategoryNode,
|
|
15
|
+
} from '../../types'
|
|
16
|
+
|
|
17
|
+
import { useCommerce } from '../../service/context'
|
|
18
|
+
import * as pathUtils from '../../service/path-utils'
|
|
19
|
+
import { getFacetValuesMutator, ObsStringMutator } from '../../util'
|
|
20
|
+
|
|
21
|
+
import NodeTabs from '../node-tabs'
|
|
22
|
+
import AddToCartWidget from '../add-to-cart-widget'
|
|
23
|
+
|
|
24
|
+
const BuyCard: React.FC<{
|
|
25
|
+
skuPath: string
|
|
26
|
+
|
|
27
|
+
/** For any selectors that show a list of choices,
|
|
28
|
+
* starting with 'scrollAfter' elements,
|
|
29
|
+
* the list will scroll, and the card will
|
|
30
|
+
* have 'scrollHeightClx' applied to it to define and fix the height.
|
|
31
|
+
* This is most like something like '80vh'
|
|
32
|
+
* defaults are '5' and 'h-[70vh]'
|
|
33
|
+
* */
|
|
34
|
+
scrollAfter?: number
|
|
35
|
+
scrollHeightClx?: string
|
|
36
|
+
|
|
37
|
+
clx?: string
|
|
38
|
+
famWidgetClx?: string
|
|
39
|
+
addWidgetClx?: string
|
|
40
|
+
|
|
41
|
+
selector: ComponentType<ItemSelectorProps>
|
|
42
|
+
selectorProps?: ItemSelectorCompProps
|
|
43
|
+
|
|
44
|
+
/** Show all items from all sibling families
|
|
45
|
+
* If skuPath defines a Family, the first of it's items will
|
|
46
|
+
* be selected. The facet widget will just select a the first
|
|
47
|
+
* item in the chosen Family without changings the overall set
|
|
48
|
+
* of choices. */
|
|
49
|
+
allVariants?: boolean
|
|
50
|
+
|
|
51
|
+
familyTabAs?: 'image' | 'label' | 'image-and-label'
|
|
52
|
+
showItemMedia?: boolean
|
|
53
|
+
|
|
54
|
+
onQuantityChanged?: (sku: string, oldV: number, newV: number) => void
|
|
55
|
+
mobile?: boolean
|
|
56
|
+
}> = observer(({
|
|
57
|
+
skuPath,
|
|
58
|
+
scrollAfter=5,
|
|
59
|
+
scrollHeightClx='h-[80vh]',
|
|
60
|
+
clx='',
|
|
61
|
+
famWidgetClx='',
|
|
62
|
+
addWidgetClx='',
|
|
63
|
+
selector: Selector,
|
|
64
|
+
selectorProps={
|
|
65
|
+
clx: '',
|
|
66
|
+
soleItemClx: '',
|
|
67
|
+
itemClx: '',
|
|
68
|
+
ext: {}
|
|
69
|
+
},
|
|
70
|
+
familyTabAs='image-and-label',
|
|
71
|
+
showItemMedia=true,
|
|
72
|
+
allVariants=false,
|
|
73
|
+
mobile=false,
|
|
74
|
+
onQuantityChanged,
|
|
75
|
+
}) => {
|
|
76
|
+
|
|
77
|
+
const cmmc = useCommerce()
|
|
78
|
+
const inst = useRef<{
|
|
79
|
+
level: number,
|
|
80
|
+
requestedFamily : Family | undefined
|
|
81
|
+
requestedNode: CategoryNode | undefined
|
|
82
|
+
currentFamTokenMutator: ObsStringMutator
|
|
83
|
+
disposers: IReactionDisposer[]
|
|
84
|
+
} | undefined>(undefined)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
useEffect(() => {
|
|
88
|
+
|
|
89
|
+
let requestedFamily = cmmc.getFamilyById(skuPath)
|
|
90
|
+
let requestedNode = requestedFamily ? undefined : cmmc.getNodeAtPath(skuPath)
|
|
91
|
+
let initialFamily = undefined
|
|
92
|
+
|
|
93
|
+
const { paths, level } = pathUtils.getSelectedPaths(skuPath)
|
|
94
|
+
|
|
95
|
+
if (level === 0) {
|
|
96
|
+
throw new Error('BuyCard.useEffect(): must specify at least at least one Level in skuPath!')
|
|
97
|
+
}
|
|
98
|
+
if (requestedFamily) {
|
|
99
|
+
if (allVariants && level >= 2) {
|
|
100
|
+
// select all siblings of requestedFamily.
|
|
101
|
+
// ie, go one level back
|
|
102
|
+
delete paths[level]
|
|
103
|
+
initialFamily = requestedFamily
|
|
104
|
+
requestedFamily = undefined
|
|
105
|
+
requestedNode = cmmc.getNodeAtPath(pathUtils.getParentPath(skuPath))
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
// select siblings paths
|
|
110
|
+
if (allVariants) {
|
|
111
|
+
paths[level + 1] = [...requestedNode!.subNodes!.map((node) => (node.skuToken))]
|
|
112
|
+
}
|
|
113
|
+
// Actually select the the first fam at the fam level,
|
|
114
|
+
else {
|
|
115
|
+
paths[level + 1] = [requestedNode!.subNodes![0].skuToken]
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const selectedFams = cmmc.selectPaths(paths)
|
|
120
|
+
initialFamily = initialFamily ? initialFamily : (requestedFamily ? requestedFamily : selectedFams[0])
|
|
121
|
+
cmmc.setCurrentItem(initialFamily.products[0].sku)
|
|
122
|
+
const currentFamTokenMutator = new ObsStringMutator(pathUtils.lastToken(initialFamily!.id))
|
|
123
|
+
|
|
124
|
+
inst.current = {
|
|
125
|
+
level,
|
|
126
|
+
requestedFamily,
|
|
127
|
+
requestedNode,
|
|
128
|
+
currentFamTokenMutator,
|
|
129
|
+
disposers: []
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (!allVariants) {
|
|
133
|
+
inst.current?.disposers.push(reaction(
|
|
134
|
+
() => {
|
|
135
|
+
const fams = cmmc.selectedFamilies
|
|
136
|
+
return (fams.length > 0) ? fams[0].id : undefined
|
|
137
|
+
},
|
|
138
|
+
(famId) => {
|
|
139
|
+
if (famId && famId !== cmmc.currentItem?.familyId) {
|
|
140
|
+
const fam = cmmc.getFamilyById(famId)
|
|
141
|
+
if (fam) {
|
|
142
|
+
cmmc.setCurrentItem(fam.products[0].sku)
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
))
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return () => {
|
|
150
|
+
inst.current?.disposers.forEach((d) => {d()})
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
}, [])
|
|
154
|
+
|
|
155
|
+
const setFamilyPath = (pathValue: string | null) => {
|
|
156
|
+
if (inst.current?.currentFamTokenMutator.get() !== pathValue) {
|
|
157
|
+
inst.current?.currentFamTokenMutator.set(pathValue)
|
|
158
|
+
}
|
|
159
|
+
const found = cmmc.selectedItems.find((item) => (item.familyId.endsWith(pathValue!)))
|
|
160
|
+
if (found) {
|
|
161
|
+
cmmc.setCurrentItem(found.sku)
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const selectSku = (sku: string) => {
|
|
166
|
+
cmmc.setCurrentItem(sku)
|
|
167
|
+
const pathValue = pathUtils.lastToken(cmmc.currentItem!.familyId)
|
|
168
|
+
if (pathValue !== inst.current?.currentFamTokenMutator.get()) {
|
|
169
|
+
inst.current?.currentFamTokenMutator.set(pathValue)
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const famTitle = inst.current?.requestedFamily ? inst.current.requestedFamily.title : cmmc.selectedFamilies[0].title
|
|
174
|
+
|
|
175
|
+
const itemsToShow = (!cmmc.hasSelection) ? undefined :
|
|
176
|
+
allVariants ? cmmc.selectedItems : cmmc.selectedFamilies[0].products as LineItem[]
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
const TitleArea: React.FC<{
|
|
180
|
+
title: string
|
|
181
|
+
byline?: string
|
|
182
|
+
clx?: string
|
|
183
|
+
}> = ({
|
|
184
|
+
title,
|
|
185
|
+
byline,
|
|
186
|
+
clx=''
|
|
187
|
+
}) => (
|
|
188
|
+
<ApplyTypography className={clx}>
|
|
189
|
+
<h3>{title}</h3>
|
|
190
|
+
{byline && (<h6>{byline}</h6>)}
|
|
191
|
+
</ApplyTypography>
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
const scroll = !!(itemsToShow && itemsToShow.length > scrollAfter)
|
|
195
|
+
return (
|
|
196
|
+
<div className={cn(
|
|
197
|
+
'px-4 md:px-6 pt-3 pb-4 flex flex-col items-center min-h-[60vh]',
|
|
198
|
+
clx,
|
|
199
|
+
scroll ? scrollHeightClx : 'h-auto'
|
|
200
|
+
)} >
|
|
201
|
+
{inst.current?.requestedNode && (<>
|
|
202
|
+
<ApplyTypography className={'mb-2 ' + (scroll ? 'shrink-0' : '')}>
|
|
203
|
+
<h3>{inst.current!.requestedNode.label}</h3>
|
|
204
|
+
{inst.current!.requestedNode.subNodesLabel && (
|
|
205
|
+
<h6 className='!text-center font-bold text-muted mt-3'>{inst.current!.requestedNode.subNodesLabel}</h6>
|
|
206
|
+
)}
|
|
207
|
+
</ApplyTypography>
|
|
208
|
+
<NodeTabs
|
|
209
|
+
className={cn(
|
|
210
|
+
'grid gap-0 align-stretch justify-normal ' + `grid-cols-${inst.current.requestedNode.subNodes!.length}`,
|
|
211
|
+
'border-b-2 rounded-lg border-level-3 mb-4 -mr-2 -ml-2 max-w-[460px] h-10', // height is needed for iPhone bug
|
|
212
|
+
(scroll ? 'shrink-0' : ''),
|
|
213
|
+
famWidgetClx
|
|
214
|
+
)}
|
|
215
|
+
mobile={mobile}
|
|
216
|
+
mutator={allVariants ?
|
|
217
|
+
{
|
|
218
|
+
get: () => (inst.current!.currentFamTokenMutator.get()),
|
|
219
|
+
set: setFamilyPath
|
|
220
|
+
}
|
|
221
|
+
:
|
|
222
|
+
getFacetValuesMutator(inst.current.level + 1, cmmc)
|
|
223
|
+
}
|
|
224
|
+
itemClx='flex-col h-auto gap-0 py-1 px-3'
|
|
225
|
+
buttonClx={
|
|
226
|
+
'h-full ' +
|
|
227
|
+
'!border-level-3'
|
|
228
|
+
}
|
|
229
|
+
levelNodes={inst.current.requestedNode.subNodes!}
|
|
230
|
+
show={familyTabAs}
|
|
231
|
+
/>
|
|
232
|
+
</>)}
|
|
233
|
+
{!inst.current?.requestedNode && famTitle && (
|
|
234
|
+
<TitleArea title={famTitle} clx=''/>
|
|
235
|
+
)}
|
|
236
|
+
{(cmmc.currentItem && showItemMedia) && (
|
|
237
|
+
<MediaStack media={cmmc.currentItem} constrainTo={{w: 200, h: 200}} clx={(scroll ? 'shrink-0' : '')}/>
|
|
238
|
+
)}
|
|
239
|
+
{itemsToShow && (
|
|
240
|
+
<Selector
|
|
241
|
+
items={itemsToShow}
|
|
242
|
+
selectedItemRef={cmmc}
|
|
243
|
+
selectSku={selectSku}
|
|
244
|
+
{...selectorProps}
|
|
245
|
+
scrollable={scroll}
|
|
246
|
+
/>
|
|
247
|
+
)}
|
|
248
|
+
{(cmmc.currentItem) && (
|
|
249
|
+
<AddToCartWidget
|
|
250
|
+
item={cmmc.currentItem}
|
|
251
|
+
onQuantityChanged={onQuantityChanged}
|
|
252
|
+
className={cn('min-w-[160px] mx-auto mt-4', (scroll ? 'shrink-0' : ''), addWidgetClx)}
|
|
253
|
+
/>
|
|
254
|
+
)}
|
|
255
|
+
</div >
|
|
256
|
+
)
|
|
257
|
+
})
|
|
258
|
+
|
|
259
|
+
export default BuyCard
|