@hanzo/commerce 5.1.3 → 6.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.
Files changed (67) hide show
  1. package/components/{buy-item/select-category-item-card.tsx → buy/_to_deprecate_select-category-item-card.tsx} +22 -32
  2. package/components/{select-category-item-panel.tsx → buy/_to_deprecate_select-category-item-panel.tsx} +16 -22
  3. package/components/{buy-item/buy-item-popup.tsx → buy/_unused_buy-popup.tsx_} +4 -4
  4. package/components/{add-to-cart-widget.tsx → buy/add-to-cart-widget.tsx} +10 -6
  5. package/components/buy/buy-button.tsx +31 -0
  6. package/components/buy/buy-card.tsx +267 -0
  7. package/components/buy/buy-drawer.tsx +89 -0
  8. package/components/buy/buy-trigger-wrapper.tsx +20 -0
  9. package/components/{cart-accordian.tsx → cart/cart-accordian.tsx} +2 -1
  10. package/components/{cart-panel → cart/cart-panel}/cart-line-item.tsx +27 -14
  11. package/components/{cart-panel → cart/cart-panel}/index.tsx +12 -3
  12. package/components/{cart-panel → cart/cart-panel}/promo-code.tsx +3 -3
  13. package/components/{payment-step-form → checkout/payment-step-form}/contact-form.tsx +1 -1
  14. package/components/{payment-step-form → checkout/payment-step-form}/index.tsx +3 -3
  15. package/components/{payment-step-form → checkout/payment-step-form}/methods/bank-transfer.tsx +1 -1
  16. package/components/{payment-step-form → checkout/payment-step-form}/methods/card.tsx +4 -4
  17. package/components/{payment-step-form → checkout/payment-step-form}/methods/crypto.tsx +3 -3
  18. package/components/{payment-step-form → checkout/payment-step-form}/methods/index.ts +1 -1
  19. package/components/{shipping-step-form.tsx → checkout/shipping-step-form.tsx} +4 -4
  20. package/components/index.ts +20 -18
  21. package/components/item/item-media.tsx +51 -0
  22. package/components/item/product-card.tsx +48 -0
  23. package/components/{facet-values-widget → level-nodes-widget}/index.tsx +20 -19
  24. package/components/level-nodes-widget/node-image.tsx +31 -0
  25. package/components/select/carousel-selector.tsx +112 -0
  26. package/components/select/image-selector.tsx +162 -0
  27. package/components/select/radio-selector.tsx +125 -0
  28. package/package.json +3 -2
  29. package/service/context.tsx +4 -4
  30. package/service/impls/standalone/actual-line-item.ts +30 -16
  31. package/service/impls/standalone/index.ts +4 -4
  32. package/service/impls/standalone/standalone-service.ts +94 -64
  33. package/types/buy-ui-spec.ts +8 -0
  34. package/types/category.ts +6 -4
  35. package/types/commerce-service.ts +21 -16
  36. package/types/index.ts +3 -2
  37. package/types/item-selector.ts +34 -6
  38. package/types/line-item.ts +2 -0
  39. package/types/product.ts +5 -5
  40. package/types/tree-node.ts +24 -0
  41. package/util/buy-ui-conf.ts +34 -0
  42. package/util/index.ts +14 -11
  43. package/util/obs-string-mutator.ts +22 -0
  44. package/util/use-sync-sku-param-w-current-item.ts +5 -5
  45. package/components/buy-item/buy-item-button-wrapper.tsx +0 -27
  46. package/components/buy-item/buy-item-button.tsx +0 -34
  47. package/components/buy-item/buy-item-card.tsx +0 -92
  48. package/components/buy-item/buy-item-mobile-drawer.tsx +0 -54
  49. package/components/buy-item/select-category-and-item-widget.tsx +0 -83
  50. package/components/category-item-radio-selector.tsx +0 -55
  51. package/components/category-item-scroll-selector.tsx +0 -40
  52. package/components/facet-values-widget/facet-image.tsx +0 -41
  53. package/components/item-carousel.tsx +0 -80
  54. package/components/product-card.tsx +0 -79
  55. package/types/facet.ts +0 -35
  56. /package/components/{cart-icon.tsx → cart/cart-icon.tsx} +0 -0
  57. /package/components/{payment-step-form → checkout/payment-step-form}/card-icon-row.tsx +0 -0
  58. /package/components/{payment-step-form → checkout/payment-step-form}/card-icons/amex.tsx +0 -0
  59. /package/components/{payment-step-form → checkout/payment-step-form}/card-icons/diners-club.tsx +0 -0
  60. /package/components/{payment-step-form → checkout/payment-step-form}/card-icons/discover.tsx +0 -0
  61. /package/components/{payment-step-form → checkout/payment-step-form}/card-icons/jcb.tsx +0 -0
  62. /package/components/{payment-step-form → checkout/payment-step-form}/card-icons/mastercard.tsx +0 -0
  63. /package/components/{payment-step-form → checkout/payment-step-form}/card-icons/visa.tsx +0 -0
  64. /package/components/{payment-step-form → checkout/payment-step-form}/cc-button.tsx +0 -0
  65. /package/components/{payment-step-form → checkout/payment-step-form}/crypto-icons/btc.tsx +0 -0
  66. /package/components/{payment-step-form → checkout/payment-step-form}/crypto-icons/eth.tsx +0 -0
  67. /package/components/{payment-step-form → checkout/payment-step-form}/crypto-icons/usdt.tsx +0 -0
