@hanzo/commerce 1.0.12 → 1.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.
@@ -7,7 +7,7 @@ import { Button } from '@hanzo/ui/primitives'
7
7
  import { cn } from '@hanzo/ui/util'
8
8
  import { useAuth } from '@hanzo/auth/service'
9
9
 
10
- import { persistCart, useCommerce } from '../service'
10
+ import { useCommerce } from '../service/context'
11
11
  import { formatPrice } from '../util'
12
12
 
13
13
  import CartLineItem from './cart-line-item'
@@ -17,6 +17,7 @@ const Cart: React.FC<PropsWithChildren & {
17
17
  isMobile?: boolean,
18
18
  hideCheckout?: boolean
19
19
  }> = observer(({
20
+ /** Children is the heading area. */
20
21
  children,
21
22
  className='',
22
23
  isMobile=false,
@@ -30,7 +31,7 @@ const Cart: React.FC<PropsWithChildren & {
30
31
  const checkout = async () => {
31
32
  setLoadingCheckout(true)
32
33
  if (auth.user) {
33
- await persistCart(cmmc.cartItems, auth.user.email)
34
+ await cmmc.createOrder(auth.user.email)
34
35
  }
35
36
  router.push('/checkout')
36
37
  }
@@ -39,18 +40,18 @@ const Cart: React.FC<PropsWithChildren & {
39
40
  <div className={cn('border p-6 rounded-lg', className)}>
40
41
  {children}
41
42
  <div className='mt-2'>
42
- {!!children && <div className='h-[1px] w-pr-80 mx-auto bg-muted-3'/>}
43
+ {!!children && <div className='h-[1px] w-pr-80 mb-3 mx-auto bg-muted-3'/>}
43
44
  {cmmc.cartItems.length === 0 ? (
44
45
  <p className='text-center mt-4'>No items in cart</p>
45
46
  ) : (<>
46
47
  {cmmc.cartItems.map((item, i) => (<CartLineItem isMobile={isMobile} item={item} key={item.sku} className='mb-4 sm:mb-2'/>))}
47
- <p className='mt-6 text-right border-t pt-1'>TOTAL: {cmmc.cartTotal === 0 ? '0' : formatPrice(cmmc.cartTotal)}</p>
48
+ <p className='mt-6 text-right border-t pt-1'>TOTAL: <span className='font-semibold'>{cmmc.cartTotal === 0 ? '0' : formatPrice(cmmc.cartTotal)}</span></p>
48
49
  </>)}
49
50
  </div>
50
51
  {cmmc.cartItems.length > 0 && !hideCheckout && (
51
52
  <>
52
53
  {!auth.loggedIn ? (
53
- <Button size='lg' variant='secondary' rounded='lg' className='mt-12 mx-auto' onClick={() => router.push('/login?redirectUrl=checkout')}>Login</Button>
54
+ <Button size='sm' variant='secondary' rounded='lg' className='mt-12 mx-auto' onClick={() => router.push('/login?redirectUrl=checkout')}>Login to checkout</Button>
54
55
  ) : (
55
56
  <Button size='lg' variant='secondary' rounded='lg' className='mt-12 mx-auto' onClick={checkout} disabled={loadingCheckout}>Checkout</Button>
56
57
  )}
@@ -8,4 +8,4 @@ export { default as ProductSelectionMobilePicker } from './product-selection-mob
8
8
  export { default as ProductSelectionRadioGroup } from './product-selection-radio-group'
9
9
  export { default as SelectCategoryAndItemWidget } from './select-category-and-item-widget'
10
10
  export { default as SelectItemInCategoryView } from './select-item-in-category-view'
11
- export * as Icons from './Icons'
11
+ export { Icons } from './Icons'
@@ -1,13 +1,12 @@
1
1
  'use client'
2
2
  import React, { useEffect } from 'react'
3
- import { computed } from 'mobx'
4
3
  import { observer } from 'mobx-react-lite'
5
4
 
6
5
  import { ApplyTypography, ListBox } from '@hanzo/ui/primitives'
7
6
  import { cn } from '@hanzo/ui/util'
8
7
 
9
8
  import type { FacetValueDesc, FacetsValue, LineItem } from '../types'
10
- import { useCommerce } from '../service'
9
+ import { useCommerce } from '../service/context'
11
10
  import { formatPrice } from '../util'
12
11
 
13
12
  import FacetTogglesWidget from './facet-toggles-widget'
@@ -1,13 +1,12 @@
1
1
  'use client'
2
- import React, { useState } from 'react'
2
+ import React from 'react'
3
3
  import Image from 'next/image'
4
4
  import { observer } from 'mobx-react-lite'
5
5
 
6
6
  import { cn } from '@hanzo/ui/util'
7
7
  import { Skeleton } from '@hanzo/ui/primitives'
8
8
 
9
- import { useCommerce } from '../service'
10
- import type { Category, LineItem, ObsLineItemRef } from '../types'
9
+ import type { Category, ObsLineItemRef } from '../types'
11
10
  import { formatPrice } from '../util'
12
11
  import { Icons } from './Icons'
13
12
 
package/index.ts ADDED
@@ -0,0 +1,4 @@
1
+ export * from './types'
2
+ export * from './service/context'
3
+ export type { StandaloneServiceOptions as ServiceOptions } from './service/impls/standalone/standalone-service'
4
+ export { useSkuAndFacetParams } from './util'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hanzo/commerce",
3
- "version": "1.0.12",
3
+ "version": "1.1.0",
4
4
  "description": "Library with shopping cart components.",
5
5
  "publishConfig": {
6
6
  "registry": "https://registry.npmjs.org/",
@@ -6,21 +6,22 @@ import { enableStaticRendering } from 'mobx-react-lite'
6
6
  enableStaticRendering(typeof window === "undefined")
7
7
 
8
8
 
9
- import type CommerceService from './commerce-service'
10
-
11
- import getServiceSingleton from './impl'
9
+ import type CommerceService from '../types/commerce-service'
10
+ import type { ServiceOptions } from '..'
11
+ import getServiceSingleton from './impls'
12
12
  import type { Category, FacetsDesc } from '../types'
13
13
 
14
14
  const CommerceServiceContext = createContext<CommerceService | undefined>(undefined)
15
15
 
16
- export const useCommerce = (): CommerceService => {
16
+
17
+ const useCommerce = (): CommerceService => {
17
18
  return useContext(CommerceServiceContext) as CommerceService
18
19
  }
19
20
 
20
- export const CommerceServiceProvider: React.FC<PropsWithChildren & {
21
+ const CommerceServiceProvider: React.FC<PropsWithChildren & {
21
22
  productsByCategory: Category[]
22
23
  facets: FacetsDesc
23
- options?: any
24
+ options?: ServiceOptions
24
25
  }> = ({
25
26
  children,
26
27
  productsByCategory,
@@ -30,10 +31,12 @@ export const CommerceServiceProvider: React.FC<PropsWithChildren & {
30
31
 
31
32
  const serviceRef = useRef<CommerceService>(getServiceSingleton(productsByCategory, facets, options))
32
33
  return (
33
- <CommerceServiceContext.Provider
34
- value={serviceRef.current}
35
- >
34
+ <CommerceServiceContext.Provider value={serviceRef.current}>
36
35
  {children}
37
36
  </CommerceServiceContext.Provider>
38
37
  )
39
38
  }
39
+
40
+ export {
41
+ useCommerce, CommerceServiceProvider
42
+ }
@@ -0,0 +1,3 @@
1
+ import { getInstance } from './standalone'
2
+
3
+ export default getInstance
@@ -7,7 +7,18 @@ import {
7
7
 
8
8
  import type { Product, LineItem } from '../../../types'
9
9
 
10
- class ObsLineItem implements LineItem {
10
+ interface ActualLineItemSnapshot {
11
+ sku: string
12
+ categoryId: string // helps impl of restoreFromSnapshot
13
+ title: string
14
+ price: number
15
+ quantity: number
16
+ timeAdded: number // helps to sort view of order and cart
17
+ }
18
+
19
+ class ActualLineItem
20
+ implements LineItem
21
+ {
11
22
 
12
23
  qu: number = 0
13
24
 
@@ -18,9 +29,10 @@ class ObsLineItem implements LineItem {
18
29
  categoryId: string
19
30
  desc?: string
20
31
  price: number
21
- img?: string
32
+ img?: string
33
+ timeAdded: number = 0 // timeAdded of being added to cart
22
34
 
23
- constructor(prod: Product) {
35
+ constructor(prod: Product, snap?: ActualLineItemSnapshot) {
24
36
  this.id = prod.id
25
37
  this.sku = prod.sku
26
38
  this.title = prod.title
@@ -30,8 +42,14 @@ class ObsLineItem implements LineItem {
30
42
  this.price = prod.price
31
43
  this.img = prod.img
32
44
 
45
+ if (snap) {
46
+ this.qu = snap.quantity
47
+ this.timeAdded = snap.quantity
48
+ }
49
+
33
50
  makeObservable(this, {
34
51
  qu: observable,
52
+ timeAdded: observable,
35
53
  canDecrement: computed,
36
54
  isInCart: computed,
37
55
 
@@ -40,15 +58,32 @@ class ObsLineItem implements LineItem {
40
58
  })
41
59
  }
42
60
 
61
+ takeSnapshot = (): ActualLineItemSnapshot => ({
62
+ sku: this.sku,
63
+ categoryId: this.categoryId,
64
+ title: this.title,
65
+ price: this.price,
66
+ quantity: this.qu,
67
+ timeAdded: this.timeAdded
68
+ } satisfies ActualLineItemSnapshot)
69
+
43
70
  get canDecrement(): boolean { return this.qu > 0 }
44
71
  get quantity(): number {return this.qu}
45
72
  get isInCart(): boolean {return this.qu > 0}
46
73
 
47
- increment(): void { this.qu++ }
74
+ increment(): void {
75
+ if (this.qu === 0) {
76
+ this.timeAdded = new Date().getTime()
77
+ }
78
+ this.qu++
79
+ }
48
80
 
49
81
  decrement(): void {
50
82
  if (this.canDecrement) {
51
83
  this.qu--
84
+ if (this.qu === 0) {
85
+ this.timeAdded = 0
86
+ }
52
87
  }
53
88
  }
54
89
 
@@ -59,4 +94,7 @@ class ObsLineItem implements LineItem {
59
94
 
60
95
  }
61
96
 
62
- export default ObsLineItem
97
+ export {
98
+ type ActualLineItemSnapshot,
99
+ ActualLineItem as default
100
+ }
@@ -0,0 +1,54 @@
1
+ import { enableStaticRendering } from 'mobx-react-lite'
2
+
3
+ import type { CommerceService, Category, FacetsDesc } from '../../../types'
4
+ import StandaloneService, {type StandaloneServiceOptions} from './standalone-service'
5
+
6
+ import { readSnapshot, listenAndWriteSnapshots } from './localStorage'
7
+
8
+ enableStaticRendering(typeof window === "undefined")
9
+
10
+ const _log = (s: string) => {
11
+ const d = new Date()
12
+ console.log(`TIMESTAMPED: ${d.getUTCMinutes()}:${d.getUTCSeconds()}:${d.getUTCMilliseconds()}`)
13
+ console.log(s)
14
+ }
15
+
16
+ // https://dev.to/ivandotv/mobx-server-side-rendering-with-next-js-4m18
17
+
18
+ let instance: StandaloneService | undefined = undefined
19
+
20
+ export const getInstance = (
21
+ categories: Category[],
22
+ facets: FacetsDesc,
23
+ options?: StandaloneServiceOptions
24
+ ): CommerceService => {
25
+
26
+ if (!options) {
27
+ throw new Error('cmmc getInstance(): Standalone Commerce Service requires config options!')
28
+ }
29
+
30
+ if (typeof window === "undefined") {
31
+ //_log("NEW INSTANCE FOR SERVER")
32
+ return new StandaloneService(
33
+ categories,
34
+ facets,
35
+ options
36
+ )
37
+ }
38
+
39
+ // Client side, create the store only once in the client
40
+ if (!instance) {
41
+ //_log("NEW INSTANCE FOR CLIENT")
42
+ const snapShot = readSnapshot()
43
+ instance = new StandaloneService(
44
+ categories,
45
+ facets,
46
+ options,
47
+ snapShot
48
+ )
49
+ listenAndWriteSnapshots(instance)
50
+ }
51
+
52
+ return instance
53
+ }
54
+
@@ -0,0 +1,34 @@
1
+ import { reaction } from 'mobx'
2
+ import StandaloneService, { type StandaloneServiceSnapshot } from './standalone-service'
3
+
4
+ const LS_KEY = 'lux-cart'
5
+
6
+ const readSnapshot = (): StandaloneServiceSnapshot | undefined => {
7
+ const snapshotAsStr = localStorage.getItem(LS_KEY)
8
+ return snapshotAsStr ? JSON.parse(snapshotAsStr) as StandaloneServiceSnapshot : undefined
9
+ }
10
+
11
+ const listenAndWriteSnapshots = (cmmc: StandaloneService): void => {
12
+
13
+ if (typeof window !== 'undefined') {
14
+ reaction(
15
+ () => (cmmc.cartTotal),
16
+ (total) => {
17
+ if (total > 0) {
18
+ const snapshot = cmmc.takeSnapshot()
19
+ // console.log(`CMMC LOCAL STORAGE UPDATE. (CART TOTAL: ${total}`)
20
+ localStorage.setItem(LS_KEY, JSON.stringify(snapshot) )
21
+ }
22
+ else {
23
+ localStorage.removeItem(LS_KEY)
24
+ }
25
+ }
26
+ )
27
+ }
28
+ }
29
+
30
+ export {
31
+ readSnapshot,
32
+ listenAndWriteSnapshots
33
+ }
34
+
@@ -0,0 +1,14 @@
1
+ import { initializeApp, getApps } from 'firebase/app'
2
+
3
+ const firebaseConfig = {
4
+ apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
5
+ authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
6
+ projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
7
+ storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET,
8
+ messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,
9
+ appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID,
10
+ measurementId: process.env.NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID,
11
+ }
12
+
13
+ // Initialize Firebase instance if there isn't one already
14
+ export default getApps().length === 0 ? initializeApp(firebaseConfig) : getApps()[0]
@@ -0,0 +1,60 @@
1
+ import firebaseApp from './firebase-app'
2
+
3
+ import {
4
+ getFirestore,
5
+ collection,
6
+ setDoc,
7
+ doc,
8
+ serverTimestamp,
9
+ type Firestore,
10
+ type FieldValue,
11
+ } from 'firebase/firestore'
12
+
13
+ import type { ActualLineItemSnapshot } from '../actual-line-item'
14
+
15
+ let dbInstance: Firestore | undefined = undefined
16
+
17
+ const getDBInstance = (name: string): Firestore => {
18
+ if (!dbInstance) {
19
+ dbInstance = getFirestore(firebaseApp, name)
20
+ }
21
+ return dbInstance
22
+ }
23
+
24
+ interface SavedOrder {
25
+ email: string
26
+ timestamp: FieldValue
27
+ items: ActualLineItemSnapshot[]
28
+ }
29
+
30
+ const createOrder = async (
31
+ email: string,
32
+ items: ActualLineItemSnapshot[],
33
+ options: {
34
+ dbName: string
35
+ ordersTable: string
36
+ }
37
+ ): Promise<{
38
+ success: boolean,
39
+ error: any
40
+ }> => {
41
+
42
+ let error: any | null = null
43
+ const ordersRef = collection(getDBInstance(options.dbName), options.ordersTable)
44
+
45
+ try {
46
+ await setDoc(doc(ordersRef, `${email}-${new Date().toISOString()}`), {
47
+ email,
48
+ timestamp: serverTimestamp(),
49
+ items,
50
+ } satisfies SavedOrder)
51
+ }
52
+ catch (e) {
53
+ console.error('Error writing item document: ', e)
54
+ error = e
55
+ }
56
+
57
+ return { success: !error, error }
58
+ }
59
+
60
+ export { createOrder }
@@ -7,22 +7,29 @@ import {
7
7
  toJS
8
8
  } from 'mobx'
9
9
 
10
- import type {
10
+ import type {
11
+ CommerceService,
11
12
  Category,
12
13
  LineItem,
13
14
  FacetsValue,
14
15
  FacetsDesc
15
16
  } from '../../../types'
16
17
 
17
- import type CommerceService from '../../commerce-service'
18
+ import { createOrder as createOrderHelper } from './orders'
18
19
 
19
- import ObservableLineItem from './obs-line-item'
20
+ import ActualLineItem, { type ActualLineItemSnapshot } from './actual-line-item'
20
21
 
21
- export type StandaloneServiceOptions = {
22
+ type StandaloneServiceOptions = {
22
23
  levelZeroPrefix?: string
24
+ dbName: string
25
+ ordersTable: string
23
26
  }
24
27
 
25
- class StandaloneCommerceService
28
+ interface StandaloneServiceSnapshot {
29
+ items: ActualLineItemSnapshot[]
30
+ }
31
+
32
+ class StandaloneService
26
33
  implements CommerceService
27
34
  {
28
35
  private _categoryMap = new Map<string, Category>()
@@ -30,25 +37,33 @@ class StandaloneCommerceService
30
37
  private _selectedFacets: FacetsValue = {}
31
38
 
32
39
  private _options : StandaloneServiceOptions
33
-
34
- private _currentItem: LineItem | undefined = undefined
40
+ private _currentItem: ActualLineItem | undefined = undefined
35
41
 
36
42
  constructor(
37
43
  categories: Category[],
38
44
  facets: FacetsDesc,
39
- options: StandaloneServiceOptions={}
45
+ options: StandaloneServiceOptions,
46
+ serviceSnapshot?: StandaloneServiceSnapshot,
40
47
  ) {
41
48
 
42
49
  this._facetsDesc = facets
43
50
  this._options = options
44
51
 
45
52
  categories.forEach((c) => {
46
- c.products = c.products.map((p) => (new ObservableLineItem(p)))
53
+ c.products = c.products.map((p) => {
54
+ if (serviceSnapshot) {
55
+ const itemSnapshot = serviceSnapshot.items.find((is) => (is.sku === p.sku))
56
+ if (itemSnapshot) {
57
+ return new ActualLineItem(p, itemSnapshot)
58
+ }
59
+ }
60
+ return new ActualLineItem(p)
61
+ })
47
62
  this._categoryMap.set(c.id, c)
48
63
  })
49
64
 
50
65
  makeObservable<
51
- StandaloneCommerceService,
66
+ StandaloneService,
52
67
  '_selectedFacets' |
53
68
  '_currentItem'
54
69
  >(this, {
@@ -67,15 +82,23 @@ class StandaloneCommerceService
67
82
  facetsValue: computed
68
83
  /* NOT setFacets. It implements it's action mechanism */
69
84
  })
85
+ }
70
86
 
87
+ async createOrder(email: string): Promise<void> {
88
+ const snapshot = this.takeSnapshot()
89
+ createOrderHelper(email, snapshot.items, this._options) // didn't want to have two levels of 'items'
71
90
  }
72
91
 
92
+ takeSnapshot = (): StandaloneServiceSnapshot => ({
93
+ items : (this.cartItems as ActualLineItem[]).map((it) => (it.takeSnapshot()))
94
+ })
95
+
73
96
  get cartItems(): LineItem[] {
74
97
  let result: LineItem[] = []
75
98
  this._categoryMap.forEach((cat) => {
76
99
  result = [...result, ...(cat.products as LineItem[]).filter((item) => (item.isInCart))]
77
100
  })
78
- return result
101
+ return result.sort((it1, it2) => ((it1 as ActualLineItem).timeAdded - (it2 as ActualLineItem).timeAdded))
79
102
  }
80
103
 
81
104
  get cartTotal(): number {
@@ -91,26 +114,25 @@ class StandaloneCommerceService
91
114
  this._currentItem = undefined
92
115
  return true
93
116
  }
94
-
95
- this._currentItem = ((): LineItem | undefined => {
117
+ // self calling function
118
+ this._currentItem = ((): ActualLineItem | undefined => {
119
+
96
120
  const categoriesTried: string[] = []
97
121
  if (this.specifiedCategories && this.specifiedCategories.length > 0) {
98
-
99
- for (let category of this.specifiedCategories) {
122
+ for (let category of this.specifiedCategories) {
100
123
  categoriesTried.push(category.id)
101
- const foundItem =
102
- (category.products as LineItem[]).find((item) => (item.sku === skuToFind))
124
+ const foundItem = category.products.find((p) => (p.sku === skuToFind))
103
125
  if (foundItem) {
104
- return foundItem
126
+ return foundItem as ActualLineItem
105
127
  }
106
128
  }
107
129
  }
108
130
 
109
- let foundItem: LineItem | undefined = undefined
131
+ let foundItem: ActualLineItem | undefined = undefined
110
132
  this._categoryMap.forEach((category, categoryId) => {
111
133
  if (foundItem) return
112
134
  if (categoriesTried.includes(categoryId)) return
113
- foundItem = (category.products as LineItem[]).find((item) => (item.sku === skuToFind))
135
+ foundItem = category.products.find((p) => (p.sku === skuToFind)) as ActualLineItem | undefined
114
136
  })
115
137
  })();
116
138
 
@@ -120,7 +142,7 @@ class StandaloneCommerceService
120
142
 
121
143
  /* ObsLineItemRef */
122
144
  get item(): LineItem | undefined {
123
- return this.currentItem
145
+ return this._currentItem
124
146
  }
125
147
 
126
148
  get currentItem(): LineItem | undefined {
@@ -154,7 +176,7 @@ class StandaloneCommerceService
154
176
  // 1-base, visiting two per iteration
155
177
  let current: string[] = this._selectedFacets[1]
156
178
  for (let i = 2; i <= keysStr.length; i++) {
157
- current = StandaloneCommerceService._visit(current, this._selectedFacets[i])
179
+ current = StandaloneService._visit(current, this._selectedFacets[i])
158
180
  }
159
181
  const prefix = this._options.levelZeroPrefix ?? ''
160
182
  return current.map((almostTheCatId) => (this._categoryMap.get(prefix + almostTheCatId)!))
@@ -206,4 +228,8 @@ class StandaloneCommerceService
206
228
  }
207
229
  }
208
230
 
209
- export default StandaloneCommerceService
231
+ export {
232
+ type StandaloneServiceOptions,
233
+ type StandaloneServiceSnapshot,
234
+ StandaloneService as default
235
+ }
package/tsconfig.json CHANGED
@@ -6,6 +6,5 @@
6
6
  ],
7
7
  "exclude": [
8
8
  "node_modules",
9
- "service/impl/hanzo-adapter/commerceJs/Commerce.test.ts"
10
9
  ]
11
10
  }
@@ -0,0 +1,14 @@
1
+ import type Product from './product'
2
+
3
+ interface Category {
4
+ id: string // LXB-AU-B
5
+ title: string // Lux Gold, Minted Bar
6
+ desc?: string
7
+ img?: string
8
+ // inbound they're Products and then interally they become LineItems
9
+ products: Product[]
10
+ }
11
+
12
+ export {
13
+ type Category as default
14
+ }
@@ -1,9 +1,6 @@
1
- import type {
2
- Category,
3
- FacetsValue,
4
- LineItem ,
5
- ObsLineItemRef
6
- } from '../types'
1
+ import type { LineItem, ObsLineItemRef } from './line-item'
2
+ import type { FacetsValue } from './facet'
3
+ import type Category from './category'
7
4
 
8
5
  interface CommerceService extends ObsLineItemRef {
9
6
 
@@ -11,6 +8,8 @@ interface CommerceService extends ObsLineItemRef {
11
8
  get cartTotal(): number
12
9
  getCartCategorySubtotal(categoryId: string): number
13
10
 
11
+ createOrder(email: string): Promise<void>
12
+
14
13
  /**
15
14
  * Sets the tokens at each level supplied.
16
15
  * If a level is specifed as [], nothing will be specified.
package/types/facet.ts ADDED
@@ -0,0 +1,37 @@
1
+ import type { ReactNode } from 'react'
2
+
3
+ interface FacetValueDesc {
4
+ value: string // a token in the sku
5
+ label: string
6
+ img : string | ReactNode // icon is required
7
+ imgAR? : number // helps with svgs
8
+ }
9
+
10
+ /* *** FOR EXAMPLE **
11
+ {
12
+ 1: [ {
13
+ token: 'AG',
14
+ label: 'Silver',
15
+ img: '/assets/img/cart/ui/facets/silver-swatch-200x200.png'
16
+ },
17
+ ... more FaceValues describing the "type" level (Silver, Gold)
18
+ ],
19
+ 2 [
20
+ {
21
+ token: 'B'
22
+ label: 'Minted Bar
23
+ },
24
+ ... more FaceValues describing the "form" level (Bar, Coin, )
25
+ ]
26
+ }
27
+ */
28
+ type FacetsDesc = Record<number, FacetValueDesc[]>
29
+
30
+ // Which facets tokens are on at each level
31
+ type FacetsValue = Record<number, string[]>
32
+
33
+ export type {
34
+ FacetValueDesc,
35
+ FacetsDesc,
36
+ FacetsValue
37
+ }
package/types/index.ts CHANGED
@@ -1,103 +1,10 @@
1
- import type { ReactNode } from 'react'
2
1
 
3
- interface Product {
4
- id: string // DB index // not a logical aspect of our domain. may not be necessary at all
5
- sku: string // human visible on orders etc.
6
- title: string
7
- shortTitle?: string
8
- titleAsOption: string
9
- categoryId: string // skuPath, eg LXB-AU-B
10
- desc?: string
11
- price: number
12
- img?: string // if undefined: (category's img exists) ? (use it) : (use generic placeholder)
13
- }
14
-
15
- interface Category {
16
- id: string // LXB-AU-B
17
- title: string // Lux Gold, Minted Bar
18
- desc?: string
19
- img?: string
20
- // inbound they're Products and then interally they become LineItems
21
- products: Product[] | LineItem[]
22
- }
23
-
24
-
25
- interface FacetValueDesc {
26
- value: string // a token in the sku
27
- label: string
28
- img : string | ReactNode // icon is required
29
- imgAR? : number // helps with svgs
30
- }
31
-
32
- /* *** FOR EXAMPLE **
33
- {
34
- 1: [ {
35
- token: 'AG',
36
- label: 'Silver',
37
- img: '/assets/img/cart/ui/facets/silver-swatch-200x200.png'
38
- },
39
- ... more FaceValues describing the "type" level (Silver, Gold)
40
- ],
41
- 2 [
42
- {
43
- token: 'B'
44
- label: 'Minted Bar
45
- },
46
- ... more FaceValues describing the "form" level (Bar, Coin, )
47
- ]
48
- }
49
- */
50
- type FacetsDesc = Record<number, FacetValueDesc[]>
51
-
52
- // Which facets tokens are on at each level
53
- type FacetsValue = Record<number, string[]>
54
-
55
- // Client code always
56
- // has a LineItem, whether the Product
57
- // is in the cart or not. Something is in the cart
58
- // when its quantity > 0. That's the only difference.
59
- // The ui, and as well as some Cart state, reacts to
60
- // changes in this quantity.
61
-
62
- // It could have more accurately been named
63
- // 'QuantifiedProduct' but that sucked.
64
- interface LineItem extends Product {
65
-
66
- /** all observable */
67
- get quantity(): number
68
- get canDecrement(): boolean
69
- get isInCart(): boolean
70
-
71
- increment(): void
72
- decrement(): void
73
- }
74
-
75
- interface ObsLineItemRef {
76
- get item(): LineItem | undefined
77
- }
78
-
79
-
80
- interface StringMutator {
81
- get(): string | null
82
- set(v: string | null): void
83
- }
84
-
85
- interface StringArrayMutator {
86
- get(): string[] | null
87
- set(v: string[] | null): void
88
- }
89
-
90
- export {
91
- type Product,
92
- type Category,
93
- type LineItem,
94
- type ObsLineItemRef,
95
- type FacetsDesc,
96
- type FacetsValue,
97
- type FacetValueDesc,
98
- type StringMutator,
99
- type StringArrayMutator
100
- }
2
+ export type { default as CommerceService } from './commerce-service'
3
+ export type { default as Product } from './product'
4
+ export type { default as Category } from './category'
5
+ export * from './line-item'
6
+ export * from './facet'
7
+ export * from './string-mutator'
101
8
 
102
9
 
103
10
 
@@ -0,0 +1,27 @@
1
+ import type Product from './product'
2
+
3
+ // Client code always
4
+ // has a LineItem, whether the Product
5
+ // is in the cart or not. Something is in the cart
6
+ // when its quantity > 0. That's the only difference.
7
+ // The ui, and as well as some Cart state, reacts to
8
+ // changes in this quantity.
9
+
10
+ // It could have more accurately been named
11
+ // 'QuantifiedProduct' but that sucked.
12
+ interface LineItem extends Product {
13
+
14
+ /** all observable */
15
+ get quantity(): number
16
+ get canDecrement(): boolean
17
+ get isInCart(): boolean
18
+
19
+ increment(): void
20
+ decrement(): void
21
+ }
22
+
23
+ interface ObsLineItemRef {
24
+ get item(): LineItem | undefined
25
+ }
26
+
27
+ export type { LineItem, ObsLineItemRef}
@@ -0,0 +1,15 @@
1
+ interface Product {
2
+ id: string // DB index // not a logical aspect of our domain. may not be necessary at all
3
+ sku: string // human visible on orders etc.
4
+ title: string
5
+ shortTitle?: string
6
+ titleAsOption: string
7
+ categoryId: string // skuPath, eg LXB-AU-B
8
+ desc?: string
9
+ price: number
10
+ img?: string // if undefined: (category's img exists) ? (use it) : (use generic placeholder)
11
+ }
12
+
13
+ export {
14
+ type Product as default
15
+ }
@@ -0,0 +1,14 @@
1
+ interface StringMutator {
2
+ get(): string | null
3
+ set(v: string | null): void
4
+ }
5
+
6
+ interface StringArrayMutator {
7
+ get(): string[] | null
8
+ set(v: string[] | null): void
9
+ }
10
+
11
+ export type {
12
+ StringMutator,
13
+ StringArrayMutator
14
+ }
@@ -1,3 +1,4 @@
1
+ "use client"
1
2
  import {
2
3
  useEffect,
3
4
  useRef,
@@ -11,8 +12,8 @@ import {
11
12
  parseAsBoolean,
12
13
  } from 'next-usequerystate'
13
14
 
14
- import type { Category, FacetsValue, StringMutator } from '../types'
15
- import { useCommerce } from '../service'
15
+ import type { FacetsValue, StringMutator } from '../types'
16
+ import { useCommerce } from '../service/context'
16
17
 
17
18
  const PLEASE_SELECT_FACETS = 'Please select an option from each group above.'
18
19
 
@@ -146,7 +147,7 @@ const useSkuAndFacetParams = (
146
147
  if (level1 && level2) {
147
148
  const categories = cmmc.setFacets(facets)
148
149
  if (categories && categories.length > 0) {
149
- cmmc.setCurrentItem(categories?.[0].products[0].sku)
150
+ cmmc.setCurrentItem(categories[0].products[0].sku)
150
151
  setMessage(undefined)
151
152
  }
152
153
  else {
@@ -1,17 +0,0 @@
1
- // Import the functions you need from the SDKs you need
2
- import { initializeApp, getApps } from "firebase/app"
3
-
4
- const firebaseConfig = {
5
- apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
6
- authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
7
- projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
8
- storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET,
9
- messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,
10
- appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID,
11
- measurementId: process.env.NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID,
12
- }
13
-
14
- // Initialize Firebase
15
- let firebase_app = getApps().length === 0 ? initializeApp(firebaseConfig) : getApps()[0]
16
-
17
- export default firebase_app
@@ -1,3 +0,0 @@
1
- import { default as getInstance } from './standalone/get-singleton'
2
-
3
- export default getInstance
@@ -1,28 +0,0 @@
1
- import type CommerceService from '../../commerce-service'
2
-
3
- import StandaloneCommerceService, {type StandaloneServiceOptions} from './service'
4
-
5
- import type { Category, FacetsDesc } from '../../../types'
6
-
7
- // https://dev.to/ivandotv/mobx-server-side-rendering-with-next-js-4m18
8
-
9
- let instance: CommerceService | undefined = undefined
10
-
11
- export default (categories: Category[], facets: FacetsDesc, options?: any): CommerceService => {
12
-
13
- const _instance = instance ??
14
- new StandaloneCommerceService(
15
- categories,
16
- facets,
17
- options ? (options as StandaloneServiceOptions) : {}
18
- )
19
- // For server side rendering always create a new store
20
- if (typeof window === "undefined") return _instance
21
-
22
- // Create the store once in the client
23
- if (!instance) {
24
- instance = _instance
25
- }
26
-
27
- return _instance
28
- }
package/service/index.ts DELETED
@@ -1,4 +0,0 @@
1
- export type { default as CommerceService } from './commerce-service'
2
- export { useCommerce, CommerceServiceProvider } from './context'
3
- export { default as persistCart } from './utils'
4
-
package/service/utils.ts DELETED
@@ -1,37 +0,0 @@
1
- import type { LineItem } from "../types"
2
- import firebase_app from "./firebase-config"
3
- import { getFirestore, collection, addDoc, setDoc, doc } from "firebase/firestore"
4
-
5
- const db = getFirestore(firebase_app, 'lux-commerce')
6
-
7
- export default async function persistCart(cart: LineItem[], userEmail: string) {
8
- let result = null
9
- let error = null
10
- const ordersRef = collection(db, "orders");
11
-
12
- const cartAsObject = cart.reduce((acc, item) => {
13
- acc[item.sku] = {
14
- sku: item.sku,
15
- title: item.title,
16
- quantity: item.quantity,
17
- description: item.desc,
18
- img: item.img,
19
- price: item.price,
20
- }
21
- return acc
22
- }, {} as Record<string, {}>)
23
-
24
- try {
25
- try {
26
- result = await setDoc(doc(ordersRef, `${userEmail}-${Date.now()}`), { order: cartAsObject })
27
- } catch (e) {
28
- console.error('Error writing item document: ', e)
29
- error = e
30
- }
31
- } catch (e) {
32
- console.error('Error writing order document: ', e)
33
- error = e
34
- }
35
-
36
- return { result, error }
37
- }