@hanzo/commerce 6.0.0 → 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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hanzo/commerce",
3
- "version": "6.0.0",
3
+ "version": "6.1.0",
4
4
  "description": "Library with shopping cart components.",
5
5
  "publishConfig": {
6
6
  "registry": "https://registry.npmjs.org/",
@@ -40,7 +40,7 @@
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
45
  "@radix-ui/react-radio-group": "^1.1.3",
46
46
  "firebase": "^10.8.0",
@@ -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}
@@ -78,8 +78,8 @@ class StandaloneService
78
78
  '_currentItem' |
79
79
  '_promo'
80
80
  >(this, {
81
- _selectedPaths : observable.deep,
82
- _currentItem: observable,
81
+ _selectedPaths : observable.deep,
82
+ _currentItem: observable.shallow,
83
83
  _promo: observable,
84
84
  })
85
85
 
@@ -91,6 +91,7 @@ class StandaloneService
91
91
  cartEmpty: computed,
92
92
  selectedItems: computed,
93
93
  selectedCategories: computed,
94
+ hasSelection: computed,
94
95
  setCurrentItem: action,
95
96
  currentItem: computed,
96
97
  item: computed,
@@ -164,7 +165,7 @@ class StandaloneService
164
165
  }
165
166
 
166
167
  takeSnapshot = (): StandaloneServiceSnapshot => ({
167
- items : (this.cartItems as ActualLineItem[]).map((it) => (it.takeSnapshot()))
168
+ items : (this.cartItems as ActualLineItem[]).map((it) => (it.takeSnapshot(this)))
168
169
  })
169
170
 
170
171
  get cartItems(): LineItem[] {
@@ -237,10 +238,19 @@ class StandaloneService
237
238
 
238
239
  setCurrentItem(skuToFind: string | undefined): boolean {
239
240
 
241
+ const logMe = (s: string) => {
242
+ //if (skuToFind?.startsWith('LXM-CR-E')) {
243
+ //console.log(s)
244
+ //}
245
+ }
246
+
240
247
  if (skuToFind === undefined || skuToFind.length === 0) {
241
248
  this._currentItem = undefined
242
249
  return true
243
250
  }
251
+
252
+ logMe("SETTING ITEM: " + skuToFind)
253
+
244
254
  // self calling function
245
255
  this._currentItem = ((): ActualLineItem | undefined => {
246
256
 
@@ -250,6 +260,7 @@ class StandaloneService
250
260
  categoriesTried.push(category.id)
251
261
  const foundItem = category.products.find((p) => (p.sku === skuToFind))
252
262
  if (foundItem) {
263
+ logMe("FOUND 1st LOOP")
253
264
  return foundItem as ActualLineItem
254
265
  }
255
266
  }
@@ -258,12 +269,15 @@ class StandaloneService
258
269
  if (categoriesTried.includes(categoryId)) continue
259
270
  const foundItem = category.products.find((p) => (p.sku === skuToFind)) as ActualLineItem | undefined
260
271
  if (foundItem) {
272
+ logMe("FOUND 2nd LOOP")
261
273
  return foundItem as ActualLineItem
262
274
  }
263
275
  }
276
+ logMe("NOT FOUND")
264
277
  return undefined
265
278
  })();
266
279
 
280
+ logMe("CURRENT ITEM SET TO: " + this._currentItem?.sku)
267
281
  return !!this._currentItem
268
282
  }
269
283
 
@@ -379,6 +393,10 @@ class StandaloneService
379
393
  (allProducts, cat) => ([...allProducts, ...(cat.products as LineItem[])]), [] as LineItem[])
380
394
  }
381
395
 
