@shopify/shop-minis-react 0.2.9 → 0.3.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.
- package/dist/components/commerce/add-to-cart.js +70 -53
- package/dist/components/commerce/add-to-cart.js.map +1 -1
- package/dist/components/commerce/buy-now.js +75 -0
- package/dist/components/commerce/buy-now.js.map +1 -0
- package/dist/index.js +230 -230
- package/dist/{hooks/shop → internal}/useShopCartActions.js +2 -2
- package/dist/internal/useShopCartActions.js.map +1 -0
- package/generated-hook-maps/hook-actions-map.json +0 -4
- package/package.json +2 -2
- package/src/components/commerce/add-to-cart.test.tsx +218 -3
- package/src/components/commerce/add-to-cart.tsx +40 -16
- package/src/components/commerce/buy-now.test.tsx +272 -0
- package/src/components/commerce/buy-now.tsx +108 -0
- package/src/components/index.ts +1 -0
- package/src/hooks/index.ts +0 -1
- package/src/{hooks/shop → internal}/useShopCartActions.ts +2 -2
- package/src/stories/AddToCart.stories.tsx +75 -10
- package/dist/hooks/shop/useShopCartActions.js.map +0 -1
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import {useState, useCallback} from 'react'
|
|
2
|
+
|
|
3
|
+
import {Product} from '@shopify/shop-minis-platform'
|
|
4
|
+
import {motion, AnimatePresence} from 'motion/react'
|
|
5
|
+
|
|
6
|
+
import {useErrorToast, useShopNavigation} from '../../hooks'
|
|
7
|
+
import {useShopCartActions} from '../../internal/useShopCartActions'
|
|
8
|
+
import {cn} from '../../lib/utils'
|
|
9
|
+
import {Button} from '../atoms/button'
|
|
10
|
+
|
|
11
|
+
interface BuyNowButtonProps {
|
|
12
|
+
disabled?: boolean
|
|
13
|
+
className?: string
|
|
14
|
+
size?: 'default' | 'sm' | 'lg'
|
|
15
|
+
/**
|
|
16
|
+
* The discount code to apply to the purchase.
|
|
17
|
+
*/
|
|
18
|
+
discountCode?: string
|
|
19
|
+
/**
|
|
20
|
+
* The GID of the product variant. E.g. `gid://shopify/ProductVariant/456`.
|
|
21
|
+
*/
|
|
22
|
+
productVariantId: string
|
|
23
|
+
/**
|
|
24
|
+
* The product to buy now.
|
|
25
|
+
*/
|
|
26
|
+
product?: Product
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function BuyNowButton({
|
|
30
|
+
disabled = false,
|
|
31
|
+
className,
|
|
32
|
+
size = 'default',
|
|
33
|
+
productVariantId,
|
|
34
|
+
discountCode,
|
|
35
|
+
product,
|
|
36
|
+
}: BuyNowButtonProps) {
|
|
37
|
+
const {buyProduct} = useShopCartActions()
|
|
38
|
+
const {navigateToProduct} = useShopNavigation()
|
|
39
|
+
const [isPurchasing, setIsPurchasing] = useState(false)
|
|
40
|
+
const {id, referral} = product ?? {}
|
|
41
|
+
|
|
42
|
+
const {showErrorToast} = useErrorToast()
|
|
43
|
+
|
|
44
|
+
const handleClick = useCallback(async () => {
|
|
45
|
+
if (disabled) return
|
|
46
|
+
|
|
47
|
+
if (id && referral) {
|
|
48
|
+
navigateToProduct({
|
|
49
|
+
productId: id,
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
return
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (isPurchasing) return
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
if (id && productVariantId) {
|
|
59
|
+
setIsPurchasing(true)
|
|
60
|
+
|
|
61
|
+
await buyProduct({
|
|
62
|
+
productId: id,
|
|
63
|
+
productVariantId,
|
|
64
|
+
quantity: 1,
|
|
65
|
+
discountCode,
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
setIsPurchasing(false)
|
|
69
|
+
}
|
|
70
|
+
} catch (error) {
|
|
71
|
+
showErrorToast({message: 'Failed to complete purchase'})
|
|
72
|
+
setIsPurchasing(false)
|
|
73
|
+
}
|
|
74
|
+
}, [
|
|
75
|
+
disabled,
|
|
76
|
+
id,
|
|
77
|
+
referral,
|
|
78
|
+
isPurchasing,
|
|
79
|
+
navigateToProduct,
|
|
80
|
+
productVariantId,
|
|
81
|
+
buyProduct,
|
|
82
|
+
discountCode,
|
|
83
|
+
showErrorToast,
|
|
84
|
+
])
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
<Button
|
|
88
|
+
onClick={handleClick}
|
|
89
|
+
disabled={disabled || isPurchasing}
|
|
90
|
+
className={cn('relative overflow-hidden', className)}
|
|
91
|
+
size={size}
|
|
92
|
+
aria-live="polite"
|
|
93
|
+
aria-busy={isPurchasing}
|
|
94
|
+
>
|
|
95
|
+
<AnimatePresence mode="wait">
|
|
96
|
+
<motion.span
|
|
97
|
+
key="text"
|
|
98
|
+
initial={{opacity: 0, y: 10}}
|
|
99
|
+
animate={{opacity: 1, y: 0}}
|
|
100
|
+
exit={{opacity: 0, y: -10}}
|
|
101
|
+
transition={{duration: 0.2}}
|
|
102
|
+
>
|
|
103
|
+
{referral ? 'View product' : 'Buy now'}
|
|
104
|
+
</motion.span>
|
|
105
|
+
</AnimatePresence>
|
|
106
|
+
</Button>
|
|
107
|
+
)
|
|
108
|
+
}
|
package/src/components/index.ts
CHANGED
package/src/hooks/index.ts
CHANGED
|
@@ -3,8 +3,8 @@ import {
|
|
|
3
3
|
BuyProductParams,
|
|
4
4
|
} from '@shopify/shop-minis-platform/actions'
|
|
5
5
|
|
|
6
|
-
import {useHandleAction} from '
|
|
7
|
-
import {useShopActions} from '
|
|
6
|
+
import {useHandleAction} from './useHandleAction'
|
|
7
|
+
import {useShopActions} from './useShopActions'
|
|
8
8
|
|
|
9
9
|
interface UseShopCartActionsReturns {
|
|
10
10
|
/**
|
|
@@ -1,7 +1,29 @@
|
|
|
1
|
+
import {Product} from '@shopify/shop-minis-platform'
|
|
2
|
+
|
|
1
3
|
import {AddToCartButton} from '../components/commerce/add-to-cart'
|
|
2
4
|
|
|
3
5
|
import type {Meta, StoryObj} from '@storybook/react-vite'
|
|
4
6
|
|
|
7
|
+
// Mock product for stories
|
|
8
|
+
const mockProduct: Product = {
|
|
9
|
+
id: 'gid://shopify/Product/123',
|
|
10
|
+
title: 'Test Product',
|
|
11
|
+
reviewAnalytics: {
|
|
12
|
+
averageRating: 4.5,
|
|
13
|
+
reviewCount: 100,
|
|
14
|
+
},
|
|
15
|
+
shop: {
|
|
16
|
+
id: 'gid://shopify/Shop/1',
|
|
17
|
+
name: 'Test Shop',
|
|
18
|
+
},
|
|
19
|
+
defaultVariantId: 'gid://shopify/ProductVariant/456',
|
|
20
|
+
isFavorited: false,
|
|
21
|
+
price: {
|
|
22
|
+
amount: '29.99',
|
|
23
|
+
currencyCode: 'USD',
|
|
24
|
+
},
|
|
25
|
+
}
|
|
26
|
+
|
|
5
27
|
const meta = {
|
|
6
28
|
title: 'Commerce/AddToCartButton',
|
|
7
29
|
component: AddToCartButton,
|
|
@@ -21,9 +43,9 @@ const meta = {
|
|
|
21
43
|
control: 'text',
|
|
22
44
|
description: 'Additional CSS classes',
|
|
23
45
|
},
|
|
24
|
-
|
|
25
|
-
control: '
|
|
26
|
-
description: 'The
|
|
46
|
+
product: {
|
|
47
|
+
control: 'object',
|
|
48
|
+
description: 'The product object containing product details',
|
|
27
49
|
},
|
|
28
50
|
productVariantId: {
|
|
29
51
|
control: 'text',
|
|
@@ -38,7 +60,7 @@ const meta = {
|
|
|
38
60
|
args: {
|
|
39
61
|
disabled: false,
|
|
40
62
|
size: 'default',
|
|
41
|
-
|
|
63
|
+
product: mockProduct,
|
|
42
64
|
productVariantId: 'gid://shopify/ProductVariant/456',
|
|
43
65
|
},
|
|
44
66
|
} satisfies Meta<typeof AddToCartButton>
|
|
@@ -97,6 +119,23 @@ export const Disabled: Story = {
|
|
|
97
119
|
},
|
|
98
120
|
}
|
|
99
121
|
|
|
122
|
+
export const ReferralProduct: Story = {
|
|
123
|
+
args: {
|
|
124
|
+
product: {...mockProduct, referral: true},
|
|
125
|
+
},
|
|
126
|
+
render: args => {
|
|
127
|
+
return (
|
|
128
|
+
<div className="space-y-2">
|
|
129
|
+
<p className="text-sm text-gray-600">
|
|
130
|
+
This is a referral product - button shows “View product”
|
|
131
|
+
instead
|
|
132
|
+
</p>
|
|
133
|
+
<AddToCartButton {...args} />
|
|
134
|
+
</div>
|
|
135
|
+
)
|
|
136
|
+
},
|
|
137
|
+
}
|
|
138
|
+
|
|
100
139
|
export const CustomStyling: Story = {
|
|
101
140
|
args: {
|
|
102
141
|
className: 'bg-purple-600 hover:bg-purple-700 text-white rounded-full px-8',
|
|
@@ -120,17 +159,17 @@ export const AllSizes: Story = {
|
|
|
120
159
|
<h3 className="text-lg font-semibold">Button Sizes</h3>
|
|
121
160
|
<div className="flex gap-4 items-center">
|
|
122
161
|
<AddToCartButton
|
|
123
|
-
|
|
162
|
+
product={mockProduct}
|
|
124
163
|
productVariantId="gid://shopify/ProductVariant/456"
|
|
125
164
|
size="sm"
|
|
126
165
|
/>
|
|
127
166
|
<AddToCartButton
|
|
128
|
-
|
|
167
|
+
product={mockProduct}
|
|
129
168
|
productVariantId="gid://shopify/ProductVariant/456"
|
|
130
169
|
size="default"
|
|
131
170
|
/>
|
|
132
171
|
<AddToCartButton
|
|
133
|
-
|
|
172
|
+
product={mockProduct}
|
|
134
173
|
productVariantId="gid://shopify/ProductVariant/456"
|
|
135
174
|
size="lg"
|
|
136
175
|
/>
|
|
@@ -142,6 +181,32 @@ export const AllSizes: Story = {
|
|
|
142
181
|
|
|
143
182
|
export const InteractiveExample: Story = {
|
|
144
183
|
render: () => {
|
|
184
|
+
const productA: Product = {
|
|
185
|
+
...mockProduct,
|
|
186
|
+
id: 'gid://shopify/Product/111',
|
|
187
|
+
title: 'Product A',
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const productB: Product = {
|
|
191
|
+
...mockProduct,
|
|
192
|
+
id: 'gid://shopify/Product/222',
|
|
193
|
+
title: 'Product B',
|
|
194
|
+
price: {
|
|
195
|
+
amount: '49.99',
|
|
196
|
+
currencyCode: 'USD',
|
|
197
|
+
},
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const productC: Product = {
|
|
201
|
+
...mockProduct,
|
|
202
|
+
id: 'gid://shopify/Product/333',
|
|
203
|
+
title: 'Product C',
|
|
204
|
+
price: {
|
|
205
|
+
amount: '19.99',
|
|
206
|
+
currencyCode: 'USD',
|
|
207
|
+
},
|
|
208
|
+
}
|
|
209
|
+
|
|
145
210
|
return (
|
|
146
211
|
<div className="flex flex-col items-center space-y-6 p-8">
|
|
147
212
|
<div className="text-center">
|
|
@@ -155,7 +220,7 @@ export const InteractiveExample: Story = {
|
|
|
155
220
|
<div className="text-center">
|
|
156
221
|
<p className="text-xs text-gray-500 mb-2">Product A</p>
|
|
157
222
|
<AddToCartButton
|
|
158
|
-
|
|
223
|
+
product={productA}
|
|
159
224
|
productVariantId="gid://shopify/ProductVariant/111"
|
|
160
225
|
/>
|
|
161
226
|
</div>
|
|
@@ -165,7 +230,7 @@ export const InteractiveExample: Story = {
|
|
|
165
230
|
Product B (with discount)
|
|
166
231
|
</p>
|
|
167
232
|
<AddToCartButton
|
|
168
|
-
|
|
233
|
+
product={productB}
|
|
169
234
|
productVariantId="gid://shopify/ProductVariant/222"
|
|
170
235
|
discountCodes={['SAVE10']}
|
|
171
236
|
/>
|
|
@@ -174,7 +239,7 @@ export const InteractiveExample: Story = {
|
|
|
174
239
|
<div className="text-center">
|
|
175
240
|
<p className="text-xs text-gray-500 mb-2">Product C (small size)</p>
|
|
176
241
|
<AddToCartButton
|
|
177
|
-
|
|
242
|
+
product={productC}
|
|
178
243
|
productVariantId="gid://shopify/ProductVariant/333"
|
|
179
244
|
size="sm"
|
|
180
245
|
/>
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"useShopCartActions.js","sources":["../../../src/hooks/shop/useShopCartActions.ts"],"sourcesContent":["import {\n AddToCartParams,\n BuyProductParams,\n} from '@shopify/shop-minis-platform/actions'\n\nimport {useHandleAction} from '../../internal/useHandleAction'\nimport {useShopActions} from '../../internal/useShopActions'\n\ninterface UseShopCartActionsReturns {\n /**\n * Add a product to the cart\n */\n addToCart: (params: AddToCartParams) => Promise<void>\n /**\n * Buy a product directly\n */\n buyProduct: (params: BuyProductParams) => Promise<void>\n}\n\nexport const useShopCartActions = (): UseShopCartActionsReturns => {\n const {addToCart, buyProduct} = useShopActions()\n\n return {\n addToCart: useHandleAction(addToCart),\n buyProduct: useHandleAction(buyProduct),\n }\n}\n"],"names":["useShopCartActions","addToCart","buyProduct","useShopActions","useHandleAction"],"mappings":";;AAmBO,MAAMA,IAAqB,MAAiC;AACjE,QAAM,EAAC,WAAAC,GAAW,YAAAC,EAAU,IAAIC,EAAe;AAExC,SAAA;AAAA,IACL,WAAWC,EAAgBH,CAAS;AAAA,IACpC,YAAYG,EAAgBF,CAAU;AAAA,EACxC;AACF;"}
|