@wix/headless-gift-voucher 0.0.1

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 (69) hide show
  1. package/cjs/dist/__mocks__/gift-card.d.ts +14 -0
  2. package/cjs/dist/__mocks__/gift-card.d.ts.map +1 -0
  3. package/cjs/dist/__mocks__/gift-card.js +49 -0
  4. package/cjs/dist/enums/index.d.ts +52 -0
  5. package/cjs/dist/enums/index.d.ts.map +1 -0
  6. package/cjs/dist/enums/index.js +52 -0
  7. package/cjs/dist/react/GiftCard.d.ts +1318 -0
  8. package/cjs/dist/react/GiftCard.d.ts.map +1 -0
  9. package/cjs/dist/react/GiftCard.js +1126 -0
  10. package/cjs/dist/react/core/GiftCard.d.ts +863 -0
  11. package/cjs/dist/react/core/GiftCard.d.ts.map +1 -0
  12. package/cjs/dist/react/core/GiftCard.js +737 -0
  13. package/cjs/dist/react/index.d.ts +2 -0
  14. package/cjs/dist/react/index.d.ts.map +1 -0
  15. package/cjs/dist/react/index.js +1 -0
  16. package/cjs/dist/services/gift-card-checkout-service.d.ts +133 -0
  17. package/cjs/dist/services/gift-card-checkout-service.d.ts.map +1 -0
  18. package/cjs/dist/services/gift-card-checkout-service.js +159 -0
  19. package/cjs/dist/services/gift-card-service.d.ts +102 -0
  20. package/cjs/dist/services/gift-card-service.d.ts.map +1 -0
  21. package/cjs/dist/services/gift-card-service.js +165 -0
  22. package/cjs/dist/services/index.d.ts +3 -0
  23. package/cjs/dist/services/index.d.ts.map +1 -0
  24. package/cjs/dist/services/index.js +2 -0
  25. package/cjs/dist/utils/formatting-utils.d.ts +13 -0
  26. package/cjs/dist/utils/formatting-utils.d.ts.map +1 -0
  27. package/cjs/dist/utils/formatting-utils.js +38 -0
  28. package/cjs/dist/utils/gift-card-utils.d.ts +18 -0
  29. package/cjs/dist/utils/gift-card-utils.d.ts.map +1 -0
  30. package/cjs/dist/utils/gift-card-utils.js +118 -0
  31. package/cjs/dist/utils/validation-utils.d.ts +10 -0
  32. package/cjs/dist/utils/validation-utils.d.ts.map +1 -0
  33. package/cjs/dist/utils/validation-utils.js +24 -0
  34. package/dist/__mocks__/gift-card.d.ts +14 -0
  35. package/dist/__mocks__/gift-card.d.ts.map +1 -0
  36. package/dist/__mocks__/gift-card.js +49 -0
  37. package/dist/enums/index.d.ts +52 -0
  38. package/dist/enums/index.d.ts.map +1 -0
  39. package/dist/enums/index.js +52 -0
  40. package/dist/react/GiftCard.d.ts +1318 -0
  41. package/dist/react/GiftCard.d.ts.map +1 -0
  42. package/dist/react/GiftCard.js +1126 -0
  43. package/dist/react/core/GiftCard.d.ts +863 -0
  44. package/dist/react/core/GiftCard.d.ts.map +1 -0
  45. package/dist/react/core/GiftCard.js +737 -0
  46. package/dist/react/index.d.ts +2 -0
  47. package/dist/react/index.d.ts.map +1 -0
  48. package/dist/react/index.js +1 -0
  49. package/dist/services/gift-card-checkout-service.d.ts +133 -0
  50. package/dist/services/gift-card-checkout-service.d.ts.map +1 -0
  51. package/dist/services/gift-card-checkout-service.js +159 -0
  52. package/dist/services/gift-card-service.d.ts +102 -0
  53. package/dist/services/gift-card-service.d.ts.map +1 -0
  54. package/dist/services/gift-card-service.js +165 -0
  55. package/dist/services/index.d.ts +3 -0
  56. package/dist/services/index.d.ts.map +1 -0
  57. package/dist/services/index.js +2 -0
  58. package/dist/utils/formatting-utils.d.ts +13 -0
  59. package/dist/utils/formatting-utils.d.ts.map +1 -0
  60. package/dist/utils/formatting-utils.js +38 -0
  61. package/dist/utils/gift-card-utils.d.ts +18 -0
  62. package/dist/utils/gift-card-utils.d.ts.map +1 -0
  63. package/dist/utils/gift-card-utils.js +118 -0
  64. package/dist/utils/validation-utils.d.ts +10 -0
  65. package/dist/utils/validation-utils.d.ts.map +1 -0
  66. package/dist/utils/validation-utils.js +24 -0
  67. package/package.json +72 -0
  68. package/react/package.json +4 -0
  69. package/services/package.json +4 -0
