@hanzo/commerce 6.4.7 → 7.0.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/Icons.tsx +1 -1
- package/components/{buy/add-to-cart-widget.tsx → add-to-cart-widget.tsx} +34 -7
- package/components/buy/buy-button.tsx +22 -18
- package/components/buy/buy-card.tsx +3 -2
- package/components/buy/carousel-buy-card.tsx +19 -17
- package/components/buy/multi-family/all-variants-carousel.tsx +3 -3
- package/components/cart/cart-panel/cart-line-item.tsx +1 -1
- package/components/checkout/payment-step-form/contact-form.tsx +2 -2
- package/components/index.ts +4 -8
- package/components/item/product-card.tsx +2 -2
- package/components/item-selector/button.tsx +4 -3
- package/components/item-selector/carousel/index.tsx +1 -1
- package/components/{quantity-indicator.tsx → item-selector/quantity-indicator.tsx} +1 -1
- package/context/commerce-ui.ts +64 -22
- package/context/index.tsx +17 -7
- package/index.ts +2 -1
- package/package.json +10 -13
- package/tsconfig.json +1 -1
- package/types/item-selector.ts +3 -0
- package/util/index.ts +3 -3
- package/util/item-selector-options-accessor.ts +3 -0
- package/util/line-item-ref.ts +22 -0
- package/util/obs-string-mutator.ts +9 -9
- package/components/buy/_to_deprecate_select-family-item-card.tsx +0 -109
- package/components/buy/_to_deprecate_select-family-item-panel.tsx +0 -192
- package/components/buy/buy-drawer.tsx +0 -83
- package/components/buy/buy-trigger-wrapper.tsx +0 -20
- package/components/buy/please-keep.txt +0 -27
- package/components/cart/cart-icon.tsx +0 -10
package/components/Icons.tsx
CHANGED
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
'use client'
|
|
2
|
-
import React from 'react'
|
|
2
|
+
import React, { useEffect, useRef } from 'react'
|
|
3
|
+
import { reaction, type IReactionDisposer } from 'mobx'
|
|
3
4
|
import { observer } from 'mobx-react-lite'
|
|
4
5
|
|
|
5
|
-
import { Button,
|
|
6
|
-
import { cn } from '@hanzo/ui/util'
|
|
6
|
+
import { Button, buttonVariants } from '@hanzo/ui/primitives'
|
|
7
|
+
import { cn, type VariantProps } from '@hanzo/ui/util'
|
|
7
8
|
|
|
8
|
-
import
|
|
9
|
-
import type { LineItem } from '
|
|
10
|
-
import { sendFBEvent, sendGAEvent } from '
|
|
9
|
+
import Icons from './Icons'
|
|
10
|
+
import type { LineItem } from '../types'
|
|
11
|
+
import { sendFBEvent, sendGAEvent } from '../util/analytics'
|
|
12
|
+
import { useCommerceUI } from '..'
|
|
11
13
|
|
|
12
14
|
const AddToCartWidget: React.FC<{
|
|
13
15
|
item: LineItem
|
|
16
|
+
registerAdd?: boolean
|
|
14
17
|
disabled?: boolean
|
|
15
18
|
className?: string
|
|
16
19
|
buttonClx?: string
|
|
@@ -19,12 +22,36 @@ const AddToCartWidget: React.FC<{
|
|
|
19
22
|
}> = observer(({
|
|
20
23
|
item,
|
|
21
24
|
variant='primary',
|
|
25
|
+
registerAdd=true,
|
|
22
26
|
disabled=false,
|
|
23
27
|
className='',
|
|
24
28
|
buttonClx='',
|
|
25
29
|
onQuantityChanged
|
|
26
30
|
}) => {
|
|
27
31
|
|
|
32
|
+
const ui = useCommerceUI()
|
|
33
|
+
|
|
34
|
+
const reactionDisposer = useRef<IReactionDisposer | undefined>(undefined)
|
|
35
|
+
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
// Only tell the micro-drawer
|
|
38
|
+
// if we're not part of the cart ui,
|
|
39
|
+
// or part of the main drawer.
|
|
40
|
+
if (registerAdd && variant !== 'minimal') {
|
|
41
|
+
reactionDisposer.current = reaction(
|
|
42
|
+
() => (item.quantity),
|
|
43
|
+
(quantity: number, previous: number) => {
|
|
44
|
+
ui.itemQuantityChanged(item, quantity, previous)
|
|
45
|
+
}
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
return () => {
|
|
49
|
+
if (reactionDisposer.current) {
|
|
50
|
+
reactionDisposer.current()
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}, [])
|
|
54
|
+
|
|
28
55
|
const ROUNDED_VAL = 'lg'
|
|
29
56
|
// no need to safelist, since its used widely
|
|
30
57
|
const ROUNDED_CLX = ` rounded-${ROUNDED_VAL} `
|
|
@@ -149,7 +176,7 @@ const AddToCartWidget: React.FC<{
|
|
|
149
176
|
<Button
|
|
150
177
|
aria-label={'Add a ' + item.title + ' to cart'}
|
|
151
178
|
size={ghost ? 'xs' : 'default'}
|
|
152
|
-
variant={variant as
|
|
179
|
+
variant={variant === 'minimal' ? 'ghost' : (variant as VariantProps<typeof buttonVariants>['variant'])}
|
|
153
180
|
rounded={ROUNDED_VAL}
|
|
154
181
|
className={cn(buttonClx, className)}
|
|
155
182
|
onClick={inc}
|
|
@@ -1,31 +1,35 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
import React, {type PropsWithChildren} from 'react'
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { Button, buttonVariants } from '@hanzo/ui/primitives'
|
|
5
|
+
import { type VariantProps } from '@hanzo/ui/util'
|
|
5
6
|
|
|
6
|
-
import BuyTriggerWrapper from './buy-trigger-wrapper'
|
|
7
7
|
import { cn } from '@hanzo/ui/util'
|
|
8
|
+
import { useCommerceUI } from '../..'
|
|
8
9
|
|
|
9
|
-
const BuyButton: React.FC<
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
10
|
+
const BuyButton: React.FC<
|
|
11
|
+
PropsWithChildren &
|
|
12
|
+
VariantProps<typeof buttonVariants> &
|
|
13
|
+
{
|
|
14
|
+
skuPath: string
|
|
15
|
+
className?: string
|
|
16
|
+
}
|
|
17
|
+
> = ({
|
|
17
18
|
skuPath,
|
|
18
|
-
variant,
|
|
19
|
-
size,
|
|
20
19
|
children,
|
|
21
20
|
className='',
|
|
22
|
-
|
|
23
|
-
}) =>
|
|
24
|
-
<BuyTriggerWrapper skuPath={skuPath} mobile={mobile}
|
|
25
|
-
trigger={ <Button size={size} variant={variant} className={cn(className, '')}>{children}</Button> }
|
|
26
|
-
/>
|
|
27
|
-
)
|
|
21
|
+
...rest
|
|
22
|
+
}) => {
|
|
28
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
|
+
}
|
|
29
33
|
|
|
30
34
|
|
|
31
35
|
export default BuyButton
|
|
@@ -19,7 +19,7 @@ import * as pathUtils from '../../service/path-utils'
|
|
|
19
19
|
import { getFacetValuesMutator, ObsStringMutator } from '../../util'
|
|
20
20
|
|
|
21
21
|
import NodeTabs from '../node-tabs'
|
|
22
|
-
import AddToCartWidget from '
|
|
22
|
+
import AddToCartWidget from '../add-to-cart-widget'
|
|
23
23
|
|
|
24
24
|
const BuyCard: React.FC<{
|
|
25
25
|
skuPath: string
|
|
@@ -215,7 +215,7 @@ const BuyCard: React.FC<{
|
|
|
215
215
|
mobile={mobile}
|
|
216
216
|
mutator={allVariants ?
|
|
217
217
|
{
|
|
218
|
-
get: () => (inst.current!.currentFamTokenMutator.
|
|
218
|
+
get: () => (inst.current!.currentFamTokenMutator.get()),
|
|
219
219
|
set: setFamilyPath
|
|
220
220
|
}
|
|
221
221
|
:
|
|
@@ -248,6 +248,7 @@ const BuyCard: React.FC<{
|
|
|
248
248
|
{(cmmc.currentItem) && (
|
|
249
249
|
<AddToCartWidget
|
|
250
250
|
item={cmmc.currentItem}
|
|
251
|
+
registerAdd={false}
|
|
251
252
|
onQuantityChanged={onQuantityChanged}
|
|
252
253
|
className={cn('min-w-[160px] mx-auto mt-4', (scroll ? 'shrink-0' : ''), addWidgetClx)}
|
|
253
254
|
/>
|
|
@@ -8,7 +8,6 @@ import React, {
|
|
|
8
8
|
import { observer } from 'mobx-react-lite'
|
|
9
9
|
|
|
10
10
|
import { cn } from '@hanzo/ui/util'
|
|
11
|
-
import { Button } from '@hanzo/ui/primitives'
|
|
12
11
|
|
|
13
12
|
import type {
|
|
14
13
|
ItemSelectorProps,
|
|
@@ -29,7 +28,7 @@ import { CarouselItemSelector, ButtonItemSelector } from '../item-selector'
|
|
|
29
28
|
import SingleFamilySelector from './single-family-selector'
|
|
30
29
|
import { FamilyCarousel, AllVariantsCarousel } from './multi-family'
|
|
31
30
|
|
|
32
|
-
import AddToCartWidget from '
|
|
31
|
+
import AddToCartWidget from '../add-to-cart-widget'
|
|
33
32
|
|
|
34
33
|
const SCROLL = {
|
|
35
34
|
scrollAfter: 5,
|
|
@@ -52,15 +51,19 @@ const sortItems = (items: LineItem[], sort: 'asc' | 'desc' | 'none'): LineItem[]
|
|
|
52
51
|
|
|
53
52
|
const CarouselBuyCard: React.FC<{
|
|
54
53
|
skuPath: string
|
|
54
|
+
checkoutButton: React.ReactNode
|
|
55
55
|
clx?: string
|
|
56
|
+
selectorClx?: string
|
|
57
|
+
addBtnClx?: string
|
|
56
58
|
mobile?: boolean
|
|
57
|
-
handleCheckout: () => void
|
|
58
59
|
onQuantityChanged?: (sku: string, oldV: number, newV: number) => void
|
|
59
60
|
}> = ({
|
|
60
61
|
skuPath,
|
|
62
|
+
checkoutButton,
|
|
61
63
|
clx='',
|
|
64
|
+
selectorClx='',
|
|
65
|
+
addBtnClx='',
|
|
62
66
|
mobile=false,
|
|
63
|
-
handleCheckout,
|
|
64
67
|
onQuantityChanged,
|
|
65
68
|
}) => {
|
|
66
69
|
|
|
@@ -92,6 +95,12 @@ const CarouselBuyCard: React.FC<{
|
|
|
92
95
|
|
|
93
96
|
useEffect(() => {
|
|
94
97
|
|
|
98
|
+
if (!skuPath || skuPath.length === 0 ) {
|
|
99
|
+
// The component is being hidden (w an amination)
|
|
100
|
+
// keep things the same so no layout jump
|
|
101
|
+
return
|
|
102
|
+
}
|
|
103
|
+
|
|
95
104
|
const peek = cmmc.peek(skuPath)
|
|
96
105
|
if (typeof peek === 'string') {
|
|
97
106
|
throw new Error(peek)
|
|
@@ -194,26 +203,18 @@ const CarouselBuyCard: React.FC<{
|
|
|
194
203
|
<div className={clx}>
|
|
195
204
|
<AddToCartWidget
|
|
196
205
|
item={cmmc.currentItem}
|
|
206
|
+
registerAdd={false}
|
|
197
207
|
onQuantityChanged={onQuantityChanged}
|
|
198
208
|
variant={cmmc.cartEmpty ? 'primary' : 'outline'}
|
|
199
|
-
className=
|
|
209
|
+
className={addBtnClx}
|
|
200
210
|
/>
|
|
201
|
-
{!cmmc.cartEmpty &&
|
|
202
|
-
<Button
|
|
203
|
-
onClick={handleCheckout}
|
|
204
|
-
variant='primary'
|
|
205
|
-
rounded='lg'
|
|
206
|
-
className='min-w-[160px] w-full sm:max-w-[320px]'
|
|
207
|
-
>
|
|
208
|
-
Checkout
|
|
209
|
-
</Button>
|
|
210
|
-
)}
|
|
211
|
+
{!cmmc.cartEmpty && checkoutButton}
|
|
211
212
|
</div>
|
|
212
213
|
) : null))
|
|
213
214
|
|
|
214
215
|
return (
|
|
215
216
|
<div className={cn(
|
|
216
|
-
'px-4 md:px-6 pt-3 pb-4 flex flex-col gap-1 items-center
|
|
217
|
+
'px-4 md:px-6 pt-3 pb-4 flex flex-col gap-1 items-center',
|
|
217
218
|
clx,
|
|
218
219
|
r.current?.single?.scrollable ? SCROLL.scrollHeightClx : 'h-auto'
|
|
219
220
|
)}>
|
|
@@ -222,13 +223,14 @@ const CarouselBuyCard: React.FC<{
|
|
|
222
223
|
{...r.current.single}
|
|
223
224
|
mediaConstraint={MEDIA_CONSTRAINT}
|
|
224
225
|
mobile={mobile}
|
|
226
|
+
clx={selectorClx}
|
|
225
227
|
/>
|
|
226
228
|
) : (r.current?.multi && r.current.families && /* safegaurd for first render, etc. */ (
|
|
227
229
|
<MultiFamilyUI
|
|
228
230
|
{...r.current.multi}
|
|
229
231
|
families={r.current.families}
|
|
230
232
|
parent={r.current.node}
|
|
231
|
-
clx=
|
|
233
|
+
clx={selectorClx}
|
|
232
234
|
/>
|
|
233
235
|
))}
|
|
234
236
|
<Buttons clx={cn(
|
|
@@ -23,7 +23,7 @@ import {
|
|
|
23
23
|
accessMultiSelectorOptions
|
|
24
24
|
} from '../../../util'
|
|
25
25
|
|
|
26
|
-
import QuantityIndicator from '../../quantity-indicator'
|
|
26
|
+
import QuantityIndicator from '../../item-selector/quantity-indicator'
|
|
27
27
|
import { ButtonItemSelector, useCommerce } from '../../..'
|
|
28
28
|
|
|
29
29
|
const debugBorder = (c: 'r' | 'g' | 'b', disable: boolean = true): string => {
|
|
@@ -208,8 +208,8 @@ const AllVariantsCarousel: React.FC<MultiFamilySelectorProps> = ({
|
|
|
208
208
|
const { showItemSwatches } = accessMultiSelectorOptions(selectorOptions)
|
|
209
209
|
if (
|
|
210
210
|
!showItemSwatches ||
|
|
211
|
-
!cmmc.currentFamily
|
|
212
|
-
cmmc.currentFamily.products.length === 1
|
|
211
|
+
!cmmc.currentFamily
|
|
212
|
+
//|| cmmc.currentFamily.products.length === 1
|
|
213
213
|
) {
|
|
214
214
|
return null
|
|
215
215
|
}
|
|
@@ -8,7 +8,7 @@ import { Image } from '@hanzo/ui/primitives'
|
|
|
8
8
|
|
|
9
9
|
import type { LineItem } from '../../../types'
|
|
10
10
|
import { formatCurrencyValue } from '../../../util'
|
|
11
|
-
import AddToCartWidget from '../../
|
|
11
|
+
import AddToCartWidget from '../../add-to-cart-widget'
|
|
12
12
|
import { useCommerce } from '../../../context'
|
|
13
13
|
|
|
14
14
|
const DEF_IMG_SIZE=40
|
package/components/index.ts
CHANGED
|
@@ -1,17 +1,13 @@
|
|
|
1
|
-
export { default as AddToCartWidget } from './
|
|
2
|
-
export { default as BuyTriggerWrapper } from './buy/buy-trigger-wrapper'
|
|
1
|
+
export { default as AddToCartWidget } from './add-to-cart-widget'
|
|
3
2
|
export { default as BuyButton } from './buy/buy-button'
|
|
4
|
-
export { default as
|
|
5
|
-
export { default as BuyDrawer } from './buy/buy-drawer'
|
|
3
|
+
export { default as CarouselBuyCard } from './buy/carousel-buy-card'
|
|
6
4
|
|
|
7
5
|
export { default as CartAccordian } from './cart/cart-accordian'
|
|
8
6
|
export { default as CartPanel } from './cart/cart-panel'
|
|
9
7
|
|
|
10
|
-
export { default as
|
|
8
|
+
export { default as Icons } from './Icons'
|
|
11
9
|
export { default as PaymentStepForm } from './checkout/payment-step-form'
|
|
12
|
-
export { default as ShippingStepForm } from './checkout/shipping-step-form'
|
|
13
|
-
|
|
14
10
|
export { default as ProductCard } from './item/product-card'
|
|
15
|
-
export {
|
|
11
|
+
export { default as ShippingStepForm } from './checkout/shipping-step-form'
|
|
16
12
|
|
|
17
13
|
export * from './item-selector'
|
|
@@ -14,7 +14,7 @@ import { cn } from '@hanzo/ui/util'
|
|
|
14
14
|
import { formatCurrencyValue } from '../../util'
|
|
15
15
|
import type { LineItem } from '../../types'
|
|
16
16
|
|
|
17
|
-
import AddToCartWidget from '../
|
|
17
|
+
import AddToCartWidget from '../add-to-cart-widget'
|
|
18
18
|
|
|
19
19
|
interface ProductCardProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
20
20
|
item: LineItem
|
|
@@ -39,7 +39,7 @@ const ProductCard: React.FC<ProductCardProps> = ({
|
|
|
39
39
|
</CardTitle>
|
|
40
40
|
</CardContent>
|
|
41
41
|
<CardFooter className='p-4 flex flex-row justify-center'>
|
|
42
|
-
<AddToCartWidget item={item}/>
|
|
42
|
+
<AddToCartWidget item={item} />
|
|
43
43
|
</CardFooter>
|
|
44
44
|
</Card>
|
|
45
45
|
)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
'use client'
|
|
2
|
-
import React
|
|
2
|
+
import React from 'react'
|
|
3
3
|
import { observer } from 'mobx-react-lite'
|
|
4
4
|
|
|
5
5
|
import * as RadioGroupPrimitive from "@radix-ui/react-radio-group"
|
|
@@ -16,7 +16,7 @@ import type { Dimensions } from '@hanzo/ui/types'
|
|
|
16
16
|
import type { ItemSelectorProps, LineItem } from '../../types'
|
|
17
17
|
import { accessItemOptions, formatCurrencyValue } from '../../util'
|
|
18
18
|
|
|
19
|
-
import QuantityIndicator from '
|
|
19
|
+
import QuantityIndicator from './quantity-indicator'
|
|
20
20
|
|
|
21
21
|
const DEFAULT_CONSTRAINT = {h: 36, w: 72} // // Apple suggest 42px for clickability
|
|
22
22
|
|
|
@@ -78,6 +78,7 @@ const ButtonItemSelector: React.FC<ItemSelectorProps> = observer(({
|
|
|
78
78
|
showFamilyInOption,
|
|
79
79
|
buttonType,
|
|
80
80
|
horizButtons,
|
|
81
|
+
showButtonIfOnlyOne
|
|
81
82
|
} = accessItemOptions(options)
|
|
82
83
|
|
|
83
84
|
const showImage = buttonType !== 'text'
|
|
@@ -148,7 +149,7 @@ const ButtonItemSelector: React.FC<ItemSelectorProps> = observer(({
|
|
|
148
149
|
)
|
|
149
150
|
})
|
|
150
151
|
|
|
151
|
-
return items.length > 1 ? (
|
|
152
|
+
return showButtonIfOnlyOne || items.length > 1 ? (
|
|
152
153
|
<RadioGroup
|
|
153
154
|
className={cn(
|
|
154
155
|
(scrollable ? 'shrink min-h-0 gap-0' : (mobile ? 'gap-3' : 'gap-1')),
|
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
import type { ItemSelectorProps, LineItem } from '../../../types'
|
|
22
22
|
import { formatCurrencyValue, accessItemOptions } from '../../../util'
|
|
23
23
|
|
|
24
|
-
import QuantityIndicator from '
|
|
24
|
+
import QuantityIndicator from '../quantity-indicator'
|
|
25
25
|
import ItemCarouselSlider from './slider'
|
|
26
26
|
|
|
27
27
|
const DEFAULT_CONSTRAINT = {w: 250, h: 250}
|
|
@@ -5,7 +5,7 @@ import { observer } from 'mobx-react-lite'
|
|
|
5
5
|
import { type LucideProps } from 'lucide-react'
|
|
6
6
|
|
|
7
7
|
import { cn } from '@hanzo/ui/util'
|
|
8
|
-
import type { LineItem } from '
|
|
8
|
+
import type { LineItem } from '../../types'
|
|
9
9
|
|
|
10
10
|
// Generalize this.
|
|
11
11
|
const BagIcon: React.FC<LucideProps> = (props: LucideProps) => (
|
package/context/commerce-ui.ts
CHANGED
|
@@ -4,54 +4,96 @@ import {
|
|
|
4
4
|
makeObservable,
|
|
5
5
|
observable,
|
|
6
6
|
} from 'mobx'
|
|
7
|
+
import type { CommerceService, LineItem } from '../types'
|
|
8
|
+
|
|
9
|
+
|
|
7
10
|
|
|
8
11
|
interface CommerceUI {
|
|
9
12
|
showBuyOptions: (skuPath: string) => void
|
|
10
13
|
hideBuyOptions: () => void
|
|
14
|
+
get buyOptionsSkuPath(): string | undefined
|
|
11
15
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
get skuPath(): string | undefined
|
|
15
|
-
clearSkuPath: () => void
|
|
16
|
+
itemQuantityChanged(item: LineItem, val: number, prevVal: number): void
|
|
17
|
+
get activeItem(): LineItem | undefined
|
|
16
18
|
}
|
|
17
19
|
|
|
18
20
|
class CommerceUIStore implements CommerceUI {
|
|
19
21
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
+
static readonly TIMEOUT = 2500
|
|
23
|
+
_buyOptionsSkuPath: string | undefined = undefined
|
|
24
|
+
_paused: boolean = false
|
|
25
|
+
_activeItem: LineItem | undefined = undefined
|
|
26
|
+
_lastActivity: number | undefined = undefined
|
|
27
|
+
_service: CommerceService
|
|
22
28
|
|
|
23
|
-
constructor() {
|
|
29
|
+
constructor(s: CommerceService) {
|
|
30
|
+
this._service = s
|
|
24
31
|
makeObservable(this, {
|
|
25
|
-
|
|
26
|
-
|
|
32
|
+
_buyOptionsSkuPath: observable,
|
|
33
|
+
_activeItem: observable.shallow,
|
|
27
34
|
showBuyOptions: action,
|
|
28
35
|
hideBuyOptions: action,
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
36
|
+
buyOptionsSkuPath: computed,
|
|
37
|
+
itemQuantityChanged: action,
|
|
38
|
+
tick: action,
|
|
39
|
+
activeItem: computed
|
|
32
40
|
})
|
|
33
41
|
}
|
|
34
42
|
|
|
35
43
|
showBuyOptions = (skuPath: string): void => {
|
|
36
|
-
this.
|
|
37
|
-
this.
|
|
44
|
+
this._service.setCurrentItem(undefined)
|
|
45
|
+
this._buyOptionsSkuPath = skuPath
|
|
46
|
+
this._paused = true
|
|
38
47
|
}
|
|
39
48
|
|
|
40
49
|
hideBuyOptions = (): void => {
|
|
41
|
-
this.
|
|
50
|
+
this._buyOptionsSkuPath = undefined
|
|
51
|
+
this._paused = false
|
|
52
|
+
if (this._lastActivity) {
|
|
53
|
+
this._lastActivity = Date.now()
|
|
54
|
+
}
|
|
42
55
|
}
|
|
43
56
|
|
|
44
|
-
get
|
|
45
|
-
return this.
|
|
57
|
+
get buyOptionsSkuPath(): string | undefined {
|
|
58
|
+
return this._buyOptionsSkuPath
|
|
46
59
|
}
|
|
47
60
|
|
|
48
|
-
|
|
49
|
-
|
|
61
|
+
tick = () => {
|
|
62
|
+
if (
|
|
63
|
+
!this._paused
|
|
64
|
+
&&
|
|
65
|
+
this._lastActivity
|
|
66
|
+
&&
|
|
67
|
+
(Date.now() - this._lastActivity >= CommerceUIStore.TIMEOUT)
|
|
68
|
+
) {
|
|
69
|
+
this._activeItem = undefined
|
|
70
|
+
this._lastActivity = undefined
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
itemQuantityChanged = (item: LineItem, val: number, oldVal: number, ): void => {
|
|
75
|
+
|
|
76
|
+
if (val === 0) {
|
|
77
|
+
if (this._activeItem?.sku === item.sku) {
|
|
78
|
+
this._activeItem = undefined
|
|
79
|
+
this._lastActivity = undefined
|
|
80
|
+
}
|
|
81
|
+
// otherwise ignore
|
|
82
|
+
}
|
|
83
|
+
else if (val < oldVal) {
|
|
84
|
+
if (this._activeItem?.sku === item.sku) {
|
|
85
|
+
this._lastActivity = Date.now()
|
|
86
|
+
}
|
|
87
|
+
// otherwise ignore
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
this._activeItem = item
|
|
91
|
+
this._lastActivity = Date.now()
|
|
92
|
+
}
|
|
50
93
|
}
|
|
51
94
|
|
|
52
|
-
|
|
53
|
-
this.
|
|
54
|
-
this._optionsShowing = false
|
|
95
|
+
get activeItem(): LineItem | undefined {
|
|
96
|
+
return this._activeItem
|
|
55
97
|
}
|
|
56
98
|
}
|
|
57
99
|
|
package/context/index.tsx
CHANGED
|
@@ -3,7 +3,8 @@ import React, {
|
|
|
3
3
|
createContext,
|
|
4
4
|
useContext,
|
|
5
5
|
useRef,
|
|
6
|
-
type PropsWithChildren
|
|
6
|
+
type PropsWithChildren,
|
|
7
|
+
useEffect
|
|
7
8
|
} from 'react'
|
|
8
9
|
|
|
9
10
|
// https://dev.to/ivandotv/mobx-server-side-rendering-with-next-js-4m18
|
|
@@ -44,13 +45,21 @@ const CommerceProvider: React.FC<PropsWithChildren & {
|
|
|
44
45
|
uiSpecs
|
|
45
46
|
}) => {
|
|
46
47
|
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
const intervalId = setInterval(() => {
|
|
50
|
+
(valueRef.current.ui as CommerceUIStore).tick()
|
|
51
|
+
}, 250)
|
|
52
|
+
return () => {clearInterval(intervalId)}
|
|
53
|
+
}, [])
|
|
54
|
+
|
|
47
55
|
// TODO: Inject Promo fixture here from siteDef
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
})
|
|
56
|
+
const service = getInstance(families, rootNode, options, uiSpecs)
|
|
57
|
+
const ui = new CommerceUIStore(service)
|
|
58
|
+
|
|
59
|
+
const valueRef = useRef<CommerceContextValue>({service, ui})
|
|
60
|
+
|
|
52
61
|
return (
|
|
53
|
-
<CommerceContext.Provider value={
|
|
62
|
+
<CommerceContext.Provider value={valueRef.current}>
|
|
54
63
|
{children}
|
|
55
64
|
</CommerceContext.Provider>
|
|
56
65
|
)
|
|
@@ -60,4 +69,5 @@ export {
|
|
|
60
69
|
useCommerce,
|
|
61
70
|
useCommerceUI,
|
|
62
71
|
CommerceProvider
|
|
63
|
-
}
|
|
72
|
+
}
|
|
73
|
+
|
package/index.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hanzo/commerce",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "7.0.0",
|
|
4
4
|
"description": "e-commerce framework.",
|
|
5
|
-
"type": "module",
|
|
6
5
|
"publishConfig": {
|
|
7
6
|
"registry": "https://registry.npmjs.org/",
|
|
8
7
|
"access": "public",
|
|
@@ -23,9 +22,7 @@
|
|
|
23
22
|
"scripts": {
|
|
24
23
|
"lat": "npm show @hanzo/commerce version",
|
|
25
24
|
"pub": "npm publish",
|
|
26
|
-
"
|
|
27
|
-
"tc": "tsc",
|
|
28
|
-
"clean": "rm -rf dist && rm -rf node_modules"
|
|
25
|
+
"tc": "tsc"
|
|
29
26
|
},
|
|
30
27
|
"exports": {
|
|
31
28
|
".": "./index.ts",
|
|
@@ -34,14 +31,14 @@
|
|
|
34
31
|
"./debug": "./service/debug.ts"
|
|
35
32
|
},
|
|
36
33
|
"dependencies": {
|
|
37
|
-
"ethers": "^6.
|
|
38
|
-
"next-usequerystate": "^1.17.
|
|
34
|
+
"ethers": "^6.12.0",
|
|
35
|
+
"next-usequerystate": "^1.17.1",
|
|
39
36
|
"react-square-web-payments-sdk": "^3.2.1",
|
|
40
|
-
"square": "^35.
|
|
37
|
+
"square": "^35.1.0"
|
|
41
38
|
},
|
|
42
39
|
"peerDependencies": {
|
|
43
40
|
"@hanzo/auth": "^2.4.5",
|
|
44
|
-
"@hanzo/ui": "^3.
|
|
41
|
+
"@hanzo/ui": "^3.7.0",
|
|
45
42
|
"@hookform/resolvers": "^3.3.4",
|
|
46
43
|
"@radix-ui/react-radio-group": "^1.1.3",
|
|
47
44
|
"firebase": "^10.8.0",
|
|
@@ -52,13 +49,13 @@
|
|
|
52
49
|
"next": "14.1.3",
|
|
53
50
|
"react": "^18.2.0",
|
|
54
51
|
"react-dom": "^18.2.0",
|
|
55
|
-
"react-hook-form": "7.
|
|
52
|
+
"react-hook-form": "^7.51.3",
|
|
56
53
|
"zod": "3.21.4"
|
|
57
54
|
},
|
|
58
55
|
"devDependencies": {
|
|
59
|
-
"@types/react": "^18.2.
|
|
60
|
-
"@types/react-dom": "^18.2.
|
|
56
|
+
"@types/react": "^18.2.79",
|
|
57
|
+
"@types/react-dom": "^18.2.25",
|
|
61
58
|
"cross-fetch": "^4.0.0",
|
|
62
|
-
"typescript": "^5.
|
|
59
|
+
"typescript": "^5.4.5"
|
|
63
60
|
}
|
|
64
61
|
}
|
package/tsconfig.json
CHANGED
package/types/item-selector.ts
CHANGED
package/util/index.ts
CHANGED
|
@@ -28,7 +28,6 @@ export function formatCurrencyValue(price: number): string {
|
|
|
28
28
|
return (str.endsWith('.00')) ? str.replace('.00', '') : str
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
|
|
32
31
|
export const getFacetValuesMutator = (level: number, cmmc: CommerceService): StringMutator => {
|
|
33
32
|
|
|
34
33
|
const setLevel = (value: string, level: number ): void => {
|
|
@@ -57,7 +56,6 @@ export const getFacetValuesMutator = (level: number, cmmc: CommerceService): Str
|
|
|
57
56
|
} satisfies StringMutator
|
|
58
57
|
}
|
|
59
58
|
|
|
60
|
-
|
|
61
59
|
export { default as useSyncSkuParamWithCurrentItem } from './use-sync-sku-param-w-current-item'
|
|
62
60
|
export { default as processSquareCardPayment } from './square-payment'
|
|
63
61
|
export { default as ObsStringMutator } from './obs-string-mutator'
|
|
@@ -68,4 +66,6 @@ export * from './selection-ui-specifiers'
|
|
|
68
66
|
export { getErrorMessage } from './error'
|
|
69
67
|
|
|
70
68
|
export { default as accessItemOptions } from './item-selector-options-accessor'
|
|
71
|
-
export { default as accessMultiSelectorOptions } from './multi-family-selector-options-accessor'
|
|
69
|
+
export { default as accessMultiSelectorOptions } from './multi-family-selector-options-accessor'
|
|
70
|
+
|
|
71
|
+
export { default as LineItemRef} from './line-item-ref'
|
|
@@ -15,6 +15,8 @@ export default (options: ItemSelectorOptions | undefined = {}): Required<ItemSel
|
|
|
15
15
|
const horizButtons = 'horizButtons' in options ? options.horizButtons! : false
|
|
16
16
|
const showSlider = 'showSlider' in options ? options.showSlider! : true
|
|
17
17
|
|
|
18
|
+
const showButtonIfOnlyOne = 'showButtonIfOnlyOne' in options ? options.showButtonIfOnlyOne! : true
|
|
19
|
+
|
|
18
20
|
const sort = 'sort' in options ? options.sort! : 'none'
|
|
19
21
|
|
|
20
22
|
return {
|
|
@@ -27,6 +29,7 @@ export default (options: ItemSelectorOptions | undefined = {}): Required<ItemSel
|
|
|
27
29
|
buttonType,
|
|
28
30
|
horizButtons,
|
|
29
31
|
showSlider,
|
|
32
|
+
showButtonIfOnlyOne,
|
|
30
33
|
sort,
|
|
31
34
|
}
|
|
32
35
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { observable, action, computed, makeObservable } from 'mobx'
|
|
2
|
+
|
|
3
|
+
import type { LineItem, ObsLineItemRef } from '../types'
|
|
4
|
+
|
|
5
|
+
class LineItemRef implements ObsLineItemRef {
|
|
6
|
+
|
|
7
|
+
_item: LineItem | undefined = undefined
|
|
8
|
+
|
|
9
|
+
constructor() {
|
|
10
|
+
|
|
11
|
+
makeObservable(this, {
|
|
12
|
+
_item: observable,
|
|
13
|
+
item: computed,
|
|
14
|
+
set: action
|
|
15
|
+
})
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
get item(): LineItem | undefined { return this._item }
|
|
19
|
+
set = (v: LineItem | undefined): void => { this._item = v }
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export default LineItemRef
|
|
@@ -1,22 +1,22 @@
|
|
|
1
|
+
import {makeObservable, observable, action} from 'mobx'
|
|
1
2
|
|
|
2
|
-
import { makeObservable, observable, action } from 'mobx'
|
|
3
3
|
import type { StringMutator } from '../types'
|
|
4
4
|
|
|
5
5
|
class ObsStringMutator implements StringMutator {
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
_s: string | null
|
|
8
|
+
|
|
9
|
+
constructor(s: string) {
|
|
10
|
+
this._s = s
|
|
10
11
|
makeObservable(this, {
|
|
11
|
-
|
|
12
|
+
_s: observable,
|
|
12
13
|
set: action,
|
|
13
|
-
//get: computed
|
|
14
|
+
//get: computed .// no need
|
|
14
15
|
})
|
|
15
16
|
}
|
|
16
17
|
|
|
17
|
-
set(v: string | null): void { this.
|
|
18
|
-
get(): string | null { return this.
|
|
18
|
+
set(v: string | null): void { this._s = v }
|
|
19
|
+
get(): string | null { return this._s }
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
export default ObsStringMutator
|
|
22
|
-
|
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
import React from 'react'
|
|
3
|
-
import { observer } from 'mobx-react-lite'
|
|
4
|
-
|
|
5
|
-
import { cn } from '@hanzo/ui/util'
|
|
6
|
-
import { Skeleton } from '@hanzo/ui/primitives'
|
|
7
|
-
|
|
8
|
-
import type { ItemSelector, LineItem } from '../../types'
|
|
9
|
-
|
|
10
|
-
import AddToCartWidget from './add-to-cart-widget'
|
|
11
|
-
import { ButtonItemSelector } from '../item-selector'
|
|
12
|
-
import { formatCurrencyValue } from '../../util'
|
|
13
|
-
|
|
14
|
-
const SelectFamilyItemCard: React.FC<React.HTMLAttributes<HTMLDivElement> & ItemSelector & {
|
|
15
|
-
isLoading?: boolean
|
|
16
|
-
mobile?: boolean
|
|
17
|
-
title?: string
|
|
18
|
-
onQuantityChanged?: (sku: string, oldV: number, newV: number) => void
|
|
19
|
-
}> = /* NOT observer */({
|
|
20
|
-
items,
|
|
21
|
-
selectedItemRef: selItemRef,
|
|
22
|
-
selectSku,
|
|
23
|
-
className,
|
|
24
|
-
isLoading = false,
|
|
25
|
-
mobile = false,
|
|
26
|
-
title,
|
|
27
|
-
onQuantityChanged,
|
|
28
|
-
...props
|
|
29
|
-
}) => {
|
|
30
|
-
|
|
31
|
-
const soleOption = items.length === 1
|
|
32
|
-
|
|
33
|
-
const SelectProductComp: React.FC<{ className?: string }> = ({
|
|
34
|
-
className = ''
|
|
35
|
-
}) => {
|
|
36
|
-
|
|
37
|
-
const mobilePicker = (mobile || items.length > 6)
|
|
38
|
-
if (soleOption) {
|
|
39
|
-
const item = items[0] as LineItem
|
|
40
|
-
return (
|
|
41
|
-
<div className={cn('flex flex-col justify-center items-center ' + (mobilePicker ? 'h-[180px] ' : 'h-auto min-h-24'), className)}>
|
|
42
|
-
<p className='text-lg text-center font-semibold'>{item.optionLabel + ', ' + formatCurrencyValue(item.price)}</p>
|
|
43
|
-
</div>
|
|
44
|
-
)
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
return (
|
|
48
|
-
<div /* id='CV_AVAIL_AMOUNTS' */ className={cn(
|
|
49
|
-
'sm:w-pr-80 sm:mx-auto md:w-full flex flex-col justify-start items-center',
|
|
50
|
-
className
|
|
51
|
-
)}>
|
|
52
|
-
{title && (<div className={'h-[1px] bg-muted-3 ' + (mobilePicker ? 'w-pr-55' : 'w-pr-60') } /> )}
|
|
53
|
-
<ButtonItemSelector
|
|
54
|
-
items={items}
|
|
55
|
-
selectedItemRef={selItemRef}
|
|
56
|
-
selectSku={selectSku}
|
|
57
|
-
clx='mt-2'
|
|
58
|
-
scrollable={false}
|
|
59
|
-
itemClx='flex flex-row gap-2.5 items-center'
|
|
60
|
-
/>
|
|
61
|
-
</div>
|
|
62
|
-
)
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const AddToCartComp: React.FC<{ className?: string }> = observer(({ className = '' }) => (
|
|
66
|
-
// TODO disable if nothing selected
|
|
67
|
-
(selItemRef.item && !isLoading) && (
|
|
68
|
-
<AddToCartWidget
|
|
69
|
-
item={selItemRef.item}
|
|
70
|
-
onQuantityChanged={onQuantityChanged}
|
|
71
|
-
className={cn('lg:min-w-[160px] lg:mx-auto', className)}
|
|
72
|
-
/>
|
|
73
|
-
)
|
|
74
|
-
))
|
|
75
|
-
|
|
76
|
-
const TitleArea: React.FC<{ className?: string }> = observer(({ className = '' }) => (
|
|
77
|
-
|
|
78
|
-
isLoading ? (<Skeleton className={'h-8 w-full ' + className} />) : (
|
|
79
|
-
|
|
80
|
-
<div className={cn('text-center flex flex-col justify-start items-center', className)}>
|
|
81
|
-
<p className='font-heading text-center'>{title}</p>
|
|
82
|
-
</div>
|
|
83
|
-
|
|
84
|
-
)))
|
|
85
|
-
|
|
86
|
-
return mobile ? (
|
|
87
|
-
<div /* id='CV_OUTER' */
|
|
88
|
-
className={cn(
|
|
89
|
-
'w-full flex flex-col justify-between items-center gap-5 py-pr-6',
|
|
90
|
-
className
|
|
91
|
-
)}
|
|
92
|
-
{...props}
|
|
93
|
-
>
|
|
94
|
-
{title && (<TitleArea className='grow pt-3 mb-0' />)}
|
|
95
|
-
<SelectProductComp className='w-pr-65' />
|
|
96
|
-
<AddToCartComp className='w-pr-65' />
|
|
97
|
-
</div>
|
|
98
|
-
) : (
|
|
99
|
-
<div className={cn('', className)} {...props}>
|
|
100
|
-
{title && (<TitleArea className='' />)}
|
|
101
|
-
<div className='flex flex-col justify-start items-center gap-4'>
|
|
102
|
-
<SelectProductComp />
|
|
103
|
-
<AddToCartComp className='' />
|
|
104
|
-
</div>
|
|
105
|
-
</div>
|
|
106
|
-
)
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
export default SelectFamilyItemCard
|
|
@@ -1,192 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
import React from 'react'
|
|
3
|
-
import Image from 'next/image'
|
|
4
|
-
import { observer } from 'mobx-react-lite'
|
|
5
|
-
|
|
6
|
-
import { cn } from '@hanzo/ui/util'
|
|
7
|
-
import { Skeleton } from '@hanzo/ui/primitives'
|
|
8
|
-
|
|
9
|
-
import type { Family, ObsLineItemRef, LineItem } from '../../types'
|
|
10
|
-
import { formatCurrencyValue } from '../../util'
|
|
11
|
-
import { Icons } from '../Icons'
|
|
12
|
-
|
|
13
|
-
import AddToCartWidget from './add-to-cart-widget'
|
|
14
|
-
import { ButtonItemSelector } from '../item-selector'
|
|
15
|
-
|
|
16
|
-
const SelectFamilyItemPanel: React.FC<
|
|
17
|
-
React.HTMLAttributes<HTMLDivElement> &
|
|
18
|
-
{
|
|
19
|
-
family: Family
|
|
20
|
-
selectedItemRef: ObsLineItemRef
|
|
21
|
-
selectSku: (sku: string) => void
|
|
22
|
-
showQuantity?: boolean
|
|
23
|
-
isLoading?: boolean
|
|
24
|
-
mobile?: boolean
|
|
25
|
-
}
|
|
26
|
-
> = /* NOT observer */ ({
|
|
27
|
-
family,
|
|
28
|
-
selectedItemRef,
|
|
29
|
-
selectSku,
|
|
30
|
-
className,
|
|
31
|
-
showQuantity=true,
|
|
32
|
-
isLoading = false,
|
|
33
|
-
mobile = false,
|
|
34
|
-
...props
|
|
35
|
-
}) => {
|
|
36
|
-
|
|
37
|
-
const soleOption = family.products.length === 1
|
|
38
|
-
|
|
39
|
-
const FamilyImage: React.FC<{ className?: string }> = ({ className = '' }) => {
|
|
40
|
-
|
|
41
|
-
if (isLoading) {
|
|
42
|
-
// deliberately not Skeleton to have a better overall pulse effect.
|
|
43
|
-
return <div className={cn(
|
|
44
|
-
'bg-level-1 rounded-xl aspect-square w-full min-h-1 ', // +
|
|
45
|
-
//' min-h-[100px] sm:min-h-[200px] lg:aspect-auto 2xl:w-auto 2xl:aspect-square',
|
|
46
|
-
className)} />
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
return family.img ? (
|
|
50
|
-
// TODO: Why so many div's?
|
|
51
|
-
<div className={cn('flex flex-col justify-start', className)}>
|
|
52
|
-
<div className={cn('w-full border rounded-xl p-6 ')}>
|
|
53
|
-
<div className={cn('w-full aspect-square relative')}>
|
|
54
|
-
<Image
|
|
55
|
-
src={family.img.src}
|
|
56
|
-
fill
|
|
57
|
-
sizes="(max-width: 480px) 100vw, (max-width: 768px) 50vw, (max-width: 1200px) 50vw, 20vw"
|
|
58
|
-
alt={family.title}
|
|
59
|
-
className=''
|
|
60
|
-
loading='lazy'
|
|
61
|
-
style={{ objectFit: 'contain' }} />
|
|
62
|
-
</div>
|
|
63
|
-
</div>
|
|
64
|
-
</div>
|
|
65
|
-
) : (
|
|
66
|
-
<div
|
|
67
|
-
aria-label='Placeholder'
|
|
68
|
-
role='img'
|
|
69
|
-
aria-roledescription='placeholder'
|
|
70
|
-
className={cn('w-full flex items-center justify-center aspect-square' , className)}
|
|
71
|
-
>
|
|
72
|
-
<Icons.barcode className='h-9 w-9 text-muted' aria-hidden='true' />
|
|
73
|
-
</div>
|
|
74
|
-
)
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const AvailableAmounts: React.FC<{ className?: string }> = observer(({ className = '' }) => {
|
|
78
|
-
|
|
79
|
-
if (soleOption) return null
|
|
80
|
-
const mobilePicker = (mobile && family.products.length > 8)
|
|
81
|
-
|
|
82
|
-
return isLoading ? (
|
|
83
|
-
<Skeleton className={'min-h-[120px] w-pr-60 mx-auto ' + className} />
|
|
84
|
-
) : (
|
|
85
|
-
<div /* id='CV_AVAIL_AMOUNTS' */ className={cn(
|
|
86
|
-
'sm:w-pr-80 sm:mx-auto md:w-full flex flex-col justify-start items-center',
|
|
87
|
-
(mobilePicker ? 'gap-4' : 'gap-8'),
|
|
88
|
-
className
|
|
89
|
-
)}>
|
|
90
|
-
<div className='w-full flex flex-col justify-start items-center'>
|
|
91
|
-
<h6 className='text-center font-semibold'>Available options</h6>
|
|
92
|
-
<div className={'h-[1px] bg-muted-3 ' + (mobilePicker ? 'w-pr-55' : 'w-pr-60') } />
|
|
93
|
-
</div>
|
|
94
|
-
<ButtonItemSelector
|
|
95
|
-
items={family.products as LineItem[]}
|
|
96
|
-
selectedItemRef={selectedItemRef}
|
|
97
|
-
selectSku={selectSku}
|
|
98
|
-
options={{ showQuantity }}
|
|
99
|
-
scrollable={false}
|
|
100
|
-
clx='block columns-2 gap-4'
|
|
101
|
-
itemClx='flex flex-row gap-2 items-center mb-2.5'
|
|
102
|
-
/>
|
|
103
|
-
</div>
|
|
104
|
-
)
|
|
105
|
-
})
|
|
106
|
-
|
|
107
|
-
const AddToCartArea: React.FC<{ className?: string }> = observer(({ className = '' }) => (
|
|
108
|
-
(selectedItemRef.item && !isLoading) ? (
|
|
109
|
-
<AddToCartWidget item={selectedItemRef.item} className={className}/>
|
|
110
|
-
) : (
|
|
111
|
-
<div className={cn('h-6 w-12 invisible', className)} />
|
|
112
|
-
)
|
|
113
|
-
))
|
|
114
|
-
|
|
115
|
-
const TitleArea: React.FC<{ className?: string }> = observer(({ className = '' }) => (
|
|
116
|
-
|
|
117
|
-
isLoading ? (<Skeleton className={'h-12 w-pr-80 mx-auto ' + className} />) : (
|
|
118
|
-
|
|
119
|
-
<div className={cn('flex flex-col justify-start items-center', className)}>
|
|
120
|
-
<h3 className='text-base md:text-lg lg:text-2xl font-heading text-center'>
|
|
121
|
-
{family.parentTitle && (
|
|
122
|
-
<span>{family.parentTitle}<br className='md:hidden' /><span className='xs:hidden md:inline '>, </span></span>
|
|
123
|
-
)}
|
|
124
|
-
<span>{family.title}</span>
|
|
125
|
-
</h3>
|
|
126
|
-
{selectedItemRef.item?.sku ? (
|
|
127
|
-
<h6 className='text-center font-semibold'>
|
|
128
|
-
{(soleOption ? '' : (selectedItemRef.item.optionLabel + ': ')) + formatCurrencyValue(selectedItemRef.item.price)}
|
|
129
|
-
</h6>
|
|
130
|
-
) : ''}
|
|
131
|
-
</div>
|
|
132
|
-
)))
|
|
133
|
-
|
|
134
|
-
const Desc: React.FC<{ className?: string }> = ({ className = '' }) => (
|
|
135
|
-
isLoading ? (
|
|
136
|
-
<Skeleton className={'min-h-20 w-full grow mx-auto ' + className} />
|
|
137
|
-
) : (
|
|
138
|
-
<p className={cn('text-base lg:text-lg mb-6 xs:mb-0', className)}>{family.desc}</p>
|
|
139
|
-
)
|
|
140
|
-
)
|
|
141
|
-
|
|
142
|
-
return mobile ? (
|
|
143
|
-
<div /* id='CV_OUTER' */
|
|
144
|
-
className={cn(
|
|
145
|
-
'w-full h-[calc(100svh-96px)] max-h-[700px] flex flex-col justify-between ' +
|
|
146
|
-
'items-stretch gap-[4vh] mt-[2vh] pb-[6vh]',
|
|
147
|
-
className
|
|
148
|
-
)}
|
|
149
|
-
{...props}
|
|
150
|
-
>
|
|
151
|
-
<div /* id='CV_TITLE_AND_IMAGE_ROW' */ className='flex flex-row justify-between items-start w-full'>
|
|
152
|
-
{isLoading ? ( <Skeleton className={'min-h-30 w-full '} /> ) : (<>
|
|
153
|
-
<FamilyImage className='w-pr-33' />
|
|
154
|
-
<TitleArea className='grow pt-3 mb-0' />
|
|
155
|
-
</>)}
|
|
156
|
-
</div>
|
|
157
|
-
<Desc className='' />
|
|
158
|
-
<AvailableAmounts className='mb-[3vh]' />
|
|
159
|
-
<AddToCartArea className='w-pr-70 mx-auto' />
|
|
160
|
-
</div>
|
|
161
|
-
) : (
|
|
162
|
-
<div /* id='CV_OUTERMOST' */>
|
|
163
|
-
<div /* id='CV_OUTER' */ className={cn('w-full flex flex-row justify-between items-stretch gap-6 sm:gap-4', className)} {...props}>
|
|
164
|
-
<div /* id='CV_IMAGE_COL_SEP' */ className={'relative ' + (!isLoading ? 'md:hidden sm:min-w-[185px] xl:flex xl:w-pr-40' : '')}>
|
|
165
|
-
<FamilyImage className='w-full' />
|
|
166
|
-
</div>
|
|
167
|
-
<div /* id='CV_CONTENT_COL */ className={'flex flex-col xl:w-pr-60 justify-center ' + (soleOption ? '' : 'gap-6') }>
|
|
168
|
-
<div /* id='CV_MD_IMAGE_AND_TITLE */ className='hidden md:flex xl:hidden flex-row gap-x-3'>
|
|
169
|
-
<FamilyImage className='min-w-pr-30' />
|
|
170
|
-
<TitleArea className='grow' />
|
|
171
|
-
</div>
|
|
172
|
-
|
|
173
|
-
<div /* id='CV_CONTENT' */ className={'flex flex-col gap-2.5 ' + (isLoading ? 'justify-between h-full' : '')}>
|
|
174
|
-
<TitleArea className='md:hidden xl:flex' />
|
|
175
|
-
<Desc className='' />
|
|
176
|
-
</div>
|
|
177
|
-
<div /* id='CV_CTA_AREA_BIG' */ className='hidden lg:flex flex-col justify-start items-center gap-6'>
|
|
178
|
-
<AvailableAmounts />
|
|
179
|
-
<AddToCartArea className='' />
|
|
180
|
-
</div>
|
|
181
|
-
</div>
|
|
182
|
-
</div>
|
|
183
|
-
<div /* id='CV_CTA_AREA_COMPACT' */ className='lg:hidden flex p-4 flex-col justify-start items-center gap-6'>
|
|
184
|
-
<AvailableAmounts />
|
|
185
|
-
<AddToCartArea className='' />
|
|
186
|
-
</div>
|
|
187
|
-
</div>
|
|
188
|
-
)
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
export default SelectFamilyItemPanel
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
import React, { useState, type ReactNode } from 'react'
|
|
3
|
-
import { useRouter } from 'next/navigation'
|
|
4
|
-
|
|
5
|
-
import { X as LucideX} from 'lucide-react'
|
|
6
|
-
|
|
7
|
-
import {
|
|
8
|
-
Drawer,
|
|
9
|
-
DrawerTrigger,
|
|
10
|
-
DrawerContent,
|
|
11
|
-
Button,
|
|
12
|
-
} from '@hanzo/ui/primitives'
|
|
13
|
-
|
|
14
|
-
import { cn } from '@hanzo/ui/util'
|
|
15
|
-
|
|
16
|
-
//import BuyCard from './buy-card'
|
|
17
|
-
|
|
18
|
-
import CarouselBuyCard from './carousel-buy-card'
|
|
19
|
-
|
|
20
|
-
const BuyDrawer: React.FC<{
|
|
21
|
-
skuPath: string
|
|
22
|
-
trigger: ReactNode
|
|
23
|
-
triggerClx?: string
|
|
24
|
-
drawerClx?: string
|
|
25
|
-
cardClx?: string
|
|
26
|
-
mobile?: boolean
|
|
27
|
-
}> = ({
|
|
28
|
-
skuPath,
|
|
29
|
-
trigger,
|
|
30
|
-
triggerClx='',
|
|
31
|
-
drawerClx='',
|
|
32
|
-
cardClx='',
|
|
33
|
-
mobile=false
|
|
34
|
-
}) => {
|
|
35
|
-
|
|
36
|
-
const [open, setOpen] = useState<boolean>(false)
|
|
37
|
-
const router = useRouter()
|
|
38
|
-
|
|
39
|
-
return (
|
|
40
|
-
<Drawer open={open} onOpenChange={setOpen} >
|
|
41
|
-
<DrawerTrigger asChild className={triggerClx}>
|
|
42
|
-
{trigger}
|
|
43
|
-
</DrawerTrigger>
|
|
44
|
-
<DrawerContent
|
|
45
|
-
className={cn('rounded-t-xl mt-6 pb-12 h-auto min-h-[35vh] pt-6 md:max-w-[550px] md:mx-auto lg:max-w-[50vw]', drawerClx)}
|
|
46
|
-
>
|
|
47
|
-
<CarouselBuyCard
|
|
48
|
-
skuPath={skuPath}
|
|
49
|
-
handleCheckout={() => {router.push('/checkout')}}
|
|
50
|
-
mobile={mobile}
|
|
51
|
-
clx={cn('w-full', cardClx)}
|
|
52
|
-
/>
|
|
53
|
-
|
|
54
|
-
<Button
|
|
55
|
-
variant='ghost'
|
|
56
|
-
size='icon'
|
|
57
|
-
onClick={() => {setOpen(false)}}
|
|
58
|
-
className={'absolute top-4 right-4 w-8 h-8 group rounded-full p-1 hidden md:flex items-center'}
|
|
59
|
-
>
|
|
60
|
-
<LucideX className='w-6 h-6 text-muted group-hover:text-foreground'/>
|
|
61
|
-
</Button>
|
|
62
|
-
|
|
63
|
-
</DrawerContent>
|
|
64
|
-
</Drawer>
|
|
65
|
-
)
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export default BuyDrawer
|
|
69
|
-
|
|
70
|
-
/*
|
|
71
|
-
<BuyCard
|
|
72
|
-
skuPath={skuPath}
|
|
73
|
-
scrollAfter={spec.selector === 'carousel' ? 999 : undefined}
|
|
74
|
-
mobile={mobile}
|
|
75
|
-
onQuantityChanged={onQuantityChanged}
|
|
76
|
-
clx={cn('w-full', cardClx)}
|
|
77
|
-
selector={selector}
|
|
78
|
-
selectorProps={{soleItemClx:'mb-3'}}
|
|
79
|
-
showItemMedia={spec.selector !== 'carousel'}
|
|
80
|
-
familyTabAs='label'
|
|
81
|
-
allVariants={spec.allVariants}
|
|
82
|
-
/>
|
|
83
|
-
*/
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
import React from 'react'
|
|
3
|
-
|
|
4
|
-
import BuyDrawer from './buy-drawer'
|
|
5
|
-
|
|
6
|
-
const BuyTriggerWrapper: React.FC<{
|
|
7
|
-
skuPath: string
|
|
8
|
-
trigger: React.ReactNode
|
|
9
|
-
mobileTrigger?: React.ReactNode
|
|
10
|
-
mobile?: boolean
|
|
11
|
-
}> = ({
|
|
12
|
-
skuPath,
|
|
13
|
-
trigger,
|
|
14
|
-
mobileTrigger,
|
|
15
|
-
mobile
|
|
16
|
-
}) => (
|
|
17
|
-
<BuyDrawer skuPath={skuPath} trigger={trigger} mobile={mobile}/>
|
|
18
|
-
)
|
|
19
|
-
|
|
20
|
-
export default BuyTriggerWrapper
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
{/*
|
|
2
|
-
<NodeTabs
|
|
3
|
-
className={cn(
|
|
4
|
-
'grid gap-0 align-stretch justify-normal ' + `grid-cols-${inst.current.parentNode.subNodes!.length}`,
|
|
5
|
-
'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
|
|
6
|
-
(scroll ? 'shrink-0' : ''),
|
|
7
|
-
famWidgetClx
|
|
8
|
-
)}
|
|
9
|
-
mobile={mobile}
|
|
10
|
-
mutator={allVariants ?
|
|
11
|
-
{
|
|
12
|
-
get: () => (inst.current!.currentFamily.s),
|
|
13
|
-
set: setFamilyPath
|
|
14
|
-
}
|
|
15
|
-
:
|
|
16
|
-
getFacetValuesMutator(inst.current.level + 1, cmmc)
|
|
17
|
-
}
|
|
18
|
-
itemClx='flex-col h-auto gap-0 py-1 px-3'
|
|
19
|
-
buttonClx={
|
|
20
|
-
'h-full ' +
|
|
21
|
-
'!border-level-3'
|
|
22
|
-
}
|
|
23
|
-
levelNodes={inst.current.parentNode.subNodes!}
|
|
24
|
-
show={familyTabAs}
|
|
25
|
-
/>
|
|
26
|
-
*/}
|
|
27
|
-
</>)}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import { type LucideProps } from 'lucide-react'
|
|
3
|
-
|
|
4
|
-
const CartIcon: React.FC<LucideProps> = (props: LucideProps) => (
|
|
5
|
-
<svg viewBox='0 0 32 32' xmlns='http://www.w3.org/2000/svg' {...props}>
|
|
6
|
-
<path d='m3.731 8-1.231 20h27l-1.231-20zm1.991 2.103h20.557l.972 15.794h-22.502l.972-15.794z'/>
|
|
7
|
-
<path d='m20.718 8.134c0-2.375-1.932-4.307-4.307-4.307s-4.307 1.932-4.307 4.307h-2.084c0-3.524 2.867-6.391 6.391-6.391s6.391 2.867 6.391 6.391z'/>
|
|
8
|
-
</svg>)
|
|
9
|
-
|
|
10
|
-
export default CartIcon
|