@hanzo/commerce 4.6.0 → 4.7.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.
@@ -10,11 +10,13 @@ import { formatPrice } from '../../util'
10
10
 
11
11
  import CartLineItem from './cart-line-item'
12
12
  import { sendFBEvent, sendGAEvent } from '../../util/analytics'
13
+ import ProductsCarousel from './products-carousel'
13
14
 
14
15
  const CartPanel: React.FC<PropsWithChildren & {
15
16
  className?: string
16
17
  isMobile?: boolean,
17
18
  noCheckout?: boolean
19
+ showProductsCarousel?: boolean
18
20
  onCheckoutOpen?: () => void
19
21
  }> = observer(({
20
22
  /** Children is the heading area. */
@@ -22,6 +24,7 @@ const CartPanel: React.FC<PropsWithChildren & {
22
24
  className='',
23
25
  isMobile=false,
24
26
  noCheckout=false,
27
+ showProductsCarousel=false,
25
28
  onCheckoutOpen,
26
29
  }) => {
27
30
  /* TODO: onCheckoutOpen is a hackish way fix a bug with multiple dialog opened at the same time.
@@ -60,6 +63,7 @@ const CartPanel: React.FC<PropsWithChildren & {
60
63
  return (
61
64
  <div className={cn('border p-4 rounded-lg', className)}>
62
65
  {children}
66
+ {showProductsCarousel && <ProductsCarousel items={cmmc.cartItems}/>}
63
67
  <div className='mt-2 w-full'>
64
68
  {cmmc.cartEmpty ? (
65
69
  <p className='text-center mt-4'>No items in cart</p>
@@ -0,0 +1,60 @@
1
+ import Spline from '@splinetool/react-spline'
2
+
3
+ import {
4
+ Carousel,
5
+ CarouselContent,
6
+ CarouselItem,
7
+ CarouselPrevious,
8
+ CarouselNext
9
+ } from '@hanzo/ui/primitives'
10
+ import {
11
+ VideoBlockComponent,
12
+ ImageBlockComponent,
13
+ type ImageBlock,
14
+ type Block,
15
+ type VideoBlock
16
+ } from '@hanzo/ui/blocks'
17
+
18
+ import type { LineItem } from '../../types'
19
+
20
+ // Carousel content hierarchy: 3D > MP4 > Image
21
+ const ProductsCarousel: React.FC<{
22
+ items: LineItem[]
23
+ }> = ({
24
+ items,
25
+ }) => (
26
+ <Carousel options={{ loop: true }} className='w-full max-w-sm mx-auto px-2' >
27
+ <CarouselContent>
28
+ {items.map(({title, img, video, animation}, index) => (
29
+ <CarouselItem key={index}>
30
+ <div className='flex aspect-square items-center justify-center p-6'>
31
+ {animation ? (
32
+ <Spline
33
+ scene={animation}
34
+ className='!aspect-[12/10] pointer-events-none !w-auto !h-auto'
35
+ />
36
+ ) : video ? (
37
+ <VideoBlockComponent
38
+ block={{blockType: 'video', ...video} satisfies VideoBlock as Block}
39
+ />
40
+ ) : (
41
+ <ImageBlockComponent
42
+ block={{
43
+ blockType: 'image',
44
+ src: img ?? '',
45
+ alt: title + ' image',
46
+ dim: { w: 250, h: 250 }
47
+ } satisfies ImageBlock as Block}
48
+ className='m-auto'
49
+ />
50
+ )}
51
+ </div>
52
+ </CarouselItem>
53
+ ))}
54
+ </CarouselContent>
55
+ <CarouselPrevious />
56
+ <CarouselNext />
57
+ </Carousel>
58
+ )
59
+
60
+ export default ProductsCarousel
@@ -0,0 +1,39 @@
1
+ 'use client'
2
+ import React from 'react'
3
+ import { observer } from 'mobx-react-lite'
4
+
5
+ import {
6
+ Accordion,
7
+ AccordionContent,
8
+ AccordionItem,
9
+ AccordionTrigger,
10
+ } from '@hanzo/ui/primitives'
11
+
12
+ import { formatPrice, useCommerce } from '../..'
13
+
14
+ import CartPanel from '../cart-panel'
15
+ import BagIcon from './icons/bag-icon'
16
+
17
+ const CartAccordian: React.FC<{className?: string}> = observer(({
18
+ className=''
19
+ }) => {
20
+
21
+ const cmmc = useCommerce()
22
+ return (
23
+ <Accordion type="single" collapsible className={className}>
24
+ <AccordionItem value="cart" className='w-full border-b-0'>
25
+ <AccordionTrigger className='!no-underline py-1'>
26
+ <div className='flex gap-4 items-center'>
27
+ <BagIcon className='w-4 h-4 sm:w-6 sm:h-6'/>
28
+ <h5 className='text-sm sm:text-xl truncate'>Order Summary {formatPrice(cmmc.cartTotal)}</h5>
29
+ </div>
30
+ </AccordionTrigger>
31
+ <AccordionContent>
32
+ <CartPanel noCheckout className='border-none w-full'/>
33
+ </AccordionContent>
34
+ </AccordionItem>
35
+ </Accordion>
36
+ )
37
+ })
38
+
39
+ export default CartAccordian
@@ -4,24 +4,22 @@ import { useState } from 'react'
4
4
  import { observer } from 'mobx-react-lite'
5
5
 
6
6
  import {
7
- Accordion,
8
- AccordionContent,
9
- AccordionItem,
10
- AccordionTrigger,
11
7
  Dialog,
12
8
  DialogPortal,
9
+ ScrollArea,
13
10
  } from '@hanzo/ui/primitives'
14
11
  import { cn } from '@hanzo/ui/util'
15
12
  import { AuthWidget } from '@hanzo/auth/components'
16
13
 
14
+ import { useCommerce } from '../..'
15
+
17
16
  import ShippingInfo from './shipping-info'
18
17
  import ThankYou from './thank-you'
19
18
  import CartPanel from '../cart-panel'
20
19
  import Payment from './payment'
21
20
  import CloseButton from './close-button'
22
21
  import StepIndicator from './step-indicator'
23
- import BagIcon from './icons/bag-icon'
24
- import { formatPrice, useCommerce } from '../..'
22
+ import CartAccordian from './cart-accordian'
25
23
 
26
24
  const CheckoutPanel: React.FC<{
27
25
  open: boolean
@@ -72,7 +70,7 @@ const CheckoutPanel: React.FC<{
72
70
  return (
73
71
  <Dialog open={open}>
74
72
  <DialogPortal>
75
- <div id='PORTAL_OUTER'
73
+ <div /* id='PORTAL_OUTER' */
76
74
  className={cn(
77
75
  'fixed top-0 shadow-lg ',
78
76
  'animate-in data-[state=open]:fade-in-90 data-[state=open]:slide-in-from-bottom-10',
@@ -80,35 +78,26 @@ const CheckoutPanel: React.FC<{
80
78
  '!max-w-none w-full h-full min-h-screen bg-transparent backdrop-blur-sm z-50'
81
79
  )}
82
80
  >
83
- <div id='GRID' className='grid grid-cols-1 md:grid-cols-2 justify-center h-full overflow-y-auto md:overflow-y-hidden'>
84
- <div className='bg-background flex flex-row items-start justify-end'>
85
- <div className='h-full w-full max-w-[750px] relative flex flex-col items-center justify-start px-6 pt-10 pb-0 md:pb-9'>
81
+ <div /* id='GRID' */ className='flex flex-col md:flex-row justify-center h-full overflow-y-auto md:overflow-y-hidden'>
82
+ <div className='w-full bg-background flex flex-row items-start justify-end'>
83
+ <ScrollArea className='h-full w-full max-w-[750px] relative flex flex-col items-center justify-start px-6 pt-12 pb-0 md:pb-9'>
86
84
  <CloseButton onClose={onClose} className='absolute top-3 left-3 w-auto h-auto rounded-full bg-level-1 hover:bg-level-2 hover:border-muted p-2' />
87
85
  <AuthWidget hideLogin className='flex md:hidden absolute top-3 right-3'/>
88
- <CartPanel noCheckout className='border border-muted-2 mt-10 w-full max-w-[550px] hidden md:flex'/>
89
-
90
- <Accordion type="single" collapsible className='flex items-center justify-center py-2 w-full md:hidden'>
91
- <AccordionItem value="cart" className='w-full border-b-0'>
92
- <AccordionTrigger className='!no-underline'>
93
- <div className='flex gap-4 items-center'>
94
- <BagIcon className='w-4 h-4 sm:w-6 sm:h-6'/>
95
- <h5 className='text-sm sm:text-xl truncate'>Order Summary {formatPrice(cmmc.cartTotal)}</h5>
96
- </div>
97
- </AccordionTrigger>
98
- <AccordionContent>
99
- <CartPanel noCheckout className='border-none w-full'/>
100
- </AccordionContent>
101
- </AccordionItem>
102
- </Accordion>
103
- </div>
86
+ <CartPanel
87
+ className='hidden md:flex border-none mt-10 w-full max-w-[550px] flex-col'
88
+ noCheckout
89
+ showProductsCarousel
90
+ />
91
+ <CartAccordian className='md:hidden flex items-center justify-center py-2 w-full' />
92
+ </ScrollArea>
104
93
  </div>
105
- <div className='bg-level-1 flex flex-row items-start justify-start md:overflow-y-auto'>
106
- <div className='h-full w-full max-w-[750px] relative flex flex-col gap-8 sm:gap-14 px-8 pb-6 pt-6 md:pt-14'>
94
+ <ScrollArea className='w-full h-full bg-level-1 flex flex-row items-start justify-start md:overflow-y-auto'>
95
+ <div className='h-full w-full max-w-[750px] relative flex flex-col gap-8 sm:gap-14 px-4 md:px-8 pb-6 pt-6 md:pt-14'>
107
96
  <AuthWidget hideLogin className='hidden md:flex absolute top-4 right-4 '/>
108
97
  <StepIndicator steps={steps} currentStep={step} className='flex gap-2 mx-auto items-center text-xxs sm:text-base' />
109
98
  {steps[step].element}
110
99
  </div>
111
- </div>
100
+ </ScrollArea>
112
101
  </div>
113
102
  </div>
114
103
  </DialogPortal>
@@ -23,15 +23,14 @@ const ContactInfo: React.FC<{
23
23
  return (
24
24
  <Form {...form}>
25
25
  <form className='text-left'>
26
- <div className='flex flex-col sm:flex-row gap-2'>
26
+ <div className='flex gap-1 sm:gap-2'>
27
27
  <FormField
28
28
  control={form.control}
29
29
  name='name'
30
30
  render={({ field }) => (
31
31
  <FormItem className='space-y-1 w-full'>
32
- <FormLabel>Full name</FormLabel>
33
32
  <FormControl>
34
- <Input {...field} className='border-muted-4'/>
33
+ <Input {...field} className='border-muted-4' placeholder='Full name'/>
35
34
  </FormControl>
36
35
  <FormMessage />
37
36
  </FormItem>
@@ -42,9 +41,8 @@ const ContactInfo: React.FC<{
42
41
  name='email'
43
42
  render={({ field }) => (
44
43
  <FormItem className='space-y-1 w-full'>
45
- <FormLabel>Email</FormLabel>
46
44
  <FormControl>
47
- <Input {...field} className='border-muted-4'/>
45
+ <Input {...field} className='border-muted-4' placeholder='Email'/>
48
46
  </FormControl>
49
47
  <FormMessage />
50
48
  </FormItem>
@@ -86,24 +86,24 @@ const Payment: React.FC<{
86
86
  <TabsList className='grid w-full grid-cols-3 mx-auto bg-level-2 h-auto'>
87
87
  <TabsTrigger
88
88
  value='card'
89
- className='whitespace-normal h-full text-sm sm:text-base'
89
+ className='whitespace-normal h-full text-xs sm:text-base px-1'
90
90
  disabled={transactionStatus === 'paid' || transactionStatus === 'confirmed'}
91
91
  >
92
- PAY WITH CARD
92
+ Pay with card
93
93
  </TabsTrigger>
94
94
  <TabsTrigger
95
95
  value='crypto'
96
- className='whitespace-normal h-full text-sm sm:text-base'
96
+ className='whitespace-normal h-full text-xs sm:text-base px-1'
97
97
  disabled={transactionStatus === 'paid' || transactionStatus === 'confirmed'}
98
98
  >
99
- PAY WITH CRYPTO
99
+ Pay with Wallet
100
100
  </TabsTrigger>
101
101
  <TabsTrigger
102
102
  value='bank'
103
- className='whitespace-normal h-full text-sm sm:text-base'
103
+ className='whitespace-normal h-full text-xs sm:text-base px-1'
104
104
  disabled={transactionStatus === 'paid' || transactionStatus === 'confirmed'}
105
105
  >
106
- BANK TRANSFER
106
+ Pay with Wire
107
107
  </TabsTrigger>
108
108
  </TabsList>
109
109
  <TabsContent value='card'>
@@ -18,13 +18,13 @@ const InfoField: React.FC<{
18
18
  }) => {
19
19
  const copyToClipboard = (label: string, text: string) => {
20
20
  navigator.clipboard.writeText(text)
21
- toast(`${label} copied to clipboard`)
21
+ toast(`${label} copied to clipboard.`)
22
22
  }
23
23
 
24
24
  return (
25
25
  <div className='flex flex-col gap-1'>
26
26
  <p className='text-xs'>{label}</p>
27
- <div className='flex items-center justify-between text-lg border rounded-lg py-2 px-4'>
27
+ <div className='flex items-center justify-between sm:text-lg border rounded-lg py-2 px-4'>
28
28
  {value}
29
29
  <Button variant='ghost' size='icon' onClick={() => copyToClipboard(label, copyValue)}>
30
30
  <Copy className='h-4 w-4'/>
@@ -57,7 +57,7 @@ const PayWithBankTransfer: React.FC<{
57
57
  }
58
58
 
59
59
  return (
60
- <div className='flex flex-col gap-6 mt-6'>
60
+ <div className='flex flex-col gap-2 mt-6'>
61
61
  <ContactInfo form={contactForm}/>
62
62
  <Tabs defaultValue="usd" className='w-full mx-auto max-w-[50rem]'>
63
63
  <TabsList className="grid w-full grid-cols-2 max-w-[15rem] mx-auto bg-level-2">
@@ -6,6 +6,7 @@ import type { UseFormReturn } from 'react-hook-form'
6
6
  import { ApplePay, GooglePay, CreditCard, PaymentForm } from 'react-square-web-payments-sdk'
7
7
 
8
8
  import { ApplyTypography, Button } from '@hanzo/ui/primitives'
9
+ import { cn } from '@hanzo/ui/util'
9
10
 
10
11
  import { processSquareCardPayment } from '../../../util'
11
12
  import { useCommerce } from '../../../service/context'
@@ -133,7 +134,7 @@ const PayWithCard: React.FC<{
133
134
  */
134
135
  locationId={process.env.NEXT_PUBLIC_SQUARE_LOCATION_ID}
135
136
  >
136
- <ApplyTypography className='flex flex-col gap-2 mt-6'>
137
+ <ApplyTypography className='flex flex-col mt-6'>
137
138
  {transactionStatus === 'paid' ? (
138
139
  <h6 className='mx-auto font-nav'>Processing your payment...</h6>
139
140
  ) : transactionStatus === 'confirmed' ? (
@@ -147,13 +148,11 @@ const PayWithCard: React.FC<{
147
148
  <GooglePay/>
148
149
  <ApplePay/>
149
150
 
150
- <div className='flex gap-2 whitespace-nowrap items-center my-6 sm:my-10 text-sm text-foreground/60'>
151
+ <div className='flex gap-2 whitespace-nowrap items-center my-1 sm:my-2 text-xs text-foreground/60'>
151
152
  <hr className='bg-foreground/60 w-full border'/> or pay with card <hr className='bg-foreground/60 w-full border'/>
152
153
  </div>
153
154
 
154
155
  <ContactInfo form={contactForm}/>
155
-
156
- <PaymentMethods/>
157
156
 
158
157
  {/* Imitates hanzo/ui Button and Input styles, I was unable to render the
159
158
  hanzo/ui button outright and keeping the submit form functionality*/}
@@ -184,6 +183,8 @@ const PayWithCard: React.FC<{
184
183
  input: {
185
184
  backgroundColor: '#1f1f1f',
186
185
  color: '#FFFFFF',
186
+ fontSize: '15px',
187
+ fontFamily: 'helvetica neue, sans-serif',
187
188
  },
188
189
  'input::placeholder': {
189
190
  color: '#999999',
@@ -192,18 +193,22 @@ const PayWithCard: React.FC<{
192
193
  color: '#ff1600',
193
194
  },
194
195
  }}
195
- buttonProps={{
196
- className: 'font-nav h-10',
197
- css: {
198
- backgroundColor: '#fff',
199
- color: '#000',
200
- padding: 0,
201
- '&:hover': {
202
- backgroundColor: '#ffffffd9',
203
- },
204
- },
205
- }}
206
- />
196
+ render={(Button: any) => (<>
197
+ <PaymentMethods/>
198
+ <Button className={cn(
199
+ 'items-center justify-center font-medium transition-colors focus-visible:outline-none focus-visible:ring-2',
200
+ 'focus-visible:ring-ring focus-visible:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none ring-offset-background',
201
+ '!bg-primary !text-primary-fg hover:!bg-primary-hover font-nav whitespace-nowrap not-typography h-10 py-2 px-4',
202
+ '!text-sm rounded-md lg:min-w-[220px] sm:min-w-[220px] flex'
203
+ )}
204
+ >
205
+ Pay
206
+ </Button>
207
+ </>)}
208
+ >
209
+
210
+ </CreditCard>
211
+
207
212
  {transactionStatus === 'error' && (
208
213
  <p className='mx-auto text-destructive'>There was an error processing your payment.</p>
209
214
  )}
@@ -57,7 +57,6 @@ const PayWithCrypto: React.FC<{
57
57
  const [loadingPrice, setLoadingPrice] = useState(false)
58
58
  //const [selectedToken, setSelectedToken] = useState('eth')
59
59
  const [amount, setAmount] = useState<number>()
60
- const [availableAmount, setAvailableAmount] = useState<number>()
61
60
  const [provider, setProvider] = useState<ethers.BrowserProvider>()
62
61
 
63
62
  //const selectedToken = 'eth'
@@ -68,11 +67,6 @@ const PayWithCrypto: React.FC<{
68
67
  return autorun(() => {
69
68
  const newProvider = new ethers.BrowserProvider(window.ethereum)
70
69
  setProvider(newProvider)
71
- if (auth.user?.walletAddress) {
72
- newProvider.getBalance(auth.user?.walletAddress).then((balance: any) => {
73
- setAvailableAmount(Number(balance)/(10**18))
74
- })
75
- }
76
70
  })
77
71
  }, [])
78
72
 
@@ -109,11 +103,6 @@ const PayWithCrypto: React.FC<{
109
103
  })
110
104
  const newProvider = new ethers.BrowserProvider(window.ethereum)
111
105
  setProvider(newProvider)
112
- if (auth.user?.walletAddress) {
113
- newProvider.getBalance(auth.user?.walletAddress).then((balance: any) => {
114
- setAvailableAmount(Number(balance)/(10**18))
115
- })
116
- }
117
106
  } catch (err) {
118
107
  toast('Please switch your wallet to the Ethereum network.')
119
108
  return
@@ -225,7 +214,6 @@ const PayWithCrypto: React.FC<{
225
214
  </div>
226
215
  </div>
227
216
  </div>
228
- <div>Available funds in your wallet: {availableAmount} ETH</div>
229
217
 
230
218
  {transactionStatus === 'error' ? (
231
219
  <h4 className='text-destructive'>There was an error while confirming the transaction.</h4>
@@ -8,7 +8,7 @@ import Jcb from './jcb'
8
8
 
9
9
  const PaymentMethods: React.FC = () => {
10
10
  return (
11
- <div className='flex gap-1 items-center'>
11
+ <div className='flex gap-1 items-center text-muted-1'>
12
12
  <LockKeyhole className='w-4 h-4'/>
13
13
  <span className='hidden sm:flex text-sm'>Secure payments with</span>
14
14
  <Amex className='w-9 h-5'/>
@@ -84,9 +84,8 @@ const ShippingInfo: React.FC<{
84
84
  name='addressLine1'
85
85
  render={({ field }) => (
86
86
  <FormItem className='space-y-1 w-full'>
87
- <FormLabel>Address line 1</FormLabel>
88
87
  <FormControl>
89
- <Input {...field} className='border-muted-4'/>
88
+ <Input {...field} className='border-muted-4' placeholder='Address line 1'/>
90
89
  </FormControl>
91
90
  <FormMessage />
92
91
  </FormItem>
@@ -97,9 +96,8 @@ const ShippingInfo: React.FC<{
97
96
  name='addressLine2'
98
97
  render={({ field }) => (
99
98
  <FormItem className='space-y-1 w-full'>
100
- <FormLabel>Address line 2 (optional)</FormLabel>
101
99
  <FormControl>
102
- <Input {...field} className='border-muted-4'/>
100
+ <Input {...field} className='border-muted-4' placeholder='Address line 2 (optional)'/>
103
101
  </FormControl>
104
102
  <FormMessage />
105
103
  </FormItem>
@@ -111,9 +109,8 @@ const ShippingInfo: React.FC<{
111
109
  name='zipCode'
112
110
  render={({ field }) => (
113
111
  <FormItem className='space-y-1 w-full'>
114
- <FormLabel>Zip code</FormLabel>
115
112
  <FormControl>
116
- <Input {...field} className='border-muted-4'/>
113
+ <Input {...field} className='border-muted-4' placeholder='Zip code'/>
117
114
  </FormControl>
118
115
  <FormMessage />
119
116
  </FormItem>
@@ -124,9 +121,8 @@ const ShippingInfo: React.FC<{
124
121
  name='city'
125
122
  render={({ field }) => (
126
123
  <FormItem className='space-y-1 w-full'>
127
- <FormLabel>City</FormLabel>
128
124
  <FormControl>
129
- <Input {...field} className='border-muted-4'/>
125
+ <Input {...field} className='border-muted-4' placeholder='City'/>
130
126
  </FormControl>
131
127
  <FormMessage />
132
128
  </FormItem>
@@ -139,9 +135,8 @@ const ShippingInfo: React.FC<{
139
135
  name='state'
140
136
  render={({ field }) => (
141
137
  <FormItem className='space-y-1 w-full'>
142
- <FormLabel>State (Optional)</FormLabel>
143
138
  <FormControl>
144
- <Input {...field} className='border-muted-4'/>
139
+ <Input {...field} className='border-muted-4' placeholder='State (optional)'/>
145
140
  </FormControl>
146
141
  <FormMessage />
147
142
  </FormItem>
@@ -152,11 +147,10 @@ const ShippingInfo: React.FC<{
152
147
  name='country'
153
148
  render={({ field }) => (
154
149
  <FormItem className='space-y-1 w-full'>
155
- <FormLabel>Country</FormLabel>
156
150
  <Select onValueChange={field.onChange} defaultValue={field.value}>
157
151
  <FormControl>
158
- <SelectTrigger>
159
- <SelectValue placeholder='Select a country' />
152
+ <SelectTrigger className='bg-level-1 border-muted-4'>
153
+ <SelectValue placeholder='Country' />
160
154
  </SelectTrigger>
161
155
  </FormControl>
162
156
  <SelectContent>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hanzo/commerce",
3
- "version": "4.6.0",
3
+ "version": "4.7.0",
4
4
  "description": "Library with shopping cart components.",
5
5
  "publishConfig": {
6
6
  "registry": "https://registry.npmjs.org/",
@@ -31,14 +31,16 @@
31
31
  "./types": "./types/index.ts"
32
32
  },
33
33
  "dependencies": {
34
+ "@splinetool/react-spline": "^2.2.6",
35
+ "@splinetool/runtime": "^1.0.75",
34
36
  "ethers": "^6.11.1",
35
37
  "next-usequerystate": "^1.17.0",
36
38
  "react-square-web-payments-sdk": "^3.2.1",
37
39
  "square": "^35.0.0"
38
40
  },
39
41
  "peerDependencies": {
40
- "@hanzo/auth": "^2.3.0",
41
- "@hanzo/ui": "^3.0.7",
42
+ "@hanzo/auth": "^2.3.1",
43
+ "@hanzo/ui": "^3.0.9",
42
44
  "@hookform/resolvers": "^3.3.4",
43
45
  "firebase": "^10.8.0",
44
46
  "lucide-react": "^0.307.0",
@@ -5,6 +5,8 @@ import {
5
5
  observable,
6
6
  } from 'mobx'
7
7
 
8
+ import type { VideoDef } from '@hanzo/ui/types'
9
+
8
10
  import type { Product, LineItem } from '../../../types'
9
11
 
10
12
  interface ActualLineItemSnapshot {
@@ -30,6 +32,8 @@ class ActualLineItem
30
32
  desc?: string
31
33
  price: number
32
34
  img?: string
35
+ video?: VideoDef
36
+ animation?: string
33
37
  timeAdded: number = 0 // timeAdded of being added to cart
34
38
 
35
39
  constructor(prod: Product, snap?: ActualLineItemSnapshot) {
@@ -41,10 +45,12 @@ class ActualLineItem
41
45
  this.desc = prod.desc
42
46
  this.price = prod.price
43
47
  this.img = prod.img
48
+ this.video = prod.video
49
+ this.animation = prod.animation
44
50
 
45
51
  if (snap) {
46
52
  this.qu = snap.quantity
47
- this.timeAdded = snap.quantity
53
+ this.timeAdded = snap.timeAdded
48
54
  }
49
55
 
50
56
  makeObservable(this, {
package/types/product.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import type { VideoDef } from '@hanzo/ui/types'
2
+
1
3
  interface Product {
2
4
  id: string // DB index // not a logical aspect of our domain. may not be necessary at all
3
5
  sku: string // human visible on orders etc.
@@ -8,6 +10,8 @@ interface Product {
8
10
  desc?: string
9
11
  price: number
10
12
  img?: string // if undefined: (category's img exists) ? (use it) : (use generic placeholder)
13
+ animation?: string // spline scene url
14
+ video?: VideoDef
11
15
  }
12
16
 
13
17
  export {