@shopify/cli 3.85.3 → 3.85.5

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 (153) hide show
  1. package/dist/assets/hydrogen/bundle/analyzer.html +155 -148
  2. package/dist/assets/hydrogen/starter/CHANGELOG.md +125 -49
  3. package/dist/assets/hydrogen/starter/app/components/AddToCartButton.tsx +1 -1
  4. package/dist/assets/hydrogen/starter/app/components/CartLineItem.tsx +1 -1
  5. package/dist/assets/hydrogen/starter/app/components/CartMain.tsx +1 -1
  6. package/dist/assets/hydrogen/starter/app/components/CartSummary.tsx +62 -29
  7. package/dist/assets/hydrogen/starter/app/components/Header.tsx +1 -1
  8. package/dist/assets/hydrogen/starter/app/components/PageLayout.tsx +1 -1
  9. package/dist/assets/hydrogen/starter/app/components/ProductForm.tsx +2 -2
  10. package/dist/assets/hydrogen/starter/app/components/SearchForm.tsx +1 -1
  11. package/dist/assets/hydrogen/starter/app/components/SearchFormPredictive.tsx +8 -3
  12. package/dist/assets/hydrogen/starter/app/components/SearchResults.tsx +3 -11
  13. package/dist/assets/hydrogen/starter/app/components/SearchResultsPredictive.tsx +2 -6
  14. package/dist/assets/hydrogen/starter/app/entry.client.tsx +10 -2
  15. package/dist/assets/hydrogen/starter/app/entry.server.tsx +5 -3
  16. package/dist/assets/hydrogen/starter/app/graphql/customer-account/CustomerAddressMutations.ts +7 -4
  17. package/dist/assets/hydrogen/starter/app/graphql/customer-account/CustomerDetailsQuery.ts +1 -1
  18. package/dist/assets/hydrogen/starter/app/graphql/customer-account/CustomerOrderQuery.ts +4 -1
  19. package/dist/assets/hydrogen/starter/app/graphql/customer-account/CustomerOrdersQuery.ts +10 -5
  20. package/dist/assets/hydrogen/starter/app/graphql/customer-account/CustomerUpdateMutation.ts +3 -2
  21. package/dist/assets/hydrogen/starter/app/lib/context.ts +34 -17
  22. package/dist/assets/hydrogen/starter/app/lib/fragments.ts +1 -0
  23. package/dist/assets/hydrogen/starter/app/lib/orderFilters.ts +90 -0
  24. package/dist/assets/hydrogen/starter/app/lib/redirect.ts +1 -1
  25. package/dist/assets/hydrogen/starter/app/lib/session.ts +1 -1
  26. package/dist/assets/hydrogen/starter/app/lib/variants.ts +1 -1
  27. package/dist/assets/hydrogen/starter/app/root.tsx +23 -18
  28. package/dist/assets/hydrogen/starter/app/routes/$.tsx +2 -2
  29. package/dist/assets/hydrogen/starter/app/routes/[robots.txt].tsx +2 -2
  30. package/dist/assets/hydrogen/starter/app/routes/[sitemap.xml].tsx +2 -3
  31. package/dist/assets/hydrogen/starter/app/routes/_index.tsx +12 -8
  32. package/dist/assets/hydrogen/starter/app/routes/account.$.tsx +4 -3
  33. package/dist/assets/hydrogen/starter/app/routes/account._index.tsx +1 -1
  34. package/dist/assets/hydrogen/starter/app/routes/account.addresses.tsx +15 -11
  35. package/dist/assets/hydrogen/starter/app/routes/account.orders.$id.tsx +47 -22
  36. package/dist/assets/hydrogen/starter/app/routes/account.orders._index.tsx +152 -23
  37. package/dist/assets/hydrogen/starter/app/routes/account.profile.tsx +11 -8
  38. package/dist/assets/hydrogen/starter/app/routes/account.tsx +16 -4
  39. package/dist/assets/hydrogen/starter/app/routes/account_.authorize.tsx +2 -2
  40. package/dist/assets/hydrogen/starter/app/routes/account_.login.tsx +5 -3
  41. package/dist/assets/hydrogen/starter/app/routes/account_.logout.tsx +3 -2
  42. package/dist/assets/hydrogen/starter/app/routes/api.$version.[graphql.json].tsx +2 -2
  43. package/dist/assets/hydrogen/starter/app/routes/blogs.$blogHandle.$articleHandle.tsx +6 -10
  44. package/dist/assets/hydrogen/starter/app/routes/blogs.$blogHandle._index.tsx +10 -7
  45. package/dist/assets/hydrogen/starter/app/routes/blogs._index.tsx +13 -7
  46. package/dist/assets/hydrogen/starter/app/routes/cart.$lines.tsx +3 -2
  47. package/dist/assets/hydrogen/starter/app/routes/cart.tsx +13 -9
  48. package/dist/assets/hydrogen/starter/app/routes/collections.$handle.tsx +8 -11
  49. package/dist/assets/hydrogen/starter/app/routes/collections._index.tsx +6 -6
  50. package/dist/assets/hydrogen/starter/app/routes/collections.all.tsx +10 -7
  51. package/dist/assets/hydrogen/starter/app/routes/discount.$code.tsx +3 -2
  52. package/dist/assets/hydrogen/starter/app/routes/pages.$handle.tsx +8 -6
  53. package/dist/assets/hydrogen/starter/app/routes/policies.$handle.tsx +7 -4
  54. package/dist/assets/hydrogen/starter/app/routes/policies._index.tsx +19 -13
  55. package/dist/assets/hydrogen/starter/app/routes/products.$handle.tsx +9 -6
  56. package/dist/assets/hydrogen/starter/app/routes/search.tsx +14 -14
  57. package/dist/assets/hydrogen/starter/app/routes/sitemap.$type.$page[.xml].tsx +2 -3
  58. package/dist/assets/hydrogen/starter/app/routes.ts +1 -1
  59. package/dist/assets/hydrogen/starter/app/styles/app.css +53 -1
  60. package/dist/assets/hydrogen/starter/customer-accountapi.generated.d.ts +47 -13
  61. package/dist/assets/hydrogen/starter/env.d.ts +1 -39
  62. package/dist/assets/hydrogen/starter/eslint.config.js +35 -52
  63. package/dist/assets/hydrogen/starter/package.json +14 -15
  64. package/dist/assets/hydrogen/starter/react-router.config.ts +9 -3
  65. package/dist/assets/hydrogen/starter/server.ts +7 -7
  66. package/dist/assets/hydrogen/starter/storefrontapi.generated.d.ts +1 -1
  67. package/dist/assets/hydrogen/starter/tsconfig.json +17 -13
  68. package/dist/assets/hydrogen/starter/vite.config.ts +3 -0
  69. package/dist/assets/hydrogen/virtual-routes/components/RequestDetails.jsx +13 -20
  70. package/dist/assets/hydrogen/virtual-routes/routes/[.]well-known.appspecific.com[.]chrome[.]devtools[.]json.jsx +37 -0
  71. package/dist/{chokidar-TTCYG5AA.js → chokidar-HTKREZL3.js} +482 -7
  72. package/dist/{chunk-RPU6TIF2.js → chunk-3ULYQTX3.js} +7 -7
  73. package/dist/{chunk-IHXRXBUN.js → chunk-AVSSZ6MY.js} +6 -6
  74. package/dist/{chunk-I4ATBX6D.js → chunk-CHLX44DR.js} +4 -4
  75. package/dist/{chunk-UW4ASA6Y.js → chunk-CQSO2B3C.js} +2 -2
  76. package/dist/{chunk-OQKAZQIC.js → chunk-DHW4GI57.js} +5 -5
  77. package/dist/{chunk-HTTZVPR6.js → chunk-FGSQR4VQ.js} +3 -3
  78. package/dist/{chunk-3XWYM7QS.js → chunk-FMD2ETAR.js} +3 -3
  79. package/dist/{chunk-FGEKMLLA.js → chunk-FRHZJBJZ.js} +5 -5
  80. package/dist/{chunk-Y4H4HMEZ.js → chunk-G2I4CD5D.js} +2 -2
  81. package/dist/{chunk-HS2O5IHL.js → chunk-GG2ELY5O.js} +2 -3
  82. package/dist/{chunk-F7TU455C.js → chunk-GQ63RYWN.js} +2 -2
  83. package/dist/{chunk-VVUZFYUK.js → chunk-IAX3IWUQ.js} +4 -4
  84. package/dist/{chunk-EWEA4LRT.js → chunk-J6T7KVRL.js} +2 -2
  85. package/dist/{chunk-GRVHLYQS.js → chunk-JF6DQIJR.js} +3 -3
  86. package/dist/{chunk-JAUHWNMJ.js → chunk-MDDWB46W.js} +5 -5
  87. package/dist/{chunk-QHKSKL4E.js → chunk-MR763CPY.js} +3 -3
  88. package/dist/{chunk-6A6GDV25.js → chunk-MUZ7NPCX.js} +4 -4
  89. package/dist/{chunk-NLFRHIZY.js → chunk-NMGJYSTC.js} +5 -5
  90. package/dist/{chunk-D5DJSKHK.js → chunk-NTCXWD2Q.js} +111 -112
  91. package/dist/{chunk-HBANZKAF.js → chunk-O2O5GRI6.js} +3 -3
  92. package/dist/{chunk-CAONVM2S.js → chunk-ODJ7LSLO.js} +3 -3
  93. package/dist/{chunk-5RNGA7FX.js → chunk-P46WMXBU.js} +5 -5
  94. package/dist/{chunk-LJXXOFEJ.js → chunk-PFBQBDNU.js} +2 -2
  95. package/dist/{chunk-OURS5IQY.js → chunk-PZUWEJO3.js} +3 -3
  96. package/dist/{chunk-VR6Z7LKU.js → chunk-R6BNSDGA.js} +3 -3
  97. package/dist/{chunk-AMWSD3HH.js → chunk-RAZVOMJW.js} +3 -3
  98. package/dist/{chunk-C45MKMJT.js → chunk-RZPYG7LO.js} +28 -25
  99. package/dist/{chunk-EDXQ22O4.js → chunk-SMBX232U.js} +6 -6
  100. package/dist/{chunk-QP2MOS2Y.js → chunk-TE4CUUT4.js} +2 -2
  101. package/dist/{chunk-V5ONTA7N.js → chunk-TXX6R3WL.js} +2 -2
  102. package/dist/{chunk-L54PNQGV.js → chunk-TYHJPUOR.js} +2 -2
  103. package/dist/{chunk-XJBIASMX.js → chunk-UF2X5VHR.js} +3 -3
  104. package/dist/{chunk-ZHKIKKU7.js → chunk-UQT7B7DM.js} +3 -3
  105. package/dist/{chunk-6RJ7HBOQ.js → chunk-VVDGGMKJ.js} +3 -3
  106. package/dist/cli/commands/auth/login.js +10 -10
  107. package/dist/cli/commands/auth/login.test.js +11 -11
  108. package/dist/cli/commands/auth/logout.js +10 -10
  109. package/dist/cli/commands/auth/logout.test.js +11 -11
  110. package/dist/cli/commands/cache/clear.js +10 -10
  111. package/dist/cli/commands/debug/command-flags.js +10 -10
  112. package/dist/cli/commands/docs/generate.js +10 -10
  113. package/dist/cli/commands/docs/generate.test.js +10 -10
  114. package/dist/cli/commands/help.js +10 -10
  115. package/dist/cli/commands/kitchen-sink/async.js +11 -11
  116. package/dist/cli/commands/kitchen-sink/async.test.js +11 -11
  117. package/dist/cli/commands/kitchen-sink/index.js +13 -13
  118. package/dist/cli/commands/kitchen-sink/index.test.js +13 -13
  119. package/dist/cli/commands/kitchen-sink/prompts.js +11 -11
  120. package/dist/cli/commands/kitchen-sink/prompts.test.js +11 -11
  121. package/dist/cli/commands/kitchen-sink/static.js +11 -11
  122. package/dist/cli/commands/kitchen-sink/static.test.js +11 -11
  123. package/dist/cli/commands/notifications/generate.js +11 -11
  124. package/dist/cli/commands/notifications/list.js +11 -11
  125. package/dist/cli/commands/search.js +11 -11
  126. package/dist/cli/commands/upgrade.js +11 -11
  127. package/dist/cli/commands/version.js +11 -11
  128. package/dist/cli/commands/version.test.js +11 -11
  129. package/dist/cli/services/commands/notifications.js +6 -6
  130. package/dist/cli/services/commands/search.js +2 -2
  131. package/dist/cli/services/commands/search.test.js +2 -2
  132. package/dist/cli/services/commands/version.js +3 -3
  133. package/dist/cli/services/commands/version.test.js +4 -4
  134. package/dist/cli/services/kitchen-sink/async.js +2 -2
  135. package/dist/cli/services/kitchen-sink/prompts.js +2 -2
  136. package/dist/cli/services/kitchen-sink/static.js +2 -2
  137. package/dist/cli/services/upgrade.js +3 -3
  138. package/dist/cli/services/upgrade.test.js +4 -4
  139. package/dist/{custom-oclif-loader-YDKLB47A.js → custom-oclif-loader-X7GLA66E.js} +2 -2
  140. package/dist/{error-handler-BYZU2C5C.js → error-handler-QOTWDLMF.js} +8 -8
  141. package/dist/hooks/postrun.js +6 -6
  142. package/dist/hooks/prerun.js +7 -7
  143. package/dist/index.js +1800 -3480
  144. package/dist/{local-XLJD5WYP.js → local-FS3UI7PE.js} +2 -2
  145. package/dist/{morph-5D7H6MU2.js → morph-D4BXY376.js} +9 -9
  146. package/dist/{node-package-manager-I7AWSWJ4.js → node-package-manager-TX3WZQGI.js} +3 -3
  147. package/dist/tsconfig.tsbuildinfo +1 -1
  148. package/dist/{ui-2AJAFJYY.js → ui-PUN5STUM.js} +2 -2
  149. package/dist/{workerd-4DJKRJUB.js → workerd-GJFM5MYN.js} +12 -12
  150. package/oclif.manifest.json +11 -56
  151. package/package.json +8 -8
  152. package/dist/chokidar-XUA2BN3J.js +0 -1120
  153. package/dist/chunk-F7J5CUMZ.js +0 -497