@@ -0,0 +1,737 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useService, WixServices } from '@wix/services-manager-react';
3
+ import { createServicesMap } from '@wix/services-manager';
4
+ import { GiftCardServiceDefinition, GiftCardService, } from '../../services/gift-card-service.js';
5
+ import { GiftCardCheckoutServiceDefinition, GiftCardCheckoutService, } from '../../services/gift-card-checkout-service.js';
6
+ import { CUSTOM_VARIANT_ID } from '../../enums/index.js';
7
+ import { getCurrencySymbol, formatDate, formatDateForInput, } from '../../utils/formatting-utils.js';
8
+ /**
9
+ * Root component that provides the GiftCard service context to its children.
10
+ * Supports both SSR (with product in config) and client-side auto-fetch (empty config).
11
+ *
12
+ * @component
13
+ * @example
14
+ * ```tsx
15
+ * // With SSR config
16
+ * <GiftCard.Root giftCardServiceConfig={{ product: myProduct }}>
17
+ * <GiftCard.Name />
18
+ * </GiftCard.Root>
19
+ *
20
+ * // Without config (auto-fetch)
21
+ * <GiftCard.Root giftCardServiceConfig={{}}>
22
+ * <GiftCard.Name />
23
+ * </GiftCard.Root>
24
+ * ```
25
+ */
26
+ export function Root(props) {
27
+ return (_jsx(WixServices, { servicesMap: createServicesMap()
28
+ .addService(GiftCardServiceDefinition, GiftCardService, props.giftCardServiceConfig)
29
+ .addService(GiftCardCheckoutServiceDefinition, GiftCardCheckoutService, {}), children: props.children }));
30
+ }
31
+ /**
32
+ * Headless component for displaying the gift card product name
33
+ *
34
+ * @component
35
+ * @example
36
+ * ```tsx
37
+ * <GiftCard.Name>
38
+ * {({ name }) => (
39
+ * <h1>{name}</h1>
40
+ * )}
41
+ * </GiftCard.Name>
42
+ * ```
43
+ */
44
+ export function Name(props) {
45
+ const service = useService(GiftCardServiceDefinition);
46
+ const product = service.product.get();
47
+ const name = product?.name ?? '';
48
+ return props.children({
49
+ name,
50
+ });
51
+ }
52
+ /**
53
+ * Headless component for handling loading state
54
+ *
55
+ * @component
56
+ * @example
57
+ * ```tsx
58
+ * <GiftCard.Loading>
59
+ * {({ isLoading }) => isLoading ? <Spinner /> : null}
60
+ * </GiftCard.Loading>
61
+ * ```
62
+ */
63
+ export function Loading(props) {
64
+ const service = useService(GiftCardServiceDefinition);
65
+ const isLoading = service.isLoading.get();
66
+ return props.children({
67
+ isLoading,
68
+ });
69
+ }
70
+ /**
71
+ * Headless component for handling error state
72
+ *
73
+ * @component
74
+ * @example
75
+ * ```tsx
76
+ * <GiftCard.Error>
77
+ * {({ error }) => error ? <div className="error">{error}</div> : null}
78
+ * </GiftCard.Error>
79
+ * ```
80
+ */
81
+ export function Error(props) {
82
+ const service = useService(GiftCardServiceDefinition);
83
+ const error = service.error.get();
84
+ return props.children({
85
+ error,
86
+ });
87
+ }
88
+ /**
89
+ * Headless component that exposes the gift card data via render props.
90
+ * Provides access to the gift card product, loading state, and error state.
91
+ *
92
+ * @component
93
+ * @example
94
+ * ```tsx
95
+ * <GiftCard.Raw>
96
+ * {({ giftCard, isLoading, error }) => {
97
+ * if (isLoading) return <div>Loading...</div>;
98
+ * if (error) return <div>Error: {error}</div>;
99
+ * return <div>{giftCard.name}</div>;
100
+ * }}
101
+ * </GiftCard.Raw>
102
+ * ```
103
+ */
104
+ export function Raw(props) {
105
+ const service = useService(GiftCardServiceDefinition);
106
+ const isLoading = service.isLoading.get();
107
+ const error = service.error.get();
108
+ const giftCard = service.product.get();
109
+ return props.children({
110
+ giftCard: giftCard ?? null,
111
+ isLoading,
112
+ error,
113
+ });
114
+ }
115
+ /**
116
+ * Headless component for displaying the gift card image.
117
+ * The image automatically updates when selecting a variant that has its own image defined.
118
+ * Returns null if no image is available.
119
+ *
120
+ * Behavior:
121
+ * - Shows the selected preset variant's image if one is defined
122
+ * - Falls back to the default product image otherwise (including when custom amount is selected)
123
+ *
124
+ * @component
125
+ * @example
126
+ * ```tsx
127
+ * <GiftCard.Image>
128
+ * {({ image }) => (
129
+ * <img src={image} alt="Gift Card" />
130
+ * )}
131
+ * </GiftCard.Image>
132
+ * ```
133
+ */
134
+ export function Image(props) {
135
+ const checkoutService = useService(GiftCardCheckoutServiceDefinition);
136
+ const currentImage = checkoutService.currentImage.get();
137
+ // Return null if no image available
138
+ if (!currentImage) {
139
+ return null;
140
+ }
141
+ return props.children({
142
+ image: currentImage,
143
+ });
144
+ }
145
+ /**
146
+ * Headless component for displaying the gift card product description.
147
+ *
148
+ * @component
149
+ * @example
150
+ * ```tsx
151
+ * <GiftCard.Description>
152
+ * {({ description }) => (
153
+ * <p>{description}</p>
154
+ * )}
155
+ * </GiftCard.Description>
156
+ * ```
157
+ */
158
+ export function Description(props) {
159
+ const service = useService(GiftCardServiceDefinition);
160
+ const product = service.product.get();
161
+ const description = product?.description ?? '';
162
+ return props.children({
163
+ description,
164
+ });
165
+ }
166
+ /**
167
+ * Headless component that provides the list of preset variants.
168
+ * Use this as a container to check for empty state before rendering the repeater.
169
+ *
170
+ * @component
171
+ * @example
172
+ * ```tsx
173
+ * <GiftCard.PresetVariants>
174
+ * {({ presetVariants }) => (
175
+ * presetVariants.length > 0 ? (
176
+ * <div className="flex gap-2">
177
+ * {presetVariants.map(variant => (
178
+ * <button key={variant.id} onClick={variant.select}>
179
+ * <Money money={{ amount: variant.value, currency: variant.currencyCode }} />
180
+ * </button>
181
+ * ))}
182
+ * </div>
183
+ * ) : (
184
+ * <div>No variants available</div>
185
+ * )
186
+ * )}
187
+ * </GiftCard.PresetVariants>
188
+ * ```
189
+ */
190
+ export function PresetVariants(props) {
191
+ const giftCardService = useService(GiftCardServiceDefinition);
192
+ const checkoutService = useService(GiftCardCheckoutServiceDefinition);
193
+ const product = giftCardService.product.get();
194
+ const selectedId = checkoutService.selectedVariantId.get();
195
+ const currencyCode = giftCardService.currencyCode.get();
196
+ const locale = giftCardService.locale.get();
197
+ const presetVariants = (product?.presetVariants ?? []).map((variant) => {
198
+ const variantId = variant._id ?? '';
199
+ const price = parseFloat(variant.price?.amount ?? '0');
200
+ const value = parseFloat(variant.value?.amount ?? variant.price?.amount ?? '0');
201
+ return {
202
+ id: variantId,
203
+ price,
204
+ value,
205
+ hasDiscount: price < value,
206
+ hasImage: Boolean(variant.image),
207
+ isSelected: selectedId === variantId,
208
+ select: () => checkoutService.setVariant(variantId),
209
+ currencyCode,
210
+ locale,
211
+ };
212
+ });
213
+ return props.children({
214
+ presetVariants,
215
+ });
216
+ }
217
+ /**
218
+ * Headless component that provides preset variants for iteration.
219
+ * Used by the UI layer to create PresetVariant.Root contexts for each variant.
220
+ *
221
+ * @component
222
+ * @example
223
+ * ```tsx
224
+ * <GiftCard.PresetVariantRepeater>
225
+ * {({ presetVariants }) =>
226
+ * presetVariants.map(variant => (
227
+ * <PresetVariant.Root key={variant.id} variant={variant}>
228
+ * <PresetVariant.Amount />
229
+ * </PresetVariant.Root>
230
+ * ))
231
+ * }
232
+ * </GiftCard.PresetVariantRepeater>
233
+ * ```
234
+ */
235
+ export function PresetVariantRepeater(props) {
236
+ const giftCardService = useService(GiftCardServiceDefinition);
237
+ const checkoutService = useService(GiftCardCheckoutServiceDefinition);
238
+ const product = giftCardService.product.get();
239
+ const selectedId = checkoutService.selectedVariantId.get();
240
+ const currencyCode = giftCardService.currencyCode.get();
241
+ const locale = giftCardService.locale.get();
242
+ const presetVariants = (product?.presetVariants ?? []).map((variant) => {
243
+ const variantId = variant._id ?? '';
244
+ const price = parseFloat(variant.price?.amount ?? '0');
245
+ const value = parseFloat(variant.value?.amount ?? variant.price?.amount ?? '0');
246
+ return {
247
+ id: variantId,
248
+ price,
249
+ value,
250
+ hasDiscount: price < value,
251
+ hasImage: Boolean(variant.image),
252
+ isSelected: selectedId === variantId,
253
+ select: () => checkoutService.setVariant(variantId),
254
+ currencyCode,
255
+ locale,
256
+ };
257
+ });
258
+ return props.children({
259
+ presetVariants,
260
+ });
261
+ }
262
+ /**
263
+ * Headless component for displaying the current gift card price based on selected variant.
264
+ * The price automatically updates when the variant selection changes.
265
+ * Use the Money component for formatting the price and value amounts.
266
+ *
267
+ * Price vs Value:
268
+ * - `price` - What the customer pays (raw number)
269
+ * - `value` - What the gift card is worth (raw number)
270
+ * - `hasDiscount` - `true` when `price < value`, indicating a discounted variant
271
+ *
272
+ * @component
273
+ * @example
274
+ * ```tsx
275
+ * <GiftCard.CurrentPrice>
276
+ * {({ price, value, hasDiscount, currencyCode }) => (
277
+ * <div className="flex items-baseline gap-3">
278
+ * {hasDiscount && (
279
+ * <Money money={{ amount: value, currency: currencyCode }} className="line-through text-muted" />
280
+ * )}
281
+ * <Money money={{ amount: price, currency: currencyCode }} className="text-4xl font-bold" />
282
+ * </div>
283
+ * )}
284
+ * </GiftCard.CurrentPrice>
285
+ * ```
286
+ */
287
+ export function CurrentPrice(props) {
288
+ const giftCardService = useService(GiftCardServiceDefinition);
289
+ const checkoutService = useService(GiftCardCheckoutServiceDefinition);
290
+ const currentPriceData = checkoutService.currentPriceData.get();
291
+ const currencyCode = giftCardService.currencyCode.get();
292
+ const locale = giftCardService.locale.get();
293
+ // Return null if no price data available
294
+ if (!currentPriceData) {
295
+ return null;
296
+ }
297
+ return props.children({
298
+ price: currentPriceData.price,
299
+ value: currentPriceData.value,
300
+ hasDiscount: currentPriceData.hasDiscount,
301
+ currencyCode,
302
+ locale,
303
+ });
304
+ }
305
+ /**
306
+ * Headless component for triggering custom amount selection.
307
+ * Only renders when custom variant is enabled for the gift card product.
308
+ *
309
+ * @component
310
+ * @example
311
+ * ```tsx
312
+ * <GiftCard.CustomVariantTrigger>
313
+ * {({ isSelected, onClick }) => (
314
+ * <button
315
+ * onClick={onClick}
316
+ * data-selected={isSelected}
317
+ * className="px-4 py-2 border-2 border-dashed rounded-xl"
318
+ * >
319
+ * Other amount
320
+ * </button>
321
+ * )}
322
+ * </GiftCard.CustomVariantTrigger>
323
+ * ```
324
+ */
325
+ export function CustomVariantTrigger(props) {
326
+ const giftCardService = useService(GiftCardServiceDefinition);
327
+ const checkoutService = useService(GiftCardCheckoutServiceDefinition);
328
+ const product = giftCardService.product.get();
329
+ const selectedVariantId = checkoutService.selectedVariantId.get();
330
+ // Don't render if product doesn't have custom variant enabled
331
+ const hasCustomVariant = Boolean(product?.customVariant);
332
+ if (!hasCustomVariant) {
333
+ return null;
334
+ }
335
+ const isSelected = selectedVariantId === CUSTOM_VARIANT_ID;
336
+ const onClick = () => {
337
+ checkoutService.setVariant(CUSTOM_VARIANT_ID);
338
+ };
339
+ return props.children({
340
+ isSelected,
341
+ onClick,
342
+ });
343
+ }
344
+ /**
345
+ * Headless component for custom gift card amount input.
346
+ * Only renders when custom variant is selected.
347
+ * The custom amount affects the currentPrice value displayed by GiftCard.CurrentPrice.
348
+ *
349
+ * Validation:
350
+ * - Amount must be within min/max range defined by the product's custom variant
351
+ * - isValid will be false if amount is outside the allowed range
352
+ * - showError flag indicates whether validation errors should be displayed (set to true after form submission)
353
+ *
354
+ * @component
355
+ * @example
356
+ * ```tsx
357
+ * <GiftCard.CustomAmountInput>
358
+ * {({ value, setValue, min, max, isValid, showError, isVisible, currencyCode }) =>
359
+ * isVisible && (
360
+ * <div className="space-y-2">
361
+ * <label>Enter amount ({currencyCode})</label>
362
+ * <input
363
+ * type="number"
364
+ * value={value || ''}
365
+ * onChange={(e) => setValue(parseFloat(e.target.value) || 0)}
366
+ * min={min ?? undefined}
367
+ * max={max ?? undefined}
368
+ * data-invalid={showError && !isValid}
369
+ * />
370
+ * {showError && !isValid && min && max && (
371
+ * <p className="text-red-500">
372
+ * Enter an amount between {min} and {max}
373
+ * </p>
374
+ * )}
375
+ * </div>
376
+ * )
377
+ * }
378
+ * </GiftCard.CustomAmountInput>
379
+ * ```
380
+ */
381
+ export function CustomAmountInput(props) {
382
+ const giftCardService = useService(GiftCardServiceDefinition);
383
+ const checkoutService = useService(GiftCardCheckoutServiceDefinition);
384
+ const selectedVariantId = checkoutService.selectedVariantId.get();
385
+ const customAmount = checkoutService.customAmount.get();
386
+ const isCustomAmountValid = checkoutService.isCustomAmountValid.get();
387
+ const customAmountData = checkoutService.customAmountData.get();
388
+ const showErrors = checkoutService.showErrors.get();
389
+ const currencyCode = giftCardService.currencyCode.get();
390
+ const currencySymbol = getCurrencySymbol(currencyCode);
391
+ const isVisible = selectedVariantId === CUSTOM_VARIANT_ID;
392
+ return props.children({
393
+ value: customAmount,
394
+ setValue: checkoutService.setCustomAmount,
395
+ min: customAmountData.min,
396
+ max: customAmountData.max,
397
+ isValid: isCustomAmountValid,
398
+ showError: showErrors,
399
+ isVisible,
400
+ currencyCode,
401
+ currencySymbol,
402
+ });
403
+ }
404
+ /**
405
+ * Headless component for gift card quantity selection.
406
+ * Exposes quantity state and setter to allow building custom quantity UIs.
407
+ *
408
+ * @component
409
+ * @example
410
+ * ```tsx
411
+ * <GiftCard.Quantity>
412
+ * {({ quantity, setQuantity }) => (
413
+ * <div className="flex items-center gap-2">
414
+ * <button onClick={() => setQuantity(quantity - 1)} disabled={quantity <= 1}>-</button>
415
+ * <span>{quantity}</span>
416
+ * <button onClick={() => setQuantity(quantity + 1)}>+</button>
417
+ * </div>
418
+ * )}
419
+ * </GiftCard.Quantity>
420
+ * ```
421
+ */
422
+ export function Quantity(props) {
423
+ const checkoutService = useService(GiftCardCheckoutServiceDefinition);
424
+ const quantity = checkoutService.quantity.get();
425
+ return props.children({
426
+ quantity,
427
+ setQuantity: checkoutService.setQuantity,
428
+ });
429
+ }
430
+ /**
431
+ * Headless component for toggling between gift and self-purchase modes.
432
+ * When isGift is true, the purchase is "for someone else" (shows recipient form).
433
+ * When isGift is false, the purchase is "for myself" (hides recipient form).
434
+ *
435
+ * @component
436
+ * @example
437
+ * ```tsx
438
+ * <GiftCard.GiftToggle>
439
+ * {({ isGift, setIsGift }) => (
440
+ * <div className="flex gap-2">
441
+ * <button
442
+ * onClick={() => setIsGift(true)}
443
+ * data-selected={isGift}
444
+ * >
445
+ * For someone else
446
+ * </button>
447
+ * <button
448
+ * onClick={() => setIsGift(false)}
449
+ * data-selected={!isGift}
450
+ * >
451
+ * For myself
452
+ * </button>
453
+ * </div>
454
+ * )}
455
+ * </GiftCard.GiftToggle>
456
+ * ```
457
+ */
458
+ export function GiftToggle(props) {
459
+ const checkoutService = useService(GiftCardCheckoutServiceDefinition);
460
+ const isGift = checkoutService.isGift.get();
461
+ return props.children({
462
+ isGift,
463
+ setIsGift: checkoutService.setIsGift,
464
+ });
465
+ }
466
+ /**
467
+ * Headless component for recipient email input.
468
+ * Exposes email state, setter, validation state, and error display flag.
469
+ *
470
+ * The `showError` flag is controlled by the checkout service and indicates
471
+ * whether validation errors should be displayed (typically set to true after
472
+ * a form submission attempt).
473
+ *
474
+ * @component
475
+ * @example
476
+ * ```tsx
477
+ * <GiftCard.RecipientEmail>
478
+ * {({ value, setValue, isValid, showError }) => (
479
+ * <div className="space-y-2">
480
+ * <label>Recipient email</label>
481
+ * <input
482
+ * type="email"
483
+ * value={value}
484
+ * onChange={(e) => setValue(e.target.value)}
485
+ * data-invalid={showError && !isValid}
486
+ * />
487
+ * {showError && !isValid && (
488
+ * <p className="text-red-500">Please enter a valid email</p>
489
+ * )}
490
+ * </div>
491
+ * )}
492
+ * </GiftCard.RecipientEmail>
493
+ * ```
494
+ */
495
+ export function RecipientEmail(props) {
496
+ const checkoutService = useService(GiftCardCheckoutServiceDefinition);
497
+ const value = checkoutService.recipientEmail.get();
498
+ const isValid = checkoutService.isRecipientEmailValid.get();
499
+ const showError = checkoutService.showErrors.get();
500
+ const isGift = checkoutService.isGift.get();
501
+ return props.children({
502
+ value,
503
+ setValue: checkoutService.setRecipientEmail,
504
+ isValid,
505
+ showError,
506
+ isVisible: isGift,
507
+ });
508
+ }
509
+ /**
510
+ * Headless component for recipient name input.
511
+ * Exposes name state and setter.
512
+ * Only visible when isGift is true (purchasing for someone else).
513
+ *
514
+ * @component
515
+ * @example
516
+ * ```tsx
517
+ * <GiftCard.RecipientName>
518
+ * {({ value, setValue, isVisible }) =>
519
+ * isVisible && (
520
+ * <div className="space-y-2">
521
+ * <label>Recipient name (optional)</label>
522
+ * <input
523
+ * type="text"
524
+ * value={value}
525
+ * onChange={(e) => setValue(e.target.value)}
526
+ * />
527
+ * </div>
528
+ * )
529
+ * }
530
+ * </GiftCard.RecipientName>
531
+ * ```
532
+ */
533
+ export function RecipientName(props) {
534
+ const checkoutService = useService(GiftCardCheckoutServiceDefinition);
535
+ const value = checkoutService.recipientName.get();
536
+ const isGift = checkoutService.isGift.get();
537
+ return props.children({
538
+ value,
539
+ setValue: checkoutService.setRecipientName,
540
+ isVisible: isGift,
541
+ });
542
+ }
543
+ /**
544
+ * Headless component for recipient delivery date input.
545
+ * Exposes date state, setter, and formatted values.
546
+ * Only visible when isGift is true (purchasing for someone else).
547
+ *
548
+ * @component
549
+ * @example
550
+ * ```tsx
551
+ * <GiftCard.RecipientDate>
552
+ * {({ value, formattedValue, inputValue, setValue, isVisible }) =>
553
+ * isVisible && (
554
+ * <div className="space-y-2">
555
+ * <label>Delivery date</label>
556
+ * <input
557
+ * type="date"
558
+ * value={inputValue}
559
+ * onChange={(e) => setValue(new Date(e.target.value))}
560
+ * />
561
+ * <p>Selected: {formattedValue}</p>
562
+ * </div>
563
+ * )
564
+ * }
565
+ * </GiftCard.RecipientDate>
566
+ * ```
567
+ */
568
+ export function RecipientDate(props) {
569
+ const giftCardService = useService(GiftCardServiceDefinition);
570
+ const checkoutService = useService(GiftCardCheckoutServiceDefinition);
571
+ const value = checkoutService.deliverAt.get();
572
+ const isGift = checkoutService.isGift.get();
573
+ const locale = giftCardService.locale.get();
574
+ return props.children({
575
+ value,
576
+ formattedValue: formatDate(value, locale),
577
+ inputValue: formatDateForInput(value),
578
+ setValue: checkoutService.setDeliverAt,
579
+ isVisible: isGift,
580
+ });
581
+ }
582
+ /**
583
+ * Headless component for recipient message textarea.
584
+ * Exposes message state and setter.
585
+ * Only visible when isGift is true (purchasing for someone else).
586
+ *
587
+ * @component
588
+ * @example
589
+ * ```tsx
590
+ * <GiftCard.RecipientMessage>
591
+ * {({ value, setValue, isVisible }) =>
592
+ * isVisible && (
593
+ * <div className="space-y-2">
594
+ * <label>Personal message (optional)</label>
595
+ * <textarea
596
+ * value={value}
597
+ * onChange={(e) => setValue(e.target.value)}
598
+ * rows={4}
599
+ * />
600
+ * </div>
601
+ * )
602
+ * }
603
+ * </GiftCard.RecipientMessage>
604
+ * ```
605
+ */
606
+ export function RecipientMessage(props) {
607
+ const checkoutService = useService(GiftCardCheckoutServiceDefinition);
608
+ const value = checkoutService.recipientMessage.get();
609
+ const isGift = checkoutService.isGift.get();
610
+ return props.children({
611
+ value,
612
+ setValue: checkoutService.setRecipientMessage,
613
+ isVisible: isGift,
614
+ });
615
+ }
616
+ /**
617
+ * Headless component for recipient form that combines all recipient fields.
618
+ * Provides consolidated access to email, name, date, and message fields.
619
+ * Only visible when isGift is true (purchasing for someone else).
620
+ *
621
+ * This is a convenience component that aggregates all recipient field data.
622
+ * For granular control, use the individual RecipientEmail, RecipientName,
623
+ * RecipientDate, and RecipientMessage components.
624
+ *
625
+ * @component
626
+ * @example
627
+ * ```tsx
628
+ * <GiftCard.RecipientForm>
629
+ * {({ isVisible, showErrors, email, name, deliverAt, message }) =>
630
+ * isVisible && (
631
+ * <div className="space-y-4">
632
+ * <div>
633
+ * <label>Recipient email</label>
634
+ * <input
635
+ * type="email"
636
+ * value={email.value}
637
+ * onChange={(e) => email.setValue(e.target.value)}
638
+ * data-invalid={showErrors && !email.isValid}
639
+ * />
640
+ * {showErrors && !email.isValid && <p>Please enter a valid email</p>}
641
+ * </div>
642
+ * <div>
643
+ * <label>Recipient name</label>
644
+ * <input
645
+ * type="text"
646
+ * value={name.value}
647
+ * onChange={(e) => name.setValue(e.target.value)}
648
+ * />
649
+ * </div>
650
+ * <div>
651
+ * <label>Delivery date</label>
652
+ * <input
653
+ * type="date"
654
+ * value={deliverAt.inputValue}
655
+ * onChange={(e) => deliverAt.setValue(new Date(e.target.value))}
656
+ * />
657
+ * </div>
658
+ * <div>
659
+ * <label>Message</label>
660
+ * <textarea
661
+ * value={message.value}
662
+ * onChange={(e) => message.setValue(e.target.value)}
663
+ * />
664
+ * </div>
665
+ * </div>
666
+ * )
667
+ * }
668
+ * </GiftCard.RecipientForm>
669
+ * ```
670
+ */
671
+ export function RecipientForm(props) {
672
+ const giftCardService = useService(GiftCardServiceDefinition);
673
+ const checkoutService = useService(GiftCardCheckoutServiceDefinition);
674
+ const isGift = checkoutService.isGift.get();
675
+ const showErrors = checkoutService.showErrors.get();
676
+ const locale = giftCardService.locale.get();
677
+ // Email data
678
+ const emailValue = checkoutService.recipientEmail.get();
679
+ const isEmailValid = checkoutService.isRecipientEmailValid.get();
680
+ // Name data
681
+ const nameValue = checkoutService.recipientName.get();
682
+ // Date data
683
+ const dateValue = checkoutService.deliverAt.get();
684
+ // Message data
685
+ const messageValue = checkoutService.recipientMessage.get();
686
+ return props.children({
687
+ isVisible: isGift,
688
+ showErrors,
689
+ email: {
690
+ value: emailValue,
691
+ setValue: checkoutService.setRecipientEmail,
692
+ isValid: isEmailValid,
693
+ },
694
+ name: {
695
+ value: nameValue,
696
+ setValue: checkoutService.setRecipientName,
697
+ },
698
+ deliverAt: {
699
+ value: dateValue,
700
+ formattedValue: formatDate(dateValue, locale),
701
+ inputValue: formatDateForInput(dateValue),
702
+ setValue: checkoutService.setDeliverAt,
703
+ },
704
+ message: {
705
+ value: messageValue,
706
+ setValue: checkoutService.setRecipientMessage,
707
+ },
708
+ });
709
+ }
710
+ /**
711
+ * Headless component that exposes cart action data via render props.
712
+ * Provides lineItems, validation state, and validateAndShowErrors function.
713
+ *
714
+ * The loading state is handled by Commerce.Actions.AddToCart / CurrentCartService.
715
+ *
716
+ * @component
717
+ * @example
718
+ * ```tsx
719
+ * <CoreGiftCard.Actions>
720
+ * {({ lineItems, validateAndShowErrors }) => (
721
+ * <Commerce.Actions.AddToCart
722
+ * lineItems={lineItems}
723
+ * onClick={() => {
724
+ * if (!validateAndShowErrors()) return; // Validation failed, errors shown
725
+ * }}
726
+ * />
727
+ * )}
728
+ * </CoreGiftCard.Actions>
729
+ * ```
730
+ */
731
+ export function Actions(props) {
732
+ const checkoutService = useService(GiftCardCheckoutServiceDefinition);
733
+ return props.children({
734
+ lineItems: checkoutService.lineItems.get(),
735
+ validateAndShowErrors: checkoutService.validateAndShowErrors,
736
+ });
737
+ }