@shopify/cli 3.72.1 → 3.73.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.
- package/dist/assets/hydrogen/starter/CHANGELOG.md +814 -0
- package/dist/assets/hydrogen/starter/app/components/Aside.tsx +1 -1
- package/dist/assets/hydrogen/starter/app/components/PaginatedResourceSection.tsx +3 -4
- package/dist/assets/hydrogen/starter/app/components/ProductForm.tsx +113 -44
- package/dist/assets/hydrogen/starter/app/components/SearchResults.tsx +8 -3
- package/dist/assets/hydrogen/starter/app/components/SearchResultsPredictive.tsx +6 -5
- package/dist/assets/hydrogen/starter/app/lib/variants.ts +3 -3
- package/dist/assets/hydrogen/starter/app/routes/account_.logout.tsx +1 -1
- package/dist/assets/hydrogen/starter/app/routes/cart.tsx +9 -16
- package/dist/assets/hydrogen/starter/app/routes/collections.$handle.tsx +1 -10
- package/dist/assets/hydrogen/starter/app/routes/collections.all.tsx +1 -10
- package/dist/assets/hydrogen/starter/app/routes/products.$handle.tsx +50 -119
- package/dist/assets/hydrogen/starter/app/routes/search.tsx +42 -38
- package/dist/assets/hydrogen/starter/app/styles/app.css +25 -1
- package/dist/assets/hydrogen/starter/guides/predictiveSearch/predictiveSearch.md +23 -20
- package/dist/assets/hydrogen/starter/guides/search/search.md +27 -25
- package/dist/assets/hydrogen/starter/package.json +3 -3
- package/dist/assets/hydrogen/starter/storefrontapi.generated.d.ts +177 -194
- package/dist/assets/hydrogen/tailwind/tailwind.css +1 -1
- package/dist/{chokidar-OESTCX4H.js → chokidar-5LLC6S6D.js} +47 -90
- package/dist/{chunk-VSLR7ET4.js → chunk-25IMI7TH.js} +8 -46
- package/dist/{chunk-NCKQIOV4.js → chunk-3HBRMIPY.js} +28 -50
- package/dist/{chunk-4DZKSJ2R.js → chunk-3OXCI3HX.js} +1402 -1989
- package/dist/{chunk-NX2KGZ2W.js → chunk-5OFJTURZ.js} +4 -4
- package/dist/{chunk-YITJ52BH.js → chunk-65GSOZHL.js} +7 -7
- package/dist/{chunk-5BCHJBAS.js → chunk-66HLUVKV.js} +4 -4
- package/dist/{chunk-J6WHIPSP.js → chunk-6PEBJVBW.js} +3 -3
- package/dist/{chunk-XVNW332R.js → chunk-75LV6AQS.js} +6 -10
- package/dist/{chunk-4ERNAW5E.js → chunk-7RKRCUFA.js} +16518 -24140
- package/dist/{chunk-FGSRYMBN.js → chunk-AFPOP3K5.js} +67 -104
- package/dist/{chunk-2HGYYNE5.js → chunk-B5EXYCV3.js} +5 -9
- package/dist/{chunk-NB4NLOEJ.js → chunk-BUFIEXZ5.js} +11 -20
- package/dist/{chunk-EQQFJ434.js → chunk-CEIZT2W3.js} +4 -4
- package/dist/{chunk-522OB3EU.js → chunk-CFIKVUNW.js} +2 -2
- package/dist/{chunk-KYB6A4PE.js → chunk-CRHXI6PS.js} +13 -23
- package/dist/{chunk-QBE6MS4X.js → chunk-ELTOJBOJ.js} +4 -5
- package/dist/{chunk-NUP5TATA.js → chunk-EZQWZ57B.js} +2 -2
- package/dist/{chunk-HAURZWLZ.js → chunk-F7D333WQ.js} +3 -3
- package/dist/{chunk-UBB7JKND.js → chunk-G2ZZKGSV.js} +2 -2
- package/dist/{chunk-CBBS4CV7.js → chunk-G5R6YD27.js} +8 -13
- package/dist/{chunk-REXJJC44.js → chunk-GDLROW57.js} +3 -3
- package/dist/{chunk-TQTXTCOI.js → chunk-I25PGLBO.js} +3 -3
- package/dist/{chunk-OXKHBIW7.js → chunk-IG5SOACB.js} +29 -34
- package/dist/{chunk-2FXFMB2U.js → chunk-IRHYYIN7.js} +4 -4
- package/dist/{chunk-OWLPHMUA.js → chunk-J673ZU5S.js} +4 -4
- package/dist/{chunk-U2VX4Q6V.js → chunk-JORKLY7M.js} +68 -49
- package/dist/{chunk-K6Y4FYT5.js → chunk-K7HGDAI4.js} +17 -20
- package/dist/{chunk-MTXNEHKM.js → chunk-KF2D6QHZ.js} +39 -68
- package/dist/{chunk-QBDNLBUG.js → chunk-KMWARALD.js} +5 -5
- package/dist/{chunk-E7MQ72JO.js → chunk-KTNFE44J.js} +111 -84
- package/dist/{chunk-BYPQXSAL.js → chunk-KUM3DVPF.js} +17 -13
- package/dist/{chunk-B2HZKGLW.js → chunk-KZBL6BQ7.js} +4 -4
- package/dist/{chunk-WNDN5FAY.js → chunk-MHUINF7I.js} +3 -3
- package/dist/{chunk-YDFZISQ3.js → chunk-NFQLKURK.js} +41 -68
- package/dist/{chunk-7JVASAC6.js → chunk-O77L7CCL.js} +4 -4
- package/dist/{chunk-SNOECVP4.js → chunk-OAZFIMJ3.js} +2 -2
- package/dist/{chunk-OYHBLPKV.js → chunk-OE3IXTC5.js} +38 -60
- package/dist/{chunk-HLKMRAT5.js → chunk-ONOLOXLM.js} +5 -5
- package/dist/{chunk-POZ5MGPT.js → chunk-PKR7KJ6P.js} +2 -3
- package/dist/{chunk-YG5QRMVT.js → chunk-PRWEHR2C.js} +3 -3
- package/dist/{chunk-72RTT46Y.js → chunk-QNK2EAZ3.js} +5 -5
- package/dist/{chunk-3G3CTE4U.js → chunk-RCA7PFH4.js} +4 -4
- package/dist/{chunk-OG2XEFVO.js → chunk-RK7JAMCI.js} +6 -6
- package/dist/{chunk-QRNLG3CD.js → chunk-SANP6FPA.js} +6 -6
- package/dist/{chunk-OJOHMVV7.js → chunk-SBPFWO4S.js} +294 -448
- package/dist/{chunk-O5K4AU7Q.js → chunk-SHWOPMLQ.js} +3 -4
- package/dist/{chunk-6Z7SI3ZW.js → chunk-SUUVDRTQ.js} +6 -7
- package/dist/{chunk-HHAGWNRI.js → chunk-TEHNKBLD.js} +6 -7
- package/dist/{chunk-XHR6UC4Y.js → chunk-VPNXQGG6.js} +4 -4
- package/dist/{chunk-MWLLV4UV.js → chunk-X5FJXK25.js} +9 -9
- package/dist/{chunk-PMUQTGZJ.js → chunk-X7YTIMNN.js} +4 -6
- package/dist/{chunk-PC7RDKJJ.js → chunk-XAGT2UNE.js} +3 -3
- package/dist/{chunk-7Q3MMWAC.js → chunk-XE5EOEBL.js} +2 -2
- package/dist/{chunk-VVD57NWZ.js → chunk-YP7WU5EU.js} +5 -5
- package/dist/{chunk-G2WBIWNF.js → chunk-YPTEMDFR.js} +5 -5
- package/dist/{chunk-4PXLANQP.js → chunk-ZENVITME.js} +4 -4
- package/dist/{chunk-HSTSRNLJ.js → chunk-ZX3L2JKV.js} +64 -86
- package/dist/cli/commands/auth/logout.js +28 -28
- package/dist/cli/commands/auth/logout.test.js +30 -30
- package/dist/cli/commands/cache/clear.js +27 -27
- package/dist/cli/commands/debug/command-flags.js +27 -27
- package/dist/cli/commands/docs/generate.js +27 -27
- package/dist/cli/commands/docs/generate.test.js +28 -28
- package/dist/cli/commands/help.js +27 -27
- package/dist/cli/commands/kitchen-sink/async.js +28 -28
- package/dist/cli/commands/kitchen-sink/async.test.js +29 -29
- package/dist/cli/commands/kitchen-sink/index.js +30 -30
- package/dist/cli/commands/kitchen-sink/index.test.js +31 -31
- package/dist/cli/commands/kitchen-sink/prompts.js +28 -28
- package/dist/cli/commands/kitchen-sink/prompts.test.js +29 -29
- package/dist/cli/commands/kitchen-sink/static.js +28 -28
- package/dist/cli/commands/kitchen-sink/static.test.js +29 -29
- package/dist/cli/commands/notifications/generate.js +28 -28
- package/dist/cli/commands/notifications/list.js +28 -28
- package/dist/cli/commands/search.js +28 -28
- package/dist/cli/commands/upgrade.js +28 -28
- package/dist/cli/commands/upgrade.test.js +3 -3
- package/dist/cli/commands/version.js +28 -28
- package/dist/cli/commands/version.test.js +29 -29
- package/dist/cli/services/commands/notifications.js +21 -21
- package/dist/cli/services/commands/search.js +15 -15
- package/dist/cli/services/commands/search.test.js +16 -16
- package/dist/cli/services/commands/version.js +16 -16
- package/dist/cli/services/commands/version.test.js +18 -18
- package/dist/cli/services/kitchen-sink/async.js +15 -15
- package/dist/cli/services/kitchen-sink/prompts.js +15 -15
- package/dist/cli/services/kitchen-sink/static.js +15 -15
- package/dist/cli/services/upgrade.js +17 -17
- package/dist/cli/services/upgrade.test.js +20 -20
- package/dist/configs/all.yml +12 -0
- package/dist/configs/recommended.yml +15 -0
- package/dist/configs/theme-app-extension.yml +3 -0
- package/dist/{custom-oclif-loader-MUOL3HPQ.js → custom-oclif-loader-V3IB4SYZ.js} +15 -15
- package/dist/data/latest.json +1 -1
- package/dist/data/manifest_theme.json +1 -0
- package/dist/data/preset.json +66 -0
- package/dist/data/preset_blocks.json +20 -13
- package/dist/data/section.json +1 -15
- package/dist/data/setting.json +1 -1
- package/dist/data/theme_block.json +1 -15
- package/dist/{del-K5ZJEWTD.js → del-P2RS6GN2.js} +9 -9
- package/dist/{devtools-KYKGATNX.js → devtools-K7FXBBFZ.js} +3 -3
- package/dist/error-handler-WK3AZ7A2.js +38 -0
- package/dist/hooks/postrun.js +22 -22
- package/dist/hooks/prerun.js +22 -22
- package/dist/{http-proxy-GGTVQ6CU.js → http-proxy-FXWKYHZ3.js} +14 -24
- package/dist/index.js +7229 -7321
- package/dist/lib-JVEIEXYB.js +6 -0
- package/dist/lib-QZGSY5YB.js +13 -0
- package/dist/{local-YCARFR7T.js → local-3ERK45M5.js} +15 -15
- package/dist/{magic-string.es-3RXPUXZF.js → magic-string.es-PJMYOE6F.js} +49 -96
- package/dist/{morph-SEECJQ2W.js → morph-OSHCID2F.js} +261 -347
- package/dist/{multipart-parser-QKUAJJP5.js → multipart-parser-WSNBP656.js} +8 -7
- package/dist/{node-NSRIJ2KL.js → node-2KLEBSMO.js} +31 -31
- package/dist/{node-package-manager-TLCUDNS2.js → node-package-manager-QIM24GB3.js} +19 -17
- package/dist/{npa-RLWNZ3BM.js → npa-TM76BGG3.js} +10 -16
- package/dist/{open-MZGVNFZO.js → open-BHIF7E3E.js} +3 -3
- package/dist/out-JR4DWQ2G.js +7 -0
- package/dist/{path-NDLRQPLA.js → path-2HZUSAGR.js} +3 -3
- package/dist/{source-map-7AAPWPHZ.js → source-map-QCVC46UY.js} +2 -2
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/{ui-5AEI3FU6.js → ui-5AHG256I.js} +15 -15
- package/dist/{workerd-SW7QDYB6.js → workerd-MSNALKI2.js} +30 -30
- package/oclif.manifest.json +81 -1
- package/package.json +8 -8
- package/dist/error-handler-MOT24ECB.js +0 -38
- package/dist/lib-PPXZBVZX.js +0 -6
- package/dist/lib-XYCLX35G.js +0 -13
- package/dist/out-MHEKZJWS.js +0 -7
|
@@ -4,7 +4,6 @@ import {Pagination} from '@shopify/hydrogen';
|
|
|
4
4
|
/**
|
|
5
5
|
* <PaginatedResourceSection > is a component that encapsulate how the previous and next behaviors throughout your application.
|
|
6
6
|
*/
|
|
7
|
-
|
|
8
7
|
export function PaginatedResourceSection<NodesType>({
|
|
9
8
|
connection,
|
|
10
9
|
children,
|
|
@@ -17,7 +16,7 @@ export function PaginatedResourceSection<NodesType>({
|
|
|
17
16
|
return (
|
|
18
17
|
<Pagination connection={connection}>
|
|
19
18
|
{({nodes, isLoading, PreviousLink, NextLink}) => {
|
|
20
|
-
const
|
|
19
|
+
const resourcesMarkup = nodes.map((node, index) =>
|
|
21
20
|
children({node, index}),
|
|
22
21
|
);
|
|
23
22
|
|
|
@@ -27,9 +26,9 @@ export function PaginatedResourceSection<NodesType>({
|
|
|
27
26
|
{isLoading ? 'Loading...' : <span>↑ Load previous</span>}
|
|
28
27
|
</PreviousLink>
|
|
29
28
|
{resourcesClassName ? (
|
|
30
|
-
<div className={resourcesClassName}>{
|
|
29
|
+
<div className={resourcesClassName}>{resourcesMarkup}</div>
|
|
31
30
|
) : (
|
|
32
|
-
|
|
31
|
+
resourcesMarkup
|
|
33
32
|
)}
|
|
34
33
|
<NextLink>
|
|
35
34
|
{isLoading ? 'Loading...' : <span>Load more ↓</span>}
|
|
@@ -1,32 +1,105 @@
|
|
|
1
|
-
import {Link} from '@remix-run/react';
|
|
2
|
-
import {type
|
|
1
|
+
import {Link, useNavigate} from '@remix-run/react';
|
|
2
|
+
import {type MappedProductOptions} from '@shopify/hydrogen';
|
|
3
3
|
import type {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
} from '
|
|
7
|
-
import {AddToCartButton} from '
|
|
8
|
-
import {useAside} from '
|
|
4
|
+
Maybe,
|
|
5
|
+
ProductOptionValueSwatch,
|
|
6
|
+
} from '@shopify/hydrogen/storefront-api-types';
|
|
7
|
+
import {AddToCartButton} from './AddToCartButton';
|
|
8
|
+
import {useAside} from './Aside';
|
|
9
|
+
import type {ProductFragment} from 'storefrontapi.generated';
|
|
9
10
|
|
|
10
11
|
export function ProductForm({
|
|
11
|
-
|
|
12
|
+
productOptions,
|
|
12
13
|
selectedVariant,
|
|
13
|
-
variants,
|
|
14
14
|
}: {
|
|
15
|
-
|
|
16
|
-
selectedVariant: ProductFragment['
|
|
17
|
-
variants: Array<ProductVariantFragment>;
|
|
15
|
+
productOptions: MappedProductOptions[];
|
|
16
|
+
selectedVariant: ProductFragment['selectedOrFirstAvailableVariant'];
|
|
18
17
|
}) {
|
|
18
|
+
const navigate = useNavigate();
|
|
19
19
|
const {open} = useAside();
|
|
20
20
|
return (
|
|
21
21
|
<div className="product-form">
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
22
|
+
{productOptions.map((option) => {
|
|
23
|
+
// If there is only a single value in the option values, don't display the option
|
|
24
|
+
if (option.optionValues.length === 1) return null;
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<div className="product-options" key={option.name}>
|
|
28
|
+
<h5>{option.name}</h5>
|
|
29
|
+
<div className="product-options-grid">
|
|
30
|
+
{option.optionValues.map((value) => {
|
|
31
|
+
const {
|
|
32
|
+
name,
|
|
33
|
+
handle,
|
|
34
|
+
variantUriQuery,
|
|
35
|
+
selected,
|
|
36
|
+
available,
|
|
37
|
+
exists,
|
|
38
|
+
isDifferentProduct,
|
|
39
|
+
swatch,
|
|
40
|
+
} = value;
|
|
41
|
+
|
|
42
|
+
if (isDifferentProduct) {
|
|
43
|
+
// SEO
|
|
44
|
+
// When the variant is a combined listing child product
|
|
45
|
+
// that leads to a different url, we need to render it
|
|
46
|
+
// as an anchor tag
|
|
47
|
+
return (
|
|
48
|
+
<Link
|
|
49
|
+
className="product-options-item"
|
|
50
|
+
key={option.name + name}
|
|
51
|
+
prefetch="intent"
|
|
52
|
+
preventScrollReset
|
|
53
|
+
replace
|
|
54
|
+
to={`/products/${handle}?${variantUriQuery}`}
|
|
55
|
+
style={{
|
|
56
|
+
border: selected
|
|
57
|
+
? '1px solid black'
|
|
58
|
+
: '1px solid transparent',
|
|
59
|
+
opacity: available ? 1 : 0.3,
|
|
60
|
+
}}
|
|
61
|
+
>
|
|
62
|
+
<ProductOptionSwatch swatch={swatch} name={name} />
|
|
63
|
+
</Link>
|
|
64
|
+
);
|
|
65
|
+
} else {
|
|
66
|
+
// SEO
|
|
67
|
+
// When the variant is an update to the search param,
|
|
68
|
+
// render it as a button with javascript navigating to
|
|
69
|
+
// the variant so that SEO bots do not index these as
|
|
70
|
+
// duplicated links
|
|
71
|
+
return (
|
|
72
|
+
<button
|
|
73
|
+
type="button"
|
|
74
|
+
className={`product-options-item${
|
|
75
|
+
exists && !selected ? ' link' : ''
|
|
76
|
+
}`}
|
|
77
|
+
key={option.name + name}
|
|
78
|
+
style={{
|
|
79
|
+
border: selected
|
|
80
|
+
? '1px solid black'
|
|
81
|
+
: '1px solid transparent',
|
|
82
|
+
opacity: available ? 1 : 0.3,
|
|
83
|
+
}}
|
|
84
|
+
disabled={!exists}
|
|
85
|
+
onClick={() => {
|
|
86
|
+
if (!selected) {
|
|
87
|
+
navigate(`?${variantUriQuery}`, {
|
|
88
|
+
replace: true,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}}
|
|
92
|
+
>
|
|
93
|
+
<ProductOptionSwatch swatch={swatch} name={name} />
|
|
94
|
+
</button>
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
})}
|
|
98
|
+
</div>
|
|
99
|
+
<br />
|
|
100
|
+
</div>
|
|
101
|
+
)
|
|
102
|
+
})}
|
|
30
103
|
<AddToCartButton
|
|
31
104
|
disabled={!selectedVariant || !selectedVariant.availableForSale}
|
|
32
105
|
onClick={() => {
|
|
@@ -50,31 +123,27 @@ export function ProductForm({
|
|
|
50
123
|
);
|
|
51
124
|
}
|
|
52
125
|
|
|
53
|
-
function
|
|
126
|
+
function ProductOptionSwatch({
|
|
127
|
+
swatch,
|
|
128
|
+
name,
|
|
129
|
+
}: {
|
|
130
|
+
swatch?: Maybe<ProductOptionValueSwatch> | undefined;
|
|
131
|
+
name: string;
|
|
132
|
+
}) {
|
|
133
|
+
const image = swatch?.image?.previewImage?.url;
|
|
134
|
+
const color = swatch?.color;
|
|
135
|
+
|
|
136
|
+
if (!image && !color) return name;
|
|
137
|
+
|
|
54
138
|
return (
|
|
55
|
-
<div
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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 />
|
|
139
|
+
<div
|
|
140
|
+
aria-label={name}
|
|
141
|
+
className="product-option-label-swatch"
|
|
142
|
+
style={{
|
|
143
|
+
backgroundColor: color || 'transparent',
|
|
144
|
+
}}
|
|
145
|
+
>
|
|
146
|
+
{!!image && <img src={image} alt={name} />}
|
|
78
147
|
</div>
|
|
79
148
|
);
|
|
80
149
|
}
|
|
@@ -113,12 +113,15 @@ function SearchResultsProducts({
|
|
|
113
113
|
term,
|
|
114
114
|
});
|
|
115
115
|
|
|
116
|
+
const price = product?.selectedOrFirstAvailableVariant?.price;
|
|
117
|
+
const image = product?.selectedOrFirstAvailableVariant?.image;
|
|
118
|
+
|
|
116
119
|
return (
|
|
117
120
|
<div className="search-results-item" key={product.id}>
|
|
118
121
|
<Link prefetch="intent" to={productUrl}>
|
|
119
|
-
{
|
|
122
|
+
{image && (
|
|
120
123
|
<Image
|
|
121
|
-
data={
|
|
124
|
+
data={image}
|
|
122
125
|
alt={product.title}
|
|
123
126
|
width={50}
|
|
124
127
|
/>
|
|
@@ -126,7 +129,9 @@ function SearchResultsProducts({
|
|
|
126
129
|
<div>
|
|
127
130
|
<p>{product.title}</p>
|
|
128
131
|
<small>
|
|
129
|
-
|
|
132
|
+
{price &&
|
|
133
|
+
<Money data={price} />
|
|
134
|
+
}
|
|
130
135
|
</small>
|
|
131
136
|
</div>
|
|
132
137
|
</Link>
|
|
@@ -133,7 +133,7 @@ function SearchResultsPredictiveCollections({
|
|
|
133
133
|
<h5>Collections</h5>
|
|
134
134
|
<ul>
|
|
135
135
|
{collections.map((collection) => {
|
|
136
|
-
const
|
|
136
|
+
const collectionUrl = urlWithTrackingParams({
|
|
137
137
|
baseUrl: `/collections/${collection.handle}`,
|
|
138
138
|
trackingParams: collection.trackingParameters,
|
|
139
139
|
term: term.current,
|
|
@@ -141,7 +141,7 @@ function SearchResultsPredictiveCollections({
|
|
|
141
141
|
|
|
142
142
|
return (
|
|
143
143
|
<li className="predictive-search-result-item" key={collection.id}>
|
|
144
|
-
<Link onClick={closeSearch} to={
|
|
144
|
+
<Link onClick={closeSearch} to={collectionUrl}>
|
|
145
145
|
{collection.image?.url && (
|
|
146
146
|
<Image
|
|
147
147
|
alt={collection.image.altText ?? ''}
|
|
@@ -213,7 +213,8 @@ function SearchResultsPredictiveProducts({
|
|
|
213
213
|
term: term.current,
|
|
214
214
|
});
|
|
215
215
|
|
|
216
|
-
const
|
|
216
|
+
const price = product?.selectedOrFirstAvailableVariant?.price;
|
|
217
|
+
const image = product?.selectedOrFirstAvailableVariant?.image;
|
|
217
218
|
return (
|
|
218
219
|
<li className="predictive-search-result-item" key={product.id}>
|
|
219
220
|
<Link to={productUrl} onClick={closeSearch}>
|
|
@@ -228,8 +229,8 @@ function SearchResultsPredictiveProducts({
|
|
|
228
229
|
<div>
|
|
229
230
|
<p>{product.title}</p>
|
|
230
231
|
<small>
|
|
231
|
-
{
|
|
232
|
-
<Money data={
|
|
232
|
+
{price && (
|
|
233
|
+
<Money data={price} />
|
|
233
234
|
)}
|
|
234
235
|
</small>
|
|
235
236
|
</div>
|
|
@@ -4,7 +4,7 @@ import {useMemo} from 'react';
|
|
|
4
4
|
|
|
5
5
|
export function useVariantUrl(
|
|
6
6
|
handle: string,
|
|
7
|
-
selectedOptions
|
|
7
|
+
selectedOptions?: SelectedOption[],
|
|
8
8
|
) {
|
|
9
9
|
const {pathname} = useLocation();
|
|
10
10
|
|
|
@@ -27,7 +27,7 @@ export function getVariantUrl({
|
|
|
27
27
|
handle: string;
|
|
28
28
|
pathname: string;
|
|
29
29
|
searchParams: URLSearchParams;
|
|
30
|
-
selectedOptions
|
|
30
|
+
selectedOptions?: SelectedOption[];
|
|
31
31
|
}) {
|
|
32
32
|
const match = /(\/[a-zA-Z]{2}-[a-zA-Z]{2}\/)/g.exec(pathname);
|
|
33
33
|
const isLocalePathname = match && match.length > 0;
|
|
@@ -36,7 +36,7 @@ export function getVariantUrl({
|
|
|
36
36
|
? `${match![0]}products/${handle}`
|
|
37
37
|
: `/products/${handle}`;
|
|
38
38
|
|
|
39
|
-
selectedOptions
|
|
39
|
+
selectedOptions?.forEach((option) => {
|
|
40
40
|
searchParams.set(option.name, option.value);
|
|
41
41
|
});
|
|
42
42
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {redirect, type ActionFunctionArgs} from '@shopify/remix-oxygen';
|
|
2
2
|
|
|
3
|
-
// if we
|
|
3
|
+
// if we don't implement this, /account/logout will get caught by account.$.tsx to do login
|
|
4
4
|
export async function loader() {
|
|
5
5
|
return redirect('/');
|
|
6
6
|
}
|
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {Suspense} from 'react';
|
|
1
|
+
import {type MetaFunction, useLoaderData} from '@remix-run/react';
|
|
3
2
|
import type {CartQueryDataReturn} from '@shopify/hydrogen';
|
|
4
3
|
import {CartForm} from '@shopify/hydrogen';
|
|
5
|
-
import {json, type ActionFunctionArgs} from '@shopify/remix-oxygen';
|
|
4
|
+
import {json, type LoaderFunctionArgs, type ActionFunctionArgs} from '@shopify/remix-oxygen';
|
|
6
5
|
import {CartMain} from '~/components/CartMain';
|
|
7
|
-
import type {RootLoader} from '~/root';
|
|
8
6
|
|
|
9
7
|
export const meta: MetaFunction = () => {
|
|
10
8
|
return [{title: `Hydrogen | Cart`}];
|
|
@@ -95,23 +93,18 @@ export async function action({request, context}: ActionFunctionArgs) {
|
|
|
95
93
|
);
|
|
96
94
|
}
|
|
97
95
|
|
|
96
|
+
export async function loader({context}: LoaderFunctionArgs) {
|
|
97
|
+
const {cart} = context;
|
|
98
|
+
return json(await cart.get());
|
|
99
|
+
}
|
|
100
|
+
|
|
98
101
|
export default function Cart() {
|
|
99
|
-
const
|
|
100
|
-
if (!rootData) return null;
|
|
102
|
+
const cart = useLoaderData<typeof loader>();
|
|
101
103
|
|
|
102
104
|
return (
|
|
103
105
|
<div className="cart">
|
|
104
106
|
<h1>Cart</h1>
|
|
105
|
-
<
|
|
106
|
-
<Await
|
|
107
|
-
resolve={rootData.cart}
|
|
108
|
-
errorElement={<div>An error occurred</div>}
|
|
109
|
-
>
|
|
110
|
-
{(cart) => {
|
|
111
|
-
return <CartMain layout="page" cart={cart} />;
|
|
112
|
-
}}
|
|
113
|
-
</Await>
|
|
114
|
-
</Suspense>
|
|
107
|
+
<CartMain layout="page" cart={cart} />
|
|
115
108
|
</div>
|
|
116
109
|
);
|
|
117
110
|
}
|
|
@@ -108,8 +108,7 @@ function ProductItem({
|
|
|
108
108
|
product: ProductItemFragment;
|
|
109
109
|
loading?: 'eager' | 'lazy';
|
|
110
110
|
}) {
|
|
111
|
-
const
|
|
112
|
-
const variantUrl = useVariantUrl(product.handle, variant.selectedOptions);
|
|
111
|
+
const variantUrl = useVariantUrl(product.handle);
|
|
113
112
|
return (
|
|
114
113
|
<Link
|
|
115
114
|
className="product-item"
|
|
@@ -158,14 +157,6 @@ const PRODUCT_ITEM_FRAGMENT = `#graphql
|
|
|
158
157
|
...MoneyProductItem
|
|
159
158
|
}
|
|
160
159
|
}
|
|
161
|
-
variants(first: 1) {
|
|
162
|
-
nodes {
|
|
163
|
-
selectedOptions {
|
|
164
|
-
name
|
|
165
|
-
value
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
160
|
}
|
|
170
161
|
` as const;
|
|
171
162
|
|
|
@@ -76,8 +76,7 @@ function ProductItem({
|
|
|
76
76
|
product: ProductItemFragment;
|
|
77
77
|
loading?: 'eager' | 'lazy';
|
|
78
78
|
}) {
|
|
79
|
-
const
|
|
80
|
-
const variantUrl = useVariantUrl(product.handle, variant.selectedOptions);
|
|
79
|
+
const variantUrl = useVariantUrl(product.handle);
|
|
81
80
|
return (
|
|
82
81
|
<Link
|
|
83
82
|
className="product-item"
|
|
@@ -126,14 +125,6 @@ const PRODUCT_ITEM_FRAGMENT = `#graphql
|
|
|
126
125
|
...MoneyProductItem
|
|
127
126
|
}
|
|
128
127
|
}
|
|
129
|
-
variants(first: 1) {
|
|
130
|
-
nodes {
|
|
131
|
-
selectedOptions {
|
|
132
|
-
name
|
|
133
|
-
value
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
128
|
}
|
|
138
129
|
` as const;
|
|
139
130
|
|
|
@@ -1,20 +1,25 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {Await, useLoaderData, type MetaFunction} from '@remix-run/react';
|
|
4
|
-
import type {ProductFragment} from 'storefrontapi.generated';
|
|
1
|
+
import {defer, type LoaderFunctionArgs} from '@shopify/remix-oxygen';
|
|
2
|
+
import {useLoaderData, type MetaFunction} from '@remix-run/react';
|
|
5
3
|
import {
|
|
6
4
|
getSelectedProductOptions,
|
|
7
5
|
Analytics,
|
|
8
6
|
useOptimisticVariant,
|
|
7
|
+
getProductOptions,
|
|
8
|
+
getAdjacentAndFirstAvailableVariants,
|
|
9
|
+
useSelectedOptionInUrlParam,
|
|
9
10
|
} from '@shopify/hydrogen';
|
|
10
|
-
import type {SelectedOption} from '@shopify/hydrogen/storefront-api-types';
|
|
11
|
-
import {getVariantUrl} from '~/lib/variants';
|
|
12
11
|
import {ProductPrice} from '~/components/ProductPrice';
|
|
13
12
|
import {ProductImage} from '~/components/ProductImage';
|
|
14
13
|
import {ProductForm} from '~/components/ProductForm';
|
|
15
14
|
|
|
16
15
|
export const meta: MetaFunction<typeof loader> = ({data}) => {
|
|
17
|
-
return [
|
|
16
|
+
return [
|
|
17
|
+
{title: `Hydrogen | ${data?.product.title ?? ''}`},
|
|
18
|
+
{
|
|
19
|
+
rel: 'canonical',
|
|
20
|
+
href: `/products/${data?.product.handle}`,
|
|
21
|
+
},
|
|
22
|
+
];
|
|
18
23
|
};
|
|
19
24
|
|
|
20
25
|
export async function loader(args: LoaderFunctionArgs) {
|
|
@@ -54,24 +59,6 @@ async function loadCriticalData({
|
|
|
54
59
|
throw new Response(null, {status: 404});
|
|
55
60
|
}
|
|
56
61
|
|
|
57
|
-
const firstVariant = product.variants.nodes[0];
|
|
58
|
-
const firstVariantIsDefault = Boolean(
|
|
59
|
-
firstVariant.selectedOptions.find(
|
|
60
|
-
(option: SelectedOption) =>
|
|
61
|
-
option.name === 'Title' && option.value === 'Default Title',
|
|
62
|
-
),
|
|
63
|
-
);
|
|
64
|
-
|
|
65
|
-
if (firstVariantIsDefault) {
|
|
66
|
-
product.selectedVariant = firstVariant;
|
|
67
|
-
} else {
|
|
68
|
-
// if no selected variant was returned from the selected options,
|
|
69
|
-
// we redirect to the first variant's url with it's selected options applied
|
|
70
|
-
if (!product.selectedVariant) {
|
|
71
|
-
throw redirectToFirstVariant({product, request});
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
62
|
return {
|
|
76
63
|
product,
|
|
77
64
|
};
|
|
@@ -83,56 +70,31 @@ async function loadCriticalData({
|
|
|
83
70
|
* Make sure to not throw any errors here, as it will cause the page to 500.
|
|
84
71
|
*/
|
|
85
72
|
function loadDeferredData({context, params}: LoaderFunctionArgs) {
|
|
86
|
-
//
|
|
87
|
-
//
|
|
88
|
-
// into it's own separate query that is deferred. So there's a brief moment
|
|
89
|
-
// where variant options might show as available when they're not, but after
|
|
90
|
-
// this deffered query resolves, the UI will update.
|
|
91
|
-
const variants = context.storefront
|
|
92
|
-
.query(VARIANTS_QUERY, {
|
|
93
|
-
variables: {handle: params.handle!},
|
|
94
|
-
})
|
|
95
|
-
.catch((error) => {
|
|
96
|
-
// Log query errors, but don't throw them so the page can still render
|
|
97
|
-
console.error(error);
|
|
98
|
-
return null;
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
return {
|
|
102
|
-
variants,
|
|
103
|
-
};
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
function redirectToFirstVariant({
|
|
107
|
-
product,
|
|
108
|
-
request,
|
|
109
|
-
}: {
|
|
110
|
-
product: ProductFragment;
|
|
111
|
-
request: Request;
|
|
112
|
-
}) {
|
|
113
|
-
const url = new URL(request.url);
|
|
114
|
-
const firstVariant = product.variants.nodes[0];
|
|
73
|
+
// Put any API calls that is not critical to be available on first page render
|
|
74
|
+
// For example: product reviews, product recommendations, social feeds.
|
|
115
75
|
|
|
116
|
-
return
|
|
117
|
-
getVariantUrl({
|
|
118
|
-
pathname: url.pathname,
|
|
119
|
-
handle: product.handle,
|
|
120
|
-
selectedOptions: firstVariant.selectedOptions,
|
|
121
|
-
searchParams: new URLSearchParams(url.search),
|
|
122
|
-
}),
|
|
123
|
-
{
|
|
124
|
-
status: 302,
|
|
125
|
-
},
|
|
126
|
-
);
|
|
76
|
+
return {};
|
|
127
77
|
}
|
|
128
78
|
|
|
129
79
|
export default function Product() {
|
|
130
|
-
const {product
|
|
80
|
+
const {product} = useLoaderData<typeof loader>();
|
|
81
|
+
|
|
82
|
+
// Optimistically selects a variant with given available variant information
|
|
131
83
|
const selectedVariant = useOptimisticVariant(
|
|
132
|
-
product.
|
|
133
|
-
|
|
84
|
+
product.selectedOrFirstAvailableVariant,
|
|
85
|
+
getAdjacentAndFirstAvailableVariants(product),
|
|
134
86
|
);
|
|
135
87
|
|
|
88
|
+
// Sets the search param to the selected variant without navigation
|
|
89
|
+
// only when no search params are set in the url
|
|
90
|
+
useSelectedOptionInUrlParam(selectedVariant.selectedOptions);
|
|
91
|
+
|
|
92
|
+
// Get the product options array
|
|
93
|
+
const productOptions = getProductOptions({
|
|
94
|
+
...product,
|
|
95
|
+
selectedOrFirstAvailableVariant: selectedVariant,
|
|
96
|
+
});
|
|
97
|
+
|
|
136
98
|
const {title, descriptionHtml} = product;
|
|
137
99
|
|
|
138
100
|
return (
|
|
@@ -145,28 +107,10 @@ export default function Product() {
|
|
|
145
107
|
compareAtPrice={selectedVariant?.compareAtPrice}
|
|
146
108
|
/>
|
|
147
109
|
<br />
|
|
148
|
-
<
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
selectedVariant={selectedVariant}
|
|
153
|
-
variants={[]}
|
|
154
|
-
/>
|
|
155
|
-
}
|
|
156
|
-
>
|
|
157
|
-
<Await
|
|
158
|
-
errorElement="There was a problem loading product variants"
|
|
159
|
-
resolve={variants}
|
|
160
|
-
>
|
|
161
|
-
{(data) => (
|
|
162
|
-
<ProductForm
|
|
163
|
-
product={product}
|
|
164
|
-
selectedVariant={selectedVariant}
|
|
165
|
-
variants={data?.product?.variants.nodes || []}
|
|
166
|
-
/>
|
|
167
|
-
)}
|
|
168
|
-
</Await>
|
|
169
|
-
</Suspense>
|
|
110
|
+
<ProductForm
|
|
111
|
+
productOptions={productOptions}
|
|
112
|
+
selectedVariant={selectedVariant}
|
|
113
|
+
/>
|
|
170
114
|
<br />
|
|
171
115
|
<br />
|
|
172
116
|
<p>
|
|
@@ -240,19 +184,30 @@ const PRODUCT_FRAGMENT = `#graphql
|
|
|
240
184
|
handle
|
|
241
185
|
descriptionHtml
|
|
242
186
|
description
|
|
187
|
+
encodedVariantExistence
|
|
188
|
+
encodedVariantAvailability
|
|
243
189
|
options {
|
|
244
190
|
name
|
|
245
191
|
optionValues {
|
|
246
192
|
name
|
|
193
|
+
firstSelectableVariant {
|
|
194
|
+
...ProductVariant
|
|
195
|
+
}
|
|
196
|
+
swatch {
|
|
197
|
+
color
|
|
198
|
+
image {
|
|
199
|
+
previewImage {
|
|
200
|
+
url
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
247
204
|
}
|
|
248
205
|
}
|
|
249
|
-
|
|
206
|
+
selectedOrFirstAvailableVariant(selectedOptions: $selectedOptions, ignoreUnknownOptions: true, caseInsensitiveMatch: true) {
|
|
250
207
|
...ProductVariant
|
|
251
208
|
}
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
...ProductVariant
|
|
255
|
-
}
|
|
209
|
+
adjacentVariants (selectedOptions: $selectedOptions) {
|
|
210
|
+
...ProductVariant
|
|
256
211
|
}
|
|
257
212
|
seo {
|
|
258
213
|
description
|
|
@@ -275,27 +230,3 @@ const PRODUCT_QUERY = `#graphql
|
|
|
275
230
|
}
|
|
276
231
|
${PRODUCT_FRAGMENT}
|
|
277
232
|
` as const;
|
|
278
|
-
|
|
279
|
-
const PRODUCT_VARIANTS_FRAGMENT = `#graphql
|
|
280
|
-
fragment ProductVariants on Product {
|
|
281
|
-
variants(first: 250) {
|
|
282
|
-
nodes {
|
|
283
|
-
...ProductVariant
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
${PRODUCT_VARIANT_FRAGMENT}
|
|
288
|
-
` as const;
|
|
289
|
-
|
|
290
|
-
const VARIANTS_QUERY = `#graphql
|
|
291
|
-
${PRODUCT_VARIANTS_FRAGMENT}
|
|
292
|
-
query ProductVariants(
|
|
293
|
-
$country: CountryCode
|
|
294
|
-
$language: LanguageCode
|
|
295
|
-
$handle: String!
|
|
296
|
-
) @inContext(country: $country, language: $language) {
|
|
297
|
-
product(handle: $handle) {
|
|
298
|
-
...ProductVariants
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
` as const;
|