@shopify/shop-minis-react 0.0.30 → 0.0.32

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.
Files changed (36) hide show
  1. package/dist/_virtual/index10.js +2 -2
  2. package/dist/_virtual/index2.js +4 -4
  3. package/dist/_virtual/index3.js +4 -4
  4. package/dist/_virtual/index5.js +2 -3
  5. package/dist/_virtual/index5.js.map +1 -1
  6. package/dist/_virtual/index6.js +3 -2
  7. package/dist/_virtual/index6.js.map +1 -1
  8. package/dist/_virtual/index7.js +2 -2
  9. package/dist/_virtual/index8.js +2 -2
  10. package/dist/_virtual/index9.js +2 -2
  11. package/dist/components/commerce/product-card.js +195 -199
  12. package/dist/components/commerce/product-card.js.map +1 -1
  13. package/dist/components/ui/badge.js +21 -19
  14. package/dist/components/ui/badge.js.map +1 -1
  15. package/dist/hooks/user/useGenerateUserToken.js +12 -0
  16. package/dist/hooks/user/useGenerateUserToken.js.map +1 -0
  17. package/dist/index.js +254 -241
  18. package/dist/index.js.map +1 -1
  19. package/dist/mocks.js +16 -8
  20. package/dist/mocks.js.map +1 -1
  21. package/dist/shop-minis-platform/src/types/user.js +6 -0
  22. package/dist/shop-minis-platform/src/types/user.js.map +1 -0
  23. package/dist/shop-minis-react/node_modules/.pnpm/@radix-ui_react-use-is-hydrated@0.1.0_@types_react@19.1.6_react@19.1.0/node_modules/@radix-ui/react-use-is-hydrated/dist/index.js +1 -1
  24. package/dist/shop-minis-react/node_modules/.pnpm/@videojs_xhr@2.7.0/node_modules/@videojs/xhr/lib/index.js +1 -1
  25. package/dist/shop-minis-react/node_modules/.pnpm/@xmldom_xmldom@0.8.10/node_modules/@xmldom/xmldom/lib/index.js +1 -1
  26. package/dist/shop-minis-react/node_modules/.pnpm/color-string@1.9.1/node_modules/color-string/index.js +1 -1
  27. package/dist/shop-minis-react/node_modules/.pnpm/mpd-parser@1.3.1/node_modules/mpd-parser/dist/mpd-parser.es.js +1 -1
  28. package/dist/shop-minis-react/node_modules/.pnpm/use-sync-external-store@1.5.0_react@19.1.0/node_modules/use-sync-external-store/shim/index.js +1 -1
  29. package/dist/shop-minis-react/node_modules/.pnpm/video.js@8.23.3/node_modules/video.js/dist/video.es.js +1 -1
  30. package/dist/utils/colors.js +1 -1
  31. package/package.json +2 -2
  32. package/src/components/commerce/product-card.tsx +211 -182
  33. package/src/components/ui/badge.tsx +7 -9
  34. package/src/hooks/index.ts +1 -0
  35. package/src/hooks/user/useGenerateUserToken.ts +25 -0
  36. package/src/mocks.ts +11 -4
@@ -1,9 +1,7 @@
1
1
  import * as React from 'react'
2
- import {useCallback} from 'react'
2
+ import {useCallback, useContext, useMemo, useState} from 'react'
3
3
 
4
4
  import {type Product, type ProductVariant} from '@shopify/shop-minis-platform'
5
- import {cva, type VariantProps} from 'class-variance-authority'
6
- import {Slot as SlotPrimitive} from 'radix-ui'
7
5
 
8
6
  import {useShopNavigation} from '../../hooks/navigation/useShopNavigation'
9
7
  import {useSavedProductsActions} from '../../hooks/user/useSavedProductsActions'
@@ -15,62 +13,61 @@ import {ThumbhashImage} from '../atoms/thumbhash-image'
15
13
  import {Touchable} from '../atoms/touchable'
16
14
  import {Badge} from '../ui/badge'
17
15
 
