@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.
- package/cjs/dist/__mocks__/gift-card.d.ts +14 -0
- package/cjs/dist/__mocks__/gift-card.d.ts.map +1 -0
- package/cjs/dist/__mocks__/gift-card.js +49 -0
- package/cjs/dist/enums/index.d.ts +52 -0
- package/cjs/dist/enums/index.d.ts.map +1 -0
- package/cjs/dist/enums/index.js +52 -0
- package/cjs/dist/react/GiftCard.d.ts +1318 -0
- package/cjs/dist/react/GiftCard.d.ts.map +1 -0
- package/cjs/dist/react/GiftCard.js +1126 -0
- package/cjs/dist/react/core/GiftCard.d.ts +863 -0
- package/cjs/dist/react/core/GiftCard.d.ts.map +1 -0
- package/cjs/dist/react/core/GiftCard.js +737 -0
- package/cjs/dist/react/index.d.ts +2 -0
- package/cjs/dist/react/index.d.ts.map +1 -0
- package/cjs/dist/react/index.js +1 -0
- package/cjs/dist/services/gift-card-checkout-service.d.ts +133 -0
- package/cjs/dist/services/gift-card-checkout-service.d.ts.map +1 -0
- package/cjs/dist/services/gift-card-checkout-service.js +159 -0
- package/cjs/dist/services/gift-card-service.d.ts +102 -0
- package/cjs/dist/services/gift-card-service.d.ts.map +1 -0
- package/cjs/dist/services/gift-card-service.js +165 -0
- package/cjs/dist/services/index.d.ts +3 -0
- package/cjs/dist/services/index.d.ts.map +1 -0
- package/cjs/dist/services/index.js +2 -0
- package/cjs/dist/utils/formatting-utils.d.ts +13 -0
- package/cjs/dist/utils/formatting-utils.d.ts.map +1 -0
- package/cjs/dist/utils/formatting-utils.js +38 -0
- package/cjs/dist/utils/gift-card-utils.d.ts +18 -0
- package/cjs/dist/utils/gift-card-utils.d.ts.map +1 -0
- package/cjs/dist/utils/gift-card-utils.js +118 -0
- package/cjs/dist/utils/validation-utils.d.ts +10 -0
- package/cjs/dist/utils/validation-utils.d.ts.map +1 -0
- package/cjs/dist/utils/validation-utils.js +24 -0
- package/dist/__mocks__/gift-card.d.ts +14 -0
- package/dist/__mocks__/gift-card.d.ts.map +1 -0
- package/dist/__mocks__/gift-card.js +49 -0
- package/dist/enums/index.d.ts +52 -0
- package/dist/enums/index.d.ts.map +1 -0
- package/dist/enums/index.js +52 -0
- package/dist/react/GiftCard.d.ts +1318 -0
- package/dist/react/GiftCard.d.ts.map +1 -0
- package/dist/react/GiftCard.js +1126 -0
- package/dist/react/core/GiftCard.d.ts +863 -0
- package/dist/react/core/GiftCard.d.ts.map +1 -0
- package/dist/react/core/GiftCard.js +737 -0
- package/dist/react/index.d.ts +2 -0
- package/dist/react/index.d.ts.map +1 -0
- package/dist/react/index.js +1 -0
- package/dist/services/gift-card-checkout-service.d.ts +133 -0
- package/dist/services/gift-card-checkout-service.d.ts.map +1 -0
- package/dist/services/gift-card-checkout-service.js +159 -0
- package/dist/services/gift-card-service.d.ts +102 -0
- package/dist/services/gift-card-service.d.ts.map +1 -0
- package/dist/services/gift-card-service.js +165 -0
- package/dist/services/index.d.ts +3 -0
- package/dist/services/index.d.ts.map +1 -0
- package/dist/services/index.js +2 -0
- package/dist/utils/formatting-utils.d.ts +13 -0
- package/dist/utils/formatting-utils.d.ts.map +1 -0
- package/dist/utils/formatting-utils.js +38 -0
- package/dist/utils/gift-card-utils.d.ts +18 -0
- package/dist/utils/gift-card-utils.d.ts.map +1 -0
- package/dist/utils/gift-card-utils.js +118 -0
- package/dist/utils/validation-utils.d.ts +10 -0
- package/dist/utils/validation-utils.d.ts.map +1 -0
- package/dist/utils/validation-utils.js +24 -0
- package/package.json +72 -0
- package/react/package.json +4 -0
- 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
|
+
}
|