hydrogen-forge 0.1.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.
Files changed (118) hide show
  1. package/README.md +212 -0
  2. package/dist/commands/add.d.ts +7 -0
  3. package/dist/commands/add.d.ts.map +1 -0
  4. package/dist/commands/add.js +123 -0
  5. package/dist/commands/add.js.map +1 -0
  6. package/dist/commands/create.d.ts +8 -0
  7. package/dist/commands/create.d.ts.map +1 -0
  8. package/dist/commands/create.js +160 -0
  9. package/dist/commands/create.js.map +1 -0
  10. package/dist/commands/setup-mcp.d.ts +7 -0
  11. package/dist/commands/setup-mcp.d.ts.map +1 -0
  12. package/dist/commands/setup-mcp.js +179 -0
  13. package/dist/commands/setup-mcp.js.map +1 -0
  14. package/dist/index.d.ts +3 -0
  15. package/dist/index.d.ts.map +1 -0
  16. package/dist/index.js +50 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/lib/generators.d.ts +6 -0
  19. package/dist/lib/generators.d.ts.map +1 -0
  20. package/dist/lib/generators.js +470 -0
  21. package/dist/lib/generators.js.map +1 -0
  22. package/dist/lib/utils.d.ts +17 -0
  23. package/dist/lib/utils.d.ts.map +1 -0
  24. package/dist/lib/utils.js +101 -0
  25. package/dist/lib/utils.js.map +1 -0
  26. package/package.json +54 -0
  27. package/templates/starter/.env.example +21 -0
  28. package/templates/starter/.graphqlrc.ts +27 -0
  29. package/templates/starter/README.md +117 -0
  30. package/templates/starter/app/assets/favicon.svg +28 -0
  31. package/templates/starter/app/components/AddToCartButton.tsx +102 -0
  32. package/templates/starter/app/components/Aside.tsx +136 -0
  33. package/templates/starter/app/components/CartLineItem.tsx +229 -0
  34. package/templates/starter/app/components/CartMain.tsx +131 -0
  35. package/templates/starter/app/components/CartSummary.tsx +315 -0
  36. package/templates/starter/app/components/CollectionFilters.tsx +330 -0
  37. package/templates/starter/app/components/CollectionGrid.tsx +141 -0
  38. package/templates/starter/app/components/Footer.tsx +218 -0
  39. package/templates/starter/app/components/Header.tsx +296 -0
  40. package/templates/starter/app/components/PageLayout.tsx +174 -0
  41. package/templates/starter/app/components/PaginatedResourceSection.tsx +41 -0
  42. package/templates/starter/app/components/ProductCard.tsx +151 -0
  43. package/templates/starter/app/components/ProductForm.tsx +156 -0
  44. package/templates/starter/app/components/ProductGallery.tsx +164 -0
  45. package/templates/starter/app/components/ProductGrid.tsx +64 -0
  46. package/templates/starter/app/components/ProductImage.tsx +23 -0
  47. package/templates/starter/app/components/ProductItem.tsx +44 -0
  48. package/templates/starter/app/components/ProductPrice.tsx +97 -0
  49. package/templates/starter/app/components/SearchDialog.tsx +599 -0
  50. package/templates/starter/app/components/SearchForm.tsx +68 -0
  51. package/templates/starter/app/components/SearchFormPredictive.tsx +76 -0
  52. package/templates/starter/app/components/SearchResults.tsx +161 -0
  53. package/templates/starter/app/components/SearchResultsPredictive.tsx +461 -0
  54. package/templates/starter/app/entry.client.tsx +21 -0
  55. package/templates/starter/app/entry.server.tsx +53 -0
  56. package/templates/starter/app/graphql/customer-account/CustomerAddressMutations.ts +64 -0
  57. package/templates/starter/app/graphql/customer-account/CustomerDetailsQuery.ts +40 -0
  58. package/templates/starter/app/graphql/customer-account/CustomerOrderQuery.ts +90 -0
  59. package/templates/starter/app/graphql/customer-account/CustomerOrdersQuery.ts +63 -0
  60. package/templates/starter/app/graphql/customer-account/CustomerUpdateMutation.ts +25 -0
  61. package/templates/starter/app/lib/context.ts +60 -0
  62. package/templates/starter/app/lib/fragments.ts +234 -0
  63. package/templates/starter/app/lib/orderFilters.ts +90 -0
  64. package/templates/starter/app/lib/redirect.ts +23 -0
  65. package/templates/starter/app/lib/search.ts +79 -0
  66. package/templates/starter/app/lib/session.ts +72 -0
  67. package/templates/starter/app/lib/variants.ts +46 -0
  68. package/templates/starter/app/root.tsx +209 -0
  69. package/templates/starter/app/routes/$.tsx +11 -0
  70. package/templates/starter/app/routes/[robots.txt].tsx +117 -0
  71. package/templates/starter/app/routes/[sitemap.xml].tsx +16 -0
  72. package/templates/starter/app/routes/_index.tsx +167 -0
  73. package/templates/starter/app/routes/account.$.tsx +9 -0
  74. package/templates/starter/app/routes/account._index.tsx +5 -0
  75. package/templates/starter/app/routes/account.addresses.tsx +516 -0
  76. package/templates/starter/app/routes/account.orders.$id.tsx +222 -0
  77. package/templates/starter/app/routes/account.orders._index.tsx +222 -0
  78. package/templates/starter/app/routes/account.profile.tsx +133 -0
  79. package/templates/starter/app/routes/account.tsx +97 -0
  80. package/templates/starter/app/routes/account_.authorize.tsx +5 -0
  81. package/templates/starter/app/routes/account_.login.tsx +7 -0
  82. package/templates/starter/app/routes/account_.logout.tsx +11 -0
  83. package/templates/starter/app/routes/api.$version.[graphql.json].tsx +14 -0
  84. package/templates/starter/app/routes/blogs.$blogHandle.$articleHandle.tsx +129 -0
  85. package/templates/starter/app/routes/blogs.$blogHandle._index.tsx +175 -0
  86. package/templates/starter/app/routes/blogs._index.tsx +109 -0
  87. package/templates/starter/app/routes/cart.$lines.tsx +70 -0
  88. package/templates/starter/app/routes/cart.tsx +117 -0
  89. package/templates/starter/app/routes/collections.$handle.tsx +161 -0
  90. package/templates/starter/app/routes/collections._index.tsx +133 -0
  91. package/templates/starter/app/routes/collections.all.tsx +122 -0
  92. package/templates/starter/app/routes/discount.$code.tsx +48 -0
  93. package/templates/starter/app/routes/pages.$handle.tsx +88 -0
  94. package/templates/starter/app/routes/policies.$handle.tsx +93 -0
  95. package/templates/starter/app/routes/policies._index.tsx +69 -0
  96. package/templates/starter/app/routes/products.$handle.tsx +232 -0
  97. package/templates/starter/app/routes/search.tsx +426 -0
  98. package/templates/starter/app/routes/sitemap.$type.$page[.xml].tsx +23 -0
  99. package/templates/starter/app/routes.ts +9 -0
  100. package/templates/starter/app/styles/app.css +574 -0
  101. package/templates/starter/app/styles/reset.css +139 -0
  102. package/templates/starter/app/styles/tailwind.css +116 -0
  103. package/templates/starter/customer-accountapi.generated.d.ts +543 -0
  104. package/templates/starter/env.d.ts +7 -0
  105. package/templates/starter/eslint.config.js +247 -0
  106. package/templates/starter/guides/predictiveSearch/predictiveSearch.jpg +0 -0
  107. package/templates/starter/guides/predictiveSearch/predictiveSearch.md +394 -0
  108. package/templates/starter/guides/search/search.jpg +0 -0
  109. package/templates/starter/guides/search/search.md +335 -0
  110. package/templates/starter/package.json +71 -0
  111. package/templates/starter/postcss.config.js +6 -0
  112. package/templates/starter/public/.gitkeep +0 -0
  113. package/templates/starter/react-router.config.ts +13 -0
  114. package/templates/starter/server.ts +59 -0
  115. package/templates/starter/storefrontapi.generated.d.ts +1264 -0
  116. package/templates/starter/tailwind.config.js +83 -0
  117. package/templates/starter/tsconfig.json +67 -0
  118. package/templates/starter/vite.config.ts +32 -0
