@shopify/cli-hydrogen 5.0.2 → 5.1.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 (243) hide show
  1. package/dist/commands/hydrogen/build.js +16 -2
  2. package/dist/commands/hydrogen/codegen-unstable.js +13 -24
  3. package/dist/commands/hydrogen/dev.js +45 -39
  4. package/dist/commands/hydrogen/env/list.js +25 -24
  5. package/dist/commands/hydrogen/env/list.test.js +46 -43
  6. package/dist/commands/hydrogen/env/pull.js +53 -25
  7. package/dist/commands/hydrogen/env/pull.test.js +123 -42
  8. package/dist/commands/hydrogen/generate/route.js +31 -132
  9. package/dist/commands/hydrogen/generate/route.test.js +34 -126
  10. package/dist/commands/hydrogen/init.js +46 -127
  11. package/dist/commands/hydrogen/init.test.js +352 -100
  12. package/dist/commands/hydrogen/link.js +70 -69
  13. package/dist/commands/hydrogen/link.test.js +72 -107
  14. package/dist/commands/hydrogen/list.js +22 -12
  15. package/dist/commands/hydrogen/list.test.js +51 -48
  16. package/dist/commands/hydrogen/login.js +31 -0
  17. package/dist/commands/hydrogen/logout.js +21 -0
  18. package/dist/commands/hydrogen/setup/css.js +79 -0
  19. package/dist/commands/hydrogen/setup/markets.js +53 -0
  20. package/dist/commands/hydrogen/setup.js +133 -0
  21. package/dist/commands/hydrogen/shortcut.js +2 -45
  22. package/dist/commands/hydrogen/shortcut.test.js +10 -37
  23. package/dist/generator-templates/assets/css-modules/package.json +6 -0
  24. package/dist/generator-templates/assets/postcss/package.json +10 -0
  25. package/dist/generator-templates/assets/postcss/postcss.config.js +8 -0
  26. package/dist/generator-templates/assets/tailwind/package.json +13 -0
  27. package/dist/generator-templates/assets/tailwind/postcss.config.js +10 -0
  28. package/dist/generator-templates/assets/tailwind/tailwind.config.js +8 -0
  29. package/dist/generator-templates/assets/tailwind/tailwind.css +3 -0
  30. package/dist/generator-templates/assets/vanilla-extract/package.json +9 -0
  31. package/dist/generator-templates/starter/.eslintignore +5 -0
  32. package/dist/generator-templates/starter/.eslintrc.js +18 -0
  33. package/dist/generator-templates/starter/.graphqlrc.yml +1 -0
  34. package/dist/generator-templates/starter/README.md +40 -0
  35. package/dist/generator-templates/starter/app/components/Aside.tsx +47 -0
  36. package/dist/generator-templates/starter/app/components/Cart.tsx +340 -0
  37. package/dist/generator-templates/starter/app/components/Footer.tsx +99 -0
  38. package/dist/generator-templates/starter/app/components/Header.tsx +178 -0
  39. package/dist/generator-templates/starter/app/components/Layout.tsx +95 -0
  40. package/dist/generator-templates/starter/app/components/Search.tsx +480 -0
  41. package/dist/generator-templates/starter/app/entry.client.tsx +12 -0
  42. package/dist/generator-templates/starter/app/entry.server.tsx +33 -0
  43. package/dist/generator-templates/starter/app/root.tsx +270 -0
  44. package/dist/generator-templates/starter/app/routes/$.tsx +7 -0
  45. package/dist/generator-templates/{routes → starter/app/routes}/[robots.txt].tsx +47 -69
  46. package/dist/generator-templates/starter/app/routes/[sitemap.xml].tsx +174 -0
  47. package/dist/generator-templates/starter/app/routes/_index.tsx +145 -0
  48. package/dist/generator-templates/starter/app/routes/account.$.tsx +9 -0
  49. package/dist/generator-templates/starter/app/routes/account.addresses.tsx +563 -0
  50. package/dist/generator-templates/starter/app/routes/account.orders.$id.tsx +309 -0
  51. package/dist/generator-templates/starter/app/routes/account.orders._index.tsx +196 -0
  52. package/dist/generator-templates/starter/app/routes/account.profile.tsx +289 -0
  53. package/dist/generator-templates/starter/app/routes/account.tsx +203 -0
  54. package/dist/generator-templates/starter/app/routes/account_.activate.$id.$activationToken.tsx +157 -0
  55. package/dist/generator-templates/starter/app/routes/account_.login.tsx +143 -0
  56. package/dist/generator-templates/starter/app/routes/account_.logout.tsx +33 -0
  57. package/dist/generator-templates/starter/app/routes/account_.recover.tsx +124 -0
  58. package/dist/generator-templates/starter/app/routes/account_.register.tsx +207 -0
  59. package/dist/generator-templates/starter/app/routes/account_.reset.$id.$resetToken.tsx +136 -0
  60. package/dist/generator-templates/starter/app/routes/api.predictive-search.tsx +342 -0
  61. package/dist/generator-templates/starter/app/routes/blogs.$blogHandle.$articleHandle.tsx +88 -0
  62. package/dist/generator-templates/starter/app/routes/blogs.$blogHandle._index.tsx +162 -0
  63. package/dist/generator-templates/starter/app/routes/blogs._index.tsx +94 -0
  64. package/dist/generator-templates/starter/app/routes/cart.tsx +104 -0
  65. package/dist/generator-templates/starter/app/routes/collections.$handle.tsx +184 -0
  66. package/dist/generator-templates/starter/app/routes/collections._index.tsx +120 -0
  67. package/dist/generator-templates/starter/app/routes/pages.$handle.tsx +57 -0
  68. package/dist/generator-templates/starter/app/routes/policies.$handle.tsx +94 -0
  69. package/dist/generator-templates/starter/app/routes/policies._index.tsx +63 -0
  70. package/dist/generator-templates/starter/app/routes/products.$handle.tsx +418 -0
  71. package/dist/generator-templates/starter/app/routes/search.tsx +168 -0
  72. package/dist/generator-templates/starter/app/styles/app.css +473 -0
  73. package/dist/generator-templates/starter/app/styles/reset.css +129 -0
  74. package/dist/generator-templates/starter/app/utils.ts +46 -0
  75. package/dist/generator-templates/starter/package.json +43 -0
  76. package/dist/generator-templates/starter/public/favicon.svg +28 -0
  77. package/dist/generator-templates/starter/remix.config.js +26 -0
  78. package/dist/generator-templates/starter/remix.env.d.ts +39 -0
  79. package/dist/generator-templates/starter/server.ts +253 -0
  80. package/dist/generator-templates/starter/storefrontapi.generated.d.ts +1906 -0
  81. package/dist/generator-templates/starter/tsconfig.json +22 -0
  82. package/dist/lib/auth.js +123 -0
  83. package/dist/lib/auth.test.js +157 -0
  84. package/dist/lib/build.js +51 -0
  85. package/dist/lib/check-version.js +3 -3
  86. package/dist/lib/check-version.test.js +24 -0
  87. package/dist/lib/codegen.js +26 -17
  88. package/dist/lib/environment-variables.js +68 -0
  89. package/dist/lib/environment-variables.test.js +147 -0
  90. package/dist/lib/file.js +41 -0
  91. package/dist/lib/file.test.js +69 -0
  92. package/dist/lib/flags.js +39 -2
  93. package/dist/lib/format-code.js +26 -0
  94. package/dist/lib/gid.js +12 -0
  95. package/dist/lib/{graphql.test.js → gid.test.js} +1 -1
  96. package/dist/lib/graphql/admin/client.js +27 -0
  97. package/dist/lib/graphql/admin/client.test.js +51 -0
  98. package/dist/lib/graphql/admin/create-storefront.js +13 -15
  99. package/dist/lib/graphql/admin/create-storefront.test.js +64 -0
  100. package/dist/lib/graphql/admin/fetch-job.js +6 -15
  101. package/dist/lib/graphql/admin/link-storefront.js +7 -11
  102. package/dist/lib/graphql/admin/link-storefront.test.js +38 -0
  103. package/dist/lib/graphql/admin/list-environments.js +2 -2
  104. package/dist/lib/graphql/admin/list-environments.test.js +44 -0
  105. package/dist/lib/graphql/admin/list-storefronts.js +7 -11
  106. package/dist/lib/graphql/admin/list-storefronts.test.js +44 -0
  107. package/dist/lib/graphql/admin/pull-variables.js +3 -3
  108. package/dist/lib/graphql/admin/pull-variables.test.js +37 -0
  109. package/dist/lib/graphql/business-platform/user-account.js +83 -0
  110. package/dist/lib/graphql/business-platform/user-account.test.js +80 -0
  111. package/dist/lib/log.js +185 -9
  112. package/dist/lib/log.test.js +92 -0
  113. package/dist/lib/mini-oxygen.js +19 -9
  114. package/dist/lib/missing-routes.js +0 -2
  115. package/dist/lib/onboarding/common.js +456 -0
  116. package/dist/lib/onboarding/index.js +2 -0
  117. package/dist/lib/onboarding/local.js +229 -0
  118. package/dist/lib/onboarding/remote.js +89 -0
  119. package/dist/lib/remix-version-interop.js +5 -5
  120. package/dist/lib/remix-version-interop.test.js +11 -1
  121. package/dist/lib/render-errors.js +13 -11
  122. package/dist/lib/setups/css/assets.js +89 -0
  123. package/dist/lib/setups/css/css-modules.js +22 -0
  124. package/dist/lib/setups/css/index.js +44 -0
  125. package/dist/lib/setups/css/postcss.js +34 -0
  126. package/dist/lib/setups/css/replacers.js +137 -0
  127. package/dist/lib/setups/css/tailwind.js +54 -0
  128. package/dist/lib/setups/css/vanilla-extract.js +22 -0
  129. package/dist/lib/setups/i18n/domains.test.js +25 -0
  130. package/dist/lib/setups/i18n/index.js +46 -0
  131. package/dist/lib/setups/i18n/replacers.js +227 -0
  132. package/dist/lib/setups/i18n/subdomains.test.js +25 -0
  133. package/dist/lib/setups/i18n/subfolders.test.js +25 -0
  134. package/dist/lib/setups/i18n/templates/domains.js +14 -0
  135. package/dist/lib/setups/i18n/templates/domains.ts +25 -0
  136. package/dist/lib/setups/i18n/templates/subdomains.js +14 -0
  137. package/dist/lib/setups/i18n/templates/subdomains.ts +24 -0
  138. package/dist/lib/setups/i18n/templates/subfolders.js +14 -0
  139. package/dist/lib/setups/i18n/templates/subfolders.ts +28 -0
  140. package/dist/lib/setups/routes/generate.js +244 -0
  141. package/dist/lib/setups/routes/generate.test.js +313 -0
  142. package/dist/lib/shell.js +52 -5
  143. package/dist/lib/shell.test.js +42 -16
  144. package/dist/lib/shopify-config.js +23 -18
  145. package/dist/lib/shopify-config.test.js +63 -73
  146. package/dist/lib/template-downloader.js +9 -7
  147. package/dist/lib/transpile-ts.js +9 -29
  148. package/dist/virtual-routes/routes/index.jsx +40 -19
  149. package/oclif.manifest.json +710 -1
  150. package/package.json +16 -16
  151. package/dist/commands/hydrogen/build.d.ts +0 -23
  152. package/dist/commands/hydrogen/check.d.ts +0 -15
  153. package/dist/commands/hydrogen/codegen-unstable.d.ts +0 -15
  154. package/dist/commands/hydrogen/dev.d.ts +0 -21
  155. package/dist/commands/hydrogen/env/list.d.ts +0 -18
  156. package/dist/commands/hydrogen/env/pull.d.ts +0 -22
  157. package/dist/commands/hydrogen/g.d.ts +0 -10
  158. package/dist/commands/hydrogen/generate/route.d.ts +0 -32
  159. package/dist/commands/hydrogen/generate/route.test.d.ts +0 -1
  160. package/dist/commands/hydrogen/generate/routes.d.ts +0 -16
  161. package/dist/commands/hydrogen/init.d.ts +0 -24
  162. package/dist/commands/hydrogen/init.test.d.ts +0 -1
  163. package/dist/commands/hydrogen/link.d.ts +0 -23
  164. package/dist/commands/hydrogen/link.test.d.ts +0 -1
  165. package/dist/commands/hydrogen/list.d.ts +0 -21
  166. package/dist/commands/hydrogen/list.test.d.ts +0 -1
  167. package/dist/commands/hydrogen/preview.d.ts +0 -17
  168. package/dist/commands/hydrogen/shortcut.d.ts +0 -9
  169. package/dist/commands/hydrogen/shortcut.test.d.ts +0 -1
  170. package/dist/commands/hydrogen/unlink.d.ts +0 -16
  171. package/dist/commands/hydrogen/unlink.test.d.ts +0 -1
  172. package/dist/create-app.d.ts +0 -1
  173. package/dist/generator-templates/routes/[sitemap.xml].tsx +0 -235
  174. package/dist/generator-templates/routes/account/login.tsx +0 -103
  175. package/dist/generator-templates/routes/account/register.tsx +0 -103
  176. package/dist/generator-templates/routes/cart.tsx +0 -81
  177. package/dist/generator-templates/routes/collections/$collectionHandle.tsx +0 -104
  178. package/dist/generator-templates/routes/collections/index.tsx +0 -102
  179. package/dist/generator-templates/routes/graphiql.tsx +0 -10
  180. package/dist/generator-templates/routes/index.tsx +0 -40
  181. package/dist/generator-templates/routes/pages/$pageHandle.tsx +0 -112
  182. package/dist/generator-templates/routes/policies/$policyHandle.tsx +0 -140
  183. package/dist/generator-templates/routes/policies/index.tsx +0 -117
  184. package/dist/generator-templates/routes/products/$productHandle.tsx +0 -92
  185. package/dist/hooks/init.d.ts +0 -5
  186. package/dist/lib/admin-session.d.ts +0 -6
  187. package/dist/lib/admin-session.js +0 -16
  188. package/dist/lib/admin-session.test.d.ts +0 -1
  189. package/dist/lib/admin-session.test.js +0 -27
  190. package/dist/lib/admin-urls.d.ts +0 -8
  191. package/dist/lib/check-lockfile.d.ts +0 -3
  192. package/dist/lib/check-lockfile.test.d.ts +0 -1
  193. package/dist/lib/check-version.d.ts +0 -16
  194. package/dist/lib/check-version.test.d.ts +0 -1
  195. package/dist/lib/codegen.d.ts +0 -26
  196. package/dist/lib/combined-environment-variables.d.ts +0 -8
  197. package/dist/lib/combined-environment-variables.js +0 -57
  198. package/dist/lib/combined-environment-variables.test.d.ts +0 -1
  199. package/dist/lib/combined-environment-variables.test.js +0 -111
  200. package/dist/lib/config.d.ts +0 -20
  201. package/dist/lib/flags.d.ts +0 -27
  202. package/dist/lib/flags.test.d.ts +0 -1
  203. package/dist/lib/graphql/admin/create-storefront.d.ts +0 -17
  204. package/dist/lib/graphql/admin/fetch-job.d.ts +0 -23
  205. package/dist/lib/graphql/admin/link-storefront.d.ts +0 -14
  206. package/dist/lib/graphql/admin/list-environments.d.ts +0 -21
  207. package/dist/lib/graphql/admin/list-storefronts.d.ts +0 -25
  208. package/dist/lib/graphql/admin/pull-variables.d.ts +0 -21
  209. package/dist/lib/graphql.d.ts +0 -21
  210. package/dist/lib/graphql.js +0 -18
  211. package/dist/lib/graphql.test.d.ts +0 -1
  212. package/dist/lib/log.d.ts +0 -6
  213. package/dist/lib/mini-oxygen.d.ts +0 -22
  214. package/dist/lib/missing-routes.d.ts +0 -8
  215. package/dist/lib/missing-routes.test.d.ts +0 -1
  216. package/dist/lib/missing-storefronts.d.ts +0 -5
  217. package/dist/lib/missing-storefronts.js +0 -18
  218. package/dist/lib/process.d.ts +0 -6
  219. package/dist/lib/pull-environment-variables.d.ts +0 -20
  220. package/dist/lib/pull-environment-variables.js +0 -57
  221. package/dist/lib/pull-environment-variables.test.d.ts +0 -1
  222. package/dist/lib/pull-environment-variables.test.js +0 -174
  223. package/dist/lib/remix-version-interop.d.ts +0 -11
  224. package/dist/lib/remix-version-interop.test.d.ts +0 -1
  225. package/dist/lib/render-errors.d.ts +0 -16
  226. package/dist/lib/shell.d.ts +0 -11
  227. package/dist/lib/shell.test.d.ts +0 -1
  228. package/dist/lib/shop.d.ts +0 -7
  229. package/dist/lib/shop.js +0 -32
  230. package/dist/lib/shop.test.d.ts +0 -1
  231. package/dist/lib/shop.test.js +0 -78
  232. package/dist/lib/shopify-config.d.ts +0 -35
  233. package/dist/lib/shopify-config.test.d.ts +0 -1
  234. package/dist/lib/string.d.ts +0 -3
  235. package/dist/lib/string.test.d.ts +0 -1
  236. package/dist/lib/template-downloader.d.ts +0 -6
  237. package/dist/lib/transpile-ts.d.ts +0 -16
  238. package/dist/lib/user-errors.d.ts +0 -9
  239. package/dist/lib/user-errors.js +0 -11
  240. package/dist/lib/virtual-routes.d.ts +0 -7
  241. package/dist/lib/virtual-routes.test.d.ts +0 -1
  242. /package/dist/{commands/hydrogen/env/list.test.d.ts → lib/setups/css/common.js} +0 -0
  243. /package/dist/{commands/hydrogen/env/pull.test.d.ts → lib/setups/i18n/mock-i18n-types.js} +0 -0
