@hanzo/commerce 4.9.0 → 5.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 +0 -1
- package/components/{checkout-panel/cart-accordian.tsx → cart-accordian.tsx} +18 -9
- package/components/cart-panel/cart-line-item.tsx +22 -28
- package/components/cart-panel/index.tsx +57 -33
- package/components/index.ts +4 -1
- package/components/item-carousel.tsx +80 -0
- package/components/{checkout-panel/steps/payment/cards/index.tsx → payment-step-form/card-icon-row.tsx} +6 -6
- package/components/{checkout-panel/steps/payment → payment-step-form}/cc-button.tsx +1 -1
- package/components/payment-step-form/contact-form.tsx +49 -0
- package/components/{checkout-panel/steps/payment → payment-step-form}/index.tsx +27 -54
- package/components/{checkout-panel/steps/payment/pay-with-bank-transfer.tsx → payment-step-form/methods/bank-transfer.tsx} +17 -17
- package/components/{checkout-panel/steps/payment/pay-with-card.tsx → payment-step-form/methods/card.tsx} +8 -20
- package/components/{checkout-panel/steps/payment/pay-with-crypto.tsx → payment-step-form/methods/crypto.tsx} +8 -22
- package/components/payment-step-form/methods/index.ts +23 -0
- package/components/{checkout-panel/steps/shipping-info.tsx → shipping-step-form.tsx} +6 -8
- package/package.json +2 -2
- package/types/checkout.ts +43 -1
- package/types/index.ts +1 -1
- package/components/cart-panel/products-carousel.tsx +0 -60
- package/components/checkout-panel/_confirm-order.tsx_unused +0 -143
- package/components/checkout-panel/close-button.tsx +0 -24
- package/components/checkout-panel/desktop.tsx +0 -46
- package/components/checkout-panel/icons/bag-icon.tsx +0 -10
- package/components/checkout-panel/index.tsx +0 -108
- package/components/checkout-panel/mobile.tsx +0 -41
- package/components/checkout-panel/steps/payment/contact-info.tsx +0 -56
- package/components/checkout-panel/steps/thank-you.tsx +0 -18
- package/components/checkout-panel/steps/types.ts +0 -14
- /package/components/{checkout-panel/steps/payment/cards → payment-step-form/card-icons}/amex.tsx +0 -0
- /package/components/{checkout-panel/steps/payment/cards → payment-step-form/card-icons}/diners-club.tsx +0 -0
- /package/components/{checkout-panel/steps/payment/cards → payment-step-form/card-icons}/discover.tsx +0 -0
- /package/components/{checkout-panel/steps/payment/cards → payment-step-form/card-icons}/jcb.tsx +0 -0
- /package/components/{checkout-panel/steps/payment/cards → payment-step-form/card-icons}/mastercard.tsx +0 -0
- /package/components/{checkout-panel/steps/payment/cards → payment-step-form/card-icons}/visa.tsx +0 -0
- /package/components/{checkout-panel/icons → payment-step-form/crypto-icons}/btc.tsx +0 -0
- /package/components/{checkout-panel/icons → payment-step-form/crypto-icons}/eth.tsx +0 -0
- /package/components/{checkout-panel/icons → payment-step-form/crypto-icons}/usdt.tsx +0 -0
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
import React from 'react'
|
|
3
3
|
import { observer } from 'mobx-react-lite'
|
|
4
4
|
|
|
5
|
+
import { ChevronRight } from 'lucide-react'
|
|
6
|
+
|
|
5
7
|
import {
|
|
6
8
|
Accordion,
|
|
7
9
|
AccordionContent,
|
|
@@ -9,23 +11,24 @@ import {
|
|
|
9
11
|
AccordionTrigger,
|
|
10
12
|
} from '@hanzo/ui/primitives'
|
|
11
13
|
|
|
12
|
-
import { formatPrice, useCommerce } from '
|
|
14
|
+
import { formatPrice, useCommerce } from '..'
|
|
13
15
|
|
|
14
|
-
import CartPanel from '
|
|
15
|
-
import BagIcon from './icons/bag-icon'
|
|
16
|
-
import { ChevronRight } from 'lucide-react'
|
|
16
|
+
import CartPanel from './cart-panel'
|
|
17
17
|
|
|
18
|
-
const CartAccordian: React.FC<{
|
|
18
|
+
const CartAccordian: React.FC<{
|
|
19
|
+
icon?: React.ReactNode
|
|
20
|
+
className?: string
|
|
21
|
+
}> = observer(({
|
|
22
|
+
icon,
|
|
19
23
|
className=''
|
|
20
24
|
}) => {
|
|
21
|
-
|
|
22
25
|
const cmmc = useCommerce()
|
|
23
26
|
return (
|
|
24
27
|
<Accordion type="single" collapsible className={className}>
|
|
25
28
|
<AccordionItem value="cart" className='w-full border-b-0'>
|
|
26
29
|
<AccordionTrigger className='!no-underline group flex justify-between'>
|
|
27
|
-
<div className='flex gap-
|
|
28
|
-
|
|
30
|
+
<div className='flex gap-0 items-center'>
|
|
31
|
+
{icon}
|
|
29
32
|
<h5 className='text-sm sm:text-xl grow'>
|
|
30
33
|
<span className='group-data-[state=open]:hidden' >Order Total:</span>
|
|
31
34
|
<span className='group-data-[state=closed]:hidden' >Your Order</span>
|
|
@@ -37,7 +40,13 @@ const CartAccordian: React.FC<{className?: string}> = observer(({
|
|
|
37
40
|
</div>
|
|
38
41
|
</AccordionTrigger>
|
|
39
42
|
<AccordionContent className='data-[state=open]:mb-4'>
|
|
40
|
-
<CartPanel
|
|
43
|
+
<CartPanel
|
|
44
|
+
className='w-full '
|
|
45
|
+
scrollAfter={4}
|
|
46
|
+
scrollHeightClx='h-[350px]'
|
|
47
|
+
itemClx='mt-2'
|
|
48
|
+
totalClx='sticky px-1 pr-2 border-t -bottom-[1px] bg-background'
|
|
49
|
+
/>
|
|
41
50
|
</AccordionContent>
|
|
42
51
|
</AccordionItem>
|
|
43
52
|
</Accordion>
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
|
-
import { observer } from 'mobx-react-lite'
|
|
3
2
|
import Image from 'next/image'
|
|
3
|
+
import { observer } from 'mobx-react-lite'
|
|
4
4
|
|
|
5
5
|
import { cn } from '@hanzo/ui/util'
|
|
6
6
|
|
|
7
7
|
import type { LineItem } from '../../types'
|
|
8
8
|
import { formatPrice } from '../../util'
|
|
9
|
-
|
|
10
9
|
import AddToCartWidget from '../add-to-cart-widget'
|
|
11
10
|
|
|
12
|
-
const
|
|
11
|
+
const DEF_IMG_SIZE=40
|
|
13
12
|
|
|
14
13
|
const renderTitle = (t: string): React.ReactNode => {
|
|
15
14
|
const toks = t.split(', ')
|
|
@@ -22,36 +21,31 @@ const renderTitle = (t: string): React.ReactNode => {
|
|
|
22
21
|
const CartLineItem: React.FC<{
|
|
23
22
|
item: LineItem
|
|
24
23
|
className?: string
|
|
25
|
-
|
|
24
|
+
imgSizePx?: number
|
|
26
25
|
}> = observer(({
|
|
27
26
|
item,
|
|
28
27
|
className='',
|
|
29
|
-
|
|
30
|
-
}) =>
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
<div
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
<
|
|
28
|
+
imgSizePx=DEF_IMG_SIZE
|
|
29
|
+
}) => (
|
|
30
|
+
<div className={cn('flex flex-col justify-start items-start text-sm font-sans', className)}>
|
|
31
|
+
<div className='flex flex-row justify-between items-center gap-2'>
|
|
32
|
+
{!!item.img ? (
|
|
33
|
+
<Image src={item.img} alt={item.title + ' image'} height={imgSizePx} width={imgSizePx} className=''/>
|
|
34
|
+
) : ( // placeholder so things align
|
|
35
|
+
<div style={{height: imgSizePx, width: imgSizePx}}/ >
|
|
36
|
+
)}
|
|
37
|
+
<div className='grow leading-tight'>{renderTitle(item.title)}</div>
|
|
38
|
+
</div>
|
|
39
|
+
<div className='flex flex-row items-center justify-between w-full'>
|
|
40
|
+
<div className='flex flex-row items-center'>
|
|
41
|
+
<AddToCartWidget className='' item={item} size='xs' buttonClx='h-8 md:h-6' ghost/>
|
|
42
|
+
{item.quantity > 1 && (<span className='pl-2.5'>{'@' + formatPrice(item.price)}</span>)}
|
|
43
43
|
</div>
|
|
44
|
-
<div className='flex flex-row items-center justify-
|
|
45
|
-
|
|
46
|
-
<AddToCartWidget isMobile={isMobile} className='' item={item} size='xs' buttonClx='h-8 md:h-6' ghost/>
|
|
47
|
-
{item.quantity > 1 && (<span className='pl-2.5'>{'@' + formatPrice(item.price)}</span>)}
|
|
48
|
-
</div>
|
|
49
|
-
<div className='flex flex-row items-center justify-end'>
|
|
50
|
-
{formatPrice(item.price * item.quantity)}
|
|
51
|
-
</div>
|
|
44
|
+
<div className='flex flex-row items-center justify-end'>
|
|
45
|
+
{formatPrice(item.price * item.quantity)}
|
|
52
46
|
</div>
|
|
53
47
|
</div>
|
|
54
|
-
|
|
55
|
-
|
|
48
|
+
</div>
|
|
49
|
+
))
|
|
56
50
|
|
|
57
51
|
export default CartLineItem
|
|
@@ -2,28 +2,40 @@
|
|
|
2
2
|
import React, { type PropsWithChildren } from 'react'
|
|
3
3
|
import { observer } from 'mobx-react-lite'
|
|
4
4
|
|
|
5
|
-
import { Button } from '@hanzo/ui/primitives'
|
|
5
|
+
import { Button, ScrollArea } from '@hanzo/ui/primitives'
|
|
6
6
|
import { cn } from '@hanzo/ui/util'
|
|
7
7
|
|
|
8
8
|
import { useCommerce } from '../../service/context'
|
|
9
9
|
import { formatPrice } from '../../util'
|
|
10
|
+
import { sendFBEvent, sendGAEvent } from '../../util/analytics'
|
|
10
11
|
|
|
11
12
|
import CartLineItem from './cart-line-item'
|
|
12
|
-
import { sendFBEvent, sendGAEvent } from '../../util/analytics'
|
|
13
|
-
import ProductsCarousel from './products-carousel'
|
|
14
13
|
|
|
15
14
|
const CartPanel: React.FC<PropsWithChildren & {
|
|
15
|
+
/** fix size and scroll after 'scrollAfter items in teh cart. */
|
|
16
|
+
scrollAfter?: number
|
|
17
|
+
scrollHeightClx?: string
|
|
18
|
+
imgSizePx?: number
|
|
16
19
|
className?: string
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
+
itemClx?: string
|
|
21
|
+
listClx?: string
|
|
22
|
+
noItemsClx?: string
|
|
23
|
+
totalClx?: string
|
|
24
|
+
buttonClx?: string
|
|
25
|
+
/** if not provided, 'checkout' button will be rendered */
|
|
20
26
|
handleCheckout?: () => void
|
|
21
27
|
}> = observer(({
|
|
22
|
-
/**
|
|
28
|
+
/** If provided, 'children' is rendered above the items. eg, a heading. */
|
|
23
29
|
children,
|
|
30
|
+
scrollAfter=5,
|
|
31
|
+
scrollHeightClx='h-[70vh]',
|
|
32
|
+
imgSizePx=40,
|
|
24
33
|
className='',
|
|
25
|
-
|
|
26
|
-
|
|
34
|
+
itemClx='',
|
|
35
|
+
listClx='',
|
|
36
|
+
noItemsClx='',
|
|
37
|
+
totalClx='',
|
|
38
|
+
buttonClx='',
|
|
27
39
|
handleCheckout,
|
|
28
40
|
}) => {
|
|
29
41
|
|
|
@@ -57,32 +69,44 @@ const CartPanel: React.FC<PropsWithChildren & {
|
|
|
57
69
|
handleCheckout && handleCheckout()
|
|
58
70
|
}
|
|
59
71
|
|
|
72
|
+
const Main: React.FC = () => (<>
|
|
73
|
+
{cmmc.cartEmpty ? (
|
|
74
|
+
<p className={cn('text-center mt-4', noItemsClx)}>No items in cart</p>
|
|
75
|
+
) : (<>
|
|
76
|
+
{cmmc.cartItems.map((item) => (
|
|
77
|
+
<CartLineItem imgSizePx={imgSizePx} item={item} key={`mobile-${item.sku}`} className={cn('mb-2', itemClx)}/>
|
|
78
|
+
))}
|
|
79
|
+
</>)}
|
|
80
|
+
<p className={cn('mt-6 text-right border-t pt-1', totalClx)}>
|
|
81
|
+
<span>TOTAL: </span>
|
|
82
|
+
<span className='font-semibold'>{cmmc.cartTotal === 0 ? '0' : formatPrice(cmmc.cartTotal)}</span>
|
|
83
|
+
</p>
|
|
84
|
+
</>)
|
|
85
|
+
|
|
86
|
+
const scrolling = (): boolean => (cmmc.cartItems.length > scrollAfter)
|
|
87
|
+
|
|
60
88
|
return (
|
|
61
|
-
<div className={cn('border p-4 rounded-lg', className)}>
|
|
89
|
+
<div className={cn('border p-4 rounded-lg flex flex-col', className, scrolling() ? scrollHeightClx : 'h-auto')}>
|
|
62
90
|
{children}
|
|
63
|
-
{
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
Checkout
|
|
83
|
-
</Button>
|
|
84
|
-
)}
|
|
85
|
-
</>)}
|
|
91
|
+
{scrolling() ? (
|
|
92
|
+
<ScrollArea className={cn('mt-2 w-full shrink py-0', listClx)}>
|
|
93
|
+
<Main />
|
|
94
|
+
</ScrollArea>
|
|
95
|
+
) : (
|
|
96
|
+
<div className={cn('mt-2 w-full', listClx)}>
|
|
97
|
+
<Main />
|
|
98
|
+
</div>
|
|
99
|
+
)}
|
|
100
|
+
{handleCheckout && !cmmc.cartEmpty && (
|
|
101
|
+
<Button
|
|
102
|
+
onClick={_handleCheckout}
|
|
103
|
+
variant='primary'
|
|
104
|
+
rounded='lg'
|
|
105
|
+
className={cn('mt-12 mx-auto w-full sm:max-w-[220px]', buttonClx)}
|
|
106
|
+
>
|
|
107
|
+
Checkout
|
|
108
|
+
</Button>
|
|
109
|
+
)}
|
|
86
110
|
</div>
|
|
87
111
|
)
|
|
88
112
|
})
|
package/components/index.ts
CHANGED
|
@@ -3,13 +3,16 @@ export { default as BuyItemButtonWrapper } from './buy-item/buy-item-button-wrap
|
|
|
3
3
|
export { default as BuyItemButton } from './buy-item/buy-item-button'
|
|
4
4
|
export { default as BuyItemCard } from './buy-item/buy-item-card'
|
|
5
5
|
export { default as BuyItemPopup } from './buy-item/buy-item-popup'
|
|
6
|
+
export { default as CartAccordian } from './cart-accordian'
|
|
6
7
|
export { default as CartPanel } from './cart-panel'
|
|
7
|
-
export { default as CheckoutPanel } from './checkout-panel'
|
|
8
8
|
export { default as CategoryItemIOSWheelSelector } from './category-item-scroll-selector'
|
|
9
9
|
export { default as CategoryItemRadioSelector } from './category-item-radio-selector'
|
|
10
10
|
export { default as FacetValuesWidget } from './facet-values-widget'
|
|
11
|
+
export { default as ItemCarousel } from './item-carousel'
|
|
12
|
+
export { default as PaymentStepForm } from './payment-step-form'
|
|
11
13
|
export { default as ProductCard } from './product-card'
|
|
12
14
|
export { default as SelectCategoryAndItemWidget } from './buy-item/select-category-and-item-widget'
|
|
13
15
|
export { default as SelectCategoryItemCard } from './buy-item/select-category-item-card'
|
|
14
16
|
export { default as SelectCategoryItemPanel } from './select-category-item-panel'
|
|
17
|
+
export { default as ShippingStepForm } from './shipping-step-form'
|
|
15
18
|
export { Icons } from './Icons'
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
|
|
3
|
+
import Spline from '@splinetool/react-spline'
|
|
4
|
+
|
|
5
|
+
import { cn } from '@hanzo/ui/util'
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
type CarouselOptionsType,
|
|
9
|
+
Carousel,
|
|
10
|
+
CarouselContent,
|
|
11
|
+
CarouselItem,
|
|
12
|
+
CarouselPrevious,
|
|
13
|
+
CarouselNext
|
|
14
|
+
} from '@hanzo/ui/primitives'
|
|
15
|
+
|
|
16
|
+
import {
|
|
17
|
+
VideoBlockComponent,
|
|
18
|
+
ImageBlockComponent,
|
|
19
|
+
type ImageBlock,
|
|
20
|
+
type Block,
|
|
21
|
+
type VideoBlock
|
|
22
|
+
} from '@hanzo/ui/blocks'
|
|
23
|
+
|
|
24
|
+
import type { LineItem } from '../types'
|
|
25
|
+
import type { Dimensions } from '@hanzo/ui/types'
|
|
26
|
+
|
|
27
|
+
// Order of precedence of visuals: 3D > MP4 > Image
|
|
28
|
+
const ItemCarousel: React.FC<{
|
|
29
|
+
items: LineItem[]
|
|
30
|
+
constrainTo: Dimensions
|
|
31
|
+
options?: CarouselOptionsType
|
|
32
|
+
className?: string
|
|
33
|
+
itemClx?: string
|
|
34
|
+
}> = ({
|
|
35
|
+
items,
|
|
36
|
+
options,
|
|
37
|
+
className='',
|
|
38
|
+
constrainTo,
|
|
39
|
+
itemClx=''
|
|
40
|
+
}) => (
|
|
41
|
+
// className: 'w-full max-w-sm mx-auto
|
|
42
|
+
// itemClx: 'flex aspect-square items-center justify-center '
|
|
43
|
+
<Carousel options={options} className={cn('px-2', className)} >
|
|
44
|
+
<CarouselContent>
|
|
45
|
+
{items.map(({title, img, video, animation}, index) => (
|
|
46
|
+
<CarouselItem key={index} className={cn('p-2 flex flex-row justify-center items-center', itemClx)}>
|
|
47
|
+
{animation ? (
|
|
48
|
+
<Spline
|
|
49
|
+
scene={animation}
|
|
50
|
+
className='pointer-events-none' // !aspect-[12/10]
|
|
51
|
+
style={{
|
|
52
|
+
width: (6/5 * (typeof constrainTo.h === 'number' ? constrainTo.h as number : parseInt(constrainTo.h as string)) ),
|
|
53
|
+
height: constrainTo.h
|
|
54
|
+
}}
|
|
55
|
+
/>
|
|
56
|
+
) : video ? (
|
|
57
|
+
<VideoBlockComponent
|
|
58
|
+
constraint={constrainTo}
|
|
59
|
+
block={{blockType: 'video', ...video} satisfies VideoBlock as Block}
|
|
60
|
+
/>
|
|
61
|
+
) : (
|
|
62
|
+
<ImageBlockComponent
|
|
63
|
+
block={{
|
|
64
|
+
blockType: 'image',
|
|
65
|
+
src: img ?? '',
|
|
66
|
+
alt: title + ' image',
|
|
67
|
+
dim: constrainTo
|
|
68
|
+
} satisfies ImageBlock as Block}
|
|
69
|
+
className='m-auto'
|
|
70
|
+
/>
|
|
71
|
+
)}
|
|
72
|
+
</CarouselItem>
|
|
73
|
+
))}
|
|
74
|
+
</CarouselContent>
|
|
75
|
+
<CarouselPrevious className='left-1'/>
|
|
76
|
+
<CarouselNext className='right-1'/>
|
|
77
|
+
</Carousel>
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
export default ItemCarousel
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { LockKeyhole } from 'lucide-react'
|
|
2
|
-
import Amex from './amex'
|
|
3
|
-
import Discover from './discover'
|
|
4
|
-
import Mastercard from './mastercard'
|
|
5
|
-
import Visa from './visa'
|
|
6
|
-
import DinersClub from './diners-club'
|
|
7
|
-
import Jcb from './jcb'
|
|
2
|
+
import Amex from './card-icons/amex'
|
|
3
|
+
import Discover from './card-icons/discover'
|
|
4
|
+
import Mastercard from './card-icons/mastercard'
|
|
5
|
+
import Visa from './card-icons/visa'
|
|
6
|
+
import DinersClub from './card-icons/diners-club'
|
|
7
|
+
import Jcb from './card-icons/jcb'
|
|
8
8
|
|
|
9
9
|
// <span className='hidden sm:flex text-sm'>Secure payments with</span>
|
|
10
10
|
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Form,
|
|
3
|
+
FormControl,
|
|
4
|
+
FormField,
|
|
5
|
+
FormItem,
|
|
6
|
+
FormMessage,
|
|
7
|
+
} from '@hanzo/ui/primitives/form'
|
|
8
|
+
import { Input } from '@hanzo/ui/primitives'
|
|
9
|
+
|
|
10
|
+
import type { ContactFormType } from '../../types'
|
|
11
|
+
|
|
12
|
+
const ContactForm: React.FC<{
|
|
13
|
+
form: ContactFormType,
|
|
14
|
+
}> = ({
|
|
15
|
+
form,
|
|
16
|
+
}) => (
|
|
17
|
+
<Form {...form}>
|
|
18
|
+
<form className='text-left'>
|
|
19
|
+
<div className='flex flex-col gap-1 sm:gap-2'>
|
|
20
|
+
<FormField
|
|
21
|
+
control={form.control}
|
|
22
|
+
name='name'
|
|
23
|
+
render={({ field }) => (
|
|
24
|
+
<FormItem className='w-full'>
|
|
25
|
+
<FormControl>
|
|
26
|
+
<Input {...field} className='border-muted-4' placeholder='Full name'/>
|
|
27
|
+
</FormControl>
|
|
28
|
+
<FormMessage className='py-0 space-y-0 !m-0'/>
|
|
29
|
+
</FormItem>
|
|
30
|
+
)}
|
|
31
|
+
/>
|
|
32
|
+
<FormField
|
|
33
|
+
control={form.control}
|
|
34
|
+
name='email'
|
|
35
|
+
render={({ field }) => (
|
|
36
|
+
<FormItem className='w-full'>
|
|
37
|
+
<FormControl>
|
|
38
|
+
<Input {...field} className='border-muted-4' placeholder='Email'/>
|
|
39
|
+
</FormControl>
|
|
40
|
+
<FormMessage className='py-0 space-y-0 !m-0'/>
|
|
41
|
+
</FormItem>
|
|
42
|
+
)}
|
|
43
|
+
/>
|
|
44
|
+
</div>
|
|
45
|
+
</form>
|
|
46
|
+
</Form>
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
export default ContactForm
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
'use client'
|
|
2
|
-
|
|
3
|
-
import { useEffect, useState } from 'react'
|
|
2
|
+
import React, { useEffect, useState } from 'react'
|
|
4
3
|
import { observer } from 'mobx-react-lite'
|
|
5
4
|
|
|
6
5
|
import { zodResolver } from '@hookform/resolvers/zod'
|
|
@@ -9,22 +8,19 @@ import { useForm } from 'react-hook-form'
|
|
|
9
8
|
|
|
10
9
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@hanzo/ui/primitives'
|
|
11
10
|
import { useAuth } from '@hanzo/auth/service'
|
|
12
|
-
import PayWithCrypto from './pay-with-crypto'
|
|
13
|
-
import PayWithBankTransfer from './pay-with-bank-transfer'
|
|
14
|
-
import PayWithCard from './pay-with-card'
|
|
15
11
|
|
|
16
|
-
import { useCommerce } from '
|
|
17
|
-
import
|
|
18
|
-
import {
|
|
12
|
+
import { useCommerce } from '../../service/context'
|
|
13
|
+
import { sendFBEvent, sendGAEvent } from '../../util/analytics'
|
|
14
|
+
import type { CheckoutStepComponentProps, TransactionStatus } from '../../types'
|
|
19
15
|
|
|
20
|
-
import
|
|
16
|
+
import METHODS from './methods'
|
|
21
17
|
|
|
22
18
|
const contactFormSchema = z.object({
|
|
23
19
|
name: z.string().min(1, 'Enter your full name.'),
|
|
24
20
|
email: z.string().email(),
|
|
25
21
|
})
|
|
26
22
|
|
|
27
|
-
const
|
|
23
|
+
const PaymentStepForm: React.FC<CheckoutStepComponentProps> = observer(({
|
|
28
24
|
onDone,
|
|
29
25
|
orderId,
|
|
30
26
|
setOrderId
|
|
@@ -87,58 +83,35 @@ const Payment: React.FC<StepComponentProps> = observer(({
|
|
|
87
83
|
const tabClx = 'whitespace-normal h-full text-xs sm:text-base px-1 text-muted ' +
|
|
88
84
|
'data-[state=active]:text-accent data-[state=active]:bg-level-2 md:data-[state=active]:bg-level-3'
|
|
89
85
|
|
|
86
|
+
const disabled = transactionStatus === 'paid' || transactionStatus === 'confirmed'
|
|
87
|
+
|
|
90
88
|
return (
|
|
91
|
-
<Tabs defaultValue='card' className='w-full
|
|
89
|
+
<Tabs defaultValue='card' className='w-full'>
|
|
92
90
|
<TabsList className={groupClx}>
|
|
91
|
+
{METHODS.map(({ label, value }) => (
|
|
93
92
|
<TabsTrigger
|
|
94
|
-
value=
|
|
95
|
-
className={tabClx}
|
|
96
|
-
disabled={transactionStatus === 'paid' || transactionStatus === 'confirmed'}
|
|
97
|
-
>
|
|
98
|
-
Card
|
|
99
|
-
</TabsTrigger>
|
|
100
|
-
<TabsTrigger
|
|
101
|
-
value='crypto'
|
|
102
|
-
className={tabClx}
|
|
103
|
-
disabled={transactionStatus === 'paid' || transactionStatus === 'confirmed'}
|
|
104
|
-
>
|
|
105
|
-
Wallet
|
|
106
|
-
</TabsTrigger>
|
|
107
|
-
<TabsTrigger
|
|
108
|
-
value='bank'
|
|
93
|
+
value={value}
|
|
109
94
|
className={tabClx}
|
|
110
|
-
disabled={
|
|
95
|
+
disabled={disabled}
|
|
96
|
+
key={`tabs-${value}`}
|
|
111
97
|
>
|
|
112
|
-
|
|
98
|
+
{label}
|
|
113
99
|
</TabsTrigger>
|
|
100
|
+
))}
|
|
114
101
|
</TabsList>
|
|
115
|
-
|
|
116
|
-
<
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
onDone={onDone}
|
|
127
|
-
transactionStatus={transactionStatus}
|
|
128
|
-
setTransactionStatus={setTransactionStatus}
|
|
129
|
-
storePaymentInfo={storePaymentInfo}
|
|
130
|
-
contactForm={contactForm}
|
|
131
|
-
/>
|
|
132
|
-
</TabsContent>
|
|
133
|
-
<TabsContent value='bank'>
|
|
134
|
-
<PayWithBankTransfer
|
|
135
|
-
onDone={onDone}
|
|
136
|
-
storePaymentInfo={storePaymentInfo}
|
|
137
|
-
contactForm={contactForm}
|
|
138
|
-
/>
|
|
139
|
-
</TabsContent>
|
|
102
|
+
{METHODS.map(({Comp: PaymentMethodComp, value}) => (
|
|
103
|
+
<TabsContent value={value} key={`content-${value}`}>
|
|
104
|
+
<PaymentMethodComp
|
|
105
|
+
onDone={onDone}
|
|
106
|
+
transactionStatus={transactionStatus}
|
|
107
|
+
setTransactionStatus={setTransactionStatus}
|
|
108
|
+
storePaymentInfo={storePaymentInfo}
|
|
109
|
+
contactForm={contactForm}
|
|
110
|
+
/>
|
|
111
|
+
</TabsContent>
|
|
112
|
+
))}
|
|
140
113
|
</Tabs>
|
|
141
114
|
)
|
|
142
115
|
})
|
|
143
116
|
|
|
144
|
-
export default
|
|
117
|
+
export default PaymentStepForm
|
|
@@ -1,21 +1,31 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import
|
|
3
|
+
import React from 'react'
|
|
4
4
|
import { Copy } from 'lucide-react'
|
|
5
|
-
import type { UseFormReturn } from 'react-hook-form'
|
|
6
5
|
|
|
7
|
-
import {
|
|
8
|
-
|
|
6
|
+
import {
|
|
7
|
+
Button,
|
|
8
|
+
Tabs,
|
|
9
|
+
TabsContent,
|
|
10
|
+
TabsList,
|
|
11
|
+
TabsTrigger,
|
|
12
|
+
toast
|
|
13
|
+
} from '@hanzo/ui/primitives'
|
|
14
|
+
|
|
15
|
+
import type { PaymentMethodComponentProps } from '../../../types'
|
|
16
|
+
|
|
17
|
+
import ContactForm from '../contact-form'
|
|
9
18
|
|
|
10
19
|
const InfoField: React.FC<{
|
|
11
20
|
label: string,
|
|
12
|
-
value: ReactNode,
|
|
21
|
+
value: React.ReactNode,
|
|
13
22
|
copyValue: string
|
|
14
23
|
}> = ({
|
|
15
24
|
label,
|
|
16
25
|
value,
|
|
17
26
|
copyValue
|
|
18
27
|
}) => {
|
|
28
|
+
|
|
19
29
|
const copyToClipboard = (label: string, text: string) => {
|
|
20
30
|
navigator.clipboard.writeText(text)
|
|
21
31
|
toast(`${label} copied to clipboard.`)
|
|
@@ -34,17 +44,7 @@ const InfoField: React.FC<{
|
|
|
34
44
|
)
|
|
35
45
|
}
|
|
36
46
|
|
|
37
|
-
const PayWithBankTransfer: React.FC<{
|
|
38
|
-
onDone: () => void
|
|
39
|
-
storePaymentInfo: (paymentInfo: any) => Promise<void>
|
|
40
|
-
contactForm: UseFormReturn<{
|
|
41
|
-
name: string
|
|
42
|
-
email: string
|
|
43
|
-
}, any, {
|
|
44
|
-
name: string
|
|
45
|
-
email: string
|
|
46
|
-
}>
|
|
47
|
-
}> = ({
|
|
47
|
+
const PayWithBankTransfer: React.FC<PaymentMethodComponentProps> = ({
|
|
48
48
|
onDone,
|
|
49
49
|
storePaymentInfo,
|
|
50
50
|
contactForm
|
|
@@ -62,7 +62,7 @@ const PayWithBankTransfer: React.FC<{
|
|
|
62
62
|
|
|
63
63
|
return (
|
|
64
64
|
<div className='flex flex-col gap-2 mt-6'>
|
|
65
|
-
<
|
|
65
|
+
<ContactForm form={contactForm}/>
|
|
66
66
|
<Tabs defaultValue="usd" className='w-full mx-auto max-w-[50rem]'>
|
|
67
67
|
<TabsList className={"grid w-full grid-cols-2 max-w-[15rem] mx-auto " + groupClx}>
|
|
68
68
|
<TabsTrigger className={tabClx} value="usd">USD</TabsTrigger>
|
|
@@ -19,27 +19,15 @@ import {
|
|
|
19
19
|
|
|
20
20
|
import { cn } from '@hanzo/ui/util'
|
|
21
21
|
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
import type {
|
|
22
|
+
import { useCommerce } from '../../../service/context'
|
|
23
|
+
import { processSquareCardPayment } from '../../../util'
|
|
24
|
+
import type { PaymentMethodComponentProps } from '../../../types'
|
|
25
|
+
import { sendFBEvent, sendGAEvent } from '../../../util/analytics'
|
|
25
26
|
|
|
26
|
-
import ContactInfo from '
|
|
27
|
-
import
|
|
28
|
-
import PaymentMethods from './cards'
|
|
27
|
+
import ContactInfo from '../contact-form'
|
|
28
|
+
import PaymentMethods from '../card-icon-row'
|
|
29
29
|
|
|
30
|
-
const PayWithCard: React.FC<{
|
|
31
|
-
onDone: () => void
|
|
32
|
-
transactionStatus: TransactionStatus
|
|
33
|
-
setTransactionStatus: (status: TransactionStatus) => void
|
|
34
|
-
storePaymentInfo: (paymentInfo: any) => Promise<void>
|
|
35
|
-
contactForm: UseFormReturn<{
|
|
36
|
-
name: string
|
|
37
|
-
email: string
|
|
38
|
-
}, any, {
|
|
39
|
-
name: string
|
|
40
|
-
email: string
|
|
41
|
-
}>
|
|
42
|
-
}> = ({
|
|
30
|
+
const PayWithCard: React.FC<PaymentMethodComponentProps> = ({
|
|
43
31
|
onDone,
|
|
44
32
|
transactionStatus,
|
|
45
33
|
setTransactionStatus,
|
|
@@ -243,4 +231,4 @@ const PayWithCard: React.FC<{
|
|
|
243
231
|
)
|
|
244
232
|
}
|
|
245
233
|
|
|
246
|
-
export default PayWithCard
|
|
234
|
+
export default PayWithCard
|