@hanzo/commerce 1.1.9 → 1.1.11
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/cart.tsx +17 -20
- package/components/checkout/confirm-order.tsx +142 -0
- package/components/checkout/contact-info.tsx +89 -0
- package/components/checkout/countries.ts +196 -0
- package/components/checkout/icons/btc.tsx +11 -0
- package/components/checkout/icons/eth.tsx +20 -0
- package/components/checkout/icons/usdt.tsx +13 -0
- package/components/checkout/index.tsx +134 -0
- package/components/checkout/pay-by-bank-transfer.tsx +83 -0
- package/components/checkout/pay-with-crypto.tsx +172 -0
- package/components/checkout/shipping-info.tsx +208 -0
- package/components/checkout/thank-you.tsx +15 -0
- package/components/facets-widget.tsx +5 -2
- package/components/index.ts +1 -0
- package/package.json +5 -2
package/components/cart.tsx
CHANGED
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
'use client'
|
|
2
|
-
import React, { type PropsWithChildren } from 'react'
|
|
3
|
-
import { useRouter } from 'next/navigation'
|
|
2
|
+
import React, { useState, type PropsWithChildren } from 'react'
|
|
4
3
|
import { observer } from 'mobx-react-lite'
|
|
5
4
|
|
|
6
5
|
import { Button } from '@hanzo/ui/primitives'
|
|
7
6
|
import { cn } from '@hanzo/ui/util'
|
|
8
|
-
import { useAuth } from '@hanzo/auth/service'
|
|
9
7
|
|
|
10
8
|
import { useCommerce } from '../service/context'
|
|
11
9
|
import { formatPrice } from '../util'
|
|
12
10
|
|
|
13
11
|
import CartLineItem from './cart-line-item'
|
|
12
|
+
import { Checkout } from '.'
|
|
14
13
|
|
|
15
14
|
const Cart: React.FC<PropsWithChildren & {
|
|
16
15
|
className?: string
|
|
@@ -23,31 +22,29 @@ const Cart: React.FC<PropsWithChildren & {
|
|
|
23
22
|
isMobile=false,
|
|
24
23
|
hideCheckout=false
|
|
25
24
|
}) => {
|
|
25
|
+
const [isCheckoutOpen, setIsCheckoutOpen] = useState(false)
|
|
26
26
|
const cmmc = useCommerce()
|
|
27
|
-
|
|
28
|
-
const auth = useAuth()
|
|
29
|
-
|
|
27
|
+
|
|
30
28
|
return (
|
|
31
29
|
<div className={cn('border p-4 rounded-lg', className)}>
|
|
32
30
|
{children}
|
|
33
|
-
<div className='mt-2'>
|
|
31
|
+
<div className='mt-2 w-full'>
|
|
34
32
|
{!!children && <div className='h-[1px] w-pr-80 mb-4 mx-auto bg-muted-3'/>}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
33
|
+
{cmmc.cartItems.length === 0 ? (
|
|
34
|
+
<p className='text-center mt-4'>No items in cart</p>
|
|
35
|
+
) : (<>
|
|
36
|
+
{cmmc.cartItems.map((item, i) => (
|
|
37
|
+
<CartLineItem isMobile={isMobile} item={item} key={item.sku} className='mb-2 md:mb-3'/>
|
|
38
|
+
))}
|
|
39
|
+
<p className='mt-6 text-right border-t pt-1'>TOTAL: <span className='font-semibold'>{cmmc.cartTotal === 0 ? '0' : formatPrice(cmmc.cartTotal)}</span></p>
|
|
40
|
+
</>)}
|
|
43
41
|
</div>
|
|
44
|
-
{
|
|
42
|
+
{!hideCheckout && (
|
|
45
43
|
<>
|
|
46
|
-
{
|
|
47
|
-
<Button size='
|
|
48
|
-
) : (
|
|
49
|
-
<Button size='lg' variant='secondary' rounded='lg' className='mt-12 mx-auto' onClick={() => router.push('/checkout')}>Checkout</Button>
|
|
44
|
+
{cmmc.cartItems.length > 0 && (
|
|
45
|
+
<Button size='lg' variant='secondary' rounded='lg' className='mt-12 mx-auto' onClick={() => setIsCheckoutOpen(!isCheckoutOpen)}>Checkout</Button>
|
|
50
46
|
)}
|
|
47
|
+
{isCheckoutOpen && <Checkout toggleCheckout={() => setIsCheckoutOpen(!isCheckoutOpen)}/>}
|
|
51
48
|
</>
|
|
52
49
|
)}
|
|
53
50
|
</div>
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { ApplyTypography, Button } from '@hanzo/ui/primitives'
|
|
4
|
+
import { BookUser, Lock, User } from 'lucide-react'
|
|
5
|
+
import { EnhHeadingBlockComponent, type EnhHeadingBlock } from '@hanzo/ui/blocks'
|
|
6
|
+
import type { UseFormReturn } from 'react-hook-form'
|
|
7
|
+
|
|
8
|
+
const ConfirmOrder: React.FC<{
|
|
9
|
+
paymentForm: UseFormReturn<{
|
|
10
|
+
name: string;
|
|
11
|
+
email: string;
|
|
12
|
+
cardName: string;
|
|
13
|
+
cardNumber: string;
|
|
14
|
+
cardExpiryDate: string;
|
|
15
|
+
cardCvc: string;
|
|
16
|
+
}, any, {
|
|
17
|
+
name: string;
|
|
18
|
+
email: string;
|
|
19
|
+
cardName: string;
|
|
20
|
+
cardNumber: string;
|
|
21
|
+
cardExpiryDate: string;
|
|
22
|
+
cardCvc: string;
|
|
23
|
+
}>,
|
|
24
|
+
shippingForm: UseFormReturn<{
|
|
25
|
+
firstName: string;
|
|
26
|
+
lastName: string;
|
|
27
|
+
addressLine1: string;
|
|
28
|
+
zipCode: string;
|
|
29
|
+
city: string;
|
|
30
|
+
country: string;
|
|
31
|
+
addressLine2?: string | undefined;
|
|
32
|
+
state?: string | undefined;
|
|
33
|
+
}, any, {
|
|
34
|
+
firstName: string;
|
|
35
|
+
lastName: string;
|
|
36
|
+
addressLine1: string;
|
|
37
|
+
zipCode: string;
|
|
38
|
+
city: string;
|
|
39
|
+
country: string;
|
|
40
|
+
addressLine2?: string | undefined;
|
|
41
|
+
state?: string | undefined;
|
|
42
|
+
}>,
|
|
43
|
+
setStep: (step: number) => void,
|
|
44
|
+
onPayClick: () => void
|
|
45
|
+
}> = ({
|
|
46
|
+
paymentForm,
|
|
47
|
+
shippingForm,
|
|
48
|
+
setStep,
|
|
49
|
+
onPayClick
|
|
50
|
+
}) => {
|
|
51
|
+
|
|
52
|
+
const paymentFormValues = paymentForm.getValues()
|
|
53
|
+
const shippingFormValues = shippingForm.getValues()
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<div className='flex flex-col gap-6'>
|
|
57
|
+
<div className='flex gap-4 items-center'>
|
|
58
|
+
<User />
|
|
59
|
+
<EnhHeadingBlockComponent block={{blockType: 'enh-heading',
|
|
60
|
+
heading: { text: `Contact Details`, level: 4 },
|
|
61
|
+
} as EnhHeadingBlock}/>
|
|
62
|
+
</div>
|
|
63
|
+
<EnhHeadingBlockComponent block={{blockType: 'enh-heading',
|
|
64
|
+
heading: { text: 'Contact name', level: 6 },
|
|
65
|
+
byline: { text: paymentFormValues.name, level: 0 },
|
|
66
|
+
} as EnhHeadingBlock}/>
|
|
67
|
+
<EnhHeadingBlockComponent block={{blockType: 'enh-heading',
|
|
68
|
+
heading: { text: 'Contact email', level: 6 },
|
|
69
|
+
byline: { text: paymentFormValues.email, level: 0 },
|
|
70
|
+
} as EnhHeadingBlock}/>
|
|
71
|
+
|
|
72
|
+
<div className='flex gap-4 items-center'>
|
|
73
|
+
<ApplyTypography>
|
|
74
|
+
<h4 className='flex gap-4 items-center'><Lock /> Payment Info</h4>
|
|
75
|
+
<p>Guaranteed safe & secure checkout.</p>
|
|
76
|
+
</ApplyTypography>
|
|
77
|
+
</div>
|
|
78
|
+
<EnhHeadingBlockComponent block={{blockType: 'enh-heading',
|
|
79
|
+
heading: { text: 'Name on card', level: 6 },
|
|
80
|
+
byline: { text: paymentFormValues.cardName, level: 0 },
|
|
81
|
+
} as EnhHeadingBlock}/>
|
|
82
|
+
<EnhHeadingBlockComponent block={{blockType: 'enh-heading',
|
|
83
|
+
heading: { text: 'Card number', level: 6 },
|
|
84
|
+
byline: { text: paymentFormValues.cardNumber, level: 0 },
|
|
85
|
+
} as EnhHeadingBlock}/>
|
|
86
|
+
<EnhHeadingBlockComponent block={{blockType: 'enh-heading',
|
|
87
|
+
heading: { text: 'Card expiry date', level: 6 },
|
|
88
|
+
byline: { text: paymentFormValues.cardExpiryDate, level: 0 },
|
|
89
|
+
} as EnhHeadingBlock}/>
|
|
90
|
+
<EnhHeadingBlockComponent block={{blockType: 'enh-heading',
|
|
91
|
+
heading: { text: 'Card CVC', level: 6 },
|
|
92
|
+
byline: { text: paymentFormValues.cardCvc, level: 0 },
|
|
93
|
+
} as EnhHeadingBlock}/>
|
|
94
|
+
|
|
95
|
+
<div className='flex gap-4 items-center'>
|
|
96
|
+
<BookUser />
|
|
97
|
+
<EnhHeadingBlockComponent block={{blockType: 'enh-heading',
|
|
98
|
+
heading: { text: `Shipping address`, level: 4 },
|
|
99
|
+
} as EnhHeadingBlock}/>
|
|
100
|
+
</div>
|
|
101
|
+
<EnhHeadingBlockComponent block={{blockType: 'enh-heading',
|
|
102
|
+
heading: { text: 'First name', level: 6 },
|
|
103
|
+
byline: { text: shippingFormValues.firstName, level: 0 },
|
|
104
|
+
} as EnhHeadingBlock}/>
|
|
105
|
+
<EnhHeadingBlockComponent block={{blockType: 'enh-heading',
|
|
106
|
+
heading: { text: 'Last name', level: 6 },
|
|
107
|
+
byline: { text: shippingFormValues.lastName, level: 0 },
|
|
108
|
+
} as EnhHeadingBlock}/>
|
|
109
|
+
<EnhHeadingBlockComponent block={{blockType: 'enh-heading',
|
|
110
|
+
heading: { text: 'Address line 1', level: 6 },
|
|
111
|
+
byline: { text: shippingFormValues.addressLine1, level: 0 },
|
|
112
|
+
} as EnhHeadingBlock}/>
|
|
113
|
+
<EnhHeadingBlockComponent block={{blockType: 'enh-heading',
|
|
114
|
+
heading: { text: 'Address line 2 (optional)', level: 6 },
|
|
115
|
+
byline: { text: shippingFormValues.addressLine2, level: 0 },
|
|
116
|
+
} as EnhHeadingBlock}/>
|
|
117
|
+
<EnhHeadingBlockComponent block={{blockType: 'enh-heading',
|
|
118
|
+
heading: { text: 'Zip code', level: 6 },
|
|
119
|
+
byline: { text: shippingFormValues.zipCode, level: 0 },
|
|
120
|
+
} as EnhHeadingBlock}/>
|
|
121
|
+
<EnhHeadingBlockComponent block={{blockType: 'enh-heading',
|
|
122
|
+
heading: { text: 'City', level: 6 },
|
|
123
|
+
byline: { text: shippingFormValues.city, level: 0 },
|
|
124
|
+
} as EnhHeadingBlock}/>
|
|
125
|
+
<EnhHeadingBlockComponent block={{blockType: 'enh-heading',
|
|
126
|
+
heading: { text: 'State (optional)', level: 6 },
|
|
127
|
+
byline: { text: shippingFormValues.state, level: 0 },
|
|
128
|
+
} as EnhHeadingBlock}/>
|
|
129
|
+
<EnhHeadingBlockComponent block={{blockType: 'enh-heading',
|
|
130
|
+
heading: { text: 'Country', level: 6 },
|
|
131
|
+
byline: { text: shippingFormValues.country, level: 0 },
|
|
132
|
+
} as EnhHeadingBlock}/>
|
|
133
|
+
|
|
134
|
+
<div className='flex justify-between mt-8'>
|
|
135
|
+
<Button variant='outline' onClick={() => setStep(1)}>Back</Button>
|
|
136
|
+
<Button onClick={onPayClick}>Pay</Button>
|
|
137
|
+
</div>
|
|
138
|
+
</div>
|
|
139
|
+
)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export default ConfirmOrder
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
Form,
|
|
5
|
+
FormControl,
|
|
6
|
+
FormField,
|
|
7
|
+
FormItem,
|
|
8
|
+
FormLabel,
|
|
9
|
+
FormMessage,
|
|
10
|
+
} from '@hanzo/ui/primitives/form'
|
|
11
|
+
import { Input, Button } from '@hanzo/ui/primitives'
|
|
12
|
+
import { User } from 'lucide-react'
|
|
13
|
+
import { EnhHeadingBlockComponent, type EnhHeadingBlock } from '@hanzo/ui/blocks'
|
|
14
|
+
import type { UseFormReturn } from 'react-hook-form'
|
|
15
|
+
|
|
16
|
+
const ContactInfo: React.FC<{
|
|
17
|
+
form: UseFormReturn<{
|
|
18
|
+
name: string;
|
|
19
|
+
email: string;
|
|
20
|
+
}, any, {
|
|
21
|
+
name: string;
|
|
22
|
+
email: string;
|
|
23
|
+
}>,
|
|
24
|
+
selectPaymentMethod: (method: 'crypto' | 'bank') => Promise<void>,
|
|
25
|
+
}> = ({
|
|
26
|
+
form,
|
|
27
|
+
selectPaymentMethod,
|
|
28
|
+
}) => {
|
|
29
|
+
return (
|
|
30
|
+
<Form {...form}>
|
|
31
|
+
<form className='text-left'>
|
|
32
|
+
<div className='flex flex-col gap-4'>
|
|
33
|
+
<div className='flex gap-4 items-center'>
|
|
34
|
+
<User />
|
|
35
|
+
<EnhHeadingBlockComponent block={{blockType: 'enh-heading',
|
|
36
|
+
heading: { text: `Contact Info`, level: 4 },
|
|
37
|
+
} as EnhHeadingBlock}/>
|
|
38
|
+
</div>
|
|
39
|
+
<div className='flex flex-col sm:flex-row gap-4'>
|
|
40
|
+
<FormField
|
|
41
|
+
control={form.control}
|
|
42
|
+
name='name'
|
|
43
|
+
render={({ field }) => (
|
|
44
|
+
<FormItem className='space-y-1 w-full'>
|
|
45
|
+
<FormLabel>Name</FormLabel>
|
|
46
|
+
<FormControl>
|
|
47
|
+
<Input {...field} />
|
|
48
|
+
</FormControl>
|
|
49
|
+
<FormMessage />
|
|
50
|
+
</FormItem>
|
|
51
|
+
)}
|
|
52
|
+
/>
|
|
53
|
+
<FormField
|
|
54
|
+
control={form.control}
|
|
55
|
+
name='email'
|
|
56
|
+
render={({ field }) => (
|
|
57
|
+
<FormItem className='space-y-1 w-full'>
|
|
58
|
+
<FormLabel>Email</FormLabel>
|
|
59
|
+
<FormControl>
|
|
60
|
+
<Input {...field} />
|
|
61
|
+
</FormControl>
|
|
62
|
+
<FormMessage />
|
|
63
|
+
</FormItem>
|
|
64
|
+
)}
|
|
65
|
+
/>
|
|
66
|
+
</div>
|
|
67
|
+
<div className='flex flex-col sm:flex-row gap-4 items-center'>
|
|
68
|
+
<Button
|
|
69
|
+
type='button'
|
|
70
|
+
onClick={() => selectPaymentMethod('crypto')}
|
|
71
|
+
className='mx-auto w-full'
|
|
72
|
+
>
|
|
73
|
+
Pay with crypto
|
|
74
|
+
</Button>
|
|
75
|
+
<Button
|
|
76
|
+
type='button'
|
|
77
|
+
onClick={() => selectPaymentMethod('bank')}
|
|
78
|
+
className='mx-auto w-full'
|
|
79
|
+
>
|
|
80
|
+
Bank transfer
|
|
81
|
+
</Button>
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
84
|
+
</form>
|
|
85
|
+
</Form>
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export default ContactInfo
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
//https://stefangabos.github.io/world_countries/
|
|
2
|
+
export const countries = [
|
|
3
|
+
{"id":4,"alpha2":"af","alpha3":"afg","name":"Afghanistan"},
|
|
4
|
+
{"id":8,"alpha2":"al","alpha3":"alb","name":"Albania"},
|
|
5
|
+
{"id":12,"alpha2":"dz","alpha3":"dza","name":"Algeria"},
|
|
6
|
+
{"id":20,"alpha2":"ad","alpha3":"and","name":"Andorra"},
|
|
7
|
+
{"id":24,"alpha2":"ao","alpha3":"ago","name":"Angola"},
|
|
8
|
+
{"id":28,"alpha2":"ag","alpha3":"atg","name":"Antigua and Barbuda"},
|
|
9
|
+
{"id":32,"alpha2":"ar","alpha3":"arg","name":"Argentina"},
|
|
10
|
+
{"id":51,"alpha2":"am","alpha3":"arm","name":"Armenia"},
|
|
11
|
+
{"id":36,"alpha2":"au","alpha3":"aus","name":"Australia"},
|
|
12
|
+
{"id":40,"alpha2":"at","alpha3":"aut","name":"Austria"},
|
|
13
|
+
{"id":31,"alpha2":"az","alpha3":"aze","name":"Azerbaijan"},
|
|
14
|
+
{"id":44,"alpha2":"bs","alpha3":"bhs","name":"Bahamas"},
|
|
15
|
+
{"id":48,"alpha2":"bh","alpha3":"bhr","name":"Bahrain"},
|
|
16
|
+
{"id":50,"alpha2":"bd","alpha3":"bgd","name":"Bangladesh"},
|
|
17
|
+
{"id":52,"alpha2":"bb","alpha3":"brb","name":"Barbados"},
|
|
18
|
+
{"id":112,"alpha2":"by","alpha3":"blr","name":"Belarus"},
|
|
19
|
+
{"id":56,"alpha2":"be","alpha3":"bel","name":"Belgium"},
|
|
20
|
+
{"id":84,"alpha2":"bz","alpha3":"blz","name":"Belize"},
|
|
21
|
+
{"id":204,"alpha2":"bj","alpha3":"ben","name":"Benin"},
|
|
22
|
+
{"id":64,"alpha2":"bt","alpha3":"btn","name":"Bhutan"},
|
|
23
|
+
{"id":68,"alpha2":"bo","alpha3":"bol","name":"Bolivia"},
|
|
24
|
+
{"id":70,"alpha2":"ba","alpha3":"bih","name":"Bosnia and Herzegovina"},
|
|
25
|
+
{"id":72,"alpha2":"bw","alpha3":"bwa","name":"Botswana"},
|
|
26
|
+
{"id":76,"alpha2":"br","alpha3":"bra","name":"Brazil"},
|
|
27
|
+
{"id":96,"alpha2":"bn","alpha3":"brn","name":"Brunei Darussalam"},
|
|
28
|
+
{"id":100,"alpha2":"bg","alpha3":"bgr","name":"Bulgaria"},
|
|
29
|
+
{"id":854,"alpha2":"bf","alpha3":"bfa","name":"Burkina Faso"},
|
|
30
|
+
{"id":108,"alpha2":"bi","alpha3":"bdi","name":"Burundi"},
|
|
31
|
+
{"id":132,"alpha2":"cv","alpha3":"cpv","name":"Cabo Verde"},
|
|
32
|
+
{"id":116,"alpha2":"kh","alpha3":"khm","name":"Cambodia"},
|
|
33
|
+
{"id":120,"alpha2":"cm","alpha3":"cmr","name":"Cameroon"},
|
|
34
|
+
{"id":124,"alpha2":"ca","alpha3":"can","name":"Canada"},
|
|
35
|
+
{"id":140,"alpha2":"cf","alpha3":"caf","name":"Central African Republic"},
|
|
36
|
+
{"id":148,"alpha2":"td","alpha3":"tcd","name":"Chad"},
|
|
37
|
+
{"id":152,"alpha2":"cl","alpha3":"chl","name":"Chile"},
|
|
38
|
+
{"id":156,"alpha2":"cn","alpha3":"chn","name":"China"},
|
|
39
|
+
{"id":170,"alpha2":"co","alpha3":"col","name":"Colombia"},
|
|
40
|
+
{"id":174,"alpha2":"km","alpha3":"com","name":"Comoros"},
|
|
41
|
+
{"id":178,"alpha2":"cg","alpha3":"cog","name":"Congo"},
|
|
42
|
+
{"id":188,"alpha2":"cr","alpha3":"cri","name":"Costa Rica"},
|
|
43
|
+
{"id":384,"alpha2":"ci","alpha3":"civ","name":"Côte d'Ivoire"},
|
|
44
|
+
{"id":191,"alpha2":"hr","alpha3":"hrv","name":"Croatia"},
|
|
45
|
+
{"id":192,"alpha2":"cu","alpha3":"cub","name":"Cuba"},
|
|
46
|
+
{"id":196,"alpha2":"cy","alpha3":"cyp","name":"Cyprus"},
|
|
47
|
+
{"id":203,"alpha2":"cz","alpha3":"cze","name":"Czechia"},
|
|
48
|
+
{"id":180,"alpha2":"cd","alpha3":"cod","name":"Democratic Republic of the Congo"},
|
|
49
|
+
{"id":208,"alpha2":"dk","alpha3":"dnk","name":"Denmark"},
|
|
50
|
+
{"id":262,"alpha2":"dj","alpha3":"dji","name":"Djibouti"},
|
|
51
|
+
{"id":212,"alpha2":"dm","alpha3":"dma","name":"Dominica"},
|
|
52
|
+
{"id":214,"alpha2":"do","alpha3":"dom","name":"Dominican Republic"},
|
|
53
|
+
{"id":218,"alpha2":"ec","alpha3":"ecu","name":"Ecuador"},
|
|
54
|
+
{"id":818,"alpha2":"eg","alpha3":"egy","name":"Egypt"},
|
|
55
|
+
{"id":222,"alpha2":"sv","alpha3":"slv","name":"El Salvador"},
|
|
56
|
+
{"id":226,"alpha2":"gq","alpha3":"gnq","name":"Equatorial Guinea"},
|
|
57
|
+
{"id":232,"alpha2":"er","alpha3":"eri","name":"Eritrea"},
|
|
58
|
+
{"id":233,"alpha2":"ee","alpha3":"est","name":"Estonia"},
|
|
59
|
+
{"id":748,"alpha2":"sz","alpha3":"swz","name":"Eswatini"},
|
|
60
|
+
{"id":231,"alpha2":"et","alpha3":"eth","name":"Ethiopia"},
|
|
61
|
+
{"id":242,"alpha2":"fj","alpha3":"fji","name":"Fiji"},
|
|
62
|
+
{"id":246,"alpha2":"fi","alpha3":"fin","name":"Finland"},
|
|
63
|
+
{"id":250,"alpha2":"fr","alpha3":"fra","name":"France"},
|
|
64
|
+
{"id":266,"alpha2":"ga","alpha3":"gab","name":"Gabon"},
|
|
65
|
+
{"id":270,"alpha2":"gm","alpha3":"gmb","name":"Gambia"},
|
|
66
|
+
{"id":268,"alpha2":"ge","alpha3":"geo","name":"Georgia"},
|
|
67
|
+
{"id":276,"alpha2":"de","alpha3":"deu","name":"Germany"},
|
|
68
|
+
{"id":288,"alpha2":"gh","alpha3":"gha","name":"Ghana"},
|
|
69
|
+
{"id":300,"alpha2":"gr","alpha3":"grc","name":"Greece"},
|
|
70
|
+
{"id":308,"alpha2":"gd","alpha3":"grd","name":"Grenada"},
|
|
71
|
+
{"id":320,"alpha2":"gt","alpha3":"gtm","name":"Guatemala"},
|
|
72
|
+
{"id":324,"alpha2":"gn","alpha3":"gin","name":"Guinea"},
|
|
73
|
+
{"id":624,"alpha2":"gw","alpha3":"gnb","name":"Guinea-Bissau"},
|
|
74
|
+
{"id":328,"alpha2":"gy","alpha3":"guy","name":"Guyana"},
|
|
75
|
+
{"id":332,"alpha2":"ht","alpha3":"hti","name":"Haiti"},
|
|
76
|
+
{"id":340,"alpha2":"hn","alpha3":"hnd","name":"Honduras"},
|
|
77
|
+
{"id":348,"alpha2":"hu","alpha3":"hun","name":"Hungary"},
|
|
78
|
+
{"id":352,"alpha2":"is","alpha3":"isl","name":"Iceland"},
|
|
79
|
+
{"id":356,"alpha2":"in","alpha3":"ind","name":"India"},
|
|
80
|
+
{"id":360,"alpha2":"id","alpha3":"idn","name":"Indonesia"},
|
|
81
|
+
{"id":364,"alpha2":"ir","alpha3":"irn","name":"Iran"},
|
|
82
|
+
{"id":368,"alpha2":"iq","alpha3":"irq","name":"Iraq"},
|
|
83
|
+
{"id":372,"alpha2":"ie","alpha3":"irl","name":"Ireland"},
|
|
84
|
+
{"id":376,"alpha2":"il","alpha3":"isr","name":"Israel"},
|
|
85
|
+
{"id":380,"alpha2":"it","alpha3":"ita","name":"Italy"},
|
|
86
|
+
{"id":388,"alpha2":"jm","alpha3":"jam","name":"Jamaica"},
|
|
87
|
+
{"id":392,"alpha2":"jp","alpha3":"jpn","name":"Japan"},
|
|
88
|
+
{"id":400,"alpha2":"jo","alpha3":"jor","name":"Jordan"},
|
|
89
|
+
{"id":398,"alpha2":"kz","alpha3":"kaz","name":"Kazakhstan"},
|
|
90
|
+
{"id":404,"alpha2":"ke","alpha3":"ken","name":"Kenya"},
|
|
91
|
+
{"id":296,"alpha2":"ki","alpha3":"kir","name":"Kiribati"},
|
|
92
|
+
{"id":414,"alpha2":"kw","alpha3":"kwt","name":"Kuwait"},
|
|
93
|
+
{"id":417,"alpha2":"kg","alpha3":"kgz","name":"Kyrgyzstan"},
|
|
94
|
+
{"id":418,"alpha2":"la","alpha3":"lao","name":"Lao People's Democratic Republic"},
|
|
95
|
+
{"id":428,"alpha2":"lv","alpha3":"lva","name":"Latvia"},
|
|
96
|
+
{"id":422,"alpha2":"lb","alpha3":"lbn","name":"Lebanon"},
|
|
97
|
+
{"id":426,"alpha2":"ls","alpha3":"lso","name":"Lesotho"},
|
|
98
|
+
{"id":430,"alpha2":"lr","alpha3":"lbr","name":"Liberia"},
|
|
99
|
+
{"id":434,"alpha2":"ly","alpha3":"lby","name":"Libya"},
|
|
100
|
+
{"id":438,"alpha2":"li","alpha3":"lie","name":"Liechtenstein"},
|
|
101
|
+
{"id":440,"alpha2":"lt","alpha3":"ltu","name":"Lithuania"},
|
|
102
|
+
{"id":442,"alpha2":"lu","alpha3":"lux","name":"Luxembourg"},
|
|
103
|
+
{"id":450,"alpha2":"mg","alpha3":"mdg","name":"Madagascar"},
|
|
104
|
+
{"id":454,"alpha2":"mw","alpha3":"mwi","name":"Malawi"},
|
|
105
|
+
{"id":458,"alpha2":"my","alpha3":"mys","name":"Malaysia"},
|
|
106
|
+
{"id":462,"alpha2":"mv","alpha3":"mdv","name":"Maldives"},
|
|
107
|
+
{"id":466,"alpha2":"ml","alpha3":"mli","name":"Mali"},
|
|
108
|
+
{"id":470,"alpha2":"mt","alpha3":"mlt","name":"Malta"},
|
|
109
|
+
{"id":584,"alpha2":"mh","alpha3":"mhl","name":"Marshall Islands"},
|
|
110
|
+
{"id":478,"alpha2":"mr","alpha3":"mrt","name":"Mauritania"},
|
|
111
|
+
{"id":480,"alpha2":"mu","alpha3":"mus","name":"Mauritius"},
|
|
112
|
+
{"id":484,"alpha2":"mx","alpha3":"mex","name":"Mexico"},
|
|
113
|
+
{"id":583,"alpha2":"fm","alpha3":"fsm","name":"Micronesia"},
|
|
114
|
+
{"id":498,"alpha2":"md","alpha3":"mda","name":"Moldova"},
|
|
115
|
+
{"id":492,"alpha2":"mc","alpha3":"mco","name":"Monaco"},
|
|
116
|
+
{"id":496,"alpha2":"mn","alpha3":"mng","name":"Mongolia"},
|
|
117
|
+
{"id":499,"alpha2":"me","alpha3":"mne","name":"Montenegro"},
|
|
118
|
+
{"id":504,"alpha2":"ma","alpha3":"mar","name":"Morocco"},
|
|
119
|
+
{"id":508,"alpha2":"mz","alpha3":"moz","name":"Mozambique"},
|
|
120
|
+
{"id":104,"alpha2":"mm","alpha3":"mmr","name":"Myanmar"},
|
|
121
|
+
{"id":516,"alpha2":"na","alpha3":"nam","name":"Namibia"},
|
|
122
|
+
{"id":520,"alpha2":"nr","alpha3":"nru","name":"Nauru"},
|
|
123
|
+
{"id":524,"alpha2":"np","alpha3":"npl","name":"Nepal"},
|
|
124
|
+
{"id":528,"alpha2":"nl","alpha3":"nld","name":"Netherlands"},
|
|
125
|
+
{"id":554,"alpha2":"nz","alpha3":"nzl","name":"New Zealand"},
|
|
126
|
+
{"id":558,"alpha2":"ni","alpha3":"nic","name":"Nicaragua"},
|
|
127
|
+
{"id":562,"alpha2":"ne","alpha3":"ner","name":"Niger"},
|
|
128
|
+
{"id":566,"alpha2":"ng","alpha3":"nga","name":"Nigeria"},
|
|
129
|
+
{"id":408,"alpha2":"kp","alpha3":"prk","name":"North Korea"},
|
|
130
|
+
{"id":807,"alpha2":"mk","alpha3":"mkd","name":"North Macedonia"},
|
|
131
|
+
{"id":578,"alpha2":"no","alpha3":"nor","name":"Norway"},
|
|
132
|
+
{"id":512,"alpha2":"om","alpha3":"omn","name":"Oman"},
|
|
133
|
+
{"id":586,"alpha2":"pk","alpha3":"pak","name":"Pakistan"},
|
|
134
|
+
{"id":585,"alpha2":"pw","alpha3":"plw","name":"Palau"},
|
|
135
|
+
{"id":591,"alpha2":"pa","alpha3":"pan","name":"Panama"},
|
|
136
|
+
{"id":598,"alpha2":"pg","alpha3":"png","name":"Papua New Guinea"},
|
|
137
|
+
{"id":600,"alpha2":"py","alpha3":"pry","name":"Paraguay"},
|
|
138
|
+
{"id":604,"alpha2":"pe","alpha3":"per","name":"Peru"},
|
|
139
|
+
{"id":608,"alpha2":"ph","alpha3":"phl","name":"Philippines"},
|
|
140
|
+
{"id":616,"alpha2":"pl","alpha3":"pol","name":"Poland"},
|
|
141
|
+
{"id":620,"alpha2":"pt","alpha3":"prt","name":"Portugal"},
|
|
142
|
+
{"id":634,"alpha2":"qa","alpha3":"qat","name":"Qatar"},
|
|
143
|
+
{"id":642,"alpha2":"ro","alpha3":"rou","name":"Romania"},
|
|
144
|
+
{"id":643,"alpha2":"ru","alpha3":"rus","name":"Russian Federation"},
|
|
145
|
+
{"id":646,"alpha2":"rw","alpha3":"rwa","name":"Rwanda"},
|
|
146
|
+
{"id":659,"alpha2":"kn","alpha3":"kna","name":"Saint Kitts and Nevis"},
|
|
147
|
+
{"id":662,"alpha2":"lc","alpha3":"lca","name":"Saint Lucia"},
|
|
148
|
+
{"id":670,"alpha2":"vc","alpha3":"vct","name":"Saint Vincent and the Grenadines"},
|
|
149
|
+
{"id":882,"alpha2":"ws","alpha3":"wsm","name":"Samoa"},
|
|
150
|
+
{"id":674,"alpha2":"sm","alpha3":"smr","name":"San Marino"},
|
|
151
|
+
{"id":678,"alpha2":"st","alpha3":"stp","name":"Sao Tome and Principe"},
|
|
152
|
+
{"id":682,"alpha2":"sa","alpha3":"sau","name":"Saudi Arabia"},
|
|
153
|
+
{"id":686,"alpha2":"sn","alpha3":"sen","name":"Senegal"},
|
|
154
|
+
{"id":688,"alpha2":"rs","alpha3":"srb","name":"Serbia"},
|
|
155
|
+
{"id":690,"alpha2":"sc","alpha3":"syc","name":"Seychelles"},
|
|
156
|
+
{"id":694,"alpha2":"sl","alpha3":"sle","name":"Sierra Leone"},
|
|
157
|
+
{"id":702,"alpha2":"sg","alpha3":"sgp","name":"Singapore"},
|
|
158
|
+
{"id":703,"alpha2":"sk","alpha3":"svk","name":"Slovakia"},
|
|
159
|
+
{"id":705,"alpha2":"si","alpha3":"svn","name":"Slovenia"},
|
|
160
|
+
{"id":90,"alpha2":"sb","alpha3":"slb","name":"Solomon Islands"},
|
|
161
|
+
{"id":706,"alpha2":"so","alpha3":"som","name":"Somalia"},
|
|
162
|
+
{"id":710,"alpha2":"za","alpha3":"zaf","name":"South Africa"},
|
|
163
|
+
{"id":410,"alpha2":"kr","alpha3":"kor","name":"South Korea"},
|
|
164
|
+
{"id":728,"alpha2":"ss","alpha3":"ssd","name":"South Sudan"},
|
|
165
|
+
{"id":724,"alpha2":"es","alpha3":"esp","name":"Spain"},
|
|
166
|
+
{"id":144,"alpha2":"lk","alpha3":"lka","name":"Sri Lanka"},
|
|
167
|
+
{"id":729,"alpha2":"sd","alpha3":"sdn","name":"Sudan"},
|
|
168
|
+
{"id":740,"alpha2":"sr","alpha3":"sur","name":"Suriname"},
|
|
169
|
+
{"id":752,"alpha2":"se","alpha3":"swe","name":"Sweden"},
|
|
170
|
+
{"id":756,"alpha2":"ch","alpha3":"che","name":"Switzerland"},
|
|
171
|
+
{"id":760,"alpha2":"sy","alpha3":"syr","name":"Syria"},
|
|
172
|
+
{"id":762,"alpha2":"tj","alpha3":"tjk","name":"Tajikistan"},
|
|
173
|
+
{"id":834,"alpha2":"tz","alpha3":"tza","name":"Tanzania"},
|
|
174
|
+
{"id":764,"alpha2":"th","alpha3":"tha","name":"Thailand"},
|
|
175
|
+
{"id":626,"alpha2":"tl","alpha3":"tls","name":"Timor-Leste"},
|
|
176
|
+
{"id":768,"alpha2":"tg","alpha3":"tgo","name":"Togo"},
|
|
177
|
+
{"id":776,"alpha2":"to","alpha3":"ton","name":"Tonga"},
|
|
178
|
+
{"id":780,"alpha2":"tt","alpha3":"tto","name":"Trinidad and Tobago"},
|
|
179
|
+
{"id":788,"alpha2":"tn","alpha3":"tun","name":"Tunisia"},
|
|
180
|
+
{"id":792,"alpha2":"tr","alpha3":"tur","name":"Türkiye"},
|
|
181
|
+
{"id":795,"alpha2":"tm","alpha3":"tkm","name":"Turkmenistan"},
|
|
182
|
+
{"id":798,"alpha2":"tv","alpha3":"tuv","name":"Tuvalu"},
|
|
183
|
+
{"id":800,"alpha2":"ug","alpha3":"uga","name":"Uganda"},
|
|
184
|
+
{"id":804,"alpha2":"ua","alpha3":"ukr","name":"Ukraine"},
|
|
185
|
+
{"id":784,"alpha2":"ae","alpha3":"are","name":"United Arab Emirates"},
|
|
186
|
+
{"id":826,"alpha2":"gb","alpha3":"gbr","name":"United Kingdom"},
|
|
187
|
+
{"id":840,"alpha2":"us","alpha3":"usa","name":"United States of America"},
|
|
188
|
+
{"id":858,"alpha2":"uy","alpha3":"ury","name":"Uruguay"},
|
|
189
|
+
{"id":860,"alpha2":"uz","alpha3":"uzb","name":"Uzbekistan"},
|
|
190
|
+
{"id":548,"alpha2":"vu","alpha3":"vut","name":"Vanuatu"},
|
|
191
|
+
{"id":862,"alpha2":"ve","alpha3":"ven","name":"Venezuela"},
|
|
192
|
+
{"id":704,"alpha2":"vn","alpha3":"vnm","name":"Viet Nam"},
|
|
193
|
+
{"id":887,"alpha2":"ye","alpha3":"yem","name":"Yemen"},
|
|
194
|
+
{"id":894,"alpha2":"zm","alpha3":"zmb","name":"Zambia"},
|
|
195
|
+
{"id":716,"alpha2":"zw","alpha3":"zwe","name":"Zimbabwe"}
|
|
196
|
+
]
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { type LucideProps } from 'lucide-react'
|
|
3
|
+
|
|
4
|
+
const Btc: React.FC<LucideProps> = (props: LucideProps) => (
|
|
5
|
+
<svg viewBox="0.004 0 64 64" {...props}>
|
|
6
|
+
<path d="M63.04 39.741c-4.274 17.143-21.638 27.575-38.783 23.301C7.12 58.768-3.313 41.404.962 24.262 5.234 7.117 22.597-3.317 39.737.957c17.144 4.274 27.576 21.64 23.302 38.784z" fill="#f7931a"/>
|
|
7
|
+
<path d="M46.11 27.441c.636-4.258-2.606-6.547-7.039-8.074l1.438-5.768-3.512-.875-1.4 5.616c-.922-.23-1.87-.447-2.812-.662l1.41-5.653-3.509-.875-1.439 5.766c-.764-.174-1.514-.346-2.242-.527l.004-.018-4.842-1.209-.934 3.75s2.605.597 2.55.634c1.422.355 1.68 1.296 1.636 2.042l-1.638 6.571c.098.025.225.061.365.117l-.37-.092-2.297 9.205c-.174.432-.615 1.08-1.609.834.035.051-2.552-.637-2.552-.637l-1.743 4.02 4.57 1.139c.85.213 1.683.436 2.502.646l-1.453 5.835 3.507.875 1.44-5.772c.957.26 1.887.5 2.797.726L27.504 50.8l3.511.875 1.453-5.823c5.987 1.133 10.49.676 12.383-4.738 1.527-4.36-.075-6.875-3.225-8.516 2.294-.531 4.022-2.04 4.483-5.157zM38.087 38.69c-1.086 4.36-8.426 2.004-10.807 1.412l1.928-7.729c2.38.594 10.011 1.77 8.88 6.317zm1.085-11.312c-.99 3.966-7.1 1.951-9.083 1.457l1.748-7.01c1.983.494 8.367 1.416 7.335 5.553z" fill="#ffffff"/>
|
|
8
|
+
</svg>
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
export default Btc
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { type LucideProps } from 'lucide-react'
|
|
3
|
+
|
|
4
|
+
const Eth: React.FC<LucideProps> = (props: LucideProps) => (
|
|
5
|
+
<svg viewBox="0 0 32 32" {...props}>
|
|
6
|
+
<g fill="none" fillRule="evenodd">
|
|
7
|
+
<circle cx="16" cy="16" r="16" fill="#627EEA"/>
|
|
8
|
+
<g fill="#FFF" fillRule="nonzero">
|
|
9
|
+
<path fillOpacity=".602" d="M16.498 4v8.87l7.497 3.35z"/>
|
|
10
|
+
<path d="M16.498 4L9 16.22l7.498-3.35z"/>
|
|
11
|
+
<path fillOpacity=".602" d="M16.498 21.968v6.027L24 17.616z"/>
|
|
12
|
+
<path d="M16.498 27.995v-6.028L9 17.616z"/>
|
|
13
|
+
<path fillOpacity=".2" d="M16.498 20.573l7.497-4.353-7.497-3.348z"/>
|
|
14
|
+
<path fillOpacity=".602" d="M9 16.22l7.498 4.353v-7.701z"/>
|
|
15
|
+
</g>
|
|
16
|
+
</g>
|
|
17
|
+
</svg>
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
export default Eth
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { type LucideProps } from 'lucide-react'
|
|
3
|
+
|
|
4
|
+
const Usdt: React.FC<LucideProps> = (props: LucideProps) => (
|
|
5
|
+
<svg viewBox="0 0 32 32" {...props}>
|
|
6
|
+
<g fill="none" fill-rule="evenodd">
|
|
7
|
+
<circle cx="16" cy="16" r="16" fill="#26A17B"/>
|
|
8
|
+
<path fill="#FFF" d="M17.922 17.383v-.002c-.11.008-.677.042-1.942.042-1.01 0-1.721-.03-1.971-.042v.003c-3.888-.171-6.79-.848-6.79-1.658 0-.809 2.902-1.486 6.79-1.66v2.644c.254.018.982.061 1.988.061 1.207 0 1.812-.05 1.925-.06v-2.643c3.88.173 6.775.85 6.775 1.658 0 .81-2.895 1.485-6.775 1.657m0-3.59v-2.366h5.414V7.819H8.595v3.608h5.414v2.365c-4.4.202-7.709 1.074-7.709 2.118 0 1.044 3.309 1.915 7.709 2.118v7.582h3.913v-7.584c4.393-.202 7.694-1.073 7.694-2.116 0-1.043-3.301-1.914-7.694-2.117"/>
|
|
9
|
+
</g>
|
|
10
|
+
</svg>
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
export default Usdt
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState } from 'react'
|
|
4
|
+
import { ChevronLeft } from 'lucide-react'
|
|
5
|
+
import { observer } from 'mobx-react-lite'
|
|
6
|
+
|
|
7
|
+
import { zodResolver } from '@hookform/resolvers/zod'
|
|
8
|
+
import * as z from 'zod'
|
|
9
|
+
import { useForm } from 'react-hook-form'
|
|
10
|
+
|
|
11
|
+
import { ApplyTypography, Button, Main, Separator } from '@hanzo/ui/primitives'
|
|
12
|
+
import { cn } from '@hanzo/ui/util'
|
|
13
|
+
import { useAuth } from '@hanzo/auth/service'
|
|
14
|
+
import { LoginComponent } from '@hanzo/auth/components'
|
|
15
|
+
|
|
16
|
+
import ShippingInfo from './shipping-info'
|
|
17
|
+
import ThankYou from './thank-you'
|
|
18
|
+
import PayWithCrypto from './pay-with-crypto'
|
|
19
|
+
import PayByBankTransfer from './pay-by-bank-transfer'
|
|
20
|
+
import ContactInfo from './contact-info'
|
|
21
|
+
import { Cart } from '..'
|
|
22
|
+
import { useCommerce } from '../../service/context'
|
|
23
|
+
|
|
24
|
+
const contactFormSchema = z.object({
|
|
25
|
+
name: z.string().min(2, 'Name must be at least 2 characters.'),
|
|
26
|
+
email: z.string().email(),
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
const Checkout: React.FC<{toggleCheckout: () => void}> = observer(({toggleCheckout}) => {
|
|
30
|
+
const auth = useAuth()
|
|
31
|
+
const cmmc = useCommerce()
|
|
32
|
+
|
|
33
|
+
const [currentStep, setCurrentStep] = useState(1)
|
|
34
|
+
const [paymentMethod, setPaymentMethod] = useState<'crypto' | 'bank' | undefined>()
|
|
35
|
+
const [orderId, setOrderId] = useState<string>()
|
|
36
|
+
|
|
37
|
+
const contactForm = useForm<z.infer<typeof contactFormSchema>>({
|
|
38
|
+
resolver: zodResolver(contactFormSchema),
|
|
39
|
+
defaultValues: {
|
|
40
|
+
name: auth.user?.displayName ?? '',
|
|
41
|
+
email: auth.user?.email ?? '',
|
|
42
|
+
},
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
if (auth.loggedIn) {
|
|
47
|
+
contactForm.setValue('name', auth.user?.displayName ?? '')
|
|
48
|
+
contactForm.setValue('email', auth.user?.email ?? '')
|
|
49
|
+
}
|
|
50
|
+
}, [auth.loggedIn])
|
|
51
|
+
|
|
52
|
+
const selectPaymentMethod = async (method: 'crypto' | 'bank') => {
|
|
53
|
+
contactForm.handleSubmit(async () => {
|
|
54
|
+
if (auth.user) {
|
|
55
|
+
if (!!orderId) {
|
|
56
|
+
await cmmc.updateOrder(orderId, auth.user.email, method)
|
|
57
|
+
} else {
|
|
58
|
+
const id = await cmmc.createOrder(auth.user.email, method)
|
|
59
|
+
setOrderId(id)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
setPaymentMethod(method)
|
|
63
|
+
setCurrentStep(2)
|
|
64
|
+
})()
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
const step1 = auth.loggedIn ? (
|
|
69
|
+
<ContactInfo form={contactForm} selectPaymentMethod={selectPaymentMethod}/>
|
|
70
|
+
) : (
|
|
71
|
+
<LoginComponent hideHeader className='max-w-[20rem] mx-auto'/>
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
const step2 = paymentMethod === 'crypto' ? (
|
|
75
|
+
<PayWithCrypto setCurrentStep={setCurrentStep}/>
|
|
76
|
+
) : (
|
|
77
|
+
<PayByBankTransfer setCurrentStep={setCurrentStep}/>
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
const step3 = (
|
|
81
|
+
<ShippingInfo orderId={orderId} paymentMethod={paymentMethod} setCurrentStep={setCurrentStep}/>
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
const step4 = <ThankYou/>
|
|
85
|
+
|
|
86
|
+
const steps = [step1, step2, step3, step4]
|
|
87
|
+
|
|
88
|
+
return (
|
|
89
|
+
<div className="fixed top-0 left-0 !max-w-none w-full h-full min-h-screen bg-background z-[51]">
|
|
90
|
+
<Main className='grid grid-cols-5 justify-center gap-8 overflow-y-auto h-full'>
|
|
91
|
+
<Button
|
|
92
|
+
variant='ghost'
|
|
93
|
+
size='icon'
|
|
94
|
+
className='absolute sm:ml-2 sm:mt-2'
|
|
95
|
+
onClick={() => {
|
|
96
|
+
setCurrentStep(0)
|
|
97
|
+
toggleCheckout()
|
|
98
|
+
}}
|
|
99
|
+
>
|
|
100
|
+
<ChevronLeft/>
|
|
101
|
+
</Button>
|
|
102
|
+
|
|
103
|
+
<div className='col-span-2 hidden md:flex'>
|
|
104
|
+
<Cart hideCheckout className='fixed justify-center border-none mt-10 w-1/3 max-w-[40rem]'/>
|
|
105
|
+
</div>
|
|
106
|
+
|
|
107
|
+
<div className='flex flex-col gap-8 sm:gap-14 col-span-5 md:col-span-3 max-w-[40rem] mx-auto'>
|
|
108
|
+
<ApplyTypography className='w-full'>
|
|
109
|
+
<h3 className='!text-center w-1/2 mx-auto sm:w-full sm:mt-5'>FINALIZE PAYMENT</h3>
|
|
110
|
+
</ApplyTypography>
|
|
111
|
+
<div className='flex gap-2 mx-auto items-center text-xxs sm:text-base'>
|
|
112
|
+
<div className={cn('w-6 h-6 rounded-full border border-foreground flex flex-col justify-center items-center', currentStep === 1 ? 'bg-foreground text-muted-4' : '')}>
|
|
113
|
+
1
|
|
114
|
+
<div className='relative text-foreground top-2 h-0 whitespace-nowrap'>Contact info</div>
|
|
115
|
+
</div>
|
|
116
|
+
<Separator className='w-[4rem] sm:w-[6rem]'/>
|
|
117
|
+
<div className={cn('w-6 h-6 rounded-full border border-foreground flex flex-col justify-center items-center', currentStep === 2 ? 'bg-foreground text-muted-4' : '')}>
|
|
118
|
+
2
|
|
119
|
+
<div className='relative text-foreground top-2 h-0 whitespace-nowrap'>Payment</div>
|
|
120
|
+
</div>
|
|
121
|
+
<Separator className='w-[4rem] sm:w-[6rem]'/>
|
|
122
|
+
<div className={cn('w-6 h-6 rounded-full border border-foreground flex flex-col justify-center items-center', currentStep === 3 ? 'bg-foreground text-muted-4' : '')}>
|
|
123
|
+
3
|
|
124
|
+
<div className='relative text-foreground top-2 h-0 whitespace-nowrap'>Shipping info</div>
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
{steps[currentStep - 1]}
|
|
128
|
+
</div>
|
|
129
|
+
</Main>
|
|
130
|
+
</div>
|
|
131
|
+
)
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
export default Checkout
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import type { ReactNode } from 'react'
|
|
4
|
+
import { Copy } from 'lucide-react'
|
|
5
|
+
|
|
6
|
+
import { Button, Tabs, TabsContent, TabsList, TabsTrigger, toast } from '@hanzo/ui/primitives'
|
|
7
|
+
import { EnhHeadingBlockComponent, type EnhHeadingBlock, type Block } from '@hanzo/ui/blocks'
|
|
8
|
+
|
|
9
|
+
const InfoField: React.FC<{label: string, value: ReactNode, copyValue: string}> = ({label, value, copyValue}) => {
|
|
10
|
+
const copyToClipboard = (label: string, text: string) => {
|
|
11
|
+
navigator.clipboard.writeText(text)
|
|
12
|
+
toast({title: `${label} copied to clipboard`})
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<div className='flex flex-col gap-1'>
|
|
17
|
+
<p className='text-xs'>{label}</p>
|
|
18
|
+
<div className='flex items-center justify-between text-lg border rounded-lg py-2 px-4'>
|
|
19
|
+
{value}
|
|
20
|
+
<Button variant='ghost' size='icon' onClick={() => copyToClipboard(label, copyValue)}>
|
|
21
|
+
<Copy className='h-4 w-4'/>
|
|
22
|
+
</Button>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const PayByBankTransfer: React.FC<{setCurrentStep: (step: number) => void}> = ({setCurrentStep}) => {
|
|
29
|
+
return (<>
|
|
30
|
+
<EnhHeadingBlockComponent
|
|
31
|
+
block={{blockType: 'enh-heading',
|
|
32
|
+
specifiers: 'center byline-center',
|
|
33
|
+
heading: { text: `WIRE INFO`, level: 3 },
|
|
34
|
+
byline: { text: 'Click the copy icon to copy text to your clipboard.', level: 6}
|
|
35
|
+
} satisfies EnhHeadingBlock as Block}
|
|
36
|
+
/>
|
|
37
|
+
<Tabs defaultValue="usd" className='w-full mx-auto max-w-[50rem]'>
|
|
38
|
+
<TabsList className="grid w-full grid-cols-2 max-w-[15rem] mx-auto bg-level-2">
|
|
39
|
+
<TabsTrigger value="usd">USD</TabsTrigger>
|
|
40
|
+
<TabsTrigger value="eur">EUR/GBP</TabsTrigger>
|
|
41
|
+
</TabsList>
|
|
42
|
+
<TabsContent value="usd">
|
|
43
|
+
<div className='flex flex-col gap-4 w-full'>
|
|
44
|
+
<InfoField
|
|
45
|
+
label='Beneficiary Bank'
|
|
46
|
+
value={<div>Bank of America<br/>NA 222 Broadway<br/>New York, New York. 10038</div>}
|
|
47
|
+
copyValue='Bank of America, NA 222 Broadway, New York, New York. 10038'
|
|
48
|
+
/>
|
|
49
|
+
<InfoField
|
|
50
|
+
label='Beneficiary'
|
|
51
|
+
value={<div>Hanzo, Inc<br/>4811 Mastin Street<br/>Merriam, KS 66203</div>}
|
|
52
|
+
copyValue='Hanzo, Inc, 4811 Mastin Street, Merriam, KS 66203'
|
|
53
|
+
/>
|
|
54
|
+
<InfoField label='Routing Number - ACH' value='113000023 / 111000025' copyValue='113000023 / 111000025'/>
|
|
55
|
+
<InfoField label='Routing Number - Wire' value='026009593' copyValue='026009593'/>
|
|
56
|
+
<InfoField label='Routing Number - SWIFT' value='BOFAUS3N' copyValue='BOFAUS3N'/>
|
|
57
|
+
<InfoField label='Reference' value='Lux' copyValue='Lux'/>
|
|
58
|
+
</div>
|
|
59
|
+
</TabsContent>
|
|
60
|
+
<TabsContent value="eur">
|
|
61
|
+
<div className='flex flex-col gap-4 w-full'>
|
|
62
|
+
<InfoField
|
|
63
|
+
label='Beneficiary Bank'
|
|
64
|
+
value={<div>Clear Junction Limited<br/>4th floor<br/>Imperial House</div>}
|
|
65
|
+
copyValue='Clear Junction Limited, 4th floor, Imperial House'
|
|
66
|
+
/>
|
|
67
|
+
<InfoField label='Account Number (GBP)' value='33781813' copyValue='33781813'/>
|
|
68
|
+
<InfoField label='Account Name' value='AiDLT Global Ltd.' copyValue='AiDLT Global Ltd.'/>
|
|
69
|
+
<InfoField label='SWIFT/BIC' value='CLJUGB21' copyValue='CLJUGB21'/>
|
|
70
|
+
<InfoField label='Sort Code (GBP)' value='041307' copyValue='041307'/>
|
|
71
|
+
<InfoField label='IBAN' value='GB75 CLJU 0413 0733 78181 3' copyValue='GB75 CLJU 0413 0733 78181 3'/>
|
|
72
|
+
<InfoField label='Reference' value='Lux' copyValue='Lux'/>
|
|
73
|
+
</div>
|
|
74
|
+
</TabsContent>
|
|
75
|
+
</Tabs>
|
|
76
|
+
<div className='flex gap-4 items-center mt-6'>
|
|
77
|
+
<Button variant='outline' onClick={() => setCurrentStep(1)} className='mx-auto w-full'>Back</Button>
|
|
78
|
+
<Button onClick={() => setCurrentStep(3)} className='mx-auto w-full'>Continue</Button>
|
|
79
|
+
</div>
|
|
80
|
+
</>)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export default PayByBankTransfer
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import React, { useEffect, useState } from 'react'
|
|
4
|
+
|
|
5
|
+
import { autorun } from 'mobx'
|
|
6
|
+
import { observer } from 'mobx-react-lite'
|
|
7
|
+
|
|
8
|
+
import { ethers } from 'ethers'
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
Button,
|
|
12
|
+
Input,
|
|
13
|
+
Select,
|
|
14
|
+
SelectContent,
|
|
15
|
+
SelectGroup,
|
|
16
|
+
SelectItem,
|
|
17
|
+
SelectTrigger,
|
|
18
|
+
SelectValue,
|
|
19
|
+
toast
|
|
20
|
+
} from '@hanzo/ui/primitives'
|
|
21
|
+
|
|
22
|
+
import { useAuth } from '@hanzo/auth/service'
|
|
23
|
+
import { Ethereum as EthIconFromAuth } from '@hanzo/auth/icons'
|
|
24
|
+
|
|
25
|
+
import Eth from './icons/eth'
|
|
26
|
+
import Btc from './icons/btc'
|
|
27
|
+
import Usdt from './icons/usdt'
|
|
28
|
+
import { useCommerce } from '../..'
|
|
29
|
+
import { formatPrice } from '../../util'
|
|
30
|
+
|
|
31
|
+
const PayWithCrypto: React.FC<{
|
|
32
|
+
setCurrentStep: (currentStep: number) => void
|
|
33
|
+
}> = observer(({
|
|
34
|
+
setCurrentStep
|
|
35
|
+
}) => {
|
|
36
|
+
|
|
37
|
+
const c = useCommerce()
|
|
38
|
+
const auth = useAuth()
|
|
39
|
+
const [loadingPrice, setLoadingPrice] = useState(false)
|
|
40
|
+
//const [selectedToken, setSelectedToken] = useState('eth')
|
|
41
|
+
const [amount, setAmount] = useState<number>()
|
|
42
|
+
const [availableAmount, setAvailableAmount] = useState<number>()
|
|
43
|
+
const [provider, setProvider] = useState<ethers.BrowserProvider>()
|
|
44
|
+
|
|
45
|
+
//const selectedToken = 'eth'
|
|
46
|
+
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
// responding to changes in user.walletAddress
|
|
49
|
+
return autorun(() => {
|
|
50
|
+
const newProvider = new ethers.BrowserProvider(window.ethereum)
|
|
51
|
+
setProvider(newProvider)
|
|
52
|
+
if (auth.user?.walletAddress) {
|
|
53
|
+
newProvider.getBalance(auth.user?.walletAddress).then((balance: any) => {
|
|
54
|
+
setAvailableAmount(Number(balance)/(10**18))
|
|
55
|
+
})
|
|
56
|
+
}
|
|
57
|
+
})
|
|
58
|
+
}, [])
|
|
59
|
+
|
|
60
|
+
// Get latest USD -> ETH exchange rate
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
const fetchPrice = () => {
|
|
63
|
+
setLoadingPrice(true)
|
|
64
|
+
fetch(process.env.NEXT_PUBLIC_ETH_EXCHANGE_RATE_API ?? '')
|
|
65
|
+
.then(res => res.json())
|
|
66
|
+
.then((exchangeRate) => {
|
|
67
|
+
const oneUsdInWei = (10**18) / exchangeRate.data.amount
|
|
68
|
+
const usdAmountInWei = oneUsdInWei * c.cartTotal
|
|
69
|
+
setAmount(usdAmountInWei)
|
|
70
|
+
setLoadingPrice(false)
|
|
71
|
+
})
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Call immediately on load
|
|
75
|
+
fetchPrice()
|
|
76
|
+
|
|
77
|
+
// Then set interval to call every 30 seconds
|
|
78
|
+
const interval = setInterval(fetchPrice, 30000)
|
|
79
|
+
|
|
80
|
+
return () => clearInterval(interval)
|
|
81
|
+
}, [c.cartTotal])
|
|
82
|
+
|
|
83
|
+
const sendPayment = async (ether: number) => {
|
|
84
|
+
// Check that we are on ethereum network
|
|
85
|
+
try {
|
|
86
|
+
await window.ethereum.request({
|
|
87
|
+
method: 'wallet_switchEthereumChain',
|
|
88
|
+
params: [{ chainId: "0x1"}],
|
|
89
|
+
})
|
|
90
|
+
const newProvider = new ethers.BrowserProvider(window.ethereum)
|
|
91
|
+
setProvider(newProvider)
|
|
92
|
+
if (auth.user?.walletAddress) {
|
|
93
|
+
newProvider.getBalance(auth.user?.walletAddress).then((balance: any) => {
|
|
94
|
+
setAvailableAmount(Number(balance)/(10**18))
|
|
95
|
+
})
|
|
96
|
+
}
|
|
97
|
+
} catch (err) {
|
|
98
|
+
toast({title: 'Please switch your wallet to the Ethereum network.'})
|
|
99
|
+
return
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
if (!provider) {
|
|
104
|
+
// :aa TODO string table
|
|
105
|
+
throw new Error('No crypto wallet found. Please install it.')
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
await window.ethereum.send('eth_requestAccounts')
|
|
109
|
+
|
|
110
|
+
const signer = await provider.getSigner()
|
|
111
|
+
ethers.getAddress(process.env.NEXT_PUBLIC_ETH_PAYMENT_ADDRESS ?? '')
|
|
112
|
+
const tx = await signer.sendTransaction({
|
|
113
|
+
to: process.env.NEXT_PUBLIC_ETH_PAYMENT_ADDRESS,
|
|
114
|
+
value: ethers.parseEther(ether.toString())
|
|
115
|
+
})
|
|
116
|
+
console.log({ ether, addr: process.env.NEXT_PUBLIC_ETH_PAYMENT_ADDRESS })
|
|
117
|
+
console.log('tx', tx)
|
|
118
|
+
setCurrentStep(3)
|
|
119
|
+
} catch (err) {
|
|
120
|
+
console.log(err)
|
|
121
|
+
// :aa TODO string table
|
|
122
|
+
toast({title: 'Not enough funds in your wallet'})
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const payWidget = !!!(auth.user?.walletAddress) ? (
|
|
127
|
+
<div className='w-full mx-auto max-w-[20rem]'>
|
|
128
|
+
<Button variant='outline' className='w-full flex items-center gap-2' onClick={auth.associateWallet.bind(auth)}>
|
|
129
|
+
<EthIconFromAuth height={20}/>Connect your wallet
|
|
130
|
+
</Button>
|
|
131
|
+
</div>
|
|
132
|
+
) : (
|
|
133
|
+
<div className='flex flex-col gap-2 w-full mx-auto max-w-[20rem]'>
|
|
134
|
+
<div>Cart value: {formatPrice(c.cartTotal)}</div>
|
|
135
|
+
<Select onValueChange={(token) => {/*ONLY ETH setSelectedToken(token) */}} defaultValue='eth'>
|
|
136
|
+
<SelectTrigger>
|
|
137
|
+
<SelectValue defaultValue='eth' />
|
|
138
|
+
</SelectTrigger>
|
|
139
|
+
<SelectContent>
|
|
140
|
+
<SelectGroup>
|
|
141
|
+
<SelectItem value='eth'><div className='flex items-center gap-2'><Eth height={14}/>ETH</div></SelectItem>
|
|
142
|
+
{/* <SelectItem value='btc' ><div className='flex items-center gap-2'><Btc height={14}/>BTC</div></SelectItem>
|
|
143
|
+
<SelectItem value='usdt' ><div className='flex items-center gap-2'><Usdt height={14}/>USDT</div></SelectItem> */}
|
|
144
|
+
</SelectGroup>
|
|
145
|
+
</SelectContent>
|
|
146
|
+
</Select>
|
|
147
|
+
<div>Available funds in your wallet: {availableAmount} ETH</div>
|
|
148
|
+
<div>
|
|
149
|
+
<Input value={amount ? amount/(10**18) : amount} contentEditable={false}/>
|
|
150
|
+
<div className='relative flex items-center gap-2 -top-[32px] justify-end px-2 py-1 rounded-lg bg-muted-4 w-fit text-xs float-right mr-3'><Eth height={10}/>ETH</div>
|
|
151
|
+
</div>
|
|
152
|
+
<Button
|
|
153
|
+
onClick={() => sendPayment(amount ? amount/(10**18) : 0)}
|
|
154
|
+
disabled={!amount || loadingPrice}
|
|
155
|
+
>
|
|
156
|
+
Pay now
|
|
157
|
+
</Button>
|
|
158
|
+
</div>
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
return (
|
|
162
|
+
<div className='flex flex-col gap-6'>
|
|
163
|
+
{payWidget}
|
|
164
|
+
<div className='flex gap-4 items-center mt-6'>
|
|
165
|
+
<Button variant='outline' onClick={() => setCurrentStep(1)} className='mx-auto w-full'>Back</Button>
|
|
166
|
+
<Button onClick={() => setCurrentStep(3)} className='mx-auto w-full'>Continue (Pay later)</Button>
|
|
167
|
+
</div>
|
|
168
|
+
</div>
|
|
169
|
+
)
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
export default PayWithCrypto
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
Form,
|
|
5
|
+
FormControl,
|
|
6
|
+
FormField,
|
|
7
|
+
FormItem,
|
|
8
|
+
FormLabel,
|
|
9
|
+
FormMessage,
|
|
10
|
+
} from '@hanzo/ui/primitives/form'
|
|
11
|
+
import { Input, Button, Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@hanzo/ui/primitives'
|
|
12
|
+
import { BookUser } from 'lucide-react'
|
|
13
|
+
import { EnhHeadingBlockComponent, type EnhHeadingBlock } from '@hanzo/ui/blocks'
|
|
14
|
+
import { useAuth } from '@hanzo/auth/service'
|
|
15
|
+
import * as z from 'zod'
|
|
16
|
+
import { useForm } from 'react-hook-form'
|
|
17
|
+
import { zodResolver } from '@hookform/resolvers/zod'
|
|
18
|
+
import { useCommerce } from '../..'
|
|
19
|
+
import { countries } from './countries'
|
|
20
|
+
|
|
21
|
+
const shippingFormSchema = z.object({
|
|
22
|
+
firstName: z.string().min(2, 'First name must be at least 2 characters.'),
|
|
23
|
+
lastName: z.string().min(2, 'Last name must be at least 2 characters.'),
|
|
24
|
+
addressLine1: z.string().min(2, 'Address must be at least 2 characters.'),
|
|
25
|
+
addressLine2: z.string().optional(),
|
|
26
|
+
zipCode: z.string().min(2, 'Zip code is invalid.'),
|
|
27
|
+
city: z.string().min(2, 'City is invalid.'),
|
|
28
|
+
state: z.string().optional(),
|
|
29
|
+
country: z.string().min(2, 'Country is invalid.'),
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
const ShippingInfo: React.FC<{
|
|
33
|
+
orderId?: string,
|
|
34
|
+
paymentMethod?: string,
|
|
35
|
+
setCurrentStep: (currentStep: number) => void
|
|
36
|
+
}> = ({
|
|
37
|
+
orderId,
|
|
38
|
+
paymentMethod,
|
|
39
|
+
setCurrentStep
|
|
40
|
+
}) => {
|
|
41
|
+
const auth = useAuth()
|
|
42
|
+
const cmmc = useCommerce()
|
|
43
|
+
|
|
44
|
+
const shippingForm = useForm<z.infer<typeof shippingFormSchema>>({
|
|
45
|
+
resolver: zodResolver(shippingFormSchema),
|
|
46
|
+
defaultValues: {
|
|
47
|
+
firstName: '',
|
|
48
|
+
lastName: '',
|
|
49
|
+
addressLine1: '',
|
|
50
|
+
addressLine2: '',
|
|
51
|
+
zipCode: '',
|
|
52
|
+
city: '',
|
|
53
|
+
state: '',
|
|
54
|
+
country: '',
|
|
55
|
+
},
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
const onSubmit = async (values: z.infer<typeof shippingFormSchema>) => {
|
|
59
|
+
if (auth.user && orderId && paymentMethod) {
|
|
60
|
+
await cmmc.updateOrder(orderId, auth.user.email, paymentMethod, values)
|
|
61
|
+
}
|
|
62
|
+
setCurrentStep(4)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
<Form {...shippingForm}>
|
|
67
|
+
<form onSubmit={shippingForm.handleSubmit(onSubmit)} className='text-left'>
|
|
68
|
+
<div className='flex flex-col gap-4'>
|
|
69
|
+
<div className='flex gap-4 items-center'>
|
|
70
|
+
<BookUser />
|
|
71
|
+
<EnhHeadingBlockComponent block={{blockType: 'enh-heading',
|
|
72
|
+
heading: { text: `Shipping address`, level: 4 },
|
|
73
|
+
} as EnhHeadingBlock}/>
|
|
74
|
+
</div>
|
|
75
|
+
<div className='flex gap-4 items-end'>
|
|
76
|
+
<FormField
|
|
77
|
+
control={shippingForm.control}
|
|
78
|
+
name='firstName'
|
|
79
|
+
render={({ field }) => (
|
|
80
|
+
<FormItem className='space-y-1 w-full'>
|
|
81
|
+
<FormLabel>First name</FormLabel>
|
|
82
|
+
<FormControl>
|
|
83
|
+
<Input {...field} />
|
|
84
|
+
</FormControl>
|
|
85
|
+
<FormMessage />
|
|
86
|
+
</FormItem>
|
|
87
|
+
)}
|
|
88
|
+
/>
|
|
89
|
+
<FormField
|
|
90
|
+
control={shippingForm.control}
|
|
91
|
+
name='lastName'
|
|
92
|
+
render={({ field }) => (
|
|
93
|
+
<FormItem className='space-y-1 w-full'>
|
|
94
|
+
<FormLabel>Last name</FormLabel>
|
|
95
|
+
<FormControl>
|
|
96
|
+
<Input {...field} />
|
|
97
|
+
</FormControl>
|
|
98
|
+
<FormMessage />
|
|
99
|
+
</FormItem>
|
|
100
|
+
)}
|
|
101
|
+
/>
|
|
102
|
+
</div>
|
|
103
|
+
<div className='flex gap-4 items-end'>
|
|
104
|
+
<FormField
|
|
105
|
+
control={shippingForm.control}
|
|
106
|
+
name='addressLine1'
|
|
107
|
+
render={({ field }) => (
|
|
108
|
+
<FormItem className='space-y-1 w-full'>
|
|
109
|
+
<FormLabel>Address line 1</FormLabel>
|
|
110
|
+
<FormControl>
|
|
111
|
+
<Input {...field} />
|
|
112
|
+
</FormControl>
|
|
113
|
+
<FormMessage />
|
|
114
|
+
</FormItem>
|
|
115
|
+
)}
|
|
116
|
+
/>
|
|
117
|
+
<FormField
|
|
118
|
+
control={shippingForm.control}
|
|
119
|
+
name='addressLine2'
|
|
120
|
+
render={({ field }) => (
|
|
121
|
+
<FormItem className='space-y-1 w-full'>
|
|
122
|
+
<FormLabel>Address line 2 (optional)</FormLabel>
|
|
123
|
+
<FormControl>
|
|
124
|
+
<Input {...field} />
|
|
125
|
+
</FormControl>
|
|
126
|
+
<FormMessage />
|
|
127
|
+
</FormItem>
|
|
128
|
+
)}
|
|
129
|
+
/>
|
|
130
|
+
</div>
|
|
131
|
+
<div className='flex gap-4 items-end'>
|
|
132
|
+
<FormField
|
|
133
|
+
control={shippingForm.control}
|
|
134
|
+
name='zipCode'
|
|
135
|
+
render={({ field }) => (
|
|
136
|
+
<FormItem className='space-y-1 w-full'>
|
|
137
|
+
<FormLabel>Zip code</FormLabel>
|
|
138
|
+
<FormControl>
|
|
139
|
+
<Input {...field} />
|
|
140
|
+
</FormControl>
|
|
141
|
+
<FormMessage />
|
|
142
|
+
</FormItem>
|
|
143
|
+
)}
|
|
144
|
+
/>
|
|
145
|
+
<FormField
|
|
146
|
+
control={shippingForm.control}
|
|
147
|
+
name='city'
|
|
148
|
+
render={({ field }) => (
|
|
149
|
+
<FormItem className='space-y-1 w-full'>
|
|
150
|
+
<FormLabel>City</FormLabel>
|
|
151
|
+
<FormControl>
|
|
152
|
+
<Input {...field} />
|
|
153
|
+
</FormControl>
|
|
154
|
+
<FormMessage />
|
|
155
|
+
</FormItem>
|
|
156
|
+
)}
|
|
157
|
+
/>
|
|
158
|
+
</div>
|
|
159
|
+
<div className='flex gap-4 items-start'>
|
|
160
|
+
<FormField
|
|
161
|
+
control={shippingForm.control}
|
|
162
|
+
name='state'
|
|
163
|
+
render={({ field }) => (
|
|
164
|
+
<FormItem className='space-y-1 w-full'>
|
|
165
|
+
<FormLabel>State (Optional)</FormLabel>
|
|
166
|
+
<FormControl>
|
|
167
|
+
<Input {...field} />
|
|
168
|
+
</FormControl>
|
|
169
|
+
<FormMessage />
|
|
170
|
+
</FormItem>
|
|
171
|
+
)}
|
|
172
|
+
/>
|
|
173
|
+
<FormField
|
|
174
|
+
control={shippingForm.control}
|
|
175
|
+
name='country'
|
|
176
|
+
render={({ field }) => (
|
|
177
|
+
<FormItem className='space-y-1 w-full'>
|
|
178
|
+
<FormLabel>Country</FormLabel>
|
|
179
|
+
<Select onValueChange={field.onChange} defaultValue={field.value}>
|
|
180
|
+
<FormControl>
|
|
181
|
+
<SelectTrigger>
|
|
182
|
+
<SelectValue placeholder='Select a country' />
|
|
183
|
+
</SelectTrigger>
|
|
184
|
+
</FormControl>
|
|
185
|
+
<SelectContent>
|
|
186
|
+
{countries.map((country) => (
|
|
187
|
+
<SelectItem key={country.id} value={country.name}>
|
|
188
|
+
{country.name}
|
|
189
|
+
</SelectItem>
|
|
190
|
+
))}
|
|
191
|
+
</SelectContent>
|
|
192
|
+
</Select>
|
|
193
|
+
</FormItem>
|
|
194
|
+
)}
|
|
195
|
+
/>
|
|
196
|
+
</div>
|
|
197
|
+
</div>
|
|
198
|
+
|
|
199
|
+
<div className='flex gap-4 items-center mt-6'>
|
|
200
|
+
<Button variant='outline' onClick={() => setCurrentStep(2)} className='mx-auto w-full'>Back</Button>
|
|
201
|
+
<Button type='submit' className='mx-auto w-full'>Confirm order</Button>
|
|
202
|
+
</div>
|
|
203
|
+
</form>
|
|
204
|
+
</Form>
|
|
205
|
+
)
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export default ShippingInfo
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import Link from 'next/link'
|
|
3
|
+
import { ApplyTypography } from '@hanzo/ui/primitives'
|
|
4
|
+
|
|
5
|
+
const ThankYou: React.FC = () => (
|
|
6
|
+
<ApplyTypography className='flex flex-col gap-4 text-center mt-10'>
|
|
7
|
+
<h3>Thank you for your order!</h3>
|
|
8
|
+
<h6>Once your payment has been confirmed we will send additional information to your email.</h6>
|
|
9
|
+
<p>
|
|
10
|
+
While you wait, we cordially invite you to join the <Link href='https://warpcast.com/~/channel/lux'>Lux Channel</Link> on <Link href='https://warpcast.com/~/invite-page/227706?id=fbc9ca91'>Warpcast</Link>.
|
|
11
|
+
</p>
|
|
12
|
+
</ApplyTypography>
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
export default ThankYou
|
|
@@ -17,6 +17,7 @@ const FacetsWidget: React.FC<PropsWithChildren & {
|
|
|
17
17
|
id?: string
|
|
18
18
|
className?: string
|
|
19
19
|
tabSize?: string
|
|
20
|
+
childrenAfter?: boolean
|
|
20
21
|
}> = ({
|
|
21
22
|
children,
|
|
22
23
|
facets,
|
|
@@ -27,11 +28,13 @@ const FacetsWidget: React.FC<PropsWithChildren & {
|
|
|
27
28
|
isMobile=false,
|
|
28
29
|
className='',
|
|
29
30
|
tabSize,
|
|
30
|
-
id='FacetsWidget'
|
|
31
|
+
id='FacetsWidget',
|
|
32
|
+
childrenAfter=true
|
|
31
33
|
}) => {
|
|
32
34
|
const horiz = className.includes('flex-row')
|
|
33
35
|
return (
|
|
34
36
|
<div id={id} className={className} >
|
|
37
|
+
{!childrenAfter && children}
|
|
35
38
|
{Object.keys(facets).map((key, i) => (
|
|
36
39
|
<FacetTogglesWidget
|
|
37
40
|
key={i}
|
|
@@ -44,7 +47,7 @@ const FacetsWidget: React.FC<PropsWithChildren & {
|
|
|
44
47
|
tabSize={tabSize}
|
|
45
48
|
/>
|
|
46
49
|
))}
|
|
47
|
-
{children}
|
|
50
|
+
{childrenAfter && children}
|
|
48
51
|
</div>
|
|
49
52
|
)
|
|
50
53
|
}
|
package/components/index.ts
CHANGED
|
@@ -8,4 +8,5 @@ export { default as ProductSelectionMobilePicker } from './product-selection-mob
|
|
|
8
8
|
export { default as ProductSelectionRadioGroup } from './product-selection-radio-group'
|
|
9
9
|
export { default as SelectCategoryAndItemWidget } from './select-category-and-item-widget'
|
|
10
10
|
export { default as SelectItemInCategoryView } from './select-item-in-category-view'
|
|
11
|
+
export { default as Checkout } from './checkout'
|
|
11
12
|
export { Icons } from './Icons'
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hanzo/commerce",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.11",
|
|
4
4
|
"description": "Library with shopping cart components.",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"registry": "https://registry.npmjs.org/",
|
|
@@ -28,9 +28,12 @@
|
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@hookform/resolvers": "^3.3.2",
|
|
31
|
+
"ethers": "^6.11.1",
|
|
31
32
|
"lucide-react": "^0.307.0",
|
|
32
33
|
"next-usequerystate": "^1.17.0",
|
|
33
|
-
"react-
|
|
34
|
+
"react-hook-form": "^7.51.0",
|
|
35
|
+
"react-mobile-picker": "^1.0.0",
|
|
36
|
+
"zod": "3.21.4"
|
|
34
37
|
},
|
|
35
38
|
"peerDependencies": {
|
|
36
39
|
"@hanzo/auth": "^1.0.12",
|