396
+ get hasSelection(): boolean {
397
+ return this.selectedCategories.length > 0
398
+ }
399
+
382
400
  getCartCategorySubtotal(categoryId: string): number {
383
401
  const c = this._categoryMap.get(categoryId)!
384
402
  return (c.products as LineItem[]).reduce(
@@ -0,0 +1,8 @@
1
+ interface BuyUISpec {
2
+ selector: 'carousel' | 'radio' | 'image'
3
+ allVariants: boolean
4
+ }
5
+
6
+ export {
7
+ type BuyUISpec as default
8
+ }
package/types/category.ts CHANGED
@@ -1,11 +1,13 @@
1
+ import type { ImageDef } from '@hanzo/ui/types'
1
2
  import type Product from './product'
2
3
 
3
4
  interface Category {
4
- id: string // LXB-AU-B
5
- title: string // Minted Bar
6
- parentTitle?: string // Lux Gold
5
+ id: string // LXB-AU-B
6
+ title: string // Minted Bar, Lux Elite Card
7
+ titleShort?: string // Elite
8
+ parentTitle?: string // Lux Gold, Lux Credit
7
9
  desc?: string
8
- img?: string
10
+ img?: ImageDef,
9
11
  // inbound they're Products and then interally they become LineItems
10
12
  products: Product[]
11
13
  }
@@ -43,6 +43,7 @@ interface CommerceService extends ObsLineItemRef {
43
43
 
44
44
  get selectedItems(): LineItem[]
45
45
  get selectedCategories(): Category[]
46
+ get hasSelection(): boolean
46
47
 
47
48
  /** Whether this path defines a Category, or if it has further levels */
48
49
  getNodeAtPath(skuPath: string): ProductTreeNode | undefined
package/types/index.ts CHANGED
@@ -3,6 +3,7 @@ export type { default as Category } from './category'
3
3
  export type { default as CommerceService } from './commerce-service'
4
4
  export type { default as Product } from './product'
5
5
  export type { default as Promo } from './promo'
6
+ export type { default as BuyUISpec } from './buy-ui-spec'
6
7
 
7
8
  export * from './checkout'
8
9
  export * from './item-selector'
@@ -4,18 +4,39 @@ interface ItemSelector {
4
4
  items: LineItem[]
5
5
  selectedItemRef: ObsLineItemRef
6
6
  selectSku: (sku: string) => void
7
- showPrice?: boolean // true by default
8
- showQuantity?: boolean // false by default (not impl)
9
7
  }
10
8
 
11
- interface ItemSelectorProps extends ItemSelector {
9
+ interface _ItemSelectorCompProps {
12
10
  clx?: string
13
- soleItemClx?: string
14
11
  itemClx?: string
12
+ soleItemClx?: string
13
+ /** type-specific props for ItemSelector's.
14
+ * eg, Carousel options.
15
+ */
15
16
  ext?: any
17
+ /** List selectors will scroll. Used internally */
18
+ scrollList: boolean
19
+ /**
20
+ * Whether the item label includes the Category name.
21
+ * eg, 'Minted Bar, 1oz' vs '1oz'
22
+ * default: true in 'allVariants' mode and false otherwise.
23
+ * This overrides
24
+ */
25
+ showCategory?: boolean
26
+ /**
27
+ * Show the current item quantity along with title and price.
28
+ * default: false
29
+ */
30
+ showQuantity?: boolean
16
31
  }
17
32
 
33
+ type ItemSelectorCompProps = Omit<_ItemSelectorCompProps, 'scrollList'>
34
+
35
+ interface ItemSelectorProps extends
36
+ ItemSelector, _ItemSelectorCompProps {}
37
+
18
38
  export {
19
39
  type ItemSelector,
40
+ type ItemSelectorCompProps,
20
41
  type ItemSelectorProps
21
42
  }
@@ -18,6 +18,8 @@ interface LineItem extends Product {
18
18
 
19
19
  increment(): void
20
20
  decrement(): void
21
+
22
+ get title(): string
21
23
  }
22
24
 
23
25
  interface ObsLineItemRef {
package/types/product.ts CHANGED
@@ -1,16 +1,15 @@
1
- import type { VideoDef } from '@hanzo/ui/types'
1
+ import type { ImageDef, VideoDef } from '@hanzo/ui/types'
2
2
 
3
3
  interface Product {
4
4
  id: string // DB index // not a logical aspect of our domain. may not be necessary at all
5
5
  sku: string // human visible on orders etc.
6
- title: string
7
- shortTitle?: string
8
- titleAsOption: string
6
+ fullTitle?: string
7
+ optionLabel: string
9
8
  categoryId: string // skuPath, eg LXB-AU-B
9
+ categoryTitle: string
10
10
  desc?: string
11
11
  price: number
12
- img?: string // if undefined: (category's img exists) ? (use it) : (use generic placeholder)
13
- imgAR?: number
12
+ img?: ImageDef // if undefined: (category's img exists) ? (use it) : (use generic placeholder)
14
13
  animation?: string // spline scene url
15
14
  video?: VideoDef
16
15
  }
@@ -0,0 +1,34 @@
1
+ import type { BuyUISpec } from '../types'
2
+
3
+ const map = new Map<string, BuyUISpec>([
4
+ ['LXM_AU', {
5
+ selector: 'radio',
6
+ allVariants: false
7
+ }],
8
+ ['LXM_AG', {
9
+ selector: 'radio',
10
+ allVariants: false
11
+ }],
12
+ ['LXM_CN', {
13
+ selector: 'image',
14
+ allVariants: false
15
+ }],
16
+ // only one option so image / radio doesn't have meaning
17
+ ['LXM_PS', {
18
+ selector: 'image',
19
+ allVariants: false
20
+ }],
21
+ ['LXM-VL', {
22
+ selector: 'radio',
23
+ allVariants: false
24
+ }],
25
+ ['LXM-CR', {
26
+ selector: 'carousel',
27
+ allVariants: true
28
+ }],
29
+ ])
30
+
31
+ export default (skuPath: string): BuyUISpec | undefined => {
32
+ const key = skuPath.split('-').slice(0, 2).join('-')
33
+ return map.get(key)
34
+ }
package/util/index.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { StringMutator, CommerceService, Promo } from '../types'
1
+ import type { StringMutator, CommerceService } from '../types'
2
2
 
3
3
  export function toTitleCase(str: string) {
4
4
  return str.replace(
@@ -59,4 +59,7 @@ export const getFacetValuesMutator = (level: number, cmmc: CommerceService): Str
59
59
 
60
60
 
61
61
  export { default as useSyncSkuParamWithCurrentItem } from './use-sync-sku-param-w-current-item'
62
- export { default as processSquareCardPayment } from './square-payment'
62
+ export { default as processSquareCardPayment } from './square-payment'
63
+ export { default as ObsStringMutator } from './obs-string-mutator'
64
+
65
+ export { default as getUISpecFromPath } from './buy-ui-conf'
@@ -0,0 +1,22 @@
1
+
2
+ import { makeObservable, observable, action } from 'mobx'
3
+ import type { StringMutator } from '../types'
4
+
5
+ class ObsStringMutator implements StringMutator {
6
+
7
+ s: string | null
8
+ constructor(_s: string) {
9
+ this.s = _s
10
+ makeObservable(this, {
11
+ s: observable,
12
+ set: action,
13
+ //get: computed
14
+ })
15
+ }
16
+
17
+ set(v: string | null): void { this.s = v }
18
+ get(): string | null { return this.s }
19
+ }
20
+
21
+ export default ObsStringMutator
22
+
@@ -1,41 +0,0 @@
1
- import React from 'react'
2
- import Image from 'next/image'
3
-
4
- import type { ProductTreeNode } from '../../types'
5
-
6
- const ICON_SIZE = 20
7
-
8
- const FacetImage: React.FC<{
9
- facetValueDesc: ProductTreeNode
10
- }> = ({
11
- facetValueDesc
12
- }) => {
13
-
14
- const {
15
- img,
16
- imgAR: ar,
17
- label
18
- } = facetValueDesc
19
-
20
- if (!img) {
21
- return null
22
- }
23
-
24
- // url
25
- if (typeof img === 'string') {
26
- return (
27
- <Image
28
- src={img as string}
29
- alt={`Toggle ${label}`}
30
- className={'block mr-1 '}
31
- width={ar ? ar * ICON_SIZE : ICON_SIZE}
32
- height={ICON_SIZE}
33
- />
34
- )
35
- }
36
- // ReactNode
37
- return img as React.ReactNode
38
- }
39
-
40
- export default FacetImage
41
-