@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,5 @@
1
+ build
2
+ node_modules
3
+ bin
4
+ *.d.ts
5
+ dist
@@ -0,0 +1,18 @@
1
+ /**
2
+ * @type {import("@types/eslint").Linter.BaseConfig}
3
+ */
4
+ module.exports = {
5
+ extends: [
6
+ '@remix-run/eslint-config',
7
+ 'plugin:hydrogen/recommended',
8
+ 'plugin:hydrogen/typescript',
9
+ ],
10
+ rules: {
11
+ '@typescript-eslint/ban-ts-comment': 'off',
12
+ '@typescript-eslint/naming-convention': 'off',
13
+ 'hydrogen/prefer-image-component': 'off',
14
+ 'no-useless-escape': 'off',
15
+ '@typescript-eslint/no-non-null-asserted-optional-chain': 'off',
16
+ 'no-case-declarations': 'off',
17
+ },
18
+ };
@@ -0,0 +1 @@
1
+ schema: node_modules/@shopify/hydrogen-react/storefront.schema.json
@@ -0,0 +1,40 @@
1
+ # Hydrogen template: Skeleton
2
+
3
+ Hydrogen is Shopify’s stack for headless commerce. Hydrogen is designed to dovetail with [Remix](https://remix.run/), Shopify’s full stack web framework. This template contains a **minimal setup** of components, queries and tooling to get started with Hydrogen.
4
+
5
+ [Check out Hydrogen docs](https://shopify.dev/custom-storefronts/hydrogen)
6
+ [Get familiar with Remix](https://remix.run/docs/en/v1)
7
+
8
+ ## What's included
9
+
10
+ - Remix
11
+ - Hydrogen
12
+ - Oxygen
13
+ - Shopify CLI
14
+ - ESLint
15
+ - Prettier
16
+ - GraphQL generator
17
+ - TypeScript and JavaScript flavors
18
+ - Minimal setup of components and routes
19
+
20
+ ## Getting started
21
+
22
+ **Requirements:**
23
+
24
+ - Node.js version 16.14.0 or higher
25
+
26
+ ```bash
27
+ npm create @shopify/hydrogen@latest
28
+ ```
29
+
30
+ ## Building for production
31
+
32
+ ```bash
33
+ npm run build
34
+ ```
35
+
36
+ ## Local development
37
+
38
+ ```bash
39
+ npm run dev
40
+ ```
@@ -0,0 +1,47 @@
1
+ /**
2
+ * A side bar component with Overlay that works without JavaScript.
3
+ * @example
4
+ * ```ts
5
+ * <Aside id="search-aside" heading="SEARCH">`
6
+ * <input type="search" />
7
+ * ...
8
+ * </Aside>
9
+ * ```
10
+ */
11
+ export function Aside({
12
+ children,
13
+ heading,
14
+ id = 'aside',
15
+ }: {
16
+ children?: React.ReactNode;
17
+ heading: React.ReactNode;
18
+ id?: string;
19
+ }) {
20
+ return (
21
+ <div aria-modal className="overlay" id={id} role="dialog">
22
+ <button
23
+ className="close-outside"
24
+ onClick={() => {
25
+ history.go(-1);
26
+ window.location.hash = '';
27
+ }}
28
+ />
29
+ <aside>
30
+ <header>
31
+ <h3>{heading}</h3>
32
+ <CloseAside />
33
+ </header>
34
+ <main>{children}</main>
35
+ </aside>
36
+ </div>
37
+ );
38
+ }
39
+
40
+ function CloseAside() {
41
+ return (
42
+ /* eslint-disable-next-line jsx-a11y/anchor-is-valid */
43
+ <a className="close" href="#" onChange={() => history.go(-1)}>
44
+ &times;
45
+ </a>
46
+ );
47
+ }
@@ -0,0 +1,340 @@
1
+ import {CartForm, Image, Money} from '@shopify/hydrogen';
2
+ import type {CartLineUpdateInput} from '@shopify/hydrogen/storefront-api-types';
3
+ import {Link} from '@remix-run/react';
4
+ import type {CartApiQueryFragment} from 'storefrontapi.generated';
5
+ import {useVariantUrl} from '~/utils';
6
+
7
+ type CartLine = CartApiQueryFragment['lines']['nodes'][0];
8
+
9
+ type CartMainProps = {
10
+ cart: CartApiQueryFragment | null;
11
+ layout: 'page' | 'aside';
12
+ };
13
+
14
+ export function CartMain({layout, cart}: CartMainProps) {
15
+ const linesCount = Boolean(cart?.lines?.nodes?.length || 0);
16
+ const withDiscount =
17
+ cart &&
18
+ Boolean(cart.discountCodes.filter((code) => code.applicable).length);
19
+ const className = `cart-main ${withDiscount ? 'with-discount' : ''}`;
20
+
21
+ return (
22
+ <div className={className}>
23
+ <CartEmpty hidden={linesCount} layout={layout} />
24
+ <CartDetails cart={cart} layout={layout} />
25
+ </div>
26
+ );
27
+ }
28
+
29
+ function CartDetails({layout, cart}: CartMainProps) {
30
+ const cartHasItems = !!cart && cart.totalQuantity > 0;
31
+
32
+ return (
33
+ <div className="cart-details">
34
+ <CartLines lines={cart?.lines} layout={layout} />
35
+ {cartHasItems && (
36
+ <CartSummary cost={cart.cost} layout={layout}>
37
+ <CartDiscounts discountCodes={cart.discountCodes} />
38
+ <CartCheckoutActions checkoutUrl={cart.checkoutUrl} />
39
+ </CartSummary>
40
+ )}
41
+ </div>
42
+ );
43
+ }
44
+
45
+ function CartLines({
46
+ lines,
47
+ layout,
48
+ }: {
49
+ layout: CartMainProps['layout'];
50
+ lines: CartApiQueryFragment['lines'] | undefined;
51
+ }) {
52
+ if (!lines) return null;
53
+
54
+ return (
55
+ <div aria-labelledby="cart-lines">
56
+ <ul>
57
+ {lines.nodes.map((line) => (
58
+ <CartLineItem key={line.id} line={line} layout={layout} />
59
+ ))}
60
+ </ul>
61
+ </div>
62
+ );
63
+ }
64
+
65
+ function CartLineItem({
66
+ layout,
67
+ line,
68
+ }: {
69
+ layout: CartMainProps['layout'];
70
+ line: CartLine;
71
+ }) {
72
+ const {id, merchandise} = line;
73
+ const {product, title, image, selectedOptions} = merchandise;
74
+ const lineItemUrl = useVariantUrl(product.handle, selectedOptions);
75
+
76
+ return (
77
+ <li key={id} className="cart-line">
78
+ {image && (
79
+ <Image
80
+ alt={title}
81
+ aspectRatio="1/1"
82
+ data={image}
83
+ height={100}
84
+ loading="lazy"
85
+ width={100}
86
+ />
87
+ )}
88
+
89
+ <div>
90
+ <Link
91
+ prefetch="intent"
92
+ to={lineItemUrl}
93
+ onClick={() => {
94
+ if (layout === 'aside') {
95
+ // close the drawer
96
+ window.location.href = lineItemUrl;
97
+ }
98
+ }}
99
+ >
100
+ <p>
101
+ <strong>{product.title}</strong>
102
+ </p>
103
+ </Link>
104
+ <CartLinePrice line={line} as="span" />
105
+ <ul>
106
+ {selectedOptions.map((option) => (
107
+ <li key={option.name}>
108
+ <small>
109
+ {option.name}: {option.value}
110
+ </small>
111
+ </li>
112
+ ))}
113
+ </ul>
114
+ <CartLineQuantity line={line} />
115
+ </div>
116
+ </li>
117
+ );
118
+ }
119
+
120
+ function CartCheckoutActions({checkoutUrl}: {checkoutUrl: string}) {
121
+ if (!checkoutUrl) return null;
122
+
123
+ return (
124
+ <div>
125
+ <a href={checkoutUrl} target="_self">
126
+ <p>Continue to Checkout &rarr;</p>
127
+ </a>
128
+ <br />
129
+ </div>
130
+ );
131
+ }
132
+
133
+ export function CartSummary({
134
+ cost,
135
+ layout,
136
+ children = null,
137
+ }: {
138
+ children?: React.ReactNode;
139
+ cost: CartApiQueryFragment['cost'];
140
+ layout: CartMainProps['layout'];
141
+ }) {
142
+ const className =
143
+ layout === 'page' ? 'cart-summary-page' : 'cart-summary-aside';
144
+
145
+ return (
146
+ <div aria-labelledby="cart-summary" className={className}>
147
+ <h4>Totals</h4>
148
+ <dl className="cart-subtotal">
149
+ <dt>Subtotal</dt>
150
+ <dd>
151
+ {cost?.subtotalAmount?.amount ? (
152
+ <Money data={cost?.subtotalAmount} />
153
+ ) : (
154
+ '-'
155
+ )}
156
+ </dd>
157
+ </dl>
158
+ {children}
159
+ </div>
160
+ );
161
+ }
162
+
163
+ function CartLineRemoveButton({lineIds}: {lineIds: string[]}) {
164
+ return (
165
+ <CartForm
166
+ route="/cart"
167
+ action={CartForm.ACTIONS.LinesRemove}
168
+ inputs={{lineIds}}
169
+ >
170
+ <button type="submit">Remove</button>
171
+ </CartForm>
172
+ );
173
+ }
174
+
175
+ function CartLineQuantity({line}: {line: CartLine}) {
176
+ if (!line || typeof line?.quantity === 'undefined') return null;
177
+ const {id: lineId, quantity} = line;
178
+ const prevQuantity = Number(Math.max(0, quantity - 1).toFixed(0));
179
+ const nextQuantity = Number((quantity + 1).toFixed(0));
180
+
181
+ return (
182
+ <div className="cart-line-quantiy">
183
+ <small>Quantity: {quantity} &nbsp;&nbsp;</small>
184
+ <CartLineUpdateButton lines={[{id: lineId, quantity: prevQuantity}]}>
185
+ <button
186
+ aria-label="Decrease quantity"
187
+ disabled={quantity <= 1}
188
+ name="decrease-quantity"
189
+ value={prevQuantity}
190
+ >
191
+ <span>&#8722; </span>
192
+ </button>
193
+ </CartLineUpdateButton>
194
+ &nbsp;
195
+ <CartLineUpdateButton lines={[{id: lineId, quantity: nextQuantity}]}>
196
+ <button
197
+ aria-label="Increase quantity"
198
+ name="increase-quantity"
199
+ value={nextQuantity}
200
+ >
201
+ <span>&#43;</span>
202
+ </button>
203
+ </CartLineUpdateButton>
204
+ &nbsp;
205
+ <CartLineRemoveButton lineIds={[lineId]} />
206
+ </div>
207
+ );
208
+ }
209
+
210
+ function CartLinePrice({
211
+ line,
212
+ priceType = 'regular',
213
+ ...passthroughProps
214
+ }: {
215
+ line: CartLine;
216
+ priceType?: 'regular' | 'compareAt';
217
+ [key: string]: any;
218
+ }) {
219
+ if (!line?.cost?.amountPerQuantity || !line?.cost?.totalAmount) return null;
220
+
221
+ const moneyV2 =
222
+ priceType === 'regular'
223
+ ? line.cost.totalAmount
224
+ : line.cost.compareAtAmountPerQuantity;
225
+
226
+ if (moneyV2 == null) {
227
+ return null;
228
+ }
229
+
230
+ return (
231
+ <div>
232
+ <Money withoutTrailingZeros {...passthroughProps} data={moneyV2} />
233
+ </div>
234
+ );
235
+ }
236
+
237
+ export function CartEmpty({
238
+ hidden = false,
239
+ layout = 'aside',
240
+ }: {
241
+ hidden: boolean;
242
+ layout?: CartMainProps['layout'];
243
+ }) {
244
+ return (
245
+ <div hidden={hidden}>
246
+ <br />
247
+ <p>
248
+ Looks like you haven&rsquo;t added anything yet, let&rsquo;s get you
249
+ started!
250
+ </p>
251
+ <br />
252
+ <Link
253
+ to="/collections"
254
+ onClick={() => {
255
+ if (layout === 'aside') {
256
+ window.location.href = '/collections';
257
+ }
258
+ }}
259
+ >
260
+ Continue shopping →
261
+ </Link>
262
+ </div>
263
+ );
264
+ }
265
+
266
+ function CartDiscounts({
267
+ discountCodes,
268
+ }: {
269
+ discountCodes: CartApiQueryFragment['discountCodes'];
270
+ }) {
271
+ const codes: string[] =
272
+ discountCodes
273
+ ?.filter((discount) => discount.applicable)
274
+ ?.map(({code}) => code) || [];
275
+
276
+ return (
277
+ <div>
278
+ {/* Have existing discount, display it with a remove option */}
279
+ <dl hidden={!codes.length}>
280
+ <div>
281
+ <dt>Discount(s)</dt>
282
+ <UpdateDiscountForm>
283
+ <div className="cart-discount">
284
+ <code>{codes?.join(', ')}</code>
285
+ &nbsp;
286
+ <button>Remove</button>
287
+ </div>
288
+ </UpdateDiscountForm>
289
+ </div>
290
+ </dl>
291
+
292
+ {/* Show an input to apply a discount */}
293
+ <UpdateDiscountForm discountCodes={codes}>
294
+ <div>
295
+ <input type="text" name="discountCode" placeholder="Discount code" />
296
+ &nbsp;
297
+ <button type="submit">Apply</button>
298
+ </div>
299
+ </UpdateDiscountForm>
300
+ </div>
301
+ );
302
+ }
303
+
304
+ function UpdateDiscountForm({
305
+ discountCodes,
306
+ children,
307
+ }: {
308
+ discountCodes?: string[];
309
+ children: React.ReactNode;
310
+ }) {
311
+ return (
312
+ <CartForm
313
+ route="/cart"
314
+ action={CartForm.ACTIONS.DiscountCodesUpdate}
315
+ inputs={{
316
+ discountCodes: discountCodes || [],
317
+ }}
318
+ >
319
+ {children}
320
+ </CartForm>
321
+ );
322
+ }
323
+
324
+ function CartLineUpdateButton({
325
+ children,
326
+ lines,
327
+ }: {
328
+ children: React.ReactNode;
329
+ lines: CartLineUpdateInput[];
330
+ }) {
331
+ return (
332
+ <CartForm
333
+ route="/cart"
334
+ action={CartForm.ACTIONS.LinesUpdate}
335
+ inputs={{lines}}
336
+ >
337
+ {children}
338
+ </CartForm>
339
+ );
340
+ }
@@ -0,0 +1,99 @@
1
+ import {useMatches, NavLink} from '@remix-run/react';
2
+ import type {FooterQuery} from 'storefrontapi.generated';
3
+
4
+ export function Footer({menu}: FooterQuery) {
5
+ return (
6
+ <footer className="footer">
7
+ <FooterMenu menu={menu} />
8
+ </footer>
9
+ );
10
+ }
11
+
12
+ function FooterMenu({menu}: Pick<FooterQuery, 'menu'>) {
13
+ const [root] = useMatches();
14
+ const publicStoreDomain = root?.data?.publicStoreDomain;
15
+ return (
16
+ <nav className="footer-menu" role="navigation">
17
+ {(menu || FALLBACK_FOOTER_MENU).items.map((item) => {
18
+ if (!item.url) return null;
19
+ // if the url is internal, we strip the domain
20
+ const url =
21
+ item.url.includes('myshopify.com') ||
22
+ item.url.includes(publicStoreDomain)
23
+ ? new URL(item.url).pathname
24
+ : item.url;
25
+ const isExternal = !url.startsWith('/');
26
+ return isExternal ? (
27
+ <a href={url} key={item.id} rel="noopener noreferrer" target="_blank">
28
+ {item.title}
29
+ </a>
30
+ ) : (
31
+ <NavLink
32
+ end
33
+ key={item.id}
34
+ prefetch="intent"
35
+ style={activeLinkStyle}
36
+ to={url}
37
+ >
38
+ {item.title}
39
+ </NavLink>
40
+ );
41
+ })}
42
+ </nav>
43
+ );
44
+ }
45
+
46
+ const FALLBACK_FOOTER_MENU = {
47
+ id: 'gid://shopify/Menu/199655620664',
48
+ items: [
49
+ {
50
+ id: 'gid://shopify/MenuItem/461633060920',
51
+ resourceId: 'gid://shopify/ShopPolicy/23358046264',
52
+ tags: [],
53
+ title: 'Privacy Policy',
54
+ type: 'SHOP_POLICY',
55
+ url: '/policies/privacy-policy',
56
+ items: [],
57
+ },
58
+ {
59
+ id: 'gid://shopify/MenuItem/461633093688',
60
+ resourceId: 'gid://shopify/ShopPolicy/23358013496',
61
+ tags: [],
62
+ title: 'Refund Policy',
63
+ type: 'SHOP_POLICY',
64
+ url: '/policies/refund-policy',
65
+ items: [],
66
+ },
67
+ {
68
+ id: 'gid://shopify/MenuItem/461633126456',
69
+ resourceId: 'gid://shopify/ShopPolicy/23358111800',
70
+ tags: [],
71
+ title: 'Shipping Policy',
72
+ type: 'SHOP_POLICY',
73
+ url: '/policies/shipping-policy',
74
+ items: [],
75
+ },
76
+ {
77
+ id: 'gid://shopify/MenuItem/461633159224',
78
+ resourceId: 'gid://shopify/ShopPolicy/23358079032',
79
+ tags: [],
80
+ title: 'Terms of Service',
81
+ type: 'SHOP_POLICY',
82
+ url: '/policies/terms-of-service',
83
+ items: [],
84
+ },
85
+ ],
86
+ };
87
+
88
+ function activeLinkStyle({
89
+ isActive,
90
+ isPending,
91
+ }: {
92
+ isActive: boolean;
93
+ isPending: boolean;
94
+ }) {
95
+ return {
96
+ fontWeight: isActive ? 'bold' : '',
97
+ color: isPending ? 'grey' : 'white',
98
+ };
99
+ }