@luxfi/core 5.0.2 → 5.0.4
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/commerce/AUTO-GEN-bullion-by-family.json +34 -0
- package/commerce/EDIT-ME-bullion-market-prices.ts +12 -0
- package/commerce/assign-prices.ts +50 -0
- package/commerce/assign-videos-by-family-group.ts +14 -0
- package/commerce/bullion-price-1oz.ts +5 -0
- package/commerce/index.ts +18 -0
- package/commerce/lux-service-options.ts +6 -0
- package/components/chat-widget.tsx +4 -2
- package/components/commerce/buy-drawer/index.tsx +5 -149
- package/components/commerce/checkout-widget/const.ts +13 -0
- package/components/commerce/checkout-widget/index.tsx +86 -0
- package/components/commerce/checkout-widget/obs-string-set.ts +48 -0
- package/components/commerce/checkout-widget/use-anim-clx-set.ts +57 -0
- package/components/commerce/checkout-widget/use-lagging-item-ref.ts +30 -0
- package/components/index.ts +3 -4
- package/next/index.ts +1 -1
- package/package.json +5 -4
- package/root-layout/index.tsx +8 -7
- package/site-def/index.ts +1 -1
- package/tsconfig.json +1 -1
- package/types/chatbot-config.ts +7 -0
- package/types/chatbot-suggested-question.ts +7 -0
- package/types/commerce-config.ts +10 -0
- package/types/index.ts +5 -1
- package/{site-def → types}/site-def.ts +14 -6
- package/components/commerce/buy-drawer/checkout.svg +0 -4
- /package/next/{determine-device-mw.ts → middleware/determine-device-mw.ts} +0 -0
@@ -0,0 +1,34 @@
|
|
1
|
+
[
|
2
|
+
{
|
3
|
+
"id": "LXM-AG-B",
|
4
|
+
"title": "Minted Bar",
|
5
|
+
"parentTitle": "Lux Silver",
|
6
|
+
"desc": "Get unprecedented access to silver with 1:1 asset-backed Lux Silver NFTs, sovereign ownership of physical silver without management fees, and mine-direct discount pricing.",
|
7
|
+
"img": {
|
8
|
+
"src": "/assets/commerce/silver/product/silver-bar-pt-800x800.png",
|
9
|
+
"dim": {
|
10
|
+
"w": 800,
|
11
|
+
"h": 800
|
12
|
+
}
|
13
|
+
},
|
14
|
+
"products": [
|
15
|
+
{
|
16
|
+
"id": "fce17569-a86c-4f69-bc61-ab435ed0c46f",
|
17
|
+
"sku": "LXM-AG-B-1_OZ",
|
18
|
+
"fullTitle": "Lux Silver, 1oz Minted Bar",
|
19
|
+
"optionLabel": "1oz",
|
20
|
+
"familyTitle": "Minted Bar",
|
21
|
+
"familyId": "LXM-AG-B",
|
22
|
+
"desc": "Get unprecedented access to silver with 1:1 asset-backed Lux Silver NFTs, sovereign ownership of physical silver without management fees, and mine-direct discount pricing.",
|
23
|
+
"price": 0,
|
24
|
+
"img": {
|
25
|
+
"src": "/assets/commerce/silver/product/silver-bar-pt-800x800.png",
|
26
|
+
"dim": {
|
27
|
+
"w": 800,
|
28
|
+
"h": 800
|
29
|
+
}
|
30
|
+
}
|
31
|
+
}
|
32
|
+
]
|
33
|
+
}
|
34
|
+
]
|
@@ -0,0 +1,50 @@
|
|
1
|
+
import type { Family } from '@hanzo/commerce/types'
|
2
|
+
|
3
|
+
import prices from './EDIT-ME-bullion-market-prices'
|
4
|
+
|
5
|
+
const sep = {
|
6
|
+
tok: '-',
|
7
|
+
subTok: '_',
|
8
|
+
decimal: '.'
|
9
|
+
}
|
10
|
+
|
11
|
+
const GRAMS_PER_OZ = 28.3495
|
12
|
+
|
13
|
+
const tree: any = {}
|
14
|
+
for (let key in prices) {
|
15
|
+
const values = prices[key as keyof typeof prices]
|
16
|
+
tree[key] = {
|
17
|
+
oz: values.market1oz * (1 - values.discount),
|
18
|
+
g: values.market1oz * (1 - values.discount) / GRAMS_PER_OZ
|
19
|
+
}
|
20
|
+
}
|
21
|
+
|
22
|
+
// LXB-AU-B-1_OZ, LXB-AU-B-2.5_g
|
23
|
+
const priceFromSKU = (
|
24
|
+
sku: string
|
25
|
+
) => {
|
26
|
+
const tokens = sku.split(sep.tok)
|
27
|
+
const type_ = tokens[1].toLowerCase()
|
28
|
+
|
29
|
+
const quanAndUnit = tokens[tokens.length - 1]
|
30
|
+
const quanAndUnitToks = quanAndUnit.split(sep.subTok)
|
31
|
+
let quantity = quanAndUnitToks[0].includes(sep.decimal) ? parseFloat(quanAndUnitToks[0].split(sep.decimal).join('.')) : parseInt(quanAndUnitToks[0])
|
32
|
+
let unit = quanAndUnitToks[1].toLowerCase()
|
33
|
+
if (unit === 'kg') {
|
34
|
+
quantity *= 1000
|
35
|
+
unit = 'g'
|
36
|
+
}
|
37
|
+
else if (unit === 'lb') {
|
38
|
+
quantity *= 16
|
39
|
+
unit = 'oz'
|
40
|
+
}
|
41
|
+
|
42
|
+
return tree[type_][unit] * quantity
|
43
|
+
}
|
44
|
+
|
45
|
+
export default (c: Family): Family => {
|
46
|
+
for (let prod of c.products) {
|
47
|
+
prod.price = priceFromSKU(prod.sku)
|
48
|
+
}
|
49
|
+
return c
|
50
|
+
}
|
@@ -0,0 +1,14 @@
|
|
1
|
+
import type { VideoDef } from '@hanzo/ui/types'
|
2
|
+
import type { Family } from '@hanzo/commerce/types'
|
3
|
+
|
4
|
+
export default (fam: Family, map: Map<string, VideoDef>): Family => {
|
5
|
+
if (fam.parentTitle) {
|
6
|
+
for (let prod of fam.products) {
|
7
|
+
const video = map.get(fam.parentTitle)
|
8
|
+
if (video) {
|
9
|
+
prod.video = video
|
10
|
+
}
|
11
|
+
}
|
12
|
+
}
|
13
|
+
return fam
|
14
|
+
}
|
@@ -0,0 +1,18 @@
|
|
1
|
+
import type { VideoDef } from '@hanzo/ui/types'
|
2
|
+
|
3
|
+
import bullionByFamily from './AUTO-GEN-bullion-by-family.json'
|
4
|
+
|
5
|
+
import assignPrices from './assign-prices'
|
6
|
+
import assignVideosByFamilyGroup from './assign-videos-by-family-group'
|
7
|
+
|
8
|
+
export const getBullionFamilies = (videoMap: Map<string, VideoDef>) => (
|
9
|
+
bullionByFamily.map(
|
10
|
+
(fam) => (assignPrices(fam))).map(
|
11
|
+
(fam) => (assignVideosByFamilyGroup(fam, videoMap)
|
12
|
+
)
|
13
|
+
)
|
14
|
+
)
|
15
|
+
|
16
|
+
export { default as serviceOptions } from './lux-service-options'
|
17
|
+
export { default as bullionPrice1oz } from './bullion-price-1oz'
|
18
|
+
|
@@ -2,9 +2,10 @@
|
|
2
2
|
import React from 'react'
|
3
3
|
|
4
4
|
import { Button, Card } from '@hanzo/ui/primitives'
|
5
|
+
import { cn } from '@hanzo/ui/util'
|
5
6
|
|
6
7
|
import LuxLogo from './icons/lux-logo'
|
7
|
-
import {
|
8
|
+
import type { ChatbotSuggestedQuestion } from '../types'
|
8
9
|
|
9
10
|
const ChatWidget: React.FC<{
|
10
11
|
title: string,
|
@@ -12,6 +13,7 @@ const ChatWidget: React.FC<{
|
|
12
13
|
subtitle?: string,
|
13
14
|
question?: string,
|
14
15
|
/*
|
16
|
+
ChatBotSuggestQuestion.icon
|
15
17
|
Currently supports these icons from remix icons (https://remixicon.com/):
|
16
18
|
GlobalLineIcon,
|
17
19
|
ShieldFlashLineIcon,
|
@@ -19,7 +21,7 @@ const ChatWidget: React.FC<{
|
|
19
21
|
GroupLineIcon,
|
20
22
|
QuestionnaireLineIcon
|
21
23
|
*/
|
22
|
-
suggestedQuestions?:
|
24
|
+
suggestedQuestions?: ChatbotSuggestedQuestion[]
|
23
25
|
}> = ({
|
24
26
|
title,
|
25
27
|
chatbotUrl,
|
@@ -1,109 +1,17 @@
|
|
1
1
|
'use client'
|
2
|
-
import React
|
3
|
-
import {
|
4
|
-
import { useRouter, usePathname } from 'next/navigation'
|
5
|
-
import { ObservableSet, observable, reaction, type IReactionDisposer, runInAction} from 'mobx'
|
2
|
+
import React from 'react'
|
3
|
+
import { useRouter } from 'next/navigation'
|
6
4
|
import { observer } from 'mobx-react-lite'
|
7
5
|
|
8
|
-
import {
|
9
|
-
import { Image } from '@hanzo/ui/primitives'
|
10
|
-
|
11
|
-
import { useCommerce, useCommerceUI, CarouselBuyCard, LineItemRef } from '@hanzo/commerce'
|
12
|
-
import type { LineItem } from '@hanzo/commerce/types'
|
6
|
+
import { useCommerceUI, CarouselBuyCard } from '@hanzo/commerce'
|
13
7
|
|
14
8
|
import CommerceDrawer from './drawer'
|
15
9
|
import CheckoutButton from '../checkout-button'
|
16
10
|
|
17
|
-
const DEF_IMG_CONSTRAINT={w: 40, h: 24}
|
18
|
-
const CO_ANIM_DURATION = 400
|
19
|
-
const CO_ANIM_TIMING_FN = 'cubic-bezier(0.4, 0, 0.2, 1)'
|
20
|
-
const CO_WIDGET_W_CLX = {
|
21
|
-
checkout: 'w-pr-40',
|
22
|
-
itemInfo: 'w-pr-60'
|
23
|
-
}
|
24
|
-
|
25
|
-
const CO_WIDGET_SHADOW_STYLE = {
|
26
|
-
border: '1px solid rgb(100 100 100)',
|
27
|
-
boxShadow: '2px 4px 4px -3px rgb(125 125 125 / 0.7), 4px -4px 8px -4px rgb(125 125 125 / 0.7)'
|
28
|
-
}
|
29
|
-
|
30
11
|
const CommerceUIComponent: React.FC = observer(() => {
|
31
12
|
|
32
13
|
const ui = useCommerceUI()
|
33
|
-
const cmmc = useCommerce()
|
34
14
|
const router = useRouter()
|
35
|
-
const isCheckout = usePathname() === '/checkout'
|
36
|
-
|
37
|
-
const animClxRef = useRef<ObservableSet<string>>(observable.set(new Set<string>(
|
38
|
-
(cmmc.cartEmpty || ui.buyOptionsSkuPath || isCheckout) ? ['hidden'] : []
|
39
|
-
)))
|
40
|
-
|
41
|
-
// a ref that is synced to ui.activeItem, but persists for CO_ANIM_DURATION longer
|
42
|
-
// so ui does not jump while animating "out"
|
43
|
-
const laggingActiveItemRef = useRef<LineItemRef>(new LineItemRef())
|
44
|
-
const disposersRef = useRef<IReactionDisposer[]>([])
|
45
|
-
|
46
|
-
useEffect(() => {
|
47
|
-
disposersRef.current.push(
|
48
|
-
reaction(
|
49
|
-
() => (ui.activeItem),
|
50
|
-
(item: LineItem | undefined) => {
|
51
|
-
if (item) {
|
52
|
-
laggingActiveItemRef.current.set(item)
|
53
|
-
}
|
54
|
-
else {
|
55
|
-
setTimeout(() => { laggingActiveItemRef.current.set(undefined) }, CO_ANIM_DURATION)
|
56
|
-
}
|
57
|
-
},
|
58
|
-
{equals: (val, prev) => (val?.sku === prev?.sku)}
|
59
|
-
)
|
60
|
-
)
|
61
|
-
|
62
|
-
disposersRef.current.push(
|
63
|
-
reaction(
|
64
|
-
() => ({
|
65
|
-
microOpen: !(cmmc.cartEmpty || !!ui.buyOptionsSkuPath || isCheckout),
|
66
|
-
buyOpen: !!ui.buyOptionsSkuPath
|
67
|
-
}),
|
68
|
-
(val, prev) => {
|
69
|
-
|
70
|
-
runInAction(() => {
|
71
|
-
if (!val.microOpen && prev.microOpen) {
|
72
|
-
animClxRef.current.add('checkout-widget-disappears')
|
73
|
-
}
|
74
|
-
else if (val.microOpen && !prev.microOpen) {
|
75
|
-
animClxRef.current.delete('hidden')
|
76
|
-
animClxRef.current.add('checkout-widget-appears')
|
77
|
-
}
|
78
|
-
if (!val.buyOpen && prev.buyOpen) {
|
79
|
-
animClxRef.current.add('checkout-widget-appears-after-buy-drawer-closes')
|
80
|
-
}
|
81
|
-
else {
|
82
|
-
animClxRef.current.delete('checkout-widget-appears-after-buy-drawer-closes')
|
83
|
-
}
|
84
|
-
})
|
85
|
-
|
86
|
-
setTimeout(() => {runInAction(() => {
|
87
|
-
animClxRef.current.delete('checkout-widget-appears')
|
88
|
-
animClxRef.current.delete('checkout-widget-appears-after-buy-drawer-closes')
|
89
|
-
if (animClxRef.current.has('checkout-widget-disappears') ) {
|
90
|
-
animClxRef.current.delete('checkout-widget-disappears')
|
91
|
-
animClxRef.current.add('hidden')
|
92
|
-
}
|
93
|
-
})}, 800)
|
94
|
-
},
|
95
|
-
{equals: (val, prev) => (
|
96
|
-
val.microOpen === prev.microOpen
|
97
|
-
&&
|
98
|
-
val.buyOpen === prev.buyOpen
|
99
|
-
)}
|
100
|
-
)
|
101
|
-
)
|
102
|
-
|
103
|
-
return () => {
|
104
|
-
disposersRef.current.forEach((d) => {d()})
|
105
|
-
}
|
106
|
-
}, [])
|
107
15
|
|
108
16
|
const handleCheckout = () => {
|
109
17
|
router.push('/checkout')
|
@@ -116,11 +24,10 @@ const CommerceUIComponent: React.FC = observer(() => {
|
|
116
24
|
}
|
117
25
|
}
|
118
26
|
|
119
|
-
return (
|
27
|
+
return (
|
120
28
|
<CommerceDrawer
|
121
29
|
open={!!ui.buyOptionsSkuPath}
|
122
30
|
setOpen={reallyOnlyCloseDrawer}
|
123
|
-
modal={true}
|
124
31
|
drawerClx={'w-full md:max-w-[550px] md:mx-auto lg:max-w-[50vw]'}
|
125
32
|
>
|
126
33
|
<CarouselBuyCard
|
@@ -133,58 +40,7 @@ const CommerceUIComponent: React.FC = observer(() => {
|
|
133
40
|
selectorClx='max-w-[475px]'
|
134
41
|
/>
|
135
42
|
</CommerceDrawer>
|
136
|
-
|
137
|
-
<div
|
138
|
-
className={cn(
|
139
|
-
'min-w-[160px] sm:max-w-[320px] w-[calc(100%-72px)] ml-2 !h-10',
|
140
|
-
'z-below-modal-2 fixed bottom-[20px] left-0 right-0',
|
141
|
-
'rounded-lg bg-background',
|
142
|
-
'flex',
|
143
|
-
ui.activeItem ? 'gap-2' : '',
|
144
|
-
Array.from(animClxRef.current).join(' ')
|
145
|
-
)}
|
146
|
-
style={laggingActiveItemRef.current.item ? {} : CO_WIDGET_SHADOW_STYLE}
|
147
|
-
>
|
148
|
-
<div
|
149
|
-
className={cn(
|
150
|
-
'flex flex-row justify-between items-center',
|
151
|
-
ui.activeItem ? CO_WIDGET_W_CLX.itemInfo : 'w-0',
|
152
|
-
laggingActiveItemRef.current.item ? 'px-3 border rounded-lg border-muted-3' : ''
|
153
|
-
)}
|
154
|
-
style={{
|
155
|
-
transitionProperty: 'width',
|
156
|
-
transitionTimingFunction: CO_ANIM_TIMING_FN,
|
157
|
-
transitionDuration: `${CO_ANIM_DURATION}ms`
|
158
|
-
}}
|
159
|
-
>
|
160
|
-
{laggingActiveItemRef.current.item?.img ? (
|
161
|
-
<Image def={laggingActiveItemRef.current.item.img} constrainTo={DEF_IMG_CONSTRAINT} preload className='grow-0 shrink-0'/>
|
162
|
-
) : ( // placeholder so things align
|
163
|
-
<div style={{height: DEF_IMG_CONSTRAINT.h, width: DEF_IMG_CONSTRAINT.w}} className='bg-level-3 grow-0 shrink-0'/>
|
164
|
-
)}
|
165
|
-
|
166
|
-
<div className='text-muted grow ml-1'>
|
167
|
-
{laggingActiveItemRef.current.item && (<>
|
168
|
-
<p className='whitespace-nowrap text-sm'>{laggingActiveItemRef.current.item.title}</p>
|
169
|
-
<p className='whitespace-nowrap text-xxs' >recently added...</p>
|
170
|
-
</>)}
|
171
|
-
</div>
|
172
|
-
</div>
|
173
|
-
<CheckoutButton
|
174
|
-
handleCheckout={handleCheckout}
|
175
|
-
centerText={!!!ui.activeItem}
|
176
|
-
variant='primary' rounded='lg'
|
177
|
-
className={cn(ui.activeItem ? CO_WIDGET_W_CLX.checkout : 'w-full')}
|
178
|
-
style={{
|
179
|
-
transitionProperty: 'width',
|
180
|
-
transitionTimingFunction: CO_ANIM_TIMING_FN,
|
181
|
-
transitionDuration: `${CO_ANIM_DURATION}ms`
|
182
|
-
}}
|
183
|
-
/>
|
184
|
-
</div>,
|
185
|
-
globalThis?.document?.body
|
186
|
-
)}
|
187
|
-
</>)
|
43
|
+
)
|
188
44
|
})
|
189
45
|
|
190
46
|
export default CommerceUIComponent
|
@@ -0,0 +1,13 @@
|
|
1
|
+
export default {
|
2
|
+
itemImgConstraint: { w: 40, h: 24 },
|
3
|
+
animDurationMs: 400,
|
4
|
+
animTimingFn: 'cubic-bezier(0.4, 0, 0.2, 1)',
|
5
|
+
compWidthClx: {
|
6
|
+
checkout: 'w-pr-40',
|
7
|
+
itemInfo: 'w-pr-60'
|
8
|
+
},
|
9
|
+
shadowStyle: {
|
10
|
+
border: '1px solid rgb(100 100 100)',
|
11
|
+
boxShadow: '2px 4px 4px -3px rgb(125 125 125 / 0.7), 4px -4px 8px -4px rgb(125 125 125 / 0.7)'
|
12
|
+
}
|
13
|
+
}
|
@@ -0,0 +1,86 @@
|
|
1
|
+
'use client'
|
2
|
+
import React from 'react'
|
3
|
+
import { createPortal } from 'react-dom'
|
4
|
+
import { usePathname, useRouter } from 'next/navigation'
|
5
|
+
import { observer } from 'mobx-react-lite'
|
6
|
+
|
7
|
+
import { cn } from '@hanzo/ui/util'
|
8
|
+
import { Image } from '@hanzo/ui/primitives'
|
9
|
+
|
10
|
+
import { useCommerceUI } from '@hanzo/commerce'
|
11
|
+
|
12
|
+
import CheckoutButton from '../checkout-button'
|
13
|
+
import useAnimationClxSet from './use-anim-clx-set'
|
14
|
+
import useLaggingItemRef from './use-lagging-item-ref'
|
15
|
+
import CONST from './const'
|
16
|
+
|
17
|
+
const CheckoutWidget: React.FC<{
|
18
|
+
clx?: string
|
19
|
+
}> = observer(({
|
20
|
+
clx=''
|
21
|
+
}) => {
|
22
|
+
|
23
|
+
const router = useRouter()
|
24
|
+
|
25
|
+
const isCheckout = usePathname() === '/checkout'
|
26
|
+
const clxSet = useAnimationClxSet(isCheckout)
|
27
|
+
|
28
|
+
const itemRef = useCommerceUI()
|
29
|
+
const laggingRef = useLaggingItemRef(itemRef, CONST.animDurationMs)
|
30
|
+
|
31
|
+
const handleCheckout = () => { router.push('/checkout')}
|
32
|
+
|
33
|
+
return globalThis?.document?.body && createPortal(
|
34
|
+
(<div
|
35
|
+
className={cn(
|
36
|
+
'min-w-[160px] sm:max-w-[320px] w-[calc(100%-72px)] ml-2 !h-10',
|
37
|
+
'z-below-modal-2 fixed bottom-[20px] left-0 right-0',
|
38
|
+
'rounded-lg bg-background',
|
39
|
+
'flex',
|
40
|
+
itemRef.item ? 'gap-2' : '',
|
41
|
+
clxSet.asArray.join(' ')
|
42
|
+
)}
|
43
|
+
style={laggingRef.item ? {} : CONST.shadowStyle}
|
44
|
+
>
|
45
|
+
<div
|
46
|
+
className={cn(
|
47
|
+
'flex flex-row justify-between items-center',
|
48
|
+
itemRef.item ? CONST.compWidthClx.itemInfo : 'w-0',
|
49
|
+
laggingRef.item ? 'px-3 border rounded-lg border-muted-3' : ''
|
50
|
+
)}
|
51
|
+
style={{
|
52
|
+
transitionProperty: 'width',
|
53
|
+
transitionTimingFunction: CONST.animTimingFn,
|
54
|
+
transitionDuration: `${CONST.animDurationMs}ms`
|
55
|
+
}}
|
56
|
+
>
|
57
|
+
{laggingRef.item?.img ? (
|
58
|
+
<Image def={laggingRef.item.img} constrainTo={CONST.itemImgConstraint} preload className='grow-0 shrink-0'/>
|
59
|
+
) : ( // placeholder so things align
|
60
|
+
<div style={{height: CONST.itemImgConstraint.h, width: CONST.itemImgConstraint.w}} className='bg-level-3 grow-0 shrink-0'/>
|
61
|
+
)}
|
62
|
+
|
63
|
+
<div className='text-muted grow ml-1'>
|
64
|
+
{laggingRef.item && (<>
|
65
|
+
<p className='whitespace-nowrap text-sm'>{laggingRef.item.title}</p>
|
66
|
+
<p className='whitespace-nowrap text-xxs' >recently added...</p>
|
67
|
+
</>)}
|
68
|
+
</div>
|
69
|
+
</div>
|
70
|
+
<CheckoutButton
|
71
|
+
handleCheckout={handleCheckout}
|
72
|
+
centerText={!!!itemRef.item}
|
73
|
+
variant='primary' rounded='lg'
|
74
|
+
className={cn(itemRef.item ? CONST.compWidthClx.checkout : 'w-full')}
|
75
|
+
style={{
|
76
|
+
transitionProperty: 'width',
|
77
|
+
transitionTimingFunction: CONST.animTimingFn,
|
78
|
+
transitionDuration: `${CONST.animDurationMs}ms`
|
79
|
+
}}
|
80
|
+
/>
|
81
|
+
</div>),
|
82
|
+
globalThis?.document?.body
|
83
|
+
)
|
84
|
+
})
|
85
|
+
|
86
|
+
export default CheckoutWidget
|
@@ -0,0 +1,48 @@
|
|
1
|
+
import {
|
2
|
+
action,
|
3
|
+
computed,
|
4
|
+
makeObservable,
|
5
|
+
observable
|
6
|
+
} from 'mobx'
|
7
|
+
|
8
|
+
class ObsStringSet {
|
9
|
+
|
10
|
+
private _set = observable.set(new Set<string>())
|
11
|
+
|
12
|
+
constructor(initial: string[] = []) {
|
13
|
+
initial.forEach((el) => {this._set.add(el)})
|
14
|
+
makeObservable(this, {
|
15
|
+
add: action,
|
16
|
+
remove: action,
|
17
|
+
asArray: computed
|
18
|
+
})
|
19
|
+
}
|
20
|
+
|
21
|
+
add = (v: string | string[]): void => {
|
22
|
+
if (Array.isArray(v)) {
|
23
|
+
v.forEach((el) => {this._set.add(el)})
|
24
|
+
}
|
25
|
+
else {
|
26
|
+
this._set.add(v)
|
27
|
+
}
|
28
|
+
}
|
29
|
+
|
30
|
+
remove = (v: string | string[]): void => {
|
31
|
+
if (Array.isArray(v)) {
|
32
|
+
v.forEach((el) => {this._set.delete(el)})
|
33
|
+
}
|
34
|
+
else {
|
35
|
+
this._set.delete(v)
|
36
|
+
}
|
37
|
+
}
|
38
|
+
|
39
|
+
has = (v: string): boolean => (
|
40
|
+
this._set.has(v)
|
41
|
+
)
|
42
|
+
|
43
|
+
get asArray() {
|
44
|
+
return Array.from(this._set)
|
45
|
+
}
|
46
|
+
}
|
47
|
+
|
48
|
+
export default ObsStringSet
|
@@ -0,0 +1,57 @@
|
|
1
|
+
import { useEffect, useRef } from 'react'
|
2
|
+
import { reaction, runInAction} from 'mobx'
|
3
|
+
|
4
|
+
import ObsStringSet from './obs-string-set'
|
5
|
+
import { useCommerce, useCommerceUI } from '@hanzo/commerce'
|
6
|
+
|
7
|
+
export default (isCheckout: boolean): ObsStringSet => {
|
8
|
+
|
9
|
+
const ui = useCommerceUI()
|
10
|
+
const cmmc = useCommerce()
|
11
|
+
|
12
|
+
const clxSetRef = useRef<ObsStringSet>(new ObsStringSet(
|
13
|
+
(cmmc.cartEmpty || ui.buyOptionsSkuPath || isCheckout) ? ['hidden'] : []
|
14
|
+
))
|
15
|
+
|
16
|
+
useEffect(() => (
|
17
|
+
reaction(
|
18
|
+
() => ({
|
19
|
+
microOpen: !(cmmc.cartEmpty || !!ui.buyOptionsSkuPath || isCheckout),
|
20
|
+
buyOpen: !!ui.buyOptionsSkuPath
|
21
|
+
}),
|
22
|
+
(val, prev) => {
|
23
|
+
|
24
|
+
runInAction(() => {
|
25
|
+
if (!val.microOpen && prev.microOpen) {
|
26
|
+
clxSetRef.current.add('checkout-widget-disappears')
|
27
|
+
}
|
28
|
+
else if (val.microOpen && !prev.microOpen) {
|
29
|
+
clxSetRef.current.remove('hidden')
|
30
|
+
clxSetRef.current.add('checkout-widget-appears')
|
31
|
+
}
|
32
|
+
if (!val.buyOpen && prev.buyOpen) {
|
33
|
+
clxSetRef.current.add('checkout-widget-appears-after-buy-drawer-closes')
|
34
|
+
}
|
35
|
+
else {
|
36
|
+
clxSetRef.current.remove('checkout-widget-appears-after-buy-drawer-closes')
|
37
|
+
}
|
38
|
+
})
|
39
|
+
|
40
|
+
setTimeout(() => {runInAction(() => {
|
41
|
+
clxSetRef.current.remove(['checkout-widget-appears', 'checkout-widget-appears-after-buy-drawer-closes'])
|
42
|
+
if (clxSetRef.current.has('checkout-widget-disappears') ) {
|
43
|
+
clxSetRef.current.remove('checkout-widget-disappears')
|
44
|
+
clxSetRef.current.add('hidden')
|
45
|
+
}
|
46
|
+
})}, 800)
|
47
|
+
},
|
48
|
+
{equals: (val, prev) => (
|
49
|
+
val.microOpen === prev.microOpen
|
50
|
+
&&
|
51
|
+
val.buyOpen === prev.buyOpen
|
52
|
+
)}
|
53
|
+
)
|
54
|
+
), [isCheckout])
|
55
|
+
|
56
|
+
return clxSetRef.current
|
57
|
+
}
|
@@ -0,0 +1,30 @@
|
|
1
|
+
import { useEffect, useRef } from 'react'
|
2
|
+
import { reaction } from 'mobx'
|
3
|
+
|
4
|
+
import type { LineItem, ObsLineItemRef } from "@hanzo/commerce/types"
|
5
|
+
import { LineItemRef } from '@hanzo/commerce'
|
6
|
+
|
7
|
+
export default (orig: ObsLineItemRef, lagMs: number): ObsLineItemRef => {
|
8
|
+
|
9
|
+
// a ref that is synced to 'orig', but persists for lagMs longer
|
10
|
+
// so ui does not jump while animating out.
|
11
|
+
// (Fascilitates for start and end states in animation)
|
12
|
+
const laggingRef = useRef<LineItemRef>(new LineItemRef())
|
13
|
+
|
14
|
+
useEffect(() => (
|
15
|
+
reaction(
|
16
|
+
() => (orig.item),
|
17
|
+
(item: LineItem | undefined) => {
|
18
|
+
if (item) {
|
19
|
+
laggingRef.current.set(item)
|
20
|
+
}
|
21
|
+
else {
|
22
|
+
setTimeout(() => { laggingRef.current.set(undefined) }, lagMs)
|
23
|
+
}
|
24
|
+
},
|
25
|
+
{equals: (val, prev) => (val?.sku === prev?.sku)}
|
26
|
+
)
|
27
|
+
), [])
|
28
|
+
|
29
|
+
return laggingRef.current
|
30
|
+
}
|
package/components/index.ts
CHANGED
@@ -9,13 +9,12 @@ export { default as Logo } from './logo'
|
|
9
9
|
export { default as MiniChart } from './mini-chart'
|
10
10
|
export { default as NotFound } from './not-found'
|
11
11
|
|
12
|
-
|
13
|
-
|
12
|
+
export { default as AuthListener } from './auth/auth-listener'
|
14
13
|
export { default as BuyDrawer } from './commerce/buy-drawer'
|
15
|
-
export { default as CheckoutPanel } from './commerce/checkout-panel'
|
16
14
|
export { default as CheckoutButton } from './commerce/checkout-button'
|
15
|
+
export { default as CheckoutPanel } from './commerce/checkout-panel'
|
16
|
+
export { default as CheckoutWidget } from './commerce/checkout-widget'
|
17
17
|
export { default as LoginPanel } from './auth/login-panel'
|
18
|
-
export { default as AuthListener } from './auth/auth-listener'
|
19
18
|
export { default as Scripts } from './scripts'
|
20
19
|
|
21
20
|
|
package/next/index.ts
CHANGED
@@ -1 +1 @@
|
|
1
|
-
export { default as determineDeviceMW } from './determine-device-mw'
|
1
|
+
export { default as determineDeviceMW } from './middleware/determine-device-mw'
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@luxfi/core",
|
3
|
-
"version": "5.0.
|
3
|
+
"version": "5.0.4",
|
4
4
|
"description": "Library that contains shared UI primitives, support for a common design system, and other boilerplate support.",
|
5
5
|
"publishConfig": {
|
6
6
|
"registry": "https://registry.npmjs.org/",
|
@@ -27,6 +27,7 @@
|
|
27
27
|
},
|
28
28
|
"exports": {
|
29
29
|
".": "./components/index.ts",
|
30
|
+
"./commerce": "./commerce/index.ts",
|
30
31
|
"./root-layout": "./root-layout/index.tsx",
|
31
32
|
"./server-actions": "./server-actions/index.ts",
|
32
33
|
"./next": "./next/index.ts",
|
@@ -36,6 +37,9 @@
|
|
36
37
|
"./conf": "./conf/index.ts"
|
37
38
|
},
|
38
39
|
"dependencies": {
|
40
|
+
"@hanzo/auth": "2.4.6",
|
41
|
+
"@hanzo/commerce": "7.0.1",
|
42
|
+
"@hanzo/ui": "3.7.0",
|
39
43
|
"@next/third-parties": "^14.1.0",
|
40
44
|
"cookies-next": "^4.1.1",
|
41
45
|
"date-fns": "^3.6.0",
|
@@ -44,9 +48,6 @@
|
|
44
48
|
"react-social-icons": "^6.4.0"
|
45
49
|
},
|
46
50
|
"peerDependencies": {
|
47
|
-
"@hanzo/auth": "2.4.6",
|
48
|
-
"@hanzo/commerce": "7.0.0",
|
49
|
-
"@hanzo/ui": "3.7.0",
|
50
51
|
"@hookform/resolvers": "^3.3.2",
|
51
52
|
"lucide-react": "^0.344.0",
|
52
53
|
"next": "14.1.3",
|
package/root-layout/index.tsx
CHANGED
@@ -12,9 +12,10 @@ import { FacebookPixelHead } from '../next/analytics/pixel-analytics'
|
|
12
12
|
|
13
13
|
import { AuthListener, ChatWidget, Header, Scripts } from '../components'
|
14
14
|
import BuyDrawer from '../components/commerce/buy-drawer'
|
15
|
+
import CheckoutWidget from '../components/commerce/checkout-widget'
|
15
16
|
|
16
17
|
import { selectionUISpecifiers } from '../conf'
|
17
|
-
import type SiteDef from '../
|
18
|
+
import type SiteDef from '../types/site-def'
|
18
19
|
|
19
20
|
import '../style/lux-global.css'
|
20
21
|
import '../style/cart-animation.css'
|
@@ -58,7 +59,6 @@ const RootLayout: React.FC<PropsWithChildren & {
|
|
58
59
|
}) => {
|
59
60
|
|
60
61
|
const currentUser = await getUserServerSide()
|
61
|
-
const usingCommerce = siteDef?.ext?.commerce && siteDef.ext.commerce.rootNode && siteDef.ext.commerce.families
|
62
62
|
|
63
63
|
const Guts: React.FC = () => (<>
|
64
64
|
{showHeader && <Header siteDef={siteDef}/>}
|
@@ -68,7 +68,7 @@ const RootLayout: React.FC<PropsWithChildren & {
|
|
68
68
|
title='LUX'
|
69
69
|
subtitle='AI'
|
70
70
|
chatbotUrl='https://lux.chat/iframe'
|
71
|
-
suggestedQuestions={siteDef.
|
71
|
+
suggestedQuestions={siteDef.chatbot?.suggestedQuestions ?? []}
|
72
72
|
/>
|
73
73
|
)}
|
74
74
|
</>)
|
@@ -92,15 +92,16 @@ const RootLayout: React.FC<PropsWithChildren & {
|
|
92
92
|
}}>
|
93
93
|
<Scripts/>
|
94
94
|
<AuthServiceProvider user={currentUser} conf={{} as AuthServiceConf}>
|
95
|
-
{
|
95
|
+
{siteDef?.commerce ? (
|
96
96
|
<CommerceProvider
|
97
|
-
rootNode={siteDef.
|
98
|
-
families={siteDef.
|
99
|
-
options={siteDef.
|
97
|
+
rootNode={siteDef.commerce!.rootNode}
|
98
|
+
families={siteDef.commerce!.families}
|
99
|
+
options={siteDef.commerce!.options}
|
100
100
|
uiSpecs={selectionUISpecifiers}
|
101
101
|
>
|
102
102
|
<Guts />
|
103
103
|
<BuyDrawer />
|
104
|
+
<CheckoutWidget />
|
104
105
|
</CommerceProvider>
|
105
106
|
) : (
|
106
107
|
<Guts />
|
package/site-def/index.ts
CHANGED
package/tsconfig.json
CHANGED
@@ -0,0 +1,10 @@
|
|
1
|
+
import type { ServiceOptions } from '@hanzo/commerce'
|
2
|
+
import type { CategoryNode, Family } from '@hanzo/commerce/types'
|
3
|
+
|
4
|
+
interface CommerceConfig {
|
5
|
+
families: Family[]
|
6
|
+
rootNode: CategoryNode
|
7
|
+
options?: ServiceOptions
|
8
|
+
}
|
9
|
+
|
10
|
+
export { type CommerceConfig as default }
|
package/types/index.ts
CHANGED
@@ -1 +1,5 @@
|
|
1
|
-
export type { ContactInfo, ContactInfoFields } from './contact-info'
|
1
|
+
export type { ContactInfo, ContactInfoFields } from './contact-info'
|
2
|
+
export type { default as SiteDef } from './site-def'
|
3
|
+
export type { default as ChatbotSuggestedQuestion } from './chatbot-suggested-question'
|
4
|
+
export type { default as ChatbotConfig } from './chatbot-config'
|
5
|
+
export type { default as CommerceConfig } from './commerce-config'
|
@@ -1,9 +1,15 @@
|
|
1
1
|
import React from 'react'
|
2
|
+
|
2
3
|
import type { LinkDef } from '@hanzo/ui/types'
|
3
4
|
|
5
|
+
import type CommerceConfig from './commerce-config'
|
6
|
+
import type ChatbotConfig from './chatbot-config'
|
7
|
+
|
4
8
|
interface SiteDef {
|
9
|
+
|
5
10
|
/** url of this site. All nav links in the system will show it in 'current' state */
|
6
11
|
currentAs?: string
|
12
|
+
|
7
13
|
nav: {
|
8
14
|
/** common elements (will auto-select currentAs if it's provide) */
|
9
15
|
/** optional feature element. right-most after 'elements' (any min-w is ignored) */
|
@@ -25,13 +31,15 @@ interface SiteDef {
|
|
25
31
|
*/
|
26
32
|
footer: LinkDef[][]
|
27
33
|
|
28
|
-
/**
|
29
|
-
|
30
|
-
|
34
|
+
/**
|
35
|
+
* optional override of default 'above copyright' horizantal links
|
36
|
+
default (undefined / absent): links in side-def/footer/legal are rendered
|
37
|
+
[] renders nothing above the copyright
|
38
|
+
*/
|
31
39
|
aboveCopyright?: LinkDef[]
|
32
40
|
|
33
|
-
|
34
|
-
|
41
|
+
commerce?: CommerceConfig
|
42
|
+
chatbot?: ChatbotConfig
|
35
43
|
}
|
36
44
|
|
37
|
-
export { type SiteDef as default }
|
45
|
+
export { type SiteDef as default }
|
@@ -1,4 +0,0 @@
|
|
1
|
-
<svg viewBox="0 0 32 40" >
|
2
|
-
<path d="M27.724,27.85a3.4,3.4,0,0,0,.825-2.672l-1.8-14.4A3.405,3.405,0,0,0,23.375,7.8h-.521a5.173,5.173,0,0,0-10.32,0H3.894a1,1,0,0,0,0,2H8.929a3.316,3.316,0,0,0-.29.978l-.382,3.055a.889.889,0,0,0-.163-.033h-3a1,1,0,0,0,0,2H8.011L6.839,25.178A3.4,3.4,0,0,0,10.212,29H25.175A3.4,3.4,0,0,0,27.724,27.85ZM17.694,5a3.2,3.2,0,0,1,3.16,2.8h-6.32A3.194,3.194,0,0,1,17.694,5ZM9.163,26.526a1.384,1.384,0,0,1-.34-1.1l1.8-14.4A1.4,1.4,0,0,1,12.013,9.8h.481V13a1,1,0,0,0,2,0V9.8h6.4V13a1,1,0,0,0,2,0V9.8h.481a1.4,1.4,0,0,1,1.39,1.226l1.8,14.4A1.4,1.4,0,0,1,25.175,27H10.212A1.38,1.38,0,0,1,9.163,26.526Z"/>
|
3
|
-
<path d="M7.894,11.8a1,1,0,0,0-1-1h-3a1,1,0,0,0,0,2h3A1,1,0,0,0,7.894,11.8Z"/>
|
4
|
-
</svg>
|
File without changes
|