@hanzo/commerce 4.9.0 → 5.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/add-to-cart-widget.tsx +0 -1
- package/components/buy-item/select-category-and-item-widget.tsx +2 -2
- package/components/buy-item/select-category-item-card.tsx +2 -2
- package/components/{checkout-panel/cart-accordian.tsx → cart-accordian.tsx} +21 -10
- package/components/cart-panel/cart-line-item.tsx +22 -16
- package/components/cart-panel/index.tsx +90 -34
- package/components/cart-panel/promo-code.tsx +98 -0
- package/components/category-item-radio-selector.tsx +2 -2
- package/components/category-item-scroll-selector.tsx +2 -2
- package/components/index.ts +4 -1
- package/components/item-carousel.tsx +80 -0
- package/components/{checkout-panel/steps/payment/cards/index.tsx → payment-step-form/card-icon-row.tsx} +8 -7
- package/components/{checkout-panel/steps/payment → payment-step-form}/cc-button.tsx +1 -1
- package/components/payment-step-form/contact-form.tsx +49 -0
- package/components/{checkout-panel/steps/payment → payment-step-form}/index.tsx +29 -56
- package/components/payment-step-form/methods/bank-transfer.tsx +79 -0
- package/components/payment-step-form/methods/card.tsx +237 -0
- package/components/{checkout-panel/steps/payment/pay-with-crypto.tsx → payment-step-form/methods/crypto.tsx} +10 -24
- package/components/payment-step-form/methods/index.ts +23 -0
- package/components/product-card.tsx +2 -2
- package/components/select-category-item-panel.tsx +2 -2
- package/components/{checkout-panel/steps/shipping-info.tsx → shipping-step-form.tsx} +6 -8
- package/index.ts +1 -1
- package/package.json +2 -2
- package/service/context.tsx +1 -0
- package/service/impls/standalone/standalone-service.ts +56 -4
- package/types/checkout.ts +42 -1
- package/types/commerce-service.ts +9 -1
- package/types/index.ts +2 -1
- package/types/promo.ts +10 -0
- package/util/index.ts +3 -2
- package/util/promo-codes.ts +46 -0
- package/components/cart-panel/products-carousel.tsx +0 -60
- package/components/checkout-panel/_confirm-order.tsx_unused +0 -143
- package/components/checkout-panel/close-button.tsx +0 -24
- package/components/checkout-panel/desktop.tsx +0 -46
- package/components/checkout-panel/icons/bag-icon.tsx +0 -10
- package/components/checkout-panel/index.tsx +0 -108
- package/components/checkout-panel/mobile.tsx +0 -41
- package/components/checkout-panel/steps/payment/contact-info.tsx +0 -56
- package/components/checkout-panel/steps/payment/pay-with-bank-transfer.tsx +0 -110
- package/components/checkout-panel/steps/payment/pay-with-card.tsx +0 -246
- package/components/checkout-panel/steps/thank-you.tsx +0 -18
- package/components/checkout-panel/steps/types.ts +0 -14
- /package/components/{checkout-panel/steps/payment/cards → payment-step-form/card-icons}/amex.tsx +0 -0
- /package/components/{checkout-panel/steps/payment/cards → payment-step-form/card-icons}/diners-club.tsx +0 -0
- /package/components/{checkout-panel/steps/payment/cards → payment-step-form/card-icons}/discover.tsx +0 -0
- /package/components/{checkout-panel/steps/payment/cards → payment-step-form/card-icons}/jcb.tsx +0 -0
- /package/components/{checkout-panel/steps/payment/cards → payment-step-form/card-icons}/mastercard.tsx +0 -0
- /package/components/{checkout-panel/steps/payment/cards → payment-step-form/card-icons}/visa.tsx +0 -0
- /package/components/{checkout-panel/icons → payment-step-form/crypto-icons}/btc.tsx +0 -0
- /package/components/{checkout-panel/icons → payment-step-form/crypto-icons}/eth.tsx +0 -0
- /package/components/{checkout-panel/icons → payment-step-form/crypto-icons}/usdt.tsx +0 -0
|
@@ -14,7 +14,8 @@ import type {
|
|
|
14
14
|
Category,
|
|
15
15
|
LineItem,
|
|
16
16
|
FacetsValue,
|
|
17
|
-
FacetValueDesc
|
|
17
|
+
FacetValueDesc,
|
|
18
|
+
Promo
|
|
18
19
|
} from '../../../types'
|
|
19
20
|
|
|
20
21
|
import {
|
|
@@ -43,6 +44,7 @@ class StandaloneService
|
|
|
43
44
|
private _categoryMap = new Map<string, Category>()
|
|
44
45
|
private _rootFacet: FacetValueDesc
|
|
45
46
|
private _selectedFacets: FacetsValue = {}
|
|
47
|
+
private _promo: Promo | null = null
|
|
46
48
|
|
|
47
49
|
private _options : StandaloneServiceOptions
|
|
48
50
|
private _currentItem: ActualLineItem | undefined = undefined
|
|
@@ -73,23 +75,28 @@ class StandaloneService
|
|
|
73
75
|
makeObservable<
|
|
74
76
|
StandaloneService,
|
|
75
77
|
'_selectedFacets' |
|
|
76
|
-
'_currentItem'
|
|
78
|
+
'_currentItem' |
|
|
79
|
+
'_promo'
|
|
77
80
|
>(this, {
|
|
78
81
|
_selectedFacets : observable.deep,
|
|
79
82
|
_currentItem: observable,
|
|
83
|
+
_promo: observable,
|
|
80
84
|
})
|
|
81
85
|
|
|
82
86
|
makeObservable(this, {
|
|
83
87
|
cartItems: computed,
|
|
84
88
|
cartQuantity: computed,
|
|
85
|
-
cartTotal: computed,
|
|
89
|
+
cartTotal: computed,
|
|
90
|
+
promoAppliedCartTotal: computed,
|
|
86
91
|
cartEmpty: computed,
|
|
87
92
|
specifiedItems: computed,
|
|
88
93
|
specifiedCategories: computed,
|
|
89
94
|
setCurrentItem: action,
|
|
90
95
|
currentItem: computed,
|
|
91
96
|
item: computed,
|
|
92
|
-
facetsValue: computed
|
|
97
|
+
facetsValue: computed,
|
|
98
|
+
appliedPromo: computed,
|
|
99
|
+
setAppliedPromo: action,
|
|
93
100
|
/* NOT setFacets. It implements it's action mechanism */
|
|
94
101
|
})
|
|
95
102
|
}
|
|
@@ -181,6 +188,43 @@ class StandaloneService
|
|
|
181
188
|
)
|
|
182
189
|
}
|
|
183
190
|
|
|
191
|
+
_promoValue_unsafe(value: number): number {
|
|
192
|
+
if (this._promo!.type === 'percent') {
|
|
193
|
+
return value * (1 - this._promo!.value / 100)
|
|
194
|
+
}
|
|
195
|
+
return value - this._promo!.value
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
get promoAppliedCartTotal(): number {
|
|
199
|
+
if (!this._promo) {
|
|
200
|
+
return this.cartTotal
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (!this._promo.skus) {
|
|
204
|
+
return this._promoValue_unsafe(this.cartTotal)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
let total = this.cartItems.reduce(
|
|
208
|
+
(total, item) => {
|
|
209
|
+
const itemPrice = this._promo!.skus!.includes(item.sku) ?
|
|
210
|
+
this._promoValue_unsafe(item.price)
|
|
211
|
+
:
|
|
212
|
+
item.price
|
|
213
|
+
return total + itemPrice * item.quantity
|
|
214
|
+
},
|
|
215
|
+
0
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
return total
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
itemPromoPrice(item: LineItem): number | undefined {
|
|
222
|
+
if (this._promo && (!this._promo.skus || this._promo.skus.includes(item.sku) )) {
|
|
223
|
+
return this._promoValue_unsafe(item.price)
|
|
224
|
+
}
|
|
225
|
+
return undefined
|
|
226
|
+
}
|
|
227
|
+
|
|
184
228
|
get cartQuantity(): number {
|
|
185
229
|
return this.cartItems.reduce(
|
|
186
230
|
(total, item) => (total + item.quantity),
|
|
@@ -188,6 +232,14 @@ class StandaloneService
|
|
|
188
232
|
)
|
|
189
233
|
}
|
|
190
234
|
|
|
235
|
+
get appliedPromo(): Promo | null {
|
|
236
|
+
return this._promo
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
setAppliedPromo(promo: Promo | null): void {
|
|
240
|
+
this._promo = promo
|
|
241
|
+
}
|
|
242
|
+
|
|
191
243
|
setCurrentItem(skuToFind: string | undefined): boolean {
|
|
192
244
|
|
|
193
245
|
if (skuToFind === undefined || skuToFind.length === 0) {
|
package/types/checkout.ts
CHANGED
|
@@ -1,6 +1,47 @@
|
|
|
1
|
+
import type { ComponentType } from 'react'
|
|
2
|
+
import type { UseFormReturn } from 'react-hook-form'
|
|
1
3
|
|
|
2
4
|
type TransactionStatus = 'unpaid' | 'paid' | 'confirmed' | 'error'
|
|
3
5
|
|
|
6
|
+
interface CheckoutStepComponentProps {
|
|
7
|
+
onDone: () => void
|
|
8
|
+
orderId: string | undefined
|
|
9
|
+
setOrderId: (orderId: string | undefined) => void
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface CheckoutStep {
|
|
13
|
+
name: string
|
|
14
|
+
label?: string
|
|
15
|
+
Comp: ComponentType<CheckoutStepComponentProps>
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
type ContactFormType = UseFormReturn<{
|
|
19
|
+
name: string
|
|
20
|
+
email: string
|
|
21
|
+
}, any, {
|
|
22
|
+
name: string
|
|
23
|
+
email: string
|
|
24
|
+
}>
|
|
25
|
+
|
|
26
|
+
interface PaymentMethodComponentProps {
|
|
27
|
+
onDone: () => void
|
|
28
|
+
transactionStatus: TransactionStatus
|
|
29
|
+
setTransactionStatus: (status: TransactionStatus) => void
|
|
30
|
+
storePaymentInfo: (paymentInfo: any) => Promise<void>
|
|
31
|
+
contactForm: ContactFormType
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface PaymentMethodDesc {
|
|
35
|
+
value: string
|
|
36
|
+
label: string
|
|
37
|
+
Comp: ComponentType<PaymentMethodComponentProps>
|
|
38
|
+
}
|
|
39
|
+
|
|
4
40
|
export {
|
|
5
|
-
type TransactionStatus
|
|
41
|
+
type TransactionStatus,
|
|
42
|
+
type CheckoutStepComponentProps,
|
|
43
|
+
type CheckoutStep,
|
|
44
|
+
type PaymentMethodComponentProps,
|
|
45
|
+
type PaymentMethodDesc,
|
|
46
|
+
type ContactFormType,
|
|
6
47
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { LineItem, ObsLineItemRef } from './line-item'
|
|
2
2
|
import type { FacetValueDesc, FacetsValue } from './facet'
|
|
3
3
|
import type Category from './category'
|
|
4
|
+
import type Promo from './promo'
|
|
4
5
|
|
|
5
6
|
interface CommerceService extends ObsLineItemRef {
|
|
6
7
|
|
|
@@ -8,10 +9,17 @@ interface CommerceService extends ObsLineItemRef {
|
|
|
8
9
|
get cartItems(): LineItem[]
|
|
9
10
|
/** Total of all quantities of all items in cart */
|
|
10
11
|
get cartQuantity(): number
|
|
11
|
-
/** Total of all prices
|
|
12
|
+
/** Total of all prices x quantities of items in cart */
|
|
12
13
|
get cartTotal(): number
|
|
13
14
|
|
|
15
|
+
get promoAppliedCartTotal(): number
|
|
16
|
+
|
|
14
17
|
get cartEmpty(): boolean
|
|
18
|
+
|
|
19
|
+
get appliedPromo(): Promo | null
|
|
20
|
+
setAppliedPromo(promo: Promo | null): void
|
|
21
|
+
/** returns the price with promo applied, of undefined if no promo or promo does not apply */
|
|
22
|
+
itemPromoPrice(item: LineItem): number | undefined
|
|
15
23
|
|
|
16
24
|
getCartCategorySubtotal(categoryId: string): number
|
|
17
25
|
|
package/types/index.ts
CHANGED
|
@@ -3,8 +3,9 @@ export type { default as Category } from './category'
|
|
|
3
3
|
export type { default as CommerceService } from './commerce-service'
|
|
4
4
|
export type { default as ItemSelector } from './item-selector'
|
|
5
5
|
export type { default as Product } from './product'
|
|
6
|
+
export type { default as Promo } from './promo'
|
|
6
7
|
|
|
7
|
-
export
|
|
8
|
+
export * from './checkout'
|
|
8
9
|
export * from './line-item'
|
|
9
10
|
export * from './facet'
|
|
10
11
|
export * from './string-mutator'
|
package/types/promo.ts
ADDED
package/util/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { StringMutator, CommerceService } from '../types'
|
|
1
|
+
import type { StringMutator, CommerceService, Promo } from '../types'
|
|
2
2
|
|
|
3
3
|
export function toTitleCase(str: string) {
|
|
4
4
|
return str.replace(
|
|
@@ -20,7 +20,7 @@ export function unslugify(str: string) {
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
|
|
23
|
-
export function
|
|
23
|
+
export function formatCurrencyValue(price: number): string {
|
|
24
24
|
const str = price.toLocaleString('en-US', {
|
|
25
25
|
style: 'currency',
|
|
26
26
|
currency: 'USD',
|
|
@@ -28,6 +28,7 @@ export function formatPrice(price: number): string {
|
|
|
28
28
|
return (str.endsWith('.00')) ? str.replace('.00', '') : str
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
|
|
31
32
|
export const getFacetValuesMutator = (level: number, cmmc: CommerceService): StringMutator => {
|
|
32
33
|
|
|
33
34
|
const setLevel = (value: string, level: number ): void => {
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
'use server'
|
|
2
|
+
|
|
3
|
+
import type { Promo } from '../types'
|
|
4
|
+
|
|
5
|
+
const PROMO_CODES: Promo[] = [
|
|
6
|
+
{
|
|
7
|
+
type: 'percent',
|
|
8
|
+
code: 'ANGEL',
|
|
9
|
+
value: 20
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
type: 'percent',
|
|
13
|
+
code: 'NASSER',
|
|
14
|
+
value: 20
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
type: 'percent',
|
|
18
|
+
code: 'DARA',
|
|
19
|
+
value: 20
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
type: 'percent',
|
|
23
|
+
code: 'ROSEY',
|
|
24
|
+
value: 20
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
type: 'percent',
|
|
28
|
+
code: 'IYKYK',
|
|
29
|
+
value: 20
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
type: 'percent',
|
|
33
|
+
code: 'GENESIS',
|
|
34
|
+
value: 99,
|
|
35
|
+
skus: ['LXM-PS-PS']
|
|
36
|
+
}
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
// TODO: implement a real API call
|
|
40
|
+
const getPromoFromApi = async (code: string) => {
|
|
41
|
+
return PROMO_CODES.find(promo => promo.code === code)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export {
|
|
45
|
+
getPromoFromApi as default
|
|
46
|
+
}
|
|
@@ -1,60 +0,0 @@
|
|
|
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
|
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
import React from 'react'
|
|
3
|
-
import type { UseFormReturn } from 'react-hook-form'
|
|
4
|
-
import { BookUser, Lock, User } from 'lucide-react'
|
|
5
|
-
|
|
6
|
-
import { ApplyTypography, Button } from '@hanzo/ui/primitives'
|
|
7
|
-
import { EnhHeadingBlockComponent, type EnhHeadingBlock } from '@hanzo/ui/blocks'
|
|
8
|
-
|
|
9
|
-
const ConfirmOrder: React.FC<{
|
|
10
|
-
paymentForm: UseFormReturn<{
|
|
11
|
-
name: string;
|
|
12
|
-
email: string;
|
|
13
|
-
cardName: string;
|
|
14
|
-
cardNumber: string;
|
|
15
|
-
cardExpiryDate: string;
|
|
16
|
-
cardCvc: string;
|
|
17
|
-
}, any, {
|
|
18
|
-
name: string;
|
|
19
|
-
email: string;
|
|
20
|
-
cardName: string;
|
|
21
|
-
cardNumber: string;
|
|
22
|
-
cardExpiryDate: string;
|
|
23
|
-
cardCvc: string;
|
|
24
|
-
}>,
|
|
25
|
-
shippingForm: UseFormReturn<{
|
|
26
|
-
firstName: string;
|
|
27
|
-
lastName: string;
|
|
28
|
-
addressLine1: string;
|
|
29
|
-
zipCode: string;
|
|
30
|
-
city: string;
|
|
31
|
-
country: string;
|
|
32
|
-
addressLine2?: string | undefined;
|
|
33
|
-
state?: string | undefined;
|
|
34
|
-
}, any, {
|
|
35
|
-
firstName: string;
|
|
36
|
-
lastName: string;
|
|
37
|
-
addressLine1: string;
|
|
38
|
-
zipCode: string;
|
|
39
|
-
city: string;
|
|
40
|
-
country: string;
|
|
41
|
-
addressLine2?: string | undefined;
|
|
42
|
-
state?: string | undefined;
|
|
43
|
-
}>,
|
|
44
|
-
setStep: (step: number) => void,
|
|
45
|
-
onPayClick: () => void
|
|
46
|
-
}> = ({
|
|
47
|
-
paymentForm,
|
|
48
|
-
shippingForm,
|
|
49
|
-
setStep,
|
|
50
|
-
onPayClick
|
|
51
|
-
}) => {
|
|
52
|
-
|
|
53
|
-
const paymentFormValues = paymentForm.getValues()
|
|
54
|
-
const shippingFormValues = shippingForm.getValues()
|
|
55
|
-
|
|
56
|
-
return (
|
|
57
|
-
<div className='flex flex-col gap-6'>
|
|
58
|
-
<div className='flex gap-4 items-center'>
|
|
59
|
-
<User />
|
|
60
|
-
<EnhHeadingBlockComponent block={{blockType: 'enh-heading',
|
|
61
|
-
heading: { text: `Contact Details`, level: 4 },
|
|
62
|
-
} as EnhHeadingBlock}/>
|
|
63
|
-
</div>
|
|
64
|
-
<EnhHeadingBlockComponent block={{blockType: 'enh-heading',
|
|
65
|
-
heading: { text: 'Contact name', level: 6 },
|
|
66
|
-
byline: { text: paymentFormValues.name, level: 0 },
|
|
67
|
-
} as EnhHeadingBlock}/>
|
|
68
|
-
<EnhHeadingBlockComponent block={{blockType: 'enh-heading',
|
|
69
|
-
heading: { text: 'Contact email', level: 6 },
|
|
70
|
-
byline: { text: paymentFormValues.email, level: 0 },
|
|
71
|
-
} as EnhHeadingBlock}/>
|
|
72
|
-
|
|
73
|
-
<div className='flex gap-4 items-center'>
|
|
74
|
-
<ApplyTypography>
|
|
75
|
-
<h4 className='flex gap-4 items-center'><Lock /> Payment Info</h4>
|
|
76
|
-
<p>Guaranteed safe & secure checkout.</p>
|
|
77
|
-
</ApplyTypography>
|
|
78
|
-
</div>
|
|
79
|
-
<EnhHeadingBlockComponent block={{blockType: 'enh-heading',
|
|
80
|
-
heading: { text: 'Name on card', level: 6 },
|
|
81
|
-
byline: { text: paymentFormValues.cardName, level: 0 },
|
|
82
|
-
} as EnhHeadingBlock}/>
|
|
83
|
-
<EnhHeadingBlockComponent block={{blockType: 'enh-heading',
|
|
84
|
-
heading: { text: 'Card number', level: 6 },
|
|
85
|
-
byline: { text: paymentFormValues.cardNumber, level: 0 },
|
|
86
|
-
} as EnhHeadingBlock}/>
|
|
87
|
-
<EnhHeadingBlockComponent block={{blockType: 'enh-heading',
|
|
88
|
-
heading: { text: 'Card expiry date', level: 6 },
|
|
89
|
-
byline: { text: paymentFormValues.cardExpiryDate, level: 0 },
|
|
90
|
-
} as EnhHeadingBlock}/>
|
|
91
|
-
<EnhHeadingBlockComponent block={{blockType: 'enh-heading',
|
|
92
|
-
heading: { text: 'Card CVC', level: 6 },
|
|
93
|
-
byline: { text: paymentFormValues.cardCvc, level: 0 },
|
|
94
|
-
} as EnhHeadingBlock}/>
|
|
95
|
-
|
|
96
|
-
<div className='flex gap-4 items-center'>
|
|
97
|
-
<BookUser />
|
|
98
|
-
<EnhHeadingBlockComponent block={{blockType: 'enh-heading',
|
|
99
|
-
heading: { text: `Shipping address`, level: 4 },
|
|
100
|
-
} as EnhHeadingBlock}/>
|
|
101
|
-
</div>
|
|
102
|
-
<EnhHeadingBlockComponent block={{blockType: 'enh-heading',
|
|
103
|
-
heading: { text: 'First name', level: 6 },
|
|
104
|
-
byline: { text: shippingFormValues.firstName, level: 0 },
|
|
105
|
-
} as EnhHeadingBlock}/>
|
|
106
|
-
<EnhHeadingBlockComponent block={{blockType: 'enh-heading',
|
|
107
|
-
heading: { text: 'Last name', level: 6 },
|
|
108
|
-
byline: { text: shippingFormValues.lastName, level: 0 },
|
|
109
|
-
} as EnhHeadingBlock}/>
|
|
110
|
-
<EnhHeadingBlockComponent block={{blockType: 'enh-heading',
|
|
111
|
-
heading: { text: 'Address line 1', level: 6 },
|
|
112
|
-
byline: { text: shippingFormValues.addressLine1, level: 0 },
|
|
113
|
-
} as EnhHeadingBlock}/>
|
|
114
|
-
<EnhHeadingBlockComponent block={{blockType: 'enh-heading',
|
|
115
|
-
heading: { text: 'Address line 2 (optional)', level: 6 },
|
|
116
|
-
byline: { text: shippingFormValues.addressLine2, level: 0 },
|
|
117
|
-
} as EnhHeadingBlock}/>
|
|
118
|
-
<EnhHeadingBlockComponent block={{blockType: 'enh-heading',
|
|
119
|
-
heading: { text: 'Zip code', level: 6 },
|
|
120
|
-
byline: { text: shippingFormValues.zipCode, level: 0 },
|
|
121
|
-
} as EnhHeadingBlock}/>
|
|
122
|
-
<EnhHeadingBlockComponent block={{blockType: 'enh-heading',
|
|
123
|
-
heading: { text: 'City', level: 6 },
|
|
124
|
-
byline: { text: shippingFormValues.city, level: 0 },
|
|
125
|
-
} as EnhHeadingBlock}/>
|
|
126
|
-
<EnhHeadingBlockComponent block={{blockType: 'enh-heading',
|
|
127
|
-
heading: { text: 'State (optional)', level: 6 },
|
|
128
|
-
byline: { text: shippingFormValues.state, level: 0 },
|
|
129
|
-
} as EnhHeadingBlock}/>
|
|
130
|
-
<EnhHeadingBlockComponent block={{blockType: 'enh-heading',
|
|
131
|
-
heading: { text: 'Country', level: 6 },
|
|
132
|
-
byline: { text: shippingFormValues.country, level: 0 },
|
|
133
|
-
} as EnhHeadingBlock}/>
|
|
134
|
-
|
|
135
|
-
<div className='flex justify-between mt-8'>
|
|
136
|
-
<Button variant='outline' onClick={() => setStep(1)}>Back</Button>
|
|
137
|
-
<Button onClick={onPayClick}>Pay</Button>
|
|
138
|
-
</div>
|
|
139
|
-
</div>
|
|
140
|
-
)
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
export default ConfirmOrder
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
import React from 'react'
|
|
3
|
-
|
|
4
|
-
import { ChevronLeft } from 'lucide-react'
|
|
5
|
-
import { Button } from '@hanzo/ui/primitives'
|
|
6
|
-
|
|
7
|
-
const CloseButton: React.FC<{
|
|
8
|
-
close: () => void
|
|
9
|
-
className?: string
|
|
10
|
-
}> = ({
|
|
11
|
-
close,
|
|
12
|
-
className=''
|
|
13
|
-
}) => (
|
|
14
|
-
<Button
|
|
15
|
-
variant='ghost'
|
|
16
|
-
size='icon'
|
|
17
|
-
onClick={close}
|
|
18
|
-
className={'group ' + className}
|
|
19
|
-
>
|
|
20
|
-
<ChevronLeft className='w-5 h-5 group-hover:scale-110 transition-scale transition-duration-300'/>
|
|
21
|
-
</Button>
|
|
22
|
-
)
|
|
23
|
-
|
|
24
|
-
export default CloseButton
|
|
@@ -1,46 +0,0 @@
|
|
|
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,10 +0,0 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import { type LucideProps } from 'lucide-react'
|
|
3
|
-
|
|
4
|
-
const BagIcon: React.FC<LucideProps> = (props: LucideProps) => (
|
|
5
|
-
<svg fill="currentColor" viewBox='0 0 20 23' {...props}>
|
|
6
|
-
<path fillRule="evenodd" d="M5 5a5 5 0 0 1 10 0v1h-2V5a3 3 0 1 0-6 0v1H5V5Zm0 1v4h2V6h6v4h2V6h3.5a1 1 0 0 1 1 1v15a1 1 0 0 1-1 1h-17a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1H5Z" clipRule="evenodd"/>
|
|
7
|
-
</svg>
|
|
8
|
-
)
|
|
9
|
-
|
|
10
|
-
export default BagIcon
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
import React, { useState } from 'react'
|
|
3
|
-
|
|
4
|
-
import { capitalize } from '@hanzo/ui/util'
|
|
5
|
-
|
|
6
|
-
import { useCommerce } from '../..'
|
|
7
|
-
|
|
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'
|
|
33
|
-
|
|
34
|
-
const CheckoutPanel: React.FC<{
|
|
35
|
-
close: () => void
|
|
36
|
-
className?: string
|
|
37
|
-
}> = ({
|
|
38
|
-
close,
|
|
39
|
-
className=''
|
|
40
|
-
}) => {
|
|
41
|
-
|
|
42
|
-
const cmmc = useCommerce()
|
|
43
|
-
|
|
44
|
-
// For sites that don't initialize cmmc
|
|
45
|
-
if (!cmmc) {
|
|
46
|
-
return <></>
|
|
47
|
-
}
|
|
48
|
-
const [stepIndex, setStepIndex] = useState<number>(0)
|
|
49
|
-
const [orderId, setOrderId] = useState<string | undefined>(undefined)
|
|
50
|
-
|
|
51
|
-
// Step.name or 'first' or 'next' or 'last'
|
|
52
|
-
const setStep = (name: string): void => {
|
|
53
|
-
|
|
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()
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
const StepToRender = STEPS[stepIndex].Comp
|
|
85
|
-
|
|
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>
|
|
105
|
-
)
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
export default CheckoutPanel
|