@shopify/create-hydrogen 5.0.23 → 5.0.25

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 (103) hide show
  1. package/dist/assets/hydrogen/bundle/analyzer.html +155 -148
  2. package/dist/assets/hydrogen/starter/.cursor/rules/hydrogen-react-router.mdc +5 -3
  3. package/dist/assets/hydrogen/starter/CHANGELOG.md +97 -9
  4. package/dist/assets/hydrogen/starter/app/components/AddToCartButton.tsx +1 -1
  5. package/dist/assets/hydrogen/starter/app/components/CartLineItem.tsx +1 -1
  6. package/dist/assets/hydrogen/starter/app/components/CartMain.tsx +1 -1
  7. package/dist/assets/hydrogen/starter/app/components/CartSummary.tsx +62 -29
  8. package/dist/assets/hydrogen/starter/app/components/Header.tsx +1 -1
  9. package/dist/assets/hydrogen/starter/app/components/PageLayout.tsx +1 -1
  10. package/dist/assets/hydrogen/starter/app/components/ProductForm.tsx +2 -2
  11. package/dist/assets/hydrogen/starter/app/components/SearchForm.tsx +1 -1
  12. package/dist/assets/hydrogen/starter/app/components/SearchFormPredictive.tsx +8 -3
  13. package/dist/assets/hydrogen/starter/app/components/SearchResults.tsx +3 -11
  14. package/dist/assets/hydrogen/starter/app/components/SearchResultsPredictive.tsx +2 -6
  15. package/dist/assets/hydrogen/starter/app/entry.client.tsx +10 -2
  16. package/dist/assets/hydrogen/starter/app/entry.server.tsx +5 -3
  17. package/dist/assets/hydrogen/starter/app/graphql/customer-account/CustomerAddressMutations.ts +7 -4
  18. package/dist/assets/hydrogen/starter/app/graphql/customer-account/CustomerDetailsQuery.ts +1 -1
  19. package/dist/assets/hydrogen/starter/app/graphql/customer-account/CustomerOrderQuery.ts +4 -1
  20. package/dist/assets/hydrogen/starter/app/graphql/customer-account/CustomerOrdersQuery.ts +10 -5
  21. package/dist/assets/hydrogen/starter/app/graphql/customer-account/CustomerUpdateMutation.ts +3 -2
  22. package/dist/assets/hydrogen/starter/app/lib/context.ts +34 -17
  23. package/dist/assets/hydrogen/starter/app/lib/fragments.ts +1 -0
  24. package/dist/assets/hydrogen/starter/app/lib/orderFilters.ts +90 -0
  25. package/dist/assets/hydrogen/starter/app/lib/redirect.ts +1 -1
  26. package/dist/assets/hydrogen/starter/app/lib/session.ts +1 -1
  27. package/dist/assets/hydrogen/starter/app/lib/variants.ts +1 -1
  28. package/dist/assets/hydrogen/starter/app/root.tsx +23 -18
  29. package/dist/assets/hydrogen/starter/app/routes/$.tsx +2 -2
  30. package/dist/assets/hydrogen/starter/app/routes/[robots.txt].tsx +2 -2
  31. package/dist/assets/hydrogen/starter/app/routes/[sitemap.xml].tsx +2 -3
  32. package/dist/assets/hydrogen/starter/app/routes/_index.tsx +12 -8
  33. package/dist/assets/hydrogen/starter/app/routes/account.$.tsx +4 -3
  34. package/dist/assets/hydrogen/starter/app/routes/account._index.tsx +1 -1
  35. package/dist/assets/hydrogen/starter/app/routes/account.addresses.tsx +15 -11
  36. package/dist/assets/hydrogen/starter/app/routes/account.orders.$id.tsx +47 -22
  37. package/dist/assets/hydrogen/starter/app/routes/account.orders._index.tsx +152 -23
  38. package/dist/assets/hydrogen/starter/app/routes/account.profile.tsx +11 -8
  39. package/dist/assets/hydrogen/starter/app/routes/account.tsx +16 -4
  40. package/dist/assets/hydrogen/starter/app/routes/account_.authorize.tsx +2 -2
  41. package/dist/assets/hydrogen/starter/app/routes/account_.login.tsx +5 -3
  42. package/dist/assets/hydrogen/starter/app/routes/account_.logout.tsx +3 -2
  43. package/dist/assets/hydrogen/starter/app/routes/api.$version.[graphql.json].tsx +2 -2
  44. package/dist/assets/hydrogen/starter/app/routes/blogs.$blogHandle.$articleHandle.tsx +6 -10
  45. package/dist/assets/hydrogen/starter/app/routes/blogs.$blogHandle._index.tsx +10 -7
  46. package/dist/assets/hydrogen/starter/app/routes/blogs._index.tsx +13 -7
  47. package/dist/assets/hydrogen/starter/app/routes/cart.$lines.tsx +3 -2
  48. package/dist/assets/hydrogen/starter/app/routes/cart.tsx +13 -9
  49. package/dist/assets/hydrogen/starter/app/routes/collections.$handle.tsx +8 -11
  50. package/dist/assets/hydrogen/starter/app/routes/collections._index.tsx +6 -6
  51. package/dist/assets/hydrogen/starter/app/routes/collections.all.tsx +10 -7
  52. package/dist/assets/hydrogen/starter/app/routes/discount.$code.tsx +3 -2
  53. package/dist/assets/hydrogen/starter/app/routes/pages.$handle.tsx +8 -6
  54. package/dist/assets/hydrogen/starter/app/routes/policies.$handle.tsx +7 -4
  55. package/dist/assets/hydrogen/starter/app/routes/policies._index.tsx +19 -13
  56. package/dist/assets/hydrogen/starter/app/routes/products.$handle.tsx +9 -6
  57. package/dist/assets/hydrogen/starter/app/routes/search.tsx +14 -14
  58. package/dist/assets/hydrogen/starter/app/routes/sitemap.$type.$page[.xml].tsx +2 -3
  59. package/dist/assets/hydrogen/starter/app/routes.ts +1 -1
  60. package/dist/assets/hydrogen/starter/app/styles/app.css +53 -1
  61. package/dist/assets/hydrogen/starter/customer-accountapi.generated.d.ts +47 -13
  62. package/dist/assets/hydrogen/starter/env.d.ts +1 -39
  63. package/dist/assets/hydrogen/starter/eslint.config.js +35 -52
  64. package/dist/assets/hydrogen/starter/package.json +14 -15
  65. package/dist/assets/hydrogen/starter/react-router.config.ts +9 -3
  66. package/dist/assets/hydrogen/starter/server.ts +7 -7
  67. package/dist/assets/hydrogen/starter/storefrontapi.generated.d.ts +1 -1
  68. package/dist/assets/hydrogen/starter/tsconfig.json +17 -13
  69. package/dist/assets/hydrogen/starter/vite.config.ts +4 -1
  70. package/dist/assets/hydrogen/virtual-routes/components/RequestDetails.jsx +13 -20
  71. package/dist/assets/hydrogen/virtual-routes/routes/[.]well-known.appspecific.com[.]chrome[.]devtools[.]json.jsx +37 -0
  72. package/dist/chunk-2LZQLWDR.js +1189 -0
  73. package/dist/{chunk-EO6F7WJJ.js → chunk-6YUUFKYO.js} +1 -1
  74. package/dist/chunk-AUULK6IN.js +5 -0
  75. package/dist/chunk-CJKPLQJ7.js +51 -0
  76. package/dist/{chunk-MNT4XW23.js → chunk-LBUW5UHX.js} +1 -1
  77. package/dist/chunk-RUCJI22O.js +3 -0
  78. package/dist/{chunk-PMDMUCNY.js → chunk-VXJIQGAB.js} +1 -1
  79. package/dist/chunk-Y5VZE2FH.js +32 -0
  80. package/dist/chunk-ZLNTSIDN.js +2 -0
  81. package/dist/create-app.js +293 -288
  82. package/dist/{del-72VO4HYK.js → del-VDYQZFAQ.js} +1 -1
  83. package/dist/devtools-3BYEW2L2.js +8 -0
  84. package/dist/error-handler-XRI3ZDLO.js +2 -0
  85. package/dist/is-wsl-52AELLDM.js +2 -0
  86. package/dist/{morph-3JSBLNUD.js → morph-S2LU6PQ4.js} +7 -7
  87. package/dist/{multipart-parser-QIHQVIZA.js → multipart-parser-MX4R5XJM.js} +1 -1
  88. package/dist/open-PMJ32HTM.js +2 -0
  89. package/dist/out-U7AI7XUQ.js +2 -0
  90. package/package.json +4 -2
  91. package/dist/chokidar-FXMI63T6.js +0 -12
  92. package/dist/chunk-3LZ6M5C2.js +0 -3
  93. package/dist/chunk-D7CI46F7.js +0 -10
  94. package/dist/chunk-FB327AH7.js +0 -5
  95. package/dist/chunk-MZPD7BFF.js +0 -23
  96. package/dist/chunk-NIHY2BIB.js +0 -1180
  97. package/dist/chunk-UASQ33JG.js +0 -45
  98. package/dist/chunk-VMIOG46Y.js +0 -2
  99. package/dist/devtools-DGRGSZU7.js +0 -8
  100. package/dist/error-handler-O653XSNU.js +0 -2
  101. package/dist/is-wsl-LL6KGQIK.js +0 -2
  102. package/dist/open-OD6DRFEG.js +0 -2
  103. package/dist/out-DXB3K325.js +0 -2