@@ -0,0 +1,162 @@
1
+ 'use client'
2
+ import React from 'react'
3
+ import { observer } from 'mobx-react-lite'
4
+
5
+ import * as RadioGroupPrimitive from "@radix-ui/react-radio-group"
6
+
7
+ import {
8
+ Image,
9
+ Label,
10
+ RadioGroup,
11
+ ScrollArea
12
+ } from '@hanzo/ui/primitives'
13
+ import { cn } from '@hanzo/ui/util'
14
+
15
+ import type { ItemSelectorProps, LineItem } from '../../types'
16
+ import { formatCurrencyValue } from '../../util'
17
+
18
+ const ImageRadioGroupItem = React.forwardRef<
19
+ React.ElementRef<typeof RadioGroupPrimitive.Item>,
20
+ Omit<React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Item>, 'value' | 'id'> & {
21
+ item: LineItem,
22
+ constrainTo: {w: number, h: number}
23
+ }
24
+ >(({
25
+ item,
26
+ constrainTo,
27
+ className,
28
+ ...props
29
+ }, ref) => {
30
+
31
+ return (
32
+ <RadioGroupPrimitive.Item
33
+ ref={ref}
34
+ className={cn(
35
+ "ring-offset-background focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
36
+ className
37
+ )}
38
+ {...props}
39
+ id={item.sku}
40
+ value={item.sku}
41
+ >
42
+ {item.img ? (
43
+ <Image def={item.img} constrainTo={constrainTo} preload className=''/>
44
+ ) : ( // placeholder so things align
45
+ <div style={{height: constrainTo.h, width: constrainTo.w}}/>
46
+ )}
47
+ </RadioGroupPrimitive.Item>
48
+ )
49
+ })
50
+ ImageRadioGroupItem.displayName = 'ImageRadioGroupItem'
51
+
52
+ const ImageItemSelector: React.FC<ItemSelectorProps> = observer(({
53
+ items,
54
+ selectedItemRef: itemRef,
55
+ selectSku,
56
+ clx='',
57
+ itemClx='',
58
+ soleItemClx='',
59
+ showCategory=false,
60
+ showQuantity=false,
61
+ scrollList=false
62
+ }) => {
63
+
64
+ const LabelText: React.FC<{item: LineItem}> = ({item}) => (
65
+ (showCategory ? (item.categoryTitle + ', ' + item.optionLabel) : item.optionLabel) +
66
+ (showCategory ? ': ' : ', ') + formatCurrencyValue(item.price)
67
+ )
68
+
69
+ const ItemAndPrice: React.FC<{
70
+ item: LineItem
71
+ listBoxMode: boolean
72
+ selected: boolean
73
+ className?: string
74
+ }> = ({
75
+ item,
76
+ listBoxMode,
77
+ selected,
78
+ className=''
79
+ }) => (
80
+ <div className={cn(
81
+ 'flex items-center',
82
+ className,
83
+ itemClx,
84
+ )}>
85
+ <ImageRadioGroupItem
86
+ item={item}
87
+ constrainTo={{h: 36, w: 72}} // Apple suggest 42px
88
+ className={
89
+ 'mr-2 ' + (listBoxMode ? '' :
90
+ 'border-transparent border-2 rounded-sm data-[state=checked]:border-foreground')
91
+ }
92
+ />
93
+ <Label htmlFor={item.sku} className={selected && listBoxMode ? 'text-accent' : ''}>
94
+ <LabelText item={item} />
95
+ </Label>
96
+ </div>
97
+ )
98
+
99
+ const Item: React.FC<{
100
+ item: LineItem
101
+ selected: boolean
102
+ listBoxMode: boolean
103
+ clx?: string
104
+ }> = ({
105
+ item,
106
+ selected,
107
+ listBoxMode,
108
+ clx=''
109
+ }) => {
110
+
111
+ const outClx = [
112
+ (listBoxMode ? 'border-b border-muted-3 py-1' : 'mb-3'),
113
+ (selected && listBoxMode ? 'border border-foreground rounded-sm' : ''),
114
+ ]
115
+
116
+ return (showQuantity ? (
117
+ <div key={item.sku} className={cn('flex flex-row items-center', ...outClx, clx )}>
118
+ <ItemAndPrice item={item} selected={selected} listBoxMode={listBoxMode} className='grow'/>
119
+ <div className='grow-0 shrink-0 font-semibold text-sm leading-none px-2'>{ item.quantity > 0 ? `(${item.quantity})` : ' '}</div>
120
+ </div>
121
+ ) : (
122
+ <ItemAndPrice
123
+ key={item.sku}
124
+ item={item}
125
+ selected={selected}
126
+ listBoxMode={listBoxMode}
127
+ className={cn(...outClx,
128
+ listBoxMode ? '' : 'mb-1',
129
+ clx)}
130
+ />
131
+ ))
132
+ }
133
+
134
+ return items.length > 1 ? (
135
+ <RadioGroup
136
+ className={cn('flex flex-col gap-0',
137
+ (scrollList ? 'shrink min-h-0' : ''),
138
+ clx,
139
+ )}
140
+ onValueChange={selectSku}
141
+ value={itemRef.item ? itemRef.item.sku : ''}
142
+ >
143
+ {scrollList ? (
144
+ <ScrollArea className='mt-2 w-full h-full py-0 border border-muted-2 rounded-sm '>
145
+ {items.map((item) => (
146
+ <Item item={item} listBoxMode={true} selected={itemRef.item?.sku === item.sku}/>
147
+ ))}
148
+ </ScrollArea>
149
+ ) : (<>
150
+ {items.map((item) => (
151
+ <Item item={item} listBoxMode={false} selected={itemRef.item?.sku === item.sku}/>
152
+ ))}
153
+ </>)}
154
+ </RadioGroup>
155
+ ) : (
156
+ <div className={soleItemClx}>
157
+ <LabelText item={items[0]} />
158
+ </div>
159
+ )
160
+ })
161
+
162
+ export default ImageItemSelector
@@ -0,0 +1,125 @@
1
+ 'use client'
2
+ import React from 'react'
3
+ import { observer } from 'mobx-react-lite'
4
+
5
+ import {
6
+ Label,
7
+ RadioGroup,
8
+ RadioGroupItem,
9
+ ScrollArea
10
+ } from '@hanzo/ui/primitives'
11
+ import { cn } from '@hanzo/ui/util'
12
+
13
+ import type { ItemSelectorProps, LineItem } from '../../types'
14
+ import { formatCurrencyValue } from '../../util'
15
+
16
+ const RadioItemSelector: React.FC<ItemSelectorProps> = observer(({
17
+ items,
18
+ selectedItemRef: itemRef,
19
+ selectSku,
20
+ clx='',
21
+ itemClx='',
22
+ soleItemClx='',
23
+ showCategory=false,
24
+ showQuantity=false,
25
+ scrollList=false
26
+ }) => {
27
+
28
+ const LabelText: React.FC<{item: LineItem}> = ({item}) => (
29
+ (showCategory ? (item.categoryTitle + ', ' + item.optionLabel) : item.optionLabel) +
30
+ (showCategory ? ': ' : ', ') + formatCurrencyValue(item.price)
31
+ )
32
+
33
+ const ItemAndPrice: React.FC<{
34
+ item: LineItem
35
+ listBoxMode: boolean
36
+ selected: boolean
37
+ className?: string
38
+ }> = ({
39
+ item,
40
+ listBoxMode,
41
+ selected,
42
+ className=''
43
+ }) => (
44
+ <div className={cn(
45
+ 'flex items-center',
46
+ className,
47
+ itemClx,
48
+ )}>
49
+ <RadioGroupItem
50
+ value={item.sku}
51
+ larger
52
+ id={item.sku}
53
+ className={'mr-2 ' + (listBoxMode ? 'hidden' : '')}
54
+ />
55
+ <Label htmlFor={item.sku} className={selected ? 'text-accent' : ''}>
56
+ <LabelText item={item} />
57
+ </Label>
58
+ </div>
59
+ )
60
+
61
+ const Item: React.FC<{
62
+ item: LineItem
63
+ selected: boolean
64
+ listBoxMode: boolean
65
+ clx?: string
66
+ }> = ({
67
+ item,
68
+ selected,
69
+ listBoxMode,
70
+ clx=''
71
+ }) => {
72
+
73
+ const outClx = [
74
+ 'h-10',
75
+ (listBoxMode ? 'border-b border-muted-3 py-1 pl-2' : 'mb-3'),
76
+ (selected && listBoxMode ? 'border border-foreground rounded-sm' : ''),
77
+ ]
78
+
79
+ return (showQuantity ? (
80
+ <div key={item.sku} className={cn('flex flex-row items-center', ...outClx, clx )}>
81
+ <ItemAndPrice item={item} selected={selected} listBoxMode={listBoxMode} className='grow'/>
82
+ <div className='grow-0 shrink-0 font-semibold text-sm leading-none px-2'>{ item.quantity > 0 ? `(${item.quantity})` : ' '}</div>
83
+ </div>
84
+ ) : (
85
+ <ItemAndPrice
86
+ key={item.sku}
87
+ item={item}
88
+ selected={selected}
89
+ listBoxMode={listBoxMode}
90
+ className={cn(...outClx,
91
+ listBoxMode ? '' : 'mb-1',
92
+ clx)}
93
+ />
94
+ ))
95
+ }
96
+
97
+ return items.length > 1 ? (
98
+ <RadioGroup
99
+ className={cn('flex flex-col gap-0',
100
+ (scrollList ? 'shrink min-h-0' : ''),
101
+ clx,
102
+ )}
103
+ onValueChange={selectSku}
104
+ value={itemRef.item ? itemRef.item.sku : ''}
105
+ >
106
+ {scrollList ? (
107
+ <ScrollArea className='mt-2 w-full h-full py-0 border border-muted-2 rounded-sm '>
108
+ {items.map((item) => (
109
+ <Item item={item} listBoxMode={true} selected={itemRef.item?.sku === item.sku}/>
110
+ ))}
111
+ </ScrollArea>
112
+ ) : (<>
113
+ {items.map((item) => (
114
+ <Item item={item} listBoxMode={false} selected={itemRef.item?.sku === item.sku}/>
115
+ ))}
116
+ </>)}
117
+ </RadioGroup>
118
+ ) : (
119
+ <div className={soleItemClx}>
120
+ <LabelText item={items[0]} />
121
+ </div>
122
+ )
123
+ })
124
+
125
+ export default RadioItemSelector
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hanzo/commerce",
3
- "version": "5.1.3",
3
+ "version": "6.1.0",
4
4
  "description": "Library with shopping cart components.",
