@shopify/create-hydrogen 4.3.13 → 5.0.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 (127) hide show
  1. package/dist/assets/hydrogen/bundle/analyzer.html +2045 -0
  2. package/dist/assets/hydrogen/i18n/domains.ts +28 -0
  3. package/dist/assets/hydrogen/i18n/mock-i18n-types.ts +3 -0
  4. package/dist/assets/hydrogen/i18n/subdomains.ts +27 -0
  5. package/dist/assets/hydrogen/i18n/subfolders.ts +29 -0
  6. package/dist/assets/hydrogen/routes/locale-check.ts +16 -0
  7. package/dist/assets/hydrogen/starter/.eslintignore +5 -0
  8. package/dist/assets/hydrogen/starter/.eslintrc.cjs +19 -0
  9. package/dist/assets/hydrogen/starter/.graphqlrc.yml +12 -0
  10. package/dist/assets/hydrogen/starter/CHANGELOG.md +709 -0
  11. package/dist/assets/hydrogen/starter/README.md +45 -0
  12. package/dist/assets/hydrogen/starter/app/assets/favicon.svg +28 -0
  13. package/dist/assets/hydrogen/starter/app/components/AddToCartButton.tsx +37 -0
  14. package/dist/assets/hydrogen/starter/app/components/Aside.tsx +76 -0
  15. package/dist/assets/hydrogen/starter/app/components/CartLineItem.tsx +150 -0
  16. package/dist/assets/hydrogen/starter/app/components/CartMain.tsx +68 -0
  17. package/dist/assets/hydrogen/starter/app/components/CartSummary.tsx +101 -0
  18. package/dist/assets/hydrogen/starter/app/components/Footer.tsx +129 -0
  19. package/dist/assets/hydrogen/starter/app/components/Header.tsx +230 -0
  20. package/dist/assets/hydrogen/starter/app/components/PageLayout.tsx +126 -0
  21. package/dist/assets/hydrogen/starter/app/components/ProductForm.tsx +80 -0
  22. package/dist/assets/hydrogen/starter/app/components/ProductImage.tsx +23 -0
  23. package/dist/assets/hydrogen/starter/app/components/ProductPrice.tsx +27 -0
  24. package/dist/assets/hydrogen/starter/app/components/Search.tsx +514 -0
  25. package/dist/assets/hydrogen/starter/app/entry.client.tsx +12 -0
  26. package/dist/assets/hydrogen/starter/app/entry.server.tsx +47 -0
  27. package/dist/assets/hydrogen/starter/app/graphql/customer-account/CustomerAddressMutations.ts +61 -0
  28. package/dist/assets/hydrogen/starter/app/graphql/customer-account/CustomerDetailsQuery.ts +40 -0
  29. package/dist/assets/hydrogen/starter/app/graphql/customer-account/CustomerOrderQuery.ts +87 -0
  30. package/dist/assets/hydrogen/starter/app/graphql/customer-account/CustomerOrdersQuery.ts +58 -0
  31. package/dist/assets/hydrogen/starter/app/graphql/customer-account/CustomerUpdateMutation.ts +24 -0
  32. package/dist/assets/hydrogen/starter/app/lib/fragments.ts +174 -0
  33. package/dist/assets/hydrogen/starter/app/lib/search.ts +29 -0
  34. package/dist/assets/hydrogen/starter/app/lib/session.ts +72 -0
  35. package/dist/assets/hydrogen/starter/app/lib/variants.ts +46 -0
  36. package/dist/assets/hydrogen/starter/app/root.tsx +191 -0
  37. package/dist/assets/hydrogen/starter/app/routes/$.tsx +11 -0
  38. package/dist/assets/hydrogen/starter/app/routes/[robots.txt].tsx +118 -0
  39. package/dist/assets/hydrogen/starter/app/routes/[sitemap.xml].tsx +177 -0
  40. package/dist/assets/hydrogen/starter/app/routes/_index.tsx +182 -0
  41. package/dist/assets/hydrogen/starter/app/routes/account.$.tsx +8 -0
  42. package/dist/assets/hydrogen/starter/app/routes/account._index.tsx +5 -0
  43. package/dist/assets/hydrogen/starter/app/routes/account.addresses.tsx +513 -0
  44. package/dist/assets/hydrogen/starter/app/routes/account.orders.$id.tsx +195 -0
  45. package/dist/assets/hydrogen/starter/app/routes/account.orders._index.tsx +107 -0
  46. package/dist/assets/hydrogen/starter/app/routes/account.profile.tsx +136 -0
  47. package/dist/assets/hydrogen/starter/app/routes/account.tsx +88 -0
  48. package/dist/assets/hydrogen/starter/app/routes/account_.authorize.tsx +5 -0
  49. package/dist/assets/hydrogen/starter/app/routes/account_.login.tsx +5 -0
  50. package/dist/assets/hydrogen/starter/app/routes/account_.logout.tsx +10 -0
  51. package/dist/assets/hydrogen/starter/app/routes/api.predictive-search.tsx +318 -0
  52. package/dist/assets/hydrogen/starter/app/routes/blogs.$blogHandle.$articleHandle.tsx +113 -0
  53. package/dist/assets/hydrogen/starter/app/routes/blogs.$blogHandle._index.tsx +188 -0
  54. package/dist/assets/hydrogen/starter/app/routes/blogs._index.tsx +119 -0
  55. package/dist/assets/hydrogen/starter/app/routes/cart.$lines.tsx +69 -0
  56. package/dist/assets/hydrogen/starter/app/routes/cart.tsx +102 -0
  57. package/dist/assets/hydrogen/starter/app/routes/collections.$handle.tsx +225 -0
  58. package/dist/assets/hydrogen/starter/app/routes/collections._index.tsx +146 -0
  59. package/dist/assets/hydrogen/starter/app/routes/collections.all.tsx +185 -0
  60. package/dist/assets/hydrogen/starter/app/routes/discount.$code.tsx +47 -0
  61. package/dist/assets/hydrogen/starter/app/routes/pages.$handle.tsx +84 -0
  62. package/dist/assets/hydrogen/starter/app/routes/policies.$handle.tsx +93 -0
  63. package/dist/assets/hydrogen/starter/app/routes/policies._index.tsx +63 -0
  64. package/dist/assets/hydrogen/starter/app/routes/products.$handle.tsx +299 -0
  65. package/dist/assets/hydrogen/starter/app/routes/search.tsx +177 -0
  66. package/dist/assets/hydrogen/starter/app/styles/app.css +486 -0
  67. package/dist/assets/hydrogen/starter/app/styles/reset.css +129 -0
  68. package/dist/assets/hydrogen/starter/customer-accountapi.generated.d.ts +509 -0
  69. package/dist/assets/hydrogen/starter/env.d.ts +54 -0
  70. package/dist/assets/hydrogen/starter/package.json +50 -0
  71. package/dist/assets/hydrogen/starter/public/.gitkeep +0 -0
  72. package/dist/assets/hydrogen/starter/server.ts +119 -0
  73. package/dist/assets/hydrogen/starter/storefrontapi.generated.d.ts +1211 -0
  74. package/dist/assets/hydrogen/starter/tsconfig.json +23 -0
  75. package/dist/assets/hydrogen/starter/vite.config.ts +41 -0
  76. package/dist/assets/hydrogen/tailwind/package.json +8 -0
  77. package/dist/assets/hydrogen/tailwind/tailwind.css +6 -0
  78. package/dist/assets/hydrogen/vanilla-extract/package.json +8 -0
  79. package/dist/assets/hydrogen/virtual-routes/assets/debug-network.css +592 -0
  80. package/dist/assets/hydrogen/virtual-routes/assets/favicon-dark.svg +20 -0
  81. package/dist/assets/hydrogen/virtual-routes/assets/favicon.svg +28 -0
  82. package/dist/assets/hydrogen/virtual-routes/assets/inter-variable-font.woff2 +0 -0
  83. package/dist/assets/hydrogen/virtual-routes/assets/jetbrainsmono-variable-font.woff2 +0 -0
  84. package/dist/assets/hydrogen/virtual-routes/assets/styles.css +238 -0
  85. package/dist/assets/hydrogen/virtual-routes/components/FlameChartWrapper.jsx +123 -0
  86. package/dist/assets/hydrogen/virtual-routes/components/HydrogenLogoBaseBW.jsx +32 -0
  87. package/dist/assets/hydrogen/virtual-routes/components/HydrogenLogoBaseColor.jsx +47 -0
  88. package/dist/assets/hydrogen/virtual-routes/components/IconBanner.jsx +292 -0
  89. package/dist/assets/hydrogen/virtual-routes/components/IconClose.jsx +38 -0
  90. package/dist/assets/hydrogen/virtual-routes/components/IconDiscard.jsx +44 -0
  91. package/dist/assets/hydrogen/virtual-routes/components/IconError.jsx +61 -0
  92. package/dist/assets/hydrogen/virtual-routes/components/IconGithub.jsx +23 -0
  93. package/dist/assets/hydrogen/virtual-routes/components/IconTwitter.jsx +21 -0
  94. package/dist/assets/hydrogen/virtual-routes/components/PageLayout.jsx +7 -0
  95. package/dist/assets/hydrogen/virtual-routes/components/RequestDetails.jsx +178 -0
  96. package/dist/assets/hydrogen/virtual-routes/components/RequestTable.jsx +91 -0
  97. package/dist/assets/hydrogen/virtual-routes/components/RequestWaterfall.jsx +151 -0
  98. package/dist/assets/hydrogen/virtual-routes/lib/useDebugNetworkServer.jsx +178 -0
  99. package/dist/assets/hydrogen/virtual-routes/routes/graphiql.jsx +5 -0
  100. package/dist/assets/hydrogen/virtual-routes/routes/index.jsx +265 -0
  101. package/dist/assets/hydrogen/virtual-routes/routes/subrequest-profiler.jsx +243 -0
  102. package/dist/assets/hydrogen/virtual-routes/virtual-root.jsx +64 -0
  103. package/dist/assets/hydrogen/vite/package.json +14 -0
  104. package/dist/assets/hydrogen/vite/vite.config.js +41 -0
  105. package/dist/chokidar-2CKIHN27.js +12 -0
  106. package/dist/chunk-EO6F7WJJ.js +2 -0
  107. package/dist/chunk-FB327AH7.js +5 -0
  108. package/dist/chunk-FJPX4XUR.js +2 -0
  109. package/dist/chunk-JKOXGRAA.js +10 -0
  110. package/dist/chunk-LNQWGFTB.js +45 -0
  111. package/dist/chunk-M6JXYI3V.js +23 -0
  112. package/dist/chunk-MNT4XW23.js +2 -0
  113. package/dist/chunk-N7HFZHSO.js +1145 -0
  114. package/dist/chunk-PMDMUCNY.js +2 -0
  115. package/dist/chunk-QGLB6FFL.js +3 -0
  116. package/dist/chunk-VMIOG46Y.js +2 -0
  117. package/dist/create-app.js +1867 -34
  118. package/dist/del-CZGKV5SQ.js +11 -0
  119. package/dist/devtools-ZCRGQE64.js +8 -0
  120. package/dist/error-handler-GEQXZJ25.js +2 -0
  121. package/dist/lib-NJYCLW6W.js +22 -0
  122. package/dist/morph-ZJCCGFNC.js +30499 -0
  123. package/dist/multipart-parser-6HGDQWV7.js +3 -0
  124. package/dist/open-OD6DRFEG.js +2 -0
  125. package/dist/out-7KAQXZLP.js +2 -0
  126. package/dist/yoga.wasm +0 -0
  127. package/package.json +7 -3
