shopify 3.93.2 → 3.94.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 (111) hide show
  1. package/dist/assets/dev-console/extensions/dev-console/assets/index-Bm_GpKQW.js +51 -0
  2. package/dist/assets/dev-console/index.html +1 -1
  3. package/dist/assets/hydrogen/starter/CHANGELOG.md +0 -64
  4. package/dist/assets/hydrogen/starter/app/components/Aside.tsx +2 -4
  5. package/dist/assets/hydrogen/starter/app/components/CartMain.tsx +3 -6
  6. package/dist/assets/hydrogen/starter/app/components/CartSummary.tsx +23 -117
  7. package/dist/assets/hydrogen/starter/app/components/Header.tsx +3 -3
  8. package/dist/assets/hydrogen/starter/app/components/PaginatedResourceSection.tsx +4 -24
  9. package/dist/assets/hydrogen/starter/app/components/ProductPrice.tsx +1 -1
  10. package/dist/assets/hydrogen/starter/app/routes/[robots.txt].tsx +53 -14
  11. package/dist/assets/hydrogen/starter/app/routes/_index.tsx +4 -11
  12. package/dist/assets/hydrogen/starter/app/routes/account.$.tsx +1 -1
  13. package/dist/assets/hydrogen/starter/app/routes/account.addresses.tsx +2 -2
  14. package/dist/assets/hydrogen/starter/app/routes/account.profile.tsx +1 -1
  15. package/dist/assets/hydrogen/starter/app/routes/api.$version.[graphql.json].tsx +14 -0
  16. package/dist/assets/hydrogen/starter/package.json +5 -5
  17. package/dist/assets/hydrogen/starter/storefrontapi.generated.d.ts +11 -0
  18. package/dist/assets/hydrogen/starter/vite.config.ts +1 -1
  19. package/dist/assets/hydrogen/vite/vite.config.js +1 -1
  20. package/dist/chunk-52KHWFYF.js +1 -0
  21. package/dist/chunk-7F3HZJH4.js +3 -0
  22. package/dist/{chunk-4VZV4LQX.js → chunk-7JFIBCHH.js} +66 -66
  23. package/dist/{chunk-MX6WWR5F.js → chunk-AQOYGO3U.js} +16 -16
  24. package/dist/{chunk-IG47ZDRU.js → chunk-DBDY2YEY.js} +1 -1
  25. package/dist/chunk-DEW5QFGH.js +415 -0
  26. package/dist/chunk-FU2Y2M3M.js +5 -0
  27. package/dist/chunk-JBSYWYIP.js +2 -0
  28. package/dist/chunk-QYR5VPQA.js +4 -0
  29. package/dist/{chunk-5FCKEHCK.js → chunk-R6N4NGU6.js} +154 -154
  30. package/dist/{chunk-XVFYDYZA.js → chunk-SVA22NZQ.js} +209 -211
  31. package/dist/cli/commands/config/autoupgrade/constants.d.ts +5 -0
  32. package/dist/cli/commands/config/autoupgrade/constants.js +6 -0
  33. package/dist/cli/commands/config/autoupgrade/off.d.ts +7 -0
  34. package/dist/cli/commands/config/autoupgrade/off.js +22 -0
  35. package/dist/cli/commands/config/autoupgrade/on.d.ts +7 -0
  36. package/dist/cli/commands/config/autoupgrade/on.js +22 -0
  37. package/dist/cli/commands/config/autoupgrade/status.d.ts +7 -0
  38. package/dist/cli/commands/config/autoupgrade/status.js +30 -0
  39. package/dist/cli/commands/docs/generate.d.ts +1 -1
  40. package/dist/cli/commands/docs/generate.js +6 -3
  41. package/dist/cli/commands/upgrade.js +3 -1
  42. package/dist/cli/services/kitchen-sink/static.js +1 -1
  43. package/dist/configs/all.yml +3 -0
  44. package/dist/configs/recommended.yml +3 -0
  45. package/dist/data/filters.json +30 -0
  46. package/dist/data/objects.json +17 -1
  47. package/dist/data/setting.json +25 -0
  48. package/dist/data/shopify_system_translations.json +29 -6
  49. package/dist/error-handler-JHFQZGYG.js +1 -0
  50. package/dist/hooks/postrun.js +1 -1
  51. package/dist/hooks/prerun.js +1 -1
  52. package/dist/{http-proxy-node16-DSQMBVDI.js → http-proxy-node16-TTURN6MD.js} +1 -1
  53. package/dist/index.js +1117 -1121
  54. package/dist/lib-3WHF5XD3.js +1 -0
  55. package/dist/{local-WHQ3ZS4K.js → local-4PW2CHVR.js} +1 -1
  56. package/dist/{morph-Q32V442A.js → morph-DQREIZD2.js} +1 -1
  57. package/dist/node-package-manager-NTQEYCSE.js +1 -0
  58. package/dist/path-HUAU3YBW.js +1 -0
  59. package/dist/tsconfig.tsbuildinfo +1 -1
  60. package/dist/{ui-GZ7DOSHP.js → ui-7Z2HOOEO.js} +1 -1
  61. package/dist/{workerd-LJU6AVMQ.js → workerd-VL54JTEU.js} +1 -1
  62. package/oclif.manifest.json +65 -4
  63. package/package.json +8 -7
  64. package/dist/assets/dev-console/extensions/dev-console/assets/index-BnXVL6nA.js +0 -78
  65. package/dist/chunk-4QL77VYJ.js +0 -415
  66. package/dist/chunk-DDTYWTF2.js +0 -1
  67. package/dist/chunk-FYQIRCLV.js +0 -5
  68. package/dist/chunk-NOSKVZWJ.js +0 -2
  69. package/dist/chunk-QJEBL3WX.js +0 -4
  70. package/dist/cli/commands/store/auth.d.ts +0 -15
  71. package/dist/cli/commands/store/auth.js +0 -46
  72. package/dist/cli/commands/store/execute.d.ts +0 -21
  73. package/dist/cli/commands/store/execute.js +0 -89
  74. package/dist/cli/services/store/auth/callback.d.ts +0 -8
  75. package/dist/cli/services/store/auth/callback.js +0 -140
  76. package/dist/cli/services/store/auth/config.d.ts +0 -6
  77. package/dist/cli/services/store/auth/config.js +0 -15
  78. package/dist/cli/services/store/auth/existing-scopes.d.ts +0 -5
  79. package/dist/cli/services/store/auth/existing-scopes.js +0 -40
  80. package/dist/cli/services/store/auth/index.d.ts +0 -18
  81. package/dist/cli/services/store/auth/index.js +0 -88
  82. package/dist/cli/services/store/auth/pkce.d.ts +0 -36
  83. package/dist/cli/services/store/auth/pkce.js +0 -49
  84. package/dist/cli/services/store/auth/recovery.d.ts +0 -4
  85. package/dist/cli/services/store/auth/recovery.js +0 -17
  86. package/dist/cli/services/store/auth/result.d.ts +0 -24
  87. package/dist/cli/services/store/auth/result.js +0 -39
  88. package/dist/cli/services/store/auth/scopes.d.ts +0 -4
  89. package/dist/cli/services/store/auth/scopes.js +0 -53
  90. package/dist/cli/services/store/auth/session-lifecycle.d.ts +0 -3
  91. package/dist/cli/services/store/auth/session-lifecycle.js +0 -69
  92. package/dist/cli/services/store/auth/session-store.d.ts +0 -32
  93. package/dist/cli/services/store/auth/session-store.js +0 -127
  94. package/dist/cli/services/store/auth/token-client.d.ts +0 -40
  95. package/dist/cli/services/store/auth/token-client.js +0 -95
  96. package/dist/cli/services/store/execute/admin-context.d.ts +0 -11
  97. package/dist/cli/services/store/execute/admin-context.js +0 -41
  98. package/dist/cli/services/store/execute/admin-transport.d.ts +0 -6
  99. package/dist/cli/services/store/execute/admin-transport.js +0 -42
  100. package/dist/cli/services/store/execute/index.d.ts +0 -13
  101. package/dist/cli/services/store/execute/index.js +0 -22
  102. package/dist/cli/services/store/execute/request.d.ts +0 -21
  103. package/dist/cli/services/store/execute/request.js +0 -88
  104. package/dist/cli/services/store/execute/result.d.ts +0 -3
  105. package/dist/cli/services/store/execute/result.js +0 -29
  106. package/dist/cli/services/store/execute/targets.d.ts +0 -18
  107. package/dist/cli/services/store/execute/targets.js +0 -21
  108. package/dist/error-handler-GZ2I7BG5.js +0 -1
  109. package/dist/lib-GGVLMXY5.js +0 -1
  110. package/dist/node-package-manager-6XMPTNUI.js +0 -1
  111. package/dist/path-IT7KPARG.js +0 -1
