@hanzo/commerce 2.1.0 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/components/add-to-cart-widget.tsx +3 -3
  2. package/components/buy-item/buy-item-button.tsx +28 -0
  3. package/components/buy-item/buy-item-card.tsx +82 -0
  4. package/components/buy-item/buy-item-popup.tsx +38 -0
  5. package/components/{select-category-and-item-widget.tsx → buy-item/select-category-and-item-widget.tsx} +4 -4
  6. package/components/buy-item/select-category-item-card.tsx +111 -0
  7. package/components/{cart-line-item.tsx → cart-panel/cart-line-item.tsx} +4 -3
  8. package/components/{cart.tsx → cart-panel/index.tsx} +24 -18
  9. package/components/category-item-ios-wheel-selector.tsx +60 -0
  10. package/components/category-item-radio-selector.tsx +55 -0
  11. package/components/{checkout → checkout-panel}/confirm-order.tsx +3 -2
  12. package/components/{checkout → checkout-panel}/index.tsx +10 -5
  13. package/components/{checkout → checkout-panel}/payment/index.tsx +2 -1
  14. package/components/{checkout → checkout-panel}/payment/pay-with-card.tsx +8 -3
  15. package/components/{checkout → checkout-panel}/payment/pay-with-crypto.tsx +2 -3
  16. package/components/{facet-toggles-widget → facet-values-widget}/facet-image.tsx +7 -3
  17. package/components/{facet-toggles-widget → facet-values-widget}/index.tsx +7 -5
  18. package/components/index.ts +11 -9
  19. package/components/{select-item-in-category-view.tsx → select-category-item-panel.tsx} +72 -76
  20. package/index.ts +2 -2
  21. package/package.json +8 -3
  22. package/service/context.tsx +4 -4
  23. package/service/impls/standalone/index.ts +4 -4
  24. package/service/impls/standalone/standalone-service.ts +117 -38
  25. package/types/category.ts +3 -2
  26. package/types/commerce-service.ts +17 -4
  27. package/types/facet.ts +1 -3
  28. package/types/index.ts +3 -1
  29. package/types/item-selector.ts +14 -0
  30. package/util/index.ts +29 -0
  31. package/util/use-sync-sku-param-w-current-item.ts +4 -1
  32. package/blocks/components/commerce-cat-and-item-block.tsx +0 -13
  33. package/blocks/def/commerce-cat-and-item-block.ts +0 -9
  34. package/components/facets-widget.tsx +0 -55
  35. package/components/product-selection-mobile-picker.tsx +0 -78
  36. package/components/product-selection-radio-group.tsx +0 -38
  37. /package/components/{checkout → checkout-panel}/countries.ts +0 -0
  38. /package/components/{checkout → checkout-panel}/icons/btc.tsx +0 -0
  39. /package/components/{checkout → checkout-panel}/icons/eth.tsx +0 -0
  40. /package/components/{checkout → checkout-panel}/icons/usdt.tsx +0 -0
  41. /package/components/{checkout → checkout-panel}/payment/contact-info.tsx +0 -0
  42. /package/components/{checkout → checkout-panel}/payment/pay-with-bank-transfer.tsx +0 -0
  43. /package/components/{checkout → checkout-panel}/payment/payment-methods/amex.tsx +0 -0
  44. /package/components/{checkout → checkout-panel}/payment/payment-methods/diners-club.tsx +0 -0
  45. /package/components/{checkout → checkout-panel}/payment/payment-methods/discover.tsx +0 -0
  46. /package/components/{checkout → checkout-panel}/payment/payment-methods/index.tsx +0 -0
  47. /package/components/{checkout → checkout-panel}/payment/payment-methods/jcb.tsx +0 -0
  48. /package/components/{checkout → checkout-panel}/payment/payment-methods/mastercard.tsx +0 -0
  49. /package/components/{checkout → checkout-panel}/payment/payment-methods/visa.tsx +0 -0
  50. /package/components/{checkout → checkout-panel}/shipping-info.tsx +0 -0
  51. /package/components/{checkout → checkout-panel}/thank-you.tsx +0 -0
@@ -1,25 +1,27 @@
1
1
  'use client'
2
2
  import React, { useState } from 'react'
3
3
 