@@ -0,0 +1,131 @@
1
+ import {useOptimisticCart} from '@shopify/hydrogen';
2
+ import {Link} from 'react-router';
3
+ import type {CartApiQueryFragment} from 'storefrontapi.generated';
4
+ import {useAside} from '~/components/Aside';
5
+ import {CartLineItem} from '~/components/CartLineItem';
6
+ import {CartSummary} from './CartSummary';
7
+
8
+ export type CartLayout = 'page' | 'aside';
9
+
10
+ export type CartMainProps = {
11
+ cart: CartApiQueryFragment | null;
12
+ layout: CartLayout;
13
+ };
14
+
15
+ /**
16
+ * The main cart component that displays the cart items and summary.
17
+ * It is used by both the /cart route and the cart aside dialog.
18
+ */
19
+ export function CartMain({layout, cart: originalCart}: CartMainProps) {
20
+ // The useOptimisticCart hook applies pending actions to the cart
21
+ // so the user immediately sees feedback when they modify the cart.
22
+ const cart = useOptimisticCart(originalCart);
23
+
24
+ const linesCount = Boolean(cart?.lines?.nodes?.length || 0);
25
+ const cartHasItems = cart?.totalQuantity ? cart.totalQuantity > 0 : false;
26
+
27
+ const containerClasses =
28
+ layout === 'page' ? 'mx-auto max-w-4xl px-4 py-8' : 'flex h-full flex-col';
29
+
30
+ return (
31
+ <div className={containerClasses}>
32
+ <CartEmpty hidden={linesCount} layout={layout} />
33
+
34
+ {linesCount && (
35
+ <div
36
+ className={
37
+ layout === 'aside' ? 'flex flex-1 flex-col overflow-hidden' : ''
38
+ }
39
+ >
40
+ {/* Cart items list */}
41
+ <div
42
+ className={layout === 'aside' ? 'flex-1 overflow-y-auto' : 'mb-8'}
43
+ aria-labelledby="cart-lines"
44
+ >
45
+ <ul className="divide-y divide-secondary-200">
46
+ {(cart?.lines?.nodes ?? []).map((line) => (
47
+ <CartLineItem key={line.id} line={line} layout={layout} />
48
+ ))}
49
+ </ul>
50
+ </div>
51
+
52
+ {/* Cart summary - sticky at bottom for aside layout */}
53
+ {cartHasItems && (
54
+ <div
55
+ className={
56
+ layout === 'aside'
57
+ ? 'border-t border-secondary-200 bg-white pt-4'
58
+ : 'rounded-lg border border-secondary-200 bg-secondary-50 p-6'
59
+ }
60
+ >
61
+ <CartSummary cart={cart} layout={layout} />
62
+ </div>
63
+ )}
64
+ </div>
65
+ )}
66
+ </div>
67
+ );
68
+ }
69
+
70
+ function CartEmpty({
71
+ hidden = false,
72
+ layout,
73
+ }: {
74
+ hidden: boolean;
75
+ layout?: CartMainProps['layout'];
76
+ }) {
77
+ const {close} = useAside();
78
+
79
+ if (hidden) return null;
80
+
81
+ return (
82
+ <div className="flex flex-1 flex-col items-center justify-center py-12 text-center">
83
+ {/* Empty cart icon */}
84
+ <div className="mb-6 rounded-full bg-secondary-100 p-6">
85
+ <svg
86
+ className="h-12 w-12 text-secondary-400"
87
+ fill="none"
88
+ stroke="currentColor"
89
+ viewBox="0 0 24 24"
90
+ >
91
+ <path
92
+ strokeLinecap="round"
93
+ strokeLinejoin="round"
94
+ strokeWidth={1.5}
95
+ d="M16 11V7a4 4 0 00-8 0v4M5 9h14l1 12H4L5 9z"
96
+ />
97
+ </svg>
98
+ </div>
99
+
100
+ <h3 className="mb-2 text-lg font-semibold text-secondary-900">
101
+ Your cart is empty
102
+ </h3>
103
+ <p className="mb-6 max-w-sm text-secondary-600">
104
+ Looks like you haven&rsquo;t added anything yet. Let&rsquo;s get you
105
+ started!
106
+ </p>
107
+
108
+ <Link
109
+ to="/collections"
110
+ onClick={layout === 'aside' ? close : undefined}
111
+ prefetch="viewport"
112
+ className="inline-flex items-center justify-center rounded-md bg-primary-600 px-6 py-3 text-sm font-medium text-white shadow-sm transition-colors hover:bg-primary-700 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2"
113
+ >
114
+ Continue shopping
115
+ <svg
116
+ className="ml-2 h-4 w-4"
117
+ fill="none"
118
+ stroke="currentColor"
119
+ viewBox="0 0 24 24"
120
+ >
121
+ <path
122
+ strokeLinecap="round"
123
+ strokeLinejoin="round"
124
+ strokeWidth={2}
125
+ d="M14 5l7 7m0 0l-7 7m7-7H3"
126
+ />
127
+ </svg>
128
+ </Link>
129
+ </div>
130
+ );
131
+ }
@@ -0,0 +1,315 @@
1
+ import type {CartApiQueryFragment} from 'storefrontapi.generated';
2
+ import type {CartLayout} from '~/components/CartMain';
3
+ import {CartForm, Money, type OptimisticCart} from '@shopify/hydrogen';
4
+ import {useEffect, useRef} from 'react';
5
+ import {useFetcher} from 'react-router';
6
+ import type {FetcherWithComponents} from 'react-router';
7
+
8
+ type CartSummaryProps = {
9
+ cart: OptimisticCart<CartApiQueryFragment | null>;
10
+ layout: CartLayout;
11
+ };
12
+
13
+ export function CartSummary({cart, layout}: CartSummaryProps) {
14
+ return (
15
+ <div aria-labelledby="cart-summary" className="space-y-4">
16
+ {/* Subtotal */}
17
+ <div className="flex items-center justify-between">
18
+ <span className="text-sm text-secondary-600">Subtotal</span>
19
+ <span className="text-base font-medium text-secondary-900">
20
+ {cart?.cost?.subtotalAmount?.amount ? (
21
+ <Money data={cart?.cost?.subtotalAmount} />
22
+ ) : (
23
+ '-'
24
+ )}
25
+ </span>
26
+ </div>
27
+
28
+ {/* Discounts & Gift Cards */}
29
+ <CartDiscounts discountCodes={cart?.discountCodes} />
30
+ <CartGiftCard giftCardCodes={cart?.appliedGiftCards} />
31
+
32
+ {/* Shipping notice */}
33
+ <p className="text-xs text-secondary-500">
34
+ Shipping and taxes calculated at checkout
35
+ </p>
36
+
37
+ {/* Checkout button */}
38
+ <CartCheckoutActions checkoutUrl={cart?.checkoutUrl} layout={layout} />
39
+ </div>
40
+ );
41
+ }
42
+
43
+ function CartCheckoutActions({
44
+ checkoutUrl,
45
+ layout,
46
+ }: {
47
+ checkoutUrl?: string;
48
+ layout: CartLayout;
49
+ }) {
50
+ if (!checkoutUrl) return null;
51
+
52
+ return (
53
+ <div className="space-y-3">
54
+ <a
55
+ href={checkoutUrl}
56
+ target="_self"
57
+ className="flex w-full items-center justify-center rounded-md bg-primary-600 px-6 py-3 text-sm font-medium text-white shadow-sm transition-colors hover:bg-primary-700 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2"
58
+ >
59
+ Continue to Checkout
60
+ <svg
61
+ className="ml-2 h-4 w-4"
62
+ fill="none"
63
+ stroke="currentColor"
64
+ viewBox="0 0 24 24"
65
+ >
66
+ <path
67
+ strokeLinecap="round"
68
+ strokeLinejoin="round"
69
+ strokeWidth={2}
70
+ d="M17 8l4 4m0 0l-4 4m4-4H3"
71
+ />
72
+ </svg>
73
+ </a>
74
+
75
+ {layout === 'aside' && (
76
+ <a
77
+ href="/cart"
78
+ className="flex w-full items-center justify-center rounded-md border border-secondary-300 bg-white px-6 py-3 text-sm font-medium text-secondary-700 transition-colors hover:bg-secondary-50"
79
+ >
80
+ View Cart
81
+ </a>
82
+ )}
83
+ </div>
84
+ );
85
+ }
86
+
87
+ function CartDiscounts({
88
+ discountCodes,
89
+ }: {
90
+ discountCodes?: CartApiQueryFragment['discountCodes'];
91
+ }) {
92
+ const codes: string[] =
93
+ discountCodes
94
+ ?.filter((discount) => discount.applicable)
95
+ ?.map(({code}) => code) || [];
96
+
97
+ return (
98
+ <div className="space-y-3">
99
+ {/* Display applied discount codes */}
100
+ {codes.length > 0 && (
101
+ <div className="flex items-center justify-between rounded-md bg-green-50 px-3 py-2">
102
+ <div className="flex items-center gap-2">
103
+ <svg
104
+ className="h-4 w-4 text-green-600"
105
+ fill="none"
106
+ stroke="currentColor"
107
+ viewBox="0 0 24 24"
108
+ >
109
+ <path
110
+ strokeLinecap="round"
111
+ strokeLinejoin="round"
112
+ strokeWidth={2}
113
+ d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z"
114
+ />
115
+ </svg>
116
+ <span className="text-sm font-medium text-green-700">
117
+ {codes.join(', ')}
118
+ </span>
119
+ </div>
120
+ <UpdateDiscountForm>
121
+ <button
122
+ type="submit"
123
+ className="text-xs font-medium text-green-600 hover:text-green-800"
124
+ >
125
+ Remove
126
+ </button>
127
+ </UpdateDiscountForm>
128
+ </div>
129
+ )}
130
+
131
+ {/* Discount code input */}
132
+ <UpdateDiscountForm discountCodes={codes}>
133
+ <div className="flex gap-2">
134
+ <input
135
+ type="text"
136
+ name="discountCode"
137
+ placeholder="Discount code"
138
+ className="flex-1 rounded-md border border-secondary-300 px-3 py-2 text-sm placeholder:text-secondary-400 focus:border-primary-500 focus:outline-none focus:ring-1 focus:ring-primary-500"
139
+ />
140
+ <button
141
+ type="submit"
142
+ className="rounded-md bg-secondary-100 px-4 py-2 text-sm font-medium text-secondary-700 transition-colors hover:bg-secondary-200"
143
+ >
144
+ Apply
145
+ </button>
146
+ </div>
147
+ </UpdateDiscountForm>
148
+ </div>
149
+ );
150
+ }
151
+
152
+ function UpdateDiscountForm({
153
+ discountCodes,
154
+ children,
155
+ }: {
156
+ discountCodes?: string[];
157
+ children: React.ReactNode;
158
+ }) {
159
+ return (
160
+ <CartForm
161
+ route="/cart"
162
+ action={CartForm.ACTIONS.DiscountCodesUpdate}
163
+ inputs={{
164
+ discountCodes: discountCodes || [],
165
+ }}
166
+ >
167
+ {children}
168
+ </CartForm>
169
+ );
170
+ }
171
+
172
+ function CartGiftCard({
173
+ giftCardCodes,
174
+ }: {
175
+ giftCardCodes: CartApiQueryFragment['appliedGiftCards'] | undefined;
176
+ }) {
177
+ const appliedGiftCardCodes = useRef<string[]>([]);
178
+ const giftCardCodeInput = useRef<HTMLInputElement>(null);
179
+ const giftCardAddFetcher = useFetcher({key: 'gift-card-add'});
180
+
181
+ // Clear the gift card code input after the gift card is added
182
+ useEffect(() => {
183
+ if (giftCardAddFetcher.data) {
184
+ giftCardCodeInput.current!.value = '';
185
+ }
186
+ }, [giftCardAddFetcher.data]);
187
+
188
+ function saveAppliedCode(code: string) {
189
+ const formattedCode = code.replace(/\s/g, ''); // Remove spaces
190
+ if (!appliedGiftCardCodes.current.includes(formattedCode)) {
191
+ appliedGiftCardCodes.current.push(formattedCode);
192
+ }
193
+ }
194
+
195
+ return (
196
+ <div className="space-y-3">
197
+ {/* Display applied gift cards */}
198
+ {giftCardCodes && giftCardCodes.length > 0 && (
199
+ <div className="space-y-2">
200
+ <span className="text-xs font-medium text-secondary-500 uppercase tracking-wide">
201
+ Applied Gift Cards
202
+ </span>
203
+ {giftCardCodes.map((giftCard) => (
204
+ <RemoveGiftCardForm key={giftCard.id} giftCardId={giftCard.id}>
205
+ <div className="flex items-center justify-between rounded-md bg-purple-50 px-3 py-2">
206
+ <div className="flex items-center gap-2">
207
+ <svg
208
+ className="h-4 w-4 text-purple-600"
209
+ fill="none"
210
+ stroke="currentColor"
211
+ viewBox="0 0 24 24"
212
+ >
213
+ <path
214
+ strokeLinecap="round"
215
+ strokeLinejoin="round"
216
+ strokeWidth={2}
217
+ d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
218
+ />
219
+ </svg>
220
+ <span className="text-sm font-medium text-purple-700">
221
+ ***{giftCard.lastCharacters}
222
+ </span>
223
+ <span className="text-sm text-purple-600">
224
+ (<Money data={giftCard.amountUsed} />)
225
+ </span>
226
+ </div>
227
+ <button
228
+ type="submit"
229
+ className="text-xs font-medium text-purple-600 hover:text-purple-800"
230
+ >
231
+ Remove
232
+ </button>
233
+ </div>
234
+ </RemoveGiftCardForm>
235
+ ))}
236
+ </div>
237
+ )}
238
+
239
+ {/* Gift card input */}
240
+ <UpdateGiftCardForm
241
+ giftCardCodes={appliedGiftCardCodes.current}
242
+ saveAppliedCode={saveAppliedCode}
243
+ fetcherKey="gift-card-add"
244
+ >
245
+ <div className="flex gap-2">
246
+ <input
247
+ type="text"
248
+ name="giftCardCode"
249
+ placeholder="Gift card code"
250
+ ref={giftCardCodeInput}
251
+ className="flex-1 rounded-md border border-secondary-300 px-3 py-2 text-sm placeholder:text-secondary-400 focus:border-primary-500 focus:outline-none focus:ring-1 focus:ring-primary-500"
252
+ />
253
+ <button
254
+ type="submit"
255
+ disabled={giftCardAddFetcher.state !== 'idle'}
256
+ className="rounded-md bg-secondary-100 px-4 py-2 text-sm font-medium text-secondary-700 transition-colors hover:bg-secondary-200 disabled:cursor-not-allowed disabled:opacity-50"
257
+ >
258
+ Apply
259
+ </button>
260
+ </div>
261
+ </UpdateGiftCardForm>
262
+ </div>
263
+ );
264
+ }
265
+
266
+ function UpdateGiftCardForm({
267
+ giftCardCodes,
268
+ saveAppliedCode,
269
+ fetcherKey,
270
+ children,
271
+ }: {
272
+ giftCardCodes?: string[];
273
+ saveAppliedCode?: (code: string) => void;
274
+ fetcherKey?: string;
275
+ children: React.ReactNode;
276
+ }) {
277
+ return (
278
+ <CartForm
279
+ fetcherKey={fetcherKey}
280
+ route="/cart"
281
+ action={CartForm.ACTIONS.GiftCardCodesUpdate}
282
+ inputs={{
283
+ giftCardCodes: giftCardCodes || [],
284
+ }}
285
+ >
286
+ {(fetcher: FetcherWithComponents<any>) => {
287
+ const code = fetcher.formData?.get('giftCardCode');
288
+ if (code && saveAppliedCode) {
289
+ saveAppliedCode(code as string);
290
+ }
291
+ return children;
292
+ }}
293
+ </CartForm>
294
+ );
295
+ }
296
+
297
+ function RemoveGiftCardForm({
298
+ giftCardId,
299
+ children,
300
+ }: {
301
+ giftCardId: string;
302
+ children: React.ReactNode;
303
+ }) {
304
+ return (
305
+ <CartForm
306
+ route="/cart"
307
+ action={CartForm.ACTIONS.GiftCardCodesRemove}
308
+ inputs={{
309
+ giftCardCodes: [giftCardId],
310
+ }}
311
+ >
312
+ {children}
313
+ </CartForm>
314
+ );
315
+ }