@@ -12,7 +12,7 @@
12
12
  <meta name="theme-color" content="#ffffff">
13
13
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
14
14
  <title>UI Extensions DevConsole</title>
15
- <script type="module" crossorigin src="/extensions/dev-console/assets/index-BnXVL6nA.js"></script>
15
+ <script type="module" crossorigin src="/extensions/dev-console/assets/index-Bm_GpKQW.js"></script>
16
16
  <link rel="stylesheet" crossorigin href="/extensions/dev-console/assets/index-DISeE29z.css">
17
17
  </head>
18
18
  <body>
@@ -1,69 +1,5 @@
1
1
  # skeleton
2
2
 
3
- ## 2026.4.0
4
-
5
- ### Major Changes
6
-
7
- - Update Storefront API and Customer Account API from 2026-01 to 2026-04. ([#3651](https://github.com/Shopify/hydrogen/pull/3651)) by [@itsjustriley](https://github.com/itsjustriley)
8
-
9
- ## Breaking changes
10
-
11
- **JSON metafield values limited to 128KB**: When using API version 2026-04 or later, the Storefront API limits JSON type metafield writes to 128KB. This limit applies at the API level - Hydrogen passes through to the Storefront API without additional restriction. Apps that used JSON metafields before April 1, 2026 are grandfathered at the existing 2MB limit. Large metafield values continue to be readable by all API versions.
12
-
13
- ## New features
14
-
15
- **New `MERCHANDISE_LINE_TRANSFORMERS_RUN_ERROR` cart error code**: Cart operations (`cartCreate`, `cartLinesAdd`, etc.) now return a specific `MERCHANDISE_LINE_TRANSFORMERS_RUN_ERROR` error code when a Cart Transform Function fails at runtime, instead of the previous generic `INVALID` error code. If you handle cart errors in your storefront code, you may want to add handling for this new code.
16
-
17
- ## Changelog links
18
- - [Storefront API 2026-04 changelog](https://shopify.dev/changelog?filter=api&api_version=2026-04&api_type=storefront-graphql)
19
- - [Customer Account API 2026-04 changelog](https://shopify.dev/changelog?filter=api&api_version=2026-04&api_type=customer-account-graphql)
20
-
21
- ### Patch Changes
22
-
23
- - Updated dependencies [[`e7215ed0d7a74cacfa8c935f414c1cf32fc2ccd0`](https://github.com/Shopify/hydrogen/commit/e7215ed0d7a74cacfa8c935f414c1cf32fc2ccd0), [`92ab8e8b59807bbdb5dbecaa18629292d5566135`](https://github.com/Shopify/hydrogen/commit/92ab8e8b59807bbdb5dbecaa18629292d5566135), [`b0caa5c013380c7837f049f48da089a1671e2c6d`](https://github.com/Shopify/hydrogen/commit/b0caa5c013380c7837f049f48da089a1671e2c6d)]:
24
- - @shopify/hydrogen@2026.4.0
25
-
26
- ## 2026.1.4
27
-
28
- ### Patch Changes
29
-
30
- - Remove redundant Storefront API proxy route from skeleton template. The server now automatically proxies requests to `/api/:version/graphql.json` via `createRequestHandler` with `proxyStandardRoutes: true` (enabled by default since December 2025). ([#3572](https://github.com/Shopify/hydrogen/pull/3572)) by [@itsjustriley](https://github.com/itsjustriley)
31
-
32
- Developers no longer need a manual route file for the tokenless Storefront API. Existing apps with this route can safely delete it - the server-level proxy provides the same functionality with better cookie forwarding and analytics integration.
33
-
34
- - Updated dependencies [[`b0a75c1d759706931876f056662de2497cb3e688`](https://github.com/Shopify/hydrogen/commit/b0a75c1d759706931876f056662de2497cb3e688), [`a44ee3566b9bb9a7f43f05dfaae6f1f2ab1d548f`](https://github.com/Shopify/hydrogen/commit/a44ee3566b9bb9a7f43f05dfaae6f1f2ab1d548f)]:
35
- - @shopify/hydrogen@2026.1.4
36
-
37
- ## 2026.1.3
38
-
39
- ### Patch Changes
40
-
41
- - Improve screen reader experience for paginated product grids by hiding decorative arrow characters from assistive technology. ([#3557](https://github.com/Shopify/hydrogen/pull/3557)) by [@itsjustriley](https://github.com/itsjustriley)
42
-
43
- - Fix broken `aria-label` on territory code input in address form. The label was the raw developer string `"territoryCode"` instead of a human-readable `"Country code"`. ([#3607](https://github.com/Shopify/hydrogen/pull/3607)) by [@itsjustriley](https://github.com/itsjustriley)
44
-
45
- - Add aria-label to ProductPrice for improved screen reader accessibility ([#3558](https://github.com/Shopify/hydrogen/pull/3558)) by [@itsjustriley](https://github.com/itsjustriley)
46
-
47
- - Updated dependencies [[`108243003a7f36349a446478f4e8ab0cade3e13a`](https://github.com/Shopify/hydrogen/commit/108243003a7f36349a446478f4e8ab0cade3e13a)]:
48
- - @shopify/hydrogen@2026.1.3
49
-
50
- ## 2026.1.2
51
-
52
- ### Patch Changes
53
-
54
- - Improve gift card accessibility in Skeleton template ([#3518](https://github.com/Shopify/hydrogen/pull/3518)) by [@itsjustriley](https://github.com/itsjustriley)
55
-
56
- - Updated shopify/cli dependencies for cli-hydrogen ([#3553](https://github.com/Shopify/hydrogen/pull/3553)) by [@andguy95](https://github.com/andguy95)
57
-
58
- - Updated loaders that used `customerAccount.handleAuthStatus()` to now await it. ([#3523](https://github.com/Shopify/hydrogen/pull/3523)) by [@fredericoo](https://github.com/fredericoo)
59
-
60
- ### Migration
61
-
62
- If you call `handleAuthStatus()` in your own loaders, update those callsites to use `await`.
63
-
64
- - Updated dependencies [[`16b0e7baca0dfd1fb330d12dac924c7593d169a8`](https://github.com/Shopify/hydrogen/commit/16b0e7baca0dfd1fb330d12dac924c7593d169a8), [`1c19b87782818dbdb4252754d2d44eb9a44fe50f`](https://github.com/Shopify/hydrogen/commit/1c19b87782818dbdb4252754d2d44eb9a44fe50f), [`029fa2d0e2297f67b37c650ba8e875ee5dee81b3`](https://github.com/Shopify/hydrogen/commit/029fa2d0e2297f67b37c650ba8e875ee5dee81b3)]:
65
- - @shopify/hydrogen@2026.1.2
66
-
67
3
  ## 2026.1.1
68
4
 
69
5
  ### Patch Changes
@@ -5,7 +5,6 @@ import {
5
5
  useEffect,
6
6
  useState,
7
7
  } from 'react';
8
- import {useId} from 'react';
9
8
 
10
9
  type AsideType = 'search' | 'cart' | 'mobile' | 'closed';
11
10
  type AsideContextValue = {
@@ -35,7 +34,7 @@ export function Aside({
35
34
  }) {
36
35
  const {type: activeType, close} = useAside();
37
36
  const expanded = type === activeType;
38
- const id = useId();
37
+
39
38
  useEffect(() => {
40
39
  const abortController = new AbortController();
41
40
 
@@ -58,12 +57,11 @@ export function Aside({
58
57
  aria-modal
59
58
  className={`overlay ${expanded ? 'expanded' : ''}`}
60
59
  role="dialog"
61
- aria-labelledby={id}
62
60
  >
63
61
  <button className="close-outside" onClick={close} />
64
62
  <aside>
65
63
  <header>
66
- <h3 id={id}>{heading}</h3>
64
+ <h3>{heading}</h3>
67
65
  <button className="close reset" onClick={close} aria-label="Close">
68
66
  &times;
69
67
  </button>
@@ -1,4 +1,4 @@
1
- import {useOptimisticCart} from '@shopify/hydrogen';
1
+ import {useOptimisticCart, type OptimisticCartLine} from '@shopify/hydrogen';
2
2
  import {Link} from 'react-router';
3
3
  import type {CartApiQueryFragment} from 'storefrontapi.generated';
4
4
  import {useAside} from '~/components/Aside';
@@ -50,10 +50,7 @@ export function CartMain({layout, cart: originalCart}: CartMainProps) {
50
50
  const childrenMap = getLineItemChildrenMap(cart?.lines?.nodes ?? []);
51
51
 
52
52
  return (
53
- <section
54
- className={className}
55
- aria-label={layout === 'page' ? 'Cart page' : 'Cart drawer'}
56
- >
53
+ <div className={className}>
57
54
  <CartEmpty hidden={linesCount} layout={layout} />
58
55
  <div className="cart-details">
59
56
  <p id="cart-lines" className="sr-only">
@@ -82,7 +79,7 @@ export function CartMain({layout, cart: originalCart}: CartMainProps) {
82
79
  </div>
83
80
  {cartHasItems && <CartSummary cart={cart} layout={layout} />}
84
81
  </div>
85
- </section>
82
+ </div>
86
83
  );
87
84
  }
88
85
 
@@ -1,7 +1,7 @@
1
1
  import type {CartApiQueryFragment} from 'storefrontapi.generated';
2
2
  import type {CartLayout} from '~/components/CartMain';
3
3
  import {CartForm, Money, type OptimisticCart} from '@shopify/hydrogen';
4
- import {useEffect, useId, useRef, useState} from 'react';
4
+ import {useEffect, useRef} from 'react';
5
5
  import {useFetcher} from 'react-router';
6
6
 
7
7
  type CartSummaryProps = {
@@ -12,16 +12,11 @@ type CartSummaryProps = {
12
12
  export function CartSummary({cart, layout}: CartSummaryProps) {
13
13
  const className =
14
14
  layout === 'page' ? 'cart-summary-page' : 'cart-summary-aside';
15
- const summaryId = useId();
16
- const discountsHeadingId = useId();
17
- const discountCodeInputId = useId();
18
- const giftCardHeadingId = useId();
19
- const giftCardInputId = useId();
20
15
 
21
16
  return (
22
- <div aria-labelledby={summaryId} className={className}>
23
- <h4 id={summaryId}>Totals</h4>
24
- <dl role="group" className="cart-subtotal">
17
+ <div aria-labelledby="cart-summary" className={className}>
18
+ <h4>Totals</h4>
19
+ <dl className="cart-subtotal">
25
20
  <dt>Subtotal</dt>
26
21
  <dd>
27
22
  {cart?.cost?.subtotalAmount?.amount ? (
@@ -31,16 +26,8 @@ export function CartSummary({cart, layout}: CartSummaryProps) {
31
26
  )}
32
27
  </dd>
33
28
  </dl>
34
- <CartDiscounts
35
- discountCodes={cart?.discountCodes}
36
- discountsHeadingId={discountsHeadingId}
37
- discountCodeInputId={discountCodeInputId}
38
- />
39
- <CartGiftCard
40
- giftCardCodes={cart?.appliedGiftCards}
41
- giftCardHeadingId={giftCardHeadingId}
42
- giftCardInputId={giftCardInputId}
43
- />
29
+ <CartDiscounts discountCodes={cart?.discountCodes} />
30
+ <CartGiftCard giftCardCodes={cart?.appliedGiftCards} />
44
31
  <CartCheckoutActions checkoutUrl={cart?.checkoutUrl} />
45
32
  </div>
46
33
  );
@@ -61,12 +48,8 @@ function CartCheckoutActions({checkoutUrl}: {checkoutUrl?: string}) {
61
48
 
62
49
  function CartDiscounts({
63
50
  discountCodes,
64
- discountsHeadingId,
65
- discountCodeInputId,
66
51
  }: {
67
52
  discountCodes?: CartApiQueryFragment['discountCodes'];
68
- discountsHeadingId: string;
69
- discountCodeInputId: string;
70
53
  }) {
71
54
  const codes: string[] =
72
55
  discountCodes
@@ -74,17 +57,13 @@ function CartDiscounts({
74
57
  ?.map(({code}) => code) || [];
75
58
 
76
59
  return (
77
- <section aria-label="Discounts">
60
+ <div>
78
61
  {/* Have existing discount, display it with a remove option */}
79
62
  <dl hidden={!codes.length}>
80
63
  <div>
81
- <dt id={discountsHeadingId}>Discounts</dt>
64
+ <dt>Discount(s)</dt>
82
65
  <UpdateDiscountForm>
83
- <div
84
- className="cart-discount"
85
- role="group"
86
- aria-labelledby={discountsHeadingId}
87
- >
66
+ <div className="cart-discount">
88
67
  <code>{codes?.join(', ')}</code>
89
68
  &nbsp;
90
69
  <button type="submit" aria-label="Remove discount">
@@ -98,11 +77,11 @@ function CartDiscounts({
98
77
  {/* Show an input to apply a discount */}
99
78
  <UpdateDiscountForm discountCodes={codes}>
100
79
  <div>
101
- <label htmlFor={discountCodeInputId} className="sr-only">
80
+ <label htmlFor="discount-code-input" className="sr-only">
102
81
  Discount code
103
82
  </label>
104
83
  <input
105
- id={discountCodeInputId}
84
+ id="discount-code-input"
106
85
  type="text"
107
86
  name="discountCode"
108
87
  placeholder="Discount code"
@@ -113,7 +92,7 @@ function CartDiscounts({
113
92
  </button>
114
93
  </div>
115
94
  </UpdateDiscountForm>
116
- </section>
95
+ </div>
117
96
  );
118
97
  }
119
98
 
@@ -139,110 +118,52 @@ function UpdateDiscountForm({
139
118
 
140
119
  function CartGiftCard({
141
120
  giftCardCodes,
142
- giftCardHeadingId,
143
- giftCardInputId,
144
121
  }: {
145
122
  giftCardCodes: CartApiQueryFragment['appliedGiftCards'] | undefined;
146
- giftCardHeadingId: string;
147
- giftCardInputId: string;
148
123
  }) {
149
124
  const giftCardCodeInput = useRef<HTMLInputElement>(null);
150
- const removeButtonRefs = useRef<Map<string, HTMLButtonElement>>(new Map());
151
- const previousCardIdsRef = useRef<string[]>([]);
152
125
  const giftCardAddFetcher = useFetcher({key: 'gift-card-add'});
153
- const [removedCardIndex, setRemovedCardIndex] = useState<number | null>(null);
154
126
 
155
127
  useEffect(() => {
156
128
  if (giftCardAddFetcher.data) {
157
- if (giftCardCodeInput.current !== null) {
158
- giftCardCodeInput.current.value = '';
159
- }
129
+ giftCardCodeInput.current!.value = '';
160
130
  }
161
131
  }, [giftCardAddFetcher.data]);
162
132
 
163
- useEffect(() => {
164
- const currentCardIds = giftCardCodes?.map((card) => card.id) || [];
165
-
166
- if (removedCardIndex !== null && giftCardCodes) {
167
- const focusTargetIndex = Math.min(
168
- removedCardIndex,
169
- giftCardCodes.length - 1,
170
- );
171
- const focusTargetCard = giftCardCodes[focusTargetIndex];
172
- const focusButton = focusTargetCard
173
- ? removeButtonRefs.current.get(focusTargetCard.id)
174
- : null;
175
-
176
- if (focusButton) {
177
- focusButton.focus();
178
- } else if (giftCardCodeInput.current) {
179
- giftCardCodeInput.current.focus();
180
- }
181
-
182
- setRemovedCardIndex(null);
183
- }
184
-
185
- previousCardIdsRef.current = currentCardIds;
186
- }, [giftCardCodes, removedCardIndex]);
187
-
188
- const handleRemoveClick = (cardId: string) => {
189
- const index = previousCardIdsRef.current.indexOf(cardId);
190
- if (index !== -1) {
191
- setRemovedCardIndex(index);
192
- }
193
- };
194
-
195
133
  return (
196
- <section aria-label="Gift cards">
134
+ <div>
197
135
  {giftCardCodes && giftCardCodes.length > 0 && (
198
136
  <dl>
199
- <dt id={giftCardHeadingId}>Applied Gift Card(s)</dt>
137
+ <dt>Applied Gift Card(s)</dt>
200
138
  {giftCardCodes.map((giftCard) => (
201
- <dd key={giftCard.id} className="cart-discount">
202
- <RemoveGiftCardForm
203
- giftCardId={giftCard.id}
204
- lastCharacters={giftCard.lastCharacters}
205
- onRemoveClick={() => handleRemoveClick(giftCard.id)}
206
- buttonRef={(el: HTMLButtonElement | null) => {
207
- if (el) {
208
- removeButtonRefs.current.set(giftCard.id, el);
209
- } else {
210
- removeButtonRefs.current.delete(giftCard.id);
211
- }
212
- }}
213
- >
139
+ <RemoveGiftCardForm key={giftCard.id} giftCardId={giftCard.id}>
140
+ <div className="cart-discount">
214
141
  <code>***{giftCard.lastCharacters}</code>
215
142
  &nbsp;
216
143
  <Money data={giftCard.amountUsed} />
217
- </RemoveGiftCardForm>
218
- </dd>
144
+ &nbsp;
145
+ <button type="submit">Remove</button>
146
+ </div>
147
+ </RemoveGiftCardForm>
219
148
  ))}
220
149
  </dl>
221
150
  )}
222
151
 
223
152
  <AddGiftCardForm fetcherKey="gift-card-add">
224
153
  <div>
225
- <label htmlFor={giftCardInputId} className="sr-only">
226
- Gift card code
227
- </label>
228
154
  <input
229
- id={giftCardInputId}
230
155
  type="text"
231
156
  name="giftCardCode"
232
157
  placeholder="Gift card code"
233
158
  ref={giftCardCodeInput}
234
159
  />
235
160
  &nbsp;
236
- <button
237
- type="submit"
238
- disabled={giftCardAddFetcher.state !== 'idle'}
239
- aria-label="Apply gift card code"
240
- >
161
+ <button type="submit" disabled={giftCardAddFetcher.state !== 'idle'}>
241
162
  Apply
242
163
  </button>
243
164
  </div>
244
165
  </AddGiftCardForm>
245
- </section>
166
+ </div>
246
167
  );
247
168
  }
248
169
 
@@ -266,16 +187,10 @@ function AddGiftCardForm({
266
187
 
267
188
  function RemoveGiftCardForm({
268
189
  giftCardId,
269
- lastCharacters,
270
190
  children,
271
- onRemoveClick,
272
- buttonRef,
273
191
  }: {
274
192
  giftCardId: string;
275
- lastCharacters: string;
276
193
  children: React.ReactNode;
277
- onRemoveClick?: () => void;
278
- buttonRef?: (el: HTMLButtonElement | null) => void;
279
194
  }) {
280
195
  return (
281
196
  <CartForm
@@ -286,15 +201,6 @@ function RemoveGiftCardForm({
286
201
  }}
287
202
  >
288
203
  {children}
289
- &nbsp;
290
- <button
291
- type="submit"
292
- aria-label={`Remove gift card ending in ${lastCharacters}`}
293
- onClick={onRemoveClick}
294
- ref={buttonRef}
295
- >
296
- Remove
297
- </button>
298
204
  </CartForm>
299
205
  );
300
206
  }
@@ -136,7 +136,7 @@ function SearchToggle() {
136
136
  );
137
137
  }
138
138
 
139
- function CartBadge({count}: {count: number}) {
139
+ function CartBadge({count}: {count: number | null}) {
140
140
  const {open} = useAside();
141
141
  const {publish, shop, cart, prevCart} = useAnalytics();
142
142
 
@@ -154,14 +154,14 @@ function CartBadge({count}: {count: number}) {
154
154
  } as CartViewPayload);
155
155
  }}
156
156
  >
157
- Cart <span aria-label={`(items: ${count})`}>{count}</span>
157
+ Cart {count === null ? <span>&nbsp;</span> : count}
158
158
  </a>
159
159
  );
160
160
  }
161
161
 
162
162
  function CartToggle({cart}: Pick<HeaderProps, 'cart'>) {
163
163
  return (
164
- <Suspense fallback={<CartBadge count={0} />}>
164
+ <Suspense fallback={<CartBadge count={null} />}>
165
165
  <Await resolve={cart}>
166
166
  <CartBanner />
167
167
  </Await>
@@ -2,17 +2,15 @@ import * as React from 'react';
2
2
  import {Pagination} from '@shopify/hydrogen';
3
3
 
4
4
  /**
5
- * <PaginatedResourceSection> encapsulates the previous and next pagination behaviors throughout your application.
5
+ * <PaginatedResourceSection > is a component that encapsulate how the previous and next behaviors throughout your application.
6
6
  */
7
7
  export function PaginatedResourceSection<NodesType>({
8
8
  connection,
9
9
  children,
10
- ariaLabel,
11
10
  resourcesClassName,
12
11
  }: {
13
12
  connection: React.ComponentProps<typeof Pagination<NodesType>>['connection'];
14
13
  children: React.FunctionComponent<{node: NodesType; index: number}>;
15
- ariaLabel?: string;
16
14
  resourcesClassName?: string;
17
15
  }) {
18
16
  return (
@@ -25,33 +23,15 @@ export function PaginatedResourceSection<NodesType>({
25
23
  return (
26
24
  <div>
27
25
  <PreviousLink>
28
- {isLoading ? (
29
- 'Loading...'
30
- ) : (
31
- <span>
32
- <span aria-hidden="true">↑</span> Load previous
33
- </span>
34
- )}
26
+ {isLoading ? 'Loading...' : <span>↑ Load previous</span>}
35
27
  </PreviousLink>
36
28
  {resourcesClassName ? (
37
- <div
38
- aria-label={ariaLabel}
39
- className={resourcesClassName}
40
- role={ariaLabel ? 'region' : undefined}
41
- >
42
- {resourcesMarkup}
43
- </div>
29
+ <div className={resourcesClassName}>{resourcesMarkup}</div>
44
30
  ) : (
45
31
  resourcesMarkup
46
32
  )}
47
33
  <NextLink>
48
- {isLoading ? (
49
- 'Loading...'
50
- ) : (
51
- <span>
52
- Load more <span aria-hidden="true">↓</span>
53
- </span>
54
- )}
34
+ {isLoading ? 'Loading...' : <span>Load more ↓</span>}
55
35
  </NextLink>
56
36
  </div>
57
37
  );
@@ -9,7 +9,7 @@ export function ProductPrice({
9
9
  compareAtPrice?: MoneyV2 | null;
10
10
  }) {
11
11
  return (
12
- <div aria-label="Price" className="product-price" role="group">
12
+ <div className="product-price">
13
13
  {compareAtPrice ? (
14
14
  <div className="product-price-on-sale">
15
15
  {price ? <Money data={price} /> : null}
@@ -1,8 +1,13 @@
1
1
  import type {Route} from './+types/[robots.txt]';
2
+ import {parseGid} from '@shopify/hydrogen';
2
3
 
3
- export function loader({request}: Route.LoaderArgs) {
4
+ export async function loader({request, context}: Route.LoaderArgs) {
4
5
  const url = new URL(request.url);
5
- const body = robotsTxtData({url: url.origin});
6
+
7
+ const {shop} = await context.storefront.query(ROBOTS_QUERY);
8
+
9
+ const shopId = parseGid(shop.id).id;
10
+ const body = robotsTxtData({url: url.origin, shopId});
6
11
 
7
12
  return new Response(body, {
8
13
  status: 200,
@@ -14,31 +19,35 @@ export function loader({request}: Route.LoaderArgs) {
14
19
  });
15
20
  }
16
21
 
17
- function robotsTxtData({url}: {url?: string}) {
22
+ function robotsTxtData({url, shopId}: {shopId?: string; url?: string}) {
18
23
  const sitemapUrl = url ? `${url}/sitemap.xml` : undefined;
19
24
 
20
25
  return `
21
26
  User-agent: *
22
- ${generalDisallowRules({sitemapUrl})}
27
+ ${generalDisallowRules({sitemapUrl, shopId})}
23
28
 
24
29
  # Google adsbot ignores robots.txt unless specifically named!
25
30
  User-agent: adsbot-google
26
- Disallow: /cart
27
- Disallow: /account
28
- Disallow: /search
29
- Allow: /search/
30
- Disallow: /search/?*
31
+ Disallow: /checkouts/
32
+ Disallow: /checkout
33
+ Disallow: /carts
34
+ Disallow: /orders
35
+ ${shopId ? `Disallow: /${shopId}/checkouts` : ''}
36
+ ${shopId ? `Disallow: /${shopId}/orders` : ''}
37
+ Disallow: /*?*oseid=*
38
+ Disallow: /*preview_theme_id*
39
+ Disallow: /*preview_script_id*
31
40
 
32
41
  User-agent: Nutch
33
42
  Disallow: /
34
43
 
35
44
  User-agent: AhrefsBot
36
45
  Crawl-delay: 10
37
- ${generalDisallowRules({sitemapUrl})}
46
+ ${generalDisallowRules({sitemapUrl, shopId})}
38
47
 
39
48
  User-agent: AhrefsSiteAudit
40
49
  Crawl-delay: 10
41
- ${generalDisallowRules({sitemapUrl})}
50
+ ${generalDisallowRules({sitemapUrl, shopId})}
42
51
 
43
52
  User-agent: MJ12bot
44
53
  Crawl-Delay: 10
@@ -52,8 +61,21 @@ Crawl-delay: 1
52
61
  * This function generates disallow rules that generally follow what Shopify's
53
62
  * Online Store has as defaults for their robots.txt
54
63
  */
55
- function generalDisallowRules({sitemapUrl}: {sitemapUrl?: string}) {
56
- return `Disallow: /cart
64
+ function generalDisallowRules({
65
+ shopId,
66
+ sitemapUrl,
67
+ }: {
68
+ shopId?: string;
69
+ sitemapUrl?: string;
70
+ }) {
71
+ return `Disallow: /admin
72
+ Disallow: /cart
73
+ Disallow: /orders
74
+ Disallow: /checkouts/
75
+ Disallow: /checkout
76
+ ${shopId ? `Disallow: /${shopId}/checkouts` : ''}
77
+ ${shopId ? `Disallow: /${shopId}/orders` : ''}
78
+ Disallow: /carts
57
79
  Disallow: /account
58
80
  Disallow: /collections/*sort_by*
59
81
  Disallow: /*/collections/*sort_by*
@@ -63,16 +85,33 @@ Disallow: /collections/*%2b*
63
85
  Disallow: /*/collections/*+*
64
86
  Disallow: /*/collections/*%2B*
65
87
  Disallow: /*/collections/*%2b*
66
- Disallow: /*/collections/*filter*&*filter*
88
+ Disallow: */collections/*filter*&*filter*
67
89
  Disallow: /blogs/*+*
68
90
  Disallow: /blogs/*%2B*
69
91
  Disallow: /blogs/*%2b*
70
92
  Disallow: /*/blogs/*+*
71
93
  Disallow: /*/blogs/*%2B*
72
94
  Disallow: /*/blogs/*%2b*
95
+ Disallow: /*?*oseid=*
96
+ Disallow: /*preview_theme_id*
97
+ Disallow: /*preview_script_id*
73
98
  Disallow: /policies/
99
+ Disallow: /*/*?*ls=*&ls=*
100
+ Disallow: /*/*?*ls%3D*%3Fls%3D*
101
+ Disallow: /*/*?*ls%3d*%3fls%3d*
74
102
  Disallow: /search
75
103
  Allow: /search/
76
104
  Disallow: /search/?*
105
+ Disallow: /apple-app-site-association
106
+ Disallow: /.well-known/shopify/monorail
77
107
  ${sitemapUrl ? `Sitemap: ${sitemapUrl}` : ''}`;
78
108
  }
109
+
110
+ const ROBOTS_QUERY = `#graphql
111
+ query StoreRobots($country: CountryCode, $language: LanguageCode)
112
+ @inContext(country: $country, language: $language) {
113
+ shop {
114
+ id
115
+ }
116
+ }
117
+ ` as const;
@@ -83,11 +83,7 @@ function FeaturedCollection({
83
83
  >
84
84
  {image && (
85
85
  <div className="featured-collection-image">
86
- <Image
87
- data={image}
88
- sizes="100vw"
89
- alt={image.altText || collection.title}
90
- />
86
+ <Image data={image} sizes="100vw" />
91
87
  </div>
92
88
  )}
93
89
  <h1>{collection.title}</h1>
@@ -101,11 +97,8 @@ function RecommendedProducts({
101
97
  products: Promise<RecommendedProductsQuery | null>;
102
98
  }) {
103
99
  return (
104
- <section
105
- className="recommended-products"
106
- aria-labelledby="recommended-products"
107
- >
108
- <h2 id="recommended-products">Recommended Products</h2>
100
+ <div className="recommended-products">
101
+ <h2>Recommended Products</h2>
109
102
  <Suspense fallback={<div>Loading...</div>}>
110
103
  <Await resolve={products}>
111
104
  {(response) => (
@@ -120,7 +113,7 @@ function RecommendedProducts({
120
113
  </Await>
121
114
  </Suspense>
122
115
  <br />
123
- </section>
116
+ </div>
124
117
  );
125
118
  }
126
119
 
@@ -3,7 +3,7 @@ import type {Route} from './+types/account.$';
3
3
 
4
4
  // fallback wild card for all unauthenticated routes in account section
5
5
  export async function loader({context}: Route.LoaderArgs) {
6
- await context.customerAccount.handleAuthStatus();
6
+ context.customerAccount.handleAuthStatus();
7
7
 
8
8
  return redirect('/account');
9
9
  }
@@ -32,7 +32,7 @@ export const meta: Route.MetaFunction = () => {
32
32
  };
33
33
 
34
34
  export async function loader({context}: Route.LoaderArgs) {
35
- await context.customerAccount.handleAuthStatus();
35
+ context.customerAccount.handleAuthStatus();
36
36
 
37
37
  return {};
38
38
  }
@@ -468,7 +468,7 @@ export function AddressForm({
468
468
  />
469
469
  <label htmlFor="territoryCode">Country Code*</label>
470
470
  <input
471
- aria-label="Country code"
471
+ aria-label="territoryCode"
472
472
  autoComplete="country"
473
473
  defaultValue={address?.territoryCode ?? ''}
474
474
  id="territoryCode"