@hanzo/commerce 4.7.0 → 4.9.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 +2 -2
- package/components/cart-panel/index.tsx +12 -15
- package/components/checkout-panel/cart-accordian.tsx +14 -6
- package/components/checkout-panel/close-button.tsx +3 -3
- package/components/checkout-panel/desktop.tsx +46 -0
- package/components/checkout-panel/index.tsx +85 -85
- package/components/checkout-panel/mobile.tsx +41 -0
- package/components/checkout-panel/{payment/payment-methods → steps/payment/cards}/index.tsx +2 -1
- package/components/checkout-panel/steps/payment/cc-button.tsx +17 -0
- package/components/checkout-panel/{payment → steps/payment}/contact-info.tsx +5 -6
- package/components/checkout-panel/{payment → steps/payment}/index.tsx +27 -21
- package/components/checkout-panel/{payment → steps/payment}/pay-with-bank-transfer.tsx +11 -7
- package/components/checkout-panel/steps/payment/pay-with-card.tsx +246 -0
- package/components/checkout-panel/{payment → steps/payment}/pay-with-crypto.tsx +7 -7
- package/components/checkout-panel/{shipping-info.tsx → steps/shipping-info.tsx} +8 -9
- package/components/checkout-panel/{thank-you.tsx → steps/thank-you.tsx} +4 -1
- package/components/checkout-panel/steps/types.ts +14 -0
- package/package.json +3 -3
- package/{components/checkout-panel → util}/countries.ts +1 -1
- package/components/checkout-panel/payment/pay-with-card.tsx +0 -222
- package/components/checkout-panel/step-indicator.tsx +0 -30
- package/components/checkout-panel/step.ts +0 -11
- /package/components/checkout-panel/{confirm-order.tsx → _confirm-order.tsx_unused} +0 -0
- /package/components/checkout-panel/{payment/payment-methods → steps/payment/cards}/amex.tsx +0 -0
- /package/components/checkout-panel/{payment/payment-methods → steps/payment/cards}/diners-club.tsx +0 -0
- /package/components/checkout-panel/{payment/payment-methods → steps/payment/cards}/discover.tsx +0 -0
- /package/components/checkout-panel/{payment/payment-methods → steps/payment/cards}/jcb.tsx +0 -0
- /package/components/checkout-panel/{payment/payment-methods → steps/payment/cards}/mastercard.tsx +0 -0
- /package/components/checkout-panel/{payment/payment-methods → steps/payment/cards}/visa.tsx +0 -0
|
@@ -29,7 +29,7 @@ const AddToCartWidget: React.FC<{
|
|
|
29
29
|
}) => {
|
|
30
30
|
|
|
31
31
|
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'
|
|
32
|
-
const digitClx = ghost ? 'px-2 md:px-0.5' : 'sm:px-2 font-bold '
|
|
32
|
+
const digitClx = ghost ? 'px-2 md:px-0.5 text-foreground ' : 'sm:px-2 font-bold text-primary-fg '
|
|
33
33
|
|
|
34
34
|
if (disabled) {
|
|
35
35
|
return (
|
|
@@ -116,7 +116,7 @@ const AddToCartWidget: React.FC<{
|
|
|
116
116
|
<Icons.trash className={iconClx} aria-hidden='true'/>
|
|
117
117
|
)}
|
|
118
118
|
</Button>
|
|
119
|
-
<div className={'text-sm flex items-center cursor-default xs:px-2
|
|
119
|
+
<div className={'text-sm flex items-center cursor-default xs:px-2 ' + digitClx} >{item.quantity}</div>
|
|
120
120
|
<Button
|
|
121
121
|
aria-label={'Add another ' + item.title + ' to the cart'}
|
|
122
122
|
size={size}
|
|
@@ -15,27 +15,24 @@ import ProductsCarousel from './products-carousel'
|
|
|
15
15
|
const CartPanel: React.FC<PropsWithChildren & {
|
|
16
16
|
className?: string
|
|
17
17
|
isMobile?: boolean,
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
showCarousel?: boolean
|
|
19
|
+
/** if not provided, checkout button will not be shown */
|
|
20
|
+
handleCheckout?: () => void
|
|
21
21
|
}> = observer(({
|
|
22
22
|
/** Children is the heading area. */
|
|
23
23
|
children,
|
|
24
24
|
className='',
|
|
25
25
|
isMobile=false,
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
onCheckoutOpen,
|
|
26
|
+
showCarousel=false,
|
|
27
|
+
handleCheckout,
|
|
29
28
|
}) => {
|
|
30
|
-
|
|
31
|
-
* Needs refactor with context or so.
|
|
32
|
-
**/
|
|
29
|
+
|
|
33
30
|
const cmmc = useCommerce()
|
|
34
31
|
if (!cmmc) {
|
|
35
32
|
return <div />
|
|
36
33
|
}
|
|
37
34
|
|
|
38
|
-
const
|
|
35
|
+
const _handleCheckout = () => {
|
|
39
36
|
sendGAEvent('begin_checkout', {
|
|
40
37
|
currency: 'USD',
|
|
41
38
|
value: cmmc.cartTotal,
|
|
@@ -57,13 +54,13 @@ const CartPanel: React.FC<PropsWithChildren & {
|
|
|
57
54
|
value: cmmc.cartTotal,
|
|
58
55
|
currency: 'USD',
|
|
59
56
|
})
|
|
60
|
-
|
|
57
|
+
handleCheckout && handleCheckout()
|
|
61
58
|
}
|
|
62
59
|
|
|
63
60
|
return (
|
|
64
61
|
<div className={cn('border p-4 rounded-lg', className)}>
|
|
65
62
|
{children}
|
|
66
|
-
{
|
|
63
|
+
{showCarousel && <ProductsCarousel items={cmmc.cartItems}/>}
|
|
67
64
|
<div className='mt-2 w-full'>
|
|
68
65
|
{cmmc.cartEmpty ? (
|
|
69
66
|
<p className='text-center mt-4'>No items in cart</p>
|
|
@@ -74,13 +71,13 @@ const CartPanel: React.FC<PropsWithChildren & {
|
|
|
74
71
|
<p className='mt-6 text-right border-t pt-1'>TOTAL: <span className='font-semibold'>{cmmc.cartTotal === 0 ? '0' : formatPrice(cmmc.cartTotal)}</span></p>
|
|
75
72
|
</>)}
|
|
76
73
|
</div>
|
|
77
|
-
{
|
|
74
|
+
{handleCheckout && (<>
|
|
78
75
|
{!cmmc.cartEmpty && (
|
|
79
76
|
<Button
|
|
80
77
|
variant='primary'
|
|
81
78
|
rounded='lg'
|
|
82
|
-
className='mt-12 mx-auto w-full'
|
|
83
|
-
onClick={
|
|
79
|
+
className='mt-12 mx-auto w-full sm:max-w-[220px]'
|
|
80
|
+
onClick={_handleCheckout}
|
|
84
81
|
>
|
|
85
82
|
Checkout
|
|
86
83
|
</Button>
|
|
@@ -13,6 +13,7 @@ import { formatPrice, useCommerce } from '../..'
|
|
|
13
13
|
|
|
14
14
|
import CartPanel from '../cart-panel'
|
|
15
15
|
import BagIcon from './icons/bag-icon'
|
|
16
|
+
import { ChevronRight } from 'lucide-react'
|
|
16
17
|
|
|
17
18
|
const CartAccordian: React.FC<{className?: string}> = observer(({
|
|
18
19
|
className=''
|
|
@@ -22,14 +23,21 @@ const CartAccordian: React.FC<{className?: string}> = observer(({
|
|
|
22
23
|
return (
|
|
23
24
|
<Accordion type="single" collapsible className={className}>
|
|
24
25
|
<AccordionItem value="cart" className='w-full border-b-0'>
|
|
25
|
-
<AccordionTrigger className='!no-underline
|
|
26
|
-
<div className='flex gap-
|
|
27
|
-
<BagIcon className='w-4 h-4 sm:w-6 sm:h-6'/>
|
|
28
|
-
<h5 className='text-sm sm:text-xl
|
|
26
|
+
<AccordionTrigger className='!no-underline group flex justify-between'>
|
|
27
|
+
<div className='flex gap-1 items-center'>
|
|
28
|
+
<BagIcon className='w-4 h-4 relative -top-0.5 sm:w-6 shrink-0 sm:h-6'/>
|
|
29
|
+
<h5 className='text-sm sm:text-xl grow'>
|
|
30
|
+
<span className='group-data-[state=open]:hidden' >Order Total:</span>
|
|
31
|
+
<span className='group-data-[state=closed]:hidden' >Your Order</span>
|
|
32
|
+
</h5>
|
|
33
|
+
</div>
|
|
34
|
+
<div className='flex gap-1 items-center'>
|
|
35
|
+
<h5 className='text-sm sm:text-xl grow truncate'>{formatPrice(cmmc.cartTotal)}</h5>
|
|
36
|
+
<ChevronRight className="h-5 w-5 -mr-2 shrink-0 transition-transform duration-200 group-data-[state=open]:rotate-90" />
|
|
29
37
|
</div>
|
|
30
38
|
</AccordionTrigger>
|
|
31
|
-
<AccordionContent>
|
|
32
|
-
<CartPanel
|
|
39
|
+
<AccordionContent className='data-[state=open]:mb-4'>
|
|
40
|
+
<CartPanel className='w-full'/>
|
|
33
41
|
</AccordionContent>
|
|
34
42
|
</AccordionItem>
|
|
35
43
|
</Accordion>
|
|
@@ -5,16 +5,16 @@ import { ChevronLeft } from 'lucide-react'
|
|
|
5
5
|
import { Button } from '@hanzo/ui/primitives'
|
|
6
6
|
|
|
7
7
|
const CloseButton: React.FC<{
|
|
8
|
-
|
|
8
|
+
close: () => void
|
|
9
9
|
className?: string
|
|
10
10
|
}> = ({
|
|
11
|
-
|
|
11
|
+
close,
|
|
12
12
|
className=''
|
|
13
13
|
}) => (
|
|
14
14
|
<Button
|
|
15
15
|
variant='ghost'
|
|
16
16
|
size='icon'
|
|
17
|
-
onClick={
|
|
17
|
+
onClick={close}
|
|
18
18
|
className={'group ' + className}
|
|
19
19
|
>
|
|
20
20
|
<ChevronLeft className='w-5 h-5 group-hover:scale-110 transition-scale transition-duration-300'/>
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
import React, { type PropsWithChildren } from 'react'
|
|
3
|
+
|
|
4
|
+
import { ScrollArea, StepIndicator } from '@hanzo/ui/primitives'
|
|
5
|
+
import { AuthWidget } from '@hanzo/auth/components'
|
|
6
|
+
|
|
7
|
+
import CartPanel from '../cart-panel'
|
|
8
|
+
import CloseButton from './close-button'
|
|
9
|
+
|
|
10
|
+
const DesktopPanel: React.FC<PropsWithChildren & {
|
|
11
|
+
index: number
|
|
12
|
+
stepNames: string[]
|
|
13
|
+
close:() => void
|
|
14
|
+
className?: string
|
|
15
|
+
}> = ({
|
|
16
|
+
index,
|
|
17
|
+
stepNames,
|
|
18
|
+
close,
|
|
19
|
+
className='',
|
|
20
|
+
children
|
|
21
|
+
}) => (
|
|
22
|
+
<div /* id='DT-GRID' */ className={className}>
|
|
23
|
+
<div className='w-full bg-background flex flex-row items-start justify-end'>
|
|
24
|
+
<ScrollArea className='h-full w-full max-w-[750px] relative flex flex-col items-center justify-start px-6 pt-12 pb-9'>
|
|
25
|
+
<CloseButton close={close} className='absolute top-3 left-3 w-auto h-auto rounded-full bg-level-1 hover:bg-level-2 hover:border-muted p-2' />
|
|
26
|
+
<CartPanel showCarousel className='flex border-none mt-10 w-full max-w-[550px] flex-col' />
|
|
27
|
+
</ScrollArea>
|
|
28
|
+
</div>
|
|
29
|
+
<ScrollArea className='w-full h-full bg-level-1 flex flex-row items-start justify-start overflow-y-auto'>
|
|
30
|
+
<div className='h-full w-full max-w-[750px] relative flex flex-col items-center gap-6 px-8 pb-6 pt-0'>
|
|
31
|
+
<AuthWidget noLogin className='hidden md:flex absolute top-4 right-4 '/>
|
|
32
|
+
<div className='bg-level-1 sticky h-[90px] pb-2 w-full top-0 flex justify-center items-end'>
|
|
33
|
+
<StepIndicator
|
|
34
|
+
dotSizeRem={1.5}
|
|
35
|
+
steps={stepNames}
|
|
36
|
+
currentStep={index}
|
|
37
|
+
className='gap-2 text-base w-pr-70 '
|
|
38
|
+
/>
|
|
39
|
+
</div>
|
|
40
|
+
{children}
|
|
41
|
+
</div>
|
|
42
|
+
</ScrollArea>
|
|
43
|
+
</div>
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
export default DesktopPanel
|
|
@@ -1,32 +1,42 @@
|
|
|
1
1
|
'use client'
|
|
2
|
+
import React, { useState } from 'react'
|
|
2
3
|
|
|
3
|
-
import {
|
|
4
|
-
import { observer } from 'mobx-react-lite'
|
|
5
|
-
|
|
6
|
-
import {
|
|
7
|
-
Dialog,
|
|
8
|
-
DialogPortal,
|
|
9
|
-
ScrollArea,
|
|
10
|
-
} from '@hanzo/ui/primitives'
|
|
11
|
-
import { cn } from '@hanzo/ui/util'
|
|
12
|
-
import { AuthWidget } from '@hanzo/auth/components'
|
|
4
|
+
import { capitalize } from '@hanzo/ui/util'
|
|
13
5
|
|
|
14
6
|
import { useCommerce } from '../..'
|
|
15
7
|
|
|
16
|
-
import
|
|
17
|
-
import
|
|
18
|
-
import
|
|
19
|
-
import Payment from './payment'
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
8
|
+
import type { CheckoutStep } from './steps/types'
|
|
9
|
+
import ShippingInfo from './steps/shipping-info'
|
|
10
|
+
import ThankYou from './steps/thank-you'
|
|
11
|
+
import Payment from './steps/payment'
|
|
12
|
+
|
|
13
|
+
const STEPS = [
|
|
14
|
+
{
|
|
15
|
+
name: 'payment',
|
|
16
|
+
Comp: Payment
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
name: 'delivery',
|
|
20
|
+
Comp: ShippingInfo
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
name: 'done',
|
|
24
|
+
label: 'Done!',
|
|
25
|
+
Comp: ThankYou
|
|
26
|
+
}
|
|
27
|
+
] satisfies CheckoutStep[]
|
|
28
|
+
|
|
29
|
+
const STEP_NAMES = STEPS.map((s) => (s.label ? s.label : capitalize(s.name)))
|
|
30
|
+
|
|
31
|
+
import DesktopCP from './desktop'
|
|
32
|
+
import MobileCP from './mobile'
|
|
23
33
|
|
|
24
34
|
const CheckoutPanel: React.FC<{
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}> =
|
|
28
|
-
|
|
29
|
-
|
|
35
|
+
close: () => void
|
|
36
|
+
className?: string
|
|
37
|
+
}> = ({
|
|
38
|
+
close,
|
|
39
|
+
className=''
|
|
30
40
|
}) => {
|
|
31
41
|
|
|
32
42
|
const cmmc = useCommerce()
|
|
@@ -35,74 +45,64 @@ const CheckoutPanel: React.FC<{
|
|
|
35
45
|
if (!cmmc) {
|
|
36
46
|
return <></>
|
|
37
47
|
}
|
|
38
|
-
|
|
39
|
-
const [
|
|
40
|
-
|
|
48
|
+
const [stepIndex, setStepIndex] = useState<number>(0)
|
|
49
|
+
const [orderId, setOrderId] = useState<string | undefined>(undefined)
|
|
50
|
+
|
|
51
|
+
// Step.name or 'first' or 'next' or 'last'
|
|
52
|
+
const setStep = (name: string): void => {
|
|
41
53
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
54
|
+
if (name === 'first') {
|
|
55
|
+
setStepIndex(0)
|
|
56
|
+
}
|
|
57
|
+
else if (name === 'last') {
|
|
58
|
+
setStepIndex(STEPS.length - 1)
|
|
59
|
+
}
|
|
60
|
+
else if (name === 'next') {
|
|
61
|
+
if (stepIndex < STEPS.length - 2) {
|
|
62
|
+
setStepIndex(stepIndex + 1)
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
throw new Error('CheckoutPanel.setStep(): Attempting to advance past last step!')
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
const indexFound = STEPS.findIndex((el) => (el.name === name))
|
|
70
|
+
if (indexFound !== -1) {
|
|
71
|
+
setStepIndex(indexFound)
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
throw new Error('CheckoutPanel.setStep(): Step named ' + name + ' not found!')
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
64
78
|
|
|
65
|
-
const
|
|
66
|
-
setStep(
|
|
67
|
-
|
|
79
|
+
const _close = () => {
|
|
80
|
+
setStep('first')
|
|
81
|
+
close()
|
|
68
82
|
}
|
|
69
83
|
|
|
84
|
+
const StepToRender = STEPS[stepIndex].Comp
|
|
85
|
+
|
|
70
86
|
return (
|
|
71
|
-
<
|
|
72
|
-
<
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
showProductsCarousel
|
|
90
|
-
/>
|
|
91
|
-
<CartAccordian className='md:hidden flex items-center justify-center py-2 w-full' />
|
|
92
|
-
</ScrollArea>
|
|
93
|
-
</div>
|
|
94
|
-
<ScrollArea className='w-full h-full bg-level-1 flex flex-row items-start justify-start md:overflow-y-auto'>
|
|
95
|
-
<div className='h-full w-full max-w-[750px] relative flex flex-col gap-8 sm:gap-14 px-4 md:px-8 pb-6 pt-6 md:pt-14'>
|
|
96
|
-
<AuthWidget hideLogin className='hidden md:flex absolute top-4 right-4 '/>
|
|
97
|
-
<StepIndicator steps={steps} currentStep={step} className='flex gap-2 mx-auto items-center text-xxs sm:text-base' />
|
|
98
|
-
{steps[step].element}
|
|
99
|
-
</div>
|
|
100
|
-
</ScrollArea>
|
|
101
|
-
</div>
|
|
102
|
-
</div>
|
|
103
|
-
</DialogPortal>
|
|
104
|
-
</Dialog>
|
|
87
|
+
<div /* id='CHECKOUT_PANEL' */ className={className} >
|
|
88
|
+
<DesktopCP
|
|
89
|
+
className='hidden md:flex h-full flex-row justify-center overflow-y-hidden'
|
|
90
|
+
close={_close}
|
|
91
|
+
index={stepIndex}
|
|
92
|
+
stepNames={STEP_NAMES}
|
|
93
|
+
>
|
|
94
|
+
<StepToRender onDone={() => {setStep('next')}} orderId={orderId} setOrderId={setOrderId}/>
|
|
95
|
+
</DesktopCP>
|
|
96
|
+
<MobileCP
|
|
97
|
+
className='md:hidden h-full overflow-y-auto'
|
|
98
|
+
close={_close}
|
|
99
|
+
index={stepIndex}
|
|
100
|
+
stepNames={STEP_NAMES}
|
|
101
|
+
>
|
|
102
|
+
<StepToRender onDone={() => {setStep('next')}} orderId={orderId} setOrderId={setOrderId}/>
|
|
103
|
+
</MobileCP>
|
|
104
|
+
</div>
|
|
105
105
|
)
|
|
106
|
-
}
|
|
106
|
+
}
|
|
107
107
|
|
|
108
108
|
export default CheckoutPanel
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
import React, { type PropsWithChildren } from 'react'
|
|
3
|
+
|
|
4
|
+
import { StepIndicator } from '@hanzo/ui/primitives'
|
|
5
|
+
import { cn } from '@hanzo/ui/util'
|
|
6
|
+
import { AuthWidget } from '@hanzo/auth/components'
|
|
7
|
+
|
|
8
|
+
import CloseButton from './close-button'
|
|
9
|
+
import CartAccordian from './cart-accordian'
|
|
10
|
+
|
|
11
|
+
const MobilePanel: React.FC<PropsWithChildren & {
|
|
12
|
+
index: number
|
|
13
|
+
stepNames: string[]
|
|
14
|
+
close:() => void
|
|
15
|
+
className?: string
|
|
16
|
+
}> = ({
|
|
17
|
+
index,
|
|
18
|
+
stepNames,
|
|
19
|
+
close,
|
|
20
|
+
className='',
|
|
21
|
+
children
|
|
22
|
+
}) => (
|
|
23
|
+
|
|
24
|
+
<div /* id='MOBILE_GRID' */ className={cn('bg-background flex flex-col justify-start px-4', className)}>
|
|
25
|
+
<div className='sticky top-0 w-full flex flex-row justify-between items-start bg-background'>
|
|
26
|
+
<CloseButton close={close} className='rounded-full bg-background hover:bg-background p-1 -ml-3' />
|
|
27
|
+
<StepIndicator
|
|
28
|
+
dotSizeRem={1}
|
|
29
|
+
steps={stepNames}
|
|
30
|
+
currentStep={index}
|
|
31
|
+
className='text-xs font-semibold w-pr-70 mt-3'
|
|
32
|
+
/>
|
|
33
|
+
{/* Need wrapper div since 'noLogin' returns null if no logged in user */}
|
|
34
|
+
<div className='w-10 h-10 flex items-center justify-center'><AuthWidget noLogin className=''/></div>
|
|
35
|
+
</div>
|
|
36
|
+
<CartAccordian className='flex items-center justify-center py-2 mt-2 w-full' />
|
|
37
|
+
{children}
|
|
38
|
+
</div>
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
export default MobilePanel
|
|
@@ -6,11 +6,12 @@ import Visa from './visa'
|
|
|
6
6
|
import DinersClub from './diners-club'
|
|
7
7
|
import Jcb from './jcb'
|
|
8
8
|
|
|
9
|
+
// <span className='hidden sm:flex text-sm'>Secure payments with</span>
|
|
10
|
+
|
|
9
11
|
const PaymentMethods: React.FC = () => {
|
|
10
12
|
return (
|
|
11
13
|
<div className='flex gap-1 items-center text-muted-1'>
|
|
12
14
|
<LockKeyhole className='w-4 h-4'/>
|
|
13
|
-
<span className='hidden sm:flex text-sm'>Secure payments with</span>
|
|
14
15
|
<Amex className='w-9 h-5'/>
|
|
15
16
|
<Discover className='w-9 h-5'/>
|
|
16
17
|
<Mastercard className='w-9 h-5'/>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Button } from '@hanzo/ui/primitives'
|
|
2
|
+
|
|
3
|
+
import PaymentMethods from './cards'
|
|
4
|
+
import type { PropsWithChildren } from 'react'
|
|
5
|
+
|
|
6
|
+
const CCButton: React.FC<PropsWithChildren> = ({
|
|
7
|
+
children
|
|
8
|
+
}) => (
|
|
9
|
+
<Button variant='outline' size='lg' className='flex items-center' >
|
|
10
|
+
<div className='font-sans text-base mr-2 text-muted'>CC</div>
|
|
11
|
+
<PaymentMethods />
|
|
12
|
+
{children}
|
|
13
|
+
</Button>
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
export default CCButton
|
|
@@ -3,7 +3,6 @@ import {
|
|
|
3
3
|
FormControl,
|
|
4
4
|
FormField,
|
|
5
5
|
FormItem,
|
|
6
|
-
FormLabel,
|
|
7
6
|
FormMessage,
|
|
8
7
|
} from '@hanzo/ui/primitives/form'
|
|
9
8
|
import { Input } from '@hanzo/ui/primitives'
|
|
@@ -23,16 +22,16 @@ const ContactInfo: React.FC<{
|
|
|
23
22
|
return (
|
|
24
23
|
<Form {...form}>
|
|
25
24
|
<form className='text-left'>
|
|
26
|
-
<div className='flex gap-1 sm:gap-2'>
|
|
25
|
+
<div className='flex flex-col gap-1 sm:gap-2'>
|
|
27
26
|
<FormField
|
|
28
27
|
control={form.control}
|
|
29
28
|
name='name'
|
|
30
29
|
render={({ field }) => (
|
|
31
|
-
<FormItem className='
|
|
30
|
+
<FormItem className='w-full'>
|
|
32
31
|
<FormControl>
|
|
33
32
|
<Input {...field} className='border-muted-4' placeholder='Full name'/>
|
|
34
33
|
</FormControl>
|
|
35
|
-
<FormMessage />
|
|
34
|
+
<FormMessage className='py-0 space-y-0 !m-0'/>
|
|
36
35
|
</FormItem>
|
|
37
36
|
)}
|
|
38
37
|
/>
|
|
@@ -40,11 +39,11 @@ const ContactInfo: React.FC<{
|
|
|
40
39
|
control={form.control}
|
|
41
40
|
name='email'
|
|
42
41
|
render={({ field }) => (
|
|
43
|
-
<FormItem className='
|
|
42
|
+
<FormItem className='w-full'>
|
|
44
43
|
<FormControl>
|
|
45
44
|
<Input {...field} className='border-muted-4' placeholder='Email'/>
|
|
46
45
|
</FormControl>
|
|
47
|
-
<FormMessage />
|
|
46
|
+
<FormMessage className='py-0 space-y-0 !m-0'/>
|
|
48
47
|
</FormItem>
|
|
49
48
|
)}
|
|
50
49
|
/>
|
|
@@ -12,21 +12,20 @@ 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
|
-
|
|
16
|
-
import
|
|
17
|
-
import {
|
|
15
|
+
|
|
16
|
+
import { useCommerce } from '../../../../service/context'
|
|
17
|
+
import type { TransactionStatus } from '../../../../types'
|
|
18
|
+
import { sendFBEvent, sendGAEvent } from '../../../../util/analytics'
|
|
19
|
+
|
|
20
|
+
import type { StepComponentProps } from '../types'
|
|
18
21
|
|
|
19
22
|
const contactFormSchema = z.object({
|
|
20
23
|
name: z.string().min(1, 'Enter your full name.'),
|
|
21
24
|
email: z.string().email(),
|
|
22
25
|
})
|
|
23
26
|
|
|
24
|
-
const Payment: React.FC<{
|
|
25
|
-
|
|
26
|
-
orderId?: string
|
|
27
|
-
setOrderId: (orderId?: string) => void
|
|
28
|
-
}> = observer(({
|
|
29
|
-
setStep,
|
|
27
|
+
const Payment: React.FC<StepComponentProps> = observer(({
|
|
28
|
+
onDone,
|
|
30
29
|
orderId,
|
|
31
30
|
setOrderId
|
|
32
31
|
}) => {
|
|
@@ -51,7 +50,7 @@ const Payment: React.FC<{
|
|
|
51
50
|
|
|
52
51
|
const storePaymentInfo = async (paymentInfo: any) => {
|
|
53
52
|
const {name, email} = contactForm.getValues()
|
|
54
|
-
let id
|
|
53
|
+
let id: string | undefined = undefined
|
|
55
54
|
if (!orderId) {
|
|
56
55
|
id = await cmmc.createOrder(email, name)
|
|
57
56
|
setOrderId(id)
|
|
@@ -81,34 +80,41 @@ const Payment: React.FC<{
|
|
|
81
80
|
}
|
|
82
81
|
}
|
|
83
82
|
|
|
83
|
+
const groupClx = 'grid w-full grid-cols-3 mx-auto ' +
|
|
84
|
+
'p-0 h-auto overflow-hidden ' +
|
|
85
|
+
'border-2 bg-background border-level-2 md:bg-level-1 md:border-level-3 '
|
|
86
|
+
|
|
87
|
+
const tabClx = 'whitespace-normal h-full text-xs sm:text-base px-1 text-muted ' +
|
|
88
|
+
'data-[state=active]:text-accent data-[state=active]:bg-level-2 md:data-[state=active]:bg-level-3'
|
|
89
|
+
|
|
84
90
|
return (
|
|
85
|
-
<Tabs defaultValue='card' className='w-full
|
|
86
|
-
<TabsList className=
|
|
91
|
+
<Tabs defaultValue='card' className='w-full sm:max-w-[500px] sm:mx-auto'>
|
|
92
|
+
<TabsList className={groupClx}>
|
|
87
93
|
<TabsTrigger
|
|
88
94
|
value='card'
|
|
89
|
-
className=
|
|
95
|
+
className={tabClx}
|
|
90
96
|
disabled={transactionStatus === 'paid' || transactionStatus === 'confirmed'}
|
|
91
97
|
>
|
|
92
|
-
|
|
98
|
+
Card
|
|
93
99
|
</TabsTrigger>
|
|
94
100
|
<TabsTrigger
|
|
95
101
|
value='crypto'
|
|
96
|
-
className=
|
|
102
|
+
className={tabClx}
|
|
97
103
|
disabled={transactionStatus === 'paid' || transactionStatus === 'confirmed'}
|
|
98
104
|
>
|
|
99
|
-
|
|
105
|
+
Wallet
|
|
100
106
|
</TabsTrigger>
|
|
101
107
|
<TabsTrigger
|
|
102
108
|
value='bank'
|
|
103
|
-
className=
|
|
109
|
+
className={tabClx}
|
|
104
110
|
disabled={transactionStatus === 'paid' || transactionStatus === 'confirmed'}
|
|
105
111
|
>
|
|
106
|
-
|
|
112
|
+
Bank Wire
|
|
107
113
|
</TabsTrigger>
|
|
108
114
|
</TabsList>
|
|
109
115
|
<TabsContent value='card'>
|
|
110
116
|
<PayWithCard
|
|
111
|
-
|
|
117
|
+
onDone={onDone}
|
|
112
118
|
transactionStatus={transactionStatus}
|
|
113
119
|
setTransactionStatus={setTransactionStatus}
|
|
114
120
|
storePaymentInfo={storePaymentInfo}
|
|
@@ -117,7 +123,7 @@ const Payment: React.FC<{
|
|
|
117
123
|
</TabsContent>
|
|
118
124
|
<TabsContent value='crypto'>
|
|
119
125
|
<PayWithCrypto
|
|
120
|
-
|
|
126
|
+
onDone={onDone}
|
|
121
127
|
transactionStatus={transactionStatus}
|
|
122
128
|
setTransactionStatus={setTransactionStatus}
|
|
123
129
|
storePaymentInfo={storePaymentInfo}
|
|
@@ -126,7 +132,7 @@ const Payment: React.FC<{
|
|
|
126
132
|
</TabsContent>
|
|
127
133
|
<TabsContent value='bank'>
|
|
128
134
|
<PayWithBankTransfer
|
|
129
|
-
|
|
135
|
+
onDone={onDone}
|
|
130
136
|
storePaymentInfo={storePaymentInfo}
|
|
131
137
|
contactForm={contactForm}
|
|
132
138
|
/>
|
|
@@ -35,7 +35,7 @@ const InfoField: React.FC<{
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
const PayWithBankTransfer: React.FC<{
|
|
38
|
-
|
|
38
|
+
onDone: () => void
|
|
39
39
|
storePaymentInfo: (paymentInfo: any) => Promise<void>
|
|
40
40
|
contactForm: UseFormReturn<{
|
|
41
41
|
name: string
|
|
@@ -45,24 +45,28 @@ const PayWithBankTransfer: React.FC<{
|
|
|
45
45
|
email: string
|
|
46
46
|
}>
|
|
47
47
|
}> = ({
|
|
48
|
-
|
|
48
|
+
onDone,
|
|
49
49
|
storePaymentInfo,
|
|
50
50
|
contactForm
|
|
51
51
|
}) => {
|
|
52
|
+
|
|
52
53
|
const payByBankTransfer = async () => {
|
|
53
|
-
contactForm.handleSubmit(async () => {
|
|
54
|
+
contactForm.handleSubmit( async () => {
|
|
54
55
|
await storePaymentInfo({paymentMethod: 'bank-transfer'})
|
|
55
|
-
|
|
56
|
+
onDone()
|
|
56
57
|
})()
|
|
57
58
|
}
|
|
58
59
|
|
|
60
|
+
const groupClx = 'border-2 bg-background border-level-2 md:bg-level-1 md:border-level-3 p-0 h-auto overflow-hidden rounded-md'
|
|
61
|
+
const tabClx = 'text-muted data-[state=active]:text-accent data-[state=active]:bg-level-2 md:data-[state=active]:bg-level-3'
|
|
62
|
+
|
|
59
63
|
return (
|
|
60
64
|
<div className='flex flex-col gap-2 mt-6'>
|
|
61
65
|
<ContactInfo form={contactForm}/>
|
|
62
66
|
<Tabs defaultValue="usd" className='w-full mx-auto max-w-[50rem]'>
|
|
63
|
-
<TabsList className="grid w-full grid-cols-2 max-w-[15rem] mx-auto
|
|
64
|
-
<TabsTrigger value="usd">USD</TabsTrigger>
|
|
65
|
-
<TabsTrigger value="eur">EUR/GBP</TabsTrigger>
|
|
67
|
+
<TabsList className={"grid w-full grid-cols-2 max-w-[15rem] mx-auto " + groupClx}>
|
|
68
|
+
<TabsTrigger className={tabClx} value="usd">USD</TabsTrigger>
|
|
69
|
+
<TabsTrigger className={tabClx} value="eur">EUR/GBP</TabsTrigger>
|
|
66
70
|
</TabsList>
|
|
67
71
|
<TabsContent value="usd">
|
|
68
72
|
<div className='flex flex-col gap-4 w-full'>
|