@@ -1,8 +1,9 @@
1
+ // NOTE: https://shopify.dev/docs/api/customer/latest/mutations/customerUpdate
1
2
  export const CUSTOMER_UPDATE_MUTATION = `#graphql
2
- # https://shopify.dev/docs/api/customer/latest/mutations/customerUpdate
3
3
  mutation customerUpdate(
4
4
  $customer: CustomerUpdateInput!
5
- ){
5
+ $language: LanguageCode
6
+ ) @inContext(language: $language) {
6
7
  customerUpdate(input: $customer) {
7
8
  customer {
8
9
  firstName
@@ -2,11 +2,27 @@ import {createHydrogenContext} from '@shopify/hydrogen';
2
2
  import {AppSession} from '~/lib/session';
3
3
  import {CART_QUERY_FRAGMENT} from '~/lib/fragments';
4
4
 
5
+ // Define the additional context object
6
+ const additionalContext = {
7
+ // Additional context for custom properties, CMS clients, 3P SDKs, etc.
8
+ // These will be available as both context.propertyName and context.get(propertyContext)
9
+ // Example of complex objects that could be added:
10
+ // cms: await createCMSClient(env),
11
+ // reviews: await createReviewsClient(env),
12
+ } as const;
13
+
14
+ // Automatically augment HydrogenAdditionalContext with the additional context type
15
+ type AdditionalContextType = typeof additionalContext;
16
+
17
+ declare global {
18
+ interface HydrogenAdditionalContext extends AdditionalContextType {}
19
+ }
20
+
5
21
  /**
6
- * The context implementation is separate from server.ts
7
- * so that type can be extracted for AppLoadContext
22
+ * Creates Hydrogen context for React Router 7.9.x
23
+ * Returns HydrogenRouterContextProvider with hybrid access patterns
8
24
  * */
9
- export async function createAppLoadContext(
25
+ export async function createHydrogenRouterContext(
10
26
  request: Request,
11
27
  env: Env,
12
28
  executionContext: ExecutionContext,
@@ -24,20 +40,21 @@ export async function createAppLoadContext(
24
40
  AppSession.init(request, [env.SESSION_SECRET]),
25
41
  ]);
26
42
 
27
- const hydrogenContext = createHydrogenContext({
28
- env,
29
- request,
30
- cache,
31
- waitUntil,
32
- session,
33
- i18n: {language: 'EN', country: 'US'},
34
- cart: {
35
- queryFragment: CART_QUERY_FRAGMENT,
43
+ const hydrogenContext = createHydrogenContext(
44
+ {
45
+ env,
46
+ request,
47
+ cache,
48
+ waitUntil,
49
+ session,
50
+ // Or detect from URL path based on locale subpath, cookies, or any other strategy
51
+ i18n: {language: 'EN', country: 'US'},
52
+ cart: {
53
+ queryFragment: CART_QUERY_FRAGMENT,
54
+ },
36
55
  },
37
- });
56
+ additionalContext,
57
+ );
38
58
 
39
- return {
40
- ...hydrogenContext,
41
- // declare additional Remix loader context
42
- };
59
+ return hydrogenContext;
43
60
  }
@@ -109,6 +109,7 @@ export const CART_QUERY_FRAGMENT = `#graphql
109
109
  updatedAt
