@shopify/cli 3.65.1 → 3.65.3

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 (122) hide show
  1. package/dist/assets/hydrogen/i18n/domains.ts +4 -11
  2. package/dist/assets/hydrogen/i18n/mock-i18n-types.ts +4 -2
  3. package/dist/assets/hydrogen/i18n/subdomains.ts +4 -11
  4. package/dist/assets/hydrogen/i18n/subfolders.ts +4 -11
  5. package/dist/assets/hydrogen/starter/CHANGELOG.md +165 -0
  6. package/dist/assets/hydrogen/starter/app/components/CartLineItem.tsx +5 -2
  7. package/dist/assets/hydrogen/starter/app/components/CartMain.tsx +2 -2
  8. package/dist/assets/hydrogen/starter/app/components/PageLayout.tsx +65 -19
  9. package/dist/assets/hydrogen/starter/app/components/PaginatedResourceSection.tsx +42 -0
  10. package/dist/assets/hydrogen/starter/app/components/SearchForm.tsx +68 -0
  11. package/dist/assets/hydrogen/starter/app/components/SearchFormPredictive.tsx +76 -0
  12. package/dist/assets/hydrogen/starter/app/components/SearchResults.tsx +164 -0
  13. package/dist/assets/hydrogen/starter/app/components/SearchResultsPredictive.tsx +322 -0
  14. package/dist/assets/hydrogen/starter/app/entry.client.tsx +10 -8
  15. package/dist/assets/hydrogen/starter/app/entry.server.tsx +1 -1
  16. package/dist/assets/hydrogen/starter/app/lib/context.ts +43 -0
  17. package/dist/assets/hydrogen/starter/app/lib/fragments.ts +53 -0
  18. package/dist/assets/hydrogen/starter/app/lib/search.ts +74 -24
  19. package/dist/assets/hydrogen/starter/app/root.tsx +4 -7
  20. package/dist/assets/hydrogen/starter/app/routes/account.addresses.tsx +2 -3
  21. package/dist/assets/hydrogen/starter/app/routes/account.orders._index.tsx +5 -19
  22. package/dist/assets/hydrogen/starter/app/routes/blogs.$blogHandle._index.tsx +11 -24
  23. package/dist/assets/hydrogen/starter/app/routes/blogs._index.tsx +14 -27
  24. package/dist/assets/hydrogen/starter/app/routes/collections.$handle.tsx +12 -30
  25. package/dist/assets/hydrogen/starter/app/routes/collections._index.tsx +13 -27
  26. package/dist/assets/hydrogen/starter/app/routes/collections.all.tsx +9 -31
  27. package/dist/assets/hydrogen/starter/app/routes/search.tsx +312 -73
  28. package/dist/assets/hydrogen/starter/app/styles/reset.css +12 -2
  29. package/dist/assets/hydrogen/starter/env.d.ts +11 -30
  30. package/dist/assets/hydrogen/starter/guides/predictiveSearch/predictiveSearch.jpg +0 -0
  31. package/dist/assets/hydrogen/starter/guides/predictiveSearch/predictiveSearch.md +391 -0
  32. package/dist/assets/hydrogen/starter/guides/search/search.jpg +0 -0
  33. package/dist/assets/hydrogen/starter/guides/search/search.md +333 -0
  34. package/dist/assets/hydrogen/starter/package.json +4 -4
  35. package/dist/assets/hydrogen/starter/server.ts +18 -74
  36. package/dist/assets/hydrogen/starter/storefrontapi.generated.d.ts +242 -172
  37. package/dist/assets/hydrogen/virtual-routes/components/{PageLayout.jsx → Layout.jsx} +2 -2
  38. package/dist/assets/hydrogen/virtual-routes/virtual-root.jsx +7 -6
  39. package/dist/{chunk-H42RFZDD.js → chunk-3ABSSTBQ.js} +4 -4
  40. package/dist/{chunk-M7WMYV4S.js → chunk-3D4VZQOH.js} +2 -2
  41. package/dist/{chunk-J7BYFGNJ.js → chunk-3GSKXZGY.js} +2 -2
  42. package/dist/{chunk-TDWX3KIR.js → chunk-3LDWVYMD.js} +2 -2
  43. package/dist/{chunk-N2BXKOJG.js → chunk-646BIVHE.js} +4 -4
  44. package/dist/{chunk-CZ3SHYYH.js → chunk-7WAEFADN.js} +4 -4
  45. package/dist/{chunk-EKT2GUGH.js → chunk-7WGBIPDW.js} +2 -2
  46. package/dist/{chunk-M6KGRVDD.js → chunk-AX77SAMU.js} +3 -3
  47. package/dist/{chunk-4HAEQQTQ.js → chunk-BQBBVYYU.js} +4 -4
  48. package/dist/{chunk-5YD4FDOS.js → chunk-BZLNTDGG.js} +3 -3
  49. package/dist/{chunk-VWALMO2Z.js → chunk-CSCEGIBZ.js} +3 -3
  50. package/dist/{chunk-F2Y7KYHZ.js → chunk-EIUQV76I.js} +5 -5
  51. package/dist/{chunk-MODBIZ4R.js → chunk-GN74L7IW.js} +2 -2
  52. package/dist/{chunk-5EAVIJTQ.js → chunk-HYCRESCR.js} +2 -2
  53. package/dist/{chunk-GDARYUPU.js → chunk-K7KD247K.js} +188 -243
  54. package/dist/{chunk-PZM45AUI.js → chunk-KIUXMPTX.js} +3 -3
  55. package/dist/{chunk-PYMSCBPA.js → chunk-LAJ4OEME.js} +2 -2
  56. package/dist/{chunk-YVHV3H5H.js → chunk-MIQBXNSN.js} +4 -4
  57. package/dist/{chunk-BLKDGMHM.js → chunk-MV6A3QHA.js} +4 -4
  58. package/dist/{chunk-CFFAWVDL.js → chunk-N3YORLAS.js} +2 -2
  59. package/dist/{chunk-EU5ZOEUT.js → chunk-NBTEOGQW.js} +2 -2
  60. package/dist/{chunk-ZXJU6UP4.js → chunk-O3JOUAA5.js} +4 -4
  61. package/dist/{chunk-EZ5DG73H.js → chunk-PEAIOYXD.js} +4 -4
  62. package/dist/{chunk-YDS7NZBQ.js → chunk-R5GT4GBL.js} +4 -4
  63. package/dist/{chunk-6M65VRAT.js → chunk-S7FJTFYR.js} +5 -5
  64. package/dist/{chunk-DX2RXOQ5.js → chunk-S7RH664J.js} +3 -3
  65. package/dist/{chunk-WMECC32P.js → chunk-SKF2SKWO.js} +3 -3
  66. package/dist/{chunk-27HGZPUX.js → chunk-SMKCVFDT.js} +3 -3
  67. package/dist/{chunk-EID6L4PR.js → chunk-T4Y7NDNJ.js} +2 -2
  68. package/dist/{chunk-PY33KMCK.js → chunk-TWWJNMTO.js} +2 -2
  69. package/dist/{chunk-YXPGPWR2.js → chunk-U2PN6QZ2.js} +5 -5
  70. package/dist/{chunk-3REVOIEW.js → chunk-UBCH575K.js} +5 -5
  71. package/dist/{chunk-A4NQWDPT.js → chunk-XLURAR5E.js} +3 -3
  72. package/dist/{chunk-ZZKUI3DP.js → chunk-YPG7LXPN.js} +3 -3
  73. package/dist/cli/commands/auth/logout.js +10 -10
  74. package/dist/cli/commands/auth/logout.test.js +11 -11
  75. package/dist/cli/commands/debug/command-flags.js +9 -9
  76. package/dist/cli/commands/demo/catalog.js +10 -10
  77. package/dist/cli/commands/demo/generate-file.js +10 -10
  78. package/dist/cli/commands/demo/index.js +10 -10
  79. package/dist/cli/commands/demo/print-ai-prompt.js +10 -10
  80. package/dist/cli/commands/docs/generate.js +9 -9
  81. package/dist/cli/commands/docs/generate.test.js +9 -9
  82. package/dist/cli/commands/help.js +9 -9
  83. package/dist/cli/commands/kitchen-sink/async.js +10 -10
  84. package/dist/cli/commands/kitchen-sink/async.test.js +10 -10
  85. package/dist/cli/commands/kitchen-sink/index.js +12 -12
  86. package/dist/cli/commands/kitchen-sink/index.test.js +12 -12
  87. package/dist/cli/commands/kitchen-sink/prompts.js +10 -10
  88. package/dist/cli/commands/kitchen-sink/prompts.test.js +10 -10
  89. package/dist/cli/commands/kitchen-sink/static.js +10 -10
  90. package/dist/cli/commands/kitchen-sink/static.test.js +10 -10
  91. package/dist/cli/commands/search.js +10 -10
  92. package/dist/cli/commands/upgrade.js +9 -9
  93. package/dist/cli/commands/version.js +10 -10
  94. package/dist/cli/commands/version.test.js +10 -10
  95. package/dist/cli/services/commands/search.js +2 -2
  96. package/dist/cli/services/commands/search.test.js +2 -2
  97. package/dist/cli/services/commands/version.js +4 -4
  98. package/dist/cli/services/commands/version.test.js +5 -5
  99. package/dist/cli/services/demo.js +2 -2
  100. package/dist/cli/services/demo.test.js +2 -2
  101. package/dist/cli/services/kitchen-sink/async.js +2 -2
  102. package/dist/cli/services/kitchen-sink/prompts.js +2 -2
  103. package/dist/cli/services/kitchen-sink/static.js +2 -2
  104. package/dist/cli/services/upgrade.js +3 -3
  105. package/dist/cli/services/upgrade.test.js +5 -5
  106. package/dist/{custom-oclif-loader-JHNX2EGV.js → custom-oclif-loader-BT7EH2NN.js} +3 -3
  107. package/dist/{error-handler-4UJ6363X.js → error-handler-OSEY6KVA.js} +8 -8
  108. package/dist/hooks/postrun.js +6 -6
  109. package/dist/hooks/prerun.js +4 -4
  110. package/dist/index.js +1333 -1279
  111. package/dist/{local-V7RONWNU.js → local-OQXN5NM2.js} +2 -2
  112. package/dist/{morph-DN4AZJZW.js → morph-IQTWRBBT.js} +16 -12
  113. package/dist/{node-3H4OKRLA.js → node-YQVH3Y7J.js} +13 -13
  114. package/dist/{node-package-manager-XM7EXHQA.js → node-package-manager-VW2DN7R4.js} +3 -3
  115. package/dist/{system-F63VIZ5U.js → system-347PZWVP.js} +2 -2
  116. package/dist/tsconfig.tsbuildinfo +1 -1
  117. package/dist/{ui-BXWWRIFS.js → ui-S7L55PBH.js} +2 -2
  118. package/dist/{workerd-A5NCF6UA.js → workerd-OLKE7G4X.js} +12 -12
  119. package/oclif.manifest.json +39 -2
  120. package/package.json +7 -7
  121. package/dist/assets/hydrogen/starter/app/components/Search.tsx +0 -514
  122. package/dist/assets/hydrogen/starter/app/routes/api.predictive-search.tsx +0 -318
