@shopify/cli 3.63.2 → 3.64.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 (131) hide show
  1. package/dist/assets/cli-ruby/lib/shopify_cli/reporting_configuration_controller.rb +2 -37
  2. package/dist/assets/cli-ruby/lib/shopify_cli/theme/file.rb +5 -1
  3. package/dist/assets/dev-console/extensions/dev-console/assets/index-Cgb-oKsM.css +1 -0
  4. package/dist/assets/dev-console/extensions/dev-console/assets/{index-Dui3DO9f.js → index-D7F9wNys.js} +12 -12
  5. package/dist/assets/dev-console/index.html +2 -2
  6. package/dist/assets/hydrogen/starter/.graphqlrc.ts +27 -0
  7. package/dist/assets/hydrogen/starter/CHANGELOG.md +108 -6
  8. package/dist/assets/hydrogen/starter/app/components/AddToCartButton.tsx +37 -0
  9. package/dist/assets/hydrogen/starter/app/components/CartLineItem.tsx +150 -0
  10. package/dist/assets/hydrogen/starter/app/components/CartMain.tsx +68 -0
  11. package/dist/assets/hydrogen/starter/app/components/CartSummary.tsx +101 -0
  12. package/dist/assets/hydrogen/starter/app/components/Header.tsx +3 -3
  13. package/dist/assets/hydrogen/starter/app/components/PageLayout.tsx +2 -2
  14. package/dist/assets/hydrogen/starter/app/components/ProductForm.tsx +80 -0
  15. package/dist/assets/hydrogen/starter/app/components/ProductImage.tsx +23 -0
  16. package/dist/assets/hydrogen/starter/app/components/ProductPrice.tsx +27 -0
  17. package/dist/assets/hydrogen/starter/app/root.tsx +11 -17
  18. package/dist/assets/hydrogen/starter/app/routes/cart.tsx +1 -1
  19. package/dist/assets/hydrogen/starter/app/routes/products.$handle.tsx +51 -232
  20. package/dist/assets/hydrogen/starter/package.json +11 -11
  21. package/dist/assets/hydrogen/tailwind/package.json +1 -6
  22. package/dist/assets/hydrogen/tailwind/tailwind.css +6 -3
  23. package/dist/assets/hydrogen/vanilla-extract/package.json +2 -3
  24. package/dist/assets/hydrogen/virtual-routes/components/{Layout.jsx → PageLayout.jsx} +2 -2
  25. package/dist/assets/hydrogen/virtual-routes/virtual-root.jsx +8 -30
  26. package/dist/{chunk-YAYFJITA.js → chunk-2DXCIFDK.js} +3 -3
  27. package/dist/{chunk-EQR6CWKL.js → chunk-63D4EGTO.js} +3 -3
  28. package/dist/{chunk-EZYMDZPN.js → chunk-6PJAGL2L.js} +5 -5
  29. package/dist/{chunk-S4VBXFXP.js → chunk-7PVTYKQI.js} +220 -16
  30. package/dist/{chunk-IRWSC76I.js → chunk-7TIDA343.js} +3 -3
  31. package/dist/{chunk-NPLAQVTF.js → chunk-7W6SRTYP.js} +3 -3
  32. package/dist/{chunk-UJYIV6JP.js → chunk-ARCFCLME.js} +5 -5
  33. package/dist/{chunk-DIZHFZTJ.js → chunk-ATGUMSCJ.js} +4 -4
  34. package/dist/chunk-B3D5VLUA.js +12 -0
  35. package/dist/{chunk-UNPXLODI.js → chunk-B7RN7IRD.js} +3 -4
  36. package/dist/{chunk-5BLMIE7F.js → chunk-DX43V2L4.js} +4 -4
  37. package/dist/{chunk-6UDFXWNE.js → chunk-EJITPGUJ.js} +21 -4
  38. package/dist/chunk-FDLU3RD4.js +29 -0
  39. package/dist/{chunk-7OCUVNSF.js → chunk-H6AQTECB.js} +1485 -2977
  40. package/dist/{chunk-T54B5GJP.js → chunk-JNUJZFQL.js} +2 -2
  41. package/dist/{chunk-XER2L725.js → chunk-KUVX423E.js} +2 -2
  42. package/dist/{chunk-R5KML52V.js → chunk-KZTALMEV.js} +4 -4
  43. package/dist/{chunk-CM4H6QMH.js → chunk-M6KGRVDD.js} +3 -3
  44. package/dist/{chunk-3MDI6LZT.js → chunk-NLE3ZLU6.js} +905 -896
  45. package/dist/{chunk-WADS2TV5.js → chunk-NO7MYZEO.js} +5 -4
  46. package/dist/{chunk-LH533WG4.js → chunk-NPH2SXRO.js} +2 -2
  47. package/dist/{chunk-VZUWS5IH.js → chunk-OVWFZSJT.js} +3 -3
  48. package/dist/{chunk-UQUO22Q5.js → chunk-OXMHVKM3.js} +5 -5
  49. package/dist/{chunk-PNFEODLY.js → chunk-QNI6VLVR.js} +3 -3
  50. package/dist/{chunk-MCT2524Y.js → chunk-QYT42J3T.js} +4 -4
  51. package/dist/{chunk-VQTHQBEC.js → chunk-S3HWVIGJ.js} +9 -14
  52. package/dist/{chunk-23OKKZ5V.js → chunk-S7A7BHNA.js} +4 -4
  53. package/dist/{chunk-3TGMDPDI.js → chunk-SSAUIEBT.js} +2 -2
  54. package/dist/{chunk-YMPGWFWU.js → chunk-UQQI7TQG.js} +3 -3
  55. package/dist/{chunk-UZUD5DRI.js → chunk-UXA5YROL.js} +2 -2
  56. package/dist/{chunk-K3CVGV3F.js → chunk-V7NH4SZB.js} +3 -3
  57. package/dist/{chunk-7AVKIH7O.js → chunk-WVY52EEZ.js} +5 -5
  58. package/dist/{chunk-QEOBHRRQ.js → chunk-X3OUSYUQ.js} +17912 -17444
  59. package/dist/{chunk-EARPFFS7.js → chunk-XNCQBHNR.js} +241 -4
  60. package/dist/{chunk-EQPYUHNM.js → chunk-ZIGJPI5N.js} +1497 -112
  61. package/dist/{chunk-A2UVOX6O.js → chunk-ZKWHKX2C.js} +7066 -6051
  62. package/dist/{chunk-GPMHDCWK.js → chunk-ZRGD2HUL.js} +3 -3
  63. package/dist/{chunk-PQKGBYDC.js → chunk-ZVT2WZZF.js} +3 -3
  64. package/dist/cli/commands/auth/logout.js +14 -19
  65. package/dist/cli/commands/auth/logout.test.js +17 -21
  66. package/dist/cli/commands/debug/command-flags.js +11 -15
  67. package/dist/cli/commands/demo/catalog.js +13 -18
  68. package/dist/cli/commands/demo/generate-file.js +13 -18
  69. package/dist/cli/commands/demo/index.js +13 -18
  70. package/dist/cli/commands/demo/print-ai-prompt.js +13 -18
  71. package/dist/cli/commands/docs/generate.js +11 -15
  72. package/dist/cli/commands/docs/generate.test.js +11 -15
  73. package/dist/cli/commands/help.js +11 -15
  74. package/dist/cli/commands/kitchen-sink/async.js +12 -16
  75. package/dist/cli/commands/kitchen-sink/async.test.js +12 -16
  76. package/dist/cli/commands/kitchen-sink/index.js +14 -18
  77. package/dist/cli/commands/kitchen-sink/index.test.js +14 -18
  78. package/dist/cli/commands/kitchen-sink/prompts.js +12 -16
  79. package/dist/cli/commands/kitchen-sink/prompts.test.js +12 -16
  80. package/dist/cli/commands/kitchen-sink/static.js +12 -16
  81. package/dist/cli/commands/kitchen-sink/static.test.js +12 -16
  82. package/dist/cli/commands/search.js +12 -16
  83. package/dist/cli/commands/upgrade.js +11 -15
  84. package/dist/cli/commands/version.js +12 -16
  85. package/dist/cli/commands/version.test.js +12 -16
  86. package/dist/cli/services/commands/search.js +4 -4
  87. package/dist/cli/services/commands/search.test.js +4 -4
  88. package/dist/cli/services/commands/version.js +6 -7
  89. package/dist/cli/services/commands/version.test.js +7 -8
  90. package/dist/cli/services/demo.js +5 -6
  91. package/dist/cli/services/demo.test.js +5 -6
  92. package/dist/cli/services/kitchen-sink/async.js +4 -4
  93. package/dist/cli/services/kitchen-sink/prompts.js +4 -4
  94. package/dist/cli/services/kitchen-sink/static.js +4 -4
  95. package/dist/cli/services/upgrade.js +5 -6
  96. package/dist/cli/services/upgrade.test.js +7 -8
  97. package/dist/{constants-3CLHB4LQ.js → constants-EVER32LA.js} +3 -3
  98. package/dist/{custom-oclif-loader-D4H5EJW6.js → custom-oclif-loader-BQAFOUNG.js} +13 -6
  99. package/dist/{error-handler-HUI4HW3X.js → error-handler-S56KHSGD.js} +10 -8
  100. package/dist/hooks/postrun.js +12 -14
  101. package/dist/hooks/prerun.js +8 -11
  102. package/dist/index.js +7811 -7490
  103. package/dist/{local-7IRDZWLW.js → local-UQAQKOVL.js} +4 -4
  104. package/dist/{morph-6NYGHGNT.js → morph-DN4AZJZW.js} +9 -9
  105. package/dist/{node-UIH7JP3D.js → node-GZYZUMBW.js} +21 -19
  106. package/dist/{node-package-manager-2LWT2MNN.js → node-package-manager-AOVZD6TP.js} +5 -6
  107. package/dist/{path-JVVXOELJ.js → path-KUSF6CYC.js} +2 -2
  108. package/dist/{system-4HHX42JS.js → system-G7DVECOP.js} +4 -4
  109. package/dist/templates/ui-extensions/projects/web_pixel_extension/package.json.liquid +1 -1
  110. package/dist/tsconfig.tsbuildinfo +1 -1
  111. package/dist/{ui-NFBKMC4P.js → ui-2AOZFYFM.js} +4 -4
  112. package/dist/{workerd-4HFD3PS4.js → workerd-2MO23YDQ.js} +22 -19
  113. package/oclif.manifest.json +265 -4
  114. package/package.json +7 -10
  115. package/dist/assets/dev-console/extensions/dev-console/assets/index-Bi7y6lI5.css +0 -1
  116. package/dist/assets/hydrogen/css-modules/package.json +0 -6
  117. package/dist/assets/hydrogen/postcss/package.json +0 -10
  118. package/dist/assets/hydrogen/postcss/postcss.config.js +0 -8
  119. package/dist/assets/hydrogen/starter/.graphqlrc.yml +0 -12
  120. package/dist/assets/hydrogen/starter/app/components/Cart.tsx +0 -364
  121. package/dist/assets/hydrogen/tailwind/postcss.config.js +0 -10
  122. package/dist/assets/hydrogen/tailwind/tailwind.config.js +0 -8
  123. package/dist/chunk-4WBV3WP3.js +0 -221
  124. package/dist/chunk-OWICSMFV.js +0 -12
  125. package/dist/chunk-QCDYZY46.js +0 -1070
  126. package/dist/chunk-QEKTVN5A.js +0 -4385
  127. package/dist/chunk-QOUOFEGO.js +0 -35
  128. package/dist/chunk-WP234IUO.js +0 -265
  129. package/dist/chunk-XLPMGRR3.js +0 -496
  130. package/dist/chunk-XSKJYEAZ.js +0 -1453
  131. package/dist/error-handler-QDDLQDOJ.js +0 -43
