@shopify/cli-hydrogen 11.1.9 → 11.1.11

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 (46) hide show
  1. package/dist/assets/hydrogen/starter/CHANGELOG.md +28 -0
  2. package/dist/assets/hydrogen/starter/app/components/Aside.tsx +4 -2
  3. package/dist/assets/hydrogen/starter/app/components/CartMain.tsx +6 -3
  4. package/dist/assets/hydrogen/starter/app/components/CartSummary.tsx +117 -23
  5. package/dist/assets/hydrogen/starter/app/components/Header.tsx +3 -3
  6. package/dist/assets/hydrogen/starter/app/components/MockShopNotice.tsx +20 -0
  7. package/dist/assets/hydrogen/starter/app/entry.client.tsx +2 -3
  8. package/dist/assets/hydrogen/starter/app/routes/[robots.txt].tsx +14 -53
  9. package/dist/assets/hydrogen/starter/app/routes/_index.tsx +15 -9
  10. package/dist/assets/hydrogen/starter/app/routes/account.$.tsx +1 -1
  11. package/dist/assets/hydrogen/starter/app/routes/account.addresses.tsx +13 -13
  12. package/dist/assets/hydrogen/starter/app/routes/account.profile.tsx +1 -1
  13. package/dist/assets/hydrogen/starter/app/routes/account.tsx +4 -7
  14. package/dist/assets/hydrogen/starter/app/routes/blogs.$blogHandle._index.tsx +2 -9
  15. package/dist/assets/hydrogen/starter/app/routes/blogs._index.tsx +1 -4
  16. package/dist/assets/hydrogen/starter/app/routes/cart.tsx +1 -5
  17. package/dist/assets/hydrogen/starter/app/routes/collections.all.tsx +1 -3
  18. package/dist/assets/hydrogen/starter/app/routes/pages.$handle.tsx +2 -8
  19. package/dist/assets/hydrogen/starter/app/routes/policies.$handle.tsx +1 -4
  20. package/dist/assets/hydrogen/starter/app/routes/policies._index.tsx +1 -1
  21. package/dist/assets/hydrogen/starter/app/routes/products.$handle.tsx +2 -9
  22. package/dist/assets/hydrogen/starter/app/routes/search.tsx +18 -12
  23. package/dist/assets/hydrogen/starter/app/styles/app.css +51 -0
  24. package/dist/assets/hydrogen/starter/guides/predictiveSearch/predictiveSearch.md +3 -3
  25. package/dist/assets/hydrogen/starter/guides/search/search.md +1 -1
  26. package/dist/assets/hydrogen/starter/package.json +10 -10
  27. package/dist/assets/hydrogen/starter/vite.config.ts +1 -1
  28. package/dist/assets/hydrogen/vite/vite.config.js +1 -1
  29. package/dist/commands/hydrogen/dev.js +0 -4
  30. package/dist/commands/hydrogen/init.d.ts +2 -4
  31. package/dist/commands/hydrogen/init.js +2 -8
  32. package/dist/commands/hydrogen/preview.js +1 -1
  33. package/dist/commands/hydrogen/setup/css.js +1 -1
  34. package/dist/commands/hydrogen/setup/vite.js +1 -1
  35. package/dist/commands/hydrogen/setup.js +31 -33
  36. package/dist/commands/hydrogen/upgrade.js +7 -0
  37. package/dist/lib/build.js +4 -5
  38. package/dist/lib/codegen.js +2 -2
  39. package/dist/lib/format-code.js +3 -3
  40. package/dist/lib/mini-oxygen/workerd.js +3 -5
  41. package/dist/lib/onboarding/common.js +21 -26
  42. package/dist/lib/onboarding/local.js +46 -59
  43. package/dist/lib/template-pack.js +84 -0
  44. package/oclif.manifest.json +2 -9
  45. package/package.json +18 -15
  46. package/dist/lib/react-router-version-check.js +0 -82
@@ -1,5 +1,33 @@
1
1
  # skeleton
2
2
 