@@ -0,0 +1,164 @@
1
+ import {Link} from '@remix-run/react';
2
+ import {Image, Money, Pagination} from '@shopify/hydrogen';
3
+ import {urlWithTrackingParams, type RegularSearchReturn} from '~/lib/search';
4
+
5
+ type SearchItems = RegularSearchReturn['result']['items'];
6
+ type PartialSearchResult<ItemType extends keyof SearchItems> = Pick<
7
+ SearchItems,
8
+ ItemType
9
+ > &
10
+ Pick<RegularSearchReturn, 'term'>;
11
+
12
+ type SearchResultsProps = RegularSearchReturn & {
13
+ children: (args: SearchItems & {term: string}) => React.ReactNode;
14
+ };
15
+
16
+ export function SearchResults({
17
+ term,
18
+ result,
19
+ children,
20
+ }: Omit<SearchResultsProps, 'error' | 'type'>) {
21
+ if (!result?.total) {
22
+ return null;
23
+ }
24
+
25
+ return children({...result.items, term});
26
+ }
27
+
28
+ SearchResults.Articles = SearchResultsArticles;
29
+ SearchResults.Pages = SearchResultsPages;
30
+ SearchResults.Products = SearchResultsProducts;
31
+ SearchResults.Empty = SearchResultsEmpty;
32
+
33
+ function SearchResultsArticles({
34
+ term,
35
+ articles,
36
+ }: PartialSearchResult<'articles'>) {
37
+ if (!articles?.nodes.length) {
38
+ return null;
39
+ }
40
+
41
+ return (
42
+ <div className="search-result">
43
+ <h2>Articles</h2>
44
+ <div>
45
+ {articles?.nodes?.map((article) => {
46
+ const articleUrl = urlWithTrackingParams({
47
+ baseUrl: `/blogs/${article.handle}`,
48
+ trackingParams: article.trackingParameters,
49
+ term,
50
+ });
51
+
52
+ return (
53
+ <div className="search-results-item" key={article.id}>
54
+ <Link prefetch="intent" to={articleUrl}>
55
+ {article.title}
56
+ </Link>
57
+ </div>
58
+ );
59
+ })}
60
+ </div>
61
+ <br />
62
+ </div>
63
+ );
64
+ }
65
+
66
+ function SearchResultsPages({term, pages}: PartialSearchResult<'pages'>) {
67
+ if (!pages?.nodes.length) {
68
+ return null;
69
+ }
70
+
71
+ return (
72
+ <div className="search-result">
73
+ <h2>Pages</h2>
74
+ <div>
75
+ {pages?.nodes?.map((page) => {
76
+ const pageUrl = urlWithTrackingParams({
77
+ baseUrl: `/pages/${page.handle}`,
78
+ trackingParams: page.trackingParameters,
79
+ term,
80
+ });
81
+
82
+ return (
83
+ <div className="search-results-item" key={page.id}>
84
+ <Link prefetch="intent" to={pageUrl}>
85
+ {page.title}
86
+ </Link>
87
+ </div>
88
+ );
89
+ })}
90
+ </div>
91
+ <br />
92
+ </div>
93
+ );
94
+ }
95
+
96
+ function SearchResultsProducts({
97
+ term,
98
+ products,
99
+ }: PartialSearchResult<'products'>) {
100
+ if (!products?.nodes.length) {
101
+ return null;
102
+ }
103
+
104
+ return (
105
+ <div className="search-result">
106
+ <h2>Products</h2>
107
+ <Pagination connection={products}>
108
+ {({nodes, isLoading, NextLink, PreviousLink}) => {
109
+ const ItemsMarkup = nodes.map((product) => {
110
+ const productUrl = urlWithTrackingParams({
111
+ baseUrl: `/products/${product.handle}`,
112
+ trackingParams: product.trackingParameters,
113
+ term,
114
+ });
115
+
116
+ return (
117
+ <div className="search-results-item" key={product.id}>
118
+ <Link prefetch="intent" to={productUrl}>
119
+ {product.variants.nodes[0].image && (
120
+ <Image
121
+ data={product.variants.nodes[0].image}
122
+ alt={product.title}
123
+ width={50}
124
+ />
125
+ )}
126
+ <div>
127
+ <p>{product.title}</p>
128
+ <small>
129
+ <Money data={product.variants.nodes[0].price} />
130
+ </small>
131
+ </div>
132
+ </Link>
133
+ </div>
134
+ );
135
+ });
136
+
137
+ return (
138
+ <div>
139
+ <div>
140
+ <PreviousLink>
141
+ {isLoading ? 'Loading...' : <span>↑ Load previous</span>}
142
+ </PreviousLink>
143
+ </div>
144
+ <div>
145
+ {ItemsMarkup}
146
+ <br />
147
+ </div>
148
+ <div>
149
+ <NextLink>
150
+ {isLoading ? 'Loading...' : <span>Load more ↓</span>}
151
+ </NextLink>
152
+ </div>
153
+ </div>
154
+ );
155
+ }}
156
+ </Pagination>
157
+ <br />
158
+ </div>
159
+ );
160
+ }
161
+
162
+ function SearchResultsEmpty() {
163
+ return <p>No results, try a different search.</p>;
164
+ }
@@ -0,0 +1,322 @@
1
+ import {Link, useFetcher, type Fetcher} from '@remix-run/react';
2
+ import {Image, Money} from '@shopify/hydrogen';
3
+ import React, {useRef, useEffect} from 'react';
4
+ import {
5
+ getEmptyPredictiveSearchResult,
6
+ urlWithTrackingParams,
7
+ type PredictiveSearchReturn,
8
+ } from '~/lib/search';
9
+ import {useAside} from './Aside';
10
+
11
+ type PredictiveSearchItems = PredictiveSearchReturn['result']['items'];
12
+
13
+ type UsePredictiveSearchReturn = {
14
+ term: React.MutableRefObject<string>;
15
+ total: number;
16
+ inputRef: React.MutableRefObject<HTMLInputElement | null>;
17
+ items: PredictiveSearchItems;
18
+ fetcher: Fetcher<PredictiveSearchReturn>;
19
+ };
20
+
21
+ type SearchResultsPredictiveArgs = Pick<
22
+ UsePredictiveSearchReturn,
23
+ 'term' | 'total' | 'inputRef' | 'items'
24
+ > & {
25
+ state: Fetcher['state'];
26
+ closeSearch: () => void;
27
+ };
28
+
29
+ type PartialPredictiveSearchResult<
30
+ ItemType extends keyof PredictiveSearchItems,
31
+ ExtraProps extends keyof SearchResultsPredictiveArgs = 'term' | 'closeSearch',
32
+ > = Pick<PredictiveSearchItems, ItemType> &
33
+ Pick<SearchResultsPredictiveArgs, ExtraProps>;
34
+
35
+ type SearchResultsPredictiveProps = {
36
+ children: (args: SearchResultsPredictiveArgs) => React.ReactNode;
37
+ };
38
+
39
+ /**
40
+ * Component that renders predictive search results
41
+ */
42
+ export function SearchResultsPredictive({
43
+ children,
44
+ }: SearchResultsPredictiveProps) {
45
+ const aside = useAside();
46
+ const {term, inputRef, fetcher, total, items} = usePredictiveSearch();
47
+
48
+ /*
49
+ * Utility that resets the search input
50
+ */
51
+ function resetInput() {
52
+ if (inputRef.current) {
53
+ inputRef.current.blur();
54
+ inputRef.current.value = '';
55
+ }
56
+ }
57
+
58
+ /**
59
+ * Utility that resets the search input and closes the search aside
60
+ */
61
+ function closeSearch() {
62
+ resetInput();
63
+ aside.close();
64
+ }
65
+
66
+ return children({
67
+ items,
68
+ closeSearch,
69
+ inputRef,
70
+ state: fetcher.state,
71
+ term,
72
+ total,
73
+ });
74
+ }
75
+
76
+ SearchResultsPredictive.Articles = SearchResultsPredictiveArticles;
77
+ SearchResultsPredictive.Collections = SearchResultsPredictiveCollections;
78
+ SearchResultsPredictive.Pages = SearchResultsPredictivePages;
79
+ SearchResultsPredictive.Products = SearchResultsPredictiveProducts;
80
+ SearchResultsPredictive.Queries = SearchResultsPredictiveQueries;
81
+ SearchResultsPredictive.Empty = SearchResultsPredictiveEmpty;
82
+
83
+ function SearchResultsPredictiveArticles({
84
+ term,
85
+ articles,
86
+ closeSearch,
87
+ }: PartialPredictiveSearchResult<'articles'>) {
88
+ if (!articles.length) return null;
89
+
90
+ return (
91
+ <div className="predictive-search-result" key="articles">
92
+ <h5>Articles</h5>
93
+ <ul>
94
+ {articles.map((article) => {
95
+ const articleUrl = urlWithTrackingParams({
96
+ baseUrl: `/blogs/${article.blog.handle}/${article.handle}`,
97
+ trackingParams: article.trackingParameters,
98
+ term: term.current ?? '',
99
+ });
100
+
101
+ return (
102
+ <li className="predictive-search-result-item" key={article.id}>
103
+ <Link onClick={closeSearch} to={articleUrl}>
104
+ {article.image?.url && (
105
+ <Image
106
+ alt={article.image.altText ?? ''}
107
+ src={article.image.url}
108
+ width={50}
109
+ height={50}
110
+ />
111
+ )}
112
+ <div>
113
+ <span>{article.title}</span>
114
+ </div>
115
+ </Link>
116
+ </li>
117
+ );
118
+ })}
119
+ </ul>
120
+ </div>
121
+ );
122
+ }
123
+
124
+ function SearchResultsPredictiveCollections({
125
+ term,
126
+ collections,
127
+ closeSearch,
128
+ }: PartialPredictiveSearchResult<'collections'>) {
129
+ if (!collections.length) return null;
130
+
131
+ return (
132
+ <div className="predictive-search-result" key="collections">
133
+ <h5>Collections</h5>
134
+ <ul>
135
+ {collections.map((collection) => {
136
+ const colllectionUrl = urlWithTrackingParams({
137
+ baseUrl: `/collections/${collection.handle}`,
138
+ trackingParams: collection.trackingParameters,
139
+ term: term.current,
140
+ });
141
+
142
+ return (
143
+ <li className="predictive-search-result-item" key={collection.id}>
144
+ <Link onClick={closeSearch} to={colllectionUrl}>
145
+ {collection.image?.url && (
146
+ <Image
147
+ alt={collection.image.altText ?? ''}
148
+ src={collection.image.url}
149
+ width={50}
150
+ height={50}
151
+ />
152
+ )}
153
+ <div>
154
+ <span>{collection.title}</span>
155
+ </div>
156
+ </Link>
157
+ </li>
158
+ );
159
+ })}
160
+ </ul>
161
+ </div>
162
+ );
163
+ }
164
+
165
+ function SearchResultsPredictivePages({
166
+ term,
167
+ pages,
168
+ closeSearch,
169
+ }: PartialPredictiveSearchResult<'pages'>) {
170
+ if (!pages.length) return null;
171
+
172
+ return (
173
+ <div className="predictive-search-result" key="pages">
174
+ <h5>Pages</h5>
175
+ <ul>
176
+ {pages.map((page) => {
177
+ const pageUrl = urlWithTrackingParams({
178
+ baseUrl: `/pages/${page.handle}`,
179
+ trackingParams: page.trackingParameters,
180
+ term: term.current,
181
+ });
182
+
183
+ return (
184
+ <li className="predictive-search-result-item" key={page.id}>
185
+ <Link onClick={closeSearch} to={pageUrl}>
186
+ <div>
187
+ <span>{page.title}</span>
188
+ </div>
189
+ </Link>
190
+ </li>
191
+ );
192
+ })}
193
+ </ul>
194
+ </div>
195
+ );
196
+ }
197
+
198
+ function SearchResultsPredictiveProducts({
199
+ term,
200
+ products,
201
+ closeSearch,
202
+ }: PartialPredictiveSearchResult<'products'>) {
203
+ if (!products.length) return null;
204
+
205
+ return (
206
+ <div className="predictive-search-result" key="products">
207
+ <h5>Products</h5>
208
+ <ul>
209
+ {products.map((product) => {
210
+ const productUrl = urlWithTrackingParams({
211
+ baseUrl: `/products/${product.handle}`,
212
+ trackingParams: product.trackingParameters,
213
+ term: term.current,
214
+ });
215
+
216
+ const image = product?.variants?.nodes?.[0].image;
217
+ return (
218
+ <li className="predictive-search-result-item" key={product.id}>
219
+ <Link to={productUrl} onClick={closeSearch}>
220
+ {image && (
221
+ <Image
222
+ alt={image.altText ?? ''}
223
+ src={image.url}
224
+ width={50}
225
+ height={50}
226
+ />
227
+ )}
228
+ <div>
229
+ <p>{product.title}</p>
230
+ <small>
231
+ {product?.variants?.nodes?.[0].price && (
232
+ <Money data={product.variants.nodes[0].price} />
233
+ )}
234
+ </small>
235
+ </div>
236
+ </Link>
237
+ </li>
238
+ );
239
+ })}
240
+ </ul>
241
+ </div>
242
+ );
243
+ }
244
+
245
+ function SearchResultsPredictiveQueries({
246
+ queries,
247
+ inputRef,
248
+ }: PartialPredictiveSearchResult<'queries', 'inputRef'>) {
249
+ if (!queries.length) return null;
250
+
251
+ return (
252
+ <div className="predictive-search-result" key="queries">
253
+ <h5>Queries</h5>
254
+ <ul>
255
+ {queries.map((suggestion) => {
256
+ if (!suggestion) return null;
257
+
258
+ return (
259
+ <li className="predictive-search-result-item" key={suggestion.text}>
260
+ <div
261
+ role="presentation"
262
+ onClick={() => {
263
+ if (!inputRef.current) return;
264
+ inputRef.current.value = suggestion.text;
265
+ inputRef.current.focus();
266
+ }}
267
+ dangerouslySetInnerHTML={{
268
+ __html: suggestion?.styledText,
269
+ }}
270
+ />
271
+ </li>
272
+ );
273
+ })}
274
+ </ul>
275
+ </div>
276
+ );
277
+ }
278
+
279
+ function SearchResultsPredictiveEmpty({
280
+ term,
281
+ }: {
282
+ term: React.MutableRefObject<string>;
283
+ }) {
284
+ if (!term.current) {
285
+ return null;
286
+ }
287
+
288
+ return (
289
+ <p>
290
+ No results found for <q>{term.current}</q>
291
+ </p>
292
+ );
293
+ }
294
+
295
+ /**
296
+ * Hook that returns the predictive search results and fetcher and input ref.
297
+ * @example
298
+ * '''ts
299
+ * const { items, total, inputRef, term, fetcher } = usePredictiveSearch();
300
+ * '''
301
+ **/
302
+ function usePredictiveSearch(): UsePredictiveSearchReturn {
303
+ const fetcher = useFetcher<PredictiveSearchReturn>({key: 'search'});
304
+ const term = useRef<string>('');
305
+ const inputRef = useRef<HTMLInputElement | null>(null);
306
+
307
+ if (fetcher?.state === 'loading') {
308
+ term.current = String(fetcher.formData?.get('q') || '');
309
+ }
310
+
311
+ // capture the search input element as a ref
312
+ useEffect(() => {
313
+ if (!inputRef.current) {
314
+ inputRef.current = document.querySelector('input[type="search"]');
315
+ }
316
+ }, []);
317
+
318
+ const {items, total} =
319
+ fetcher?.data?.result ?? getEmptyPredictiveSearchResult();
320
+
321
+ return {items, total, inputRef, term, fetcher};
322
+ }
@@ -2,11 +2,13 @@ import {RemixBrowser} from '@remix-run/react';
2
2
  import {startTransition, StrictMode} from 'react';