110
110
  id
111
111
  appliedGiftCards {
112
+ id
112
113
  lastCharacters
113
114
  amountUsed {
114
115
  ...Money
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Field name constants for order filtering
3
+ */
4
+ export const ORDER_FILTER_FIELDS = {
5
+ NAME: 'name',
6
+ CONFIRMATION_NUMBER: 'confirmation_number',
7
+ } as const;
8
+
9
+ /**
10
+ * Parameters for filtering customer orders, see: https://shopify.dev/docs/api/customer/latest/queries/customer#returns-Customer.fields.orders.arguments.query
11
+ */
12
+ export interface OrderFilterParams {
13
+ /** Order name or number (e.g., "#1001" or "1001") */
14
+ name?: string;
15
+ /** Order confirmation number */
16
+ confirmationNumber?: string;
17
+ }
18
+
19
+ /**
20
+ * Sanitizes a filter value to prevent injection attacks or malformed queries.
21
+ * Allows only alphanumeric characters, underscore, and dash.
22
+ * @param value - The input string to sanitize
23
+ * @returns The sanitized string
24
+ */
25
+ function sanitizeFilterValue(value: string): string {
26
+ // Only allow alphanumeric, underscore, and dash
27
+ // Remove anything else to prevent injection
28
+ return value.replace(/[^a-zA-Z0-9_\-]/g, '');
29
+ }
30
+
31
+ /**
32
+ * Builds a query string for filtering customer orders using the Customer Account API
33
+ * @param filters - The filter parameters
34
+ * @returns A formatted query string for the GraphQL query parameter, or undefined if no filters
35
+ * @example
36
+ * buildOrderSearchQuery(\{ name: '1001' \}) // returns "name:1001"
37
+ * buildOrderSearchQuery(\{ name: '1001', confirmationNumber: 'ABC123' \}) // returns "name:1001 AND confirmation_number:ABC123"
38
+ */
39
+ export function buildOrderSearchQuery(
40
+ filters: OrderFilterParams,
41
+ ): string | undefined {
42
+ const queryParts: string[] = [];
43
+
44
+ if (filters.name) {
45
+ // Remove # if present and trim
46
+ const cleanName = filters.name.replace(/^#/, '').trim();
47
+ const sanitizedName = sanitizeFilterValue(cleanName);
48
+ if (sanitizedName) {
49
+ queryParts.push(`name:${sanitizedName}`);
50
+ }
51
+ }
52
+
53
+ if (filters.confirmationNumber) {
54
+ const cleanConfirmation = filters.confirmationNumber.trim();
55
+ const sanitizedConfirmation = sanitizeFilterValue(cleanConfirmation);
56
+ if (sanitizedConfirmation) {
57
+ queryParts.push(`confirmation_number:${sanitizedConfirmation}`);
58
+ }
59
+ }
60
+
61
+ return queryParts.length > 0 ? queryParts.join(' AND ') : undefined;
62
+ }
63
+
64
+ /**
65
+ * Parses order filter parameters from URLSearchParams
66
+ * @param searchParams - The URL search parameters
67
+ * @returns Parsed filter parameters
68
+ * @example
69
+ * const url = new URL('https://example.com/orders?name=1001&confirmation_number=ABC123');
70
+ * parseOrderFilters(url.searchParams) // returns \{ name: '1001', confirmationNumber: 'ABC123' \}
71
+ */
72
+ export function parseOrderFilters(
73
+ searchParams: URLSearchParams,
74
+ ): OrderFilterParams {
75
+ const filters: OrderFilterParams = {};
76
+
77
+ const name = searchParams.get(ORDER_FILTER_FIELDS.NAME);
78
+ if (name) {
79
+ filters.name = name;
80
+ }
81
+
82
+ const confirmationNumber = searchParams.get(
83
+ ORDER_FILTER_FIELDS.CONFIRMATION_NUMBER,
84
+ );
85
+ if (confirmationNumber) {
86
+ filters.confirmationNumber = confirmationNumber;
87
+ }
88
+
89
+ return filters;
90
+ }
@@ -1,4 +1,4 @@
1
- import {redirect} from '@shopify/remix-oxygen';
1
+ import {redirect} from 'react-router';
2
2
 
3
3
  export function redirectIfHandleIsLocalized(
4
4
  request: Request,
@@ -3,7 +3,7 @@ import {
3
3
  createCookieSessionStorage,
4
4
  type SessionStorage,
5
5
  type Session,
6
- } from '@shopify/remix-oxygen';
6
+ } from 'react-router';
7
7
 
8
8
  /**
9
9
  * This is a custom session implementation for your Hydrogen shop.
@@ -1,4 +1,4 @@
1
- import { useLocation } from 'react-router';
1
+ import {useLocation} from 'react-router';
2
2
  import type {SelectedOption} from '@shopify/hydrogen/storefront-api-types';
3
3
  import {useMemo} from 'react';
4
4
 
@@ -1,5 +1,4 @@
1
1
  import {Analytics, getShopAnalytics, useNonce} from '@shopify/hydrogen';
2
- import {type LoaderFunctionArgs} from '@shopify/remix-oxygen';
3
2
  import {
4
3
  Outlet,
5
4
  useRouteError,
@@ -11,6 +10,7 @@ import {
11
10
  ScrollRestoration,
12
11
  useRouteLoaderData,
13
12
  } from 'react-router';
13
+ import type {Route} from './+types/root';
14
14
  import favicon from '~/assets/favicon.svg';
15
15
  import {FOOTER_QUERY, HEADER_QUERY} from '~/lib/fragments';
16
16
  import resetStyles from '~/styles/reset.css?url';
@@ -65,7 +65,7 @@ export function links() {
65
65
  ];
66
66
  }
67
67
 
68
- export async function loader(args: LoaderFunctionArgs) {
68
+ export async function loader(args: Route.LoaderArgs) {
69
69
  // Start fetching non-critical data without blocking time to first byte
70
70
  const deferredData = loadDeferredData(args);
71
71
 
@@ -97,7 +97,7 @@ export async function loader(args: LoaderFunctionArgs) {
97
97
  * Load data necessary for rendering content above the fold. This is the critical data
98
98
  * needed to render the page. If it's unavailable, the whole page should 400 or 500 error.
99
99
  */
100
- async function loadCriticalData({context}: LoaderFunctionArgs) {
100
+ async function loadCriticalData({context}: Route.LoaderArgs) {
101
101
  const {storefront} = context;
102
102
 
103
103
  const [header] = await Promise.all([
@@ -118,7 +118,7 @@ async function loadCriticalData({context}: LoaderFunctionArgs) {
118
118
  * fetched after the initial page load. If it's unavailable, the page should still 200.
119
119
  * Make sure to not throw any errors here, as it will cause the page to 500.
120
120
  */
121
- function loadDeferredData({context}: LoaderFunctionArgs) {
121
+ function loadDeferredData({context}: Route.LoaderArgs) {
122
122
  const {storefront, customerAccount, cart} = context;
123
123
 
124
124
  // defer the footer query (below the fold)
@@ -129,7 +129,7 @@ function loadDeferredData({context}: LoaderFunctionArgs) {
129
129
  footerMenuHandle: 'footer', // Adjust to your footer menu handle
130
130
  },
131
131
  })
132
- .catch((error) => {
132
+ .catch((error: Error) => {
133
133
  // Log query errors, but don't throw them so the page can still render
134
134
  console.error(error);
135
135
  return null;
@@ -143,7 +143,6 @@ function loadDeferredData({context}: LoaderFunctionArgs) {
143
143
 
144
144
  export function Layout({children}: {children?: React.ReactNode}) {
145
145
  const nonce = useNonce();
146
- const data = useRouteLoaderData<RootLoader>('root');
147
146
 
148
147
  return (
149
148
  <html lang="en">
@@ -156,17 +155,7 @@ export function Layout({children}: {children?: React.ReactNode}) {
156
155
  <Links />
157
156
  </head>
158
157
  <body>
159
- {data ? (
160
- <Analytics.Provider
161
- cart={data.cart}
162
- shop={data.shop}
163
- consent={data.consent}
164
- >
165
- <PageLayout {...data}>{children}</PageLayout>
166
- </Analytics.Provider>
167
- ) : (
168
- children
169
- )}
158
+ {children}
170
159
  <ScrollRestoration nonce={nonce} />
171
160
  <Scripts nonce={nonce} />
172
161
  </body>
@@ -175,7 +164,23 @@ export function Layout({children}: {children?: React.ReactNode}) {
175
164
  }
176
165
 
177
166
  export default function App() {
178
- return <Outlet />;
167
+ const data = useRouteLoaderData<RootLoader>('root');
168
+
169
+ if (!data) {
170
+ return <Outlet />;
171
+ }
172
+
173
+ return (
174
+ <Analytics.Provider
175
+ cart={data.cart}
176
+ shop={data.shop}
177
+ consent={data.consent}
178
+ >
179
+ <PageLayout {...data}>
180
+ <Outlet />
181
+ </PageLayout>
182
+ </Analytics.Provider>
183
+ );
179
184
  }
180
185
 
181
186
  export function ErrorBoundary() {
@@ -1,6 +1,6 @@
1
- import type {LoaderFunctionArgs} from '@shopify/remix-oxygen';
1
+ import type {Route} from './+types/$';
2
2
 
3
- export async function loader({request}: LoaderFunctionArgs) {
3
+ export async function loader({request}: Route.LoaderArgs) {
4
4
  throw new Response(`${new URL(request.url).pathname} not found`, {
5
5
  status: 404,
6
6
  });
@@ -1,7 +1,7 @@
1
- import {type LoaderFunctionArgs} from '@shopify/remix-oxygen';
1
+ import type {Route} from './+types/[robots.txt]';
2
2
  import {parseGid} from '@shopify/hydrogen';
3
3
 
4
- export async function loader({request, context}: LoaderFunctionArgs) {
4
+ export async function loader({request, context}: Route.LoaderArgs) {
5
5
  const url = new URL(request.url);
6
6
 
7
7
  const {shop} = await context.storefront.query(ROBOTS_QUERY);
@@ -1,10 +1,10 @@
1
- import type {LoaderFunctionArgs} from '@shopify/remix-oxygen';
1
+ import type {Route} from './+types/[sitemap.xml]';
2
2
  import {getSitemapIndex} from '@shopify/hydrogen';
3
3
 
4
4
  export async function loader({
5
5
  request,
6
6
  context: {storefront},
7
- }: LoaderFunctionArgs) {
7
+ }: Route.LoaderArgs) {
8
8
  const response = await getSitemapIndex({
9
9
  storefront,
10
10
  request,
@@ -14,4 +14,3 @@ export async function loader({
14
14
 
15
15
  return response;
16
16
  }
17
-
@@ -1,18 +1,22 @@
1
- import {type LoaderFunctionArgs} from '@shopify/remix-oxygen';
2
- import { Await, useLoaderData, Link, type MetaFunction } from 'react-router';
1
+ import {
2
+ Await,
3
+ useLoaderData,
4
+ Link,
5
+ } from 'react-router';
6
+ import type {Route} from './+types/_index';
3
7
  import {Suspense} from 'react';
4
- import {Image, Money} from '@shopify/hydrogen';
8
+ import {Image} from '@shopify/hydrogen';
5
9
  import type {
6
10
  FeaturedCollectionFragment,
7
11
  RecommendedProductsQuery,
8
12
  } from 'storefrontapi.generated';
9
13
  import {ProductItem} from '~/components/ProductItem';
10
14
 
11
- export const meta: MetaFunction = () => {
15
+ export const meta: Route.MetaFunction = () => {
12
16
  return [{title: 'Hydrogen | Home'}];
13
17
  };
14
18
 
15
- export async function loader(args: LoaderFunctionArgs) {
19
+ export async function loader(args: Route.LoaderArgs) {
16
20
  // Start fetching non-critical data without blocking time to first byte
17
21
  const deferredData = loadDeferredData(args);
18
22
 
@@ -26,7 +30,7 @@ export async function loader(args: LoaderFunctionArgs) {
26
30
  * Load data necessary for rendering content above the fold. This is the critical data
27
31
  * needed to render the page. If it's unavailable, the whole page should 400 or 500 error.
28
32
  */
29
- async function loadCriticalData({context}: LoaderFunctionArgs) {
33
+ async function loadCriticalData({context}: Route.LoaderArgs) {
30
34
  const [{collections}] = await Promise.all([
31
35
  context.storefront.query(FEATURED_COLLECTION_QUERY),
32
36
  // Add other queries here, so that they are loaded in parallel
@@ -42,10 +46,10 @@ async function loadCriticalData({context}: LoaderFunctionArgs) {
42
46
  * fetched after the initial page load. If it's unavailable, the page should still 200.
43
47
  * Make sure to not throw any errors here, as it will cause the page to 500.
44
48
  */
45
- function loadDeferredData({context}: LoaderFunctionArgs) {
49
+ function loadDeferredData({context}: Route.LoaderArgs) {
46
50
  const recommendedProducts = context.storefront
47
51
  .query(RECOMMENDED_PRODUCTS_QUERY)
48
- .catch((error) => {
52
+ .catch((error: Error) => {
49
53
  // Log query errors, but don't throw them so the page can still render
50
54
  console.error(error);
51
55
  return null;
@@ -1,8 +1,9 @@
1
- import {redirect, type LoaderFunctionArgs} from '@shopify/remix-oxygen';
1
+ import {redirect} from 'react-router';
2
+ import type {Route} from './+types/account.$';
2
3
 
3
4
  // fallback wild card for all unauthenticated routes in account section
4
- export async function loader({context}: LoaderFunctionArgs) {
5
- await context.customerAccount.handleAuthStatus();
5
+ export async function loader({context}: Route.LoaderArgs) {
6
+ context.customerAccount.handleAuthStatus();
6
7
 
7
8
  return redirect('/account');
8
9
  }
@@ -1,4 +1,4 @@
1
- import {redirect} from '@shopify/remix-oxygen';
1
+ import {redirect} from 'react-router';
2
2
 
3
3
  export async function loader() {
4
4
  return redirect('/account/orders');
@@ -5,17 +5,13 @@ import type {
5
5
  } from 'customer-accountapi.generated';
6
6
  import {
7
7
  data,
8
- type ActionFunctionArgs,
9
- type LoaderFunctionArgs,
10
- } from '@shopify/remix-oxygen';
11
- import {
12
8
  Form,
13
9
  useActionData,
14
10
  useNavigation,
15
11
  useOutletContext,
16
- type MetaFunction,
17
12
  type Fetcher,
18
13
  } from 'react-router';
14
+ import type {Route} from './+types/account.addresses';
19
15
  import {
20
16
  UPDATE_ADDRESS_MUTATION,
21
17
  DELETE_ADDRESS_MUTATION,
@@ -31,17 +27,17 @@ export type ActionResponse = {
31
27
  updatedAddress?: AddressFragment;
32
28
  };
33
29
 
34
- export const meta: MetaFunction = () => {
30
+ export const meta: Route.MetaFunction = () => {
35
31
  return [{title: 'Addresses'}];
36
32
  };
37
33
 
38
- export async function loader({context}: LoaderFunctionArgs) {
39
- await context.customerAccount.handleAuthStatus();
34
+ export async function loader({context}: Route.LoaderArgs) {
35
+ context.customerAccount.handleAuthStatus();
40
36
 
41
37
  return {};
42
38
  }
43
39
 
44
- export async function action({request, context}: ActionFunctionArgs) {
40
+ export async function action({request, context}: Route.ActionArgs) {
45
41
  const {customerAccount} = context;
46
42
 
47
43
  try {
@@ -96,7 +92,11 @@ export async function action({request, context}: ActionFunctionArgs) {
96
92
  const {data, errors} = await customerAccount.mutate(
97
93
  CREATE_ADDRESS_MUTATION,
98
94
  {
99
- variables: {address, defaultAddress},
95
+ variables: {
96
+ address,
97
+ defaultAddress,
98
+ language: customerAccount.i18n.language,
99
+ },
100
100
  },
101
101
  );
102
102
 
@@ -145,6 +145,7 @@ export async function action({request, context}: ActionFunctionArgs) {
145
145
  address,
146
146
  addressId: decodeURIComponent(addressId),
147
147
  defaultAddress,
148
+ language: customerAccount.i18n.language,
148
149
  },
149
150
  },
150
151
  );
@@ -190,7 +191,10 @@ export async function action({request, context}: ActionFunctionArgs) {
190
191
  const {data, errors} = await customerAccount.mutate(
191
192
  DELETE_ADDRESS_MUTATION,
192
193
  {
193
- variables: {addressId: decodeURIComponent(addressId)},
194
+ variables: {
195
+ addressId: decodeURIComponent(addressId),
196
+ language: customerAccount.i18n.language,
197
+ },
194
198
  },
195
199
  );
196
200
 
@@ -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