@hanzo/commerce 1.0.12 → 1.1.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.
- package/components/cart.tsx +8 -16
- package/components/index.ts +1 -1
- package/components/select-category-and-item-widget.tsx +1 -2
- package/components/select-item-in-category-view.tsx +2 -3
- package/index.ts +4 -0
- package/package.json +1 -1
- package/service/context.tsx +12 -9
- package/service/impls/index.ts +3 -0
- package/service/{impl/standalone/obs-line-item.ts → impls/standalone/actual-line-item.ts} +43 -5
- package/service/impls/standalone/index.ts +54 -0
- package/service/impls/standalone/localStorage.ts +34 -0
- package/service/impls/standalone/orders/firebase-app.ts +14 -0
- package/service/impls/standalone/orders/index.ts +102 -0
- package/service/{impl/standalone/service.ts → impls/standalone/standalone-service.ts} +64 -28
- package/tsconfig.json +0 -1
- package/types/category.ts +14 -0
- package/{service → types}/commerce-service.ts +7 -7
- package/types/facet.ts +37 -0
- package/types/index.ts +6 -99
- package/types/line-item.ts +27 -0
- package/types/product.ts +15 -0
- package/types/string-mutator.ts +14 -0
- package/util/use-sku-and-facet-params.ts +10 -11
- package/service/firebase-config.ts +0 -17
- package/service/impl/index.ts +0 -3
- package/service/impl/standalone/get-singleton.ts +0 -28
- package/service/index.ts +0 -4
- package/service/utils.ts +0 -37
package/components/cart.tsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
'use client'
|
|
2
|
-
import React, { type PropsWithChildren
|
|
2
|
+
import React, { type PropsWithChildren } from 'react'
|
|
3
3
|
import { useRouter } from 'next/navigation'
|
|
4
4
|
import { observer } from 'mobx-react-lite'
|
|
5
5
|
|
|
@@ -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 {
|
|
10
|
+
import { useCommerce } from '../service/context'
|
|
11
11
|
import { formatPrice } from '../util'
|
|
12
12
|
|
|
13
13
|
import CartLineItem from './cart-line-item'
|
|
@@ -17,42 +17,34 @@ 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,
|
|
23
24
|
hideCheckout=false
|
|
24
25
|
}) => {
|
|
25
|
-
const [loadingCheckout, setLoadingCheckout] = useState(false)
|
|
26
26
|
const cmmc = useCommerce()
|
|
27
27
|
const router = useRouter()
|
|
28
28
|
const auth = useAuth()
|
|
29
|
-
|
|
30
|
-
const checkout = async () => {
|
|
31
|
-
setLoadingCheckout(true)
|
|
32
|
-
if (auth.user) {
|
|
33
|
-
await persistCart(cmmc.cartItems, auth.user.email)
|
|
34
|
-
}
|
|
35
|
-
router.push('/checkout')
|
|
36
|
-
}
|
|
37
29
|
|
|
38
30
|
return (
|
|
39
31
|
<div className={cn('border p-6 rounded-lg', className)}>
|
|
40
32
|
{children}
|
|
41
33
|
<div className='mt-2'>
|
|
42
|
-
{!!children && <div className='h-[1px] w-pr-80 mx-auto bg-muted-3'/>}
|
|
34
|
+
{!!children && <div className='h-[1px] w-pr-80 mb-3 mx-auto bg-muted-3'/>}
|
|
43
35
|
{cmmc.cartItems.length === 0 ? (
|
|
44
36
|
<p className='text-center mt-4'>No items in cart</p>
|
|
45
37
|
) : (<>
|
|
46
38
|
{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>
|
|
39
|
+
<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
40
|
</>)}
|
|
49
41
|
</div>
|
|
50
42
|
{cmmc.cartItems.length > 0 && !hideCheckout && (
|
|
51
43
|
<>
|
|
52
|
-
{!auth.loggedIn ? (
|
|
53
|
-
<Button size='
|
|
44
|
+
{!(auth && auth.loggedIn) ? (
|
|
45
|
+
<Button size='sm' variant='secondary' rounded='lg' className='mt-12 mx-auto' onClick={() => router.push('/login?redirectUrl=checkout')}>Login to checkout</Button>
|
|
54
46
|
) : (
|
|
55
|
-
<Button size='lg' variant='secondary' rounded='lg' className='mt-12 mx-auto' onClick={checkout}
|
|
47
|
+
<Button size='lg' variant='secondary' rounded='lg' className='mt-12 mx-auto' onClick={() => router.push('/checkout')}>Checkout</Button>
|
|
56
48
|
)}
|
|
57
49
|
</>
|
|
58
50
|
)}
|
package/components/index.ts
CHANGED
|
@@ -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
|
|
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
|
|
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 {
|
|
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
package/package.json
CHANGED
package/service/context.tsx
CHANGED
|
@@ -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 '
|
|
10
|
-
|
|
11
|
-
import getServiceSingleton from './
|
|
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
|
-
|
|
16
|
+
|
|
17
|
+
const useCommerce = (): CommerceService => {
|
|
17
18
|
return useContext(CommerceServiceContext) as CommerceService
|
|
18
19
|
}
|
|
19
20
|
|
|
20
|
-
|
|
21
|
+
const CommerceServiceProvider: React.FC<PropsWithChildren & {
|
|
21
22
|
productsByCategory: Category[]
|
|
22
23
|
facets: FacetsDesc
|
|
23
|
-
options?:
|
|
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
|
+
}
|
|
@@ -7,7 +7,18 @@ import {
|
|
|
7
7
|
|
|
8
8
|
import type { Product, LineItem } from '../../../types'
|
|
9
9
|
|
|
10
|
-
|
|
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 {
|
|
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
|
|
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,102 @@
|
|
|
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
|
+
paymentMethod: string
|
|
27
|
+
status: string
|
|
28
|
+
timestamp: FieldValue
|
|
29
|
+
items: ActualLineItemSnapshot[]
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const createOrder = async (
|
|
33
|
+
email: string,
|
|
34
|
+
paymentMethod: string,
|
|
35
|
+
items: ActualLineItemSnapshot[],
|
|
36
|
+
options: {
|
|
37
|
+
dbName: string
|
|
38
|
+
ordersTable: string
|
|
39
|
+
}
|
|
40
|
+
): Promise<{
|
|
41
|
+
success: boolean,
|
|
42
|
+
error: any,
|
|
43
|
+
id?: string
|
|
44
|
+
}> => {
|
|
45
|
+
|
|
46
|
+
let error: any | null = null
|
|
47
|
+
const ordersRef = collection(getDBInstance(options.dbName), options.ordersTable)
|
|
48
|
+
const orderId = `${email}-${new Date().toISOString()}`
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
await setDoc(doc(ordersRef, orderId), {
|
|
52
|
+
email,
|
|
53
|
+
paymentMethod,
|
|
54
|
+
status: 'open',
|
|
55
|
+
timestamp: serverTimestamp(),
|
|
56
|
+
items,
|
|
57
|
+
} satisfies SavedOrder)
|
|
58
|
+
return { success: !error, error, id: orderId }
|
|
59
|
+
}
|
|
60
|
+
catch (e) {
|
|
61
|
+
console.error('Error writing item document: ', e)
|
|
62
|
+
error = e
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return { success: !error, error }
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const updateOrder = async (
|
|
69
|
+
orderId: string,
|
|
70
|
+
email: string,
|
|
71
|
+
paymentMethod: string,
|
|
72
|
+
items: ActualLineItemSnapshot[],
|
|
73
|
+
options: {
|
|
74
|
+
dbName: string
|
|
75
|
+
ordersTable: string
|
|
76
|
+
}
|
|
77
|
+
): Promise<{
|
|
78
|
+
success: boolean,
|
|
79
|
+
error: any
|
|
80
|
+
}> => {
|
|
81
|
+
|
|
82
|
+
let error: any | null = null
|
|
83
|
+
const ordersRef = collection(getDBInstance(options.dbName), options.ordersTable)
|
|
84
|
+
|
|
85
|
+
try {
|
|
86
|
+
await setDoc(doc(ordersRef, orderId), {
|
|
87
|
+
email,
|
|
88
|
+
paymentMethod,
|
|
89
|
+
status: 'open',
|
|
90
|
+
timestamp: serverTimestamp(),
|
|
91
|
+
items,
|
|
92
|
+
} satisfies SavedOrder)
|
|
93
|
+
}
|
|
94
|
+
catch (e) {
|
|
95
|
+
console.error('Error writing item document: ', e)
|
|
96
|
+
error = e
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return { success: !error, error }
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export { createOrder, updateOrder }
|
|
@@ -7,22 +7,32 @@ 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
|
|
18
|
+
import {
|
|
19
|
+
createOrder as createOrderHelper,
|
|
20
|
+
updateOrder as updateOrderHelper
|
|
21
|
+
} from './orders'
|
|
18
22
|
|
|
19
|
-
import
|
|
23
|
+
import ActualLineItem, { type ActualLineItemSnapshot } from './actual-line-item'
|
|
20
24
|
|
|
21
|
-
|
|
25
|
+
type StandaloneServiceOptions = {
|
|
22
26
|
levelZeroPrefix?: string
|
|
27
|
+
dbName: string
|
|
28
|
+
ordersTable: string
|
|
23
29
|
}
|
|
24
30
|
|
|
25
|
-
|
|
31
|
+
interface StandaloneServiceSnapshot {
|
|
32
|
+
items: ActualLineItemSnapshot[]
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
class StandaloneService
|
|
26
36
|
implements CommerceService
|
|
27
37
|
{
|
|
28
38
|
private _categoryMap = new Map<string, Category>()
|
|
@@ -30,25 +40,33 @@ class StandaloneCommerceService
|
|
|
30
40
|
private _selectedFacets: FacetsValue = {}
|
|
31
41
|
|
|
32
42
|
private _options : StandaloneServiceOptions
|
|
33
|
-
|
|
34
|
-
private _currentItem: LineItem | undefined = undefined
|
|
43
|
+
private _currentItem: ActualLineItem | undefined = undefined
|
|
35
44
|
|
|
36
45
|
constructor(
|
|
37
46
|
categories: Category[],
|
|
38
47
|
facets: FacetsDesc,
|
|
39
|
-
options: StandaloneServiceOptions
|
|
48
|
+
options: StandaloneServiceOptions,
|
|
49
|
+
serviceSnapshot?: StandaloneServiceSnapshot,
|
|
40
50
|
) {
|
|
41
51
|
|
|
42
52
|
this._facetsDesc = facets
|
|
43
53
|
this._options = options
|
|
44
54
|
|
|
45
55
|
categories.forEach((c) => {
|
|
46
|
-
c.products = c.products.map((p) =>
|
|
56
|
+
c.products = c.products.map((p) => {
|
|
57
|
+
if (serviceSnapshot) {
|
|
58
|
+
const itemSnapshot = serviceSnapshot.items.find((is) => (is.sku === p.sku))
|
|
59
|
+
if (itemSnapshot) {
|
|
60
|
+
return new ActualLineItem(p, itemSnapshot)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return new ActualLineItem(p)
|
|
64
|
+
})
|
|
47
65
|
this._categoryMap.set(c.id, c)
|
|
48
66
|
})
|
|
49
67
|
|
|
50
68
|
makeObservable<
|
|
51
|
-
|
|
69
|
+
StandaloneService,
|
|
52
70
|
'_selectedFacets' |
|
|
53
71
|
'_currentItem'
|
|
54
72
|
>(this, {
|
|
@@ -67,15 +85,29 @@ class StandaloneCommerceService
|
|
|
67
85
|
facetsValue: computed
|
|
68
86
|
/* NOT setFacets. It implements it's action mechanism */
|
|
69
87
|
})
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async createOrder(email: string, paymentMethod: string): Promise<string | undefined> {
|
|
91
|
+
const snapshot = this.takeSnapshot()
|
|
92
|
+
const order = await createOrderHelper(email, paymentMethod, snapshot.items, this._options) // didn't want to have two levels of 'items'
|
|
93
|
+
return order.id
|
|
94
|
+
}
|
|
70
95
|
|
|
96
|
+
async updateOrder(orderId: string, email: string, paymentMethod: string): Promise<void> {
|
|
97
|
+
const snapshot = this.takeSnapshot()
|
|
98
|
+
updateOrderHelper(orderId, email, paymentMethod, snapshot.items, this._options) // didn't want to have two levels of 'items'
|
|
71
99
|
}
|
|
72
100
|
|
|
101
|
+
takeSnapshot = (): StandaloneServiceSnapshot => ({
|
|
102
|
+
items : (this.cartItems as ActualLineItem[]).map((it) => (it.takeSnapshot()))
|
|
103
|
+
})
|
|
104
|
+
|
|
73
105
|
get cartItems(): LineItem[] {
|
|
74
106
|
let result: LineItem[] = []
|
|
75
107
|
this._categoryMap.forEach((cat) => {
|
|
76
108
|
result = [...result, ...(cat.products as LineItem[]).filter((item) => (item.isInCart))]
|
|
77
109
|
})
|
|
78
|
-
return result
|
|
110
|
+
return result.sort((it1, it2) => ((it1 as ActualLineItem).timeAdded - (it2 as ActualLineItem).timeAdded))
|
|
79
111
|
}
|
|
80
112
|
|
|
81
113
|
get cartTotal(): number {
|
|
@@ -91,27 +123,27 @@ class StandaloneCommerceService
|
|
|
91
123
|
this._currentItem = undefined
|
|
92
124
|
return true
|
|
93
125
|
}
|
|
94
|
-
|
|
95
|
-
this._currentItem = (():
|
|
126
|
+
// self calling function
|
|
127
|
+
this._currentItem = ((): ActualLineItem | undefined => {
|
|
128
|
+
|
|
96
129
|
const categoriesTried: string[] = []
|
|
97
130
|
if (this.specifiedCategories && this.specifiedCategories.length > 0) {
|
|
98
|
-
|
|
99
|
-
for (let category of this.specifiedCategories) {
|
|
131
|
+
for (let category of this.specifiedCategories) {
|
|
100
132
|
categoriesTried.push(category.id)
|
|
101
|
-
const foundItem =
|
|
102
|
-
(category.products as LineItem[]).find((item) => (item.sku === skuToFind))
|
|
133
|
+
const foundItem = category.products.find((p) => (p.sku === skuToFind))
|
|
103
134
|
if (foundItem) {
|
|
104
|
-
return foundItem
|
|
135
|
+
return foundItem as ActualLineItem
|
|
105
136
|
}
|
|
106
137
|
}
|
|
107
138
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
if (foundItem)
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
}
|
|
139
|
+
for( const [categoryId, category] of this._categoryMap.entries()) {
|
|
140
|
+
if (categoriesTried.includes(categoryId)) continue
|
|
141
|
+
const foundItem = category.products.find((p) => (p.sku === skuToFind)) as ActualLineItem | undefined
|
|
142
|
+
if (foundItem) {
|
|
143
|
+
return foundItem as ActualLineItem
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return undefined
|
|
115
147
|
})();
|
|
116
148
|
|
|
117
149
|
return !!this._currentItem
|
|
@@ -120,7 +152,7 @@ class StandaloneCommerceService
|
|
|
120
152
|
|
|
121
153
|
/* ObsLineItemRef */
|
|
122
154
|
get item(): LineItem | undefined {
|
|
123
|
-
return this.
|
|
155
|
+
return this._currentItem
|
|
124
156
|
}
|
|
125
157
|
|
|
126
158
|
get currentItem(): LineItem | undefined {
|
|
@@ -154,7 +186,7 @@ class StandaloneCommerceService
|
|
|
154
186
|
// 1-base, visiting two per iteration
|
|
155
187
|
let current: string[] = this._selectedFacets[1]
|
|
156
188
|
for (let i = 2; i <= keysStr.length; i++) {
|
|
157
|
-
current =
|
|
189
|
+
current = StandaloneService._visit(current, this._selectedFacets[i])
|
|
158
190
|
}
|
|
159
191
|
const prefix = this._options.levelZeroPrefix ?? ''
|
|
160
192
|
return current.map((almostTheCatId) => (this._categoryMap.get(prefix + almostTheCatId)!))
|
|
@@ -206,4 +238,8 @@ class StandaloneCommerceService
|
|
|
206
238
|
}
|
|
207
239
|
}
|
|
208
240
|
|
|
209
|
-
export
|
|
241
|
+
export {
|
|
242
|
+
type StandaloneServiceOptions,
|
|
243
|
+
type StandaloneServiceSnapshot,
|
|
244
|
+
StandaloneService as default
|
|
245
|
+
}
|
package/tsconfig.json
CHANGED
|
@@ -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
|
-
|
|
3
|
-
|
|
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,9 @@ interface CommerceService extends ObsLineItemRef {
|
|
|
11
8
|
get cartTotal(): number
|
|
12
9
|
getCartCategorySubtotal(categoryId: string): number
|
|
13
10
|
|
|
11
|
+
createOrder(email: string, paymentMethod: string): Promise<string | undefined>
|
|
12
|
+
updateOrder(orderId: string, email: string, paymentMethod: string): Promise<void>
|
|
13
|
+
|
|
14
14
|
/**
|
|
15
15
|
* Sets the tokens at each level supplied.
|
|
16
16
|
* If a level is specifed as [], nothing will be specified.
|
|
@@ -30,7 +30,7 @@ interface CommerceService extends ObsLineItemRef {
|
|
|
30
30
|
* "current" is unrelated to what is "specified",
|
|
31
31
|
* ie, facets' values
|
|
32
32
|
* */
|
|
33
|
-
setCurrentItem(sku: string | undefined): boolean //
|
|
33
|
+
setCurrentItem(sku: string | undefined): boolean // valid sku and was set.
|
|
34
34
|
/**
|
|
35
35
|
* For convenience, so widgets can share state.
|
|
36
36
|
* "current" is unrelated to what is "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
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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}
|
package/types/product.ts
ADDED
|
@@ -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
|
+
}
|
|
@@ -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 {
|
|
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
|
|
|
@@ -21,6 +22,7 @@ interface GetMutator {
|
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
const useSkuAndFacetParams = (
|
|
25
|
+
setMessage: (m: string | undefined) => void,
|
|
24
26
|
setLoading?: (l: boolean) => void
|
|
25
27
|
) => {
|
|
26
28
|
|
|
@@ -49,8 +51,6 @@ const useSkuAndFacetParams = (
|
|
|
49
51
|
parseAsBoolean.withDefault(false).withOptions({ clearOnDefault: true })
|
|
50
52
|
)
|
|
51
53
|
|
|
52
|
-
const [message, setMessage] = useState<string | undefined>(undefined)
|
|
53
|
-
|
|
54
54
|
const directMutator: GetMutator = (level: 1 | 2): StringMutator => {
|
|
55
55
|
|
|
56
56
|
const setLevel = (value: string, level: 1 | 2 ): void => {
|
|
@@ -83,6 +83,8 @@ const useSkuAndFacetParams = (
|
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
+
let message: string | undefined = undefined
|
|
87
|
+
|
|
86
88
|
useEffect(() => {
|
|
87
89
|
|
|
88
90
|
const setCurrentCategoryFromSku = (sku: string) => {
|
|
@@ -110,14 +112,14 @@ const useSkuAndFacetParams = (
|
|
|
110
112
|
}
|
|
111
113
|
})
|
|
112
114
|
}
|
|
113
|
-
setMessage(undefined)
|
|
114
115
|
encRef.current.usingSkuMode = true
|
|
115
116
|
}
|
|
116
117
|
else {
|
|
118
|
+
|
|
117
119
|
setSkuParam('')
|
|
118
120
|
// if sent here w an invalid sku,
|
|
119
121
|
// it will effectively put us in facet params mode
|
|
120
|
-
|
|
122
|
+
message = 'Invalid sku. ' + PLEASE_SELECT_FACETS
|
|
121
123
|
}
|
|
122
124
|
}
|
|
123
125
|
|
|
@@ -146,8 +148,7 @@ const useSkuAndFacetParams = (
|
|
|
146
148
|
if (level1 && level2) {
|
|
147
149
|
const categories = cmmc.setFacets(facets)
|
|
148
150
|
if (categories && categories.length > 0) {
|
|
149
|
-
cmmc.setCurrentItem(categories
|
|
150
|
-
setMessage(undefined)
|
|
151
|
+
cmmc.setCurrentItem(categories[0].products[0].sku)
|
|
151
152
|
}
|
|
152
153
|
else {
|
|
153
154
|
cmmc.setCurrentItem(undefined)
|
|
@@ -155,17 +156,15 @@ const useSkuAndFacetParams = (
|
|
|
155
156
|
}
|
|
156
157
|
}
|
|
157
158
|
else {
|
|
158
|
-
setMessage(PLEASE_SELECT_FACETS)
|
|
159
|
+
setMessage(message ?? PLEASE_SELECT_FACETS)
|
|
159
160
|
}
|
|
160
161
|
setLoading && setLoading(false)
|
|
161
162
|
}, [level1 , level2])
|
|
162
163
|
|
|
163
164
|
return {
|
|
164
|
-
message,
|
|
165
165
|
getMutator: encRef.current.usingSkuMode ?
|
|
166
166
|
directMutator : paramsMutator
|
|
167
167
|
}
|
|
168
|
-
|
|
169
168
|
}
|
|
170
169
|
|
|
171
170
|
export default useSkuAndFacetParams
|
|
@@ -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
|
package/service/impl/index.ts
DELETED
|
@@ -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
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
|
-
}
|