@hanzo/commerce 7.3.0 → 7.3.2
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/Icons.tsx +34 -34
- package/components/add-to-cart-widget.tsx +183 -183
- package/components/buy/buy-card.tsx +259 -259
- package/components/buy/carousel-buy-card.tsx +242 -242
- package/components/buy/multi-family/all-variants-carousel.tsx +261 -261
- package/components/buy/multi-family/family-carousel/index.tsx +77 -77
- package/components/buy/multi-family/family-carousel/slide.tsx +83 -83
- package/components/buy/multi-family/family-carousel/state.ts +87 -87
- package/components/buy/multi-family/index.ts +2 -2
- package/components/buy/single-family-selector.tsx +90 -90
- package/components/buy/title-and-byline.tsx +25 -25
- package/components/cart/cart-panel/cart-line-item.tsx +76 -76
- package/components/cart/cart-panel/index.tsx +154 -154
- package/components/cart/cart-panel/promo-code.tsx +106 -106
- package/components/cart/cart-panel/total-area.tsx +60 -60
- package/components/checkout/payment-step-form/card-icon-row.tsx +26 -26
- package/components/checkout/payment-step-form/card-icons/amex.tsx +32 -32
- package/components/checkout/payment-step-form/card-icons/diners-club.tsx +13 -13
- package/components/checkout/payment-step-form/card-icons/discover.tsx +25 -25
- package/components/checkout/payment-step-form/card-icons/jcb.tsx +26 -26
- package/components/checkout/payment-step-form/card-icons/mastercard.tsx +27 -27
- package/components/checkout/payment-step-form/card-icons/visa.tsx +25 -25
- package/components/checkout/payment-step-form/cc-button.tsx +17 -17
- package/components/checkout/payment-step-form/contact-form.tsx +49 -49
- package/components/checkout/payment-step-form/crypto-icons/btc.tsx +11 -11
- package/components/checkout/payment-step-form/crypto-icons/eth.tsx +20 -20
- package/components/checkout/payment-step-form/crypto-icons/usdt.tsx +13 -13
- package/components/checkout/payment-step-form/index.tsx +122 -122
- package/components/checkout/payment-step-form/methods/bank-transfer.tsx +79 -79
- package/components/checkout/payment-step-form/methods/card.tsx +232 -232
- package/components/checkout/payment-step-form/methods/crypto.tsx +227 -227
- package/components/checkout/payment-step-form/methods/index.ts +22 -22
- package/components/checkout/shipping-step-form.tsx +172 -172
- package/components/index.ts +11 -11
- package/components/item/product-card.tsx +48 -48
- package/components/item-selector/button.tsx +188 -188
- package/components/item-selector/carousel/index.tsx +197 -197
- package/components/item-selector/carousel/slider.tsx +40 -40
- package/components/item-selector/index.ts +5 -5
- package/components/item-selector/quantity-indicator.tsx +48 -48
- package/components/node-tabs/index.tsx +91 -91
- package/components/node-tabs/node-image.tsx +31 -31
- package/index.ts +12 -12
- package/package.json +60 -60
- package/service/context.tsx +45 -45
- package/service/debug.ts +41 -41
- package/service/impls/standalone/actual-line-item.ts +135 -135
- package/service/impls/standalone/get-instance.ts +64 -64
- package/service/impls/standalone/index.ts +579 -579
- package/service/impls/standalone/order/firebase.ts +14 -14
- package/service/impls/standalone/order/index.ts +128 -128
- package/service/impls/standalone/persistence.ts +33 -33
- package/service/path-utils.ts +25 -25
- package/service/sep.ts +6 -6
- package/tsconfig.json +10 -10
- package/types/README.md +1 -1
- package/types/category-node.ts +49 -49
- package/types/checkout.ts +46 -46
- package/types/commerce-config.ts +13 -13
- package/types/commerce-service.ts +127 -127
- package/types/family.ts +25 -25
- package/types/index.ts +15 -15
- package/types/item-selector.ts +96 -96
- package/types/line-item.ts +29 -29
- package/types/multi-family-selector-props.ts +19 -19
- package/types/product.ts +20 -20
- package/types/promo.ts +9 -9
- package/types/selection-ui-specifier.ts +51 -51
- package/types/string-mutator.ts +13 -13
- package/types/token-separators.ts +7 -7
- package/util/analytics.ts +20 -20
- package/util/countries.ts +195 -195
- package/util/error.ts +34 -34
- package/util/index.ts +70 -70
- package/util/item-selector-options-accessor.ts +34 -34
- package/util/line-item-ref.ts +23 -23
- package/util/multi-family-selector-options-accessor.ts +14 -14
- package/util/obs-string-mutator.ts +22 -22
- package/util/product-media-accessor.ts +58 -58
- package/util/promo-codes.ts +108 -108
- package/util/selection-ui-specifiers.ts +29 -29
- package/util/square-payment.ts +42 -42
- package/util/use-sync-sku-param-w-current-item.ts +88 -88
|
@@ -1,172 +1,172 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import * as z from 'zod'
|
|
4
|
-
import { useForm } from 'react-hook-form'
|
|
5
|
-
import { zodResolver } from '@hookform/resolvers/zod'
|
|
6
|
-
|
|
7
|
-
import {
|
|
8
|
-
Input,
|
|
9
|
-
Button,
|
|
10
|
-
Form,
|
|
11
|
-
FormControl,
|
|
12
|
-
FormField,
|
|
13
|
-
FormItem,
|
|
14
|
-
FormMessage,
|
|
15
|
-
Select,
|
|
16
|
-
SelectContent,
|
|
17
|
-
SelectItem,
|
|
18
|
-
SelectTrigger,
|
|
19
|
-
SelectValue
|
|
20
|
-
} from '@hanzo/ui/primitives'
|
|
21
|
-
|
|
22
|
-
import { useCommerce } from '../../service/context'
|
|
23
|
-
|
|
24
|
-
import countries from '../../util/countries'
|
|
25
|
-
import { sendGAEvent } from '../../util/analytics'
|
|
26
|
-
|
|
27
|
-
import type { CheckoutStepComponentProps } from '../../types'
|
|
28
|
-
|
|
29
|
-
const shippingFormSchema = z.object({
|
|
30
|
-
addressLine1: z.string().min(2, 'Address must be at least 2 characters.'),
|
|
31
|
-
addressLine2: z.string().optional(),
|
|
32
|
-
zipCode: z.string().min(2, 'Zip code is invalid.'),
|
|
33
|
-
city: z.string().min(2, 'City is invalid.'),
|
|
34
|
-
state: z.string().optional(),
|
|
35
|
-
country: z.string().min(2, 'Country is invalid.'),
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
const ShippingStepForm: React.FC<CheckoutStepComponentProps> = ({
|
|
39
|
-
orderId,
|
|
40
|
-
onDone
|
|
41
|
-
}) => {
|
|
42
|
-
const cmmc = useCommerce()
|
|
43
|
-
|
|
44
|
-
const shippingForm = useForm<z.infer<typeof shippingFormSchema>>({
|
|
45
|
-
resolver: zodResolver(shippingFormSchema),
|
|
46
|
-
defaultValues: {
|
|
47
|
-
addressLine1: '',
|
|
48
|
-
addressLine2: '',
|
|
49
|
-
zipCode: '',
|
|
50
|
-
city: '',
|
|
51
|
-
state: '',
|
|
52
|
-
country: '',
|
|
53
|
-
},
|
|
54
|
-
})
|
|
55
|
-
|
|
56
|
-
const onSubmit = async (values: z.infer<typeof shippingFormSchema>) => {
|
|
57
|
-
if (orderId) {
|
|
58
|
-
await cmmc.updateOrderShippingInfo(orderId, values)
|
|
59
|
-
}
|
|
60
|
-
sendGAEvent('add_shipping_info', {
|
|
61
|
-
items: cmmc.cartItems.map((item) => ({
|
|
62
|
-
item_id: item.sku,
|
|
63
|
-
item_name: item.title,
|
|
64
|
-
item_category: item.familyId,
|
|
65
|
-
price: item.price,
|
|
66
|
-
quantity: item.quantity
|
|
67
|
-
})),
|
|
68
|
-
num_items: cmmc.cartItems.length,
|
|
69
|
-
value: cmmc.cartTotal,
|
|
70
|
-
currency: 'USD',
|
|
71
|
-
})
|
|
72
|
-
onDone()
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
return (
|
|
76
|
-
<Form {...shippingForm}>
|
|
77
|
-
<form onSubmit={shippingForm.handleSubmit(onSubmit)} className='text-left'>
|
|
78
|
-
<div className='flex flex-col gap-4'>
|
|
79
|
-
<FormField
|
|
80
|
-
control={shippingForm.control}
|
|
81
|
-
name='addressLine1'
|
|
82
|
-
render={({ field }) => (
|
|
83
|
-
<FormItem className='space-y-1 w-full'>
|
|
84
|
-
<FormControl>
|
|
85
|
-
<Input {...field} placeholder='Address line 1'/>
|
|
86
|
-
</FormControl>
|
|
87
|
-
<FormMessage />
|
|
88
|
-
</FormItem>
|
|
89
|
-
)}
|
|
90
|
-
/>
|
|
91
|
-
<FormField
|
|
92
|
-
control={shippingForm.control}
|
|
93
|
-
name='addressLine2'
|
|
94
|
-
render={({ field }) => (
|
|
95
|
-
<FormItem className='space-y-1 w-full'>
|
|
96
|
-
<FormControl>
|
|
97
|
-
<Input {...field} placeholder='Address line 2 (optional)'/>
|
|
98
|
-
</FormControl>
|
|
99
|
-
<FormMessage />
|
|
100
|
-
</FormItem>
|
|
101
|
-
)}
|
|
102
|
-
/>
|
|
103
|
-
<div className='flex gap-4 items-end'>
|
|
104
|
-
<FormField
|
|
105
|
-
control={shippingForm.control}
|
|
106
|
-
name='zipCode'
|
|
107
|
-
render={({ field }) => (
|
|
108
|
-
<FormItem className='space-y-1 w-full'>
|
|
109
|
-
<FormControl>
|
|
110
|
-
<Input {...field} placeholder='Zip code'/>
|
|
111
|
-
</FormControl>
|
|
112
|
-
<FormMessage />
|
|
113
|
-
</FormItem>
|
|
114
|
-
)}
|
|
115
|
-
/>
|
|
116
|
-
<FormField
|
|
117
|
-
control={shippingForm.control}
|
|
118
|
-
name='city'
|
|
119
|
-
render={({ field }) => (
|
|
120
|
-
<FormItem className='space-y-1 w-full'>
|
|
121
|
-
<FormControl>
|
|
122
|
-
<Input {...field} placeholder='City'/>
|
|
123
|
-
</FormControl>
|
|
124
|
-
<FormMessage />
|
|
125
|
-
</FormItem>
|
|
126
|
-
)}
|
|
127
|
-
/>
|
|
128
|
-
</div>
|
|
129
|
-
<div className='flex gap-4 items-start'>
|
|
130
|
-
<FormField
|
|
131
|
-
control={shippingForm.control}
|
|
132
|
-
name='state'
|
|
133
|
-
render={({ field }) => (
|
|
134
|
-
<FormItem className='space-y-1 w-full'>
|
|
135
|
-
<FormControl>
|
|
136
|
-
<Input {...field} placeholder='State (optional)'/>
|
|
137
|
-
</FormControl>
|
|
138
|
-
<FormMessage />
|
|
139
|
-
</FormItem>
|
|
140
|
-
)}
|
|
141
|
-
/>
|
|
142
|
-
<FormField
|
|
143
|
-
control={shippingForm.control}
|
|
144
|
-
name='country'
|
|
145
|
-
render={({ field }) => (
|
|
146
|
-
<FormItem className='space-y-1 w-full'>
|
|
147
|
-
<Select onValueChange={field.onChange} defaultValue={field.value}>
|
|
148
|
-
<FormControl>
|
|
149
|
-
<SelectTrigger className='bg-level-1 border-muted-4'>
|
|
150
|
-
<SelectValue placeholder='Country' />
|
|
151
|
-
</SelectTrigger>
|
|
152
|
-
</FormControl>
|
|
153
|
-
<SelectContent>
|
|
154
|
-
{countries.map((country) => (
|
|
155
|
-
<SelectItem key={country.id} value={country.name}>
|
|
156
|
-
{country.name}
|
|
157
|
-
</SelectItem>
|
|
158
|
-
))}
|
|
159
|
-
</SelectContent>
|
|
160
|
-
</Select>
|
|
161
|
-
</FormItem>
|
|
162
|
-
)}
|
|
163
|
-
/>
|
|
164
|
-
</div>
|
|
165
|
-
</div>
|
|
166
|
-
<Button type='submit' className='mx-auto w-full mt-4'>Confirm order</Button>
|
|
167
|
-
</form>
|
|
168
|
-
</Form>
|
|
169
|
-
)
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
export default ShippingStepForm
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import * as z from 'zod'
|
|
4
|
+
import { useForm } from 'react-hook-form'
|
|
5
|
+
import { zodResolver } from '@hookform/resolvers/zod'
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
Input,
|
|
9
|
+
Button,
|
|
10
|
+
Form,
|
|
11
|
+
FormControl,
|
|
12
|
+
FormField,
|
|
13
|
+
FormItem,
|
|
14
|
+
FormMessage,
|
|
15
|
+
Select,
|
|
16
|
+
SelectContent,
|
|
17
|
+
SelectItem,
|
|
18
|
+
SelectTrigger,
|
|
19
|
+
SelectValue
|
|
20
|
+
} from '@hanzo/ui/primitives'
|
|
21
|
+
|
|
22
|
+
import { useCommerce } from '../../service/context'
|
|
23
|
+
|
|
24
|
+
import countries from '../../util/countries'
|
|
25
|
+
import { sendGAEvent } from '../../util/analytics'
|
|
26
|
+
|
|
27
|
+
import type { CheckoutStepComponentProps } from '../../types'
|
|
28
|
+
|
|
29
|
+
const shippingFormSchema = z.object({
|
|
30
|
+
addressLine1: z.string().min(2, 'Address must be at least 2 characters.'),
|
|
31
|
+
addressLine2: z.string().optional(),
|
|
32
|
+
zipCode: z.string().min(2, 'Zip code is invalid.'),
|
|
33
|
+
city: z.string().min(2, 'City is invalid.'),
|
|
34
|
+
state: z.string().optional(),
|
|
35
|
+
country: z.string().min(2, 'Country is invalid.'),
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
const ShippingStepForm: React.FC<CheckoutStepComponentProps> = ({
|
|
39
|
+
orderId,
|
|
40
|
+
onDone
|
|
41
|
+
}) => {
|
|
42
|
+
const cmmc = useCommerce()
|
|
43
|
+
|
|
44
|
+
const shippingForm = useForm<z.infer<typeof shippingFormSchema>>({
|
|
45
|
+
resolver: zodResolver(shippingFormSchema),
|
|
46
|
+
defaultValues: {
|
|
47
|
+
addressLine1: '',
|
|
48
|
+
addressLine2: '',
|
|
49
|
+
zipCode: '',
|
|
50
|
+
city: '',
|
|
51
|
+
state: '',
|
|
52
|
+
country: '',
|
|
53
|
+
},
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
const onSubmit = async (values: z.infer<typeof shippingFormSchema>) => {
|
|
57
|
+
if (orderId) {
|
|
58
|
+
await cmmc.updateOrderShippingInfo(orderId, values)
|
|
59
|
+
}
|
|
60
|
+
sendGAEvent('add_shipping_info', {
|
|
61
|
+
items: cmmc.cartItems.map((item) => ({
|
|
62
|
+
item_id: item.sku,
|
|
63
|
+
item_name: item.title,
|
|
64
|
+
item_category: item.familyId,
|
|
65
|
+
price: item.price,
|
|
66
|
+
quantity: item.quantity
|
|
67
|
+
})),
|
|
68
|
+
num_items: cmmc.cartItems.length,
|
|
69
|
+
value: cmmc.cartTotal,
|
|
70
|
+
currency: 'USD',
|
|
71
|
+
})
|
|
72
|
+
onDone()
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
<Form {...shippingForm}>
|
|
77
|
+
<form onSubmit={shippingForm.handleSubmit(onSubmit)} className='text-left'>
|
|
78
|
+
<div className='flex flex-col gap-4'>
|
|
79
|
+
<FormField
|
|
80
|
+
control={shippingForm.control}
|
|
81
|
+
name='addressLine1'
|
|
82
|
+
render={({ field }) => (
|
|
83
|
+
<FormItem className='space-y-1 w-full'>
|
|
84
|
+
<FormControl>
|
|
85
|
+
<Input {...field} placeholder='Address line 1'/>
|
|
86
|
+
</FormControl>
|
|
87
|
+
<FormMessage />
|
|
88
|
+
</FormItem>
|
|
89
|
+
)}
|
|
90
|
+
/>
|
|
91
|
+
<FormField
|
|
92
|
+
control={shippingForm.control}
|
|
93
|
+
name='addressLine2'
|
|
94
|
+
render={({ field }) => (
|
|
95
|
+
<FormItem className='space-y-1 w-full'>
|
|
96
|
+
<FormControl>
|
|
97
|
+
<Input {...field} placeholder='Address line 2 (optional)'/>
|
|
98
|
+
</FormControl>
|
|
99
|
+
<FormMessage />
|
|
100
|
+
</FormItem>
|
|
101
|
+
)}
|
|
102
|
+
/>
|
|
103
|
+
<div className='flex gap-4 items-end'>
|
|
104
|
+
<FormField
|
|
105
|
+
control={shippingForm.control}
|
|
106
|
+
name='zipCode'
|
|
107
|
+
render={({ field }) => (
|
|
108
|
+
<FormItem className='space-y-1 w-full'>
|
|
109
|
+
<FormControl>
|
|
110
|
+
<Input {...field} placeholder='Zip code'/>
|
|
111
|
+
</FormControl>
|
|
112
|
+
<FormMessage />
|
|
113
|
+
</FormItem>
|
|
114
|
+
)}
|
|
115
|
+
/>
|
|
116
|
+
<FormField
|
|
117
|
+
control={shippingForm.control}
|
|
118
|
+
name='city'
|
|
119
|
+
render={({ field }) => (
|
|
120
|
+
<FormItem className='space-y-1 w-full'>
|
|
121
|
+
<FormControl>
|
|
122
|
+
<Input {...field} placeholder='City'/>
|
|
123
|
+
</FormControl>
|
|
124
|
+
<FormMessage />
|
|
125
|
+
</FormItem>
|
|
126
|
+
)}
|
|
127
|
+
/>
|
|
128
|
+
</div>
|
|
129
|
+
<div className='flex gap-4 items-start'>
|
|
130
|
+
<FormField
|
|
131
|
+
control={shippingForm.control}
|
|
132
|
+
name='state'
|
|
133
|
+
render={({ field }) => (
|
|
134
|
+
<FormItem className='space-y-1 w-full'>
|
|
135
|
+
<FormControl>
|
|
136
|
+
<Input {...field} placeholder='State (optional)'/>
|
|
137
|
+
</FormControl>
|
|
138
|
+
<FormMessage />
|
|
139
|
+
</FormItem>
|
|
140
|
+
)}
|
|
141
|
+
/>
|
|
142
|
+
<FormField
|
|
143
|
+
control={shippingForm.control}
|
|
144
|
+
name='country'
|
|
145
|
+
render={({ field }) => (
|
|
146
|
+
<FormItem className='space-y-1 w-full'>
|
|
147
|
+
<Select onValueChange={field.onChange} defaultValue={field.value}>
|
|
148
|
+
<FormControl>
|
|
149
|
+
<SelectTrigger className='bg-level-1 border-muted-4'>
|
|
150
|
+
<SelectValue placeholder='Country' />
|
|
151
|
+
</SelectTrigger>
|
|
152
|
+
</FormControl>
|
|
153
|
+
<SelectContent>
|
|
154
|
+
{countries.map((country) => (
|
|
155
|
+
<SelectItem key={country.id} value={country.name}>
|
|
156
|
+
{country.name}
|
|
157
|
+
</SelectItem>
|
|
158
|
+
))}
|
|
159
|
+
</SelectContent>
|
|
160
|
+
</Select>
|
|
161
|
+
</FormItem>
|
|
162
|
+
)}
|
|
163
|
+
/>
|
|
164
|
+
</div>
|
|
165
|
+
</div>
|
|
166
|
+
<Button type='submit' className='mx-auto w-full mt-4'>Confirm order</Button>
|
|
167
|
+
</form>
|
|
168
|
+
</Form>
|
|
169
|
+
)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export default ShippingStepForm
|
package/components/index.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
export { default as AddToCartWidget } from './add-to-cart-widget'
|
|
2
|
-
export { default as CarouselBuyCard } from './buy/carousel-buy-card'
|
|
3
|
-
|
|
4
|
-
export { default as CartPanel } from './cart/cart-panel'
|
|
5
|
-
|
|
6
|
-
export { default as Icons } from './Icons'
|
|
7
|
-
export { default as PaymentStepForm } from './checkout/payment-step-form'
|
|
8
|
-
export { default as ProductCard } from './item/product-card'
|
|
9
|
-
export { default as ShippingStepForm } from './checkout/shipping-step-form'
|
|
10
|
-
|
|
11
|
-
export * from './item-selector'
|
|
1
|
+
export { default as AddToCartWidget } from './add-to-cart-widget'
|
|
2
|
+
export { default as CarouselBuyCard } from './buy/carousel-buy-card'
|
|
3
|
+
|
|
4
|
+
export { default as CartPanel } from './cart/cart-panel'
|
|
5
|
+
|
|
6
|
+
export { default as Icons } from './Icons'
|
|
7
|
+
export { default as PaymentStepForm } from './checkout/payment-step-form'
|
|
8
|
+
export { default as ProductCard } from './item/product-card'
|
|
9
|
+
export { default as ShippingStepForm } from './checkout/shipping-step-form'
|
|
10
|
+
|
|
11
|
+
export * from './item-selector'
|
|
@@ -1,48 +1,48 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
Card,
|
|
5
|
-
CardContent,
|
|
6
|
-
CardFooter,
|
|
7
|
-
CardHeader,
|
|
8
|
-
CardTitle,
|
|
9
|
-
MediaStack
|
|
10
|
-
} from '@hanzo/ui/primitives'
|
|
11
|
-
|
|
12
|
-
import { cn } from '@hanzo/ui/util'
|
|
13
|
-
|
|
14
|
-
import { formatCurrencyValue } from '../../util'
|
|
15
|
-
import type { LineItem } from '../../types'
|
|
16
|
-
|
|
17
|
-
import AddToCartWidget from '../add-to-cart-widget'
|
|
18
|
-
|
|
19
|
-
interface ProductCardProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
20
|
-
item: LineItem
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const ProductCard: React.FC<ProductCardProps> = ({
|
|
24
|
-
item,
|
|
25
|
-
className,
|
|
26
|
-
...props
|
|
27
|
-
}) => (
|
|
28
|
-
<Card
|
|
29
|
-
className={cn('max-h-[360px] lg:min-w-[200px] max-w-[260px] overflow-hidden', className)}
|
|
30
|
-
{...props}
|
|
31
|
-
>
|
|
32
|
-
<CardHeader className='w-full border-b p-6 min-h-[180px] max-h-[240px] relative'>
|
|
33
|
-
<MediaStack media={item} constrainTo={{w: 700, h:700}} clx='p-6' />
|
|
34
|
-
</CardHeader>
|
|
35
|
-
<CardContent className='grid gap-2.5 p-4'>
|
|
36
|
-
<CardTitle className='text-sm sm:text-base flex flex-col justify-start items-center line-clap-3'>
|
|
37
|
-
{item.title.split(', ').map((e, i) => (<p key={i}>{e}</p>))}
|
|
38
|
-
<p className='mt-1 font-semibold'>{formatCurrencyValue(item.price)}</p>
|
|
39
|
-
</CardTitle>
|
|
40
|
-
</CardContent>
|
|
41
|
-
<CardFooter className='p-4 flex flex-row justify-center'>
|
|
42
|
-
<AddToCartWidget item={item} />
|
|
43
|
-
</CardFooter>
|
|
44
|
-
</Card>
|
|
45
|
-
)
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
export default ProductCard
|
|
1
|
+
import React from 'react'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
Card,
|
|
5
|
+
CardContent,
|
|
6
|
+
CardFooter,
|
|
7
|
+
CardHeader,
|
|
8
|
+
CardTitle,
|
|
9
|
+
MediaStack
|
|
10
|
+
} from '@hanzo/ui/primitives'
|
|
11
|
+
|
|
12
|
+
import { cn } from '@hanzo/ui/util'
|
|
13
|
+
|
|
14
|
+
import { formatCurrencyValue } from '../../util'
|
|
15
|
+
import type { LineItem } from '../../types'
|
|
16
|
+
|
|
17
|
+
import AddToCartWidget from '../add-to-cart-widget'
|
|
18
|
+
|
|
19
|
+
interface ProductCardProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
20
|
+
item: LineItem
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const ProductCard: React.FC<ProductCardProps> = ({
|
|
24
|
+
item,
|
|
25
|
+
className,
|
|
26
|
+
...props
|
|
27
|
+
}) => (
|
|
28
|
+
<Card
|
|
29
|
+
className={cn('max-h-[360px] lg:min-w-[200px] max-w-[260px] overflow-hidden', className)}
|
|
30
|
+
{...props}
|
|
31
|
+
>
|
|
32
|
+
<CardHeader className='w-full border-b p-6 min-h-[180px] max-h-[240px] relative'>
|
|
33
|
+
<MediaStack media={item} constrainTo={{w: 700, h:700}} clx='p-6' />
|
|
34
|
+
</CardHeader>
|
|
35
|
+
<CardContent className='grid gap-2.5 p-4'>
|
|
36
|
+
<CardTitle className='text-sm sm:text-base flex flex-col justify-start items-center line-clap-3'>
|
|
37
|
+
{item.title.split(', ').map((e, i) => (<p key={i}>{e}</p>))}
|
|
38
|
+
<p className='mt-1 font-semibold'>{formatCurrencyValue(item.price)}</p>
|
|
39
|
+
</CardTitle>
|
|
40
|
+
</CardContent>
|
|
41
|
+
<CardFooter className='p-4 flex flex-row justify-center'>
|
|
42
|
+
<AddToCartWidget item={item} />
|
|
43
|
+
</CardFooter>
|
|
44
|
+
</Card>
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
export default ProductCard
|