@@ -12,8 +12,8 @@
12
12
  <meta name="theme-color" content="#ffffff">
13
13
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
14
14
  <title>UI Extensions DevConsole</title>
15
- <script type="module" crossorigin src="/extensions/dev-console/assets/index-Dui3DO9f.js"></script>
16
- <link rel="stylesheet" crossorigin href="/extensions/dev-console/assets/index-Bi7y6lI5.css">
15
+ <script type="module" crossorigin src="/extensions/dev-console/assets/index-D7F9wNys.js"></script>
16
+ <link rel="stylesheet" crossorigin href="/extensions/dev-console/assets/index-Cgb-oKsM.css">
17
17
  </head>
18
18
  <body>
19
19
  <div id="root"></div>
@@ -0,0 +1,27 @@
1
+ import type {IGraphQLConfig} from 'graphql-config';
2
+ import {getSchema} from '@shopify/hydrogen-codegen';
3
+
4
+ /**
5
+ * GraphQL Config
6
+ * @see https://the-guild.dev/graphql/config/docs/user/usage
7
+ * @type {IGraphQLConfig}
8
+ */
9
+ export default {
10
+ projects: {
11
+ default: {
12
+ schema: getSchema('storefront'),
13
+ documents: [
14
+ './*.{ts,tsx,js,jsx}',
15
+ './app/**/*.{ts,tsx,js,jsx}',
16
+ '!./app/graphql/**/*.{ts,tsx,js,jsx}',
17
+ ],
18
+ },
19
+
20
+ customer: {
21
+ schema: getSchema('customer-account'),
22
+ documents: ['./app/graphql/customer-account/*.{ts,tsx,js,jsx}'],
23
+ },
24
+
25
+ // Add your own GraphQL projects here for CMS, Shopify Admin API, etc.
26
+ },
27
+ } as IGraphQLConfig;
@@ -1,9 +1,103 @@
1
1
  # skeleton