@@ -0,0 +1,513 @@
1
+ import type {CustomerAddressInput} from '@shopify/hydrogen/customer-account-api-types';
2
+ import type {
3
+ AddressFragment,
4
+ CustomerFragment,
5
+ } from 'customer-accountapi.generated';
6
+ import {
7
+ json,
8
+ type ActionFunctionArgs,
9
+ type LoaderFunctionArgs,
10
+ } from '@shopify/remix-oxygen';
11
+ import {
12
+ Form,
13
+ useActionData,
14
+ useNavigation,
15
+ useOutletContext,
16
+ type MetaFunction,
17
+ } from '@remix-run/react';
18
+ import {
19
+ UPDATE_ADDRESS_MUTATION,
20
+ DELETE_ADDRESS_MUTATION,
21
+ CREATE_ADDRESS_MUTATION,
22
+ } from '~/graphql/customer-account/CustomerAddressMutations';
23
+
24
+ export type ActionResponse = {
25
+ addressId?: string | null;
26
+ createdAddress?: AddressFragment;
27
+ defaultAddress?: string | null;
28
+ deletedAddress?: string | null;
29
+ error: Record<AddressFragment['id'], string> | null;
30
+ updatedAddress?: AddressFragment;
31
+ };
32
+
33
+ export const meta: MetaFunction = () => {
34
+ return [{title: 'Addresses'}];
35
+ };
36
+
37
+ export async function loader({context}: LoaderFunctionArgs) {
38
+ await context.customerAccount.handleAuthStatus();
39
+
40
+ return json({});
41
+ }
42
+
43
+ export async function action({request, context}: ActionFunctionArgs) {
44
+ const {customerAccount} = context;
45
+
46
+ try {
47
+ const form = await request.formData();
48
+
49
+ const addressId = form.has('addressId')
50
+ ? String(form.get('addressId'))
51
+ : null;
52
+ if (!addressId) {
53
+ throw new Error('You must provide an address id.');
54
+ }
55
+
56
+ // this will ensure redirecting to login never happen for mutatation
57
+ const isLoggedIn = await customerAccount.isLoggedIn();
58
+ if (!isLoggedIn) {
59
+ return json(
60
+ {error: {[addressId]: 'Unauthorized'}},
61
+ {
62
+ status: 401,
63
+ },
64
+ );
65
+ }
66
+
67
+ const defaultAddress = form.has('defaultAddress')
68
+ ? String(form.get('defaultAddress')) === 'on'
69
+ : false;
70
+ const address: CustomerAddressInput = {};
71
+ const keys: (keyof CustomerAddressInput)[] = [
72
+ 'address1',
73
+ 'address2',
74
+ 'city',
75
+ 'company',
76
+ 'territoryCode',
77
+ 'firstName',
78
+ 'lastName',
79
+ 'phoneNumber',
80
+ 'zoneCode',
81
+ 'zip',
82
+ ];
83
+
84
+ for (const key of keys) {
85
+ const value = form.get(key);
86
+ if (typeof value === 'string') {
87
+ address[key] = value;
88
+ }
89
+ }
90
+
91
+ switch (request.method) {
92
+ case 'POST': {
93
+ // handle new address creation
94
+ try {
95
+ const {data, errors} = await customerAccount.mutate(
96
+ CREATE_ADDRESS_MUTATION,
97
+ {
98
+ variables: {address, defaultAddress},
99
+ },
100
+ );
101
+
102
+ if (errors?.length) {
103
+ throw new Error(errors[0].message);
104
+ }
105
+
106
+ if (data?.customerAddressCreate?.userErrors?.length) {
107
+ throw new Error(data?.customerAddressCreate?.userErrors[0].message);
108
+ }
109
+
110
+ if (!data?.customerAddressCreate?.customerAddress) {
111
+ throw new Error('Customer address create failed.');
112
+ }
113
+
114
+ return json({
115
+ error: null,
116
+ createdAddress: data?.customerAddressCreate?.customerAddress,
117
+ defaultAddress,
118
+ });
119
+ } catch (error: unknown) {
120
+ if (error instanceof Error) {
121
+ return json(
122
+ {error: {[addressId]: error.message}},
123
+ {
124
+ status: 400,
125
+ },
126
+ );
127
+ }
128
+ return json(
129
+ {error: {[addressId]: error}},
130
+ {
131
+ status: 400,
132
+ },
133
+ );
134
+ }
135
+ }
136
+
137
+ case 'PUT': {
138
+ // handle address updates
139
+ try {
140
+ const {data, errors} = await customerAccount.mutate(
141
+ UPDATE_ADDRESS_MUTATION,
142
+ {
143
+ variables: {
144
+ address,
145
+ addressId: decodeURIComponent(addressId),
146
+ defaultAddress,
147
+ },
148
+ },
149
+ );
150
+
151
+ if (errors?.length) {
152
+ throw new Error(errors[0].message);
153
+ }
154
+
155
+ if (data?.customerAddressUpdate?.userErrors?.length) {
156
+ throw new Error(data?.customerAddressUpdate?.userErrors[0].message);
157
+ }
158
+
159
+ if (!data?.customerAddressUpdate?.customerAddress) {
160
+ throw new Error('Customer address update failed.');
161
+ }
162
+
163
+ return json({
164
+ error: null,
165
+ updatedAddress: address,
166
+ defaultAddress,
167
+ });
168
+ } catch (error: unknown) {
169
+ if (error instanceof Error) {
170
+ return json(
171
+ {error: {[addressId]: error.message}},
172
+ {
173
+ status: 400,
174
+ },
175
+ );
176
+ }
177
+ return json(
178
+ {error: {[addressId]: error}},
179
+ {
180
+ status: 400,
181
+ },
182
+ );
183
+ }
184
+ }
185
+
186
+ case 'DELETE': {
187
+ // handles address deletion
188
+ try {
189
+ const {data, errors} = await customerAccount.mutate(
190
+ DELETE_ADDRESS_MUTATION,
191
+ {
192
+ variables: {addressId: decodeURIComponent(addressId)},
193
+ },
194
+ );
195
+
196
+ if (errors?.length) {
197
+ throw new Error(errors[0].message);
198
+ }
199
+
200
+ if (data?.customerAddressDelete?.userErrors?.length) {
201
+ throw new Error(data?.customerAddressDelete?.userErrors[0].message);
202
+ }
203
+
204
+ if (!data?.customerAddressDelete?.deletedAddressId) {
205
+ throw new Error('Customer address delete failed.');
206
+ }
207
+
208
+ return json({error: null, deletedAddress: addressId});
209
+ } catch (error: unknown) {
210
+ if (error instanceof Error) {
211
+ return json(
212
+ {error: {[addressId]: error.message}},
213
+ {
214
+ status: 400,
215
+ },
216
+ );
217
+ }
218
+ return json(
219
+ {error: {[addressId]: error}},
220
+ {
221
+ status: 400,
222
+ },
223
+ );
224
+ }
225
+ }
226
+
227
+ default: {
228
+ return json(
229
+ {error: {[addressId]: 'Method not allowed'}},
230
+ {
231
+ status: 405,
232
+ },
233
+ );
234
+ }
235
+ }
236
+ } catch (error: unknown) {
237
+ if (error instanceof Error) {
238
+ return json(
239
+ {error: error.message},
240
+ {
241
+ status: 400,
242
+ },
243
+ );
244
+ }
245
+ return json(
246
+ {error},
247
+ {
248
+ status: 400,
249
+ },
250
+ );
251
+ }
252
+ }
253
+
254
+ export default function Addresses() {
255
+ const {customer} = useOutletContext<{customer: CustomerFragment}>();
256
+ const {defaultAddress, addresses} = customer;
257
+
258
+ return (
259
+ <div className="account-addresses">
260
+ <h2>Addresses</h2>
261
+ <br />
262
+ {!addresses.nodes.length ? (
263
+ <p>You have no addresses saved.</p>
264
+ ) : (
265
+ <div>
266
+ <div>
267
+ <legend>Create address</legend>
268
+ <NewAddressForm />
269
+ </div>
270
+ <br />
271
+ <hr />
272
+ <br />
273
+ <ExistingAddresses
274
+ addresses={addresses}
275
+ defaultAddress={defaultAddress}
276
+ />
277
+ </div>
278
+ )}
279
+ </div>
280
+ );
281
+ }
282
+
283
+ function NewAddressForm() {
284
+ const newAddress = {
285
+ address1: '',
286
+ address2: '',
287
+ city: '',
288
+ company: '',
289
+ territoryCode: '',
290
+ firstName: '',
291
+ id: 'new',
292
+ lastName: '',
293
+ phoneNumber: '',
294
+ zoneCode: '',
295
+ zip: '',
296
+ } as CustomerAddressInput;
297
+
298
+ return (
299
+ <AddressForm
300
+ addressId={'NEW_ADDRESS_ID'}
301
+ address={newAddress}
302
+ defaultAddress={null}
303
+ >
304
+ {({stateForMethod}) => (
305
+ <div>
306
+ <button
307
+ disabled={stateForMethod('POST') !== 'idle'}
308
+ formMethod="POST"
309
+ type="submit"
310
+ >
311
+ {stateForMethod('POST') !== 'idle' ? 'Creating' : 'Create'}
312
+ </button>
313
+ </div>
314
+ )}
315
+ </AddressForm>
316
+ );
317
+ }
318
+
319
+ function ExistingAddresses({
320
+ addresses,
321
+ defaultAddress,
322
+ }: Pick<CustomerFragment, 'addresses' | 'defaultAddress'>) {
323
+ return (
324
+ <div>
325
+ <legend>Existing addresses</legend>
326
+ {addresses.nodes.map((address) => (
327
+ <AddressForm
328
+ key={address.id}
329
+ addressId={address.id}
330
+ address={address}
331
+ defaultAddress={defaultAddress}
332
+ >
333
+ {({stateForMethod}) => (
334
+ <div>
335
+ <button
336
+ disabled={stateForMethod('PUT') !== 'idle'}
337
+ formMethod="PUT"
338
+ type="submit"
339
+ >
340
+ {stateForMethod('PUT') !== 'idle' ? 'Saving' : 'Save'}
341
+ </button>
342
+ <button
343
+ disabled={stateForMethod('DELETE') !== 'idle'}
344
+ formMethod="DELETE"
345
+ type="submit"
346
+ >
347
+ {stateForMethod('DELETE') !== 'idle' ? 'Deleting' : 'Delete'}
348
+ </button>
349
+ </div>
350
+ )}
351
+ </AddressForm>
352
+ ))}
353
+ </div>
354
+ );
355
+ }
356
+
357
+ export function AddressForm({
358
+ addressId,
359
+ address,
360
+ defaultAddress,
361
+ children,
362
+ }: {
363
+ addressId: AddressFragment['id'];
364
+ address: CustomerAddressInput;
365
+ defaultAddress: CustomerFragment['defaultAddress'];
366
+ children: (props: {
367
+ stateForMethod: (
368
+ method: 'PUT' | 'POST' | 'DELETE',
369
+ ) => ReturnType<typeof useNavigation>['state'];
370
+ }) => React.ReactNode;
371
+ }) {
372
+ const {state, formMethod} = useNavigation();
373
+ const action = useActionData<ActionResponse>();
374
+ const error = action?.error?.[addressId];
375
+ const isDefaultAddress = defaultAddress?.id === addressId;
376
+ return (
377
+ <Form id={addressId}>
378
+ <fieldset>
379
+ <input type="hidden" name="addressId" defaultValue={addressId} />
380
+ <label htmlFor="firstName">First name*</label>
381
+ <input
382
+ aria-label="First name"
383
+ autoComplete="given-name"
384
+ defaultValue={address?.firstName ?? ''}
385
+ id="firstName"
386
+ name="firstName"
387
+ placeholder="First name"
388
+ required
389
+ type="text"
390
+ />
391
+ <label htmlFor="lastName">Last name*</label>
392
+ <input
393
+ aria-label="Last name"
394
+ autoComplete="family-name"
395
+ defaultValue={address?.lastName ?? ''}
396
+ id="lastName"
397
+ name="lastName"
398
+ placeholder="Last name"
399
+ required
400
+ type="text"
401
+ />
402
+ <label htmlFor="company">Company</label>
403
+ <input
404
+ aria-label="Company"
405
+ autoComplete="organization"
406
+ defaultValue={address?.company ?? ''}
407
+ id="company"
408
+ name="company"
409
+ placeholder="Company"
410
+ type="text"
411
+ />
412
+ <label htmlFor="address1">Address line*</label>
413
+ <input
414
+ aria-label="Address line 1"
415
+ autoComplete="address-line1"
416
+ defaultValue={address?.address1 ?? ''}
417
+ id="address1"
418
+ name="address1"
419
+ placeholder="Address line 1*"
420
+ required
421
+ type="text"
422
+ />
423
+ <label htmlFor="address2">Address line 2</label>
424
+ <input
425
+ aria-label="Address line 2"
426
+ autoComplete="address-line2"
427
+ defaultValue={address?.address2 ?? ''}
428
+ id="address2"
429
+ name="address2"
430
+ placeholder="Address line 2"
431
+ type="text"
432
+ />
433
+ <label htmlFor="city">City*</label>
434
+ <input
435
+ aria-label="City"
436
+ autoComplete="address-level2"
437
+ defaultValue={address?.city ?? ''}
438
+ id="city"
439
+ name="city"
440
+ placeholder="City"
441
+ required
442
+ type="text"
443
+ />
444
+ <label htmlFor="zoneCode">State / Province*</label>
445
+ <input
446
+ aria-label="State/Province"
447
+ autoComplete="address-level1"
448
+ defaultValue={address?.zoneCode ?? ''}
449
+ id="zoneCode"
450
+ name="zoneCode"
451
+ placeholder="State / Province"
452
+ required
453
+ type="text"
454
+ />
455
+ <label htmlFor="zip">Zip / Postal Code*</label>
456
+ <input
457
+ aria-label="Zip"
458
+ autoComplete="postal-code"
459
+ defaultValue={address?.zip ?? ''}
460
+ id="zip"
461
+ name="zip"
462
+ placeholder="Zip / Postal Code"
463
+ required
464
+ type="text"
465
+ />
466
+ <label htmlFor="territoryCode">Country Code*</label>
467
+ <input
468
+ aria-label="territoryCode"
469
+ autoComplete="country"
470
+ defaultValue={address?.territoryCode ?? ''}
471
+ id="territoryCode"
472
+ name="territoryCode"
473
+ placeholder="Country"
474
+ required
475
+ type="text"
476
+ maxLength={2}
477
+ />
478
+ <label htmlFor="phoneNumber">Phone</label>
479
+ <input
480
+ aria-label="Phone Number"
481
+ autoComplete="tel"
482
+ defaultValue={address?.phoneNumber ?? ''}
483
+ id="phoneNumber"
484
+ name="phoneNumber"
485
+ placeholder="+16135551111"
486
+ pattern="^\+?[1-9]\d{3,14}$"
487
+ type="tel"
488
+ />
489
+ <div>
490
+ <input
491
+ defaultChecked={isDefaultAddress}
492
+ id="defaultAddress"
493
+ name="defaultAddress"
494
+ type="checkbox"
495
+ />
496
+ <label htmlFor="defaultAddress">Set as default address</label>
497
+ </div>
498
+ {error ? (
499
+ <p>
500
+ <mark>
501
+ <small>{error}</small>
502
+ </mark>
503
+ </p>
504
+ ) : (
505
+ <br />
506
+ )}
507
+ {children({
508
+ stateForMethod: (method) => (formMethod === method ? state : 'idle'),
509
+ })}
510
+ </fieldset>
511
+ </Form>
512
+ );
513
+ }
@@ -0,0 +1,195 @@
1
+ import {json, redirect, type LoaderFunctionArgs} from '@shopify/remix-oxygen';
2
+ import {useLoaderData, type MetaFunction} from '@remix-run/react';
3
+ import {Money, Image, flattenConnection} from '@shopify/hydrogen';
4
+ import type {OrderLineItemFullFragment} from 'customer-accountapi.generated';
5
+ import {CUSTOMER_ORDER_QUERY} from '~/graphql/customer-account/CustomerOrderQuery';
6
+
7
+ export const meta: MetaFunction<typeof loader> = ({data}) => {
8
+ return [{title: `Order ${data?.order?.name}`}];
9
+ };
10
+
11
+ export async function loader({params, context}: LoaderFunctionArgs) {
12
+ if (!params.id) {
13
+ return redirect('/account/orders');
14
+ }
15
+
16
+ const orderId = atob(params.id);
17
+ const {data, errors} = await context.customerAccount.query(
18
+ CUSTOMER_ORDER_QUERY,
19
+ {
20
+ variables: {orderId},
21
+ },
22
+ );
23
+
24
+ if (errors?.length || !data?.order) {
25
+ throw new Error('Order not found');
26
+ }
27
+
28
+ const {order} = data;
29
+
30
+ const lineItems = flattenConnection(order.lineItems);
31
+ const discountApplications = flattenConnection(order.discountApplications);
32
+ const fulfillmentStatus = flattenConnection(order.fulfillments)[0].status;
33
+
34
+ const firstDiscount = discountApplications[0]?.value;
35
+
36
+ const discountValue =
37
+ firstDiscount?.__typename === 'MoneyV2' && firstDiscount;
38
+
39
+ const discountPercentage =
40
+ firstDiscount?.__typename === 'PricingPercentageValue' &&
41
+ firstDiscount?.percentage;
42
+
43
+ return json({
44
+ order,
45
+ lineItems,
46
+ discountValue,
47
+ discountPercentage,
48
+ fulfillmentStatus,
49
+ });
50
+ }
51
+
52
+ export default function OrderRoute() {
53
+ const {
54
+ order,
55
+ lineItems,
56
+ discountValue,
57
+ discountPercentage,
58
+ fulfillmentStatus,
59
+ } = useLoaderData<typeof loader>();
60
+ return (
61
+ <div className="account-order">
62
+ <h2>Order {order.name}</h2>
63
+ <p>Placed on {new Date(order.processedAt!).toDateString()}</p>
64
+ <br />
65
+ <div>
66
+ <table>
67
+ <thead>
68
+ <tr>
69
+ <th scope="col">Product</th>
70
+ <th scope="col">Price</th>
71
+ <th scope="col">Quantity</th>
72
+ <th scope="col">Total</th>
73
+ </tr>
74
+ </thead>
75
+ <tbody>
76
+ {lineItems.map((lineItem, lineItemIndex) => (
77
+ // eslint-disable-next-line react/no-array-index-key
78
+ <OrderLineRow key={lineItemIndex} lineItem={lineItem} />
79
+ ))}
80
+ </tbody>
81
+ <tfoot>
82
+ {((discountValue && discountValue.amount) ||
83
+ discountPercentage) && (
84
+ <tr>
85
+ <th scope="row" colSpan={3}>
86
+ <p>Discounts</p>
87
+ </th>
88
+ <th scope="row">
89
+ <p>Discounts</p>
90
+ </th>
91
+ <td>
92
+ {discountPercentage ? (
93
+ <span>-{discountPercentage}% OFF</span>
94
+ ) : (
95
+ discountValue && <Money data={discountValue!} />
96
+ )}
97
+ </td>
98
+ </tr>
99
+ )}
100
+ <tr>
101
+ <th scope="row" colSpan={3}>
102
+ <p>Subtotal</p>
103
+ </th>
104
+ <th scope="row">
105
+ <p>Subtotal</p>
106
+ </th>
107
+ <td>
108
+ <Money data={order.subtotal!} />
109
+ </td>
110
+ </tr>
111
+ <tr>
112
+ <th scope="row" colSpan={3}>
113
+ Tax
114
+ </th>
115
+ <th scope="row">
116
+ <p>Tax</p>
117
+ </th>
118
+ <td>
119
+ <Money data={order.totalTax!} />
120
+ </td>
121
+ </tr>
122
+ <tr>
123
+ <th scope="row" colSpan={3}>
124
+ Total
125
+ </th>
126
+ <th scope="row">
127
+ <p>Total</p>
128
+ </th>
129
+ <td>
130
+ <Money data={order.totalPrice!} />
131
+ </td>
132
+ </tr>
133
+ </tfoot>
134
+ </table>
135
+ <div>
136
+ <h3>Shipping Address</h3>
137
+ {order?.shippingAddress ? (
138
+ <address>
139
+ <p>{order.shippingAddress.name}</p>
140
+ {order.shippingAddress.formatted ? (
141
+ <p>{order.shippingAddress.formatted}</p>
142
+ ) : (
143
+ ''
144
+ )}
145
+ {order.shippingAddress.formattedArea ? (
146
+ <p>{order.shippingAddress.formattedArea}</p>
147
+ ) : (
148
+ ''
149
+ )}
150
+ </address>
151
+ ) : (
152
+ <p>No shipping address defined</p>
153
+ )}
154
+ <h3>Status</h3>
155
+ <div>
156
+ <p>{fulfillmentStatus}</p>
157
+ </div>
158
+ </div>
159
+ </div>
160
+ <br />
161
+ <p>
162
+ <a target="_blank" href={order.statusPageUrl} rel="noreferrer">
163
+ View Order Status →
164
+ </a>
165
+ </p>
166
+ </div>
167
+ );
168
+ }
169
+
170
+ function OrderLineRow({lineItem}: {lineItem: OrderLineItemFullFragment}) {
171
+ return (
172
+ <tr key={lineItem.id}>
173
+ <td>
174
+ <div>
175
+ {lineItem?.image && (
176
+ <div>
177
+ <Image data={lineItem.image} width={96} height={96} />
178
+ </div>
179
+ )}
180
+ <div>
181
+ <p>{lineItem.title}</p>
182
+ <small>{lineItem.variantTitle}</small>
183
+ </div>
184
+ </div>
185
+ </td>
186
+ <td>
187
+ <Money data={lineItem.price!} />
188
+ </td>
189
+ <td>{lineItem.quantity}</td>
190
+ <td>
191
+ <Money data={lineItem.totalDiscount!} />
192
+ </td>
193
+ </tr>
194
+ );
195
+ }