@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.
- package/dist/assets/cli-ruby/lib/shopify_cli/reporting_configuration_controller.rb +2 -37
- package/dist/assets/cli-ruby/lib/shopify_cli/theme/file.rb +5 -1
- package/dist/assets/dev-console/extensions/dev-console/assets/index-Cgb-oKsM.css +1 -0
- package/dist/assets/dev-console/extensions/dev-console/assets/{index-Dui3DO9f.js → index-D7F9wNys.js} +12 -12
- package/dist/assets/dev-console/index.html +2 -2
- package/dist/assets/hydrogen/starter/.graphqlrc.ts +27 -0
- package/dist/assets/hydrogen/starter/CHANGELOG.md +108 -6
- package/dist/assets/hydrogen/starter/app/components/AddToCartButton.tsx +37 -0
- package/dist/assets/hydrogen/starter/app/components/CartLineItem.tsx +150 -0
- package/dist/assets/hydrogen/starter/app/components/CartMain.tsx +68 -0
- package/dist/assets/hydrogen/starter/app/components/CartSummary.tsx +101 -0
- package/dist/assets/hydrogen/starter/app/components/Header.tsx +3 -3
- package/dist/assets/hydrogen/starter/app/components/PageLayout.tsx +2 -2
- package/dist/assets/hydrogen/starter/app/components/ProductForm.tsx +80 -0
- package/dist/assets/hydrogen/starter/app/components/ProductImage.tsx +23 -0
- package/dist/assets/hydrogen/starter/app/components/ProductPrice.tsx +27 -0
- package/dist/assets/hydrogen/starter/app/root.tsx +11 -17
- package/dist/assets/hydrogen/starter/app/routes/cart.tsx +1 -1
- package/dist/assets/hydrogen/starter/app/routes/products.$handle.tsx +51 -232
- package/dist/assets/hydrogen/starter/package.json +11 -11
- package/dist/assets/hydrogen/tailwind/package.json +1 -6
- package/dist/assets/hydrogen/tailwind/tailwind.css +6 -3
- package/dist/assets/hydrogen/vanilla-extract/package.json +2 -3
- package/dist/assets/hydrogen/virtual-routes/components/{Layout.jsx → PageLayout.jsx} +2 -2
- package/dist/assets/hydrogen/virtual-routes/virtual-root.jsx +8 -30
- package/dist/{chunk-YAYFJITA.js → chunk-2DXCIFDK.js} +3 -3
- package/dist/{chunk-EQR6CWKL.js → chunk-63D4EGTO.js} +3 -3
- package/dist/{chunk-EZYMDZPN.js → chunk-6PJAGL2L.js} +5 -5
- package/dist/{chunk-S4VBXFXP.js → chunk-7PVTYKQI.js} +220 -16
- package/dist/{chunk-IRWSC76I.js → chunk-7TIDA343.js} +3 -3
- package/dist/{chunk-NPLAQVTF.js → chunk-7W6SRTYP.js} +3 -3
- package/dist/{chunk-UJYIV6JP.js → chunk-ARCFCLME.js} +5 -5
- package/dist/{chunk-DIZHFZTJ.js → chunk-ATGUMSCJ.js} +4 -4
- package/dist/chunk-B3D5VLUA.js +12 -0
- package/dist/{chunk-UNPXLODI.js → chunk-B7RN7IRD.js} +3 -4
- package/dist/{chunk-5BLMIE7F.js → chunk-DX43V2L4.js} +4 -4
- package/dist/{chunk-6UDFXWNE.js → chunk-EJITPGUJ.js} +21 -4
- package/dist/chunk-FDLU3RD4.js +29 -0
- package/dist/{chunk-7OCUVNSF.js → chunk-H6AQTECB.js} +1485 -2977
- package/dist/{chunk-T54B5GJP.js → chunk-JNUJZFQL.js} +2 -2
- package/dist/{chunk-XER2L725.js → chunk-KUVX423E.js} +2 -2
- package/dist/{chunk-R5KML52V.js → chunk-KZTALMEV.js} +4 -4
- package/dist/{chunk-CM4H6QMH.js → chunk-M6KGRVDD.js} +3 -3
- package/dist/{chunk-3MDI6LZT.js → chunk-NLE3ZLU6.js} +905 -896
- package/dist/{chunk-WADS2TV5.js → chunk-NO7MYZEO.js} +5 -4
- package/dist/{chunk-LH533WG4.js → chunk-NPH2SXRO.js} +2 -2
- package/dist/{chunk-VZUWS5IH.js → chunk-OVWFZSJT.js} +3 -3
- package/dist/{chunk-UQUO22Q5.js → chunk-OXMHVKM3.js} +5 -5
- package/dist/{chunk-PNFEODLY.js → chunk-QNI6VLVR.js} +3 -3
- package/dist/{chunk-MCT2524Y.js → chunk-QYT42J3T.js} +4 -4
- package/dist/{chunk-VQTHQBEC.js → chunk-S3HWVIGJ.js} +9 -14
- package/dist/{chunk-23OKKZ5V.js → chunk-S7A7BHNA.js} +4 -4
- package/dist/{chunk-3TGMDPDI.js → chunk-SSAUIEBT.js} +2 -2
- package/dist/{chunk-YMPGWFWU.js → chunk-UQQI7TQG.js} +3 -3
- package/dist/{chunk-UZUD5DRI.js → chunk-UXA5YROL.js} +2 -2
- package/dist/{chunk-K3CVGV3F.js → chunk-V7NH4SZB.js} +3 -3
- package/dist/{chunk-7AVKIH7O.js → chunk-WVY52EEZ.js} +5 -5
- package/dist/{chunk-QEOBHRRQ.js → chunk-X3OUSYUQ.js} +17912 -17444
- package/dist/{chunk-EARPFFS7.js → chunk-XNCQBHNR.js} +241 -4
- package/dist/{chunk-EQPYUHNM.js → chunk-ZIGJPI5N.js} +1497 -112
- package/dist/{chunk-A2UVOX6O.js → chunk-ZKWHKX2C.js} +7066 -6051
- package/dist/{chunk-GPMHDCWK.js → chunk-ZRGD2HUL.js} +3 -3
- package/dist/{chunk-PQKGBYDC.js → chunk-ZVT2WZZF.js} +3 -3
- package/dist/cli/commands/auth/logout.js +14 -19
- package/dist/cli/commands/auth/logout.test.js +17 -21
- package/dist/cli/commands/debug/command-flags.js +11 -15
- package/dist/cli/commands/demo/catalog.js +13 -18
- package/dist/cli/commands/demo/generate-file.js +13 -18
- package/dist/cli/commands/demo/index.js +13 -18
- package/dist/cli/commands/demo/print-ai-prompt.js +13 -18
- package/dist/cli/commands/docs/generate.js +11 -15
- package/dist/cli/commands/docs/generate.test.js +11 -15
- package/dist/cli/commands/help.js +11 -15
- package/dist/cli/commands/kitchen-sink/async.js +12 -16
- package/dist/cli/commands/kitchen-sink/async.test.js +12 -16
- package/dist/cli/commands/kitchen-sink/index.js +14 -18
- package/dist/cli/commands/kitchen-sink/index.test.js +14 -18
- package/dist/cli/commands/kitchen-sink/prompts.js +12 -16
- package/dist/cli/commands/kitchen-sink/prompts.test.js +12 -16
- package/dist/cli/commands/kitchen-sink/static.js +12 -16
- package/dist/cli/commands/kitchen-sink/static.test.js +12 -16
- package/dist/cli/commands/search.js +12 -16
- package/dist/cli/commands/upgrade.js +11 -15
- package/dist/cli/commands/version.js +12 -16
- package/dist/cli/commands/version.test.js +12 -16
- package/dist/cli/services/commands/search.js +4 -4
- package/dist/cli/services/commands/search.test.js +4 -4
- package/dist/cli/services/commands/version.js +6 -7
- package/dist/cli/services/commands/version.test.js +7 -8
- package/dist/cli/services/demo.js +5 -6
- package/dist/cli/services/demo.test.js +5 -6
- package/dist/cli/services/kitchen-sink/async.js +4 -4
- package/dist/cli/services/kitchen-sink/prompts.js +4 -4
- package/dist/cli/services/kitchen-sink/static.js +4 -4
- package/dist/cli/services/upgrade.js +5 -6
- package/dist/cli/services/upgrade.test.js +7 -8
- package/dist/{constants-3CLHB4LQ.js → constants-EVER32LA.js} +3 -3
- package/dist/{custom-oclif-loader-D4H5EJW6.js → custom-oclif-loader-BQAFOUNG.js} +13 -6
- package/dist/{error-handler-HUI4HW3X.js → error-handler-S56KHSGD.js} +10 -8
- package/dist/hooks/postrun.js +12 -14
- package/dist/hooks/prerun.js +8 -11
- package/dist/index.js +7811 -7490
- package/dist/{local-7IRDZWLW.js → local-UQAQKOVL.js} +4 -4
- package/dist/{morph-6NYGHGNT.js → morph-DN4AZJZW.js} +9 -9
- package/dist/{node-UIH7JP3D.js → node-GZYZUMBW.js} +21 -19
- package/dist/{node-package-manager-2LWT2MNN.js → node-package-manager-AOVZD6TP.js} +5 -6
- package/dist/{path-JVVXOELJ.js → path-KUSF6CYC.js} +2 -2
- package/dist/{system-4HHX42JS.js → system-G7DVECOP.js} +4 -4
- package/dist/templates/ui-extensions/projects/web_pixel_extension/package.json.liquid +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/{ui-NFBKMC4P.js → ui-2AOZFYFM.js} +4 -4
- package/dist/{workerd-4HFD3PS4.js → workerd-2MO23YDQ.js} +22 -19
- package/oclif.manifest.json +265 -4
- package/package.json +7 -10
- package/dist/assets/dev-console/extensions/dev-console/assets/index-Bi7y6lI5.css +0 -1
- package/dist/assets/hydrogen/css-modules/package.json +0 -6
- package/dist/assets/hydrogen/postcss/package.json +0 -10
- package/dist/assets/hydrogen/postcss/postcss.config.js +0 -8
- package/dist/assets/hydrogen/starter/.graphqlrc.yml +0 -12
- package/dist/assets/hydrogen/starter/app/components/Cart.tsx +0 -364
- package/dist/assets/hydrogen/tailwind/postcss.config.js +0 -10
- package/dist/assets/hydrogen/tailwind/tailwind.config.js +0 -8
- package/dist/chunk-4WBV3WP3.js +0 -221
- package/dist/chunk-OWICSMFV.js +0 -12
- package/dist/chunk-QCDYZY46.js +0 -1070
- package/dist/chunk-QEKTVN5A.js +0 -4385
- package/dist/chunk-QOUOFEGO.js +0 -35
- package/dist/chunk-WP234IUO.js +0 -265
- package/dist/chunk-XLPMGRR3.js +0 -496
- package/dist/chunk-XSKJYEAZ.js +0 -1453
- 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-
|
|
16
|
-
<link rel="stylesheet" crossorigin href="/extensions/dev-console/assets/index-
|
|
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
|
-
##
|
|
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
|
-
-
|
|
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/
|
|
73
|
-
- @shopify/cli-hydrogen@
|
|
74
|
-
- @shopify/
|
|
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} </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>− </span>
|
|
90
|
+
</button>
|
|
91
|
+
</CartLineUpdateButton>
|
|
92
|
+
|
|
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>+</span>
|
|
101
|
+
</button>
|
|
102
|
+
</CartLineUpdateButton>
|
|
103
|
+
|
|
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’t added anything yet, let’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 →</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
|
+
|
|
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
|
+
|
|
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> </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={
|
|
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/
|
|
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
|
|
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
|
+
}
|