2
2
 
3
- ## 0.0.0-next-9eb60d7-20240607102913
3
+ ## 2024.7.2
4
4
 
5
5
  ### Patch Changes
6
6
 
7
+ - Changed the GraphQL config file format to be TS/JS instead of YAML. ([#2311](https://github.com/Shopify/hydrogen/pull/2311)) by [@frandiox](https://github.com/frandiox)
8
+
9
+ - Updated dependencies [[`18ea233c`](https://github.com/Shopify/hydrogen/commit/18ea233cd327bf3001ec9b107ad66b05c9c78584), [`8b2322d7`](https://github.com/Shopify/hydrogen/commit/8b2322d783078298cd5d20ec5f3b1faf99b7895b)]:
10
+ - @shopify/cli-hydrogen@8.3.0
11
+
12
+ ## 2024.7.1
13
+
14
+ ### Patch Changes
15
+
16
+ - Update `@shopify/oxygen-workers-types` to fix issues on Windows. ([#2252](https://github.com/Shopify/hydrogen/pull/2252)) by [@michenly](https://github.com/michenly)
17
+
18
+ - [**Breaking change**] ([#2113](https://github.com/Shopify/hydrogen/pull/2113)) by [@blittle](https://github.com/blittle)
19
+
20
+ Previously the `VariantSelector` component would filter out options that only had one value. This is undesireable for some apps. We've removed that filter, if you'd like to retain the existing functionality, simply filter the options prop before it is passed to the `VariantSelector` component:
21
+
22
+ ```diff
23
+ <VariantSelector
24
+ handle={product.handle}
25
+ + options={product.options.filter((option) => option.values.length > 1)}
26
+ - options={product.options}
27
+ variants={variants}>
28
+ </VariantSelector>
29
+ ```
30
+
31
+ Fixes [#1198](https://github.com/Shopify/hydrogen/discussions/1198)
32
+
33
+ - Update remix to v2.10.1 ([#2290](https://github.com/Shopify/hydrogen/pull/2290)) by [@michenly](https://github.com/michenly)
34
+
35
+ - Update root to use [Remix's Layout Export pattern](https://remix.run/docs/en/main/file-conventions/root#layout-export) and eliminate the use of `useLoaderData` in root. ([#2292](https://github.com/Shopify/hydrogen/pull/2292)) by [@michenly](https://github.com/michenly)
36
+
37
+ The diff below showcase how you can make this refactor in existing application.
38
+
39
+ ```diff
40
+ import {
41
+ Outlet,
42
+ - useLoaderData,
43
+ + useRouteLoaderData,
44
+ } from '@remix-run/react';
45
+ -import {Layout} from '~/components/Layout';
46
+ +import {PageLayout} from '~/components/PageLayout';
47
+
48
+ -export default function App() {
49
+ +export function Layout({children}: {children?: React.ReactNode}) {
50
+ const nonce = useNonce();
51
+ - const data = useLoaderData<typeof loader>();
52
+ + const data = useRouteLoaderData<typeof loader>('root');
53
+
54
+ return (
55
+ <html>
56
+ ...
57
+ <body>
58
+ - <Layout {...data}>
59
+ - <Outlet />
60
+ - </Layout>
61
+ + {data? (
62
+ + <PageLayout {...data}>{children}</PageLayout>
63
+ + ) : (
64
+ + children
65
+ + )}
66
+ </body>
67
+ </html>
68
+ );
69
+ }
70
+
71
+ +export default function App() {
72
+ + return <Outlet />;
73
+ +}
74
+
75
+ export function ErrorBoundary() {
76
+ - const rootData = useLoaderData<typeof loader>();
77
+
78
+ return (
79
+ - <html>
80
+ - ...
81
+ - <body>
82
+ - <Layout {...rootData}>
83
+ - <div className="route-error">
84
+ - <h1>Error</h1>
85
+ - ...
86
+ - </div>
87
+ - </Layout>
88
+ - </body>
89
+ - </html>
90
+ + <div className="route-error">
91
+ + <h1>Error</h1>
92
+ + ...
93
+ + </div>
94
+ );
95
+ }
96
+
97
+ ```
98
+
99
+ - Refactor the cart and product form components ([#2132](https://github.com/Shopify/hydrogen/pull/2132)) by [@blittle](https://github.com/blittle)
100
+
7
101
  - Remove manual setting of session in headers and recommend setting it in server after response is created. ([#2137](https://github.com/Shopify/hydrogen/pull/2137)) by [@michenly](https://github.com/michenly)
8
102
 
9
103
  Step 1: Add `isPending` implementation in session
@@ -66,12 +160,20 @@
66
160
  }
67
161
  ```
68
162
 
69
- - Trigger changeset for all packages for next release
163
+ - Moved `@shopify/cli` from `dependencies` to `devDependencies`. ([#2312](https://github.com/Shopify/hydrogen/pull/2312)) by [@frandiox](https://github.com/frandiox)
164
+
165
+ - The `@shopify/cli` package now bundles the `@shopify/cli-hydrogen` plugin. Therefore, you can now remove the latter from your local dependencies: ([#2306](https://github.com/Shopify/hydrogen/pull/2306)) by [@frandiox](https://github.com/frandiox)
166
+
167
+ ```diff
168
+ "@shopify/cli": "3.64.0",
169
+ - "@shopify/cli-hydrogen": "^8.1.1",
170
+ "@shopify/hydrogen": "2024.7.0",
171
+ ```
70
172
 
71
- - Updated dependencies [[`4337200c`](https://github.com/Shopify/hydrogen/commit/4337200c7908d56c039171c283a4d92c31a8b7b6), [`10a419bf`](https://github.com/Shopify/hydrogen/commit/10a419bf1db79cdfd8c41c0223ce695959f60da9), [`4337200c`](https://github.com/Shopify/hydrogen/commit/4337200c7908d56c039171c283a4d92c31a8b7b6), [`9eb60d73`](https://github.com/Shopify/hydrogen/commit/9eb60d73e552c3d22b9325ecbcd5878810893ad3)]:
72
- - @shopify/hydrogen@0.0.0-next-9eb60d7-20240607102913
73
- - @shopify/cli-hydrogen@0.0.0-next-9eb60d7-20240607102913
74
- - @shopify/remix-oxygen@0.0.0-next-9eb60d7-20240607102913
173
+ - Updated dependencies [[`a0e84d76`](https://github.com/Shopify/hydrogen/commit/a0e84d76b67d4c57c4defee06185949c41782eab), [`426bb390`](https://github.com/Shopify/hydrogen/commit/426bb390b25f51e57499ff6673aef70ded935e87), [`4337200c`](https://github.com/Shopify/hydrogen/commit/4337200c7908d56c039171c283a4d92c31a8b7b6), [`710625c7`](https://github.com/Shopify/hydrogen/commit/710625c740a6656488d4b419e2d2451bef9d076f), [`8b9c726d`](https://github.com/Shopify/hydrogen/commit/8b9c726d34f3482b5b5a0da4c7c0c2f20e2c9caa), [`10a419bf`](https://github.com/Shopify/hydrogen/commit/10a419bf1db79cdfd8c41c0223ce695959f60da9), [`6a6278bb`](https://github.com/Shopify/hydrogen/commit/6a6278bb9187b3b5a98cd98ec9dd278882d03c0d), [`66236ca6`](https://github.com/Shopify/hydrogen/commit/66236ca65ddefac99eaa553c7877c85863d84cc2), [`dcbd0bbf`](https://github.com/Shopify/hydrogen/commit/dcbd0bbf4073a3e35e96f3cce257f7b19b2b2aea), [`a5e03e2a`](https://github.com/Shopify/hydrogen/commit/a5e03e2a1e99fcd83ee5a2be7bf6f5f6b47984b3), [`c2690653`](https://github.com/Shopify/hydrogen/commit/c2690653b6b24f7318e9088551a37195255a2247), [`54c2f7ad`](https://github.com/Shopify/hydrogen/commit/54c2f7ad3d0d52e6be10b2a54a1a4fd0cc107a35), [`4337200c`](https://github.com/Shopify/hydrogen/commit/4337200c7908d56c039171c283a4d92c31a8b7b6), [`e96b332b`](https://github.com/Shopify/hydrogen/commit/e96b332ba1aba79aa3d5c2ce18001292070faf49), [`f3065371`](https://github.com/Shopify/hydrogen/commit/f3065371c1dda222c6e40bd8c20528dc9fdea9a5), [`6cd5554b`](https://github.com/Shopify/hydrogen/commit/6cd5554b160d314d35964a5ee8976ed60972bf17), [`9eb60d73`](https://github.com/Shopify/hydrogen/commit/9eb60d73e552c3d22b9325ecbcd5878810893ad3), [`e432533e`](https://github.com/Shopify/hydrogen/commit/e432533e7391ec3fe16a4a24f2b3363206842580), [`de3f70be`](https://github.com/Shopify/hydrogen/commit/de3f70be1a838eda746903cbb38cc25cf0e09fa3), [`83cb96f4`](https://github.com/Shopify/hydrogen/commit/83cb96f42078bf79b20a153d8a8461f75d573ab1)]:
174
+ - @shopify/remix-oxygen@2.0.5
175
+ - @shopify/cli-hydrogen@8.2.0
176
+ - @shopify/hydrogen@2024.7.1
75
177
 
76
178
  ## 2024.4.5
77
179
 
@@ -0,0 +1,37 @@
1
+ import {type FetcherWithComponents} from '@remix-run/react';
2
+ import {CartForm, type OptimisticCartLineInput} from '@shopify/hydrogen';
3
+
4
+ export function AddToCartButton({
5
+ analytics,
6
+ children,
7
+ disabled,
8
+ lines,
9
+ onClick,
10
+ }: {
11
+ analytics?: unknown;
12
+ children: React.ReactNode;
13
+ disabled?: boolean;
14
+ lines: Array<OptimisticCartLineInput>;
15
+ onClick?: () => void;
16
+ }) {
17
+ return (
18
+ <CartForm route="/cart" inputs={{lines}} action={CartForm.ACTIONS.LinesAdd}>
19
+ {(fetcher: FetcherWithComponents<any>) => (
20
+ <>
21
+ <input
22
+ name="analytics"
23
+ type="hidden"
24
+ value={JSON.stringify(analytics)}
25
+ />
26
+ <button
27
+ type="submit"
28
+ onClick={onClick}
29
+ disabled={disabled ?? fetcher.state !== 'idle'}
30
+ >
31
+ {children}
32
+ </button>
33
+ </>
34
+ )}
35
+ </CartForm>
36
+ );
37
+ }
@@ -0,0 +1,150 @@
1
+ import type {CartLineUpdateInput} from '@shopify/hydrogen/storefront-api-types';
2
+ import type {CartLayout} from '~/components/CartMain';
3
+ import {CartForm, Image, type OptimisticCartLine} from '@shopify/hydrogen';
4
+ import {useVariantUrl} from '~/lib/variants';
5
+ import {Link} from '@remix-run/react';
6
+ import {ProductPrice} from './ProductPrice';
7
+ import {useAside} from './Aside';
8
+
9
+ /**
10
+ * A single line item in the cart. It displays the product image, title, price.
11
+ * It also provides controls to update the quantity or remove the line item.
12
+ */
13
+ export function CartLineItem({
14
+ layout,
15
+ line,
16
+ }: {
17
+ layout: CartLayout;
18
+ line: OptimisticCartLine;
19
+ }) {
20
+ const {id, merchandise} = line;
21
+ const {product, title, image, selectedOptions} = merchandise;
22
+ const lineItemUrl = useVariantUrl(product.handle, selectedOptions);
23
+ const {close} = useAside();
24
+
25
+ return (
26
+ <li key={id} className="cart-line">
27
+ {image && (
28
+ <Image
29
+ alt={title}
30
+ aspectRatio="1/1"
31
+ data={image}
32
+ height={100}
33
+ loading="lazy"
34
+ width={100}
35
+ />
36
+ )}
37
+
38
+ <div>
39
+ <Link
40
+ prefetch="intent"
41
+ to={lineItemUrl}
42
+ onClick={() => {
43
+ if (layout === 'aside') {
44
+ close();
45
+ }
46
+ }}
47
+ >
48
+ <p>
49
+ <strong>{product.title}</strong>
50
+ </p>
51
+ </Link>
52
+ <ProductPrice price={line?.cost?.totalAmount} />
53
+ <ul>
54
+ {selectedOptions.map((option) => (
55
+ <li key={option.name}>
56
+ <small>
57
+ {option.name}: {option.value}
58
+ </small>
59
+ </li>
60
+ ))}
61
+ </ul>
62
+ <CartLineQuantity line={line} />
63
+ </div>
64
+ </li>
65
+ );
66
+ }
67
+
68
+ /**
69
+ * Provides the controls to update the quantity of a line item in the cart.
70
+ * These controls are disabled when the line item is new, and the server
71
+ * hasn't yet responded that it was successfully added to the cart.
72
+ */
73
+ function CartLineQuantity({line}: {line: OptimisticCartLine}) {
74
+ if (!line || typeof line?.quantity === 'undefined') return null;
75
+ const {id: lineId, quantity, isOptimistic} = line;
76
+ const prevQuantity = Number(Math.max(0, quantity - 1).toFixed(0));
77
+ const nextQuantity = Number((quantity + 1).toFixed(0));
78
+
79
+ return (
80
+ <div className="cart-line-quantity">
81
+ <small>Quantity: {quantity} &nbsp;&nbsp;</small>
82
+ <CartLineUpdateButton lines={[{id: lineId, quantity: prevQuantity}]}>
83
+ <button
84
+ aria-label="Decrease quantity"
85
+ disabled={quantity <= 1 || !!isOptimistic}
86
+ name="decrease-quantity"
87
+ value={prevQuantity}
88
+ >
89
+ <span>&#8722; </span>
90
+ </button>
91
+ </CartLineUpdateButton>
92
+ &nbsp;
93
+ <CartLineUpdateButton lines={[{id: lineId, quantity: nextQuantity}]}>
94
+ <button
95
+ aria-label="Increase quantity"
96
+ name="increase-quantity"
97
+ value={nextQuantity}
98
+ disabled={!!isOptimistic}
99
+ >
100
+ <span>&#43;</span>
101
+ </button>
102
+ </CartLineUpdateButton>
103
+ &nbsp;
104
+ <CartLineRemoveButton lineIds={[lineId]} disabled={!!isOptimistic} />
105
+ </div>
106
+ );
107
+ }
108
+
109
+ /**
110
+ * A button that removes a line item from the cart. It is disabled
111
+ * when the line item is new, and the server hasn't yet responded
112
+ * that it was successfully added to the cart.
113
+ */
114
+ function CartLineRemoveButton({
115
+ lineIds,
116
+ disabled,
117
+ }: {
118
+ lineIds: string[];
119
+ disabled: boolean;
120
+ }) {
121
+ return (
122
+ <CartForm
123
+ route="/cart"
124
+ action={CartForm.ACTIONS.LinesRemove}
125
+ inputs={{lineIds}}
126
+ >
127
+ <button disabled={disabled} type="submit">
128
+ Remove
129
+ </button>
130
+ </CartForm>
131
+ );
132
+ }
133
+
134
+ function CartLineUpdateButton({
135
+ children,
136
+ lines,
137
+ }: {
138
+ children: React.ReactNode;
139
+ lines: CartLineUpdateInput[];
140
+ }) {
141
+ return (
142
+ <CartForm
143
+ route="/cart"
144
+ action={CartForm.ACTIONS.LinesUpdate}
145
+ inputs={{lines}}
146
+ >
147
+ {children}
148
+ </CartForm>
149
+ );
150
+ }
@@ -0,0 +1,68 @@
1
+ import {type OptimisticCartLine, useOptimisticCart} from '@shopify/hydrogen';
2
+ import {Link} from '@remix-run/react';
3
+ import type {CartApiQueryFragment} from 'storefrontapi.generated';
4
+ import {useAside} from '~/components/Aside';
5
+ import {CartLineItem} from '~/components/CartLineItem';
6
+ import {CartSummary} from './CartSummary';
7
+
8
+ export type CartLayout = 'page' | 'aside';
9
+
10
+ export type CartMainProps = {
11
+ cart: CartApiQueryFragment | null;
12
+ layout: CartLayout;
13
+ };
14
+
15
+ /**
16
+ * The main cart component that displays the cart items and summary.
17
+ * It is used by both the /cart route and the cart aside dialog.
18
+ */
19
+ export function CartMain({layout, cart: originalCart}: CartMainProps) {
20
+ // The useOptimisticCart hook applies pending actions to the cart
21
+ // so the user immediately sees feedback when they modify the cart.
22
+ const cart = useOptimisticCart(originalCart);
23
+
24
+ const linesCount = Boolean(cart?.lines?.nodes?.length || 0);
25
+ const withDiscount =
26
+ cart &&
27
+ Boolean(cart?.discountCodes?.filter((code) => code.applicable)?.length);
28
+ const className = `cart-main ${withDiscount ? 'with-discount' : ''}`;
29
+ const cartHasItems = cart?.totalQuantity! > 0;
30
+
31
+ return (
32
+ <div className={className}>
33
+ <CartEmpty hidden={linesCount} layout={layout} />
34
+ <div className="cart-details">
35
+ <div aria-labelledby="cart-lines">
36
+ <ul>
37
+ {(cart?.lines?.nodes ?? []).map((line: OptimisticCartLine) => (
38
+ <CartLineItem key={line.id} line={line} layout={layout} />
39
+ ))}
40
+ </ul>
41
+ </div>
42
+ {cartHasItems && <CartSummary cart={cart} layout={layout} />}
43
+ </div>
44
+ </div>
45
+ );
46
+ }
47
+
48
+ function CartEmpty({
49
+ hidden = false,
50
+ }: {
51
+ hidden: boolean;
52
+ layout?: CartMainProps['layout'];
53
+ }) {
54
+ const {close} = useAside();
55
+ return (
56
+ <div hidden={hidden}>
57
+ <br />
58
+ <p>
59
+ Looks like you haven&rsquo;t added anything yet, let&rsquo;s get you
60
+ started!
61
+ </p>
62
+ <br />
63
+ <Link to="/collections" onClick={close} prefetch="viewport">
64
+ Continue shopping →
65
+ </Link>
66
+ </div>
67
+ );
68
+ }
@@ -0,0 +1,101 @@
1
+ import type {CartApiQueryFragment} from 'storefrontapi.generated';
2
+ import type {CartLayout} from '~/components/CartMain';
3
+ import {CartForm, Money, type OptimisticCart} from '@shopify/hydrogen';
4
+
5
+ type CartSummaryProps = {
6
+ cart: OptimisticCart<CartApiQueryFragment | null>;
7
+ layout: CartLayout;
8
+ };
9
+
10
+ export function CartSummary({cart, layout}: CartSummaryProps) {
11
+ const className =
12
+ layout === 'page' ? 'cart-summary-page' : 'cart-summary-aside';
13
+
14
+ return (
15
+ <div aria-labelledby="cart-summary" className={className}>
16
+ <h4>Totals</h4>
17
+ <dl className="cart-subtotal">
18
+ <dt>Subtotal</dt>
19
+ <dd>
20
+ {cart.cost?.subtotalAmount?.amount ? (
21
+ <Money data={cart.cost?.subtotalAmount} />
22
+ ) : (
23
+ '-'
24
+ )}
25
+ </dd>
26
+ </dl>
27
+ <CartDiscounts discountCodes={cart.discountCodes} />
28
+ <CartCheckoutActions checkoutUrl={cart.checkoutUrl} />
29
+ </div>
30
+ );
31
+ }
32
+ function CartCheckoutActions({checkoutUrl}: {checkoutUrl?: string}) {
33
+ if (!checkoutUrl) return null;
34
+
35
+ return (
36
+ <div>
37
+ <a href={checkoutUrl} target="_self">
38
+ <p>Continue to Checkout &rarr;</p>
39
+ </a>
40
+ <br />
41
+ </div>
42
+ );
43
+ }
44
+
45
+ function CartDiscounts({
46
+ discountCodes,
47
+ }: {
48
+ discountCodes?: CartApiQueryFragment['discountCodes'];
49
+ }) {
50
+ const codes: string[] =
51
+ discountCodes
52
+ ?.filter((discount) => discount.applicable)
53
+ ?.map(({code}) => code) || [];
54
+
55
+ return (
56
+ <div>
57
+ {/* Have existing discount, display it with a remove option */}
58
+ <dl hidden={!codes.length}>
59
+ <div>
60
+ <dt>Discount(s)</dt>
61
+ <UpdateDiscountForm>
62
+ <div className="cart-discount">
63
+ <code>{codes?.join(', ')}</code>
64
+ &nbsp;
65
+ <button>Remove</button>
66
+ </div>
67
+ </UpdateDiscountForm>
68
+ </div>
69
+ </dl>
70
+
71
+ {/* Show an input to apply a discount */}
72
+ <UpdateDiscountForm discountCodes={codes}>
73
+ <div>
74
+ <input type="text" name="discountCode" placeholder="Discount code" />
75
+ &nbsp;
76
+ <button type="submit">Apply</button>
77
+ </div>
78
+ </UpdateDiscountForm>
79
+ </div>
80
+ );
81
+ }
82
+
83
+ function UpdateDiscountForm({
84
+ discountCodes,
85
+ children,
86
+ }: {
87
+ discountCodes?: string[];
88
+ children: React.ReactNode;
89
+ }) {
90
+ return (
91
+ <CartForm
92
+ route="/cart"
93
+ action={CartForm.ACTIONS.DiscountCodesUpdate}
94
+ inputs={{
95
+ discountCodes: discountCodes || [],
96
+ }}
97
+ >
98
+ {children}
99
+ </CartForm>
100
+ );
101
+ }
@@ -138,7 +138,7 @@ function SearchToggle() {
138
138
  );
139
139
  }
140
140
 
141
- function CartBadge({count}: {count: number}) {
141
+ function CartBadge({count}: {count: number | null}) {
142
142
  const {open} = useAside();
143
143
  const {publish, shop, cart, prevCart} = useAnalytics();
144
144
 
@@ -156,14 +156,14 @@ function CartBadge({count}: {count: number}) {
156
156
  } as CartViewPayload);
157
157
  }}
158
158
  >
159
- Cart {count}
159
+ Cart {count === null ? <span>&nbsp;</span> : count}
160
160
  </a>
161
161
  );
162
162
  }
163
163
 
164
164
  function CartToggle({cart}: Pick<HeaderProps, 'cart'>) {
165
165
  return (
166
- <Suspense fallback={<CartBadge count={0} />}>
166
+ <Suspense fallback={<CartBadge count={null} />}>
167
167
  <Await resolve={cart}>
168
168
  {(cart) => {
169
169
  if (!cart) return <CartBadge count={0} />;
@@ -8,7 +8,7 @@ import type {
8
8
  import {Aside} from '~/components/Aside';
9
9
  import {Footer} from '~/components/Footer';
10
10
  import {Header, HeaderMenu} from '~/components/Header';
11
- import {CartMain} from '~/components/Cart';
11
+ import {CartMain} from '~/components/CartMain';
12
12
  import {
13
13
  PredictiveSearchForm,
14
14
  PredictiveSearchResults,
@@ -60,7 +60,7 @@ function CartAside({cart}: {cart: PageLayoutProps['cart']}) {
60
60
  <Suspense fallback={<p>Loading cart ...</p>}>
61
61
  <Await resolve={cart}>
62
62
  {(cart) => {
63
- return <CartMain cart={cart!} layout="aside" />;
63
+ return <CartMain cart={cart} layout="aside" />;
64
64
  }}
65
65
  </Await>
66
66
  </Suspense>
@@ -0,0 +1,80 @@
1
+ import {Link} from '@remix-run/react';
2
+ import {type VariantOption, VariantSelector} from '@shopify/hydrogen';
3
+ import type {
4
+ ProductFragment,
5
+ ProductVariantFragment,
6
+ } from 'storefrontapi.generated';
7
+ import {AddToCartButton} from '~/components/AddToCartButton';
8
+ import {useAside} from '~/components/Aside';
9
+
10
+ export function ProductForm({
11
+ product,
12
+ selectedVariant,
13
+ variants,
14
+ }: {
15
+ product: ProductFragment;
16
+ selectedVariant: ProductFragment['selectedVariant'];
17
+ variants: Array<ProductVariantFragment>;
18
+ }) {
19
+ const {open} = useAside();
20
+ return (
21
+ <div className="product-form">
22
+ <VariantSelector
23
+ handle={product.handle}
24
+ options={product.options.filter((option) => option.values.length > 1)}
25
+ variants={variants}
26
+ >
27
+ {({option}) => <ProductOptions key={option.name} option={option} />}
28
+ </VariantSelector>
29
+ <br />
30
+ <AddToCartButton
31
+ disabled={!selectedVariant || !selectedVariant.availableForSale}
32
+ onClick={() => {
33
+ open('cart');
34
+ }}
35
+ lines={
36
+ selectedVariant
37
+ ? [
38
+ {
39
+ merchandiseId: selectedVariant.id,
40
+ quantity: 1,
41
+ selectedVariant,
42
+ },
43
+ ]
44
+ : []
45
+ }
46
+ >
47
+ {selectedVariant?.availableForSale ? 'Add to cart' : 'Sold out'}
48
+ </AddToCartButton>
49
+ </div>
50
+ );
51
+ }
52
+
53
+ function ProductOptions({option}: {option: VariantOption}) {
54
+ return (
55
+ <div className="product-options" key={option.name}>
56
+ <h5>{option.name}</h5>
57
+ <div className="product-options-grid">
58
+ {option.values.map(({value, isAvailable, isActive, to}) => {
59
+ return (
60
+ <Link
61
+ className="product-options-item"
62
+ key={option.name + value}
63
+ prefetch="intent"
64
+ preventScrollReset
65
+ replace
66
+ to={to}
67
+ style={{
68
+ border: isActive ? '1px solid black' : '1px solid transparent',
69
+ opacity: isAvailable ? 1 : 0.3,
70
+ }}
71
+ >
72
+ {value}
73
+ </Link>
74
+ );
75
+ })}
76
+ </div>
77
+ <br />
78
+ </div>
79
+ );
80
+ }
@@ -0,0 +1,23 @@
1
+ import type {ProductVariantFragment} from 'storefrontapi.generated';
2
+ import {Image} from '@shopify/hydrogen';
3
+
4
+ export function ProductImage({
5
+ image,
6
+ }: {
7
+ image: ProductVariantFragment['image'];
8
+ }) {
9
+ if (!image) {
10
+ return <div className="product-image" />;
11
+ }
12
+ return (
13
+ <div className="product-image">
14
+ <Image
15
+ alt={image.altText || 'Product Image'}
16
+ aspectRatio="1/1"
17
+ data={image}
18
+ key={image.id}
19
+ sizes="(min-width: 45em) 50vw, 100vw"
20
+ />
21
+ </div>
22
+ );
23
+ }