@shopify/cli-hydrogen 12.0.0 → 12.0.1

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 (72) hide show
  1. package/README.md +1 -1
  2. package/dist/assets/hydrogen/routes/locale-check.ts +3 -3
  3. package/dist/assets/hydrogen/starter/.graphqlrc.ts +4 -2
  4. package/dist/assets/hydrogen/starter/CHANGELOG.md +271 -0
  5. package/dist/assets/hydrogen/starter/app/components/Aside.tsx +4 -2
  6. package/dist/assets/hydrogen/starter/app/components/CartLineItem.tsx +67 -38
  7. package/dist/assets/hydrogen/starter/app/components/CartMain.tsx +49 -8
  8. package/dist/assets/hydrogen/starter/app/components/CartSummary.tsx +133 -57
  9. package/dist/assets/hydrogen/starter/app/components/Header.tsx +3 -3
  10. package/dist/assets/hydrogen/starter/app/components/MockShopNotice.tsx +20 -0
  11. package/dist/assets/hydrogen/starter/app/components/PaginatedResourceSection.tsx +24 -4
  12. package/dist/assets/hydrogen/starter/app/components/ProductPrice.tsx +1 -1
  13. package/dist/assets/hydrogen/starter/app/entry.client.tsx +2 -3
  14. package/dist/assets/hydrogen/starter/app/lib/context.ts +5 -0
  15. package/dist/assets/hydrogen/starter/app/lib/fragments.ts +8 -0
  16. package/dist/assets/hydrogen/starter/app/routes/[robots.txt].tsx +14 -53
  17. package/dist/assets/hydrogen/starter/app/routes/_index.tsx +15 -9
  18. package/dist/assets/hydrogen/starter/app/routes/account.$.tsx +1 -1
  19. package/dist/assets/hydrogen/starter/app/routes/account.addresses.tsx +14 -14
  20. package/dist/assets/hydrogen/starter/app/routes/account.profile.tsx +1 -1
  21. package/dist/assets/hydrogen/starter/app/routes/account.tsx +4 -7
  22. package/dist/assets/hydrogen/starter/app/routes/account_.login.tsx +10 -0
  23. package/dist/assets/hydrogen/starter/app/routes/blogs.$blogHandle._index.tsx +2 -9
  24. package/dist/assets/hydrogen/starter/app/routes/blogs._index.tsx +1 -4
  25. package/dist/assets/hydrogen/starter/app/routes/cart.tsx +3 -11
  26. package/dist/assets/hydrogen/starter/app/routes/collections.all.tsx +1 -3
  27. package/dist/assets/hydrogen/starter/app/routes/pages.$handle.tsx +2 -8
  28. package/dist/assets/hydrogen/starter/app/routes/policies.$handle.tsx +1 -4
  29. package/dist/assets/hydrogen/starter/app/routes/policies._index.tsx +1 -1
  30. package/dist/assets/hydrogen/starter/app/routes/products.$handle.tsx +2 -9
  31. package/dist/assets/hydrogen/starter/app/routes/search.tsx +18 -12
  32. package/dist/assets/hydrogen/starter/app/styles/app.css +72 -1
  33. package/dist/assets/hydrogen/starter/eslint.config.js +1 -0
  34. package/dist/assets/hydrogen/starter/guides/predictiveSearch/predictiveSearch.md +3 -3
  35. package/dist/assets/hydrogen/starter/guides/search/search.md +1 -1
  36. package/dist/assets/hydrogen/starter/package.json +16 -16
  37. package/dist/assets/hydrogen/starter/server.ts +5 -7
  38. package/dist/assets/hydrogen/starter/storefrontapi.generated.d.ts +89 -11
  39. package/dist/assets/hydrogen/starter/vite.config.ts +10 -4
  40. package/dist/assets/hydrogen/vite/package.json +1 -2
  41. package/dist/assets/hydrogen/vite/vite.config.js +4 -3
  42. package/dist/commands/hydrogen/deploy.js +19 -13
  43. package/dist/commands/hydrogen/dev.js +6 -6
  44. package/dist/commands/hydrogen/generate/route.js +1 -1
  45. package/dist/commands/hydrogen/init.d.ts +2 -4
  46. package/dist/commands/hydrogen/init.js +2 -8
  47. package/dist/commands/hydrogen/preview.js +1 -1
  48. package/dist/commands/hydrogen/setup/css.js +1 -1
  49. package/dist/commands/hydrogen/setup/markets.js +1 -1
  50. package/dist/commands/hydrogen/setup/vite.js +1 -1
  51. package/dist/commands/hydrogen/setup.js +31 -33
  52. package/dist/commands/hydrogen/upgrade.js +173 -39
  53. package/dist/lib/build.js +4 -5
  54. package/dist/lib/bundle/vite-plugin.js +7 -21
  55. package/dist/lib/check-lockfile.js +16 -7
  56. package/dist/lib/codegen.js +2 -2
  57. package/dist/lib/format-code.js +3 -3
  58. package/dist/lib/graphql/admin/client.js +48 -23
  59. package/dist/lib/mini-oxygen/workerd.js +3 -5
  60. package/dist/lib/onboarding/common.js +21 -26
  61. package/dist/lib/onboarding/local.js +46 -59
  62. package/dist/lib/package-managers.js +2 -0
  63. package/dist/lib/patch-cli.js +198 -0
  64. package/dist/lib/setups/routes/generate.js +5 -3
  65. package/dist/lib/template-pack.js +93 -0
  66. package/dist/lib/transpile/morph/functions.js +1 -1
  67. package/dist/lib/transpile/morph/typedefs.js +2 -3
  68. package/dist/lib/upgrade-e2e-utils.js +50 -0
  69. package/oclif.manifest.json +7 -15
  70. package/package.json +22 -19
  71. package/dist/assets/hydrogen/starter/app/routes/api.$version.[graphql.json].tsx +0 -14
  72. package/dist/lib/react-router-version-check.js +0 -82
