@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.
Files changed (32) hide show
  1. package/components/add-to-cart-widget.tsx +2 -2
  2. package/components/cart-panel/index.tsx +13 -12
  3. package/components/cart-panel/products-carousel.tsx +60 -0
  4. package/components/checkout-panel/cart-accordian.tsx +47 -0
  5. package/components/checkout-panel/close-button.tsx +3 -3
  6. package/components/checkout-panel/desktop.tsx +46 -0
  7. package/components/checkout-panel/index.tsx +86 -97
  8. package/components/checkout-panel/mobile.tsx +41 -0
  9. package/components/checkout-panel/{payment/payment-methods → steps/payment/cards}/index.tsx +3 -2
  10. package/components/checkout-panel/steps/payment/cc-button.tsx +17 -0
  11. package/components/checkout-panel/{payment → steps/payment}/contact-info.tsx +7 -10
  12. package/components/checkout-panel/{payment → steps/payment}/index.tsx +27 -21
  13. package/components/checkout-panel/{payment → steps/payment}/pay-with-bank-transfer.tsx +14 -10
  14. package/components/checkout-panel/steps/payment/pay-with-card.tsx +246 -0
  15. package/components/checkout-panel/{payment → steps/payment}/pay-with-crypto.tsx +7 -19
  16. package/components/checkout-panel/{shipping-info.tsx → steps/shipping-info.tsx} +15 -22
  17. package/components/checkout-panel/{thank-you.tsx → steps/thank-you.tsx} +4 -1
  18. package/components/checkout-panel/steps/types.ts +14 -0
  19. package/package.json +5 -3
  20. package/service/impls/standalone/actual-line-item.ts +7 -1
  21. package/types/product.ts +4 -0
  22. package/{components/checkout-panel → util}/countries.ts +1 -1
  23. package/components/checkout-panel/payment/pay-with-card.tsx +0 -217
  24. package/components/checkout-panel/step-indicator.tsx +0 -30
  25. package/components/checkout-panel/step.ts +0 -11
  26. /package/components/checkout-panel/{confirm-order.tsx → _confirm-order.tsx_unused} +0 -0
  27. /package/components/checkout-panel/{payment/payment-methods → steps/payment/cards}/amex.tsx +0 -0
  28. /package/components/checkout-panel/{payment/payment-methods → steps/payment/cards}/diners-club.tsx +0 -0
  29. /package/components/checkout-panel/{payment/payment-methods → steps/payment/cards}/discover.tsx +0 -0
  30. /package/components/checkout-panel/{payment/payment-methods → steps/payment/cards}/jcb.tsx +0 -0
  31. /package/components/checkout-panel/{payment/payment-methods → steps/payment/cards}/mastercard.tsx +0 -0
  32. /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 text-primary-fg ' + digitClx} >{item.quantity}</div>
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
- noCheckout?: boolean
18
- onCheckoutOpen?: () => void
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
- noCheckout=false,
25
- onCheckoutOpen,
26
+ showCarousel=false,
27
+ handleCheckout,
26
28
  }) => {
27
- /* TODO: onCheckoutOpen is a hackish way fix a bug with multiple dialog opened at the same time.
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 showCheckout = () => {
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
- onCheckoutOpen && onCheckoutOpen()
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
- {!noCheckout && (<>
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={showCheckout}
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
- onClose: () => void
8
+ close: () => void
9
9
  className?: string
10
10
  }> = ({
11
- onClose,
11
+ close,
12
12
  className=''
13
13
  }) => (
14
14
  <Button
15
15
  variant='ghost'
16
16
  size='icon'
17
- onClick={onClose}
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 { useState } from 'react'
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 ShippingInfo from './shipping-info'
18
- import ThankYou from './thank-you'
19
- import CartPanel from '../cart-panel'
20
- import Payment from './payment'
21
- import CloseButton from './close-button'
22
- import StepIndicator from './step-indicator'
23
- import BagIcon from './icons/bag-icon'
24
- import { formatPrice, useCommerce } from '../..'
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
- open: boolean
28
- onCheckoutClose: () => void
29
- }> = observer(({
30
- open,
31
- onCheckoutClose
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 [step, setStep] = useState<number>(1)
42
- const [orderId, setOrderId] = useState<string>()
48
+ const [stepIndex, setStepIndex] = useState<number>(0)
49
+ const [orderId, setOrderId] = useState<string | undefined>(undefined)
43
50
 
44
- const steps = {
45
- 1: {
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
- const onClose = () => {
68
- setStep(1)
69
- onCheckoutClose()
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
- return (
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
- <Accordion type="single" collapsible className='flex items-center justify-center py-2 w-full md:hidden'>
91
- <AccordionItem value="cart" className='w-full border-b-0'>
92
- <AccordionTrigger className='!no-underline'>
93
- <div className='flex gap-4 items-center'>
94
- <BagIcon className='w-4 h-4 sm:w-6 sm:h-6'/>
95
- <h5 className='text-sm sm:text-xl truncate'>Order Summary {formatPrice(cmmc.cartTotal)}</h5>
96
- </div>
97
- </AccordionTrigger>
98
- <AccordionContent>
99
- <CartPanel noCheckout className='border-none w-full'/>
100
- </AccordionContent>
101
- </AccordionItem>
102
- </Accordion>
103
- </div>
104
- </div>
105
- <div className='bg-level-1 flex flex-row items-start justify-start md:overflow-y-auto'>
106
- <div className='h-full w-full max-w-[750px] relative flex flex-col gap-8 sm:gap-14 px-8 pb-6 pt-6 md:pt-14'>
107
- <AuthWidget hideLogin className='hidden md:flex absolute top-4 right-4 '/>
108
- <StepIndicator steps={steps} currentStep={step} className='flex gap-2 mx-auto items-center text-xxs sm:text-base' />
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:flex-row 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='space-y-1 w-full'>
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='space-y-1 w-full'>
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
  />