@shopify/cli-hydrogen 10.0.0 → 10.0.2
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 +87 -40
- package/dist/assets/hydrogen/starter/app/components/CartLineItem.tsx +15 -0
- package/dist/assets/hydrogen/starter/app/components/CartMain.tsx +1 -1
- package/dist/assets/hydrogen/starter/app/components/ProductItem.tsx +44 -0
- package/dist/assets/hydrogen/starter/app/lib/redirect.ts +23 -0
- package/dist/assets/hydrogen/starter/app/routes/_index.tsx +8 -23
- package/dist/assets/hydrogen/starter/app/routes/blogs.$blogHandle.$articleHandle.tsx +22 -2
- package/dist/assets/hydrogen/starter/app/routes/blogs.$blogHandle._index.tsx +4 -0
- package/dist/assets/hydrogen/starter/app/routes/collections.$handle.tsx +7 -41
- package/dist/assets/hydrogen/starter/app/routes/collections.all.tsx +11 -44
- package/dist/assets/hydrogen/starter/app/routes/pages.$handle.tsx +9 -1
- package/dist/assets/hydrogen/starter/app/routes/products.$handle.tsx +5 -1
- package/dist/assets/hydrogen/starter/app/styles/app.css +15 -3
- package/dist/assets/hydrogen/starter/package.json +5 -5
- package/dist/assets/hydrogen/starter/storefrontapi.generated.d.ts +57 -36
- package/dist/assets/hydrogen/tailwind/package.json +4 -1
- package/oclif.manifest.json +1 -1
- package/package.json +6 -6
|
@@ -1,5 +1,52 @@
|
|
|
1
1
|
# skeleton
|
|
2
2
|
|
|
3
|
+
## 2025.4.0
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Moved the Cursor rules into more generic LLM prompt files. If you were using the Cursor rules, you will find the prompts in the `cookbook/llms` folder and they can be put into your `.cursor/rules` folder manually. LLM prompt files will be maintained moving forward, while previous Cursor rules will not be updated anymore. ([#2936](https://github.com/Shopify/hydrogen/pull/2936)) by [@ruggishop](https://github.com/ruggishop)
|
|
8
|
+
|
|
9
|
+
- Added bundles recipe ([#2915](https://github.com/Shopify/hydrogen/pull/2915)) by [@ruggishop](https://github.com/ruggishop)
|
|
10
|
+
|
|
11
|
+
- Update copy for subscriptions, combined listings, bundles recipes ([#2924](https://github.com/Shopify/hydrogen/pull/2924)) by [@ruggishop](https://github.com/ruggishop)
|
|
12
|
+
|
|
13
|
+
- Bump skeleton @shopify/cli and @shopify/mini-oxygen ([#2883](https://github.com/Shopify/hydrogen/pull/2883)) by [@juanpprieto](https://github.com/juanpprieto)
|
|
14
|
+
|
|
15
|
+
- Remove rules from the template. ([#2931](https://github.com/Shopify/hydrogen/pull/2931)) by [@ruggishop](https://github.com/ruggishop)
|
|
16
|
+
|
|
17
|
+
- Update SFAPI and CAAPI versions to 2025.04 ([#2886](https://github.com/Shopify/hydrogen/pull/2886)) by [@juanpprieto](https://github.com/juanpprieto)
|
|
18
|
+
|
|
19
|
+
- Updated recipes: subscriptions, bundles, combined-listings. New recipe: markets. ([#2930](https://github.com/Shopify/hydrogen/pull/2930)) by [@ruggishop](https://github.com/ruggishop)
|
|
20
|
+
|
|
21
|
+
- Updated the subscriptions recipe to better display the purchase options. ([#2912](https://github.com/Shopify/hydrogen/pull/2912)) by [@ruggishop](https://github.com/ruggishop)
|
|
22
|
+
|
|
23
|
+
- Bump recipes with copy adjustments ([#2935](https://github.com/Shopify/hydrogen/pull/2935)) by [@ruggishop](https://github.com/ruggishop)
|
|
24
|
+
|
|
25
|
+
- Added a Combined Listings recipe. ([#2876](https://github.com/Shopify/hydrogen/pull/2876)) by [@ruggishop](https://github.com/ruggishop)
|
|
26
|
+
|
|
27
|
+
- Updated dependencies [[`af23e710`](https://github.com/Shopify/hydrogen/commit/af23e710dac83bb57498d9c2ef1d8bcf9df55d34), [`9d8a6644`](https://github.com/Shopify/hydrogen/commit/9d8a6644a5b67dca890c6687df390aee78fc85c3)]:
|
|
28
|
+
- @shopify/hydrogen@2025.4.0
|
|
29
|
+
|
|
30
|
+
## 2025.1.7
|
|
31
|
+
|
|
32
|
+
### Patch Changes
|
|
33
|
+
|
|
34
|
+
- Fix an issue with our starter template where duplicate content can exist on URLs that use internationalized handles. For example, if you have a product handle in english of `the-havoc` and translate it to `das-chaos` in German, duplicate content exists at both: ([#2821](https://github.com/Shopify/hydrogen/pull/2821)) by [@blittle](https://github.com/blittle)
|
|
35
|
+
|
|
36
|
+
1. https://hydrogen.shop/de-de/products/das-chaos
|
|
37
|
+
2. https://hydrogen.shop/de-de/products/the-havoc
|
|
38
|
+
|
|
39
|
+
We've changed the starter template to make the second redirect to the first.
|
|
40
|
+
|
|
41
|
+
- Added the Cursor rule for the subscriptions recipe. ([#2874](https://github.com/Shopify/hydrogen/pull/2874)) by [@ruggishop](https://github.com/ruggishop)
|
|
42
|
+
|
|
43
|
+
- Fix faulty truthiness check for cart quantity ([#2855](https://github.com/Shopify/hydrogen/pull/2855)) by [@frontsideair](https://github.com/frontsideair)
|
|
44
|
+
|
|
45
|
+
- Refactor ProductItem into a separate component ([#2872](https://github.com/Shopify/hydrogen/pull/2872)) by [@juanpprieto](https://github.com/juanpprieto)
|
|
46
|
+
|
|
47
|
+
- Updated dependencies [[`f80f3bc7`](https://github.com/Shopify/hydrogen/commit/f80f3bc7239b3ee6641cb468a17e15c77bb7815b), [`61ddf924`](https://github.com/Shopify/hydrogen/commit/61ddf92487524b3c04632ae2cfdaa2869a3ae02c), [`642bde4f`](https://github.com/Shopify/hydrogen/commit/642bde4f3df11511e125b013abd977618da25692)]:
|
|
48
|
+
- @shopify/hydrogen@2025.1.4
|
|
49
|
+
|
|
3
50
|
## 2025.1.6
|
|
4
51
|
|
|
5
52
|
### Patch Changes
|
|
@@ -117,13 +164,13 @@
|
|
|
117
164
|
1. Add a routes.ts file. This is your new Remix route configuration file.
|
|
118
165
|
|
|
119
166
|
```ts
|
|
120
|
-
import {
|
|
121
|
-
import {
|
|
122
|
-
import {
|
|
167
|
+
import {flatRoutes} from '@remix-run/fs-routes';
|
|
168
|
+
import {layout, type RouteConfig} from '@remix-run/route-config';
|
|
169
|
+
import {hydrogenRoutes} from '@shopify/hydrogen';
|
|
123
170
|
|
|
124
171
|
export default hydrogenRoutes([
|
|
125
172
|
// Your entire app reading from routes folder using Layout from layout.tsx
|
|
126
|
-
layout(
|
|
173
|
+
layout('./layout.tsx', await flatRoutes()),
|
|
127
174
|
]) satisfies RouteConfig;
|
|
128
175
|
```
|
|
129
176
|
|
|
@@ -714,25 +761,25 @@
|
|
|
714
761
|
8. Update the `ProductForm` component.
|
|
715
762
|
|
|
716
763
|
```tsx
|
|
717
|
-
import {
|
|
718
|
-
import {
|
|
764
|
+
import {Link, useNavigate} from '@remix-run/react';
|
|
765
|
+
import {type MappedProductOptions} from '@shopify/hydrogen';
|
|
719
766
|
import type {
|
|
720
767
|
Maybe,
|
|
721
768
|
ProductOptionValueSwatch,
|
|
722
|
-
} from
|
|
723
|
-
import {
|
|
724
|
-
import {
|
|
725
|
-
import type {
|
|
769
|
+
} from '@shopify/hydrogen/storefront-api-types';
|
|
770
|
+
import {AddToCartButton} from './AddToCartButton';
|
|
771
|
+
import {useAside} from './Aside';
|
|
772
|
+
import type {ProductFragment} from 'storefrontapi.generated';
|
|
726
773
|
|
|
727
774
|
export function ProductForm({
|
|
728
775
|
productOptions,
|
|
729
776
|
selectedVariant,
|
|
730
777
|
}: {
|
|
731
778
|
productOptions: MappedProductOptions[];
|
|
732
|
-
selectedVariant: ProductFragment[
|
|
779
|
+
selectedVariant: ProductFragment['selectedOrFirstAvailableVariant'];
|
|
733
780
|
}) {
|
|
734
781
|
const navigate = useNavigate();
|
|
735
|
-
const {
|
|
782
|
+
const {open} = useAside();
|
|
736
783
|
return (
|
|
737
784
|
<div className="product-form">
|
|
738
785
|
{productOptions.map((option) => (
|
|
@@ -766,8 +813,8 @@
|
|
|
766
813
|
to={`/products/${handle}?${variantUriQuery}`}
|
|
767
814
|
style={{
|
|
768
815
|
border: selected
|
|
769
|
-
?
|
|
770
|
-
:
|
|
816
|
+
? '1px solid black'
|
|
817
|
+
: '1px solid transparent',
|
|
771
818
|
opacity: available ? 1 : 0.3,
|
|
772
819
|
}}
|
|
773
820
|
>
|
|
@@ -784,13 +831,13 @@
|
|
|
784
831
|
<button
|
|
785
832
|
type="button"
|
|
786
833
|
className={`product-options-item${
|
|
787
|
-
exists && !selected ?
|
|
834
|
+
exists && !selected ? ' link' : ''
|
|
788
835
|
}`}
|
|
789
836
|
key={option.name + name}
|
|
790
837
|
style={{
|
|
791
838
|
border: selected
|
|
792
|
-
?
|
|
793
|
-
:
|
|
839
|
+
? '1px solid black'
|
|
840
|
+
: '1px solid transparent',
|
|
794
841
|
opacity: available ? 1 : 0.3,
|
|
795
842
|
}}
|
|
796
843
|
disabled={!exists}
|
|
@@ -814,7 +861,7 @@
|
|
|
814
861
|
<AddToCartButton
|
|
815
862
|
disabled={!selectedVariant || !selectedVariant.availableForSale}
|
|
816
863
|
onClick={() => {
|
|
817
|
-
open(
|
|
864
|
+
open('cart');
|
|
818
865
|
}}
|
|
819
866
|
lines={
|
|
820
867
|
selectedVariant
|
|
@@ -828,7 +875,7 @@
|
|
|
828
875
|
: []
|
|
829
876
|
}
|
|
830
877
|
>
|
|
831
|
-
{selectedVariant?.availableForSale ?
|
|
878
|
+
{selectedVariant?.availableForSale ? 'Add to cart' : 'Sold out'}
|
|
832
879
|
</AddToCartButton>
|
|
833
880
|
</div>
|
|
834
881
|
);
|
|
@@ -851,7 +898,7 @@
|
|
|
851
898
|
aria-label={name}
|
|
852
899
|
className="product-option-label-swatch"
|
|
853
900
|
style={{
|
|
854
|
-
backgroundColor: color ||
|
|
901
|
+
backgroundColor: color || 'transparent',
|
|
855
902
|
}}
|
|
856
903
|
>
|
|
857
904
|
{!!image && <img src={image} alt={name} />}
|
|
@@ -1352,21 +1399,21 @@
|
|
|
1352
1399
|
New `withCache.fetch` is for caching simple fetch requests. This method caches the responses if they are OK responses, and you can pass `shouldCacheResponse`, `cacheKey`, etc. to modify behavior. `data` is the consumed body of the response (we need to consume to cache it).
|
|
1353
1400
|
|
|
1354
1401
|
```ts
|
|
1355
|
-
const withCache = createWithCache({
|
|
1402
|
+
const withCache = createWithCache({cache, waitUntil, request});
|
|
1356
1403
|
|
|
1357
|
-
const {
|
|
1358
|
-
|
|
1404
|
+
const {data, response} = await withCache.fetch<{data: T; error: string}>(
|
|
1405
|
+
'my-cms.com/api',
|
|
1359
1406
|
{
|
|
1360
|
-
method:
|
|
1361
|
-
headers: {
|
|
1407
|
+
method: 'POST',
|
|
1408
|
+
headers: {'Content-type': 'application/json'},
|
|
1362
1409
|
body,
|
|
1363
1410
|
},
|
|
1364
1411
|
{
|
|
1365
1412
|
cacheStrategy: CacheLong(),
|
|
1366
1413
|
// Cache if there are no data errors or a specific data that make this result not suited for caching
|
|
1367
1414
|
shouldCacheResponse: (result) => !result?.error,
|
|
1368
|
-
cacheKey: [
|
|
1369
|
-
displayName:
|
|
1415
|
+
cacheKey: ['my-cms', body],
|
|
1416
|
+
displayName: 'My CMS query',
|
|
1370
1417
|
},
|
|
1371
1418
|
);
|
|
1372
1419
|
```
|
|
@@ -1942,9 +1989,9 @@
|
|
|
1942
1989
|
|
|
1943
1990
|
```tsx
|
|
1944
1991
|
// app/lib/root-data.ts
|
|
1945
|
-
import {
|
|
1946
|
-
import type {
|
|
1947
|
-
import type {
|
|
1992
|
+
import {useMatches} from '@remix-run/react';
|
|
1993
|
+
import type {SerializeFrom} from '@shopify/remix-oxygen';
|
|
1994
|
+
import type {loader} from '~/root';
|
|
1948
1995
|
|
|
1949
1996
|
/**
|
|
1950
1997
|
* Access the result of the root loader from a React component.
|
|
@@ -2106,10 +2153,10 @@
|
|
|
2106
2153
|
- This is an important fix to a bug with 404 routes and path-based i18n projects where some unknown routes would not properly render a 404. This fixes all new projects, but to fix existing projects, add a `($locale).tsx` route with the following contents: ([#1732](https://github.com/Shopify/hydrogen/pull/1732)) by [@blittle](https://github.com/blittle)
|
|
2107
2154
|
|
|
2108
2155
|
```ts
|
|
2109
|
-
import {
|
|
2156
|
+
import {type LoaderFunctionArgs} from '@remix-run/server-runtime';
|
|
2110
2157
|
|
|
2111
|
-
export async function loader({
|
|
2112
|
-
const {
|
|
2158
|
+
export async function loader({params, context}: LoaderFunctionArgs) {
|
|
2159
|
+
const {language, country} = context.storefront.i18n;
|
|
2113
2160
|
|
|
2114
2161
|
if (
|
|
2115
2162
|
params.locale &&
|
|
@@ -2117,7 +2164,7 @@
|
|
|
2117
2164
|
) {
|
|
2118
2165
|
// If the locale URL param is defined, yet we still are still at the default locale
|
|
2119
2166
|
// then the the locale param must be invalid, send to the 404 page
|
|
2120
|
-
throw new Response(null, {
|
|
2167
|
+
throw new Response(null, {status: 404});
|
|
2121
2168
|
}
|
|
2122
2169
|
|
|
2123
2170
|
return null;
|
|
@@ -2173,11 +2220,11 @@
|
|
|
2173
2220
|
```yaml
|
|
2174
2221
|
projects:
|
|
2175
2222
|
default:
|
|
2176
|
-
schema:
|
|
2223
|
+
schema: 'node_modules/@shopify/hydrogen/storefront.schema.json'
|
|
2177
2224
|
documents:
|
|
2178
|
-
-
|
|
2179
|
-
-
|
|
2180
|
-
-
|
|
2225
|
+
- '!*.d.ts'
|
|
2226
|
+
- '*.{ts,tsx,js,jsx}'
|
|
2227
|
+
- 'app/**/*.{ts,tsx,js,jsx}'
|
|
2181
2228
|
```
|
|
2182
2229
|
|
|
2183
2230
|
- Improve resiliency of `HydrogenSession` ([#1583](https://github.com/Shopify/hydrogen/pull/1583)) by [@blittle](https://github.com/blittle)
|
|
@@ -2392,8 +2439,8 @@
|
|
|
2392
2439
|
```ts
|
|
2393
2440
|
// root.tsx
|
|
2394
2441
|
|
|
2395
|
-
import {
|
|
2396
|
-
import {
|
|
2442
|
+
import {useMatches} from '@remix-run/react';
|
|
2443
|
+
import {type SerializeFrom} from '@shopify/remix-oxygen';
|
|
2397
2444
|
|
|
2398
2445
|
export const useRootLoaderData = () => {
|
|
2399
2446
|
const [root] = useMatches();
|
|
@@ -123,6 +123,7 @@ function CartLineRemoveButton({
|
|
|
123
123
|
}) {
|
|
124
124
|
return (
|
|
125
125
|
<CartForm
|
|
126
|
+
fetcherKey={getUpdateKey(lineIds)}
|
|
126
127
|
route="/cart"
|
|
127
128
|
action={CartForm.ACTIONS.LinesRemove}
|
|
128
129
|
inputs={{lineIds}}
|
|
@@ -141,8 +142,11 @@ function CartLineUpdateButton({
|
|
|
141
142
|
children: React.ReactNode;
|
|
142
143
|
lines: CartLineUpdateInput[];
|
|
143
144
|
}) {
|
|
145
|
+
const lineIds = lines.map((line) => line.id);
|
|
146
|
+
|
|
144
147
|
return (
|
|
145
148
|
<CartForm
|
|
149
|
+
fetcherKey={getUpdateKey(lineIds)}
|
|
146
150
|
route="/cart"
|
|
147
151
|
action={CartForm.ACTIONS.LinesUpdate}
|
|
148
152
|
inputs={{lines}}
|
|
@@ -151,3 +155,14 @@ function CartLineUpdateButton({
|
|
|
151
155
|
</CartForm>
|
|
152
156
|
);
|
|
153
157
|
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Returns a unique key for the update action. This is used to make sure actions modifying the same line
|
|
161
|
+
* items are not run concurrently, but cancel each other. For example, if the user clicks "Increase quantity"
|
|
162
|
+
* and "Decrease quantity" in rapid succession, the actions will cancel each other and only the last one will run.
|
|
163
|
+
* @param lineIds - line ids affected by the update
|
|
164
|
+
* @returns
|
|
165
|
+
*/
|
|
166
|
+
function getUpdateKey(lineIds: string[]) {
|
|
167
|
+
return [CartForm.ACTIONS.LinesUpdate, ...lineIds].join('-');
|
|
168
|
+
}
|
|
@@ -26,7 +26,7 @@ export function CartMain({layout, cart: originalCart}: CartMainProps) {
|
|
|
26
26
|
cart &&
|
|
27
27
|
Boolean(cart?.discountCodes?.filter((code) => code.applicable)?.length);
|
|
28
28
|
const className = `cart-main ${withDiscount ? 'with-discount' : ''}`;
|
|
29
|
-
const cartHasItems = cart?.totalQuantity
|
|
29
|
+
const cartHasItems = cart?.totalQuantity ? cart.totalQuantity > 0 : false;
|
|
30
30
|
|
|
31
31
|
return (
|
|
32
32
|
<div className={className}>
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import {Link} from '@remix-run/react';
|
|
2
|
+
import {Image, Money} from '@shopify/hydrogen';
|
|
3
|
+
import type {
|
|
4
|
+
ProductItemFragment,
|
|
5
|
+
CollectionItemFragment,
|
|
6
|
+
RecommendedProductFragment,
|
|
7
|
+
} from 'storefrontapi.generated';
|
|
8
|
+
import {useVariantUrl} from '~/lib/variants';
|
|
9
|
+
|
|
10
|
+
export function ProductItem({
|
|
11
|
+
product,
|
|
12
|
+
loading,
|
|
13
|
+
}: {
|
|
14
|
+
product:
|
|
15
|
+
| CollectionItemFragment
|
|
16
|
+
| ProductItemFragment
|
|
17
|
+
| RecommendedProductFragment;
|
|
18
|
+
loading?: 'eager' | 'lazy';
|
|
19
|
+
}) {
|
|
20
|
+
const variantUrl = useVariantUrl(product.handle);
|
|
21
|
+
const image = product.featuredImage;
|
|
22
|
+
return (
|
|
23
|
+
<Link
|
|
24
|
+
className="product-item"
|
|
25
|
+
key={product.id}
|
|
26
|
+
prefetch="intent"
|
|
27
|
+
to={variantUrl}
|
|
28
|
+
>
|
|
29
|
+
{image && (
|
|
30
|
+
<Image
|
|
31
|
+
alt={image.altText || product.title}
|
|
32
|
+
aspectRatio="1/1"
|
|
33
|
+
data={image}
|
|
34
|
+
loading={loading}
|
|
35
|
+
sizes="(min-width: 45em) 400px, 100vw"
|
|
36
|
+
/>
|
|
37
|
+
)}
|
|
38
|
+
<h4>{product.title}</h4>
|
|
39
|
+
<small>
|
|
40
|
+
<Money data={product.priceRange.minVariantPrice} />
|
|
41
|
+
</small>
|
|
42
|
+
</Link>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import {redirect} from '@shopify/remix-oxygen';
|
|
2
|
+
|
|
3
|
+
export function redirectIfHandleIsLocalized(
|
|
4
|
+
request: Request,
|
|
5
|
+
...localizedResources: Array<{
|
|
6
|
+
handle: string;
|
|
7
|
+
data: {handle: string} & unknown;
|
|
8
|
+
}>
|
|
9
|
+
) {
|
|
10
|
+
const url = new URL(request.url);
|
|
11
|
+
let shouldRedirect = false;
|
|
12
|
+
|
|
13
|
+
localizedResources.forEach(({handle, data}) => {
|
|
14
|
+
if (handle !== data.handle) {
|
|
15
|
+
url.pathname = url.pathname.replace(handle, data.handle);
|
|
16
|
+
shouldRedirect = true;
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
if (shouldRedirect) {
|
|
21
|
+
throw redirect(url.toString());
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -6,6 +6,7 @@ import type {
|
|
|
6
6
|
FeaturedCollectionFragment,
|
|
7
7
|
RecommendedProductsQuery,
|
|
8
8
|
} from 'storefrontapi.generated';
|
|
9
|
+
import {ProductItem} from '~/components/ProductItem';
|
|
9
10
|
|
|
10
11
|
export const meta: MetaFunction = () => {
|
|
11
12
|
return [{title: 'Hydrogen | Home'}];
|
|
@@ -101,21 +102,7 @@ function RecommendedProducts({
|
|
|
101
102
|
<div className="recommended-products-grid">
|
|
102
103
|
{response
|
|
103
104
|
? response.products.nodes.map((product) => (
|
|
104
|
-
<
|
|
105
|
-
key={product.id}
|
|
106
|
-
className="recommended-product"
|
|
107
|
-
to={`/products/${product.handle}`}
|
|
108
|
-
>
|
|
109
|
-
<Image
|
|
110
|
-
data={product.images.nodes[0]}
|
|
111
|
-
aspectRatio="1/1"
|
|
112
|
-
sizes="(min-width: 45em) 20vw, 50vw"
|
|
113
|
-
/>
|
|
114
|
-
<h4>{product.title}</h4>
|
|
115
|
-
<small>
|
|
116
|
-
<Money data={product.priceRange.minVariantPrice} />
|
|
117
|
-
</small>
|
|
118
|
-
</Link>
|
|
105
|
+
<ProductItem key={product.id} product={product} />
|
|
119
106
|
))
|
|
120
107
|
: null}
|
|
121
108
|
</div>
|
|
@@ -161,14 +148,12 @@ const RECOMMENDED_PRODUCTS_QUERY = `#graphql
|
|
|
161
148
|
currencyCode
|
|
162
149
|
}
|
|
163
150
|
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
height
|
|
171
|
-
}
|
|
151
|
+
featuredImage {
|
|
152
|
+
id
|
|
153
|
+
url
|
|
154
|
+
altText
|
|
155
|
+
width
|
|
156
|
+
height
|
|
172
157
|
}
|
|
173
158
|
}
|
|
174
159
|
query RecommendedProducts ($country: CountryCode, $language: LanguageCode)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {type LoaderFunctionArgs} from '@shopify/remix-oxygen';
|
|
2
2
|
import {useLoaderData, type MetaFunction} from '@remix-run/react';
|
|
3
3
|
import {Image} from '@shopify/hydrogen';
|
|
4
|
+
import {redirectIfHandleIsLocalized} from '~/lib/redirect';
|
|
4
5
|
|
|
5
6
|
export const meta: MetaFunction<typeof loader> = ({data}) => {
|
|
6
7
|
return [{title: `Hydrogen | ${data?.article.title ?? ''} article`}];
|
|
@@ -20,7 +21,11 @@ export async function loader(args: LoaderFunctionArgs) {
|
|
|
20
21
|
* Load data necessary for rendering content above the fold. This is the critical data
|
|
21
22
|
* needed to render the page. If it's unavailable, the whole page should 400 or 500 error.
|
|
22
23
|
*/
|
|
23
|
-
async function loadCriticalData({
|
|
24
|
+
async function loadCriticalData({
|
|
25
|
+
context,
|
|
26
|
+
request,
|
|
27
|
+
params,
|
|
28
|
+
}: LoaderFunctionArgs) {
|
|
24
29
|
const {blogHandle, articleHandle} = params;
|
|
25
30
|
|
|
26
31
|
if (!articleHandle || !blogHandle) {
|
|
@@ -38,6 +43,18 @@ async function loadCriticalData({context, params}: LoaderFunctionArgs) {
|
|
|
38
43
|
throw new Response(null, {status: 404});
|
|
39
44
|
}
|
|
40
45
|
|
|
46
|
+
redirectIfHandleIsLocalized(
|
|
47
|
+
request,
|
|
48
|
+
{
|
|
49
|
+
handle: articleHandle,
|
|
50
|
+
data: blog.articleByHandle,
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
handle: blogHandle,
|
|
54
|
+
data: blog,
|
|
55
|
+
},
|
|
56
|
+
);
|
|
57
|
+
|
|
41
58
|
const article = blog.articleByHandle;
|
|
42
59
|
|
|
43
60
|
return {article};
|
|
@@ -67,7 +84,8 @@ export default function Article() {
|
|
|
67
84
|
<h1>
|
|
68
85
|
{title}
|
|
69
86
|
<div>
|
|
70
|
-
{publishedDate} ·
|
|
87
|
+
<time dateTime={article.publishedAt}>{publishedDate}</time> ·{' '}
|
|
88
|
+
<address>{author?.name}</address>
|
|
71
89
|
</div>
|
|
72
90
|
</h1>
|
|
73
91
|
|
|
@@ -89,7 +107,9 @@ const ARTICLE_QUERY = `#graphql
|
|
|
89
107
|
$language: LanguageCode
|
|
90
108
|
) @inContext(language: $language, country: $country) {
|
|
91
109
|
blog(handle: $blogHandle) {
|
|
110
|
+
handle
|
|
92
111
|
articleByHandle(handle: $articleHandle) {
|
|
112
|
+
handle
|
|
93
113
|
title
|
|
94
114
|
contentHtml
|
|
95
115
|
publishedAt
|
|
@@ -3,6 +3,7 @@ import {Link, useLoaderData, type MetaFunction} from '@remix-run/react';
|
|
|
3
3
|
import {Image, getPaginationVariables} from '@shopify/hydrogen';
|
|
4
4
|
import type {ArticleItemFragment} from 'storefrontapi.generated';
|
|
5
5
|
import {PaginatedResourceSection} from '~/components/PaginatedResourceSection';
|
|
6
|
+
import {redirectIfHandleIsLocalized} from '~/lib/redirect';
|
|
6
7
|
|
|
7
8
|
export const meta: MetaFunction<typeof loader> = ({data}) => {
|
|
8
9
|
return [{title: `Hydrogen | ${data?.blog.title ?? ''} blog`}];
|
|
@@ -49,6 +50,8 @@ async function loadCriticalData({
|
|
|
49
50
|
throw new Response('Not found', {status: 404});
|
|
50
51
|
}
|
|
51
52
|
|
|
53
|
+
redirectIfHandleIsLocalized(request, {handle: params.blogHandle, data: blog});
|
|
54
|
+
|
|
52
55
|
return {blog};
|
|
53
56
|
}
|
|
54
57
|
|
|
@@ -128,6 +131,7 @@ const BLOGS_QUERY = `#graphql
|
|
|
128
131
|
) @inContext(language: $language) {
|
|
129
132
|
blog(handle: $blogHandle) {
|
|
130
133
|
title
|
|
134
|
+
handle
|
|
131
135
|
seo {
|
|
132
136
|
title
|
|
133
137
|
description
|
|
@@ -1,14 +1,9 @@
|
|
|
1
1
|
import {redirect, type LoaderFunctionArgs} from '@shopify/remix-oxygen';
|
|
2
|
-
import {useLoaderData,
|
|
3
|
-
import {
|
|
4
|
-
getPaginationVariables,
|
|
5
|
-
Image,
|
|
6
|
-
Money,
|
|
7
|
-
Analytics,
|
|
8
|
-
} from '@shopify/hydrogen';
|
|
9
|
-
import type {ProductItemFragment} from 'storefrontapi.generated';
|
|
10
|
-
import {useVariantUrl} from '~/lib/variants';
|
|
2
|
+
import {useLoaderData, type MetaFunction} from '@remix-run/react';
|
|
3
|
+
import {getPaginationVariables, Analytics} from '@shopify/hydrogen';
|
|
11
4
|
import {PaginatedResourceSection} from '~/components/PaginatedResourceSection';
|
|
5
|
+
import {redirectIfHandleIsLocalized} from '~/lib/redirect';
|
|
6
|
+
import {ProductItem} from '~/components/ProductItem';
|
|
12
7
|
|
|
13
8
|
export const meta: MetaFunction<typeof loader> = ({data}) => {
|
|
14
9
|
return [{title: `Hydrogen | ${data?.collection.title ?? ''} Collection`}];
|
|
@@ -56,6 +51,9 @@ async function loadCriticalData({
|
|
|
56
51
|
});
|
|
57
52
|
}
|
|
58
53
|
|
|
54
|
+
// The API handle might be localized, so redirect to the localized handle
|
|
55
|
+
redirectIfHandleIsLocalized(request, {handle, data: collection});
|
|
56
|
+
|
|
59
57
|
return {
|
|
60
58
|
collection,
|
|
61
59
|
};
|
|
@@ -101,38 +99,6 @@ export default function Collection() {
|
|
|
101
99
|
);
|
|
102
100
|
}
|
|
103
101
|
|
|
104
|
-
function ProductItem({
|
|
105
|
-
product,
|
|
106
|
-
loading,
|
|
107
|
-
}: {
|
|
108
|
-
product: ProductItemFragment;
|
|
109
|
-
loading?: 'eager' | 'lazy';
|
|
110
|
-
}) {
|
|
111
|
-
const variantUrl = useVariantUrl(product.handle);
|
|
112
|
-
return (
|
|
113
|
-
<Link
|
|
114
|
-
className="product-item"
|
|
115
|
-
key={product.id}
|
|
116
|
-
prefetch="intent"
|
|
117
|
-
to={variantUrl}
|
|
118
|
-
>
|
|
119
|
-
{product.featuredImage && (
|
|
120
|
-
<Image
|
|
121
|
-
alt={product.featuredImage.altText || product.title}
|
|
122
|
-
aspectRatio="1/1"
|
|
123
|
-
data={product.featuredImage}
|
|
124
|
-
loading={loading}
|
|
125
|
-
sizes="(min-width: 45em) 400px, 100vw"
|
|
126
|
-
/>
|
|
127
|
-
)}
|
|
128
|
-
<h4>{product.title}</h4>
|
|
129
|
-
<small>
|
|
130
|
-
<Money data={product.priceRange.minVariantPrice} />
|
|
131
|
-
</small>
|
|
132
|
-
</Link>
|
|
133
|
-
);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
102
|
const PRODUCT_ITEM_FRAGMENT = `#graphql
|
|
137
103
|
fragment MoneyProductItem on MoneyV2 {
|
|
138
104
|
amount
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import {type LoaderFunctionArgs} from '@shopify/remix-oxygen';
|
|
2
|
-
import {useLoaderData,
|
|
3
|
-
import {getPaginationVariables
|
|
4
|
-
import type {ProductItemFragment} from 'storefrontapi.generated';
|
|
5
|
-
import {useVariantUrl} from '~/lib/variants';
|
|
2
|
+
import {useLoaderData, type MetaFunction} from '@remix-run/react';
|
|
3
|
+
import {getPaginationVariables} from '@shopify/hydrogen';
|
|
6
4
|
import {PaginatedResourceSection} from '~/components/PaginatedResourceSection';
|
|
5
|
+
import {ProductItem} from '~/components/ProductItem';
|
|
7
6
|
|
|
8
7
|
export const meta: MetaFunction<typeof loader> = () => {
|
|
9
8
|
return [{title: `Hydrogen | Products`}];
|
|
@@ -69,44 +68,12 @@ export default function Collection() {
|
|
|
69
68
|
);
|
|
70
69
|
}
|
|
71
70
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
loading,
|
|
75
|
-
}: {
|
|
76
|
-
product: ProductItemFragment;
|
|
77
|
-
loading?: 'eager' | 'lazy';
|
|
78
|
-
}) {
|
|
79
|
-
const variantUrl = useVariantUrl(product.handle);
|
|
80
|
-
return (
|
|
81
|
-
<Link
|
|
82
|
-
className="product-item"
|
|
83
|
-
key={product.id}
|
|
84
|
-
prefetch="intent"
|
|
85
|
-
to={variantUrl}
|
|
86
|
-
>
|
|
87
|
-
{product.featuredImage && (
|
|
88
|
-
<Image
|
|
89
|
-
alt={product.featuredImage.altText || product.title}
|
|
90
|
-
aspectRatio="1/1"
|
|
91
|
-
data={product.featuredImage}
|
|
92
|
-
loading={loading}
|
|
93
|
-
sizes="(min-width: 45em) 400px, 100vw"
|
|
94
|
-
/>
|
|
95
|
-
)}
|
|
96
|
-
<h4>{product.title}</h4>
|
|
97
|
-
<small>
|
|
98
|
-
<Money data={product.priceRange.minVariantPrice} />
|
|
99
|
-
</small>
|
|
100
|
-
</Link>
|
|
101
|
-
);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
const PRODUCT_ITEM_FRAGMENT = `#graphql
|
|
105
|
-
fragment MoneyProductItem on MoneyV2 {
|
|
71
|
+
const COLLECTION_ITEM_FRAGMENT = `#graphql
|
|
72
|
+
fragment MoneyCollectionItem on MoneyV2 {
|
|
106
73
|
amount
|
|
107
74
|
currencyCode
|
|
108
75
|
}
|
|
109
|
-
fragment
|
|
76
|
+
fragment CollectionItem on Product {
|
|
110
77
|
id
|
|
111
78
|
handle
|
|
112
79
|
title
|
|
@@ -119,16 +86,16 @@ const PRODUCT_ITEM_FRAGMENT = `#graphql
|
|
|
119
86
|
}
|
|
120
87
|
priceRange {
|
|
121
88
|
minVariantPrice {
|
|
122
|
-
...
|
|
89
|
+
...MoneyCollectionItem
|
|
123
90
|
}
|
|
124
91
|
maxVariantPrice {
|
|
125
|
-
...
|
|
92
|
+
...MoneyCollectionItem
|
|
126
93
|
}
|
|
127
94
|
}
|
|
128
95
|
}
|
|
129
96
|
` as const;
|
|
130
97
|
|
|
131
|
-
// NOTE: https://shopify.dev/docs/api/storefront/
|
|
98
|
+
// NOTE: https://shopify.dev/docs/api/storefront/latest/objects/product
|
|
132
99
|
const CATALOG_QUERY = `#graphql
|
|
133
100
|
query Catalog(
|
|
134
101
|
$country: CountryCode
|
|
@@ -140,7 +107,7 @@ const CATALOG_QUERY = `#graphql
|
|
|
140
107
|
) @inContext(country: $country, language: $language) {
|
|
141
108
|
products(first: $first, last: $last, before: $startCursor, after: $endCursor) {
|
|
142
109
|
nodes {
|
|
143
|
-
...
|
|
110
|
+
...CollectionItem
|
|
144
111
|
}
|
|
145
112
|
pageInfo {
|
|
146
113
|
hasPreviousPage
|
|
@@ -150,5 +117,5 @@ const CATALOG_QUERY = `#graphql
|
|
|
150
117
|
}
|
|
151
118
|
}
|
|
152
119
|
}
|
|
153
|
-
${
|
|
120
|
+
${COLLECTION_ITEM_FRAGMENT}
|
|
154
121
|
` as const;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {type LoaderFunctionArgs} from '@shopify/remix-oxygen';
|
|
2
2
|
import {useLoaderData, type MetaFunction} from '@remix-run/react';
|
|
3
|
+
import {redirectIfHandleIsLocalized} from '~/lib/redirect';
|
|
3
4
|
|
|
4
5
|
export const meta: MetaFunction<typeof loader> = ({data}) => {
|
|
5
6
|
return [{title: `Hydrogen | ${data?.page.title ?? ''}`}];
|
|
@@ -19,7 +20,11 @@ export async function loader(args: LoaderFunctionArgs) {
|
|
|
19
20
|
* Load data necessary for rendering content above the fold. This is the critical data
|
|
20
21
|
* needed to render the page. If it's unavailable, the whole page should 400 or 500 error.
|
|
21
22
|
*/
|
|
22
|
-
async function loadCriticalData({
|
|
23
|
+
async function loadCriticalData({
|
|
24
|
+
context,
|
|
25
|
+
request,
|
|
26
|
+
params,
|
|
27
|
+
}: LoaderFunctionArgs) {
|
|
23
28
|
if (!params.handle) {
|
|
24
29
|
throw new Error('Missing page handle');
|
|
25
30
|
}
|
|
@@ -37,6 +42,8 @@ async function loadCriticalData({context, params}: LoaderFunctionArgs) {
|
|
|
37
42
|
throw new Response('Not Found', {status: 404});
|
|
38
43
|
}
|
|
39
44
|
|
|
45
|
+
redirectIfHandleIsLocalized(request, {handle: params.handle, data: page});
|
|
46
|
+
|
|
40
47
|
return {
|
|
41
48
|
page,
|
|
42
49
|
};
|
|
@@ -72,6 +79,7 @@ const PAGE_QUERY = `#graphql
|
|
|
72
79
|
)
|
|
73
80
|
@inContext(language: $language, country: $country) {
|
|
74
81
|
page(handle: $handle) {
|
|
82
|
+
handle
|
|
75
83
|
id
|
|
76
84
|
title
|
|
77
85
|
body
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {type LoaderFunctionArgs} from '@shopify/remix-oxygen';
|
|
1
|
+
import {redirect, type LoaderFunctionArgs} from '@shopify/remix-oxygen';
|
|
2
2
|
import {useLoaderData, type MetaFunction} from '@remix-run/react';
|
|
3
3
|
import {
|
|
4
4
|
getSelectedProductOptions,
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
import {ProductPrice} from '~/components/ProductPrice';
|
|
12
12
|
import {ProductImage} from '~/components/ProductImage';
|
|
13
13
|
import {ProductForm} from '~/components/ProductForm';
|
|
14
|
+
import {redirectIfHandleIsLocalized} from '~/lib/redirect';
|
|
14
15
|
|
|
15
16
|
export const meta: MetaFunction<typeof loader> = ({data}) => {
|
|
16
17
|
return [
|
|
@@ -59,6 +60,9 @@ async function loadCriticalData({
|
|
|
59
60
|
throw new Response(null, {status: 404});
|
|
60
61
|
}
|
|
61
62
|
|
|
63
|
+
// The API handle might be localized, so redirect to the localized handle
|
|
64
|
+
redirectIfHandleIsLocalized(request, {handle, data: product});
|
|
65
|
+
|
|
62
66
|
return {
|
|
63
67
|
product,
|
|
64
68
|
};
|
|
@@ -27,12 +27,17 @@ img {
|
|
|
27
27
|
* components/Aside
|
|
28
28
|
* --------------------------------------------------
|
|
29
29
|
*/
|
|
30
|
+
@media (max-width: 45em) {
|
|
31
|
+
html:has(.overlay.expanded) {
|
|
32
|
+
overflow: hidden;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
30
36
|
aside {
|
|
31
37
|
background: var(--color-light);
|
|
32
38
|
box-shadow: 0 0 50px rgba(0, 0, 0, 0.3);
|
|
33
39
|
height: 100vh;
|
|
34
|
-
|
|
35
|
-
min-width: var(--aside-width);
|
|
40
|
+
width: min(var(--aside-width), 100vw);
|
|
36
41
|
position: fixed;
|
|
37
42
|
right: calc(-1 * var(--aside-width));
|
|
38
43
|
top: 0;
|
|
@@ -201,6 +206,11 @@ button.reset:hover:not(:has(> *)) {
|
|
|
201
206
|
margin-left: auto;
|
|
202
207
|
}
|
|
203
208
|
|
|
209
|
+
.header-ctas > * {
|
|
210
|
+
min-width: fit-content;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
|
|
204
214
|
/*
|
|
205
215
|
* --------------------------------------------------
|
|
206
216
|
* components/Footer
|
|
@@ -212,14 +222,16 @@ button.reset:hover:not(:has(> *)) {
|
|
|
212
222
|
}
|
|
213
223
|
|
|
214
224
|
.footer-menu {
|
|
215
|
-
|
|
225
|
+
justify-content: center;
|
|
216
226
|
display: flex;
|
|
227
|
+
flex-wrap: wrap;
|
|
217
228
|
grid-gap: 1rem;
|
|
218
229
|
padding: 1rem;
|
|
219
230
|
}
|
|
220
231
|
|
|
221
232
|
.footer-menu a {
|
|
222
233
|
color: var(--color-light);
|
|
234
|
+
min-width: fit-content;
|
|
223
235
|
}
|
|
224
236
|
|
|
225
237
|
/*
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "skeleton",
|
|
3
3
|
"private": true,
|
|
4
4
|
"sideEffects": false,
|
|
5
|
-
"version": "2025.
|
|
5
|
+
"version": "2025.4.0",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"build": "shopify hydrogen build --codegen",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"@remix-run/react": "^2.16.1",
|
|
18
18
|
"@remix-run/server-runtime": "^2.16.1",
|
|
19
19
|
"graphql": "^16.10.0",
|
|
20
|
-
"@shopify/hydrogen": "2025.
|
|
20
|
+
"@shopify/hydrogen": "2025.4.0",
|
|
21
21
|
"@shopify/remix-oxygen": "^2.0.12",
|
|
22
22
|
"graphql-tag": "^2.12.6",
|
|
23
23
|
"isbot": "^5.1.22",
|
|
@@ -32,9 +32,9 @@
|
|
|
32
32
|
"@remix-run/dev": "^2.16.1",
|
|
33
33
|
"@remix-run/fs-routes": "^2.16.1",
|
|
34
34
|
"@remix-run/route-config": "^2.16.1",
|
|
35
|
-
"@shopify/cli": "~3.
|
|
35
|
+
"@shopify/cli": "~3.79.2",
|
|
36
36
|
"@shopify/hydrogen-codegen": "^0.3.3",
|
|
37
|
-
"@shopify/mini-oxygen": "^3.2.
|
|
37
|
+
"@shopify/mini-oxygen": "^3.2.1",
|
|
38
38
|
"@shopify/oxygen-workers-types": "^4.1.6",
|
|
39
39
|
"@shopify/prettier-config": "^1.1.2",
|
|
40
40
|
"@total-typescript/ts-reset": "^0.6.1",
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
"eslint-plugin-react-hooks": "^5.1.0",
|
|
56
56
|
"globals": "^15.14.0",
|
|
57
57
|
"typescript": "^5.2.2",
|
|
58
|
-
"vite": "^6.2.
|
|
58
|
+
"vite": "^6.2.4",
|
|
59
59
|
"vite-tsconfig-paths": "^4.3.1"
|
|
60
60
|
},
|
|
61
61
|
"engines": {
|
|
@@ -331,11 +331,9 @@ export type RecommendedProductFragment = Pick<
|
|
|
331
331
|
priceRange: {
|
|
332
332
|
minVariantPrice: Pick<StorefrontAPI.MoneyV2, 'amount' | 'currencyCode'>;
|
|
333
333
|
};
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
>;
|
|
338
|
-
};
|
|
334
|
+
featuredImage?: StorefrontAPI.Maybe<
|
|
335
|
+
Pick<StorefrontAPI.Image, 'id' | 'url' | 'altText' | 'width' | 'height'>
|
|
336
|
+
>;
|
|
339
337
|
};
|
|
340
338
|
|
|
341
339
|
export type RecommendedProductsQueryVariables = StorefrontAPI.Exact<{
|
|
@@ -353,14 +351,12 @@ export type RecommendedProductsQuery = {
|
|
|
353
351
|
'amount' | 'currencyCode'
|
|
354
352
|
>;
|
|
355
353
|
};
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
>;
|
|
363
|
-
};
|
|
354
|
+
featuredImage?: StorefrontAPI.Maybe<
|
|
355
|
+
Pick<
|
|
356
|
+
StorefrontAPI.Image,
|
|
357
|
+
'id' | 'url' | 'altText' | 'width' | 'height'
|
|
358
|
+
>
|
|
359
|
+
>;
|
|
364
360
|
}
|
|
365
361
|
>;
|
|
366
362
|
};
|
|
@@ -374,22 +370,29 @@ export type ArticleQueryVariables = StorefrontAPI.Exact<{
|
|
|
374
370
|
}>;
|
|
375
371
|
|
|
376
372
|
export type ArticleQuery = {
|
|
377
|
-
blog?: StorefrontAPI.Maybe<
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
373
|
+
blog?: StorefrontAPI.Maybe<
|
|
374
|
+
Pick<StorefrontAPI.Blog, 'handle'> & {
|
|
375
|
+
articleByHandle?: StorefrontAPI.Maybe<
|
|
376
|
+
Pick<
|
|
377
|
+
StorefrontAPI.Article,
|
|
378
|
+
'handle' | 'title' | 'contentHtml' | 'publishedAt'
|
|
379
|
+
> & {
|
|
380
|
+
author?: StorefrontAPI.Maybe<
|
|
381
|
+
Pick<StorefrontAPI.ArticleAuthor, 'name'>
|
|
382
|
+
>;
|
|
383
|
+
image?: StorefrontAPI.Maybe<
|
|
384
|
+
Pick<
|
|
385
|
+
StorefrontAPI.Image,
|
|
386
|
+
'id' | 'altText' | 'url' | 'width' | 'height'
|
|
387
|
+
>
|
|
388
|
+
>;
|
|
389
|
+
seo?: StorefrontAPI.Maybe<
|
|
390
|
+
Pick<StorefrontAPI.Seo, 'description' | 'title'>
|
|
391
|
+
>;
|
|
392
|
+
}
|
|
393
|
+
>;
|
|
394
|
+
}
|
|
395
|
+
>;
|
|
393
396
|
};
|
|
394
397
|
|
|
395
398
|
export type BlogQueryVariables = StorefrontAPI.Exact<{
|
|
@@ -407,7 +410,7 @@ export type BlogQueryVariables = StorefrontAPI.Exact<{
|
|
|
407
410
|
|
|
408
411
|
export type BlogQuery = {
|
|
409
412
|
blog?: StorefrontAPI.Maybe<
|
|
410
|
-
Pick<StorefrontAPI.Blog, 'title'> & {
|
|
413
|
+
Pick<StorefrontAPI.Blog, 'title' | 'handle'> & {
|
|
411
414
|
seo?: StorefrontAPI.Maybe<
|
|
412
415
|
Pick<StorefrontAPI.Seo, 'title' | 'description'>
|
|
413
416
|
>;
|
|
@@ -587,6 +590,24 @@ export type StoreCollectionsQuery = {
|
|
|
587
590
|
};
|
|
588
591
|
};
|
|
589
592
|
|
|
593
|
+
export type MoneyCollectionItemFragment = Pick<
|
|
594
|
+
StorefrontAPI.MoneyV2,
|
|
595
|
+
'amount' | 'currencyCode'
|
|
596
|
+
>;
|
|
597
|
+
|
|
598
|
+
export type CollectionItemFragment = Pick<
|
|
599
|
+
StorefrontAPI.Product,
|
|
600
|
+
'id' | 'handle' | 'title'
|
|
601
|
+
> & {
|
|
602
|
+
featuredImage?: StorefrontAPI.Maybe<
|
|
603
|
+
Pick<StorefrontAPI.Image, 'id' | 'altText' | 'url' | 'width' | 'height'>
|
|
604
|
+
>;
|
|
605
|
+
priceRange: {
|
|
606
|
+
minVariantPrice: Pick<StorefrontAPI.MoneyV2, 'amount' | 'currencyCode'>;
|
|
607
|
+
maxVariantPrice: Pick<StorefrontAPI.MoneyV2, 'amount' | 'currencyCode'>;
|
|
608
|
+
};
|
|
609
|
+
};
|
|
610
|
+
|
|
590
611
|
export type CatalogQueryVariables = StorefrontAPI.Exact<{
|
|
591
612
|
country?: StorefrontAPI.InputMaybe<StorefrontAPI.CountryCode>;
|
|
592
613
|
language?: StorefrontAPI.InputMaybe<StorefrontAPI.LanguageCode>;
|
|
@@ -637,7 +658,7 @@ export type PageQueryVariables = StorefrontAPI.Exact<{
|
|
|
637
658
|
|
|
638
659
|
export type PageQuery = {
|
|
639
660
|
page?: StorefrontAPI.Maybe<
|
|
640
|
-
Pick<StorefrontAPI.Page, 'id' | 'title' | 'body'> & {
|
|
661
|
+
Pick<StorefrontAPI.Page, 'handle' | 'id' | 'title' | 'body'> & {
|
|
641
662
|
seo?: StorefrontAPI.Maybe<
|
|
642
663
|
Pick<StorefrontAPI.Seo, 'description' | 'title'>
|
|
643
664
|
>;
|
|
@@ -1181,15 +1202,15 @@ interface GeneratedQueryTypes {
|
|
|
1181
1202
|
return: FeaturedCollectionQuery;
|
|
1182
1203
|
variables: FeaturedCollectionQueryVariables;
|
|
1183
1204
|
};
|
|
1184
|
-
'#graphql\n fragment RecommendedProduct on Product {\n id\n title\n handle\n priceRange {\n minVariantPrice {\n amount\n currencyCode\n }\n }\n
|
|
1205
|
+
'#graphql\n fragment RecommendedProduct on Product {\n id\n title\n handle\n priceRange {\n minVariantPrice {\n amount\n currencyCode\n }\n }\n featuredImage {\n id\n url\n altText\n width\n height\n }\n }\n query RecommendedProducts ($country: CountryCode, $language: LanguageCode)\n @inContext(country: $country, language: $language) {\n products(first: 4, sortKey: UPDATED_AT, reverse: true) {\n nodes {\n ...RecommendedProduct\n }\n }\n }\n': {
|
|
1185
1206
|
return: RecommendedProductsQuery;
|
|
1186
1207
|
variables: RecommendedProductsQueryVariables;
|
|
1187
1208
|
};
|
|
1188
|
-
'#graphql\n query Article(\n $articleHandle: String!\n $blogHandle: String!\n $country: CountryCode\n $language: LanguageCode\n ) @inContext(language: $language, country: $country) {\n blog(handle: $blogHandle) {\n articleByHandle(handle: $articleHandle) {\n title\n contentHtml\n publishedAt\n author: authorV2 {\n name\n }\n image {\n id\n altText\n url\n width\n height\n }\n seo {\n description\n title\n }\n }\n }\n }\n': {
|
|
1209
|
+
'#graphql\n query Article(\n $articleHandle: String!\n $blogHandle: String!\n $country: CountryCode\n $language: LanguageCode\n ) @inContext(language: $language, country: $country) {\n blog(handle: $blogHandle) {\n handle\n articleByHandle(handle: $articleHandle) {\n handle\n title\n contentHtml\n publishedAt\n author: authorV2 {\n name\n }\n image {\n id\n altText\n url\n width\n height\n }\n seo {\n description\n title\n }\n }\n }\n }\n': {
|
|
1189
1210
|
return: ArticleQuery;
|
|
1190
1211
|
variables: ArticleQueryVariables;
|
|
1191
1212
|
};
|
|
1192
|
-
'#graphql\n query Blog(\n $language: LanguageCode\n $blogHandle: String!\n $first: Int\n $last: Int\n $startCursor: String\n $endCursor: String\n ) @inContext(language: $language) {\n blog(handle: $blogHandle) {\n title\n seo {\n title\n description\n }\n articles(\n first: $first,\n last: $last,\n before: $startCursor,\n after: $endCursor\n ) {\n nodes {\n ...ArticleItem\n }\n pageInfo {\n hasPreviousPage\n hasNextPage\n hasNextPage\n endCursor\n startCursor\n }\n\n }\n }\n }\n fragment ArticleItem on Article {\n author: authorV2 {\n name\n }\n contentHtml\n handle\n id\n image {\n id\n altText\n url\n width\n height\n }\n publishedAt\n title\n blog {\n handle\n }\n }\n': {
|
|
1213
|
+
'#graphql\n query Blog(\n $language: LanguageCode\n $blogHandle: String!\n $first: Int\n $last: Int\n $startCursor: String\n $endCursor: String\n ) @inContext(language: $language) {\n blog(handle: $blogHandle) {\n title\n handle\n seo {\n title\n description\n }\n articles(\n first: $first,\n last: $last,\n before: $startCursor,\n after: $endCursor\n ) {\n nodes {\n ...ArticleItem\n }\n pageInfo {\n hasPreviousPage\n hasNextPage\n hasNextPage\n endCursor\n startCursor\n }\n\n }\n }\n }\n fragment ArticleItem on Article {\n author: authorV2 {\n name\n }\n contentHtml\n handle\n id\n image {\n id\n altText\n url\n width\n height\n }\n publishedAt\n title\n blog {\n handle\n }\n }\n': {
|
|
1193
1214
|
return: BlogQuery;
|
|
1194
1215
|
variables: BlogQueryVariables;
|
|
1195
1216
|
};
|
|
@@ -1205,11 +1226,11 @@ interface GeneratedQueryTypes {
|
|
|
1205
1226
|
return: StoreCollectionsQuery;
|
|
1206
1227
|
variables: StoreCollectionsQueryVariables;
|
|
1207
1228
|
};
|
|
1208
|
-
'#graphql\n query Catalog(\n $country: CountryCode\n $language: LanguageCode\n $first: Int\n $last: Int\n $startCursor: String\n $endCursor: String\n ) @inContext(country: $country, language: $language) {\n products(first: $first, last: $last, before: $startCursor, after: $endCursor) {\n nodes {\n ...
|
|
1229
|
+
'#graphql\n query Catalog(\n $country: CountryCode\n $language: LanguageCode\n $first: Int\n $last: Int\n $startCursor: String\n $endCursor: String\n ) @inContext(country: $country, language: $language) {\n products(first: $first, last: $last, before: $startCursor, after: $endCursor) {\n nodes {\n ...CollectionItem\n }\n pageInfo {\n hasPreviousPage\n hasNextPage\n startCursor\n endCursor\n }\n }\n }\n #graphql\n fragment MoneyCollectionItem on MoneyV2 {\n amount\n currencyCode\n }\n fragment CollectionItem on Product {\n id\n handle\n title\n featuredImage {\n id\n altText\n url\n width\n height\n }\n priceRange {\n minVariantPrice {\n ...MoneyCollectionItem\n }\n maxVariantPrice {\n ...MoneyCollectionItem\n }\n }\n }\n\n': {
|
|
1209
1230
|
return: CatalogQuery;
|
|
1210
1231
|
variables: CatalogQueryVariables;
|
|
1211
1232
|
};
|
|
1212
|
-
'#graphql\n query Page(\n $language: LanguageCode,\n $country: CountryCode,\n $handle: String!\n )\n @inContext(language: $language, country: $country) {\n page(handle: $handle) {\n id\n title\n body\n seo {\n description\n title\n }\n }\n }\n': {
|
|
1233
|
+
'#graphql\n query Page(\n $language: LanguageCode,\n $country: CountryCode,\n $handle: String!\n )\n @inContext(language: $language, country: $country) {\n page(handle: $handle) {\n handle\n id\n title\n body\n seo {\n description\n title\n }\n }\n }\n': {
|
|
1213
1234
|
return: PageQuery;
|
|
1214
1235
|
variables: PageQueryVariables;
|
|
1215
1236
|
};
|
package/oclif.manifest.json
CHANGED
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"access": "public",
|
|
5
5
|
"@shopify:registry": "https://registry.npmjs.org"
|
|
6
6
|
},
|
|
7
|
-
"version": "10.0.
|
|
7
|
+
"version": "10.0.2",
|
|
8
8
|
"license": "MIT",
|
|
9
9
|
"type": "module",
|
|
10
10
|
"repository": {
|
|
@@ -33,15 +33,15 @@
|
|
|
33
33
|
"flame-chart-js": "2.3.2",
|
|
34
34
|
"get-port": "^7.0.0",
|
|
35
35
|
"type-fest": "^4.33.0",
|
|
36
|
-
"vite": "^6.2.
|
|
36
|
+
"vite": "^6.2.4",
|
|
37
37
|
"vitest": "^1.0.4"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"@ast-grep/napi": "0.11.0",
|
|
41
41
|
"@oclif/core": "3.26.5",
|
|
42
|
-
"@shopify/cli-kit": "^3.
|
|
43
|
-
"@shopify/oxygen-cli": "4.6.
|
|
44
|
-
"@shopify/plugin-cloudflare": "^3.
|
|
42
|
+
"@shopify/cli-kit": "^3.79.2",
|
|
43
|
+
"@shopify/oxygen-cli": "4.6.18",
|
|
44
|
+
"@shopify/plugin-cloudflare": "^3.78.1",
|
|
45
45
|
"ansi-escapes": "^6.2.0",
|
|
46
46
|
"chokidar": "3.5.3",
|
|
47
47
|
"cli-truncate": "^4.0.0",
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
"@graphql-codegen/cli": "^5.0.2",
|
|
63
63
|
"@remix-run/dev": "^2.16.1",
|
|
64
64
|
"@shopify/hydrogen-codegen": "^0.3.3",
|
|
65
|
-
"@shopify/mini-oxygen": "^3.2.
|
|
65
|
+
"@shopify/mini-oxygen": "^3.2.1",
|
|
66
66
|
"graphql-config": "^5.0.3",
|
|
67
67
|
"vite": "^5.1.0 || ^6.2.0"
|
|
68
68
|
},
|