@@ -1,25 +1,30 @@
1
- import {redirect, type LoaderFunctionArgs} from '@shopify/remix-oxygen';
2
- import { useLoaderData, type MetaFunction } from 'react-router';
3
- import {Money, Image, flattenConnection} from '@shopify/hydrogen';
4
- import type {OrderLineItemFullFragment} from 'customer-accountapi.generated';
1
+ import {redirect, useLoaderData} from 'react-router';
2
+ import type {Route} from './+types/account.orders.$id';
3
+ import {Money, Image} from '@shopify/hydrogen';
4
+ import type {
5
+ OrderLineItemFullFragment,
6
+ OrderQuery,
7
+ } from 'customer-accountapi.generated';
5
8
  import {CUSTOMER_ORDER_QUERY} from '~/graphql/customer-account/CustomerOrderQuery';
6
9
 
7
- export const meta: MetaFunction<typeof loader> = ({data}) => {
10
+ export const meta: Route.MetaFunction = ({data}) => {
8
11
  return [{title: `Order ${data?.order?.name}`}];
9
12
  };
10
13
 
11
- export async function loader({params, context}: LoaderFunctionArgs) {
14
+ export async function loader({params, context}: Route.LoaderArgs) {
15
+ const {customerAccount} = context;
12
16
  if (!params.id) {
13
17
  return redirect('/account/orders');
14
18
  }
15
19
 
16
20
  const orderId = atob(params.id);
17
- const {data, errors} = await context.customerAccount.query(
18
- CUSTOMER_ORDER_QUERY,
19
- {
20
- variables: {orderId},
21
- },
22
- );
21
+ const {data, errors}: {data: OrderQuery; errors?: Array<{message: string}>} =
22
+ await customerAccount.query(CUSTOMER_ORDER_QUERY, {
23
+ variables: {
24
+ orderId,
25
+ language: customerAccount.i18n.language,
26
+ },
27
+ });
23
28
 
24
29
  if (errors?.length || !data?.order) {
25
30
  throw new Error('Order not found');
@@ -27,20 +32,37 @@ export async function loader({params, context}: LoaderFunctionArgs) {
27
32
 
28
33
  const {order} = data;
29
34
 
30
- const lineItems = flattenConnection(order.lineItems);
31
- const discountApplications = flattenConnection(order.discountApplications);
35
+ // Extract line items directly from nodes array
36
+ const lineItems = order.lineItems.nodes;
37
+
38
+ // Extract discount applications directly from nodes array
39
+ const discountApplications = order.discountApplications.nodes;
32
40
 
33
- const fulfillmentStatus =
34
- flattenConnection(order.fulfillments)[0]?.status ?? 'N/A';
41
+ // Get fulfillment status from first fulfillment node
42
+ const fulfillmentStatus = order.fulfillments.nodes[0]?.status ?? 'N/A';
35
43
 
44
+ // Get first discount value with proper type checking
36
45
  const firstDiscount = discountApplications[0]?.value;
37
46
 
47
+ // Type guard for MoneyV2 discount
38
48
  const discountValue =
39
- firstDiscount?.__typename === 'MoneyV2' && firstDiscount;
49
+ firstDiscount?.__typename === 'MoneyV2'
50
+ ? (firstDiscount as Extract<
51
+ typeof firstDiscount,
52
+ {__typename: 'MoneyV2'}
53
+ >)
54
+ : null;
40
55
 
56
+ // Type guard for percentage discount
41
57
  const discountPercentage =
42
- firstDiscount?.__typename === 'PricingPercentageValue' &&
43
- firstDiscount?.percentage;
58
+ firstDiscount?.__typename === 'PricingPercentageValue'
59
+ ? (
60
+ firstDiscount as Extract<
61
+ typeof firstDiscount,
62
+ {__typename: 'PricingPercentageValue'}
63
+ >
64
+ ).percentage
65
+ : null;
44
66
 
45
67
  return {
46
68
  order,
@@ -60,9 +82,12 @@ export default function OrderRoute() {
60
82
  fulfillmentStatus,
61
83
  } = useLoaderData<typeof loader>();
62
84
  return (
63
- (<div className="account-order">
85
+ <div className="account-order">
64
86
  <h2>Order {order.name}</h2>
65
87
  <p>Placed on {new Date(order.processedAt!).toDateString()}</p>
88
+ {order.confirmationNumber && (
89
+ <p>Confirmation: {order.confirmationNumber}</p>
90
+ )}
66
91
  <br />
67
92
  <div>
68
93
  <table>
@@ -77,7 +102,7 @@ export default function OrderRoute() {
77
102
  <tbody>
78
103
  {lineItems.map((lineItem, lineItemIndex) => (
79
104
  // eslint-disable-next-line react/no-array-index-key
80
- (<OrderLineRow key={lineItemIndex} lineItem={lineItem} />)
105
+ <OrderLineRow key={lineItemIndex} lineItem={lineItem} />
81
106
  ))}
82
107
  </tbody>
83
108
  <tfoot>
@@ -165,7 +190,7 @@ export default function OrderRoute() {
165
190
  View Order Status →
166
191
  </a>
167
192
  </p>
168
- </div>)
193
+ </div>
169
194
  );
170
195
  }
171
196
 
@@ -1,10 +1,22 @@
1
- import { Link, useLoaderData, type MetaFunction } from 'react-router';
1
+ import {
2
+ Link,
3
+ useLoaderData,
4
+ useNavigation,
5
+ useSearchParams,
6
+ } from 'react-router';
7
+ import type {Route} from './+types/account.orders._index';
8
+ import {useRef} from 'react';
2
9
  import {
3
10
  Money,
4
11
  getPaginationVariables,
5
12
  flattenConnection,
6
13
  } from '@shopify/hydrogen';
7
- import {type LoaderFunctionArgs} from '@shopify/remix-oxygen';
14
+ import {
15
+ buildOrderSearchQuery,
16
+ parseOrderFilters,
17
+ ORDER_FILTER_FIELDS,
18
+ type OrderFilterParams,
19
+ } from '~/lib/orderFilters';
8
20
  import {CUSTOMER_ORDERS_QUERY} from '~/graphql/customer-account/CustomerOrdersQuery';
9
21
  import type {
10
22
  CustomerOrdersFragment,
@@ -12,67 +24,181 @@ import type {
12
24
  } from 'customer-accountapi.generated';
13
25
  import {PaginatedResourceSection} from '~/components/PaginatedResourceSection';
14
26
 
15
- export const meta: MetaFunction = () => {
27
+ type OrdersLoaderData = {
28
+ customer: CustomerOrdersFragment;
29
+ filters: OrderFilterParams;
30
+ };
31
+
32
+ export const meta: Route.MetaFunction = () => {
16
33
  return [{title: 'Orders'}];
17
34
  };
18
35
 
19
- export async function loader({request, context}: LoaderFunctionArgs) {
36
+ export async function loader({request, context}: Route.LoaderArgs) {
37
+ const {customerAccount} = context;
20
38
  const paginationVariables = getPaginationVariables(request, {
21
39
  pageBy: 20,
22
40
  });
23
41
 
24
- const {data, errors} = await context.customerAccount.query(
25
- CUSTOMER_ORDERS_QUERY,
26
- {
27
- variables: {
28
- ...paginationVariables,
29
- },
42
+ const url = new URL(request.url);
43
+ const filters = parseOrderFilters(url.searchParams);
44
+ const query = buildOrderSearchQuery(filters);
45
+
46
+ const {data, errors} = await customerAccount.query(CUSTOMER_ORDERS_QUERY, {
47
+ variables: {
48
+ ...paginationVariables,
49
+ query,
50
+ language: customerAccount.i18n.language,
30
51
  },
31
- );
52
+ });
32
53
 
33
54
  if (errors?.length || !data?.customer) {
34
55
  throw Error('Customer orders not found');
35
56
  }
36
57
 
37
- return {customer: data.customer};
58
+ return {customer: data.customer, filters};
38
59
  }
39
60
 
40
61
  export default function Orders() {
41
- const {customer} = useLoaderData<{customer: CustomerOrdersFragment}>();
62
+ const {customer, filters} = useLoaderData<OrdersLoaderData>();
42
63
  const {orders} = customer;
64
+
43
65
  return (
44
66
  <div className="orders">
45
- {orders.nodes.length ? <OrdersTable orders={orders} /> : <EmptyOrders />}
67
+ <OrderSearchForm currentFilters={filters} />
68
+ <OrdersTable orders={orders} filters={filters} />
46
69
  </div>
47
70
  );
48
71
  }
49
72
 
50
- function OrdersTable({orders}: Pick<CustomerOrdersFragment, 'orders'>) {
73
+ function OrdersTable({
74
+ orders,
75
+ filters,
76
+ }: {
77
+ orders: CustomerOrdersFragment['orders'];
78
+ filters: OrderFilterParams;
79
+ }) {
80
+ const hasFilters = !!(filters.name || filters.confirmationNumber);
81
+
51
82
  return (
52
- <div className="acccount-orders">
83
+ <div className="acccount-orders" aria-live="polite">
53
84
  {orders?.nodes.length ? (
54
85
  <PaginatedResourceSection connection={orders}>
55
86
  {({node: order}) => <OrderItem key={order.id} order={order} />}
56
87
  </PaginatedResourceSection>
57
88
  ) : (
58
- <EmptyOrders />
89
+ <EmptyOrders hasFilters={hasFilters} />
59
90
  )}
60
91
  </div>
61
92
  );
62
93
  }
63
94
 
64
- function EmptyOrders() {
95
+ function EmptyOrders({hasFilters = false}: {hasFilters?: boolean}) {
65
96
  return (
66
97
  <div>
67
- <p>You haven&apos;t placed any orders yet.</p>
68
- <br />
69
- <p>
70
- <Link to="/collections">Start Shopping →</Link>
71
- </p>
98
+ {hasFilters ? (
99
+ <>
100
+ <p>No orders found matching your search.</p>
101
+ <br />
102
+ <p>
103
+ <Link to="/account/orders">Clear filters →</Link>
104
+ </p>
105
+ </>
106
+ ) : (
107
+ <>
108
+ <p>You haven&apos;t placed any orders yet.</p>
109
+ <br />
110
+ <p>
111
+ <Link to="/collections">Start Shopping →</Link>
112
+ </p>
113
+ </>
114
+ )}
72
115
  </div>
73
116
  );
74
117
  }
75
118
 
119
+ function OrderSearchForm({
120
+ currentFilters,
121
+ }: {
122
+ currentFilters: OrderFilterParams;
123
+ }) {
124
+ const [searchParams, setSearchParams] = useSearchParams();
125
+ const navigation = useNavigation();
126
+ const isSearching =
127
+ navigation.state !== 'idle' &&
128
+ navigation.location?.pathname?.includes('orders');
129
+ const formRef = useRef<HTMLFormElement>(null);
130
+
131
+ const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
132
+ event.preventDefault();
133
+ const formData = new FormData(event.currentTarget);
134
+ const params = new URLSearchParams();
135
+
136
+ const name = formData.get(ORDER_FILTER_FIELDS.NAME)?.toString().trim();
137
+ const confirmationNumber = formData
138
+ .get(ORDER_FILTER_FIELDS.CONFIRMATION_NUMBER)
139
+ ?.toString()
140
+ .trim();
141
+
142
+ if (name) params.set(ORDER_FILTER_FIELDS.NAME, name);
143
+ if (confirmationNumber)
144
+ params.set(ORDER_FILTER_FIELDS.CONFIRMATION_NUMBER, confirmationNumber);
145
+
146
+ setSearchParams(params);
147
+ };
148
+
149
+ const hasFilters = currentFilters.name || currentFilters.confirmationNumber;
150
+
151
+ return (
152
+ <form
153
+ ref={formRef}
154
+ onSubmit={handleSubmit}
155
+ className="order-search-form"
156
+ aria-label="Search orders"
157
+ >
158
+ <fieldset className="order-search-fieldset">
159
+ <legend className="order-search-legend">Filter Orders</legend>
160
+
161
+ <div className="order-search-inputs">
162
+ <input
163
+ type="search"
164
+ name={ORDER_FILTER_FIELDS.NAME}
165
+ placeholder="Order #"
166
+ aria-label="Order number"
167
+ defaultValue={currentFilters.name || ''}
168
+ className="order-search-input"
169
+ />
170
+ <input
171
+ type="search"
172
+ name={ORDER_FILTER_FIELDS.CONFIRMATION_NUMBER}
173
+ placeholder="Confirmation #"
174
+ aria-label="Confirmation number"
175
+ defaultValue={currentFilters.confirmationNumber || ''}
176
+ className="order-search-input"
177
+ />
178
+ </div>
179
+
180
+ <div className="order-search-buttons">
181
+ <button type="submit" disabled={isSearching}>
182
+ {isSearching ? 'Searching' : 'Search'}
183
+ </button>
184
+ {hasFilters && (
185
+ <button
186
+ type="button"
187
+ disabled={isSearching}
188
+ onClick={() => {
189
+ setSearchParams(new URLSearchParams());
190
+ formRef.current?.reset();
191
+ }}
192
+ >
193
+ Clear
194
+ </button>
195
+ )}
196
+ </div>
197
+ </fieldset>
198
+ </form>
199
+ );
200
+ }
201
+
76
202
  function OrderItem({order}: {order: OrderItemFragment}) {
77
203
  const fulfillmentStatus = flattenConnection(order.fulfillments)[0]?.status;
78
204
  return (
@@ -82,6 +208,9 @@ function OrderItem({order}: {order: OrderItemFragment}) {
82
208
  <strong>#{order.number}</strong>
83
209
  </Link>
84
210
  <p>{new Date(order.processedAt).toDateString()}</p>
211
+ {order.confirmationNumber && (
212
+ <p>Confirmation: {order.confirmationNumber}</p>
213
+ )}
85
214
  <p>{order.financialStatus}</p>
86
215
  {fulfillmentStatus && <p>{fulfillmentStatus}</p>}
87
216
  <Money data={order.totalPrice} />
@@ -3,27 +3,29 @@ import type {CustomerUpdateInput} from '@shopify/hydrogen/customer-account-api-t
3
3
  import {CUSTOMER_UPDATE_MUTATION} from '~/graphql/customer-account/CustomerUpdateMutation';
4
4
  import {
5
5
  data,
6
- type ActionFunctionArgs,
7
- type LoaderFunctionArgs,
8
- } from '@shopify/remix-oxygen';
9
- import { Form, useActionData, useNavigation, useOutletContext, type MetaFunction } from 'react-router';
6
+ Form,
7
+ useActionData,
8
+ useNavigation,
9
+ useOutletContext,
10
+ } from 'react-router';
11
+ import type {Route} from './+types/account.profile';
10
12
 
11
13
  export type ActionResponse = {
12
14
  error: string | null;
13
15
  customer: CustomerFragment | null;
14
16
  };
15
17
 
16
- export const meta: MetaFunction = () => {
18
+ export const meta: Route.MetaFunction = () => {
17
19
  return [{title: 'Profile'}];
18
20
  };
19
21
 
20
- export async function loader({context}: LoaderFunctionArgs) {
21
- await context.customerAccount.handleAuthStatus();
22
+ export async function loader({context}: Route.LoaderArgs) {
23
+ context.customerAccount.handleAuthStatus();
22
24
 
23
25
  return {};
24
26
  }
25
27
 
26
- export async function action({request, context}: ActionFunctionArgs) {
28
+ export async function action({request, context}: Route.ActionArgs) {
27
29
  const {customerAccount} = context;
28
30
 
29
31
  if (request.method !== 'PUT') {
@@ -50,6 +52,7 @@ export async function action({request, context}: ActionFunctionArgs) {
50
52
  {
51
53
  variables: {
52
54
  customer,
55
+ language: customerAccount.i18n.language,
53
56
  },
54
57
  },
55
58
  );
@@ -1,14 +1,26 @@
1
- import {data as remixData, type LoaderFunctionArgs} from '@shopify/remix-oxygen';
2
- import { Form, NavLink, Outlet, useLoaderData } from 'react-router';
1
+ import {
2
+ data as remixData,
3
+ Form,
4
+ NavLink,
5
+ Outlet,
6
+ useLoaderData,
7
+ } from 'react-router';
8
+ import type {Route} from './+types/account';
3
9
  import {CUSTOMER_DETAILS_QUERY} from '~/graphql/customer-account/CustomerDetailsQuery';
4
10
 
5
11
  export function shouldRevalidate() {
6
12
  return true;
7
13
  }
8
14
 
9
- export async function loader({context}: LoaderFunctionArgs) {
10
- const {data, errors} = await context.customerAccount.query(
15
+ export async function loader({context}: Route.LoaderArgs) {
16
+ const {customerAccount} = context;
17
+ const {data, errors} = await customerAccount.query(
11
18
  CUSTOMER_DETAILS_QUERY,
19
+ {
20
+ variables: {
21
+ language: customerAccount.i18n.language,
22
+ },
23
+ },
12
24
  );
13
25
 
14
26
  if (errors?.length || !data?.customer) {
@@ -1,5 +1,5 @@
1
- import type {LoaderFunctionArgs} from '@shopify/remix-oxygen';
1
+ import type {Route} from './+types/account_.authorize';
2
2
 
3
- export async function loader({context}: LoaderFunctionArgs) {
3
+ export async function loader({context}: Route.LoaderArgs) {
4
4
  return context.customerAccount.authorize();
5
5
  }
@@ -1,5 +1,7 @@
1
- import type {LoaderFunctionArgs} from '@shopify/remix-oxygen';
1
+ import type {Route} from './+types/account_.login';
2
2
 
3
- export async function loader({request, context}: LoaderFunctionArgs) {
4
- return context.customerAccount.login();
3
+ export async function loader({request, context}: Route.LoaderArgs) {
4
+ return context.customerAccount.login({
5
+ countryCode: context.storefront.i18n.country,
6
+ });
5
7
  }
@@ -1,10 +1,11 @@
1
- import {redirect, type ActionFunctionArgs} from '@shopify/remix-oxygen';
1
+ import {redirect} from 'react-router';
2
+ import type {Route} from './+types/account_.logout';
2
3
 
3
4
  // if we don't implement this, /account/logout will get caught by account.$.tsx to do login
4
5
  export async function loader() {
5
6
  return redirect('/');
6
7
  }
7
8
 
8
- export async function action({context}: ActionFunctionArgs) {
9
+ export async function action({context}: Route.ActionArgs) {
9
10
  return context.customerAccount.logout();
10
11
  }
@@ -1,6 +1,6 @@
1
- import {LoaderFunctionArgs} from 'react-router';
1
+ import type {Route} from './+types/api.$version.[graphql.json]';
2
2
 
3
- export async function action({params, context, request}: LoaderFunctionArgs) {
3
+ export async function action({params, context, request}: Route.ActionArgs) {
4
4
  const response = await fetch(
5
5
  `https://${context.env.PUBLIC_CHECKOUT_DOMAIN}/api/${params.version}/graphql.json`,
6
6
  {
@@ -1,13 +1,13 @@
1
- import {type LoaderFunctionArgs} from '@shopify/remix-oxygen';
2
- import { useLoaderData, type MetaFunction } from 'react-router';
1
+ import {useLoaderData} from 'react-router';
2
+ import type {Route} from './+types/blogs.$blogHandle.$articleHandle';
3
3
  import {Image} from '@shopify/hydrogen';
4
4
  import {redirectIfHandleIsLocalized} from '~/lib/redirect';
5
5
 
6
- export const meta: MetaFunction<typeof loader> = ({data}) => {
6
+ export const meta: Route.MetaFunction = ({data}) => {
7
7
  return [{title: `Hydrogen | ${data?.article.title ?? ''} article`}];
8
8
  };
9
9
 
10
- export async function loader(args: LoaderFunctionArgs) {
10
+ export async function loader(args: Route.LoaderArgs) {
11
11
  // Start fetching non-critical data without blocking time to first byte
12
12
  const deferredData = loadDeferredData(args);
13
13
 
@@ -21,11 +21,7 @@ export async function loader(args: LoaderFunctionArgs) {
21
21
  * Load data necessary for rendering content above the fold. This is the critical data
22
22
  * needed to render the page. If it's unavailable, the whole page should 400 or 500 error.
23
23
  */
24
- async function loadCriticalData({
25
- context,
26
- request,
27
- params,
28
- }: LoaderFunctionArgs) {
24
+ async function loadCriticalData({context, request, params}: Route.LoaderArgs) {
29
25
  const {blogHandle, articleHandle} = params;
30
26
 
31
27
  if (!articleHandle || !blogHandle) {
@@ -65,7 +61,7 @@ async function loadCriticalData({
65
61
  * fetched after the initial page load. If it's unavailable, the page should still 200.
66
62
  * Make sure to not throw any errors here, as it will cause the page to 500.
67
63
  */
68
- function loadDeferredData({context}: LoaderFunctionArgs) {
64
+ function loadDeferredData({context}: Route.LoaderArgs) {
69
65
  return {};
70
66
  }
71
67
 
@@ -1,15 +1,18 @@
1
- import {type LoaderFunctionArgs} from '@shopify/remix-oxygen';
2
- import { Link, useLoaderData, type MetaFunction } from 'react-router';
1
+ import {
2
+ Link,
3
+ useLoaderData,
4
+ } from 'react-router';
5
+ import type {Route} from './+types/blogs.$blogHandle._index';
3
6
  import {Image, getPaginationVariables} from '@shopify/hydrogen';
4
7
  import type {ArticleItemFragment} from 'storefrontapi.generated';
5
8
  import {PaginatedResourceSection} from '~/components/PaginatedResourceSection';
6
9
  import {redirectIfHandleIsLocalized} from '~/lib/redirect';
7
10
 
8
- export const meta: MetaFunction<typeof loader> = ({data}) => {
11
+ export const meta: Route.MetaFunction = ({data}) => {
9
12
  return [{title: `Hydrogen | ${data?.blog.title ?? ''} blog`}];
10
13
  };
11
14
 
12
- export async function loader(args: LoaderFunctionArgs) {
15
+ export async function loader(args: Route.LoaderArgs) {
13
16
  // Start fetching non-critical data without blocking time to first byte
14
17
  const deferredData = loadDeferredData(args);
15
18
 
@@ -27,7 +30,7 @@ async function loadCriticalData({
27
30
  context,
28
31
  request,
29
32
  params,
30
- }: LoaderFunctionArgs) {
33
+ }: Route.LoaderArgs) {
31
34
  const paginationVariables = getPaginationVariables(request, {
32
35
  pageBy: 4,
33
36
  });
@@ -60,7 +63,7 @@ async function loadCriticalData({
60
63
  * fetched after the initial page load. If it's unavailable, the page should still 200.
61
64
  * Make sure to not throw any errors here, as it will cause the page to 500.
62
65
  */
63
- function loadDeferredData({context}: LoaderFunctionArgs) {
66
+ function loadDeferredData({context}: Route.LoaderArgs) {
64
67
  return {};
65
68
  }
66
69
 
@@ -72,7 +75,7 @@ export default function Blog() {
72
75
  <div className="blog">
73
76
  <h1>{blog.title}</h1>
74
77
  <div className="blog-grid">
75
- <PaginatedResourceSection connection={articles}>
78
+ <PaginatedResourceSection<ArticleItemFragment> connection={articles}>
76
79
  {({node: article, index}) => (
77
80
  <ArticleItem
78
81
  article={article}
@@ -1,13 +1,19 @@
1
- import {type LoaderFunctionArgs} from '@shopify/remix-oxygen';
2
- import { Link, useLoaderData, type MetaFunction } from 'react-router';
1
+ import {
2
+ Link,
3
+ useLoaderData,
4
+ } from 'react-router';
5
+ import type {Route} from './+types/blogs._index';
3
6
  import {getPaginationVariables} from '@shopify/hydrogen';
4
7
  import {PaginatedResourceSection} from '~/components/PaginatedResourceSection';
8
+ import type {BlogsQuery} from 'storefrontapi.generated';
5
9
 
6
- export const meta: MetaFunction = () => {
10
+ type BlogNode = BlogsQuery['blogs']['nodes'][0];
11
+
12
+ export const meta: Route.MetaFunction = () => {
7
13
  return [{title: `Hydrogen | Blogs`}];
8
14
  };
9
15
 
10
- export async function loader(args: LoaderFunctionArgs) {
16
+ export async function loader(args: Route.LoaderArgs) {
11
17
  // Start fetching non-critical data without blocking time to first byte
12
18
  const deferredData = loadDeferredData(args);
13
19
 
@@ -21,7 +27,7 @@ export async function loader(args: LoaderFunctionArgs) {
21
27
  * Load data necessary for rendering content above the fold. This is the critical data
22
28
  * needed to render the page. If it's unavailable, the whole page should 400 or 500 error.
23
29
  */
24
- async function loadCriticalData({context, request}: LoaderFunctionArgs) {
30
+ async function loadCriticalData({context, request}: Route.LoaderArgs) {
25
31
  const paginationVariables = getPaginationVariables(request, {
26
32
  pageBy: 10,
27
33
  });
@@ -43,7 +49,7 @@ async function loadCriticalData({context, request}: LoaderFunctionArgs) {
43
49
  * fetched after the initial page load. If it's unavailable, the page should still 200.
44
50
  * Make sure to not throw any errors here, as it will cause the page to 500.
45
51
  */
46
- function loadDeferredData({context}: LoaderFunctionArgs) {
52
+ function loadDeferredData({context}: Route.LoaderArgs) {
47
53
  return {};
48
54
  }
49
55
 
@@ -54,7 +60,7 @@ export default function Blogs() {
54
60
  <div className="blogs">
55
61
  <h1>Blogs</h1>
56
62
  <div className="blogs-grid">
57
- <PaginatedResourceSection connection={blogs}>
63
+ <PaginatedResourceSection<BlogNode> connection={blogs}>
58
64
  {({node: blog}) => (
59
65
  <Link
60
66
  className="blog"
@@ -1,4 +1,5 @@
1
- import {redirect, type LoaderFunctionArgs} from '@shopify/remix-oxygen';
1
+ import {redirect} from 'react-router';
2
+ import type {Route} from './+types/cart.$lines';
2
3
 
3
4
  /**
4
5
  * Automatically creates a new cart based on the URL and redirects straight to checkout.
@@ -18,7 +19,7 @@ import {redirect, type LoaderFunctionArgs} from '@shopify/remix-oxygen';
18
19
  *
19
20
  * ```
20
21
  */
21
- export async function loader({request, context, params}: LoaderFunctionArgs) {
22
+ export async function loader({request, context, params}: Route.LoaderArgs) {
22
23
  const {cart} = context;
23
24
  const {lines} = params;
24
25
  if (!lines) return redirect('/cart');