5
5
  "publishConfig": {
6
6
  "registry": "https://registry.npmjs.org/",
@@ -40,8 +40,9 @@
40
40
  },
41
41
  "peerDependencies": {
42
42
  "@hanzo/auth": "^2.3.3",
43
- "@hanzo/ui": "^3.1.0",
43
+ "@hanzo/ui": "^3.2.0",
44
44
  "@hookform/resolvers": "^3.3.4",
45
+ "@radix-ui/react-radio-group": "^1.1.3",
45
46
  "firebase": "^10.8.0",
46
47
  "lucide-react": "^0.307.0",
47
48
  "mobx": "^6.12.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, FacetValueDesc } from '../types'
12
+ import type { Category, ProductTreeNode } from '../types'
13
13
 
14
14
  const CommerceServiceContext = createContext<CommerceService | undefined>(undefined)
15
15
 
@@ -20,17 +20,17 @@ const useCommerce = (): CommerceService => {
20
20
 
21
21
  const CommerceServiceProvider: React.FC<PropsWithChildren & {
22
22
  productsByCategory: Category[]
23
- rootFacet: FacetValueDesc
23
+ rootNode: ProductTreeNode
24
24
  options?: ServiceOptions
25
25
  }> = ({
26
26
  children,
27
27
  productsByCategory,
28
- rootFacet,
28
+ rootNode,
29
29
  options
30
30
  }) => {
31
31
 
32
32
  // TODO: Inject Promo fixture here
33
- const serviceRef = useRef<CommerceService>(getServiceSingleton(productsByCategory, rootFacet, options))
33
+ const serviceRef = useRef<CommerceService>(getServiceSingleton(productsByCategory, rootNode, options))
34
34
  return (
35
35
  <CommerceServiceContext.Provider value={serviceRef.current}>
36
36
  {children}
@@ -5,9 +5,9 @@ import {
5
5
  observable,
6
6
  } from 'mobx'
7
7
 
8
- import type { VideoDef } from '@hanzo/ui/types'
8
+ import type { ImageDef, VideoDef } from '@hanzo/ui/types'
9
9
 
10
- import type { Product, LineItem } from '../../../types'
10
+ import type { Product, LineItem, CommerceService } from '../../../types'
11
11
 
12
12
  interface ActualLineItemSnapshot {
13
13
  sku: string
@@ -26,12 +26,13 @@ class ActualLineItem
26
26
 
27
27
  id: string
28
28
  sku: string
29
- title: string
30
- titleAsOption: string
29
+ fullTitle?: string
30
+ optionLabel: string
31
+ categoryTitle: string
31
32
  categoryId: string
32
33
  desc?: string
33
34
  price: number
34
- img?: string
35
+ img?: ImageDef
35
36
  video?: VideoDef
36
37
  animation?: string
37
38
  timeAdded: number = 0 // timeAdded of being added to cart
@@ -39,8 +40,9 @@ class ActualLineItem
39
40
  constructor(prod: Product, snap?: ActualLineItemSnapshot) {
40
41
  this.id = prod.id
41
42
  this.sku = prod.sku
42
- this.title = prod.title
43
- this.titleAsOption = prod.titleAsOption
43
+ this.fullTitle = prod.fullTitle
44
+ this.optionLabel = prod.optionLabel
45
+ this.categoryTitle = prod.categoryTitle
44
46
  this.categoryId = prod.categoryId
45
47
  this.desc = prod.desc
46
48
  this.price = prod.price
@@ -58,20 +60,32 @@ class ActualLineItem
58
60
  timeAdded: observable,
59
61
  canDecrement: computed,
60
62
  isInCart: computed,
61
-
63
+ title: computed,
62
64
  increment: action,
63
65
  decrement: action,
64
66
  })
65
67
  }
66
68
 
67
- takeSnapshot = (): ActualLineItemSnapshot => ({
68
- sku: this.sku,
69
- categoryId: this.categoryId,
70
- title: this.title,
71
- price: this.price,
72
- quantity: this.qu,
73
- timeAdded: this.timeAdded
74
- } satisfies ActualLineItemSnapshot)
69
+ get title(): string {
70
+ return this.fullTitle ? this.fullTitle : (this.categoryTitle + ', ' + this.optionLabel)
71
+ }
72
+
73
+ takeSnapshot = (cmmc: CommerceService): ActualLineItemSnapshot => {
74
+
75
+ const title = this.fullTitle ?
76
+ this.fullTitle
77
+ :
78
+ ((cmmc.getCategory(this.categoryId)?.title ?? this.categoryTitle) + ', ' + this.optionLabel)
79
+
80
+ return({
81
+ sku: this.sku,
82
+ categoryId: this.categoryId,
83
+ title,
84
+ price: this.price,
85
+ quantity: this.qu,
86
+ timeAdded: this.timeAdded
87
+ } satisfies ActualLineItemSnapshot)
88
+ }
75
89
 
76
90
  get canDecrement(): boolean { return this.qu > 0 }
77
91
  get quantity(): number {return this.qu}
@@ -1,6 +1,6 @@
1
1
  import { enableStaticRendering } from 'mobx-react-lite'
2
2
 
3
- import type { CommerceService, Category, FacetValueDesc } from '../../../types'
3
+ import type { CommerceService, Category, ProductTreeNode } 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
- rootFacet: FacetValueDesc,
22
+ rootNode: ProductTreeNode,
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
- rootFacet,
34
+ rootNode,
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
- rootFacet,
45
+ rootNode,
46
46
  options,
47
47
  snapShot
48
48
  )