18
- const productCardVariants = cva(
19
- 'relative w-full overflow-hidden rounded-xl border border-gray-200',
20
- {
21
- variants: {
22
- variant: {
23
- default: '',
24
- priceOverlay: '',
25
- compact: '',
26
- },
27
- touchable: {
28
- true: 'cursor-pointer',
29
- false: '',
30
- },
31
- },
32
- defaultVariants: {
33
- variant: 'default',
34
- touchable: true,
35
- },
36
- }
37
- )
16
+ // Context definition
17
+ interface ProductCardContextValue {
18
+ // Core data
19
+ product: Product
20
+ selectedProductVariant?: ProductVariant
38
21
 
39
- // Primitive components (building blocks)
40
- export interface ProductCardRootProps
41
- extends React.ComponentProps<'div'>,
42
- VariantProps<typeof productCardVariants> {
43
- variant?: 'default' | 'priceOverlay' | 'compact'
44
- touchable?: boolean
45
- asChild?: boolean
46
- onPress?: () => void
22
+ // UI configuration
23
+ variant: 'default' | 'priceOverlay' | 'compact'
24
+ touchable: boolean
25
+ badgeText?: string
26
+ badgeVariant?: 'primary' | 'secondary' | 'destructive' | 'outline' | 'none'
27
+
28
+ // State
29
+ isFavorited: boolean
30
+
31
+ // Actions
32
+ onClick: () => void
33
+ onFavoriteToggle: () => void
47
34
  }
48
35
 
49
- function ProductCardRoot({
36
+ const ProductCardContext = React.createContext<
37
+ ProductCardContextValue | undefined
38
+ >(undefined)
39
+
40
+ function useProductCardContext() {
41
+ const context = useContext(ProductCardContext)
42
+ if (!context) {
43
+ throw new Error(
44
+ 'ProductCard components must be used within a ProductCard provider'
45
+ )
46
+ }
47
+ return context
48
+ }
49
+
50
+ // Primitive components (building blocks)
51
+ function ProductCardContainer({
50
52
  className,
51
- variant,
52
- touchable = true,
53
- asChild = false,
54
- onPress,
55
53
  ...props
56
- }: ProductCardRootProps) {
57
- const Comp = asChild ? SlotPrimitive.Slot : 'div'
54
+ }: React.ComponentProps<'div'>) {
55
+ const {touchable, onClick} = useProductCardContext()
58
56
 
59
57
  const content = (
60
- <Comp
58
+ <div
61
59
  className={cn(
62
- productCardVariants({variant, touchable}),
63
- 'border-0',
60
+ 'relative w-full overflow-hidden rounded-xl border-0',
64
61
  className
65
62
  )}
66
63
  {...props}
67
64
  />
68
65
  )
69
66
 
70
- if (touchable && onPress) {
67
+ if (touchable && onClick) {
71
68
  return (
72
69
  <Touchable
73
- onClick={onPress}
70
+ onClick={onClick}
74
71
  whileTap={{opacity: 0.7}}
75
72
  transition={{
76
73
  opacity: {type: 'tween', duration: 0.08, ease: 'easeInOut'},
@@ -86,11 +83,10 @@ function ProductCardRoot({
86
83
 
87
84
  function ProductCardImageContainer({
88
85
  className,
89
- variant = 'default',
90
86
  ...props
91
- }: React.ComponentProps<'div'> & {
92
- variant?: 'default' | 'priceOverlay' | 'compact'
93
- }) {
87
+ }: React.ComponentProps<'div'>) {
88
+ const {variant} = useProductCardContext()
89
+
94
90
  return (
95
91
  <div
96
92
  data-slot="product-card-image-container"
@@ -107,36 +103,28 @@ function ProductCardImageContainer({
107
103
  )
108
104
  }
109
105
 
110
- function ProductCardImage({
111
- className,
112
- src,
113
- alt,
114
- aspectRatio,
115
- thumbhash,
116
- ...props
117
- }: React.ComponentProps<'img'> & {
118
- src?: string
119
- alt?: string
120
- aspectRatio?: number
121
- thumbhash?: string
122
- }) {
106
+ function ProductCardImage({className, ...props}: React.ComponentProps<'img'>) {
107
+ const {product, selectedProductVariant} = useProductCardContext()
108
+
109
+ // Derive display image locally
110
+ const displayImage = selectedProductVariant?.image || product.featuredImage
111
+ const src = displayImage?.url
112
+ const alt = displayImage?.altText || product.title
113
+ const thumbhash = product.featuredImage?.thumbhash
114
+
123
115
  const renderImageElement = useCallback(
124
116
  (src: string) => {
125
- if (thumbhash) {
126
- return (
127
- <ThumbhashImage
128
- data-slot="product-card-image"
129
- src={src}
130
- alt={alt}
131
- aspectRatio={aspectRatio}
132
- thumbhash={thumbhash}
133
- className={cn('w-full h-full object-cover', className)}
134
- {...props}
135
- />
136
- )
137
- }
138
-
139
- return (
117
+ const imageElement = thumbhash ? (
118
+ <ThumbhashImage
119
+ data-slot="product-card-image"
120
+ src={src}
121
+ alt={alt}
122
+ aspectRatio={1}
123
+ thumbhash={thumbhash}
124
+ className={cn('w-full h-full object-cover', className)}
125
+ {...props}
126
+ />
127
+ ) : (
140
128
  <img
141
129
  data-slot="product-card-image"
142
130
  src={src}
@@ -145,8 +133,10 @@ function ProductCardImage({
145
133
  {...props}
146
134
  />
147
135
  )
136
+
137
+ return imageElement
148
138
  },
149
- [alt, aspectRatio, className, props, thumbhash]
139
+ [alt, className, props, thumbhash]
150
140
  )
151
141
 
152
142
  return (
@@ -163,11 +153,18 @@ function ProductCardImage({
163
153
  function ProductCardBadge({
164
154
  className,
165
155
  position = 'bottom-left',
156
+ variant,
166
157
  children,
167
158
  ...props
168
159
  }: React.ComponentProps<typeof Badge> & {
169
160
  position?: 'top-left' | 'bottom-left'
170
161
  }) {
162
+ const {badgeText, badgeVariant} = useProductCardContext()
163
+ // If no children provided, use badgeText from context
164
+ const content = children || badgeText
165
+
166
+ if (!content) return null
167
+
171
168
  return (
172
169
  <div
173
170
  className={cn(
@@ -176,10 +173,17 @@ function ProductCardBadge({
176
173
  )}
177
174
  >
178
175
  <Badge
179
- className={cn('bg-black/50 text-white rounded', className)}
176
+ variant={variant ?? badgeVariant ?? 'none'}
177
+ className={cn(
178
+ !badgeVariant &&
179
+ !variant &&
180
+ 'bg-black/50 text-white border-transparent',
181
+ 'rounded',
182
+ className
183
+ )}
180
184
  {...props}
181
185
  >
182
- {children}
186
+ {content}
183
187
  </Badge>
184
188
  </div>
185
189
  )
@@ -187,27 +191,18 @@ function ProductCardBadge({
187
191
 
188
192
  function ProductCardFavoriteButton({
189
193
  className,
190
- onPress,
191
- filled = false,
192
194
  ...props
193
- }: React.ComponentProps<'div'> & {
194
- onPress?: () => void
195
- filled?: boolean
196
- }) {
195
+ }: React.ComponentProps<'div'>) {
196
+ const {isFavorited, onFavoriteToggle} = useProductCardContext()
197
197
  return (
198
198
  <div className={cn('absolute bottom-3 right-3 z-10', className)} {...props}>
199
- <FavoriteButton onClick={onPress} filled={filled} />
199
+ <FavoriteButton onClick={onFavoriteToggle} filled={isFavorited} />
200
200
  </div>
201
201
  )
202
202
  }
203
203
 
204
- function ProductCardInfo({
205
- className,
206
- variant = 'default',
207
- ...props
208
- }: React.ComponentProps<'div'> & {
209
- variant?: 'default' | 'priceOverlay' | 'compact'
210
- }) {
204
+ function ProductCardInfo({className, ...props}: React.ComponentProps<'div'>) {
205
+ const {variant} = useProductCardContext()
211
206
  if (variant !== 'default') {
212
207
  return null
213
208
  }
@@ -226,6 +221,7 @@ function ProductCardTitle({
226
221
  children,
227
222
  ...props
228
223
  }: React.ComponentProps<'h3'>) {
224
+ const {product} = useProductCardContext()
229
225
  return (
230
226
  <h3
231
227
  data-slot="product-card-title"
@@ -236,11 +232,46 @@ function ProductCardTitle({
236
232
  )}
237
233
  {...props}
238
234
  >
239
- {children}
235
+ {children || product.title}
240
236
  </h3>
241
237
  )
242
238
  }
243
239
 
240
+ function ProductCardPrice({className}: {className?: string}) {
241
+ const {product, selectedProductVariant} = useProductCardContext()
242
+
243
+ // Derive price data locally
244
+ const displayPrice = selectedProductVariant?.price || product?.price
245
+ const displayCompareAtPrice =
246
+ selectedProductVariant?.compareAtPrice || product?.compareAtPrice
247
+
248
+ return (
249
+ <ProductVariantPrice
250
+ amount={displayPrice?.amount || ''}
251
+ currencyCode={displayPrice?.currencyCode || ''}
252
+ compareAtPriceAmount={displayCompareAtPrice?.amount}
253
+ compareAtPriceCurrencyCode={displayCompareAtPrice?.currencyCode}
254
+ className={className}
255
+ />
256
+ )
257
+ }
258
+
259
+ // Special PriceOverlayBadge for price overlay variant
260
+ function ProductCardPriceOverlayBadge() {
261
+ const {product, selectedProductVariant, variant} = useProductCardContext()
262
+ if (variant !== 'priceOverlay') return null
263
+ const displayPrice = selectedProductVariant?.price || product.price
264
+ const currencyCode = displayPrice?.currencyCode
265
+ const amount = displayPrice?.amount
266
+
267
+ if (!currencyCode || !amount) return null
268
+ return (
269
+ <ProductCardBadge position="top-left">
270
+ {formatMoney(amount, currencyCode)}
271
+ </ProductCardBadge>
272
+ )
273
+ }
274
+
244
275
  export interface ProductCardProps {
245
276
  /** The product to display in the card */
246
277
  product: Product
@@ -253,61 +284,43 @@ export interface ProductCardProps {
253
284
  /** Optional text to display in a badge on the card */
254
285
  badgeText?: string
255
286
  /** Visual style variant for the badge */
256
- badgeVariant?: 'default' | 'secondary' | 'destructive' | 'outline'
287
+ badgeVariant?: 'primary' | 'secondary' | 'destructive' | 'outline' | 'none'
288
+ /** Callback fired when the product is clicked */
289
+ onProductClick?: () => void
257
290
  /** Callback fired when the favorite button is toggled */
258
291
  onFavoriteToggled?: (isFavorited: boolean) => void
259
- /** Optional ID for the section containing this card */
260
- sectionId?: string
292
+ /** Custom layout via children */
293
+ children?: React.ReactNode
261
294
  }
262
295
 
263
- // Composed ProductCard component
264
296
  function ProductCard({
265
297
  product,
266
298
  selectedProductVariant,
267
299
  variant = 'default',
268
300
  touchable = true,
269
301
  badgeText,
270
- badgeVariant = 'secondary',
302
+ badgeVariant,
303
+ onProductClick,
271
304
  onFavoriteToggled,
305
+ children,
272
306
  }: ProductCardProps) {
273
307
  const {navigateToProduct} = useShopNavigation()
274
308
  const {saveProduct, unsaveProduct} = useSavedProductsActions()
275
309
 
276
- const {
277
- id,
278
- title,
279
- featuredImage,
280
- price,
281
- compareAtPrice,
282
- isFavorited,
283
- defaultVariantId,
284
- shop,
285
- } = product
286
-
287
- // Use selected variant data if available
288
- const displayImage = selectedProductVariant?.image || featuredImage
289
- const displayPrice = selectedProductVariant?.price || price
290
- const displayCompareAtPrice =
291
- selectedProductVariant?.compareAtPrice || compareAtPrice
292
-
293
310
  // Local state for optimistic UI updates
294
- const [isFavoritedLocal, setIsFavoritedLocal] = React.useState(isFavorited)
295
-
296
- const currencyCode = displayPrice?.currencyCode
297
- const amount = displayPrice?.amount
298
- const imageUrl = displayImage?.url
299
- const imageAltText = displayImage?.altText || title
300
- const compareAtPriceAmount = displayCompareAtPrice?.amount
311
+ const [isFavoritedLocal, setIsFavoritedLocal] = useState(product.isFavorited)
301
312
 
302
- const handlePress = React.useCallback(() => {
313
+ const handleClick = useCallback(() => {
303
314
  if (!touchable) return
304
315
 
316
+ onProductClick?.()
317
+
305
318
  navigateToProduct({
306
- productId: id,
319
+ productId: product.id,
307
320
  })
308
- }, [navigateToProduct, id, touchable])
321
+ }, [navigateToProduct, product.id, touchable, onProductClick])
309
322
 
310
- const handleFavoritePress = React.useCallback(async () => {
323
+ const handleFavoriteClick = useCallback(async () => {
311
324
  const previousState = isFavoritedLocal
312
325
 
313
326
  // Optimistic update
@@ -317,15 +330,17 @@ function ProductCard({
317
330
  try {
318
331
  if (previousState) {
319
332
  await unsaveProduct({
320
- productId: id,
321
- shopId: shop.id,
322
- productVariantId: selectedProductVariant?.id || defaultVariantId,
333
+ productId: product.id,
334
+ shopId: product.shop.id,
335
+ productVariantId:
336
+ selectedProductVariant?.id || product.defaultVariantId,
323
337
  })
324
338
  } else {
325
339
  await saveProduct({
326
- productId: id,
327
- shopId: shop.id,
328
- productVariantId: selectedProductVariant?.id || defaultVariantId,
340
+ productId: product.id,
341
+ shopId: product.shop.id,
342
+ productVariantId:
343
+ selectedProductVariant?.id || product.defaultVariantId,
329
344
  })
330
345
  }
331
346
  } catch (error) {
@@ -335,63 +350,77 @@ function ProductCard({
335
350
  }
336
351
  }, [
337
352
  isFavoritedLocal,
338
- id,
339
- shop.id,
353
+ product.id,
354
+ product.shop.id,
355
+ product.defaultVariantId,
340
356
  selectedProductVariant?.id,
341
- defaultVariantId,
342
357
  saveProduct,
343
358
  unsaveProduct,
344
359
  onFavoriteToggled,
345
360
  ])
346
361
 
347
- return (
348
- <ProductCardRoot
349
- variant={variant}
350
- touchable={touchable}
351
- onPress={handlePress}
352
- >
353
- <ProductCardImageContainer variant={variant}>
354
- <ProductCardImage
355
- src={imageUrl}
356
- alt={imageAltText}
357
- aspectRatio={1}
358
- thumbhash={featuredImage?.thumbhash ?? undefined}
359
- />
360
-
361
- {/* Price overlay badge for priceOverlay variant */}
362
- {variant === 'priceOverlay' && currencyCode && amount && (
363
- <ProductCardBadge position="top-left">
364
- {formatMoney(amount, currencyCode)}
365
- </ProductCardBadge>
366
- )}
367
-
368
- {/* Custom badge */}
369
- {badgeText && (
370
- <ProductCardBadge position="bottom-left" variant={badgeVariant}>
371
- {badgeText}
372
- </ProductCardBadge>
373
- )}
374
-
375
- {/* Favorite button */}
376
- <ProductCardFavoriteButton
377
- filled={isFavoritedLocal}
378
- onPress={handleFavoritePress}
379
- />
380
- </ProductCardImageContainer>
381
-
382
- {/* Product info for default variant */}
383
- <ProductCardInfo variant={variant}>
384
- <ProductCardTitle>{title}</ProductCardTitle>
362
+ const contextValue = useMemo<ProductCardContextValue>(
363
+ () => ({
364
+ // Core data
365
+ product,
366
+ selectedProductVariant,
367
+
368
+ // UI configuration
369
+ variant,
370
+ touchable,
371
+ badgeText,
372
+ badgeVariant,
373
+
374
+ // State
375
+ isFavorited: isFavoritedLocal,
376
+
377
+ // Actions
378
+ onClick: handleClick,
379
+ onFavoriteToggle: handleFavoriteClick,
380
+ }),
381
+ [
382
+ product,
383
+ selectedProductVariant,
384
+ variant,
385
+ touchable,
386
+ badgeText,
387
+ badgeVariant,
388
+ isFavoritedLocal,
389
+ handleClick,
390
+ handleFavoriteClick,
391
+ ]
392
+ )
385
393
 
386
- <ProductVariantPrice
387
- amount={amount}
388
- currencyCode={currencyCode}
389
- compareAtPriceAmount={compareAtPriceAmount}
390
- compareAtPriceCurrencyCode={displayCompareAtPrice?.currencyCode}
391
- />
392
- </ProductCardInfo>
393
- </ProductCardRoot>
394
+ return (
395
+ <ProductCardContext.Provider value={contextValue}>
396
+ {children ?? (
397
+ <ProductCardContainer>
398
+ <ProductCardImageContainer>
399
+ <ProductCardImage />
400
+ {variant === 'priceOverlay' && <ProductCardPriceOverlayBadge />}
401
+ <ProductCardBadge />
402
+ <ProductCardFavoriteButton />
403
+ </ProductCardImageContainer>
404
+ {variant === 'default' && (
405
+ <ProductCardInfo>
406
+ <ProductCardTitle />
407
+ <ProductCardPrice />
408
+ </ProductCardInfo>
409
+ )}
410
+ </ProductCardContainer>
411
+ )}
412
+ </ProductCardContext.Provider>
394
413
  )
395
414
  }
396
415
 
397
- export {ProductCard}
416
+ export {
417
+ ProductCard,
418
+ ProductCardContainer,
419
+ ProductCardImageContainer,
420
+ ProductCardImage,
421
+ ProductCardBadge,
422
+ ProductCardFavoriteButton,
423
+ ProductCardInfo,
424
+ ProductCardTitle,
425
+ ProductCardPrice,
426
+ }
@@ -6,22 +6,20 @@ import {Slot as SlotPrimitive} from 'radix-ui'
6
6
  import {cn} from '../../lib/utils'
7
7
 
8
8
  const badgeVariants = cva(
9
- 'inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden',
9
+ 'inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none transition-[color,box-shadow] overflow-hidden',
10
10
  {
11
11
  variants: {
12
12
  variant: {
13
- default:
14
- 'border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90',
15
- secondary:
16
- 'border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90',
13
+ primary: 'border-transparent bg-primary text-primary-foreground',
14
+ secondary: 'border-transparent bg-secondary text-secondary-foreground',
17
15
  destructive:
18
- 'border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60',
19
- outline:
20
- 'text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground',
16
+ 'border-transparent bg-destructive text-white dark:bg-destructive/60',
17
+ outline: 'bg-white text-foreground',
18
+ none: '', // Allows custom classes
21
19
  },
22
20
  },
23
21
  defaultVariants: {
24
- variant: 'default',
22
+ variant: 'primary',
25
23
  },
26
24
  }
27
25
  )
@@ -8,6 +8,7 @@ export * from './user/useFollowedShopsActions'
8
8
  export * from './user/useCurrentUser'
9
9
  export * from './user/useOrders'
10
10
  export * from './user/useBuyerAttributes'
11
+ export * from './user/useGenerateUserToken'
11
12
 
12
13
  // - Product Hooks
13
14
  export * from './product/useProductListActions'
@@ -0,0 +1,25 @@
1
+ import {
2
+ GeneratedTokenData,
3
+ UserTokenGenerateUserErrors,
4
+ } from '@shopify/shop-minis-platform'
5
+
6
+ import {useHandleAction} from '../../internal/useHandleAction'
7
+ import {useShopActions} from '../../internal/useShopActions'
8
+
9
+ interface UseGenerateUserTokenReturns {
10
+ /**
11
+ * Generates a temporary token for the user.
12
+ */
13
+ generateUserToken: () => Promise<{
14
+ data: GeneratedTokenData
15
+ userErrors?: UserTokenGenerateUserErrors[]
16
+ }>
17
+ }
18
+
19
+ export function useGenerateUserToken(): UseGenerateUserTokenReturns {
20
+ const {generateUserToken} = useShopActions()
21
+
22
+ return {
23
+ generateUserToken: useHandleAction(generateUserToken),
24
+ }
25
+ }
package/src/mocks.ts CHANGED
@@ -1,4 +1,4 @@
1
- import {Product, Gender} from '@shopify/shop-minis-platform'
1
+ import {Product, Gender, UserState} from '@shopify/shop-minis-platform'
2
2
  import {ShopActions} from '@shopify/shop-minis-platform/actions'
3
3
 
4
4
  // Helper functions for common data structures
@@ -437,6 +437,13 @@ function makeMockActions(): ShopActions {
437
437
  },
438
438
  ],
439
439
  },
440
+ generateUserToken: {
441
+ data: {
442
+ token: 'user-token-123',
443
+ expiresAt: '2025-01-01',
444
+ userState: UserState.VERIFIED,
445
+ },
446
+ },
440
447
  } as const
441
448
 
442
449
  const mock: Partial<ShopActions> = {}
@@ -461,9 +468,9 @@ const isMobile = (): boolean => {
461
468
  return isIOS || isAndroid
462
469
  }
463
470
 
464
- export const injectMocks = () => {
465
- // Only inject mocks if we aren't on a mobile device
466
- if (isMobile()) {
471
+ export const injectMocks = ({force}: {force?: boolean} = {}) => {
472
+ // Only inject mocks if we aren't on a mobile device or we force it
473
+ if (isMobile() && !force) {
467
474
  return
468
475
  }
469
476