@hanzo/commerce 6.1.12 → 6.2.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-family-item-card.tsx +3 -4
- package/components/buy/_to_deprecate_select-family-item-panel.tsx +4 -5
- package/components/buy/add-to-cart-widget.tsx +43 -32
- package/components/buy/buy-button.tsx +1 -1
- package/components/buy/buy-card.tsx +17 -26
- package/components/buy/buy-drawer.tsx +5 -24
- package/components/buy/carousel-buy-card.tsx +145 -194
- package/components/buy/foo.txt +27 -0
- package/components/cart/cart-panel/cart-line-item.tsx +1 -1
- package/components/checkout/payment-step-form/contact-form.tsx +2 -2
- package/components/checkout/payment-step-form/methods/crypto.tsx +1 -2
- package/components/checkout/shipping-step-form.tsx +5 -5
- package/components/index.ts +1 -3
- package/components/item-selector/button.tsx +175 -0
- package/components/item-selector/carousel.tsx +162 -0
- package/components/item-selector/index.ts +5 -0
- package/components/quantity-indicator.tsx +48 -0
- package/components/select-family/family-carousel/index.tsx +47 -38
- package/components/select-family/family-carousel/slide.tsx +83 -0
- package/components/select-family/family-carousel/state.ts +83 -0
- package/components/select-family/level-nodes-widget/index.tsx +2 -2
- package/components/select-family/level-nodes-widget/node-image.tsx +2 -2
- package/index.ts +7 -7
- package/package.json +7 -4
- package/service/context.tsx +8 -7
- package/service/debug.ts +41 -0
- package/service/get-instance.ts +3 -0
- package/service/impls/standalone/actual-line-item.ts +5 -7
- package/service/impls/standalone/index.ts +11 -3
- package/service/impls/standalone/standalone-service.ts +133 -38
- package/service/path-utils.ts +26 -0
- package/service/sep.ts +7 -0
- package/types/category-node.ts +38 -0
- package/types/commerce-service.ts +44 -12
- package/types/family.ts +4 -1
- package/types/index.ts +3 -2
- package/types/item-selector.ts +29 -14
- package/types/product.ts +5 -1
- package/types/selection-ui-specifier.ts +36 -0
- package/types/token-separators.ts +7 -0
- package/util/error.ts +34 -0
- package/util/index.ts +4 -1
- package/util/product-media-accessor.ts +58 -0
- package/util/promo-codes.ts +1 -2
- package/util/selection-ui-specifiers.ts +34 -0
- package/util/use-sync-sku-param-w-current-item.ts +2 -2
- package/components/select-family/family-carousel/family-slide.tsx +0 -36
- package/components/select-product/carousel-selector.tsx +0 -112
- package/components/select-product/image-selector.tsx +0 -162
- package/components/select-product/radio-selector.tsx +0 -125
- package/service/impls/index.ts +0 -3
- package/types/buy-ui-spec.ts +0 -8
- package/types/tree-node.ts +0 -24
- package/util/buy-ui-conf.ts +0 -35
- package/util/sep.ts +0 -5
|
@@ -8,7 +8,7 @@ import { Skeleton } from '@hanzo/ui/primitives'
|
|
|
8
8
|
import type { ItemSelector, LineItem } from '../../types'
|
|
9
9
|
|
|
10
10
|
import AddToCartWidget from './add-to-cart-widget'
|
|
11
|
-
import
|
|
11
|
+
import { ButtonItemSelector } from '../item-selector'
|
|
12
12
|
import { formatCurrencyValue } from '../../util'
|
|
13
13
|
|
|
14
14
|
const SelectFamilyItemCard: React.FC<React.HTMLAttributes<HTMLDivElement> & ItemSelector & {
|
|
@@ -50,13 +50,12 @@ const SelectFamilyItemCard: React.FC<React.HTMLAttributes<HTMLDivElement> & Item
|
|
|
50
50
|
className
|
|
51
51
|
)}>
|
|
52
52
|
{title && (<div className={'h-[1px] bg-muted-3 ' + (mobilePicker ? 'w-pr-55' : 'w-pr-60') } /> )}
|
|
53
|
-
<
|
|
53
|
+
<ButtonItemSelector
|
|
54
54
|
items={items}
|
|
55
55
|
selectedItemRef={selItemRef}
|
|
56
56
|
selectSku={selectSku}
|
|
57
57
|
clx='mt-2'
|
|
58
|
-
|
|
59
|
-
scrollList={false}
|
|
58
|
+
scrollable={false}
|
|
60
59
|
itemClx='flex flex-row gap-2.5 items-center'
|
|
61
60
|
/>
|
|
62
61
|
</div>
|
|
@@ -11,7 +11,7 @@ import { formatCurrencyValue } from '../../util'
|
|
|
11
11
|
import { Icons } from '../Icons'
|
|
12
12
|
|
|
13
13
|
import AddToCartWidget from './add-to-cart-widget'
|
|
14
|
-
import
|
|
14
|
+
import { ButtonItemSelector } from '../item-selector'
|
|
15
15
|
|
|
16
16
|
const SelectFamilyItemPanel: React.FC<
|
|
17
17
|
React.HTMLAttributes<HTMLDivElement> &
|
|
@@ -91,13 +91,12 @@ const SelectFamilyItemPanel: React.FC<
|
|
|
91
91
|
<h6 className='text-center font-semibold'>Available options</h6>
|
|
92
92
|
<div className={'h-[1px] bg-muted-3 ' + (mobilePicker ? 'w-pr-55' : 'w-pr-60') } />
|
|
93
93
|
</div>
|
|
94
|
-
<
|
|
94
|
+
<ButtonItemSelector
|
|
95
95
|
items={family.products as LineItem[]}
|
|
96
96
|
selectedItemRef={selectedItemRef}
|
|
97
97
|
selectSku={selectSku}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
98
|
+
options={{ showQuantity }}
|
|
99
|
+
scrollable={false}
|
|
101
100
|
clx='block columns-2 gap-4'
|
|
102
101
|
itemClx='flex flex-row gap-2 items-center mb-2.5'
|
|
103
102
|
/>
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import React from 'react'
|
|
3
3
|
import { observer } from 'mobx-react-lite'
|
|
4
4
|
|
|
5
|
-
import { Button, toast, type ButtonSizes } from '@hanzo/ui/primitives'
|
|
5
|
+
import { Button, toast, type ButtonSizes, type ButtonVariants } from '@hanzo/ui/primitives'
|
|
6
6
|
import { cn } from '@hanzo/ui/util'
|
|
7
7
|
|
|
8
8
|
import { Icons } from '../Icons'
|
|
@@ -11,15 +11,15 @@ import { sendFBEvent, sendGAEvent } from '../../util/analytics'
|
|
|
11
11
|
|
|
12
12
|
const AddToCartWidget: React.FC<{
|
|
13
13
|
item: LineItem
|
|
14
|
-
ghost?: boolean
|
|
15
14
|
disabled?: boolean
|
|
16
15
|
className?: string
|
|
17
16
|
buttonClx?: string
|
|
18
17
|
size?: ButtonSizes
|
|
18
|
+
variant?: 'minimal' | 'primary' | 'outline'
|
|
19
19
|
onQuantityChanged?: (sku: string, oldV: number, newV: number) => void
|
|
20
20
|
}> = observer(({
|
|
21
21
|
item,
|
|
22
|
-
|
|
22
|
+
variant='primary',
|
|
23
23
|
disabled=false,
|
|
24
24
|
className='',
|
|
25
25
|
buttonClx='',
|
|
@@ -27,12 +27,23 @@ const AddToCartWidget: React.FC<{
|
|
|
27
27
|
onQuantityChanged
|
|
28
28
|
}) => {
|
|
29
29
|
|
|
30
|
-
const
|
|
31
|
-
|
|
30
|
+
const ROUNDED_VAL = 'lg'
|
|
31
|
+
// no need to safelist, since its used widely
|
|
32
|
+
const ROUNDED_CLX = ` rounded-${ROUNDED_VAL} `
|
|
33
|
+
|
|
34
|
+
const ghost = variant === 'minimal'
|
|
35
|
+
const outline = variant === 'outline'
|
|
36
|
+
const primary = variant === 'primary'
|
|
37
|
+
|
|
38
|
+
let iconClx = ghost ? 'h-4 w-4 md:h-3 md:w-3 text-muted-3 ' : 'h-5 w-7 px-1 opacity-70 '
|
|
39
|
+
iconClx += ghost ? 'group-hover:text-foreground' : 'group-hover:opacity-100'
|
|
40
|
+
|
|
41
|
+
let digitClx = ghost ? 'px-2 md:px-0.5 ' : 'sm:px-2 font-semibold '
|
|
42
|
+
digitClx += (ghost || outline) ? 'text-foreground ' : 'text-primary-fg '
|
|
32
43
|
|
|
33
44
|
if (disabled) {
|
|
34
45
|
return (
|
|
35
|
-
<div className={cn('flex flex-row items-stretch
|
|
46
|
+
<div className={cn('flex flex-row items-stretch' + ROUNDED_CLX + (!primary ? 'bg-transparent' : 'bg-primary'), className)}>
|
|
36
47
|
<div className={'text-sm flex items-center cursor-default ' + digitClx} >{item.quantity}</div>
|
|
37
48
|
</div>
|
|
38
49
|
)
|
|
@@ -44,14 +55,6 @@ const AddToCartWidget: React.FC<{
|
|
|
44
55
|
if (onQuantityChanged) {
|
|
45
56
|
onQuantityChanged(item.sku, old, old + 1)
|
|
46
57
|
}
|
|
47
|
-
/*
|
|
48
|
-
if (old === 0) {
|
|
49
|
-
toast(`Added ${item.title} to your bag.`)
|
|
50
|
-
}
|
|
51
|
-
else {
|
|
52
|
-
toast(`Changed quantity to ${old + 1} for ${item.title}.`)
|
|
53
|
-
}
|
|
54
|
-
*/
|
|
55
58
|
sendGAEvent('add_to_cart', {
|
|
56
59
|
items: [{
|
|
57
60
|
item_id: item.sku,
|
|
@@ -81,14 +84,6 @@ const AddToCartWidget: React.FC<{
|
|
|
81
84
|
if (onQuantityChanged) {
|
|
82
85
|
onQuantityChanged(item.sku, old, old - 1)
|
|
83
86
|
}
|
|
84
|
-
/*
|
|
85
|
-
if (old === 1) {
|
|
86
|
-
toast(`Removed ${item.title} from your bag.`)
|
|
87
|
-
}
|
|
88
|
-
else {
|
|
89
|
-
toast(`Changed quantity to ${old - 1} for ${item.title}.`)
|
|
90
|
-
}
|
|
91
|
-
*/
|
|
92
87
|
sendGAEvent('remove_from_cart', {
|
|
93
88
|
items: [{
|
|
94
89
|
item_id: item.sku,
|
|
@@ -103,13 +98,24 @@ const AddToCartWidget: React.FC<{
|
|
|
103
98
|
}
|
|
104
99
|
|
|
105
100
|
return ( item.isInCart ? (
|
|
106
|
-
<div className={cn(
|
|
101
|
+
<div className={cn(
|
|
102
|
+
'flex flex-row items-stretch justify-between ',
|
|
103
|
+
ROUNDED_CLX,
|
|
104
|
+
(primary ? 'bg-primary' : 'bg-transparent'),
|
|
105
|
+
(outline ? 'border border-muted' : ''),
|
|
106
|
+
className
|
|
107
|
+
)}>
|
|
107
108
|
<Button
|
|
108
109
|
aria-label={'Remove a ' + item.title + ' from the cart'}
|
|
109
110
|
size={size}
|
|
110
|
-
variant={
|
|
111
|
-
rounded={ghost ? 'full' :
|
|
112
|
-
className={cn(
|
|
111
|
+
variant={primary ? 'primary' : 'ghost'}
|
|
112
|
+
rounded={ghost ? 'full' : ROUNDED_VAL}
|
|
113
|
+
className={cn(
|
|
114
|
+
'lg:min-w-0 lg:px-2 grow justify-start group',
|
|
115
|
+
(ghost ? 'px-1' : 'px-2'),
|
|
116
|
+
(outline ? 'hover:bg-transparent' : ''),
|
|
117
|
+
buttonClx
|
|
118
|
+
)}
|
|
113
119
|
key='left'
|
|
114
120
|
onClick={dec}
|
|
115
121
|
>
|
|
@@ -119,13 +125,18 @@ const AddToCartWidget: React.FC<{
|
|
|
119
125
|
<Icons.trash className={iconClx} aria-hidden='true'/>
|
|
120
126
|
)}
|
|
121
127
|
</Button>
|
|
122
|
-
<div className={'text-sm flex items-center cursor-default xs:px-2 ' + digitClx} >{item.quantity}{ghost ? '' : ' in Bag'}</div>
|
|
128
|
+
<div className={'text-sm grow-0 shrink-0 flex items-center cursor-default xs:px-2 ' + digitClx} >{item.quantity}{ghost ? '' : ' in Bag'}</div>
|
|
123
129
|
<Button
|
|
124
130
|
aria-label={'Add another ' + item.title + ' to the cart'}
|
|
125
131
|
size={size}
|
|
126
|
-
variant={
|
|
127
|
-
rounded={ghost ? 'full' :
|
|
128
|
-
className={cn(
|
|
132
|
+
variant={primary ? 'primary' : 'ghost'}
|
|
133
|
+
rounded={ghost ? 'full' : ROUNDED_VAL}
|
|
134
|
+
className={cn(
|
|
135
|
+
'lg:min-w-0 lg:px-2 grow justify-end group',
|
|
136
|
+
(ghost ? 'px-1' : 'px-2'),
|
|
137
|
+
(outline ? 'hover:bg-transparent' : ''),
|
|
138
|
+
buttonClx
|
|
139
|
+
)}
|
|
129
140
|
onClick={inc}
|
|
130
141
|
key='right'
|
|
131
142
|
>
|
|
@@ -136,8 +147,8 @@ const AddToCartWidget: React.FC<{
|
|
|
136
147
|
<Button
|
|
137
148
|
aria-label={'Add a ' + item.title + ' to cart'}
|
|
138
149
|
size={size}
|
|
139
|
-
variant=
|
|
140
|
-
rounded=
|
|
150
|
+
variant={variant as ButtonVariants}
|
|
151
|
+
rounded={ROUNDED_VAL}
|
|
141
152
|
className={cn(buttonClx, className)}
|
|
142
153
|
onClick={inc}
|
|
143
154
|
>
|
|
@@ -10,7 +10,7 @@ const BuyButton: React.FC<PropsWithChildren & {
|
|
|
10
10
|
skuPath: string
|
|
11
11
|
variant? : ButtonVariants
|
|
12
12
|
size?: ButtonSizes
|
|
13
|
-
/* rounded?:
|
|
13
|
+
/* rounded?: ButtonRoundedValue // TODO: wait for version bump*/
|
|
14
14
|
className?: string
|
|
15
15
|
mobile?: boolean
|
|
16
16
|
}> = ({
|
|
@@ -7,16 +7,15 @@ import { cn } from '@hanzo/ui/util'
|
|
|
7
7
|
import { ApplyTypography, MediaStack } from '@hanzo/ui/primitives'
|
|
8
8
|
|
|
9
9
|
import type {
|
|
10
|
-
SelectedPaths,
|
|
11
10
|
ItemSelectorProps,
|
|
12
11
|
ItemSelectorCompProps,
|
|
13
12
|
LineItem,
|
|
14
13
|
Family,
|
|
15
|
-
|
|
14
|
+
CategoryNode,
|
|
16
15
|
} from '../../types'
|
|
17
16
|
|
|
18
17
|
import { useCommerce } from '../../service/context'
|
|
19
|
-
import
|
|
18
|
+
import * as pathUtils from '../../service/path-utils'
|
|
20
19
|
import { getFacetValuesMutator, ObsStringMutator } from '../../util'
|
|
21
20
|
|
|
22
21
|
import LevelNodesWidget from '../select-family/level-nodes-widget'
|
|
@@ -79,8 +78,8 @@ const BuyCard: React.FC<{
|
|
|
79
78
|
const inst = useRef<{
|
|
80
79
|
level: number,
|
|
81
80
|
requestedFamily : Family | undefined
|
|
82
|
-
requestedNode:
|
|
83
|
-
|
|
81
|
+
requestedNode: CategoryNode | undefined
|
|
82
|
+
currentFamTokenMutator: ObsStringMutator
|
|
84
83
|
disposers: IReactionDisposer[]
|
|
85
84
|
} | undefined>(undefined)
|
|
86
85
|
|
|
@@ -90,15 +89,12 @@ const BuyCard: React.FC<{
|
|
|
90
89
|
let requestedFamily = cmmc.getFamily(skuPath)
|
|
91
90
|
let requestedNode = requestedFamily ? undefined : cmmc.getNodeAtPath(skuPath)
|
|
92
91
|
let initialFamily = undefined
|
|
93
|
-
|
|
94
|
-
const level =
|
|
92
|
+
|
|
93
|
+
const { paths, level } = pathUtils.getSelectedPaths(skuPath)
|
|
94
|
+
|
|
95
95
|
if (level === 0) {
|
|
96
96
|
throw new Error('BuyCard.useEffect(): must specify at least at least one Level in skuPath!')
|
|
97
97
|
}
|
|
98
|
-
const paths: SelectedPaths = {}
|
|
99
|
-
for (let l = 1; l <= level; l++ ) {
|
|
100
|
-
paths[l] = [toks[l]]
|
|
101
|
-
}
|
|
102
98
|
if (requestedFamily) {
|
|
103
99
|
if (allVariants && level >= 2) {
|
|
104
100
|
// select all siblings of requestedFamily.
|
|
@@ -106,7 +102,7 @@ const BuyCard: React.FC<{
|
|
|
106
102
|
delete paths[level]
|
|
107
103
|
initialFamily = requestedFamily
|
|
108
104
|
requestedFamily = undefined
|
|
109
|
-
requestedNode = cmmc.getNodeAtPath(
|
|
105
|
+
requestedNode = cmmc.getNodeAtPath(pathUtils.getParentPath(skuPath))
|
|
110
106
|
}
|
|
111
107
|
}
|
|
112
108
|
else {
|
|
@@ -123,13 +119,13 @@ const BuyCard: React.FC<{
|
|
|
123
119
|
const selectedFams = cmmc.selectPaths(paths)
|
|
124
120
|
initialFamily = initialFamily ? initialFamily : (requestedFamily ? requestedFamily : selectedFams[0])
|
|
125
121
|
cmmc.setCurrentItem(initialFamily.products[0].sku)
|
|
126
|
-
const
|
|
122
|
+
const currentFamTokenMutator = new ObsStringMutator(pathUtils.lastToken(initialFamily!.id))
|
|
127
123
|
|
|
128
124
|
inst.current = {
|
|
129
125
|
level,
|
|
130
126
|
requestedFamily,
|
|
131
127
|
requestedNode,
|
|
132
|
-
|
|
128
|
+
currentFamTokenMutator,
|
|
133
129
|
disposers: []
|
|
134
130
|
}
|
|
135
131
|
|
|
@@ -157,8 +153,8 @@ const BuyCard: React.FC<{
|
|
|
157
153
|
}, [])
|
|
158
154
|
|
|
159
155
|
const setFamilyPath = (pathValue: string | null) => {
|
|
160
|
-
if (inst.current?.
|
|
161
|
-
inst.current?.
|
|
156
|
+
if (inst.current?.currentFamTokenMutator.get() !== pathValue) {
|
|
157
|
+
inst.current?.currentFamTokenMutator.set(pathValue)
|
|
162
158
|
}
|
|
163
159
|
const found = cmmc.selectedItems.find((item) => (item.familyId.endsWith(pathValue!)))
|
|
164
160
|
if (found) {
|
|
@@ -168,9 +164,9 @@ const BuyCard: React.FC<{
|
|
|
168
164
|
|
|
169
165
|
const selectSku = (sku: string) => {
|
|
170
166
|
cmmc.setCurrentItem(sku)
|
|
171
|
-
const pathValue = cmmc.currentItem
|
|
172
|
-
if (pathValue !== inst.current?.
|
|
173
|
-
inst.current?.
|
|
167
|
+
const pathValue = pathUtils.lastToken(cmmc.currentItem!.familyId)
|
|
168
|
+
if (pathValue !== inst.current?.currentFamTokenMutator.get()) {
|
|
169
|
+
inst.current?.currentFamTokenMutator.set(pathValue)
|
|
174
170
|
}
|
|
175
171
|
}
|
|
176
172
|
|
|
@@ -219,7 +215,7 @@ const BuyCard: React.FC<{
|
|
|
219
215
|
mobile={mobile}
|
|
220
216
|
mutator={allVariants ?
|
|
221
217
|
{
|
|
222
|
-
get: () => (inst.current!.
|
|
218
|
+
get: () => (inst.current!.currentFamTokenMutator.s),
|
|
223
219
|
set: setFamilyPath
|
|
224
220
|
}
|
|
225
221
|
:
|
|
@@ -246,12 +242,7 @@ const BuyCard: React.FC<{
|
|
|
246
242
|
selectedItemRef={cmmc}
|
|
247
243
|
selectSku={selectSku}
|
|
248
244
|
{...selectorProps}
|
|
249
|
-
|
|
250
|
-
showFamily={selectorProps.showFamily ?
|
|
251
|
-
selectorProps.showFamily
|
|
252
|
-
:
|
|
253
|
-
(allVariants && !inst.current?.requestedFamily)
|
|
254
|
-
}
|
|
245
|
+
scrollable={scroll}
|
|
255
246
|
/>
|
|
256
247
|
)}
|
|
257
248
|
{(cmmc.currentItem) && (
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
import React, { useState, type ReactNode } from 'react'
|
|
3
|
+
import { useRouter } from 'next/navigation'
|
|
3
4
|
|
|
4
5
|
import { X as LucideX} from 'lucide-react'
|
|
5
6
|
|
|
@@ -12,14 +13,9 @@ import {
|
|
|
12
13
|
|
|
13
14
|
import { cn } from '@hanzo/ui/util'
|
|
14
15
|
|
|
15
|
-
import { getUISpecFromPath } from '../../util'
|
|
16
|
-
|
|
17
16
|
//import BuyCard from './buy-card'
|
|
18
|
-
import FamilyCar from '../select-family/family-carousel'
|
|
19
17
|
|
|
20
|
-
import
|
|
21
|
-
import ImageItemSelector from '../select-product/image-selector'
|
|
22
|
-
import CarouselItemSelector from '../select-product/carousel-selector'
|
|
18
|
+
import CarouselBuyCard from './carousel-buy-card'
|
|
23
19
|
|
|
24
20
|
const BuyDrawer: React.FC<{
|
|
25
21
|
skuPath: string
|
|
@@ -38,20 +34,7 @@ const BuyDrawer: React.FC<{
|
|
|
38
34
|
}) => {
|
|
39
35
|
|
|
40
36
|
const [open, setOpen] = useState<boolean>(false)
|
|
41
|
-
|
|
42
|
-
const onQuantityChanged = (sku: string, oldV: number, newV: number) => {
|
|
43
|
-
//if (oldV === 0 && newV === 1) {
|
|
44
|
-
// setTimeout(() => {setOpen(false)}, 150)
|
|
45
|
-
//}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const spec = getUISpecFromPath(skuPath) ?? {
|
|
49
|
-
selector: 'radio',
|
|
50
|
-
allVariants: false
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const selector = spec.selector === 'radio' ? RadioItemSelector :
|
|
54
|
-
(spec.selector === 'image' ? ImageItemSelector : CarouselItemSelector)
|
|
37
|
+
const router = useRouter()
|
|
55
38
|
|
|
56
39
|
return (
|
|
57
40
|
<Drawer open={open} onOpenChange={setOpen} >
|
|
@@ -61,13 +44,11 @@ const BuyDrawer: React.FC<{
|
|
|
61
44
|
<DrawerContent
|
|
62
45
|
className={cn('rounded-t-xl mt-6 pb-12 h-auto min-h-[35vh] pt-6 md:max-w-[550px] md:mx-auto', drawerClx)}
|
|
63
46
|
>
|
|
64
|
-
|
|
65
|
-
<FamilyCar
|
|
47
|
+
<CarouselBuyCard
|
|
66
48
|
skuPath={skuPath}
|
|
49
|
+
handleCheckout={() => {router.push('/checkout')}}
|
|
67
50
|
mobile={mobile}
|
|
68
|
-
onQuantityChanged={onQuantityChanged}
|
|
69
51
|
clx={cn('w-full', cardClx)}
|
|
70
|
-
|
|
71
52
|
/>
|
|
72
53
|
|
|
73
54
|
<Button
|