3
+ ## 2026.1.2
4
+
5
+ ### Patch Changes
6
+
7
+ - Improve gift card accessibility in Skeleton template ([#3518](https://github.com/Shopify/hydrogen/pull/3518)) by [@itsjustriley](https://github.com/itsjustriley)
8
+
9
+ - Updated shopify/cli dependencies for cli-hydrogen ([#3553](https://github.com/Shopify/hydrogen/pull/3553)) by [@andguy95](https://github.com/andguy95)
10
+
11
+ - Updated loaders that used `customerAccount.handleAuthStatus()` to now await it. ([#3523](https://github.com/Shopify/hydrogen/pull/3523)) by [@fredericoo](https://github.com/fredericoo)
12
+
13
+ ### Migration
14
+
15
+ If you call `handleAuthStatus()` in your own loaders, update those callsites to use `await`.
16
+
17
+ - 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)]:
18
+ - @shopify/hydrogen@2026.1.2
19
+
20
+ ## 2026.1.1
21
+
22
+ ### Patch Changes
23
+
24
+ - Added a notice with relevant information on how to link a store in case there is no store linked yet ([#3448](https://github.com/Shopify/hydrogen/pull/3448)) by [@fredericoo](https://github.com/fredericoo)
25
+
26
+ - Fixed an issue where users without addresses could not add the first one ([#3456](https://github.com/Shopify/hydrogen/pull/3456)) by [@fredericoo](https://github.com/fredericoo)
27
+
28
+ - Updated dependencies [[`ff93a1daf2207e52e1f8331f9ff2ccd1f9b7fed6`](https://github.com/Shopify/hydrogen/commit/ff93a1daf2207e52e1f8331f9ff2ccd1f9b7fed6)]:
29
+ - @shopify/hydrogen@2026.1.1
30
+
3
31
  ## 2026.1.0
4
32
 
5
33
  ### Major Changes
@@ -5,6 +5,7 @@ import {
5
5
  useEffect,
6
6
  useState,
7
7
  } from 'react';
8
+ import {useId} from 'react';
8
9
 
9
10
  type AsideType = 'search' | 'cart' | 'mobile' | 'closed';
10
11
  type AsideContextValue = {
@@ -34,7 +35,7 @@ export function Aside({
34
35
  }) {
35
36
  const {type: activeType, close} = useAside();
36
37
  const expanded = type === activeType;
37
-
38
+ const id = useId();
38
39
  useEffect(() => {
39
40
  const abortController = new AbortController();
40
41
 
@@ -57,11 +58,12 @@ export function Aside({
57
58
  aria-modal
58
59
  className={`overlay ${expanded ? 'expanded' : ''}`}
59
60
  role="dialog"
61
+ aria-labelledby={id}
60
62
  >
61
63
  <button className="close-outside" onClick={close} />
62
64
  <aside>
63
65
  <header>
64
- <h3>{heading}</h3>
66
+ <h3 id={id}>{heading}</h3>
65
67
  <button className="close reset" onClick={close} aria-label="Close">
66
68
  &times;
67
69
  </button>
@@ -1,4 +1,4 @@
1
- import {useOptimisticCart, type OptimisticCartLine} from '@shopify/hydrogen';
1
+ import {useOptimisticCart} 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,7 +50,10 @@ export function CartMain({layout, cart: originalCart}: CartMainProps) {
50
50
  const childrenMap = getLineItemChildrenMap(cart?.lines?.nodes ?? []);
51
51
 
52
52
  return (
53
- <div className={className}>
53
+ <section
54
+ className={className}
55
+ aria-label={layout === 'page' ? 'Cart page' : 'Cart drawer'}
56
+ >
54
57
  <CartEmpty hidden={linesCount} layout={layout} />
55
58
  <div className="cart-details">
56
59
  <p id="cart-lines" className="sr-only">
@@ -79,7 +82,7 @@ export function CartMain({layout, cart: originalCart}: CartMainProps) {
79
82
  </div>
80
83
  {cartHasItems && <CartSummary cart={cart} layout={layout} />}
81
84
  </div>
82
- </div>
85
+ </section>
83
86
  );
84
87
  }
85
88
 
@@ -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, useRef} from 'react';
4
+ import {useEffect, useId, useRef, useState} from 'react';
5
5
  import {useFetcher} from 'react-router';
6
6
 
7
7
  type CartSummaryProps = {
@@ -12,11 +12,16 @@ 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();
15
20
 
16
21
  return (
17
- <div aria-labelledby="cart-summary" className={className}>
18
- <h4>Totals</h4>
19
- <dl className="cart-subtotal">
22
+ <div aria-labelledby={summaryId} className={className}>
23
+ <h4 id={summaryId}>Totals</h4>
24
+ <dl role="group" className="cart-subtotal">
20
25
  <dt>Subtotal</dt>
21
26
  <dd>
22
27
  {cart?.cost?.subtotalAmount?.amount ? (
@@ -26,8 +31,16 @@ export function CartSummary({cart, layout}: CartSummaryProps) {
26
31
  )}
27
32
  </dd>
28
33
  </dl>
29
- <CartDiscounts discountCodes={cart?.discountCodes} />
30
- <CartGiftCard giftCardCodes={cart?.appliedGiftCards} />
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
+ />
31
44
  <CartCheckoutActions checkoutUrl={cart?.checkoutUrl} />
32
45
  </div>
33
46
  );
@@ -48,8 +61,12 @@ function CartCheckoutActions({checkoutUrl}: {checkoutUrl?: string}) {
48
61
 
49
62
  function CartDiscounts({
50
63
  discountCodes,
64
+ discountsHeadingId,
65
+ discountCodeInputId,
51
66
  }: {
52
67
  discountCodes?: CartApiQueryFragment['discountCodes'];
68
+ discountsHeadingId: string;
69
+ discountCodeInputId: string;
53
70
  }) {
54
71
  const codes: string[] =
55
72
  discountCodes
@@ -57,13 +74,17 @@ function CartDiscounts({
57
74
  ?.map(({code}) => code) || [];
58
75
 
59
76
  return (
60
- <div>
77
+ <section aria-label="Discounts">
61
78
  {/* Have existing discount, display it with a remove option */}
62
79
  <dl hidden={!codes.length}>
63
80
  <div>
64
- <dt>Discount(s)</dt>
81
+ <dt id={discountsHeadingId}>Discounts</dt>
65
82
  <UpdateDiscountForm>
66
- <div className="cart-discount">
83
+ <div
84
+ className="cart-discount"
85
+ role="group"
86
+ aria-labelledby={discountsHeadingId}
87
+ >
67
88
  <code>{codes?.join(', ')}</code>
68
89
  &nbsp;
69
90
  <button type="submit" aria-label="Remove discount">
@@ -77,11 +98,11 @@ function CartDiscounts({
77
98
  {/* Show an input to apply a discount */}
78
99
  <UpdateDiscountForm discountCodes={codes}>
79
100
  <div>
80
- <label htmlFor="discount-code-input" className="sr-only">
101
+ <label htmlFor={discountCodeInputId} className="sr-only">
81
102
  Discount code
82
103
  </label>
83
104
  <input
84
- id="discount-code-input"
105
+ id={discountCodeInputId}
85
106
  type="text"
86
107
  name="discountCode"
87
108
  placeholder="Discount code"
@@ -92,7 +113,7 @@ function CartDiscounts({
92
113
  </button>
93
114
  </div>
94
115
  </UpdateDiscountForm>
95
- </div>
116
+ </section>
96
117
  );
97
118
  }
98
119
 
@@ -118,52 +139,110 @@ function UpdateDiscountForm({
118
139
 
119
140
  function CartGiftCard({
120
141
  giftCardCodes,
142
+ giftCardHeadingId,
143
+ giftCardInputId,
121
144
  }: {
122
145
  giftCardCodes: CartApiQueryFragment['appliedGiftCards'] | undefined;
146
+ giftCardHeadingId: string;
147
+ giftCardInputId: string;
123
148
  }) {
124
149
  const giftCardCodeInput = useRef<HTMLInputElement>(null);
150
+ const removeButtonRefs = useRef<Map<string, HTMLButtonElement>>(new Map());
151
+ const previousCardIdsRef = useRef<string[]>([]);
125
152
  const giftCardAddFetcher = useFetcher({key: 'gift-card-add'});
153
+ const [removedCardIndex, setRemovedCardIndex] = useState<number | null>(null);
126
154
 
127
155
  useEffect(() => {
128
156
  if (giftCardAddFetcher.data) {
129
- giftCardCodeInput.current!.value = '';
157
+ if (giftCardCodeInput.current !== null) {
158
+ giftCardCodeInput.current.value = '';
159
+ }
130
160
  }
131
161
  }, [giftCardAddFetcher.data]);
132
162
 
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
+
133
195
  return (
134
- <div>
196
+ <section aria-label="Gift cards">
135
197
  {giftCardCodes && giftCardCodes.length > 0 && (
136
198
  <dl>
137
- <dt>Applied Gift Card(s)</dt>
199
+ <dt id={giftCardHeadingId}>Applied Gift Card(s)</dt>
138
200
  {giftCardCodes.map((giftCard) => (
139
- <RemoveGiftCardForm key={giftCard.id} giftCardId={giftCard.id}>
140
- <div className="cart-discount">
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
+ >
141
214
  <code>***{giftCard.lastCharacters}</code>
142
215
  &nbsp;
143
216
  <Money data={giftCard.amountUsed} />
144
- &nbsp;
145
- <button type="submit">Remove</button>
146
- </div>
147
- </RemoveGiftCardForm>
217
+ </RemoveGiftCardForm>
218
+ </dd>
148
219
  ))}
149
220
  </dl>
150
221
  )}
151
222
 
152
223
  <AddGiftCardForm fetcherKey="gift-card-add">
153
224
  <div>
225
+ <label htmlFor={giftCardInputId} className="sr-only">
226
+ Gift card code
227
+ </label>
154
228
  <input
229
+ id={giftCardInputId}
155
230
  type="text"
156
231
  name="giftCardCode"
157
232
  placeholder="Gift card code"
158
233
  ref={giftCardCodeInput}
159
234
  />
160
235
  &nbsp;
161
- <button type="submit" disabled={giftCardAddFetcher.state !== 'idle'}>
236
+ <button
237
+ type="submit"
238
+ disabled={giftCardAddFetcher.state !== 'idle'}
239
+ aria-label="Apply gift card code"
240
+ >
162
241
  Apply
163
242
  </button>
164
243
  </div>
165
244
  </AddGiftCardForm>
166
- </div>
245
+ </section>
167
246
  );
168
247
  }
169
248
 
@@ -187,10 +266,16 @@ function AddGiftCardForm({
187
266
 
188
267
  function RemoveGiftCardForm({
189
268
  giftCardId,
269
+ lastCharacters,
190
270
  children,
271
+ onRemoveClick,
272
+ buttonRef,
191
273
  }: {
192
274
  giftCardId: string;
275
+ lastCharacters: string;
193
276
  children: React.ReactNode;
277
+ onRemoveClick?: () => void;
278
+ buttonRef?: (el: HTMLButtonElement | null) => void;
194
279
  }) {
195
280
  return (
196
281
  <CartForm
@@ -201,6 +286,15 @@ function RemoveGiftCardForm({
201
286
  }}
202
287
  >
203
288
  {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>
204
298
  </CartForm>
205
299
  );
206
300
  }
@@ -136,7 +136,7 @@ function SearchToggle() {
136
136
  );
137
137
  }
138
138
 
139
- function CartBadge({count}: {count: number | null}) {
139
+ function CartBadge({count}: {count: number}) {
140
140
  const {open} = useAside();
141
141
  const {publish, shop, cart, prevCart} = useAnalytics();
142
142
 
@@ -154,14 +154,14 @@ function CartBadge({count}: {count: number | null}) {
154
154
  } as CartViewPayload);
155
155
  }}
156
156
  >
157
- Cart {count === null ? <span>&nbsp;</span> : count}
157
+ Cart <span aria-label={`(items: ${count})`}>{count}</span>
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={null} />}>
164
+ <Suspense fallback={<CartBadge count={0} />}>
165
165
  <Await resolve={cart}>
166
166
  <CartBanner />
167
167
  </Await>
@@ -0,0 +1,20 @@
1
+ export function MockShopNotice() {
2
+ return (
3
+ <section
4
+ className="mock-shop-notice"
5
+ aria-labelledby="mock-shop-notice-heading"
6
+ >
7
+ <div className="inner">
8
+ <h2 id="mock-shop-notice-heading">Welcome to Hydrogen!</h2>
9
+ <p>
10
+ You&rsquo;re seeing mocked products because no store is connected to
11
+ this project yet.
12
+ </p>
13
+ <p>
14
+ Link a store by running <code>npx shopify hydrogen link</code> in your
15
+ terminal.
16
+ </p>
17
+ </div>
18
+ </section>
19
+ );
20
+ }
@@ -6,9 +6,8 @@ import {NonceProvider} from '@shopify/hydrogen';
6
6
  if (!window.location.origin.includes('webcache.googleusercontent.com')) {
7
7
  startTransition(() => {
8
8
  // Extract nonce from existing script tags
9
- const existingNonce = document
10
- .querySelector<HTMLScriptElement>('script[nonce]')
11
- ?.nonce;
9
+ const existingNonce =
10
+ document.querySelector<HTMLScriptElement>('script[nonce]')?.nonce;
12
11
 
13
12
  hydrateRoot(
14
13
  document,
@@ -1,13 +1,8 @@
1
1
  import type {Route} from './+types/[robots.txt]';
2
- import {parseGid} from '@shopify/hydrogen';
3
2
 
4
- export async function loader({request, context}: Route.LoaderArgs) {
3
+ export function loader({request}: Route.LoaderArgs) {
5
4
  const url = new URL(request.url);
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});
5
+ const body = robotsTxtData({url: url.origin});
11
6
 
12
7
  return new Response(body, {
13
8
  status: 200,
@@ -19,35 +14,31 @@ export async function loader({request, context}: Route.LoaderArgs) {
19
14
  });
20
15
  }
21
16
 
22
- function robotsTxtData({url, shopId}: {shopId?: string; url?: string}) {
17
+ function robotsTxtData({url}: {url?: string}) {
23
18
  const sitemapUrl = url ? `${url}/sitemap.xml` : undefined;
24
19
 
25
20
  return `
26
21
  User-agent: *
27
- ${generalDisallowRules({sitemapUrl, shopId})}
22
+ ${generalDisallowRules({sitemapUrl})}
28
23
 
29
24
  # Google adsbot ignores robots.txt unless specifically named!
30
25
  User-agent: adsbot-google
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*
26
+ Disallow: /cart
27
+ Disallow: /account
28
+ Disallow: /search
29
+ Allow: /search/
30
+ Disallow: /search/?*
40
31
 
41
32
  User-agent: Nutch
42
33
  Disallow: /
43
34
 
44
35
  User-agent: AhrefsBot
45
36
  Crawl-delay: 10
46
- ${generalDisallowRules({sitemapUrl, shopId})}
37
+ ${generalDisallowRules({sitemapUrl})}
47
38
 
48
39
  User-agent: AhrefsSiteAudit
49
40
  Crawl-delay: 10
50
- ${generalDisallowRules({sitemapUrl, shopId})}
41
+ ${generalDisallowRules({sitemapUrl})}
51
42
 
52
43
  User-agent: MJ12bot
53
44
  Crawl-Delay: 10
@@ -61,21 +52,8 @@ Crawl-delay: 1
61
52
  * This function generates disallow rules that generally follow what Shopify's
62
53
  * Online Store has as defaults for their robots.txt
63
54
  */
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
55
+ function generalDisallowRules({sitemapUrl}: {sitemapUrl?: string}) {
56
+ return `Disallow: /cart
79
57
  Disallow: /account
80
58
  Disallow: /collections/*sort_by*
81
59
  Disallow: /*/collections/*sort_by*
@@ -85,33 +63,16 @@ Disallow: /collections/*%2b*
85
63
  Disallow: /*/collections/*+*
86
64
  Disallow: /*/collections/*%2B*
87
65
  Disallow: /*/collections/*%2b*
88
- Disallow: */collections/*filter*&*filter*
66
+ Disallow: /*/collections/*filter*&*filter*
89
67
  Disallow: /blogs/*+*
90
68
  Disallow: /blogs/*%2B*
91
69
  Disallow: /blogs/*%2b*
92
70
  Disallow: /*/blogs/*+*
93
71
  Disallow: /*/blogs/*%2B*
94
72
  Disallow: /*/blogs/*%2b*
95
- Disallow: /*?*oseid=*
96
- Disallow: /*preview_theme_id*
97
- Disallow: /*preview_script_id*
98
73
  Disallow: /policies/
99
- Disallow: /*/*?*ls=*&ls=*
100
- Disallow: /*/*?*ls%3D*%3Fls%3D*
101
- Disallow: /*/*?*ls%3d*%3fls%3d*
102
74
  Disallow: /search
103
75
  Allow: /search/
104
76
  Disallow: /search/?*
105
- Disallow: /apple-app-site-association
106
- Disallow: /.well-known/shopify/monorail
107
77
  ${sitemapUrl ? `Sitemap: ${sitemapUrl}` : ''}`;
108
78
  }
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;
@@ -1,8 +1,4 @@
1
- import {
2
- Await,
3
- useLoaderData,
4
- Link,
5
- } from 'react-router';
1
+ import {Await, useLoaderData, Link} from 'react-router';
6
2
  import type {Route} from './+types/_index';
7
3
  import {Suspense} from 'react';
8
4
  import {Image} from '@shopify/hydrogen';
@@ -11,6 +7,7 @@ import type {
11
7
  RecommendedProductsQuery,
12
8
  } from 'storefrontapi.generated';
13
9
  import {ProductItem} from '~/components/ProductItem';
10
+ import {MockShopNotice} from '~/components/MockShopNotice';
14
11
 
15
12
  export const meta: Route.MetaFunction = () => {
16
13
  return [{title: 'Hydrogen | Home'}];
@@ -37,6 +34,7 @@ async function loadCriticalData({context}: Route.LoaderArgs) {
37
34
  ]);
38
35
 
39
36
  return {
37
+ isShopLinked: Boolean(context.env.PUBLIC_STORE_DOMAIN),
40
38
  featuredCollection: collections.nodes[0],
41
39
  };
42
40
  }
@@ -64,6 +62,7 @@ export default function Homepage() {
64
62
  const data = useLoaderData<typeof loader>();
65
63
  return (
66
64
  <div className="home">
65
+ {data.isShopLinked ? null : <MockShopNotice />}
67
66
  <FeaturedCollection collection={data.featuredCollection} />
68
67
  <RecommendedProducts products={data.recommendedProducts} />
69
68
  </div>
@@ -84,7 +83,11 @@ function FeaturedCollection({
84
83
  >
85
84
  {image && (
86
85
  <div className="featured-collection-image">
87
- <Image data={image} sizes="100vw" />
86
+ <Image
87
+ data={image}
88
+ sizes="100vw"
89
+ alt={image.altText || collection.title}
90
+ />
88
91
  </div>
89
92
  )}
90
93
  <h1>{collection.title}</h1>
@@ -98,8 +101,11 @@ function RecommendedProducts({
98
101
  products: Promise<RecommendedProductsQuery | null>;
99
102
  }) {
100
103
  return (
101
- <div className="recommended-products">
102
- <h2>Recommended Products</h2>
104
+ <section
105
+ className="recommended-products"
106
+ aria-labelledby="recommended-products"
107
+ >
108
+ <h2 id="recommended-products">Recommended Products</h2>
103
109
  <Suspense fallback={<div>Loading...</div>}>
104
110
  <Await resolve={products}>
105
111
  {(response) => (
@@ -114,7 +120,7 @@ function RecommendedProducts({
114
120
  </Await>
115
121
  </Suspense>
116
122
  <br />
117
- </div>
123
+ </section>
118
124
  );
119
125
  }
120
126
 
@@ -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
- context.customerAccount.handleAuthStatus();
6
+ await 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
- context.customerAccount.handleAuthStatus();
35
+ await context.customerAccount.handleAuthStatus();
36
36
 
37
37
  return {};
38
38
  }
@@ -264,23 +264,23 @@ export default function Addresses() {
264
264
  <div className="account-addresses">
265
265
  <h2>Addresses</h2>
266
266
  <br />
267
- {!addresses.nodes.length ? (
268
- <p>You have no addresses saved.</p>
269
- ) : (
267
+ <div>
270
268
  <div>
271
- <div>
272
- <legend>Create address</legend>
273
- <NewAddressForm />
274
- </div>
275
- <br />
276
- <hr />
277
- <br />
269
+ <legend>Create address</legend>
270
+ <NewAddressForm key={addresses.nodes.length} />
271
+ </div>
272
+ <br />
273
+ <hr />
274
+ <br />
275
+ {!addresses.nodes.length ? (
276
+ <p>You have no addresses saved.</p>
277
+ ) : (
278
278
  <ExistingAddresses
279
279
  addresses={addresses}
280
280
  defaultAddress={defaultAddress}
281
281
  />
282
- </div>
283
- )}
282
+ )}
283
+ </div>
284
284
  </div>
285
285
  );
286
286
  }
@@ -20,7 +20,7 @@ export const meta: Route.MetaFunction = () => {
20
20
  };
21
21
 
22
22
  export async function loader({context}: Route.LoaderArgs) {
23
- context.customerAccount.handleAuthStatus();
23
+ await context.customerAccount.handleAuthStatus();
24
24
 
25
25
  return {};
26
26
  }
@@ -14,14 +14,11 @@ export function shouldRevalidate() {
14
14
 
15
15
  export async function loader({context}: Route.LoaderArgs) {
16
16
  const {customerAccount} = context;
17
- const {data, errors} = await customerAccount.query(
18
- CUSTOMER_DETAILS_QUERY,
19
- {
20
- variables: {
21
- language: customerAccount.i18n.language,
22
- },
17
+ const {data, errors} = await customerAccount.query(CUSTOMER_DETAILS_QUERY, {
18
+ variables: {
19
+ language: customerAccount.i18n.language,
23
20
  },
24
- );
21
+ });
25
22
 
26
23
  if (errors?.length || !data?.customer) {
27
24
  throw new Error('Customer not found');