@hanzo/commerce 4.6.0 → 4.8.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 +13 -12
- package/components/cart-panel/products-carousel.tsx +60 -0
- package/components/checkout-panel/cart-accordian.tsx +47 -0
- package/components/checkout-panel/close-button.tsx +3 -3
- package/components/checkout-panel/desktop.tsx +46 -0
- package/components/checkout-panel/index.tsx +86 -97
- package/components/checkout-panel/mobile.tsx +41 -0
- package/components/checkout-panel/{payment/payment-methods → steps/payment/cards}/index.tsx +3 -2
- package/components/checkout-panel/steps/payment/cc-button.tsx +17 -0
- package/components/checkout-panel/{payment → steps/payment}/contact-info.tsx +7 -10
- package/components/checkout-panel/{payment → steps/payment}/index.tsx +27 -21
- package/components/checkout-panel/{payment → steps/payment}/pay-with-bank-transfer.tsx +14 -10
- package/components/checkout-panel/steps/payment/pay-with-card.tsx +246 -0
- package/components/checkout-panel/{payment → steps/payment}/pay-with-crypto.tsx +7 -19
- package/components/checkout-panel/{shipping-info.tsx → steps/shipping-info.tsx} +15 -22
- 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 +5 -3
- package/service/impls/standalone/actual-line-item.ts +7 -1
- package/types/product.ts +4 -0
- package/{components/checkout-panel → util}/countries.ts +1 -1
- package/components/checkout-panel/payment/pay-with-card.tsx +0 -217
- 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}
|
|
@@ -10,29 +10,29 @@ import { formatPrice } from '../../util'
|
|
|
10
10
|
|
|
11
11
|
import CartLineItem from './cart-line-item'
|
|
12
12
|
import { sendFBEvent, sendGAEvent } from '../../util/analytics'
|
|
13
|
+
import ProductsCarousel from './products-carousel'
|
|
13
14
|
|
|
14
15
|
const CartPanel: React.FC<PropsWithChildren & {
|
|
15
16
|
className?: string
|
|
16
17
|
isMobile?: boolean,
|
|
17
|
-
|
|
18
|
-
|
|
18
|
+
showCarousel?: boolean
|
|
19
|
+
/** if not provided, checkout button will not be shown */
|
|
20
|
+
handleCheckout?: () => void
|
|
19
21
|
}> = observer(({
|
|
20
22
|
/** Children is the heading area. */
|
|
21
23
|
children,
|
|
22
24
|
className='',
|
|
23
25
|
isMobile=false,
|
|
24
|
-
|
|
25
|
-
|
|
26
|
+
showCarousel=false,
|
|
27
|
+
handleCheckout,
|
|
26
28
|
}) => {
|
|
27
|
-
|
|
28
|
-
* Needs refactor with context or so.
|
|
29
|
-
**/
|
|
29
|
+
|
|
30
30
|
const cmmc = useCommerce()
|
|
31
31
|
if (!cmmc) {
|
|
32
32
|
return <div />
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
const
|
|
35
|
+
const _handleCheckout = () => {
|
|
36
36
|
sendGAEvent('begin_checkout', {
|
|
37
37
|
currency: 'USD',
|
|
38
38
|
value: cmmc.cartTotal,
|
|
@@ -54,12 +54,13 @@ const CartPanel: React.FC<PropsWithChildren & {
|
|
|
54
54
|
value: cmmc.cartTotal,
|
|
55
55
|
currency: 'USD',
|
|
56
56
|
})
|
|
57
|
-
|
|
57
|
+
handleCheckout && handleCheckout()
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
return (
|
|
61
61
|
<div className={cn('border p-4 rounded-lg', className)}>
|
|
62
62
|
{children}
|
|
63
|
+
{showCarousel && <ProductsCarousel items={cmmc.cartItems}/>}
|
|
63
64
|
<div className='mt-2 w-full'>
|
|
64
65
|
{cmmc.cartEmpty ? (
|
|
65
66
|
<p className='text-center mt-4'>No items in cart</p>
|
|
@@ -70,13 +71,13 @@ const CartPanel: React.FC<PropsWithChildren & {
|
|
|
70
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>
|
|
71
72
|
</>)}
|
|
72
73
|
</div>
|
|
73
|
-
{
|
|
74
|
+
{handleCheckout && (<>
|
|
74
75
|
{!cmmc.cartEmpty && (
|
|
75
76
|
<Button
|
|
76
77
|
variant='primary'
|
|
77
78
|
rounded='lg'
|
|
78
|
-
className='mt-12 mx-auto w-full'
|
|
79
|
-
onClick={
|
|
79
|
+
className='mt-12 mx-auto w-full sm:max-w-[220px]'
|
|
80
|
+
onClick={_handleCheckout}
|
|
80
81
|
>
|
|
81
82
|
Checkout
|
|
82
83
|
</Button>
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import Spline from '@splinetool/react-spline'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
Carousel,
|
|
5
|
+
CarouselContent,
|
|
6
|
+
CarouselItem,
|
|
7
|
+
CarouselPrevious,
|
|
8
|
+
CarouselNext
|
|
9
|
+
} from '@hanzo/ui/primitives'
|
|
10
|
+
import {
|
|
11
|
+
VideoBlockComponent,
|
|
12
|
+
ImageBlockComponent,
|
|
13
|
+
type ImageBlock,
|
|
14
|
+
type Block,
|
|
15
|
+
type VideoBlock
|
|
16
|
+
} from '@hanzo/ui/blocks'
|
|
17
|
+
|
|
18
|
+
import type { LineItem } from '../../types'
|
|
19
|
+
|
|
20
|
+
// Carousel content hierarchy: 3D > MP4 > Image
|
|
21
|
+
const ProductsCarousel: React.FC<{
|
|
22
|
+
items: LineItem[]
|
|
23
|
+
}> = ({
|
|
24
|
+
items,
|
|
25
|
+
}) => (
|
|
26
|
+
<Carousel options={{ loop: true }} className='w-full max-w-sm mx-auto px-2' >
|
|
27
|
+
<CarouselContent>
|
|
28
|
+
{items.map(({title, img, video, animation}, index) => (
|
|
29
|
+
<CarouselItem key={index}>
|
|
30
|
+
<div className='flex aspect-square items-center justify-center p-6'>
|
|
31
|
+
{animation ? (
|
|
32
|
+
<Spline
|
|
33
|
+
scene={animation}
|
|
34
|
+
className='!aspect-[12/10] pointer-events-none !w-auto !h-auto'
|
|
35
|
+
/>
|
|
36
|
+
) : video ? (
|
|
37
|
+
<VideoBlockComponent
|
|
38
|
+
block={{blockType: 'video', ...video} satisfies VideoBlock as Block}
|
|
39
|
+
/>
|
|
40
|
+
) : (
|
|
41
|
+
<ImageBlockComponent
|
|
42
|
+
block={{
|
|
43
|
+
blockType: 'image',
|
|
44
|
+
src: img ?? '',
|
|
45
|
+
alt: title + ' image',
|
|
46
|
+
dim: { w: 250, h: 250 }
|
|
47
|
+
} satisfies ImageBlock as Block}
|
|
48
|
+
className='m-auto'
|
|
49
|
+
/>
|
|
50
|
+
)}
|
|
51
|
+
</div>
|
|
52
|
+
</CarouselItem>
|
|
53
|
+
))}
|
|
54
|
+
</CarouselContent>
|
|
55
|
+
<CarouselPrevious />
|
|
56
|
+
<CarouselNext />
|
|
57
|
+
</Carousel>
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
export default ProductsCarousel
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
import React from 'react'
|
|
3
|
+
import { observer } from 'mobx-react-lite'
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
Accordion,
|
|
7
|
+
AccordionContent,
|
|
8
|
+
AccordionItem,
|
|
9
|
+
AccordionTrigger,
|
|
10
|
+
} from '@hanzo/ui/primitives'
|
|
11
|
+
|
|
12
|
+
import { formatPrice, useCommerce } from '../..'
|
|
13
|
+
|
|
14
|
+
import CartPanel from '../cart-panel'
|
|
15
|
+
import BagIcon from './icons/bag-icon'
|
|
16
|
+
import { ChevronRight } from 'lucide-react'
|
|
17
|
+
|
|
18
|
+
const CartAccordian: React.FC<{className?: string}> = observer(({
|
|
19
|
+
className=''
|
|
20
|
+
}) => {
|
|
21
|
+
|
|
22
|
+
const cmmc = useCommerce()
|
|
23
|
+
return (
|
|
24
|
+
<Accordion type="single" collapsible className={className}>
|
|
25
|
+
<AccordionItem value="cart" className='w-full border-b-0'>
|
|
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" />
|
|
37
|
+
</div>
|
|
38
|
+
</AccordionTrigger>
|
|
39
|
+
<AccordionContent className='data-[state=open]:mb-4'>
|
|
40
|
+
<CartPanel className='w-full'/>
|
|
41
|
+
</AccordionContent>
|
|
42
|
+
</AccordionItem>
|
|
43
|
+
</Accordion>
|
|
44
|
+
)
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
export default CartAccordian
|
|
@@ -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,34 +1,42 @@
|
|
|
1
1
|
'use client'
|
|
2
|
+
import React, { useState } from 'react'
|
|
2
3
|
|
|
3
|
-
import {
|
|
4
|
-
import { observer } from 'mobx-react-lite'
|
|
4
|
+
import { capitalize } from '@hanzo/ui/util'
|
|
5
5
|
|
|
6
|
-
import {
|
|
7
|
-
Accordion,
|
|
8
|
-
AccordionContent,
|
|
9
|
-
AccordionItem,
|
|
10
|
-
AccordionTrigger,
|
|
11
|
-
Dialog,
|
|
12
|
-
DialogPortal,
|
|
13
|
-
} from '@hanzo/ui/primitives'
|
|
14
|
-
import { cn } from '@hanzo/ui/util'
|
|
15
|
-
import { AuthWidget } from '@hanzo/auth/components'
|
|
6
|
+
import { useCommerce } from '../..'
|
|
16
7
|
|
|
17
|
-
import
|
|
18
|
-
import
|
|
19
|
-
import
|
|
20
|
-
import Payment from './payment'
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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'
|
|
25
33
|
|
|
26
34
|
const CheckoutPanel: React.FC<{
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}> =
|
|
30
|
-
|
|
31
|
-
|
|
35
|
+
close: () => void
|
|
36
|
+
className?: string
|
|
37
|
+
}> = ({
|
|
38
|
+
close,
|
|
39
|
+
className=''
|
|
32
40
|
}) => {
|
|
33
41
|
|
|
34
42
|
const cmmc = useCommerce()
|
|
@@ -37,83 +45,64 @@ const CheckoutPanel: React.FC<{
|
|
|
37
45
|
if (!cmmc) {
|
|
38
46
|
return <></>
|
|
39
47
|
}
|
|
40
|
-
|
|
41
|
-
const [
|
|
42
|
-
const [orderId, setOrderId] = useState<string>()
|
|
48
|
+
const [stepIndex, setStepIndex] = useState<number>(0)
|
|
49
|
+
const [orderId, setOrderId] = useState<string | undefined>(undefined)
|
|
43
50
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
label: 'Payment',
|
|
47
|
-
element: (
|
|
48
|
-
<Payment
|
|
49
|
-
orderId={orderId}
|
|
50
|
-
setOrderId={setOrderId}
|
|
51
|
-
setStep={setStep}
|
|
52
|
-
/>
|
|
53
|
-
)}
|
|
54
|
-
,
|
|
55
|
-
2: {
|
|
56
|
-
label: 'Delivery',
|
|
57
|
-
element: (
|
|
58
|
-
<ShippingInfo orderId={orderId} setStep={setStep}/>
|
|
59
|
-
)},
|
|
60
|
-
3: {
|
|
61
|
-
label: 'Done!',
|
|
62
|
-
element: (
|
|
63
|
-
<ThankYou/>
|
|
64
|
-
)}
|
|
65
|
-
} as Record<number, {label: string, element: JSX.Element}>
|
|
51
|
+
// Step.name or 'first' or 'next' or 'last'
|
|
52
|
+
const setStep = (name: string): void => {
|
|
66
53
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
+
}
|
|
78
|
+
|
|
79
|
+
const _close = () => {
|
|
80
|
+
setStep('first')
|
|
81
|
+
close()
|
|
70
82
|
}
|
|
71
83
|
|
|
72
|
-
|
|
73
|
-
<Dialog open={open}>
|
|
74
|
-
<DialogPortal>
|
|
75
|
-
<div id='PORTAL_OUTER'
|
|
76
|
-
className={cn(
|
|
77
|
-
'fixed top-0 shadow-lg ',
|
|
78
|
-
'animate-in data-[state=open]:fade-in-90 data-[state=open]:slide-in-from-bottom-10',
|
|
79
|
-
'sm:zoom-in-90 data-[state=open]:sm:slide-in-from-bottom-0',
|
|
80
|
-
'!max-w-none w-full h-full min-h-screen bg-transparent backdrop-blur-sm z-50'
|
|
81
|
-
)}
|
|
82
|
-
>
|
|
83
|
-
<div id='GRID' className='grid grid-cols-1 md:grid-cols-2 justify-center h-full overflow-y-auto md:overflow-y-hidden'>
|
|
84
|
-
<div className='bg-background flex flex-row items-start justify-end'>
|
|
85
|
-
<div className='h-full w-full max-w-[750px] relative flex flex-col items-center justify-start px-6 pt-10 pb-0 md:pb-9'>
|
|
86
|
-
<CloseButton onClose={onClose} className='absolute top-3 left-3 w-auto h-auto rounded-full bg-level-1 hover:bg-level-2 hover:border-muted p-2' />
|
|
87
|
-
<AuthWidget hideLogin className='flex md:hidden absolute top-3 right-3'/>
|
|
88
|
-
<CartPanel noCheckout className='border border-muted-2 mt-10 w-full max-w-[550px] hidden md:flex'/>
|
|
84
|
+
const StepToRender = STEPS[stepIndex].Comp
|
|
89
85
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
{steps[step].element}
|
|
110
|
-
</div>
|
|
111
|
-
</div>
|
|
112
|
-
</div>
|
|
113
|
-
</div>
|
|
114
|
-
</DialogPortal>
|
|
115
|
-
</Dialog>
|
|
86
|
+
return (
|
|
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>
|
|
116
105
|
)
|
|
117
|
-
}
|
|
106
|
+
}
|
|
118
107
|
|
|
119
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
|
-
<div className='flex gap-1 items-center'>
|
|
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,17 +22,16 @@ const ContactInfo: React.FC<{
|
|
|
23
22
|
return (
|
|
24
23
|
<Form {...form}>
|
|
25
24
|
<form className='text-left'>
|
|
26
|
-
<div className='flex flex-col sm:
|
|
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='
|
|
32
|
-
<FormLabel>Full name</FormLabel>
|
|
30
|
+
<FormItem className='w-full'>
|
|
33
31
|
<FormControl>
|
|
34
|
-
<Input {...field} className='border-muted-4'/>
|
|
32
|
+
<Input {...field} className='border-muted-4' placeholder='Full name'/>
|
|
35
33
|
</FormControl>
|
|
36
|
-
<FormMessage />
|
|
34
|
+
<FormMessage className='py-0 space-y-0 !m-0'/>
|
|
37
35
|
</FormItem>
|
|
38
36
|
)}
|
|
39
37
|
/>
|
|
@@ -41,12 +39,11 @@ const ContactInfo: React.FC<{
|
|
|
41
39
|
control={form.control}
|
|
42
40
|
name='email'
|
|
43
41
|
render={({ field }) => (
|
|
44
|
-
<FormItem className='
|
|
45
|
-
<FormLabel>Email</FormLabel>
|
|
42
|
+
<FormItem className='w-full'>
|
|
46
43
|
<FormControl>
|
|
47
|
-
<Input {...field} className='border-muted-4'/>
|
|
44
|
+
<Input {...field} className='border-muted-4' placeholder='Email'/>
|
|
48
45
|
</FormControl>
|
|
49
|
-
<FormMessage />
|
|
46
|
+
<FormMessage className='py-0 space-y-0 !m-0'/>
|
|
50
47
|
</FormItem>
|
|
51
48
|
)}
|
|
52
49
|
/>
|