@@ -1,9 +1,8 @@
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
- import type {FetcherWithComponents} from 'react-router';
7
6
 
8
7
  type CartSummaryProps = {
9
8
  cart: OptimisticCart<CartApiQueryFragment | null>;
@@ -13,11 +12,16 @@ type CartSummaryProps = {
13
12
  export function CartSummary({cart, layout}: CartSummaryProps) {
14
13
  const className =
15
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();
16
20
 
17
21
  return (
18
- <div aria-labelledby="cart-summary" className={className}>
19
- <h4>Totals</h4>
20
- <dl className="cart-subtotal">
22
+ <div aria-labelledby={summaryId} className={className}>
23
+ <h4 id={summaryId}>Totals</h4>
24
+ <dl role="group" className="cart-subtotal">
21
25
  <dt>Subtotal</dt>
22
26
  <dd>
23
27
  {cart?.cost?.subtotalAmount?.amount ? (
@@ -27,8 +31,16 @@ export function CartSummary({cart, layout}: CartSummaryProps) {
27
31
  )}
28
32
  </dd>
29
33
  </dl>
30
- <CartDiscounts discountCodes={cart?.discountCodes} />
31
- <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
+ />
32
44
  <CartCheckoutActions checkoutUrl={cart?.checkoutUrl} />
33
45
  </div>
34
46
  );
@@ -49,8 +61,12 @@ function CartCheckoutActions({checkoutUrl}: {checkoutUrl?: string}) {
49
61
 
50
62
  function CartDiscounts({
51
63
  discountCodes,
64
+ discountsHeadingId,
65
+ discountCodeInputId,
52
66
  }: {
53
67
  discountCodes?: CartApiQueryFragment['discountCodes'];
68
+ discountsHeadingId: string;
69
+ discountCodeInputId: string;
54
70
  }) {
55
71
  const codes: string[] =
56
72
  discountCodes
@@ -58,16 +74,22 @@ function CartDiscounts({
58
74
  ?.map(({code}) => code) || [];
59
75
 
60
76
  return (
61
- <div>
77
+ <section aria-label="Discounts">
62
78
  {/* Have existing discount, display it with a remove option */}
63
79
  <dl hidden={!codes.length}>
64
80
  <div>
65
- <dt>Discount(s)</dt>
81
+ <dt id={discountsHeadingId}>Discounts</dt>
66
82
  <UpdateDiscountForm>
67
- <div className="cart-discount">
83
+ <div
84
+ className="cart-discount"
85
+ role="group"
86
+ aria-labelledby={discountsHeadingId}
87
+ >
68
88
  <code>{codes?.join(', ')}</code>
69
89
  &nbsp;
70
- <button>Remove</button>
90
+ <button type="submit" aria-label="Remove discount">
91
+ Remove
92
+ </button>
71
93
  </div>
72
94
  </UpdateDiscountForm>
73
95
  </div>
@@ -76,12 +98,22 @@ function CartDiscounts({
76
98
  {/* Show an input to apply a discount */}
77
99
  <UpdateDiscountForm discountCodes={codes}>
78
100
  <div>
79
- <input type="text" name="discountCode" placeholder="Discount code" />
101
+ <label htmlFor={discountCodeInputId} className="sr-only">
102
+ Discount code
103
+ </label>
104
+ <input
105
+ id={discountCodeInputId}
106
+ type="text"
107
+ name="discountCode"
108
+ placeholder="Discount code"
109
+ />
80
110
  &nbsp;
81
- <button type="submit">Apply</button>
111
+ <button type="submit" aria-label="Apply discount code">
112
+ Apply
113
+ </button>
82
114
  </div>
83
115
  </UpdateDiscountForm>
84
- </div>
116
+ </section>
85
117
  );
86
118
  }
87
119
 
@@ -107,78 +139,117 @@ function UpdateDiscountForm({
107
139
 
108
140
  function CartGiftCard({
109
141
  giftCardCodes,
142
+ giftCardHeadingId,
143
+ giftCardInputId,
110
144
  }: {
111
145
  giftCardCodes: CartApiQueryFragment['appliedGiftCards'] | undefined;
146
+ giftCardHeadingId: string;
147
+ giftCardInputId: string;
112
148
  }) {
113
- const appliedGiftCardCodes = useRef<string[]>([]);
114
149
  const giftCardCodeInput = useRef<HTMLInputElement>(null);
150
+ const removeButtonRefs = useRef<Map<string, HTMLButtonElement>>(new Map());
151
+ const previousCardIdsRef = useRef<string[]>([]);
115
152
  const giftCardAddFetcher = useFetcher({key: 'gift-card-add'});
153
+ const [removedCardIndex, setRemovedCardIndex] = useState<number | null>(null);
116
154
 
117
- // Clear the gift card code input after the gift card is added
118
155
  useEffect(() => {
119
156
  if (giftCardAddFetcher.data) {
120
- giftCardCodeInput.current!.value = '';
157
+ if (giftCardCodeInput.current !== null) {
158
+ giftCardCodeInput.current.value = '';
159
+ }
121
160
  }
122
161
  }, [giftCardAddFetcher.data]);
123
162
 
124
- function saveAppliedCode(code: string) {
125
- const formattedCode = code.replace(/\s/g, ''); // Remove spaces
126
- if (!appliedGiftCardCodes.current.includes(formattedCode)) {
127
- appliedGiftCardCodes.current.push(formattedCode);
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);
128
183
  }
129
- }
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
+ };
130
194
 
131
195
  return (
132
- <div>
133
- {/* Display applied gift cards with individual remove buttons */}
196
+ <section aria-label="Gift cards">
134
197
  {giftCardCodes && giftCardCodes.length > 0 && (
135
198
  <dl>
136
- <dt>Applied Gift Card(s)</dt>
199
+ <dt id={giftCardHeadingId}>Applied Gift Card(s)</dt>
137
200
  {giftCardCodes.map((giftCard) => (
138
- <RemoveGiftCardForm key={giftCard.id} giftCardId={giftCard.id}>
139
- <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
+ >
140
214
  <code>***{giftCard.lastCharacters}</code>
141
215
  &nbsp;
142
216
  <Money data={giftCard.amountUsed} />
143
- &nbsp;
144
- <button type="submit">Remove</button>
145
- </div>
146
- </RemoveGiftCardForm>
217
+ </RemoveGiftCardForm>
218
+ </dd>
147
219
  ))}
148
220
  </dl>
149
221
  )}
150
222
 
151
- {/* Show an input to apply a gift card */}
152
- <UpdateGiftCardForm
153
- giftCardCodes={appliedGiftCardCodes.current}
154
- saveAppliedCode={saveAppliedCode}
155
- fetcherKey="gift-card-add"
156
- >
223
+ <AddGiftCardForm fetcherKey="gift-card-add">
157
224
  <div>
225
+ <label htmlFor={giftCardInputId} className="sr-only">
226
+ Gift card code
227
+ </label>
158
228
  <input
229
+ id={giftCardInputId}
159
230
  type="text"
160
231
  name="giftCardCode"
161
232
  placeholder="Gift card code"
162
233
  ref={giftCardCodeInput}
163
234
  />
164
235
  &nbsp;
165
- <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
+ >
166
241
  Apply
167
242
  </button>
168
243
  </div>
169
- </UpdateGiftCardForm>
170
- </div>
244
+ </AddGiftCardForm>
245
+ </section>
171
246
  );
172
247
  }
173
248
 
174
- function UpdateGiftCardForm({
175
- giftCardCodes,
176
- saveAppliedCode,
249
+ function AddGiftCardForm({
177
250
  fetcherKey,
178
251
  children,
179
252
  }: {
180
- giftCardCodes?: string[];
181
- saveAppliedCode?: (code: string) => void;
182
253
  fetcherKey?: string;
183
254
  children: React.ReactNode;
184
255
  }) {
@@ -186,28 +257,25 @@ function UpdateGiftCardForm({
186
257
  <CartForm
187
258
  fetcherKey={fetcherKey}
188
259
  route="/cart"
189
- action={CartForm.ACTIONS.GiftCardCodesUpdate}
190
- inputs={{
191
- giftCardCodes: giftCardCodes || [],
192
- }}
260
+ action={CartForm.ACTIONS.GiftCardCodesAdd}
193
261
  >
194
- {(fetcher: FetcherWithComponents<any>) => {
195
- const code = fetcher.formData?.get('giftCardCode');
196
- if (code && saveAppliedCode) {
197
- saveAppliedCode(code as string);
198
- }
199
- return children;
200
- }}
262
+ {children}
201
263
  </CartForm>
202
264
  );
203
265
  }
204
266
 
205
267
  function RemoveGiftCardForm({
206
268
  giftCardId,
269
+ lastCharacters,
207
270
  children,
271
+ onRemoveClick,
272
+ buttonRef,
208
273
  }: {
209
274
  giftCardId: string;
275
+ lastCharacters: string;
210
276
  children: React.ReactNode;
277
+ onRemoveClick?: () => void;
278
+ buttonRef?: (el: HTMLButtonElement | null) => void;
211
279
  }) {
212
280
  return (
213
281
  <CartForm
@@ -218,7 +286,15 @@ function RemoveGiftCardForm({
218
286
  }}
219
287
  >
220
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>
221
298
  </CartForm>
222
299
  );
223
300
  }
224
-
@@ -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
+ }
@@ -2,15 +2,17 @@ import * as React from 'react';
2
2
  import {Pagination} from '@shopify/hydrogen';
3
3
 
4
4
  /**
5
- * <PaginatedResourceSection > is a component that encapsulate how the previous and next behaviors throughout your application.
5
+ * <PaginatedResourceSection> encapsulates the previous and next pagination behaviors throughout your application.
6
6
  */
7
7
  export function PaginatedResourceSection<NodesType>({
8
8
  connection,
9
9
  children,
10
+ ariaLabel,
10
11
  resourcesClassName,
11
12
  }: {
12
13
  connection: React.ComponentProps<typeof Pagination<NodesType>>['connection'];
13
14
  children: React.FunctionComponent<{node: NodesType; index: number}>;
15
+ ariaLabel?: string;
14
16
  resourcesClassName?: string;
15
17
  }) {
16
18
  return (
@@ -23,15 +25,33 @@ export function PaginatedResourceSection<NodesType>({
23
25
  return (
24
26
  <div>
25
27
  <PreviousLink>
26
- {isLoading ? 'Loading...' : <span>↑ Load previous</span>}
28
+ {isLoading ? (
29
+ 'Loading...'
30
+ ) : (
31
+ <span>
32
+ <span aria-hidden="true">↑</span> Load previous
33
+ </span>
34
+ )}
27
35
  </PreviousLink>
28
36
  {resourcesClassName ? (
29
- <div className={resourcesClassName}>{resourcesMarkup}</div>
37
+ <div
38
+ aria-label={ariaLabel}
39
+ className={resourcesClassName}
40
+ role={ariaLabel ? 'region' : undefined}
41
+ >
42
+ {resourcesMarkup}
43
+ </div>
30
44
  ) : (
31
45
  resourcesMarkup
32
46
  )}
33
47
  <NextLink>
34
- {isLoading ? 'Loading...' : <span>Load more ↓</span>}
48
+ {isLoading ? (
49
+ 'Loading...'
50
+ ) : (
51
+ <span>
52
+ Load more <span aria-hidden="true">↓</span>
53
+ </span>
54
+ )}
35
55
  </NextLink>
36
56
  </div>
37
57
  );
@@ -9,7 +9,7 @@ export function ProductPrice({
9
9
  compareAtPrice?: MoneyV2 | null;
10
10
  }) {
11
11
  return (
12
- <div className="product-price">
12
+ <div aria-label="Price" className="product-price" role="group">
13
13
  {compareAtPrice ? (
14
14
  <div className="product-price-on-sale">
15
15
  {price ? <Money data={price} /> : null}
@@ -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,6 +1,7 @@
1
1
  import {createHydrogenContext} from '@shopify/hydrogen';
2
2
  import {AppSession} from '~/lib/session';
3
3
  import {CART_QUERY_FRAGMENT} from '~/lib/fragments';
4
+ import type {CartApiQueryFragment} from 'storefrontapi.generated';
4
5
 
5
6
  // Define the additional context object
6
7
  const additionalContext = {
@@ -16,6 +17,10 @@ type AdditionalContextType = typeof additionalContext;
16
17
 
17
18
  declare global {
18
19
  interface HydrogenAdditionalContext extends AdditionalContextType {}
20
+
21
+ // Augment HydrogenCustomCartFragment with the codegen'd cart fragment type so
22
+ // that context.cart.get() and all cart mutations return the extended cart type.
23
+ interface HydrogenCustomCartFragment extends CartApiQueryFragment {}
19
24
  }
20
25
 
21
26
  /**
@@ -54,6 +54,11 @@ export const CART_QUERY_FRAGMENT = `#graphql
54
54
  }
55
55
  }
56
56
  }
57
+ parentRelationship {
58
+ parent {
59
+ id
60
+ }
61
+ }
57
62
  }
58
63
  fragment CartLineComponent on ComponentizableCartLine {
59
64
  id
@@ -104,6 +109,9 @@ export const CART_QUERY_FRAGMENT = `#graphql
104
109
  }
105
110
  }
106
111
  }
112
+ lineComponents {
113
+ ...CartLine
114
+ }
107
115
  }
108
116
  fragment CartApiQuery on Cart {
109
117
  updatedAt
@@ -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
  }