3
3
  import {hydrateRoot} from 'react-dom/client';
4
4
 
5
- startTransition(() => {
6
- hydrateRoot(
7
- document,
8
- <StrictMode>
9
- <RemixBrowser />
10
- </StrictMode>,
11
- );
12
- });
5
+ if (!window.location.origin.includes('webcache.googleusercontent.com')) {
6
+ startTransition(() => {
7
+ hydrateRoot(
8
+ document,
9
+ <StrictMode>
10
+ <RemixBrowser />
11
+ </StrictMode>,
12
+ );
13
+ });
14
+ }
@@ -15,7 +15,7 @@ export default async function handleRequest(
15
15
  shop: {
16
16
  checkoutDomain: context.env.PUBLIC_CHECKOUT_DOMAIN,
17
17
  storeDomain: context.env.PUBLIC_STORE_DOMAIN,
18
- }
18
+ },
19
19
  });
20
20
 
21
21
  const body = await renderToReadableStream(
@@ -0,0 +1,43 @@
1
+ import {createHydrogenContext} from '@shopify/hydrogen';
2
+ import {AppSession} from '~/lib/session';
3
+ import {CART_QUERY_FRAGMENT} from '~/lib/fragments';
4
+
5
+ /**
6
+ * The context implementation is separate from server.ts
7
+ * so that type can be extracted for AppLoadContext
8
+ * */
9
+ export async function createAppLoadContext(
10
+ request: Request,
11
+ env: Env,
12
+ executionContext: ExecutionContext,
13
+ ) {
14
+ /**
15
+ * Open a cache instance in the worker and a custom session instance.
16
+ */
17
+ if (!env?.SESSION_SECRET) {
18
+ throw new Error('SESSION_SECRET environment variable is not set');
19
+ }
20
+
21
+ const waitUntil = executionContext.waitUntil.bind(executionContext);
22
+ const [cache, session] = await Promise.all([
23
+ caches.open('hydrogen'),
24
+ AppSession.init(request, [env.SESSION_SECRET]),
25
+ ]);
26
+
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,
36
+ },
37
+ });
38
+
39
+ return {
40
+ ...hydrogenContext,
41
+ // declare additional Remix loader context
42
+ };
43
+ }
@@ -55,6 +55,56 @@ export const CART_QUERY_FRAGMENT = `#graphql
55
55
  }
56
56
  }
57
57
  }
58
+ fragment CartLineComponent on ComponentizableCartLine {
59
+ id
60
+ quantity
61
+ attributes {
62
+ key
63
+ value
64
+ }
65
+ cost {
66
+ totalAmount {
67
+ ...Money
68
+ }
69
+ amountPerQuantity {
70
+ ...Money
71
+ }
72
+ compareAtAmountPerQuantity {
73
+ ...Money
74
+ }
75
+ }
76
+ merchandise {
77
+ ... on ProductVariant {
78
+ id
79
+ availableForSale
80
+ compareAtPrice {
81
+ ...Money
82
+ }
83
+ price {
84
+ ...Money
85
+ }
86
+ requiresShipping
87
+ title
88
+ image {
89
+ id
90
+ url
91
+ altText
92
+ width
93
+ height
94
+ }
95
+ product {
96
+ handle
97
+ title
98
+ id
99
+ vendor
100
+ }
101
+ selectedOptions {
102
+ name
103
+ value
104
+ }
105
+ }
106
+ }
107
+ }
58
108
  fragment CartApiQuery on Cart {
59
109
  updatedAt
60
110
  id
@@ -76,6 +126,9 @@ export const CART_QUERY_FRAGMENT = `#graphql
76
126
  nodes {
77
127
  ...CartLine
78
128
  }
129
+ nodes {
130
+ ...CartLineComponent
131
+ }
79
132
  }
80
133
  cost {
81
134
  subtotalAmount {