@@ -0,0 +1,104 @@
1
+ import {Await, useMatches} from '@remix-run/react';
2
+ import {Suspense} from 'react';
3
+ import type {CartQueryData} from '@shopify/hydrogen';
4
+ import {CartForm} from '@shopify/hydrogen';
5
+ import type {V2_MetaFunction} from '@shopify/remix-oxygen';
6
+ import {type ActionArgs, json} from '@shopify/remix-oxygen';
7
+ import type {CartApiQueryFragment} from 'storefrontapi.generated';
8
+ import {CartMain} from '~/components/Cart';
9
+
10
+ export const meta: V2_MetaFunction = () => {
11
+ return [{title: `Hydrogen | Cart`}];
12
+ };
13
+
14
+ export async function action({request, context}: ActionArgs) {
15
+ const {session, cart} = context;
16
+
17
+ const [formData, customerAccessToken] = await Promise.all([
18
+ request.formData(),
19
+ session.get('customerAccessToken'),
20
+ ]);
21
+
22
+ const {action, inputs} = CartForm.getFormInput(formData);
23
+
24
+ if (!action) {
25
+ throw new Error('No action provided');
26
+ }
27
+
28
+ let status = 200;
29
+ let result: CartQueryData;
30
+
31
+ switch (action) {
32
+ case CartForm.ACTIONS.LinesAdd:
33
+ result = await cart.addLines(inputs.lines);
34
+ break;
35
+ case CartForm.ACTIONS.LinesUpdate:
36
+ result = await cart.updateLines(inputs.lines);
37
+ break;
38
+ case CartForm.ACTIONS.LinesRemove:
39
+ result = await cart.removeLines(inputs.lineIds);
40
+ break;
41
+ case CartForm.ACTIONS.DiscountCodesUpdate: {
42
+ const formDiscountCode = inputs.discountCode;
43
+
44
+ // User inputted discount code
45
+ const discountCodes = (
46
+ formDiscountCode ? [formDiscountCode] : []
47
+ ) as string[];
48
+
49
+ // Combine discount codes already applied on cart
50
+ discountCodes.push(...inputs.discountCodes);
51
+
52
+ result = await cart.updateDiscountCodes(discountCodes);
53
+ break;
54
+ }
55
+ case CartForm.ACTIONS.BuyerIdentityUpdate: {
56
+ result = await cart.updateBuyerIdentity({
57
+ ...inputs.buyerIdentity,
58
+ customerAccessToken,
59
+ });
60
+ break;
61
+ }
62
+ default:
63
+ throw new Error(`${action} cart action is not defined`);
64
+ }
65
+
66
+ const cartId = result.cart.id;
67
+ const headers = cart.setCartId(result.cart.id);
68
+ const {cart: cartResult, errors} = result;
69
+
70
+ const redirectTo = formData.get('redirectTo') ?? null;
71
+ if (typeof redirectTo === 'string') {
72
+ status = 303;
73
+ headers.set('Location', redirectTo);
74
+ }
75
+
76
+ return json(
77
+ {
78
+ cart: cartResult,
79
+ errors,
80
+ analytics: {
81
+ cartId,
82
+ },
83
+ },
84
+ {status, headers},
85
+ );
86
+ }
87
+
88
+ export default function Cart() {
89
+ const [root] = useMatches();
90
+ const cart = root.data?.cart as Promise<CartApiQueryFragment | null>;
91
+
92
+ return (
93
+ <div className="cart">
94
+ <h1>Cart</h1>
95
+ <Suspense fallback={<p>Loading cart ...</p>}>
96
+ <Await errorElement={<div>An error occurred</div>} resolve={cart}>
97
+ {(cart) => {
98
+ return <CartMain layout="page" cart={cart} />;
99
+ }}
100
+ </Await>
101
+ </Suspense>
102
+ </div>
103
+ );
104
+ }
@@ -0,0 +1,184 @@
1
+ import type {V2_MetaFunction} from '@shopify/remix-oxygen';
2
+ import {json, redirect, type LoaderArgs} from '@shopify/remix-oxygen';
3
+ import {useLoaderData, Link} from '@remix-run/react';
4
+ import {
5
+ Pagination,
6
+ getPaginationVariables,
7
+ Image,
8
+ Money,
9
+ } from '@shopify/hydrogen';
10
+ import type {ProductItemFragment} from 'storefrontapi.generated';
11
+ import {useVariantUrl} from '~/utils';
12
+
13
+ export const meta: V2_MetaFunction = ({data}) => {
14
+ return [{title: `Hydrogen | ${data.collection.title} Collection`}];
15
+ };
16
+
17
+ export async function loader({request, params, context}: LoaderArgs) {
18
+ const {handle} = params;
19
+ const {storefront} = context;
20
+ const paginationVariables = getPaginationVariables(request, {
21
+ pageBy: 8,
22
+ });
23
+
24
+ if (!handle) {
25
+ return redirect('/collections');
26
+ }
27
+
28
+ const {collection} = await storefront.query(COLLECTION_QUERY, {
29
+ variables: {handle, ...paginationVariables},
30
+ });
31
+
32
+ if (!collection) {
33
+ throw new Response(`Collection ${handle} not found`, {
34
+ status: 404,
35
+ });
36
+ }
37
+ return json({collection});
38
+ }
39
+
40
+ export default function Collection() {
41
+ const {collection} = useLoaderData<typeof loader>();
42
+
43
+ return (
44
+ <div className="collection">
45
+ <h1>{collection.title}</h1>
46
+ <p className="collection-description">{collection.description}</p>
47
+ <Pagination connection={collection.products}>
48
+ {({nodes, isLoading, PreviousLink, NextLink}) => (
49
+ <>
50
+ <PreviousLink>
51
+ {isLoading ? 'Loading...' : <span>↑ Load previous</span>}
52
+ </PreviousLink>
53
+ <ProductsGrid products={nodes} />
54
+ <br />
55
+ <NextLink>
56
+ {isLoading ? 'Loading...' : <span>Load more ↓</span>}
57
+ </NextLink>
58
+ </>
59
+ )}
60
+ </Pagination>
61
+ </div>
62
+ );
63
+ }
64
+
65
+ function ProductsGrid({products}: {products: ProductItemFragment[]}) {
66
+ return (
67
+ <div className="products-grid">
68
+ {products.map((product, index) => {
69
+ return (
70
+ <ProductItem
71
+ key={product.id}
72
+ product={product}
73
+ loading={index < 8 ? 'eager' : undefined}
74
+ />
75
+ );
76
+ })}
77
+ </div>
78
+ );
79
+ }
80
+
81
+ function ProductItem({
82
+ product,
83
+ loading,
84
+ }: {
85
+ product: ProductItemFragment;
86
+ loading?: 'eager' | 'lazy';
87
+ }) {
88
+ const variant = product.variants.nodes[0];
89
+ const variantUrl = useVariantUrl(product.handle, variant.selectedOptions);
90
+ return (
91
+ <Link
92
+ className="product-item"
93
+ key={product.id}
94
+ prefetch="intent"
95
+ to={variantUrl}
96
+ >
97
+ {product.featuredImage && (
98
+ <Image
99
+ alt={product.featuredImage.altText || product.title}
100
+ aspectRatio="1/1"
101
+ data={product.featuredImage}
102
+ loading={loading}
103
+ sizes="(min-width: 45em) 400px, 100vw"
104
+ />
105
+ )}
106
+ <h4>{product.title}</h4>
107
+ <small>
108
+ <Money data={product.priceRange.minVariantPrice} />
109
+ </small>
110
+ </Link>
111
+ );
112
+ }
113
+
114
+ const PRODUCT_ITEM_FRAGMENT = `#graphql
115
+ fragment MoneyProductItem on MoneyV2 {
116
+ amount
117
+ currencyCode
118
+ }
119
+ fragment ProductItem on Product {
120
+ id
121
+ handle
122
+ title
123
+ featuredImage {
124
+ id
125
+ altText
126
+ url
127
+ width
128
+ height
129
+ }
130
+ priceRange {
131
+ minVariantPrice {
132
+ ...MoneyProductItem
133
+ }
134
+ maxVariantPrice {
135
+ ...MoneyProductItem
136
+ }
137
+ }
138
+ variants(first: 1) {
139
+ nodes {
140
+ selectedOptions {
141
+ name
142
+ value
143
+ }
144
+ }
145
+ }
146
+ }
147
+ ` as const;
148
+
149
+ // NOTE: https://shopify.dev/docs/api/storefront/2022-04/objects/collection
150
+ const COLLECTION_QUERY = `#graphql
151
+ ${PRODUCT_ITEM_FRAGMENT}
152
+ query Collection(
153
+ $handle: String!
154
+ $country: CountryCode
155
+ $language: LanguageCode
156
+ $first: Int
157
+ $last: Int
158
+ $startCursor: String
159
+ $endCursor: String
160
+ ) @inContext(country: $country, language: $language) {
161
+ collection(handle: $handle) {
162
+ id
163
+ handle
164
+ title
165
+ description
166
+ products(
167
+ first: $first,
168
+ last: $last,
169
+ before: $startCursor,
170
+ after: $endCursor
171
+ ) {
172
+ nodes {
173
+ ...ProductItem
174
+ }
175
+ pageInfo {
176
+ hasPreviousPage
177
+ hasNextPage
178
+ hasNextPage
179
+ endCursor
180
+ }
181
+ }
182
+ }
183
+ }
184
+ ` as const;
@@ -0,0 +1,120 @@
1
+ import {useLoaderData, Link} from '@remix-run/react';
2
+ import {json, type LoaderArgs} from '@shopify/remix-oxygen';
3
+ import {Pagination, getPaginationVariables, Image} from '@shopify/hydrogen';
4
+ import type {CollectionFragment} from 'storefrontapi.generated';
5
+
6
+ export async function loader({context, request}: LoaderArgs) {
7
+ const paginationVariables = getPaginationVariables(request, {
8
+ pageBy: 4,
9
+ });
10
+
11
+ const {collections} = await context.storefront.query(COLLECTIONS_QUERY, {
12
+ variables: paginationVariables,
13
+ });
14
+
15
+ return json({collections});
16
+ }
17
+
18
+ export default function Collections() {
19
+ const {collections} = useLoaderData<typeof loader>();
20
+
21
+ return (
22
+ <div className="collections">
23
+ <h1>Collections</h1>
24
+ <Pagination connection={collections}>
25
+ {({nodes, isLoading, PreviousLink, NextLink}) => (
26
+ <div>
27
+ <PreviousLink>
28
+ {isLoading ? 'Loading...' : <span>↑ Load previous</span>}
29
+ </PreviousLink>
30
+ <CollectionsGrid collections={nodes} />
31
+ <NextLink>
32
+ {isLoading ? 'Loading...' : <span>Load more ↓</span>}
33
+ </NextLink>
34
+ </div>
35
+ )}
36
+ </Pagination>
37
+ </div>
38
+ );
39
+ }
40
+
41
+ function CollectionsGrid({collections}: {collections: CollectionFragment[]}) {
42
+ return (
43
+ <div className="collections-grid">
44
+ {collections.map((collection, index) => (
45
+ <CollectionItem
46
+ key={collection.id}
47
+ collection={collection}
48
+ index={index}
49
+ />
50
+ ))}
51
+ </div>
52
+ );
53
+ }
54
+
55
+ function CollectionItem({
56
+ collection,
57
+ index,
58
+ }: {
59
+ collection: CollectionFragment;
60
+ index: number;
61
+ }) {
62
+ return (
63
+ <Link
64
+ className="collection-item"
65
+ key={collection.id}
66
+ to={`/collections/${collection.handle}`}
67
+ prefetch="intent"
68
+ >
69
+ {collection.image && (
70
+ <Image
71
+ alt={collection.image.altText || collection.title}
72
+ aspectRatio="1/1"
73
+ data={collection.image}
74
+ loading={index < 3 ? 'eager' : undefined}
75
+ />
76
+ )}
77
+ <h5>{collection.title}</h5>
78
+ </Link>
79
+ );
80
+ }
81
+
82
+ const COLLECTIONS_QUERY = `#graphql
83
+ fragment Collection on Collection {
84
+ id
85
+ title
86
+ handle
87
+ image {
88
+ id
89
+ url
90
+ altText
91
+ width
92
+ height
93
+ }
94
+ }
95
+ query StoreCollections(
96
+ $country: CountryCode
97
+ $endCursor: String
98
+ $first: Int
99
+ $language: LanguageCode
100
+ $last: Int
101
+ $startCursor: String
102
+ ) @inContext(country: $country, language: $language) {
103
+ collections(
104
+ first: $first,
105
+ last: $last,
106
+ before: $startCursor,
107
+ after: $endCursor
108
+ ) {
109
+ nodes {
110
+ ...Collection
111
+ }
112
+ pageInfo {
113
+ hasNextPage
114
+ hasPreviousPage
115
+ startCursor
116
+ endCursor
117
+ }
118
+ }
119
+ }
120
+ ` as const;
@@ -0,0 +1,57 @@
1
+ import type {V2_MetaFunction} from '@shopify/remix-oxygen';
2
+ import {json, type LoaderArgs} from '@shopify/remix-oxygen';
3
+ import {useLoaderData} from '@remix-run/react';
4
+
5
+ export const meta: V2_MetaFunction = ({data}) => {
6
+ return [{title: `Hydrogen | ${data.page.title}`}];
7
+ };
8
+
9
+ export async function loader({params, context}: LoaderArgs) {
10
+ if (!params.handle) {
11
+ throw new Error('Missing page handle');
12
+ }
13
+
14
+ const {page} = await context.storefront.query(PAGE_QUERY, {
15
+ variables: {
16
+ handle: params.handle,
17
+ },
18
+ });
19
+
20
+ if (!page) {
21
+ throw new Response('Not Found', {status: 404});
22
+ }
23
+
24
+ return json({page});
25
+ }
26
+
27
+ export default function Page() {
28
+ const {page} = useLoaderData<typeof loader>();
29
+
30
+ return (
31
+ <div className="page">
32
+ <header>
33
+ <h1>{page.title}</h1>
34
+ </header>
35
+ <main dangerouslySetInnerHTML={{__html: page.body}} />
36
+ </div>
37
+ );
38
+ }
39
+
40
+ const PAGE_QUERY = `#graphql
41
+ query Page(
42
+ $language: LanguageCode,
43
+ $country: CountryCode,
44
+ $handle: String!
45
+ )
46
+ @inContext(language: $language, country: $country) {
47
+ page(handle: $handle) {
48
+ id
49
+ title
50
+ body
51
+ seo {
52
+ description
53
+ title
54
+ }
55
+ }
56
+ }
57
+ ` as const;
@@ -0,0 +1,94 @@
1
+ import type {V2_MetaFunction} from '@shopify/remix-oxygen';
2
+ import {json, type LoaderArgs} from '@shopify/remix-oxygen';
3
+ import {Link, useLoaderData} from '@remix-run/react';
4
+ import {type Shop} from '@shopify/hydrogen-react/storefront-api-types';
5
+
6
+ type SelectedPolicies = keyof Pick<
7
+ Shop,
8
+ 'privacyPolicy' | 'shippingPolicy' | 'termsOfService' | 'refundPolicy'
9
+ >;
10
+
11
+ export const meta: V2_MetaFunction = ({data}) => {
12
+ return [{title: `Hydrogen | ${data.policy.title}`}];
13
+ };
14
+
15
+ export async function loader({params, context}: LoaderArgs) {
16
+ if (!params.handle) {
17
+ throw new Response('No handle was passed in', {status: 404});
18
+ }
19
+
20
+ const policyName = params.handle.replace(
21
+ /-([a-z])/g,
22
+ (_: unknown, m1: string) => m1.toUpperCase(),
23
+ ) as SelectedPolicies;
24
+
25
+ const data = await context.storefront.query(POLICY_CONTENT_QUERY, {
26
+ variables: {
27
+ privacyPolicy: false,
28
+ shippingPolicy: false,
29
+ termsOfService: false,
30
+ refundPolicy: false,
31
+ [policyName]: true,
32
+ language: context.storefront.i18n?.language,
33
+ },
34
+ });
35
+
36
+ const policy = data.shop?.[policyName];
37
+
38
+ if (!policy) {
39
+ throw new Response('Could not find the policy', {status: 404});
40
+ }
41
+
42
+ return json({policy});
43
+ }
44
+
45
+ export default function Policy() {
46
+ const {policy} = useLoaderData<typeof loader>();
47
+
48
+ return (
49
+ <div className="policy">
50
+ <br />
51
+ <br />
52
+ <div>
53
+ <Link to="/policies">← Back to Policies</Link>
54
+ </div>
55
+ <br />
56
+ <h1>{policy.title}</h1>
57
+ <div dangerouslySetInnerHTML={{__html: policy.body}} />
58
+ </div>
59
+ );
60
+ }
61
+
62
+ // NOTE: https://shopify.dev/docs/api/storefront/latest/objects/Shop
63
+ const POLICY_CONTENT_QUERY = `#graphql
64
+ fragment Policy on ShopPolicy {
65
+ body
66
+ handle
67
+ id
68
+ title
69
+ url
70
+ }
71
+ query Policy(
72
+ $country: CountryCode
73
+ $language: LanguageCode
74
+ $privacyPolicy: Boolean!
75
+ $refundPolicy: Boolean!
76
+ $shippingPolicy: Boolean!
77
+ $termsOfService: Boolean!
78
+ ) @inContext(language: $language, country: $country) {
79
+ shop {
80
+ privacyPolicy @include(if: $privacyPolicy) {
81
+ ...Policy
82
+ }
83
+ shippingPolicy @include(if: $shippingPolicy) {
84
+ ...Policy
85
+ }
86
+ termsOfService @include(if: $termsOfService) {
87
+ ...Policy
88
+ }
89
+ refundPolicy @include(if: $refundPolicy) {
90
+ ...Policy
91
+ }
92
+ }
93
+ }
94
+ ` as const;
@@ -0,0 +1,63 @@
1
+ import {json, type LoaderArgs} from '@shopify/remix-oxygen';
2
+ import {useLoaderData, Link} from '@remix-run/react';
3
+
4
+ export async function loader({context}: LoaderArgs) {
5
+ const data = await context.storefront.query(POLICIES_QUERY);
6
+ const policies = Object.values(data.shop || {});
7
+
8
+ if (!policies.length) {
9
+ throw new Response('No policies found', {status: 404});
10
+ }
11
+
12
+ return json({policies});
13
+ }
14
+
15
+ export default function Policies() {
16
+ const {policies} = useLoaderData<typeof loader>();
17
+
18
+ return (
19
+ <div className="policies">
20
+ <h1>Policies</h1>
21
+ <div>
22
+ {policies.map((policy) => {
23
+ if (!policy) return null;
24
+ return (
25
+ <fieldset key={policy.id}>
26
+ <Link to={`/policies/${policy.handle}`}>{policy.title}</Link>
27
+ </fieldset>
28
+ );
29
+ })}
30
+ </div>
31
+ </div>
32
+ );
33
+ }
34
+
35
+ const POLICIES_QUERY = `#graphql
36
+ fragment PolicyItem on ShopPolicy {
37
+ id
38
+ title
39
+ handle
40
+ }
41
+ query Policies ($country: CountryCode, $language: LanguageCode)
42
+ @inContext(country: $country, language: $language) {
43
+ shop {
44
+ privacyPolicy {
45
+ ...PolicyItem
46
+ }
47
+ shippingPolicy {
48
+ ...PolicyItem
49
+ }
50
+ termsOfService {
51
+ ...PolicyItem
52
+ }
53
+ refundPolicy {
54
+ ...PolicyItem
55
+ }
56
+ subscriptionPolicy {
57
+ id
58
+ title
59
+ handle
60
+ }
61
+ }
62
+ }
63
+ ` as const;