@hanzo/commerce 7.7.1 → 7.7.3
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/dist/components/cart/cart-panel/promo-code.d.ts +2 -2
- package/dist/service/impls/standalone/index.js +4 -4
- package/dist/service/impls/standalone/index.js.map +1 -1
- package/package.json +46 -17
- package/billing.ts +0 -2
- package/client.ts +0 -783
- package/components/Icons.tsx +0 -35
- package/components/add-to-cart-widget.tsx +0 -195
- package/components/buy/buy-card.tsx +0 -269
- package/components/buy/carousel-buy-card.tsx +0 -243
- package/components/buy/multi-family/all-variants-carousel.tsx +0 -259
- package/components/buy/multi-family/family-carousel/index.tsx +0 -75
- package/components/buy/multi-family/family-carousel/slide.tsx +0 -82
- package/components/buy/multi-family/family-carousel/state.ts +0 -87
- package/components/buy/multi-family/index.ts +0 -2
- package/components/buy/single-family-selector.tsx +0 -91
- package/components/buy/title-and-byline.tsx +0 -26
- package/components/cart/cart-panel/cart-line-item.tsx +0 -77
- package/components/cart/cart-panel/index.tsx +0 -157
- package/components/cart/cart-panel/promo-code.tsx +0 -105
- package/components/cart/cart-panel/total-area.tsx +0 -61
- package/components/checkout/payment-step-form/card-icon-row.tsx +0 -26
- package/components/checkout/payment-step-form/card-icons/amex.tsx +0 -32
- package/components/checkout/payment-step-form/card-icons/diners-club.tsx +0 -13
- package/components/checkout/payment-step-form/card-icons/discover.tsx +0 -25
- package/components/checkout/payment-step-form/card-icons/jcb.tsx +0 -26
- package/components/checkout/payment-step-form/card-icons/mastercard.tsx +0 -27
- package/components/checkout/payment-step-form/card-icons/visa.tsx +0 -25
- package/components/checkout/payment-step-form/cc-button.tsx +0 -18
- package/components/checkout/payment-step-form/contact-form.tsx +0 -48
- package/components/checkout/payment-step-form/crypto-icons/btc.tsx +0 -11
- package/components/checkout/payment-step-form/crypto-icons/eth.tsx +0 -20
- package/components/checkout/payment-step-form/crypto-icons/usdt.tsx +0 -13
- package/components/checkout/payment-step-form/index.tsx +0 -113
- package/components/checkout/payment-step-form/methods/bank-transfer.tsx +0 -81
- package/components/checkout/payment-step-form/methods/card.tsx +0 -253
- package/components/checkout/payment-step-form/methods/crypto.tsx +0 -226
- package/components/checkout/payment-step-form/methods/index.ts +0 -23
- package/components/checkout/shipping-step-form.tsx +0 -171
- package/components/index.ts +0 -11
- package/components/item/product-card.tsx +0 -66
- package/components/item-selector/button.tsx +0 -213
- package/components/item-selector/carousel/index.tsx +0 -196
- package/components/item-selector/carousel/slider.tsx +0 -37
- package/components/item-selector/index.ts +0 -5
- package/components/item-selector/quantity-indicator.tsx +0 -50
- package/components/node-tabs/index.tsx +0 -90
- package/components/node-tabs/node-image.tsx +0 -31
- package/index.ts +0 -55
- package/metering.test.ts +0 -199
- package/metering.ts +0 -289
- package/product-card.test.tsx +0 -104
- package/service/context.tsx +0 -45
- package/service/debug.ts +0 -41
- package/service/impls/standalone/actual-line-item.ts +0 -136
- package/service/impls/standalone/get-instance.ts +0 -64
- package/service/impls/standalone/index.ts +0 -579
- package/service/impls/standalone/order/firebase.ts +0 -5
- package/service/impls/standalone/order/index.ts +0 -58
- package/service/impls/standalone/persistence.ts +0 -33
- package/service/path-utils.ts +0 -26
- package/service/sep.ts +0 -7
- package/tsconfig.json +0 -17
- package/types/README.md +0 -2
- package/types/category-node.ts +0 -50
- package/types/checkout.ts +0 -47
- package/types/commerce-config.ts +0 -13
- package/types/commerce-service.ts +0 -128
- package/types/family.ts +0 -26
- package/types/index.ts +0 -15
- package/types/item-selector.ts +0 -97
- package/types/line-item.ts +0 -29
- package/types/multi-family-selector-props.ts +0 -20
- package/types/product.ts +0 -21
- package/types/promo.ts +0 -10
- package/types/selection-ui-specifier.ts +0 -52
- package/types/string-mutator.ts +0 -14
- package/types/token-separators.ts +0 -7
- package/util/analytics.ts +0 -21
- package/util/countries.ts +0 -196
- package/util/error.ts +0 -34
- package/util/index.ts +0 -71
- package/util/item-selector-options-accessor.ts +0 -35
- package/util/line-item-ref.ts +0 -23
- package/util/multi-family-selector-options-accessor.ts +0 -15
- package/util/obs-string-mutator.ts +0 -22
- package/util/product-media-accessor.ts +0 -58
- package/util/promo-codes.ts +0 -106
- package/util/selection-ui-specifiers.ts +0 -30
- package/util/square-payment.ts +0 -50
- package/util/use-sync-sku-param-w-current-item.ts +0 -88
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
import type { Family, LineItem, ObsLineItemRef, Product } from '../../../../types'
|
|
2
|
-
import { observable, action, computed, makeObservable } from 'mobx'
|
|
3
|
-
|
|
4
|
-
class SlideState implements ObsLineItemRef {
|
|
5
|
-
|
|
6
|
-
_fam: Family
|
|
7
|
-
_item: Product
|
|
8
|
-
_syncSku: (sku: string) => void
|
|
9
|
-
_selected: boolean
|
|
10
|
-
|
|
11
|
-
constructor(fam: Family, initiallySelected: boolean, sync: (sku: string) => void) {
|
|
12
|
-
this._fam = fam
|
|
13
|
-
this._item = fam.products[0]
|
|
14
|
-
this._selected = initiallySelected
|
|
15
|
-
this._syncSku = sync
|
|
16
|
-
|
|
17
|
-
makeObservable(this, {
|
|
18
|
-
_item: observable,
|
|
19
|
-
item: computed,
|
|
20
|
-
selectSku: action
|
|
21
|
-
})
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
selectSku = (sku: string) => {
|
|
25
|
-
if (this._item.sku === sku) return;
|
|
26
|
-
const found = this._fam.products.find((i) => (i.sku === sku))
|
|
27
|
-
if (found) {
|
|
28
|
-
this._item = found
|
|
29
|
-
if (this._selected) {
|
|
30
|
-
this._syncSku(sku)
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
else {
|
|
34
|
-
throw new Error('SlideState.selectSku(): no item found!')
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
setSelected = (b: boolean) => {this._selected = b}
|
|
39
|
-
|
|
40
|
-
get id(): string {return (this._fam.id)}
|
|
41
|
-
|
|
42
|
-
get item(): LineItem | undefined {
|
|
43
|
-
return this._item as LineItem
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
class FamilyCarouselState {
|
|
48
|
-
|
|
49
|
-
_map = new Map<string, SlideState>()
|
|
50
|
-
_syncSku: (sku: string) => void
|
|
51
|
-
|
|
52
|
-
constructor(
|
|
53
|
-
fams: Family[],
|
|
54
|
-
// initialFamilyId: string,
|
|
55
|
-
syncSkuToCurrentSlide: (sku: string) => void
|
|
56
|
-
) {
|
|
57
|
-
|
|
58
|
-
fams.forEach((f, index) => {this._map.set(f.id, new SlideState(
|
|
59
|
-
f,
|
|
60
|
-
(index === 0),
|
|
61
|
-
syncSkuToCurrentSlide
|
|
62
|
-
))})
|
|
63
|
-
this._syncSku = syncSkuToCurrentSlide
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
setCurrentFamily = (id: string) => {
|
|
67
|
-
|
|
68
|
-
this._map.forEach((s) => {
|
|
69
|
-
s.setSelected(s.id === id)
|
|
70
|
-
})
|
|
71
|
-
|
|
72
|
-
const slideState = this._map.get(id)
|
|
73
|
-
if (slideState) {
|
|
74
|
-
this._syncSku(slideState.item!.sku)
|
|
75
|
-
}
|
|
76
|
-
else {
|
|
77
|
-
throw new Error(`FamilyCarouselState.setCurrentFamily: no state object for id '${id}'!`)
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
getSlideState = (familyId: string): SlideState | undefined => (this._map.get(familyId))
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
export {
|
|
85
|
-
SlideState,
|
|
86
|
-
FamilyCarouselState
|
|
87
|
-
}
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import { observer } from 'mobx-react-lite'
|
|
3
|
-
|
|
4
|
-
import {
|
|
5
|
-
MediaStack,
|
|
6
|
-
Skeleton,
|
|
7
|
-
cn,
|
|
8
|
-
} from '@hanzo/ui'
|
|
9
|
-
import type { Dimensions } from '@hanzo/ui/types'
|
|
10
|
-
|
|
11
|
-
import { useCommerce } from '../..'
|
|
12
|
-
|
|
13
|
-
import type { ItemSelectorOptions, ItemSelectorProps, LineItem } from '../../types'
|
|
14
|
-
import TitleAndByline from './title-and-byline'
|
|
15
|
-
import { accessItemOptions } from '../../util'
|
|
16
|
-
const DEF_MEDIA_CONSTRAINT = {w: 200, h: 200}
|
|
17
|
-
|
|
18
|
-
const SingleFamilySelector: React.FC<{
|
|
19
|
-
items: LineItem[] // these may be sorted, so are we can't just use currentFamily.products
|
|
20
|
-
Selector: React.ComponentType<ItemSelectorProps>
|
|
21
|
-
selOptions: ItemSelectorOptions | undefined
|
|
22
|
-
scrollable?: boolean
|
|
23
|
-
showItemMedia?: boolean
|
|
24
|
-
mediaConstraint?: Dimensions
|
|
25
|
-
mobile?: boolean
|
|
26
|
-
clx?: string
|
|
27
|
-
titleAreaClx?: string
|
|
28
|
-
}> = ({
|
|
29
|
-
items,
|
|
30
|
-
Selector,
|
|
31
|
-
selOptions,
|
|
32
|
-
scrollable=false,
|
|
33
|
-
showItemMedia=true,
|
|
34
|
-
mediaConstraint=DEF_MEDIA_CONSTRAINT,
|
|
35
|
-
mobile=false,
|
|
36
|
-
clx='',
|
|
37
|
-
titleAreaClx=''
|
|
38
|
-
}) => {
|
|
39
|
-
|
|
40
|
-
const cmmc = useCommerce()
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
const MyTitleAndByline: React.FC<{clx?: string}> = observer(({
|
|
44
|
-
clx=''
|
|
45
|
-
}) => {
|
|
46
|
-
const { familyTitle, showFamilyByline } = accessItemOptions(selOptions)
|
|
47
|
-
const title = (familyTitle === 'none' ?
|
|
48
|
-
undefined
|
|
49
|
-
:
|
|
50
|
-
(familyTitle === 'long' ?
|
|
51
|
-
cmmc.currentFamily?.title
|
|
52
|
-
:
|
|
53
|
-
(cmmc.currentFamily?.titleShort ?? cmmc.currentFamily?.title)
|
|
54
|
-
)
|
|
55
|
-
)
|
|
56
|
-
const byline = (familyTitle !== 'none') && showFamilyByline ? cmmc.currentFamily?.byline : undefined
|
|
57
|
-
return ( <TitleAndByline clx={clx} title={title} byline={byline}/> )
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
const ItemMedia: React.FC = observer(() => (
|
|
61
|
-
cmmc.currentItem ? (
|
|
62
|
-
<MediaStack
|
|
63
|
-
media={cmmc.currentItem!}
|
|
64
|
-
constrainTo={mediaConstraint}
|
|
65
|
-
className={'mb-2 ' + (scrollable ? 'shrink-0' : '')}
|
|
66
|
-
/>
|
|
67
|
-
) : (
|
|
68
|
-
<Skeleton className={'w-[200px] h-[200px] my-2 ' + (scrollable ? 'shrink-0' : '')}/>
|
|
69
|
-
)
|
|
70
|
-
))
|
|
71
|
-
|
|
72
|
-
return (
|
|
73
|
-
<div className={cn(clx, 'flex flex-col items-center', (scrollable ? 'shrink' : ''))}>
|
|
74
|
-
<MyTitleAndByline clx={cn(titleAreaClx, (scrollable ? 'shrink-0' : ''))} />
|
|
75
|
-
{showItemMedia && (<ItemMedia />)}
|
|
76
|
-
{items && (
|
|
77
|
-
<Selector
|
|
78
|
-
items={items}
|
|
79
|
-
selectedItemRef={cmmc}
|
|
80
|
-
selectSku={cmmc.setCurrentItem.bind(cmmc)}
|
|
81
|
-
scrollable={scrollable}
|
|
82
|
-
mobile={mobile}
|
|
83
|
-
options={selOptions}
|
|
84
|
-
clx={(scrollable ? 'shrink' : '')}
|
|
85
|
-
/>
|
|
86
|
-
)}
|
|
87
|
-
</div>
|
|
88
|
-
)
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
export default SingleFamilySelector
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
ApplyTypography,
|
|
5
|
-
cn,
|
|
6
|
-
} from '@hanzo/ui'
|
|
7
|
-
const TitleAndByline: React.FC<{
|
|
8
|
-
title: string | undefined
|
|
9
|
-
byline?: string | undefined
|
|
10
|
-
clx?: string
|
|
11
|
-
bylineClx?: string
|
|
12
|
-
}> = ({
|
|
13
|
-
title,
|
|
14
|
-
byline,
|
|
15
|
-
clx='',
|
|
16
|
-
bylineClx=''
|
|
17
|
-
}) => ( (title || byline) ? (
|
|
18
|
-
<ApplyTypography className={cn('flex flex-col items-center !gap-0 [&>*]:!m-0 ', clx)} >
|
|
19
|
-
<h4>{title}</h4>
|
|
20
|
-
{byline && (<h6 className={bylineClx}>{byline}</h6>)}
|
|
21
|
-
</ApplyTypography>
|
|
22
|
-
) : (null)
|
|
23
|
-
)
|
|
24
|
-
|
|
25
|
-
export default TitleAndByline
|
|
26
|
-
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import React from 'react'
|
|
4
|
-
import { observer } from 'mobx-react-lite'
|
|
5
|
-
|
|
6
|
-
import {
|
|
7
|
-
cn,
|
|
8
|
-
MediaStack,
|
|
9
|
-
} from '@hanzo/ui'
|
|
10
|
-
import type { LineItem } from '../../../types'
|
|
11
|
-
import { formatCurrencyValue } from '../../../util'
|
|
12
|
-
import AddToCartWidget from '../../add-to-cart-widget'
|
|
13
|
-
import { useCommerce } from '../../../service/context'
|
|
14
|
-
|
|
15
|
-
const DEF_IMG_SIZE=40
|
|
16
|
-
|
|
17
|
-
const renderTitle = (t: string): React.ReactNode => {
|
|
18
|
-
const toks = t.split(', ')
|
|
19
|
-
if (toks.length === 2) {
|
|
20
|
-
return <><p>{toks[0]},</p><p className=''>{toks[1]}</p></>
|
|
21
|
-
}
|
|
22
|
-
return <p>{t}</p>
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const CartLineItem: React.FC<{
|
|
26
|
-
item: LineItem
|
|
27
|
-
className?: string
|
|
28
|
-
imgSizePx?: number
|
|
29
|
-
showPromoCode?: boolean
|
|
30
|
-
itemClicked?: (item: LineItem) => void
|
|
31
|
-
selected?: boolean
|
|
32
|
-
}> = observer(({
|
|
33
|
-
item,
|
|
34
|
-
className='',
|
|
35
|
-
imgSizePx=DEF_IMG_SIZE,
|
|
36
|
-
showPromoCode=false,
|
|
37
|
-
itemClicked,
|
|
38
|
-
selected=false
|
|
39
|
-
}) => {
|
|
40
|
-
|
|
41
|
-
const cmmc = useCommerce()
|
|
42
|
-
const promoPrice = showPromoCode ? cmmc.itemPromoPrice(item) : undefined
|
|
43
|
-
|
|
44
|
-
return (
|
|
45
|
-
<div
|
|
46
|
-
className={cn(
|
|
47
|
-
'flex flex-col justify-start items-start text-sm font-sans',
|
|
48
|
-
'border border-background rounded-sm p-1',
|
|
49
|
-
selected ? 'border-foreground' : '',
|
|
50
|
-
(itemClicked && !selected ? 'cursor-pointer hover:!border-muted-4' : 'cursor-default '),
|
|
51
|
-
className
|
|
52
|
-
)}
|
|
53
|
-
onClick={() => {itemClicked && itemClicked(item)}}
|
|
54
|
-
>
|
|
55
|
-
<div className='flex flex-row justify-between items-center gap-1' >
|
|
56
|
-
{item.img ? (
|
|
57
|
-
<MediaStack media={item} constrainTo={{w: imgSizePx, h: imgSizePx}} />
|
|
58
|
-
) : ( // placeholder so things align
|
|
59
|
-
<div style={{height: imgSizePx, width: imgSizePx}} className='bg-level-3'/>
|
|
60
|
-
)}
|
|
61
|
-
<div className='grow leading-tight ml-1'>{renderTitle(item.title)}</div>
|
|
62
|
-
</div>
|
|
63
|
-
<div className='flex flex-row items-center justify-between w-full'>
|
|
64
|
-
<div className='flex flex-row items-center'>
|
|
65
|
-
<AddToCartWidget variant='minimal' item={item} />
|
|
66
|
-
{item.quantity > 1 && (<span className='pl-2.5'>{'@' + formatCurrencyValue(item.price)}</span>)}
|
|
67
|
-
</div>
|
|
68
|
-
<div className='flex flex-row gap-1 items-center justify-end'>
|
|
69
|
-
<span className={promoPrice ? 'line-through text-muted-2' : ''}>{formatCurrencyValue(item.price * item.quantity)}</span>
|
|
70
|
-
{promoPrice && <span>{formatCurrencyValue(promoPrice * item.quantity)}</span>}
|
|
71
|
-
</div>
|
|
72
|
-
</div>
|
|
73
|
-
</div>
|
|
74
|
-
)
|
|
75
|
-
})
|
|
76
|
-
|
|
77
|
-
export default CartLineItem
|
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
import React, { useLayoutEffect, type PropsWithChildren } from 'react'
|
|
3
|
-
import { reaction } from 'mobx'
|
|
4
|
-
import { observer } from 'mobx-react-lite'
|
|
5
|
-
|
|
6
|
-
import {
|
|
7
|
-
Button,
|
|
8
|
-
ScrollArea,
|
|
9
|
-
cn,
|
|
10
|
-
sx,
|
|
11
|
-
} from '@hanzo/ui'
|
|
12
|
-
import type { LineItem } from '../../../types'
|
|
13
|
-
import { useCommerce } from '../../../service/context'
|
|
14
|
-
import { sendFBEvent, sendGAEvent } from '../../../util/analytics'
|
|
15
|
-
|
|
16
|
-
import CartLineItem from './cart-line-item'
|
|
17
|
-
import TotalArea from './total-area'
|
|
18
|
-
|
|
19
|
-
const CartPanel: React.FC<PropsWithChildren & {
|
|
20
|
-
/** fix size and scroll after 'scrollAfter items in teh cart. */
|
|
21
|
-
scrollAfter?: number
|
|
22
|
-
scrollHeightClx?: string
|
|
23
|
-
imgSizePx?: number
|
|
24
|
-
className?: string
|
|
25
|
-
itemClx?: string
|
|
26
|
-
listClx?: string
|
|
27
|
-
noItemsClx?: string
|
|
28
|
-
totalClx?: string
|
|
29
|
-
buttonClx?: string
|
|
30
|
-
showPromoCode?: boolean
|
|
31
|
-
showShipping?: boolean
|
|
32
|
-
selectItems?: boolean
|
|
33
|
-
/** if not provided, 'checkout' button will be rendered */
|
|
34
|
-
handleCheckout?: () => void
|
|
35
|
-
}> = observer(({
|
|
36
|
-
/** If provided, 'children' is rendered above the items. eg, a heading. */
|
|
37
|
-
children,
|
|
38
|
-
scrollAfter=5,
|
|
39
|
-
scrollHeightClx='h-[70vh]',
|
|
40
|
-
imgSizePx=40,
|
|
41
|
-
className='',
|
|
42
|
-
itemClx='',
|
|
43
|
-
listClx='',
|
|
44
|
-
noItemsClx='',
|
|
45
|
-
totalClx='',
|
|
46
|
-
buttonClx='',
|
|
47
|
-
showPromoCode=false,
|
|
48
|
-
showShipping=false,
|
|
49
|
-
selectItems=false,
|
|
50
|
-
handleCheckout,
|
|
51
|
-
}) => {
|
|
52
|
-
|
|
53
|
-
const cmmc = useCommerce()
|
|
54
|
-
|
|
55
|
-
useLayoutEffect(() => {
|
|
56
|
-
if (!cmmc) {
|
|
57
|
-
return
|
|
58
|
-
}
|
|
59
|
-
if (!cmmc.cartEmpty && !cmmc.currentItem) {
|
|
60
|
-
cmmc.setCurrentItem(cmmc.cartItems[0].sku)
|
|
61
|
-
}
|
|
62
|
-
// return mobx disposer
|
|
63
|
-
return reaction(() => (
|
|
64
|
-
cmmc.cartItems.length
|
|
65
|
-
),
|
|
66
|
-
(itemCount) => {
|
|
67
|
-
if (itemCount > 0) {
|
|
68
|
-
cmmc.setCurrentItem(cmmc.cartItems[0].sku)
|
|
69
|
-
}
|
|
70
|
-
})
|
|
71
|
-
}, [])
|
|
72
|
-
|
|
73
|
-
if (!cmmc) {
|
|
74
|
-
return <div />
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const _handleCheckout = () => {
|
|
78
|
-
sendGAEvent('begin_checkout', {
|
|
79
|
-
currency: 'USD',
|
|
80
|
-
value: cmmc.cartTotal,
|
|
81
|
-
items: cmmc.cartItems.map((item) => ({
|
|
82
|
-
item_id: item.sku,
|
|
83
|
-
item_name: item.title,
|
|
84
|
-
item_category: item.familyId,
|
|
85
|
-
price: item.price,
|
|
86
|
-
quantity: item.quantity
|
|
87
|
-
})),
|
|
88
|
-
})
|
|
89
|
-
sendFBEvent('InitiateCheckout', {
|
|
90
|
-
content_ids: cmmc.cartItems.map((item) => item.sku),
|
|
91
|
-
contents: cmmc.cartItems.map(item => ({
|
|
92
|
-
id: item.sku,
|
|
93
|
-
quantity: item.quantity
|
|
94
|
-
})),
|
|
95
|
-
num_items: cmmc.cartItems.length,
|
|
96
|
-
value: cmmc.cartTotal,
|
|
97
|
-
currency: 'USD',
|
|
98
|
-
})
|
|
99
|
-
handleCheckout && handleCheckout()
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
const itemClicked = (item: LineItem) => {
|
|
103
|
-
cmmc.setCurrentItem(item.sku)
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const MainStuff: React.FC = observer(() => (<>
|
|
107
|
-
{cmmc.cartEmpty ? (
|
|
108
|
-
<p className={cn('text-center my-3', noItemsClx)}>No items</p>
|
|
109
|
-
) : (<>
|
|
110
|
-
{cmmc.cartItems.map((item) => (
|
|
111
|
-
<CartLineItem
|
|
112
|
-
key={`mobile-${item.sku}`}
|
|
113
|
-
imgSizePx={imgSizePx}
|
|
114
|
-
item={item}
|
|
115
|
-
className={cn('mb-2', itemClx)}
|
|
116
|
-
showPromoCode={showPromoCode}
|
|
117
|
-
itemClicked={selectItems ? itemClicked : undefined}
|
|
118
|
-
selected={selectItems ? cmmc.currentItem?.sku === item.sku : false}
|
|
119
|
-
/>
|
|
120
|
-
))}
|
|
121
|
-
</>)}
|
|
122
|
-
<TotalArea clx={totalClx} showPromoCode={showPromoCode} showShipping={showShipping}/>
|
|
123
|
-
</>))
|
|
124
|
-
|
|
125
|
-
const scrolling = (cmmc.cartItems.length > scrollAfter)
|
|
126
|
-
|
|
127
|
-
return (
|
|
128
|
-
<div className={cn(
|
|
129
|
-
'border p-4 rounded-lg flex flex-col',
|
|
130
|
-
className,
|
|
131
|
-
scrolling ? scrollHeightClx : 'h-auto'
|
|
132
|
-
)}>
|
|
133
|
-
{children}
|
|
134
|
-
{scrolling ? (
|
|
135
|
-
<ScrollArea className={cn('mt-2 w-full shrink py-0', listClx)}>
|
|
136
|
-
<MainStuff />
|
|
137
|
-
</ScrollArea>
|
|
138
|
-
) : (
|
|
139
|
-
<div className={cn('mt-2 w-full', listClx)}>
|
|
140
|
-
<MainStuff />
|
|
141
|
-
</div>
|
|
142
|
-
)}
|
|
143
|
-
{handleCheckout && !cmmc.cartEmpty && (
|
|
144
|
-
<Button
|
|
145
|
-
onClick={_handleCheckout}
|
|
146
|
-
variant='primary'
|
|
147
|
-
{...sx('rounded-lg')}
|
|
148
|
-
className={cn('mt-12 mx-auto w-full sm:max-w-[220px]', buttonClx)}
|
|
149
|
-
>
|
|
150
|
-
Checkout
|
|
151
|
-
</Button>
|
|
152
|
-
)}
|
|
153
|
-
</div>
|
|
154
|
-
)
|
|
155
|
-
})
|
|
156
|
-
|
|
157
|
-
export default CartPanel
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import { useEffect, useState } from 'react'
|
|
4
|
-
import { useSearchParams } from 'next/navigation'
|
|
5
|
-
import { observer } from 'mobx-react-lite'
|
|
6
|
-
|
|
7
|
-
import { useForm } from 'react-hook-form'
|
|
8
|
-
import * as z from 'zod'
|
|
9
|
-
import { zodResolver } from '@hookform/resolvers/zod'
|
|
10
|
-
import { Check } from 'lucide-react'
|
|
11
|
-
|
|
12
|
-
import {
|
|
13
|
-
Button,
|
|
14
|
-
Input,
|
|
15
|
-
Form,
|
|
16
|
-
FormControl,
|
|
17
|
-
FormField,
|
|
18
|
-
FormItem,
|
|
19
|
-
} from '@hanzo/ui'
|
|
20
|
-
import { useCommerce } from '../../../service/context'
|
|
21
|
-
import type { Promo } from '../../../types'
|
|
22
|
-
import getPromoFromApi from '../../../util/promo-codes'
|
|
23
|
-
|
|
24
|
-
const formSchema = z.object({
|
|
25
|
-
code: z.string().min(1, 'Invalid code'),
|
|
26
|
-
})
|
|
27
|
-
|
|
28
|
-
const PromoCode = observer(() => {
|
|
29
|
-
const cmmc = useCommerce()
|
|
30
|
-
const searchParams = useSearchParams()
|
|
31
|
-
|
|
32
|
-
const [codeAccepted, setCodeAccepted] = useState<boolean>(false)
|
|
33
|
-
|
|
34
|
-
useEffect(() => {
|
|
35
|
-
const code = searchParams.get('code')
|
|
36
|
-
if (code) {
|
|
37
|
-
getPromoFromApi(code).then((promo?: Promo) => {
|
|
38
|
-
if (promo) {
|
|
39
|
-
form.setValue('code', code)
|
|
40
|
-
setCodeAccepted(true)
|
|
41
|
-
cmmc.setAppliedPromo(promo)
|
|
42
|
-
}
|
|
43
|
-
})
|
|
44
|
-
}
|
|
45
|
-
}, [searchParams])
|
|
46
|
-
|
|
47
|
-
useEffect(() => {
|
|
48
|
-
if (cmmc.appliedPromo) {
|
|
49
|
-
form.setValue('code', cmmc.appliedPromo.code)
|
|
50
|
-
setCodeAccepted(true)
|
|
51
|
-
}
|
|
52
|
-
}, [cmmc.appliedPromo])
|
|
53
|
-
|
|
54
|
-
const form = useForm<z.infer<typeof formSchema>>({
|
|
55
|
-
resolver: zodResolver(formSchema as any),
|
|
56
|
-
defaultValues: {
|
|
57
|
-
code: '',
|
|
58
|
-
},
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
const applyPromoCode = async (values: z.infer<typeof formSchema>) => {
|
|
62
|
-
const { code } = values
|
|
63
|
-
const promo = await getPromoFromApi(code)
|
|
64
|
-
if (!promo) {
|
|
65
|
-
form.setError('code', { message: 'Invalid code' })
|
|
66
|
-
return
|
|
67
|
-
}
|
|
68
|
-
setCodeAccepted(true)
|
|
69
|
-
cmmc.setAppliedPromo(promo)
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const removePromoCode = () => {
|
|
73
|
-
cmmc.setAppliedPromo(null)
|
|
74
|
-
setCodeAccepted(false)
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
return (
|
|
78
|
-
<div className='flex flex-col gap-2 border-t py-1'>
|
|
79
|
-
<Form {...form}>
|
|
80
|
-
<form onSubmit={form.handleSubmit(applyPromoCode)}>
|
|
81
|
-
<div className='flex gap-2 items-center'>
|
|
82
|
-
<FormField
|
|
83
|
-
control={form.control}
|
|
84
|
-
name='code'
|
|
85
|
-
render={({ field }) => (
|
|
86
|
-
<FormItem className='w-full'>
|
|
87
|
-
<FormControl>
|
|
88
|
-
<Input {...field} className='h-9' placeholder='Discount or invite code' onInput={removePromoCode}/>
|
|
89
|
-
</FormControl>
|
|
90
|
-
</FormItem>
|
|
91
|
-
)}
|
|
92
|
-
/>
|
|
93
|
-
{codeAccepted ? (
|
|
94
|
-
<Check/>
|
|
95
|
-
) : (
|
|
96
|
-
<Button variant='outline' className='!w-fit !min-w-0 font-inter text-muted text-sm'>Apply</Button>
|
|
97
|
-
)}
|
|
98
|
-
</div>
|
|
99
|
-
</form>
|
|
100
|
-
</Form>
|
|
101
|
-
</div>
|
|
102
|
-
)
|
|
103
|
-
})
|
|
104
|
-
|
|
105
|
-
export default PromoCode
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
import React, { Suspense } from 'react'
|
|
3
|
-
import { observer } from 'mobx-react-lite'
|
|
4
|
-
|
|
5
|
-
import {
|
|
6
|
-
cn,
|
|
7
|
-
} from '@hanzo/ui'
|
|
8
|
-
import { formatCurrencyValue } from '../../../util'
|
|
9
|
-
import PromoCode from './promo-code'
|
|
10
|
-
import { useCommerce } from '../../../service/context'
|
|
11
|
-
|
|
12
|
-
const TotalArea: React.FC<{
|
|
13
|
-
showPromoCode?: boolean
|
|
14
|
-
showShipping?: boolean
|
|
15
|
-
clx?: string
|
|
16
|
-
}> = observer(({
|
|
17
|
-
showPromoCode=false,
|
|
18
|
-
showShipping=false,
|
|
19
|
-
clx
|
|
20
|
-
}) => {
|
|
21
|
-
|
|
22
|
-
const cmmc = useCommerce()
|
|
23
|
-
|
|
24
|
-
return (
|
|
25
|
-
<div className={clx}>
|
|
26
|
-
{showPromoCode && (
|
|
27
|
-
<Suspense>
|
|
28
|
-
<PromoCode/>
|
|
29
|
-
</Suspense>
|
|
30
|
-
)}
|
|
31
|
-
{(showShipping || showPromoCode) && (
|
|
32
|
-
<div className={'flex flex-col gap-0.5 pb-1.5 text-sm ' + (!showPromoCode ? 'border-t pt-1.5' : '')} >
|
|
33
|
-
<p className='flex justify-between'>
|
|
34
|
-
<span className='text-muted-1'>Subtotal</span>
|
|
35
|
-
<span className='font-semibold'>{cmmc.cartTotal === 0 ? '0' : formatCurrencyValue(cmmc.cartTotal)}</span>
|
|
36
|
-
</p>
|
|
37
|
-
{cmmc.promoAppliedCartTotal !== cmmc.cartTotal && (
|
|
38
|
-
<p className='flex justify-between'>
|
|
39
|
-
<span className='text-muted-1'>Promo Discount</span>
|
|
40
|
-
<span className='font-semibold'>-{formatCurrencyValue(cmmc.cartTotal - cmmc.promoAppliedCartTotal)}</span>
|
|
41
|
-
</p>
|
|
42
|
-
)}
|
|
43
|
-
{showShipping && (
|
|
44
|
-
<p className='flex justify-between'>
|
|
45
|
-
<span className='text-muted-1'>Shipping</span>
|
|
46
|
-
<span className='font-semibold'>Free Global Shipping</span>
|
|
47
|
-
</p>
|
|
48
|
-
)}
|
|
49
|
-
</div>
|
|
50
|
-
)}
|
|
51
|
-
<p className={cn('border-t py-2 flex justify-between')}>
|
|
52
|
-
TOTAL
|
|
53
|
-
<span className='font-semibold'>
|
|
54
|
-
{formatCurrencyValue(showPromoCode ? cmmc.promoAppliedCartTotal : cmmc.cartTotal)}
|
|
55
|
-
</span>
|
|
56
|
-
</p>
|
|
57
|
-
</div>
|
|
58
|
-
)
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
export default TotalArea
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { LockKeyhole } from 'lucide-react'
|
|
2
|
-
import Amex from './card-icons/amex'
|
|
3
|
-
import Discover from './card-icons/discover'
|
|
4
|
-
import Mastercard from './card-icons/mastercard'
|
|
5
|
-
import Visa from './card-icons/visa'
|
|
6
|
-
import DinersClub from './card-icons/diners-club'
|
|
7
|
-
import Jcb from './card-icons/jcb'
|
|
8
|
-
|
|
9
|
-
// <span className='hidden sm:flex text-sm'>Secure payments with</span>
|
|
10
|
-
|
|
11
|
-
const PaymentMethods: React.FC = () => {
|
|
12
|
-
return (
|
|
13
|
-
<div className='flex gap-1 items-center text-muted-1 pb-3'>
|
|
14
|
-
<LockKeyhole className='w-4 h-4'/>
|
|
15
|
-
<span className='hidden sm:flex text-sm'>Secure payments with</span>
|
|
16
|
-
<Amex className='w-9 h-5'/>
|
|
17
|
-
<Discover className='w-9 h-5'/>
|
|
18
|
-
<Mastercard className='w-9 h-5'/>
|
|
19
|
-
<Visa className='w-9 h-5'/>
|
|
20
|
-
<DinersClub className='w-9 h-5'/>
|
|
21
|
-
<Jcb className='w-9 h-5'/>
|
|
22
|
-
</div>
|
|
23
|
-
)
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export default PaymentMethods
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import { type LucideProps } from 'lucide-react'
|
|
3
|
-
|
|
4
|
-
const Amex: React.FC<LucideProps> = (props: LucideProps) => (
|
|
5
|
-
<svg
|
|
6
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
7
|
-
role="img"
|
|
8
|
-
viewBox="0 0 38 24"
|
|
9
|
-
aria-labelledby="pi-american_express"
|
|
10
|
-
{...props}
|
|
11
|
-
>
|
|
12
|
-
<title id="pi-american_express">American Express</title>
|
|
13
|
-
<g fill="none">
|
|
14
|
-
<path
|
|
15
|
-
fill="#000"
|
|
16
|
-
d="M35,0 L3,0 C1.3,0 0,1.3 0,3 L0,21 C0,22.7 1.4,24 3,24 L35,24 C36.7,24 38,22.7 38,21 L38,3 C38,1.3 36.6,0 35,0 Z"
|
|
17
|
-
opacity=".07"
|
|
18
|
-
/>
|
|
19
|
-
<path
|
|
20
|
-
fill="#006FCF"
|
|
21
|
-
d="M35,1 C36.1,1 37,1.9 37,3 L37,21 C37,22.1 36.1,23 35,23 L3,23 C1.9,23 1,22.1 1,21 L1,3 C1,1.9 1.9,1 3,1 L35,1"
|
|
22
|
-
/>
|
|
23
|
-
<path
|
|
24
|
-
fill="#FFF"
|
|
25
|
-
d="M8.971,10.268 L9.745,12.144 L8.203,12.144 L8.971,10.268 Z M25.046,10.346 L22.069,10.346 L22.069,11.173 L24.998,11.173 L24.998,12.412 L22.075,12.412 L22.075,13.334 L25.052,13.334 L25.052,14.073 L27.129,11.828 L25.052,9.488 L25.046,10.346 L25.046,10.346 Z M10.983,8.006 L14.978,8.006 L15.865,9.941 L16.687,8 L27.057,8 L28.135,9.19 L29.25,8 L34.013,8 L30.494,11.852 L33.977,15.68 L29.143,15.68 L28.065,14.49 L26.94,15.68 L10.03,15.68 L9.536,14.49 L8.406,14.49 L7.911,15.68 L4,15.68 L7.286,8 L10.716,8 L10.983,8.006 Z M19.646,9.084 L17.407,9.084 L15.907,12.62 L14.282,9.084 L12.06,9.084 L12.06,13.894 L10,9.084 L8.007,9.084 L5.625,14.596 L7.18,14.596 L7.674,13.406 L10.27,13.406 L10.764,14.596 L13.484,14.596 L13.484,10.661 L15.235,14.602 L16.425,14.602 L18.165,10.673 L18.165,14.603 L19.623,14.603 L19.647,9.083 L19.646,9.084 Z M28.986,11.852 L31.517,9.084 L29.695,9.084 L28.094,10.81 L26.546,9.084 L20.652,9.084 L20.652,14.602 L26.462,14.602 L28.076,12.864 L29.624,14.602 L31.499,14.602 L28.987,11.852 L28.986,11.852 Z"
|
|
26
|
-
/>
|
|
27
|
-
</g>
|
|
28
|
-
</svg>
|
|
29
|
-
|
|
30
|
-
)
|
|
31
|
-
|
|
32
|
-
export default Amex
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import { type LucideProps } from 'lucide-react'
|
|
3
|
-
|
|
4
|
-
const DinersClub: React.FC<LucideProps> = (props: LucideProps) => (
|
|
5
|
-
<svg enableBackground="new 0 0 780 500" viewBox="0 0 780 500" xmlns="http://www.w3.org/2000/svg" {...props}>
|
|
6
|
-
<path d="m40 0h700c22.092 0 40 17.909 40 40v420c0 22.092-17.908 40-40 40h-700c-22.091 0-40-17.908-40-40v-420c0-22.091 17.909-40 40-40z" fill="#0079be"/>
|
|
7
|
-
<path d="m599.93 251.45c0-99.415-82.98-168.13-173.9-168.1h-78.242c-92.003-.033-167.73 68.705-167.73 168.1 0 90.93 75.727 165.64 167.73 165.2h78.242c90.914.436 173.9-74.294 173.9-165.2z" fill="#fff"/>
|
|
8
|
-
<path d="m348.28 97.43c-84.07.027-152.19 68.308-152.21 152.58.02 84.258 68.144 152.53 152.21 152.56 84.09-.027 152.23-68.303 152.24-152.56-.011-84.272-68.149-152.55-152.24-152.58z" fill="#0079be"/>
|
|
9
|
-
<path d="m252.07 249.6c.08-41.181 25.746-76.297 61.94-90.25v180.48c-36.194-13.948-61.861-49.045-61.94-90.23zm131 90.274v-180.53c36.207 13.92 61.914 49.057 61.979 90.257-.065 41.212-25.772 76.322-61.979 90.269z" fill="#fff"/>
|
|
10
|
-
</svg>
|
|
11
|
-
)
|
|
12
|
-
|
|
13
|
-
export default DinersClub
|