4
- import { ToggleGroup, ToggleGroupItem, type toggleVariants} from "@hanzo/ui/primitives"
4
+ import { ToggleGroup, ToggleGroupItem} from "@hanzo/ui/primitives"
5
5
  import { cn } from '@hanzo/ui/util'
6
6
 
7
7
  import type { FacetValueDesc, StringMutator, StringArrayMutator } from '../../types'
8
8
  import FacetImage from './facet-image'
9
9
 
10
- const FacetTogglesWidget: React.FC<{
10
+ const FacetValuesWidget: React.FC<{
11
11
  facetValues: FacetValueDesc[]
12
12
  mutator: StringMutator | StringArrayMutator
13
13
  multiple?: boolean
14
14
  className?: string
15
15
  buttonClx?: string
16
+ itemClx?: string
16
17
  isMobile?: boolean
17
18
  tabSize?: string
18
19
  }> = ({
19
20
  facetValues,
20
21
  mutator,
21
- multiple='',
22
+ multiple=false,
22
23
  buttonClx='',
24
+ itemClx='',
23
25
  className='',
24
26
  isMobile=false,
25
27
  tabSize
@@ -73,7 +75,7 @@ const FacetTogglesWidget: React.FC<{
73
75
  {...roundedToSpread}
74
76
  className={buttonClx}
75
77
  >
76
- <span className={cn('flex flex-row justify-center gap-1 h-4 items-center')} >
78
+ <span className={cn('flex flex-row justify-center gap-1 h-6 items-center', itemClx)} >
77
79
  <FacetImage facetValueDesc={fv} />
78
80
  {(!isMobile || !fv.img) && (<span className='whitespace-nowrap'>{fv.label}</span>)}
79
81
  </span>
@@ -85,4 +87,4 @@ const FacetTogglesWidget: React.FC<{
85
87
  }
86
88
  // hidden md:block
87
89
 
88
- export default FacetTogglesWidget
90
+ export default FacetValuesWidget
@@ -1,12 +1,14 @@
1
1
  export { default as AddToCartWidget } from './add-to-cart-widget'
2
- export { default as Cart } from './cart'
3
- export { default as CartLineItem } from './cart-line-item'
4
- export { default as FacetsWidget } from './facets-widget'
5
- export { default as FacetTogglesWidget } from './facet-toggles-widget'
2
+ export { default as BuyItemButton } from './buy-item/buy-item-button'
3
+ export { default as BuyItemCard } from './buy-item/buy-item-card'
4
+ export { default as BuyItemPopup } from './buy-item/buy-item-popup'
5
+ export { default as CartPanel } from './cart-panel'
6
+ export { default as CheckoutPanel } from './checkout-panel'
7
+ export { default as CategoryItemIOSWheelSelector } from './category-item-ios-wheel-selector'
8
+ export { default as CategoryItemRadioSelector } from './category-item-radio-selector'
9
+ export { default as FacetValuesWidget } from './facet-values-widget'
6
10
  export { default as ProductCard } from './product-card'
7
- export { default as ProductSelectionMobilePicker } from './product-selection-mobile-picker'
8
- export { default as ProductSelectionRadioGroup } from './product-selection-radio-group'
9
- export { default as SelectCategoryAndItemWidget } from './select-category-and-item-widget'
10
- export { default as SelectItemInCategoryView } from './select-item-in-category-view'
11
- export { default as Checkout } from './checkout'
11
+ export { default as SelectCategoryAndItemWidget } from './buy-item/select-category-and-item-widget'
12
+ export { default as SelectCategoryItemCard } from './buy-item/select-category-item-card'
13
+ export { default as SelectCategoryItemPanel } from './select-category-item-panel'
12
14
  export { Icons } from './Icons'
@@ -6,35 +6,36 @@ import { observer } from 'mobx-react-lite'
6
6
  import { cn } from '@hanzo/ui/util'
7
7
  import { Skeleton } from '@hanzo/ui/primitives'
8
8
 
9
- import type { Category, ObsLineItemRef } from '../types'
9
+ import type { ItemSelector } from '../types'
10
10
  import { formatPrice } from '../util'
11
11
  import { Icons } from './Icons'
12
12
 
13
13
  import AddToCartWidget from './add-to-cart-widget'
14
- import ProductSelectionRadioGroup from './product-selection-radio-group'
15
- import ProductSelectionMobilePicker from './product-selection-mobile-picker'
16
-
17
- const SelectItemInCategoryView: React.FC<React.HTMLAttributes<HTMLDivElement> & {
18
- category: Category
19
- lineItemRef: ObsLineItemRef
20
- handleItemSelected: (sku: string) => void
21
- isLoading?: boolean
22
- mobile?: boolean
23
- }> = ({
14
+ import CategoryItemRadioSelector from './category-item-radio-selector'
15
+ import CategoryItemIOSWheelSelector from './category-item-ios-wheel-selector'
16
+
17
+ const SelectCategoryItemPanel: React.FC<
18
+ React.HTMLAttributes<HTMLDivElement> & ItemSelector &
19
+ {
20
+ isLoading?: boolean
21
+ mobile?: boolean
22
+ }
23
+ > = /* NOT observer */ ({
24
24
  category,
25
- lineItemRef,
26
- handleItemSelected,
25
+ selectedItemRef,
26
+ selectSku,
27
27
  className,
28
+ showQuantity=true,
28
29
  isLoading = false,
29
30
  mobile = false,
30
31
  ...props
31
32
  }) => {
32
33
 
33
- const waiting = (): boolean => (isLoading) // keep for convenience for now
34
+ const soleOption = category.products.length === 1
34
35
 
35
36
  const CategoryImage: React.FC<{ className?: string }> = ({ className = '' }) => {
36
37
 
37
- if (waiting()) {
38
+ if (isLoading) {
38
39
  // deliberately not Skeleton to have a better overall pulse effect.
39
40
  return <div className={cn(
40
41
  'bg-level-1 rounded-xl aspect-square w-full min-h-1 ', // +
@@ -42,25 +43,13 @@ const SelectItemInCategoryView: React.FC<React.HTMLAttributes<HTMLDivElement> &
42
43
  className)} />
43
44
  }
44
45
 
45
- if (!category.img) {
46
- return (
47
- <div
48
- aria-label='Placeholder'
49
- role='img'
50
- aria-roledescription='placeholder'
51
- className={cn('w-full flex items-center justify-center aspect-square' , className)}
52
- >
53
- <Icons.barcode className='h-9 w-9 text-muted' aria-hidden='true' />
54
- </div>
55
- )
56
- }
57
-
58
- return (
46
+ return category.img ? (
47
+ // TODO: Why so many div's?
59
48
  <div className={cn('flex flex-col justify-start', className)}>
60
49
  <div className={cn('w-full border rounded-xl p-6 ')}>
61
50
  <div className={cn('w-full aspect-square relative')}>
62
51
  <Image
63
- src={category.img!}
52
+ src={category.img}
64
53
  fill
65
54
  sizes="(max-width: 480px) 100vw, (max-width: 768px) 50vw, (max-width: 1200px) 50vw, 20vw"
66
55
  alt={category.title}
@@ -70,81 +59,88 @@ const SelectItemInCategoryView: React.FC<React.HTMLAttributes<HTMLDivElement> &
70
59
  </div>
71
60
  </div>
72
61
  </div>
62
+ ) : (
63
+ <div
64
+ aria-label='Placeholder'
65
+ role='img'
66
+ aria-roledescription='placeholder'
67
+ className={cn('w-full flex items-center justify-center aspect-square' , className)}
68
+ >
69
+ <Icons.barcode className='h-9 w-9 text-muted' aria-hidden='true' />
70
+ </div>
73
71
  )
74
72
  }
75
73
 
76
74
  const AvailableAmounts: React.FC<{ className?: string }> = observer(({ className = '' }) => {
77
75
 
78
- const soleOption = !waiting() && category.products.length === 1
79
- const mobilePicker = !waiting() && (mobile && category.products.length > 8)
76
+ if (soleOption) return null
77
+ const mobilePicker = (mobile && category.products.length > 8)
80
78
 
81
- return waiting() ? (
79
+ return isLoading ? (
82
80
  <Skeleton className={'min-h-[120px] w-pr-60 mx-auto ' + className} />
83
81
  ) : (
84
82
  <div /* id='CV_AVAIL_AMOUNTS' */ className={cn(
85
83
  'sm:w-pr-80 sm:mx-auto md:w-full flex flex-col justify-start items-center',
86
- (soleOption ? 'gap-2' : mobilePicker ? 'gap-4' : 'gap-8'),
84
+ (mobilePicker ? 'gap-4' : 'gap-8'),
87
85
  className
88
86
  )}>
89
87
  <div className='w-full flex flex-col justify-start items-center'>
90
- <h6 className='text-center font-semibold'>{`Available size${soleOption ? '' : 's'}`}</h6>
91
- <div className={'h-[1px] bg-muted-3 ' + (mobilePicker ? 'w-pr-55' : 'w-pr-80') } />
88
+ <h6 className='text-center font-semibold'>Available options</h6>
89
+ <div className={'h-[1px] bg-muted-3 ' + (mobilePicker ? 'w-pr-55' : 'w-pr-60') } />
92
90
  </div>
93
- {soleOption ? (
94
- <p>{category.products[0].titleAsOption}</p>
95
- ) : ( mobilePicker ? (
96
- <ProductSelectionMobilePicker
97
- products={category.products}
98
- selectedSku={lineItemRef.item?.sku ?? undefined}
99
- onValueChange={handleItemSelected}
100
- height={180}
101
- itemHeight={30}
102
- outerClx='mb-4'
103
- />
104
- ) : (
105
- <ProductSelectionRadioGroup
106
- products={category.products}
107
- selectedSku={lineItemRef.item?.sku ?? undefined}
108
- onValueChange={handleItemSelected}
109
- groupClx='grid grid-cols-2 gap-0 gap-y-3 gap-x-8 '
110
- itemClx='flex flex-row gap-2 items-center'
111
- />
112
- )
91
+ {mobilePicker ? (
92
+ <CategoryItemIOSWheelSelector
93
+ category={category}
94
+ selectedItemRef={selectedItemRef}
95
+ selectSku={selectSku}
96
+ showQuantity={showQuantity}
97
+ height={180}
98
+ itemHeight={30}
99
+ outerClx='mb-4'
100
+ />
101
+ ) : (
102
+ <CategoryItemRadioSelector
103
+ category={category}
104
+ selectedItemRef={selectedItemRef}
105
+ selectSku={selectSku}
106
+ showQuantity={showQuantity}
107
+ groupClx='block columns-2 gap-4'
108
+ itemClx='flex flex-row gap-2 items-center mb-2.5'
109
+ />
113
110
  )}
114
111
  </div>
115
112
  )
116
113
  })
117
114
 
118
115
  const AddToCartArea: React.FC<{ className?: string }> = observer(({ className = '' }) => (
119
- (lineItemRef.item && !isLoading) ? (
120
- <AddToCartWidget size='default' item={lineItemRef.item} className={className}/>
116
+ (selectedItemRef.item && !isLoading) ? (
117
+ <AddToCartWidget size='default' item={selectedItemRef.item} className={className}/>
121
118
  ) : (
122
119
  <div className={cn('h-6 w-12 invisible', className)} />
123
120
  )
124
121
  ))
125
122
 
126
123
  const TitleArea: React.FC<{ className?: string }> = observer(({ className = '' }) => (
127
- waiting() ? (<Skeleton className={'h-12 w-pr-80 mx-auto ' + className} />) : (
128
-
129
- <div className={cn('flex flex-col justify-start items-center mb-6', className)}>
130
- <h3 className='text-base md:text-lg lg:text-2xl md:mt-[2vw] font-nav text-center'>
131
- <span className='xs:inline md:hidden'>
132
- {category.title.split(', ').map((s, i) => (<p key={i}>{s}</p>))}
133
- </span>
134
- <span className='xs:hidden md:inline '>
135
- {category.title}
136
- </span>
124
+
125
+ isLoading ? (<Skeleton className={'h-12 w-pr-80 mx-auto ' + className} />) : (
126
+
127
+ <div className={cn('flex flex-col justify-start items-center', className)}>
128
+ <h3 className='text-base md:text-lg lg:text-2xl font-nav text-center'>
129
+ {category.parentTitle && (
130
+ <span>{category.parentTitle}<br className='md:hidden' /><span className='xs:hidden md:inline '>,&nbsp;</span></span>
131
+ )}
132
+ <span>{category.title}</span>
137
133
  </h3>
138
- {lineItemRef.item?.sku ? (
134
+ {selectedItemRef.item?.sku ? (
139
135
  <h6 className='text-center font-semibold'>
140
- {lineItemRef.item.titleAsOption + ': ' + formatPrice(lineItemRef.item.price)}
136
+ {(soleOption ? '' : (selectedItemRef.item.titleAsOption + ': ')) + formatPrice(selectedItemRef.item.price)}
141
137
  </h6>
142
138
  ) : ''}
143
139
  </div>
144
140
  )))
145
141
 
146
142
  const Desc: React.FC<{ className?: string }> = ({ className = '' }) => (
147
- waiting() ? (
143
+ isLoading ? (
148
144
  <Skeleton className={'min-h-20 w-full grow mx-auto ' + className} />
149
145
  ) : (
150
146
  <p className={cn('text-base lg:text-lg mb-6 xs:mb-0', className)}>{category.desc}</p>
@@ -161,7 +157,7 @@ const SelectItemInCategoryView: React.FC<React.HTMLAttributes<HTMLDivElement> &
161
157
  {...props}
162
158
  >
163
159
  <div /* id='CV_TITLE_AND_IMAGE_ROW' */ className='flex flex-row justify-between items-start w-full'>
164
- {waiting() ? ( <Skeleton className={'min-h-30 w-full '} /> ) : (<>
160
+ {isLoading ? ( <Skeleton className={'min-h-30 w-full '} /> ) : (<>
165
161
  <CategoryImage className='w-pr-33' />
166
162
  <TitleArea className='grow pt-3 mb-0' />
167
163
  </>)}
@@ -176,17 +172,17 @@ const SelectItemInCategoryView: React.FC<React.HTMLAttributes<HTMLDivElement> &
176
172
  <div /* id='CV_IMAGE_COL_SEP' */ className={'relative ' + (!isLoading ? 'md:hidden sm:min-w-[185px] xl:flex xl:w-pr-40' : '')}>
177
173
  <CategoryImage className='w-full' />
178
174
  </div>
179
- <div /* id='CV_CONTENT_COL */ className='flex flex-col gap-y-6 xl:w-pr-60'>
175
+ <div /* id='CV_CONTENT_COL */ className={'flex flex-col xl:w-pr-60 justify-center ' + (soleOption ? '' : 'gap-6') }>
180
176
  <div /* id='CV_MD_IMAGE_AND_TITLE */ className='hidden md:flex xl:hidden flex-row gap-x-3'>
181
177
  <CategoryImage className='min-w-pr-30' />
182
178
  <TitleArea className='grow' />
183
179
  </div>
184
-
180
+
185
181
  <div /* id='CV_CONTENT' */ className={'flex flex-col gap-2.5 ' + (isLoading ? 'justify-between h-full' : '')}>
186
182
  <TitleArea className='md:hidden xl:flex' />
187
183
  <Desc className='' />
188
184
  </div>
189
- <div /* id='CV_CTA_AREA_BIG' */ className='hidden lg:flex p-4 flex-col justify-start items-center gap-6'>
185
+ <div /* id='CV_CTA_AREA_BIG' */ className='hidden lg:flex flex-col justify-start items-center gap-6'>
190
186
  <AvailableAmounts />
191
187
  <AddToCartArea className='' />
192
188
  </div>
@@ -201,4 +197,4 @@ const SelectItemInCategoryView: React.FC<React.HTMLAttributes<HTMLDivElement> &
201
197
  }
202
198
 
203
199
 
204
- export default SelectItemInCategoryView
200
+ export default SelectCategoryItemPanel
package/index.ts CHANGED
@@ -1,4 +1,4 @@
1
- export * from './types'
2
1
  export * from './service/context'
2
+ export * from './components'
3
3
  export type { StandaloneServiceOptions as ServiceOptions } from './service/impls/standalone/standalone-service'
4
- export { useSyncSkuParamWithCurrentItem } from './util'
4
+ export { useSyncSkuParamWithCurrentItem, getFacetValuesMutator, formatPrice } from './util'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hanzo/commerce",
3
- "version": "2.1.0",
3
+ "version": "3.0.1",
4
4
  "description": "Library with shopping cart components.",
5
5
  "publishConfig": {
6
6
  "registry": "https://registry.npmjs.org/",
@@ -26,6 +26,10 @@
26
26
  "tc": "tsc",
27
27
  "clean": "rm -rf dist && rm -rf node_modules"
28
28
  },
29
+ "exports": {
30
+ ".": "./index.ts",
31
+ "./types": "./types/index.ts"
32
+ },
29
33
  "dependencies": {
30
34
  "ethers": "^6.11.1",
31
35
  "next-usequerystate": "^1.17.0",
@@ -34,13 +38,14 @@
34
38
  "square": "^35.0.0"
35
39
  },
36
40
  "peerDependencies": {
37
- "@hanzo/auth": "^2.0.1",
38
- "@hanzo/ui": "^3.0.0",
41
+ "@hanzo/auth": "^2.0.2",
42
+ "@hanzo/ui": "^3.0.2",
39
43
  "@hookform/resolvers": "^3.3.4",
40
44
  "firebase": "^10.8.0",
41
45
  "lucide-react": "^0.307.0",
42
46
  "mobx": "^6.12.0",
43
47
  "mobx-react-lite": "^4.0.5",
48
+ "mobx-utils": "^6.0.8",
44
49
  "next": "^14.1.0",
45
50
  "react": "^18.2.0",
46
51
  "react-dom": "^18.2.0",
@@ -9,7 +9,7 @@ enableStaticRendering(typeof window === "undefined")
9
9
  import type CommerceService from '../types/commerce-service'
10
10
  import type { ServiceOptions } from '..'
11
11
  import getServiceSingleton from './impls'
12
- import type { Category, FacetsDesc } from '../types'
12
+ import type { Category, FacetValueDesc } from '../types'
13
13
 
14
14
  const CommerceServiceContext = createContext<CommerceService | undefined>(undefined)
15
15
 
@@ -20,16 +20,16 @@ const useCommerce = (): CommerceService => {
20
20
 
21
21
  const CommerceServiceProvider: React.FC<PropsWithChildren & {
22
22
  productsByCategory: Category[]
23
- facets: FacetsDesc
23
+ rootFacet: FacetValueDesc
24
24
  options?: ServiceOptions
25
25
  }> = ({
26
26
  children,
27
27
  productsByCategory,
28
- facets,
28
+ rootFacet,
29
29
  options
30
30
  }) => {
31
31
 
32
- const serviceRef = useRef<CommerceService>(getServiceSingleton(productsByCategory, facets, options))
32
+ const serviceRef = useRef<CommerceService>(getServiceSingleton(productsByCategory, rootFacet, options))
33
33
  return (
34
34
  <CommerceServiceContext.Provider value={serviceRef.current}>
35
35
  {children}
@@ -1,6 +1,6 @@
1
1
  import { enableStaticRendering } from 'mobx-react-lite'
2
2
 
3
- import type { CommerceService, Category, FacetsDesc } from '../../../types'
3
+ import type { CommerceService, Category, FacetValueDesc } from '../../../types'
4
4
  import StandaloneService, {type StandaloneServiceOptions} from './standalone-service'
5
5
 
6
6
  import { readSnapshot, listenAndWriteSnapshots } from './localStorage'
@@ -19,7 +19,7 @@ let instance: StandaloneService | undefined = undefined
19
19
 
20
20
  export const getInstance = (
21
21
  categories: Category[],
22
- facets: FacetsDesc,
22
+ rootFacet: FacetValueDesc,
23
23
  options?: StandaloneServiceOptions
24
24
  ): CommerceService => {
25
25
 
@@ -31,7 +31,7 @@ export const getInstance = (
31
31
  //_log("NEW INSTANCE FOR SERVER")
32
32
  return new StandaloneService(
33
33
  categories,
34
- facets,
34
+ rootFacet,
35
35
  options
36
36
  )
37
37
  }
@@ -42,7 +42,7 @@ export const getInstance = (
42
42
  const snapShot = readSnapshot()
43
43
  instance = new StandaloneService(
44
44
  categories,
45
- facets,
45
+ rootFacet,
46
46
  options,
47
47
  snapShot
48
48
  )