@hanzo/commerce 2.1.0 → 3.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/add-to-cart-widget.tsx +3 -3
- package/components/buy-item/buy-item-button.tsx +28 -0
- package/components/buy-item/buy-item-card.tsx +82 -0
- package/components/buy-item/buy-item-popup.tsx +38 -0
- package/components/{select-category-and-item-widget.tsx → buy-item/select-category-and-item-widget.tsx} +4 -4
- package/components/buy-item/select-category-item-card.tsx +111 -0
- package/components/{cart-line-item.tsx → cart-panel/cart-line-item.tsx} +4 -3
- package/components/{cart.tsx → cart-panel/index.tsx} +24 -17
- package/components/category-item-ios-wheel-selector.tsx +60 -0
- package/components/category-item-radio-selector.tsx +55 -0
- package/components/{checkout → checkout-panel}/confirm-order.tsx +3 -2
- package/components/{checkout → checkout-panel}/index.tsx +10 -5
- package/components/{checkout → checkout-panel}/payment/index.tsx +2 -1
- package/components/{checkout → checkout-panel}/payment/pay-with-card.tsx +8 -3
- package/components/{checkout → checkout-panel}/payment/pay-with-crypto.tsx +2 -3
- package/components/{facet-toggles-widget → facet-values-widget}/facet-image.tsx +7 -3
- package/components/{facet-toggles-widget → facet-values-widget}/index.tsx +7 -5
- package/components/index.ts +11 -9
- package/components/{select-item-in-category-view.tsx → select-category-item-panel.tsx} +72 -76
- package/index.ts +2 -2
- package/package.json +8 -3
- package/service/context.tsx +4 -4
- package/service/impls/standalone/index.ts +4 -4
- package/service/impls/standalone/standalone-service.ts +117 -38
- package/types/category.ts +3 -2
- package/types/commerce-service.ts +17 -4
- package/types/facet.ts +1 -3
- package/types/index.ts +3 -1
- package/types/item-selector.ts +14 -0
- package/util/index.ts +29 -0
- package/util/use-sync-sku-param-w-current-item.ts +4 -1
- package/blocks/components/commerce-cat-and-item-block.tsx +0 -13
- package/blocks/def/commerce-cat-and-item-block.ts +0 -9
- package/components/facets-widget.tsx +0 -55
- package/components/product-selection-mobile-picker.tsx +0 -78
- package/components/product-selection-radio-group.tsx +0 -38
- /package/components/{checkout → checkout-panel}/countries.ts +0 -0
- /package/components/{checkout → checkout-panel}/icons/btc.tsx +0 -0
- /package/components/{checkout → checkout-panel}/icons/eth.tsx +0 -0
- /package/components/{checkout → checkout-panel}/icons/usdt.tsx +0 -0
- /package/components/{checkout → checkout-panel}/payment/contact-info.tsx +0 -0
- /package/components/{checkout → checkout-panel}/payment/pay-with-bank-transfer.tsx +0 -0
- /package/components/{checkout → checkout-panel}/payment/payment-methods/amex.tsx +0 -0
- /package/components/{checkout → checkout-panel}/payment/payment-methods/diners-club.tsx +0 -0
- /package/components/{checkout → checkout-panel}/payment/payment-methods/discover.tsx +0 -0
- /package/components/{checkout → checkout-panel}/payment/payment-methods/index.tsx +0 -0
- /package/components/{checkout → checkout-panel}/payment/payment-methods/jcb.tsx +0 -0
- /package/components/{checkout → checkout-panel}/payment/payment-methods/mastercard.tsx +0 -0
- /package/components/{checkout → checkout-panel}/payment/payment-methods/visa.tsx +0 -0
- /package/components/{checkout → checkout-panel}/shipping-info.tsx +0 -0
- /package/components/{checkout → checkout-panel}/thank-you.tsx +0 -0
|
@@ -26,7 +26,7 @@ const AddToCartWidget: React.FC<{
|
|
|
26
26
|
size='xs'
|
|
27
27
|
}) => {
|
|
28
28
|
|
|
29
|
-
const iconClx = ghost ? 'h-4 w-4 md:h-3 md:w-3 text-muted-3 hover:text-foreground' : 'h-5 w-
|
|
29
|
+
const iconClx = ghost ? 'h-4 w-4 md:h-3 md:w-3 text-muted-3 hover:text-foreground' : 'h-5 w-7 px-1'
|
|
30
30
|
const digitClx = ghost ? 'px-2 md:px-0.5' : 'sm:px-2 font-bold '
|
|
31
31
|
|
|
32
32
|
if (disabled) {
|
|
@@ -44,7 +44,7 @@ const AddToCartWidget: React.FC<{
|
|
|
44
44
|
size={size}
|
|
45
45
|
variant={ghost ? 'ghost' : 'secondary'}
|
|
46
46
|
rounded={ghost ? 'full' : 'xl'}
|
|
47
|
-
className={cn('px-1 lg:min-w-0 lg:px-2
|
|
47
|
+
className={cn('px-1 lg:min-w-0 lg:px-2 xs:justify-end', buttonClx)}
|
|
48
48
|
key='left'
|
|
49
49
|
onClick={item.decrement.bind(item)}
|
|
50
50
|
>
|
|
@@ -60,7 +60,7 @@ const AddToCartWidget: React.FC<{
|
|
|
60
60
|
size={size}
|
|
61
61
|
variant={ghost ? 'ghost' : 'secondary'}
|
|
62
62
|
rounded={ghost ? 'full' : 'xl'}
|
|
63
|
-
className={cn('px-1 lg:min-w-0 lg:px-2 xs:
|
|
63
|
+
className={cn('px-1 lg:min-w-0 lg:px-2 xs:justify-start', buttonClx)}
|
|
64
64
|
onClick={item.increment.bind(item)}
|
|
65
65
|
key='right'
|
|
66
66
|
>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
import React, {type PropsWithChildren} from 'react'
|
|
3
|
+
|
|
4
|
+
import { type ButtonVariants, type ButtonSizes, Button } from '@hanzo/ui/primitives'
|
|
5
|
+
|
|
6
|
+
import BuyItemPopup from './buy-item-popup'
|
|
7
|
+
|
|
8
|
+
const BuyItemButton: React.FC<PropsWithChildren & {
|
|
9
|
+
skuPath: string
|
|
10
|
+
variant? : ButtonVariants
|
|
11
|
+
size?: ButtonSizes
|
|
12
|
+
/* rounded?: ButtonRounded // wait for version bump*/
|
|
13
|
+
className?: string
|
|
14
|
+
popupClx?: string
|
|
15
|
+
}> = ({
|
|
16
|
+
skuPath,
|
|
17
|
+
variant,
|
|
18
|
+
size,
|
|
19
|
+
children,
|
|
20
|
+
className='',
|
|
21
|
+
popupClx=''
|
|
22
|
+
}) => (
|
|
23
|
+
<BuyItemPopup skuPath={skuPath} popupClx={popupClx}>
|
|
24
|
+
<Button size={size} variant={variant} className={className}>{children}</Button>
|
|
25
|
+
</BuyItemPopup>
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
export default BuyItemButton
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
import React, { useRef, useEffect } from 'react'
|
|
3
|
+
import { autorun } from 'mobx'
|
|
4
|
+
import { observer } from 'mobx-react-lite'
|
|
5
|
+
|
|
6
|
+
import { cn } from '@hanzo/ui/util'
|
|
7
|
+
|
|
8
|
+
import type { FacetsValue } from '../../types'
|
|
9
|
+
import { useCommerce } from '../../service/context'
|
|
10
|
+
import { getFacetValuesMutator } from '../../util'
|
|
11
|
+
import FacetValuesWidget from '../facet-values-widget'
|
|
12
|
+
import SelectCategoryItemCard from './select-category-item-card'
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
const BuyItemCard: React.FC<{
|
|
16
|
+
skuPath: string
|
|
17
|
+
className?: string
|
|
18
|
+
}> = observer(({
|
|
19
|
+
skuPath,
|
|
20
|
+
className=''
|
|
21
|
+
}) => {
|
|
22
|
+
|
|
23
|
+
const cmmc = useCommerce()
|
|
24
|
+
const levelRef = useRef<number>(-1)
|
|
25
|
+
|
|
26
|
+
const cat = cmmc.getCategory(skuPath)
|
|
27
|
+
const facets = cat ? undefined : cmmc.getFacetValuesAtSkuPath(skuPath)
|
|
28
|
+
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
|
|
31
|
+
if (facets) {
|
|
32
|
+
const toks = skuPath.split('-')
|
|
33
|
+
const levelSpecified = toks.length - 1
|
|
34
|
+
const fsv: FacetsValue = {}
|
|
35
|
+
for (let level = 1; level <= levelSpecified; level++ ) {
|
|
36
|
+
fsv[level] = [toks[level]]
|
|
37
|
+
}
|
|
38
|
+
fsv[levelSpecified + 1] = [facets[0].value]
|
|
39
|
+
levelRef.current = levelSpecified
|
|
40
|
+
cmmc.setFacets(fsv)
|
|
41
|
+
}
|
|
42
|
+
return autorun(() => {
|
|
43
|
+
const cats = cmmc.specifiedCategories
|
|
44
|
+
// Original cat was legit
|
|
45
|
+
if (cat && (cats.length === 0 || cats[0].id !== cat.id)) {
|
|
46
|
+
if (!cmmc.currentItem || cmmc.currentItem.categoryId !== cat.id) {
|
|
47
|
+
cmmc.setCurrentItem(cat.products[0].sku)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
else if (cats.length > 0) {
|
|
51
|
+
if (!cmmc.currentItem || cmmc.currentItem.categoryId !== cats[0].id) {
|
|
52
|
+
cmmc.setCurrentItem(cats[0].products[0].sku)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
})
|
|
56
|
+
}, [cat, facets])
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<div className={className} >
|
|
60
|
+
{facets && levelRef.current > 0 && (
|
|
61
|
+
<FacetValuesWidget
|
|
62
|
+
className={cn('grid gap-0 ' + `grid-cols-${facets.length}` + ' self-start ', 'border-b mb-2 -mr-2 -ml-2')}
|
|
63
|
+
isMobile={false}
|
|
64
|
+
mutator={getFacetValuesMutator(levelRef.current + 1, cmmc)}
|
|
65
|
+
itemClx='flex-col h-auto gap-0 pb-1 pt-3 px-3'
|
|
66
|
+
buttonClx='h-auto !rounded-bl-none !rounded-br-none !rounded-tl-lg !rounded-tr-lg '
|
|
67
|
+
facetValues={facets}
|
|
68
|
+
/>
|
|
69
|
+
)}
|
|
70
|
+
{cmmc.specifiedCategories[0] && (
|
|
71
|
+
<SelectCategoryItemCard
|
|
72
|
+
noTitle
|
|
73
|
+
category={cmmc.specifiedCategories[0]}
|
|
74
|
+
selectedItemRef={cmmc /* ...conveniently. :) */ }
|
|
75
|
+
selectSku={cmmc.setCurrentItem.bind(cmmc)}
|
|
76
|
+
/>
|
|
77
|
+
)}
|
|
78
|
+
</div >
|
|
79
|
+
)
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
export default BuyItemCard
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
import React, {type PropsWithChildren} from 'react'
|
|
3
|
+
|
|
4
|
+
import { X } from 'lucide-react'
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
Popover,
|
|
8
|
+
PopoverContent,
|
|
9
|
+
PopoverTrigger,
|
|
10
|
+
PopoverClose
|
|
11
|
+
} from "@hanzo/ui/primitives"
|
|
12
|
+
|
|
13
|
+
import { cn } from '@hanzo/ui/util'
|
|
14
|
+
|
|
15
|
+
import BuyItemCard from './buy-item-card'
|
|
16
|
+
|
|
17
|
+
const BuyItemPopup: React.FC<PropsWithChildren & {
|
|
18
|
+
skuPath: string
|
|
19
|
+
popupClx?: string
|
|
20
|
+
cardClx?: string
|
|
21
|
+
}> = ({
|
|
22
|
+
skuPath,
|
|
23
|
+
children,
|
|
24
|
+
popupClx='',
|
|
25
|
+
cardClx='',
|
|
26
|
+
}) => (
|
|
27
|
+
<Popover>
|
|
28
|
+
<PopoverTrigger asChild>
|
|
29
|
+
{children}
|
|
30
|
+
</PopoverTrigger>
|
|
31
|
+
<PopoverContent className={cn('relative flex flex-col p-0 px-4 pb-4 pt-2', popupClx)}>
|
|
32
|
+
<PopoverClose className='absolute z-20 right-2 top-2 self-end hover:bg-level-3 text-muted hover:text-accent p-1 rounded-full'><X className='w-5 h-5'/></PopoverClose>
|
|
33
|
+
<BuyItemCard skuPath={skuPath} className={cn("w-full relative ", cardClx)}/>
|
|
34
|
+
</PopoverContent>
|
|
35
|
+
</Popover>
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
export default BuyItemPopup
|
|
@@ -5,11 +5,11 @@ import { observer } from 'mobx-react-lite'
|
|
|
5
5
|
import { ApplyTypography, ListBox } from '@hanzo/ui/primitives'
|
|
6
6
|
import { cn } from '@hanzo/ui/util'
|
|
7
7
|
|
|
8
|
-
import type { FacetValueDesc, FacetsValue, LineItem } from '
|
|
9
|
-
import { useCommerce } from '
|
|
10
|
-
import { formatPrice } from '
|
|
8
|
+
import type { FacetValueDesc, FacetsValue, LineItem } from '../../types'
|
|
9
|
+
import { useCommerce } from '../../service/context'
|
|
10
|
+
import { formatPrice } from '../../util'
|
|
11
11
|
|
|
12
|
-
import FacetTogglesWidget from '
|
|
12
|
+
import FacetTogglesWidget from '../facet-values-widget'
|
|
13
13
|
|
|
14
14
|
const formatItem = (item: LineItem, withQuantity: boolean = false): string => (
|
|
15
15
|
`${item.titleAsOption}, ${formatPrice(item.price)}${(withQuantity && item.quantity > 0) ? ` (${item.quantity})` : ''}`
|
|
@@ -0,0 +1,111 @@
|
|
|
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 CategoryItemRadioSelector from '../category-item-radio-selector'
|
|
12
|
+
import CategoryItemIOSWheelSelector from '../category-item-ios-wheel-selector'
|
|
13
|
+
import { formatPrice } from '../../util'
|
|
14
|
+
|
|
15
|
+
const SelectCategoryItemCard: React.FC<React.HTMLAttributes<HTMLDivElement> & ItemSelector & {
|
|
16
|
+
isLoading?: boolean
|
|
17
|
+
mobile?: boolean
|
|
18
|
+
noTitle?: boolean
|
|
19
|
+
}> = /* NOT observer */({
|
|
20
|
+
category,
|
|
21
|
+
selectedItemRef: selItemRef,
|
|
22
|
+
selectSku,
|
|
23
|
+
className,
|
|
24
|
+
isLoading = false,
|
|
25
|
+
mobile = false,
|
|
26
|
+
noTitle = false,
|
|
27
|
+
...props
|
|
28
|
+
}) => {
|
|
29
|
+
|
|
30
|
+
const soleOption = category.products.length === 1
|
|
31
|
+
|
|
32
|
+
const SelectProductComp: React.FC<{ className?: string }> = ({ className = '' }) => {
|
|
33
|
+
|
|
34
|
+
if (soleOption) {
|
|
35
|
+
const item = category.products[0] as LineItem
|
|
36
|
+
return (
|
|
37
|
+
<p >{item.titleAsOption + ', ' + formatPrice(item.price) + (item.quantity > 0 ? `(${item.quantity})` : '')}</p>
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
const mobilePicker = (mobile && category.products.length > 6)
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<div /* id='CV_AVAIL_AMOUNTS' */ className={cn(
|
|
44
|
+
'sm:w-pr-80 sm:mx-auto md:w-full flex flex-col justify-start items-center',
|
|
45
|
+
className
|
|
46
|
+
)}>
|
|
47
|
+
{!noTitle && (<div className={'h-[1px] bg-muted-3 ' + (mobilePicker ? 'w-pr-55' : 'w-pr-60') } /> )}
|
|
48
|
+
{mobilePicker ? (
|
|
49
|
+
<CategoryItemIOSWheelSelector
|
|
50
|
+
category={category}
|
|
51
|
+
selectedItemRef={selItemRef}
|
|
52
|
+
selectSku={selectSku}
|
|
53
|
+
height={180}
|
|
54
|
+
itemHeight={30}
|
|
55
|
+
outerClx='mb-4'
|
|
56
|
+
/>
|
|
57
|
+
) : (
|
|
58
|
+
<CategoryItemRadioSelector
|
|
59
|
+
category={category}
|
|
60
|
+
selectedItemRef={selItemRef}
|
|
61
|
+
selectSku={selectSku}
|
|
62
|
+
groupClx='mt-2'
|
|
63
|
+
itemClx='flex flex-row gap-2.5 items-center'
|
|
64
|
+
/>
|
|
65
|
+
)}
|
|
66
|
+
</div>
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const AddToCartComp: React.FC<{ className?: string }> = observer(({ className = '' }) => (
|
|
71
|
+
// TODO disable if nothing selected
|
|
72
|
+
(selItemRef.item && !isLoading) && (
|
|
73
|
+
<AddToCartWidget size='default' item={selItemRef.item} className={cn('lg:min-w-[160px] lg:mx-auto', className)}/>
|
|
74
|
+
)
|
|
75
|
+
))
|
|
76
|
+
|
|
77
|
+
const TitleArea: React.FC<{ className?: string }> = observer(({ className = '' }) => (
|
|
78
|
+
|
|
79
|
+
isLoading ? (<Skeleton className={'h-8 w-full ' + className} />) : (
|
|
80
|
+
|
|
81
|
+
<div className={cn('text-center flex flex-col justify-start items-center', className)}>
|
|
82
|
+
<p className='font-nav text-center'>{category.title}</p>
|
|
83
|
+
</div>
|
|
84
|
+
|
|
85
|
+
)))
|
|
86
|
+
|
|
87
|
+
return mobile ? (
|
|
88
|
+
<div /* id='CV_OUTER' */
|
|
89
|
+
className={cn(
|
|
90
|
+
'w-full h-[calc(100svh-96px)] max-h-[700px] flex flex-col justify-between ' +
|
|
91
|
+
'items-stretch gap-[4vh] mt-[2vh] pb-[6vh]',
|
|
92
|
+
className
|
|
93
|
+
)}
|
|
94
|
+
{...props}
|
|
95
|
+
>
|
|
96
|
+
{!noTitle && (<TitleArea className='grow pt-3 mb-0' />)}
|
|
97
|
+
<SelectProductComp className='mb-[3vh]' />
|
|
98
|
+
<AddToCartComp className='w-pr-70 mx-auto' />
|
|
99
|
+
</div>
|
|
100
|
+
) : (
|
|
101
|
+
<div className={cn('', className)} {...props}>
|
|
102
|
+
{!noTitle && (<TitleArea className='' />)}
|
|
103
|
+
<div className='flex flex-col justify-start items-center gap-4'>
|
|
104
|
+
<SelectProductComp />
|
|
105
|
+
<AddToCartComp className='' />
|
|
106
|
+
</div>
|
|
107
|
+
</div>
|
|
108
|
+
)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export default SelectCategoryItemCard
|
|
@@ -4,9 +4,10 @@ import Image from 'next/image'
|
|
|
4
4
|
|
|
5
5
|
import { cn } from '@hanzo/ui/util'
|
|
6
6
|
|
|
7
|
-
import type { LineItem } from '
|
|
8
|
-
import
|
|
9
|
-
|
|
7
|
+
import type { LineItem } from '../../types'
|
|
8
|
+
import { formatPrice } from '../../util'
|
|
9
|
+
|
|
10
|
+
import AddToCartWidget from '../add-to-cart-widget'
|
|
10
11
|
|
|
11
12
|
const IMG_SIZE=40
|
|
12
13
|
|
|
@@ -5,24 +5,25 @@ import { observer } from 'mobx-react-lite'
|
|
|
5
5
|
import { Button } from '@hanzo/ui/primitives'
|
|
6
6
|
import { cn } from '@hanzo/ui/util'
|
|
7
7
|
|
|
8
|
-
import { useCommerce } from '
|
|
9
|
-
import { formatPrice } from '
|
|
8
|
+
import { useCommerce } from '../../service/context'
|
|
9
|
+
import { formatPrice } from '../../util'
|
|
10
10
|
|
|
11
11
|
import CartLineItem from './cart-line-item'
|
|
12
|
-
import
|
|
12
|
+
import CheckoutPanel from '../checkout-panel'
|
|
13
13
|
|
|
14
|
-
const
|
|
14
|
+
const CartPanel: React.FC<PropsWithChildren & {
|
|
15
15
|
className?: string
|
|
16
16
|
isMobile?: boolean,
|
|
17
|
-
|
|
17
|
+
noCheckout?: boolean
|
|
18
18
|
}> = observer(({
|
|
19
19
|
/** Children is the heading area. */
|
|
20
20
|
children,
|
|
21
21
|
className='',
|
|
22
22
|
isMobile=false,
|
|
23
|
-
|
|
23
|
+
noCheckout=false
|
|
24
24
|
}) => {
|
|
25
|
-
|
|
25
|
+
|
|
26
|
+
const [checkoutOpen, setCheckoutOpen] = useState<boolean>(false)
|
|
26
27
|
const cmmc = useCommerce()
|
|
27
28
|
|
|
28
29
|
return (
|
|
@@ -30,7 +31,7 @@ const Cart: React.FC<PropsWithChildren & {
|
|
|
30
31
|
{children}
|
|
31
32
|
<div className='mt-2 w-full'>
|
|
32
33
|
{!!children && <div className='h-[1px] w-pr-80 mb-4 mx-auto bg-muted-3'/>}
|
|
33
|
-
{cmmc.
|
|
34
|
+
{cmmc.cartEmpty ? (
|
|
34
35
|
<p className='text-center mt-4'>No items in cart</p>
|
|
35
36
|
) : (<>
|
|
36
37
|
{cmmc.cartItems.map((item, i) => (
|
|
@@ -39,16 +40,22 @@ const Cart: React.FC<PropsWithChildren & {
|
|
|
39
40
|
<p className='mt-6 text-right border-t pt-1'>TOTAL: <span className='font-semibold'>{cmmc.cartTotal === 0 ? '0' : formatPrice(cmmc.cartTotal)}</span></p>
|
|
40
41
|
</>)}
|
|
41
42
|
</div>
|
|
42
|
-
{!
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
43
|
+
{!noCheckout && (<>
|
|
44
|
+
{!cmmc.cartEmpty && (
|
|
45
|
+
<Button
|
|
46
|
+
size='lg'
|
|
47
|
+
variant='secondary'
|
|
48
|
+
rounded='lg'
|
|
49
|
+
className='mt-12 mx-auto'
|
|
50
|
+
onClick={() => setCheckoutOpen(!checkoutOpen)}
|
|
51
|
+
>
|
|
52
|
+
Checkout
|
|
53
|
+
</Button>
|
|
54
|
+
)}
|
|
55
|
+
{checkoutOpen && <CheckoutPanel close={() => {setCheckoutOpen(false)}}/>}
|
|
56
|
+
</>)}
|
|
50
57
|
</div>
|
|
51
58
|
)
|
|
52
59
|
})
|
|
53
60
|
|
|
54
|
-
export default
|
|
61
|
+
export default CartPanel
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
import React from 'react'
|
|
3
|
+
import Picker from 'react-mobile-picker'
|
|
4
|
+
|
|
5
|
+
import type { ItemSelector } from '../types'
|
|
6
|
+
import { formatPrice } from '../util'
|
|
7
|
+
import { cn } from '@hanzo/ui/util'
|
|
8
|
+
|
|
9
|
+
// from source code
|
|
10
|
+
interface PickerItemRenderProps {
|
|
11
|
+
selected: boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const CategoryItemIOSWheelSelector: React.FC<ItemSelector & {
|
|
15
|
+
height?: number
|
|
16
|
+
itemHeight?: number
|
|
17
|
+
outerClx?: string
|
|
18
|
+
itemClx?: string
|
|
19
|
+
}> = ({
|
|
20
|
+
category,
|
|
21
|
+
selectedItemRef: iRef,
|
|
22
|
+
selectSku,
|
|
23
|
+
height,
|
|
24
|
+
itemHeight,
|
|
25
|
+
outerClx='',
|
|
26
|
+
itemClx=''
|
|
27
|
+
}) => {
|
|
28
|
+
|
|
29
|
+
// @ts-ignore
|
|
30
|
+
const onChange = (val, ignore) => {
|
|
31
|
+
selectSku(val.item)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<div className={cn('border border-muted-4 rounded-lg px-2', outerClx)}>
|
|
36
|
+
<Picker
|
|
37
|
+
className=''
|
|
38
|
+
value={iRef.item ? {item: iRef.item.sku} : {item: undefined}}
|
|
39
|
+
onChange={onChange}
|
|
40
|
+
wheelMode="natural"
|
|
41
|
+
height={height}
|
|
42
|
+
itemHeight={itemHeight}
|
|
43
|
+
>
|
|
44
|
+
<Picker.Column name="item">
|
|
45
|
+
{category.products.map((prod) => (
|
|
46
|
+
<Picker.Item key={prod.sku} value={prod.sku}>
|
|
47
|
+
{({ selected }: PickerItemRenderProps) => (
|
|
48
|
+
<div className={cn(selected ? 'font-semibold text-accent' : 'text-muted', itemClx)}>
|
|
49
|
+
{prod.titleAsOption + ', ' + formatPrice(prod.price)}
|
|
50
|
+
</div>
|
|
51
|
+
)}
|
|
52
|
+
</Picker.Item>
|
|
53
|
+
))}
|
|
54
|
+
</Picker.Column>
|
|
55
|
+
</Picker>
|
|
56
|
+
</div>
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export default CategoryItemIOSWheelSelector
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
import React from 'react'
|
|
3
|
+
import { observer } from 'mobx-react-lite'
|
|
4
|
+
|
|
5
|
+
import { Label, RadioGroup, RadioGroupItem } from '@hanzo/ui/primitives'
|
|
6
|
+
import type { ItemSelector, LineItem, Product } from '../types'
|
|
7
|
+
import { formatPrice } from '../util'
|
|
8
|
+
import { cn } from '@hanzo/ui/util'
|
|
9
|
+
|
|
10
|
+
const CategoryItemRadioSelector: React.FC<ItemSelector & {
|
|
11
|
+
groupClx?: string
|
|
12
|
+
itemClx?: string
|
|
13
|
+
}> = observer(({
|
|
14
|
+
category,
|
|
15
|
+
selectedItemRef: itemRef,
|
|
16
|
+
selectSku,
|
|
17
|
+
groupClx='',
|
|
18
|
+
itemClx='',
|
|
19
|
+
showPrice=true,
|
|
20
|
+
showQuantity=true
|
|
21
|
+
}) => {
|
|
22
|
+
|
|
23
|
+
const Choice: React.FC<{
|
|
24
|
+
item: Product
|
|
25
|
+
className?: string
|
|
26
|
+
}> = ({
|
|
27
|
+
item,
|
|
28
|
+
className=''
|
|
29
|
+
}) => (
|
|
30
|
+
<div className={cn(className, itemClx)}>
|
|
31
|
+
<RadioGroupItem value={item.sku} id={item.sku} />
|
|
32
|
+
<Label htmlFor={item.sku}>{item.titleAsOption + (showPrice ? (', ' + formatPrice(item.price)) : '')}</Label>
|
|
33
|
+
</div>
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
return ( category.products.length > 1 ? (
|
|
37
|
+
<RadioGroup
|
|
38
|
+
className={cn(showQuantity ? 'table' : '', groupClx)}
|
|
39
|
+
onValueChange={selectSku}
|
|
40
|
+
value={itemRef.item ? itemRef.item.sku : ''}
|
|
41
|
+
>
|
|
42
|
+
{(category.products as LineItem[]).map((item) => (showQuantity ? (
|
|
43
|
+
<div className='table-row' key={item.sku}>
|
|
44
|
+
<Choice item={item} className='table-cell pr-2 align-text-top pb-2'/>
|
|
45
|
+
<div className='table-cell font-semibold text-sm leading-none align-text-top pb-2'>{ item.quantity > 0 ? `(${item.quantity})` : ' '}</div>
|
|
46
|
+
</div>
|
|
47
|
+
) : (
|
|
48
|
+
<Choice item={item} className='mb-2' key={item.sku}/>
|
|
49
|
+
)))}
|
|
50
|
+
</RadioGroup>
|
|
51
|
+
) : (<p>TODO</p>)
|
|
52
|
+
)
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
export default CategoryItemRadioSelector
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
'use client'
|
|
2
|
+
import React from 'react'
|
|
3
|
+
import type { UseFormReturn } from 'react-hook-form'
|
|
4
|
+
import { BookUser, Lock, User } from 'lucide-react'
|
|
2
5
|
|
|
3
6
|
import { ApplyTypography, Button } from '@hanzo/ui/primitives'
|
|
4
|
-
import { BookUser, Lock, User } from 'lucide-react'
|
|
5
7
|
import { EnhHeadingBlockComponent, type EnhHeadingBlock } from '@hanzo/ui/blocks'
|
|
6
|
-
import type { UseFormReturn } from 'react-hook-form'
|
|
7
8
|
|
|
8
9
|
const ConfirmOrder: React.FC<{
|
|
9
10
|
paymentForm: UseFormReturn<{
|
|
@@ -11,10 +11,15 @@ import { LoginComponent, AuthWidget } from '@hanzo/auth/components'
|
|
|
11
11
|
|
|
12
12
|
import ShippingInfo from './shipping-info'
|
|
13
13
|
import ThankYou from './thank-you'
|
|
14
|
-
import
|
|
14
|
+
import CartPanel from '../cart-panel'
|
|
15
15
|
import Payment from './payment'
|
|
16
16
|
|
|
17
|
-
const
|
|
17
|
+
const CheckoutPanel: React.FC<{
|
|
18
|
+
close: () => void
|
|
19
|
+
}> = observer(({
|
|
20
|
+
close
|
|
21
|
+
}) => {
|
|
22
|
+
|
|
18
23
|
const auth = useAuth()
|
|
19
24
|
|
|
20
25
|
const [currentStep, setCurrentStep] = useState(1)
|
|
@@ -48,7 +53,7 @@ const Checkout: React.FC<{toggleCheckout: () => void}> = observer(({toggleChecko
|
|
|
48
53
|
size='icon'
|
|
49
54
|
onClick={() => {
|
|
50
55
|
setCurrentStep(0)
|
|
51
|
-
|
|
56
|
+
close()
|
|
52
57
|
}}
|
|
53
58
|
>
|
|
54
59
|
<ChevronLeft/>
|
|
@@ -58,7 +63,7 @@ const Checkout: React.FC<{toggleCheckout: () => void}> = observer(({toggleChecko
|
|
|
58
63
|
|
|
59
64
|
<div className='grid grid-cols-5 justify-center gap-8 h-full'>
|
|
60
65
|
<div className='col-span-2 hidden md:flex'>
|
|
61
|
-
<
|
|
66
|
+
<CartPanel noCheckout className='fixed justify-center border-none mt-10 w-1/3 max-w-[40rem]'/>
|
|
62
67
|
</div>
|
|
63
68
|
|
|
64
69
|
<div className='flex flex-col gap-8 sm:gap-14 col-span-5 md:col-span-3 max-w-[30rem] w-full mx-auto overflow-y-auto h-[calc(100%-40px)] sm:h-[calc(100%-48px)] py-4 px-1'>
|
|
@@ -83,4 +88,4 @@ const Checkout: React.FC<{toggleCheckout: () => void}> = observer(({toggleChecko
|
|
|
83
88
|
)
|
|
84
89
|
})
|
|
85
90
|
|
|
86
|
-
export default
|
|
91
|
+
export default CheckoutPanel
|
|
@@ -12,7 +12,8 @@ import { useAuth } from '@hanzo/auth/service'
|
|
|
12
12
|
import PayWithCrypto from './pay-with-crypto'
|
|
13
13
|
import PayWithBankTransfer from './pay-with-bank-transfer'
|
|
14
14
|
import PayWithCard from './pay-with-card'
|
|
15
|
-
import { useCommerce
|
|
15
|
+
import { useCommerce } from '../../../service/context'
|
|
16
|
+
import type { TransactionStatus } from '../../../types'
|
|
16
17
|
|
|
17
18
|
const contactFormSchema = z.object({
|
|
18
19
|
name: z.string().min(1, 'Enter your full name.'),
|
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
'use client'
|
|
2
|
+
import React from 'react'
|
|
2
3
|
|
|
4
|
+
import type { UseFormReturn } from 'react-hook-form'
|
|
3
5
|
// @ts-ignore
|
|
4
6
|
import { ApplePay, GooglePay, CreditCard, PaymentForm } from 'react-square-web-payments-sdk'
|
|
5
|
-
|
|
7
|
+
|
|
6
8
|
import { ApplyTypography, Button } from '@hanzo/ui/primitives'
|
|
7
|
-
|
|
8
|
-
import PaymentMethods from './payment-methods'
|
|
9
|
+
|
|
9
10
|
import { processSquareCardPayment } from '../../../util'
|
|
11
|
+
import { useCommerce } from '../../../service/context'
|
|
12
|
+
import type { TransactionStatus } from '../../../types'
|
|
13
|
+
|
|
14
|
+
import PaymentMethods from './payment-methods'
|
|
10
15
|
import ContactInfo from './contact-info'
|
|
11
16
|
|
|
12
17
|
const PayWithCard: React.FC<{
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
'use client'
|
|
2
|
-
|
|
3
2
|
import React, { useEffect, useState } from 'react'
|
|
4
|
-
|
|
5
3
|
import { autorun } from 'mobx'
|
|
6
4
|
import { observer } from 'mobx-react-lite'
|
|
7
5
|
|
|
@@ -23,7 +21,8 @@ import { useAuth } from '@hanzo/auth/service'
|
|
|
23
21
|
import { Ethereum as EthIconFromAuth } from '@hanzo/auth/icons'
|
|
24
22
|
|
|
25
23
|
import Eth from '../icons/eth'
|
|
26
|
-
import { useCommerce
|
|
24
|
+
import { useCommerce } from '../../../service/context'
|
|
25
|
+
import type { TransactionStatus } from '../../../types'
|
|
27
26
|
import { formatPrice } from '../../../util'
|
|
28
27
|
|
|
29
28
|
declare global {
|
|
@@ -3,7 +3,7 @@ import Image from 'next/image'
|
|
|
3
3
|
|
|
4
4
|
import type { FacetValueDesc } from '../../types'
|
|
5
5
|
|
|
6
|
-
const ICON_SIZE =
|
|
6
|
+
const ICON_SIZE = 20
|
|
7
7
|
const SVG_MULT = 1.5
|
|
8
8
|
|
|
9
9
|
const FacetImage: React.FC<{
|
|
@@ -27,16 +27,19 @@ const FacetImage: React.FC<{
|
|
|
27
27
|
<Image
|
|
28
28
|
src={img as string}
|
|
29
29
|
alt={`Toggle ${label}`}
|
|
30
|
-
className={'block mr-1
|
|
30
|
+
className={'block mr-1 '}
|
|
31
31
|
width={ar ? ar * ICON_SIZE : ICON_SIZE}
|
|
32
32
|
height={ICON_SIZE}
|
|
33
33
|
/>
|
|
34
34
|
)
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
return img as React.ReactNode
|
|
38
|
+
|
|
37
39
|
// Otherwise, assume it's a ReactNode of an imported SVG
|
|
38
40
|
|
|
39
41
|
// If it's not square, center it in the appropriate dimension
|
|
42
|
+
/*
|
|
40
43
|
const svgStyle: any = { position: 'relative' }
|
|
41
44
|
if (ar) {
|
|
42
45
|
if (ar < 1) {
|
|
@@ -51,7 +54,7 @@ const FacetImage: React.FC<{
|
|
|
51
54
|
|
|
52
55
|
return (
|
|
53
56
|
<span
|
|
54
|
-
className='
|
|
57
|
+
className='flex justify-center items-center overflow-hidden '
|
|
55
58
|
style={{
|
|
56
59
|
color: 'inherit',
|
|
57
60
|
width: ICON_SIZE * SVG_MULT,
|
|
@@ -65,6 +68,7 @@ const FacetImage: React.FC<{
|
|
|
65
68
|
})}
|
|
66
69
|
</span>
|
|
67
70
|
)
|
|
71
|
+
*/
|
|
68
72
|
}
|
|
69
73
|
|
|
70
74
|
export default FacetImage
|