@shopify/cli 3.63.1 → 3.64.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 (130) 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 +12 -30
  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/CHANGELOG.md +98 -5
  7. package/dist/assets/hydrogen/starter/app/components/AddToCartButton.tsx +37 -0
  8. package/dist/assets/hydrogen/starter/app/components/CartLineItem.tsx +150 -0
  9. package/dist/assets/hydrogen/starter/app/components/CartMain.tsx +68 -0
  10. package/dist/assets/hydrogen/starter/app/components/CartSummary.tsx +101 -0
  11. package/dist/assets/hydrogen/starter/app/components/Header.tsx +3 -3
  12. package/dist/assets/hydrogen/starter/app/components/PageLayout.tsx +1 -1
  13. package/dist/assets/hydrogen/starter/app/components/ProductForm.tsx +80 -0
  14. package/dist/assets/hydrogen/starter/app/components/ProductImage.tsx +23 -0
  15. package/dist/assets/hydrogen/starter/app/components/ProductPrice.tsx +27 -0
  16. package/dist/assets/hydrogen/starter/app/components/Search.tsx +1 -0
  17. package/dist/assets/hydrogen/starter/app/root.tsx +11 -17
  18. package/dist/assets/hydrogen/starter/app/routes/cart.tsx +2 -2
  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-TD4ZV7BS.js → chunk-2TZRSXDZ.js} +3 -3
  27. package/dist/{chunk-EARPFFS7.js → chunk-5A6QTLMF.js} +241 -4
  28. package/dist/{chunk-SRLLZENE.js → chunk-6LPAIJ4Y.js} +2 -2
  29. package/dist/{chunk-TYOQQCHN.js → chunk-6TPBPH6V.js} +905 -896
  30. package/dist/{chunk-UZ3EKGD4.js → chunk-7GYETEC3.js} +3 -3
  31. package/dist/{chunk-UNPXLODI.js → chunk-B7RN7IRD.js} +3 -4
  32. package/dist/{chunk-SML7F4EL.js → chunk-BKBHZTOE.js} +3 -3
  33. package/dist/{chunk-A2UVOX6O.js → chunk-C2AKAEMR.js} +7066 -6051
  34. package/dist/{chunk-HZUY7I72.js → chunk-CLBFKLWA.js} +4 -4
  35. package/dist/{chunk-6UDFXWNE.js → chunk-EJITPGUJ.js} +21 -4
  36. package/dist/{chunk-SCKZAIMH.js → chunk-G4TC4KHZ.js} +3 -3
  37. package/dist/{chunk-ZSWSHFDT.js → chunk-GEAZ65AU.js} +1429 -2847
  38. package/dist/{chunk-HCYSYR53.js → chunk-GUB3OCYR.js} +4 -4
  39. package/dist/{chunk-QCB66M7L.js → chunk-H66NIXGL.js} +9 -14
  40. package/dist/chunk-I6HNYSMO.js +29 -0
  41. package/dist/{chunk-GICOHCP4.js → chunk-JSLLI5MY.js} +3 -3
  42. package/dist/{chunk-N5XLVLT2.js → chunk-KDK32T2A.js} +5 -5
  43. package/dist/{chunk-CBNQDBKA.js → chunk-L5WUZYLP.js} +5 -5
  44. package/dist/{chunk-OVW3UN2A.js → chunk-LHP5F5LY.js} +4 -4
  45. package/dist/{chunk-TOO3W2BY.js → chunk-LP2H26NY.js} +3 -3
  46. package/dist/{chunk-NIEJLNRY.js → chunk-LQX3GDBO.js} +2 -2
  47. package/dist/{chunk-WJ6UGHRU.js → chunk-LVLKZYIC.js} +3 -3
  48. package/dist/{chunk-WADS2TV5.js → chunk-NO7MYZEO.js} +5 -4
  49. package/dist/{chunk-LH533WG4.js → chunk-NPH2SXRO.js} +2 -2
  50. package/dist/{chunk-IJ3PP7ZN.js → chunk-PIJQNKK6.js} +5 -5
  51. package/dist/{chunk-S4VBXFXP.js → chunk-PWYMBX34.js} +220 -16
  52. package/dist/{chunk-DIRL62CL.js → chunk-QCK4QLB3.js} +17912 -17444
  53. package/dist/{chunk-5D3LUZSS.js → chunk-QI5O2CU6.js} +5 -5
  54. package/dist/{chunk-BZUV56IY.js → chunk-RX7VCI62.js} +4 -4
  55. package/dist/{chunk-HULX6T4O.js → chunk-T7DHXEC7.js} +3 -3
  56. package/dist/{chunk-VB3O7QTH.js → chunk-TCQUNDAY.js} +3 -3
  57. package/dist/{chunk-CM4H6QMH.js → chunk-TSZPIP7J.js} +3 -3
  58. package/dist/{chunk-OHZL7KKD.js → chunk-UTXBNNRZ.js} +4 -4
  59. package/dist/{chunk-QAB3YP77.js → chunk-VPPZXHAK.js} +2 -2
  60. package/dist/{chunk-ARLG6JTK.js → chunk-W26344X5.js} +3 -3
  61. package/dist/{chunk-EQPYUHNM.js → chunk-WIG6CPGR.js} +1497 -112
  62. package/dist/{chunk-MMRWTLF3.js → chunk-X34BQYQP.js} +2 -2
  63. package/dist/chunk-YLPSXWEZ.js +12 -0
  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-T44V63XJ.js → custom-oclif-loader-F5UKFNJS.js} +13 -6
  99. package/dist/{error-handler-5KEL3EJC.js → error-handler-GCSQB44R.js} +10 -8
  100. package/dist/hooks/postrun.js +12 -14
  101. package/dist/hooks/prerun.js +8 -11
  102. package/dist/index.js +7708 -7452
  103. package/dist/{local-LBAOYPL4.js → local-VDSO2Y52.js} +4 -4
  104. package/dist/{morph-6NYGHGNT.js → morph-DQWX4LPS.js} +9 -9
  105. package/dist/{node-3X5EZ2GL.js → node-3B62KLPY.js} +21 -19
  106. package/dist/{node-package-manager-BU3KHLYT.js → node-package-manager-QLAS6UPG.js} +5 -6
  107. package/dist/{path-JVVXOELJ.js → path-KUSF6CYC.js} +2 -2
  108. package/dist/{system-GPQDWNIQ.js → system-CPU6ZDBW.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-4ZQNF2YQ.js → ui-Y4GW3DUK.js} +4 -4
  112. package/dist/{workerd-66AMV47V.js → workerd-LSXMDY5M.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/app/components/Cart.tsx +0 -364
  120. package/dist/assets/hydrogen/tailwind/postcss.config.js +0 -10
  121. package/dist/assets/hydrogen/tailwind/tailwind.config.js +0 -8
  122. package/dist/chunk-6EIVOQYP.js +0 -1070
  123. package/dist/chunk-GITQBCE5.js +0 -4385
  124. package/dist/chunk-HNAZOLMG.js +0 -1453
  125. package/dist/chunk-MO3UHUJB.js +0 -221
  126. package/dist/chunk-NBSFQYKI.js +0 -496
  127. package/dist/chunk-NVFSN34M.js +0 -265
  128. package/dist/chunk-QOUOFEGO.js +0 -35
  129. package/dist/chunk-WLZZJLBE.js +0 -12
  130. package/dist/error-handler-QVRMTAG7.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>
@@ -1,9 +1,94 @@
1
1
  # skeleton
2
2
 
3
- ## 0.0.0-next-9eb60d7-20240607102913
3
+ ## 0.0.0-next-18e76ba-20240708092928
4
4
 
5
5
  ### Patch Changes
6
6
 
7
+ - Update `@shopify/oxygen-workers-types` to fix issues on Windows. ([#2252](https://github.com/Shopify/hydrogen/pull/2252)) by [@michenly](https://github.com/michenly)
8
+
9
+ - [**Breaking change**] ([#2113](https://github.com/Shopify/hydrogen/pull/2113)) by [@blittle](https://github.com/blittle)
10
+
11
+ 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:
12
+
13
+ ```diff
14
+ <VariantSelector
15
+ handle={product.handle}
16
+ + options={product.options.filter((option) => option.values.length > 1)}
17
+ - options={product.options}
18
+ variants={variants}>
19
+ </VariantSelector>
20
+ ```
21
+
22
+ Fixes [#1198](https://github.com/Shopify/hydrogen/discussions/1198)
23
+
24
+ - Update remix to v2.10.1 ([#2290](https://github.com/Shopify/hydrogen/pull/2290)) by [@michenly](https://github.com/michenly)
25
+
26
+ - 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)
27
+
28
+ The diff below showcase how you can make this refactor in existing application.
29
+
30
+ ```diff
31
+ import {
32
+ Outlet,
33
+ - useLoaderData,
34
+ + useRouteLoaderData,
35
+ } from '@remix-run/react';
36
+ -import {Layout} from '~/components/Layout';
37
+ +import {PageLayout} from '~/components/PageLayout';
38
+
39
+ -export default function App() {
40
+ +export function Layout({children}: {children?: React.ReactNode}) {
41
+ const nonce = useNonce();
42
+ - const data = useLoaderData<typeof loader>();
43
+ + const data = useRouteLoaderData<typeof loader>('root');
44
+
45
+ return (
46
+ <html>
47
+ ...
48
+ <body>
49
+ - <Layout {...data}>
50
+ - <Outlet />
51
+ - </Layout>
52
+ + {data? (
53
+ + <PageLayout {...data}>{children}</PageLayout>
54
+ + ) : (
55
+ + children
56
+ + )}
57
+ </body>
58
+ </html>
59
+ );
60
+ }
61
+
62
+ +export default function App() {
63
+ + return <Outlet />;
64
+ +}
65
+
66
+ export function ErrorBoundary() {
67
+ const rootData = useLoaderData<typeof loader>();
68
+
69
+ return (
70
+ - <html>
71
+ - ...
72
+ - <body>
73
+ - <Layout {...rootData}>
74
+ - <div className="route-error">
75
+ - <h1>Error</h1>
76
+ - ...
77
+ - </div>
78
+ - </Layout>
79
+ - </body>
80
+ - </html>
81
+ + <div className="route-error">
82
+ + <h1>Error</h1>
83
+ + ...
84
+ + </div>
85
+ );
86
+ }
87
+
88
+ ```
89
+
90
+ - Refactor the cart and product form components ([#2132](https://github.com/Shopify/hydrogen/pull/2132)) by [@blittle](https://github.com/blittle)
91
+
7
92
  - 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
93
 
9
94
  Step 1: Add `isPending` implementation in session
@@ -68,10 +153,18 @@
68
153
 
69
154
  - Trigger changeset for all packages for next release
70
155
 
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
156
+ - 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)
157
+
158
+ ```diff
159
+ "@shopify/cli": "3.64.0",
160
+ - "@shopify/cli-hydrogen": "^8.1.1",
161
+ "@shopify/hydrogen": "2024.7.0",
162
+ ```
163
+
164
+ - 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), [`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)]:
165
+ - @shopify/remix-oxygen@0.0.0-next-18e76ba-20240708092928
166
+ - @shopify/cli-hydrogen@0.0.0-next-18e76ba-20240708092928
167
+ - @shopify/hydrogen@0.0.0-next-18e76ba-20240708092928
75
168
 
76
169
  ## 2024.4.5
77
170
 
@@ -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;
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 && 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>;
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,
@@ -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
+ }
@@ -0,0 +1,27 @@
1
+ import {Money} from '@shopify/hydrogen';
2
+ import type {MoneyV2} from '@shopify/hydrogen/storefront-api-types';
3
+
4
+ export function ProductPrice({
5
+ price,
6
+ compareAtPrice,
7
+ }: {
8
+ price?: MoneyV2;
9
+ compareAtPrice?: MoneyV2 | null;
10
+ }) {
11
+ return (
12
+ <div className="product-price">
13
+ {compareAtPrice ? (
14
+ <div className="product-price-on-sale">
15
+ {price ? <Money data={price} /> : null}
16
+ <s>
17
+ <Money data={compareAtPrice} />
18
+ </s>
19
+ </div>
20
+ ) : price ? (
21
+ <Money data={price} />
22
+ ) : (
23
+ <span>&nbsp;</span>
24
+ )}
25
+ </div>
26
+ );
27
+ }
@@ -309,6 +309,7 @@ export function PredictiveSearchForm({
309
309
  {...props}
310
310
  className={className}
311
311
  onSubmit={(event) => {
312
+ debugger;
312
313
  event.preventDefault();
313
314
  event.stopPropagation();
314
315
  if (!inputRef?.current || inputRef.current.value === '') {