shopify 3.93.1 → 3.93.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 +64 -0
- package/dist/assets/hydrogen/starter/app/components/Aside.tsx +4 -2
- package/dist/assets/hydrogen/starter/app/components/CartMain.tsx +6 -3
- package/dist/assets/hydrogen/starter/app/components/CartSummary.tsx +117 -23
- package/dist/assets/hydrogen/starter/app/components/Header.tsx +3 -3
- package/dist/assets/hydrogen/starter/app/components/PaginatedResourceSection.tsx +24 -4
- package/dist/assets/hydrogen/starter/app/components/ProductPrice.tsx +1 -1
- package/dist/assets/hydrogen/starter/app/routes/[robots.txt].tsx +14 -53
- package/dist/assets/hydrogen/starter/app/routes/_index.tsx +11 -4
- package/dist/assets/hydrogen/starter/app/routes/account.$.tsx +1 -1
- package/dist/assets/hydrogen/starter/app/routes/account.addresses.tsx +2 -2
- package/dist/assets/hydrogen/starter/app/routes/account.profile.tsx +1 -1
- package/dist/assets/hydrogen/starter/package.json +5 -5
- package/dist/assets/hydrogen/starter/storefrontapi.generated.d.ts +0 -11
- package/dist/assets/hydrogen/starter/vite.config.ts +1 -1
- package/dist/assets/hydrogen/vite/vite.config.js +1 -1
- package/dist/{chunk-WOERFYNW.js → chunk-4QL77VYJ.js} +2 -2
- package/dist/{chunk-TCRHJ3ZH.js → chunk-4VZV4LQX.js} +2 -2
- package/dist/{chunk-SVYSLNQH.js → chunk-5FCKEHCK.js} +177 -181
- package/dist/{chunk-VLDSGLBP.js → chunk-DDTYWTF2.js} +1 -1
- package/dist/{chunk-XV44IQDO.js → chunk-FYQIRCLV.js} +1 -1
- package/dist/{chunk-T57REQVZ.js → chunk-IG47ZDRU.js} +1 -1
- package/dist/{chunk-PB3UDYWH.js → chunk-MX6WWR5F.js} +1 -1
- package/dist/{chunk-P3ASN7B5.js → chunk-XVFYDYZA.js} +1 -1
- package/dist/{error-handler-54XVSWV5.js → error-handler-GZ2I7BG5.js} +1 -1
- package/dist/hooks/postrun.js +1 -1
- package/dist/hooks/prerun.js +1 -1
- package/dist/{http-proxy-node16-KBILO6A6.js → http-proxy-node16-DSQMBVDI.js} +1 -1
- package/dist/index.js +753 -753
- package/dist/{lib-EN3PX6IK.js → lib-GGVLMXY5.js} +1 -1
- package/dist/{local-JCUIPKND.js → local-WHQ3ZS4K.js} +1 -1
- package/dist/{node-package-manager-P7JQBCHZ.js → node-package-manager-6XMPTNUI.js} +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/{ui-XTVYPIVY.js → ui-GZ7DOSHP.js} +1 -1
- package/dist/{workerd-3GJRSBJN.js → workerd-LJU6AVMQ.js} +1 -1
- package/oclif.manifest.json +2 -3
- package/package.json +7 -7
- package/dist/assets/hydrogen/starter/app/routes/api.$version.[graphql.json].tsx +0 -14
- /package/dist/{morph-6DCXNO2H.js → morph-Q32V442A.js} +0 -0
|
@@ -1,5 +1,69 @@
|
|
|
1
1
|
# skeleton
|
|
2
2
|
|
|
3
|
+
## 2026.4.0
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- Update Storefront API and Customer Account API from 2026-01 to 2026-04. ([#3651](https://github.com/Shopify/hydrogen/pull/3651)) by [@itsjustriley](https://github.com/itsjustriley)
|
|
8
|
+
|
|
9
|
+
## Breaking changes
|
|
10
|
+
|
|
11
|
+
**JSON metafield values limited to 128KB**: When using API version 2026-04 or later, the Storefront API limits JSON type metafield writes to 128KB. This limit applies at the API level - Hydrogen passes through to the Storefront API without additional restriction. Apps that used JSON metafields before April 1, 2026 are grandfathered at the existing 2MB limit. Large metafield values continue to be readable by all API versions.
|
|
12
|
+
|
|
13
|
+
## New features
|
|
14
|
+
|
|
15
|
+
**New `MERCHANDISE_LINE_TRANSFORMERS_RUN_ERROR` cart error code**: Cart operations (`cartCreate`, `cartLinesAdd`, etc.) now return a specific `MERCHANDISE_LINE_TRANSFORMERS_RUN_ERROR` error code when a Cart Transform Function fails at runtime, instead of the previous generic `INVALID` error code. If you handle cart errors in your storefront code, you may want to add handling for this new code.
|
|
16
|
+
|
|
17
|
+
## Changelog links
|
|
18
|
+
- [Storefront API 2026-04 changelog](https://shopify.dev/changelog?filter=api&api_version=2026-04&api_type=storefront-graphql)
|
|
19
|
+
- [Customer Account API 2026-04 changelog](https://shopify.dev/changelog?filter=api&api_version=2026-04&api_type=customer-account-graphql)
|
|
20
|
+
|
|
21
|
+
### Patch Changes
|
|
22
|
+
|
|
23
|
+
- Updated dependencies [[`e7215ed0d7a74cacfa8c935f414c1cf32fc2ccd0`](https://github.com/Shopify/hydrogen/commit/e7215ed0d7a74cacfa8c935f414c1cf32fc2ccd0), [`92ab8e8b59807bbdb5dbecaa18629292d5566135`](https://github.com/Shopify/hydrogen/commit/92ab8e8b59807bbdb5dbecaa18629292d5566135), [`b0caa5c013380c7837f049f48da089a1671e2c6d`](https://github.com/Shopify/hydrogen/commit/b0caa5c013380c7837f049f48da089a1671e2c6d)]:
|
|
24
|
+
- @shopify/hydrogen@2026.4.0
|
|
25
|
+
|
|
26
|
+
## 2026.1.4
|
|
27
|
+
|
|
28
|
+
### Patch Changes
|
|
29
|
+
|
|
30
|
+
- Remove redundant Storefront API proxy route from skeleton template. The server now automatically proxies requests to `/api/:version/graphql.json` via `createRequestHandler` with `proxyStandardRoutes: true` (enabled by default since December 2025). ([#3572](https://github.com/Shopify/hydrogen/pull/3572)) by [@itsjustriley](https://github.com/itsjustriley)
|
|
31
|
+
|
|
32
|
+
Developers no longer need a manual route file for the tokenless Storefront API. Existing apps with this route can safely delete it - the server-level proxy provides the same functionality with better cookie forwarding and analytics integration.
|
|
33
|
+
|
|
34
|
+
- Updated dependencies [[`b0a75c1d759706931876f056662de2497cb3e688`](https://github.com/Shopify/hydrogen/commit/b0a75c1d759706931876f056662de2497cb3e688), [`a44ee3566b9bb9a7f43f05dfaae6f1f2ab1d548f`](https://github.com/Shopify/hydrogen/commit/a44ee3566b9bb9a7f43f05dfaae6f1f2ab1d548f)]:
|
|
35
|
+
- @shopify/hydrogen@2026.1.4
|
|
36
|
+
|
|
37
|
+
## 2026.1.3
|
|
38
|
+
|
|
39
|
+
### Patch Changes
|
|
40
|
+
|
|
41
|
+
- Improve screen reader experience for paginated product grids by hiding decorative arrow characters from assistive technology. ([#3557](https://github.com/Shopify/hydrogen/pull/3557)) by [@itsjustriley](https://github.com/itsjustriley)
|
|
42
|
+
|
|
43
|
+
- Fix broken `aria-label` on territory code input in address form. The label was the raw developer string `"territoryCode"` instead of a human-readable `"Country code"`. ([#3607](https://github.com/Shopify/hydrogen/pull/3607)) by [@itsjustriley](https://github.com/itsjustriley)
|
|
44
|
+
|
|
45
|
+
- Add aria-label to ProductPrice for improved screen reader accessibility ([#3558](https://github.com/Shopify/hydrogen/pull/3558)) by [@itsjustriley](https://github.com/itsjustriley)
|
|
46
|
+
|
|
47
|
+
- Updated dependencies [[`108243003a7f36349a446478f4e8ab0cade3e13a`](https://github.com/Shopify/hydrogen/commit/108243003a7f36349a446478f4e8ab0cade3e13a)]:
|
|
48
|
+
- @shopify/hydrogen@2026.1.3
|
|
49
|
+
|
|
50
|
+
## 2026.1.2
|
|
51
|
+
|
|
52
|
+
### Patch Changes
|
|
53
|
+
|
|
54
|
+
- Improve gift card accessibility in Skeleton template ([#3518](https://github.com/Shopify/hydrogen/pull/3518)) by [@itsjustriley](https://github.com/itsjustriley)
|
|
55
|
+
|
|
56
|
+
- Updated shopify/cli dependencies for cli-hydrogen ([#3553](https://github.com/Shopify/hydrogen/pull/3553)) by [@andguy95](https://github.com/andguy95)
|
|
57
|
+
|
|
58
|
+
- Updated loaders that used `customerAccount.handleAuthStatus()` to now await it. ([#3523](https://github.com/Shopify/hydrogen/pull/3523)) by [@fredericoo](https://github.com/fredericoo)
|
|
59
|
+
|
|
60
|
+
### Migration
|
|
61
|
+
|
|
62
|
+
If you call `handleAuthStatus()` in your own loaders, update those callsites to use `await`.
|
|
63
|
+
|
|
64
|
+
- Updated dependencies [[`16b0e7baca0dfd1fb330d12dac924c7593d169a8`](https://github.com/Shopify/hydrogen/commit/16b0e7baca0dfd1fb330d12dac924c7593d169a8), [`1c19b87782818dbdb4252754d2d44eb9a44fe50f`](https://github.com/Shopify/hydrogen/commit/1c19b87782818dbdb4252754d2d44eb9a44fe50f), [`029fa2d0e2297f67b37c650ba8e875ee5dee81b3`](https://github.com/Shopify/hydrogen/commit/029fa2d0e2297f67b37c650ba8e875ee5dee81b3)]:
|
|
65
|
+
- @shopify/hydrogen@2026.1.2
|
|
66
|
+
|
|
3
67
|
## 2026.1.1
|
|
4
68
|
|
|
5
69
|
### Patch Changes
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
useEffect,
|
|
6
6
|
useState,
|
|
7
7
|
} from 'react';
|
|
8
|
+
import {useId} from 'react';
|
|
8
9
|
|
|
9
10
|
type AsideType = 'search' | 'cart' | 'mobile' | 'closed';
|
|
10
11
|
type AsideContextValue = {
|
|
@@ -34,7 +35,7 @@ export function Aside({
|
|
|
34
35
|
}) {
|
|
35
36
|
const {type: activeType, close} = useAside();
|
|
36
37
|
const expanded = type === activeType;
|
|
37
|
-
|
|
38
|
+
const id = useId();
|
|
38
39
|
useEffect(() => {
|
|
39
40
|
const abortController = new AbortController();
|
|
40
41
|
|
|
@@ -57,11 +58,12 @@ export function Aside({
|
|
|
57
58
|
aria-modal
|
|
58
59
|
className={`overlay ${expanded ? 'expanded' : ''}`}
|
|
59
60
|
role="dialog"
|
|
61
|
+
aria-labelledby={id}
|
|
60
62
|
>
|
|
61
63
|
<button className="close-outside" onClick={close} />
|
|
62
64
|
<aside>
|
|
63
65
|
<header>
|
|
64
|
-
<h3>{heading}</h3>
|
|
66
|
+
<h3 id={id}>{heading}</h3>
|
|
65
67
|
<button className="close reset" onClick={close} aria-label="Close">
|
|
66
68
|
×
|
|
67
69
|
</button>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {useOptimisticCart
|
|
1
|
+
import {useOptimisticCart} from '@shopify/hydrogen';
|
|
2
2
|
import {Link} from 'react-router';
|
|
3
3
|
import type {CartApiQueryFragment} from 'storefrontapi.generated';
|
|
4
4
|
import {useAside} from '~/components/Aside';
|
|
@@ -50,7 +50,10 @@ export function CartMain({layout, cart: originalCart}: CartMainProps) {
|
|
|
50
50
|
const childrenMap = getLineItemChildrenMap(cart?.lines?.nodes ?? []);
|
|
51
51
|
|
|
52
52
|
return (
|
|
53
|
-
<
|
|
53
|
+
<section
|
|
54
|
+
className={className}
|
|
55
|
+
aria-label={layout === 'page' ? 'Cart page' : 'Cart drawer'}
|
|
56
|
+
>
|
|
54
57
|
<CartEmpty hidden={linesCount} layout={layout} />
|
|
55
58
|
<div className="cart-details">
|
|
56
59
|
<p id="cart-lines" className="sr-only">
|
|
@@ -79,7 +82,7 @@ export function CartMain({layout, cart: originalCart}: CartMainProps) {
|
|
|
79
82
|
</div>
|
|
80
83
|
{cartHasItems && <CartSummary cart={cart} layout={layout} />}
|
|
81
84
|
</div>
|
|
82
|
-
</
|
|
85
|
+
</section>
|
|
83
86
|
);
|
|
84
87
|
}
|
|
85
88
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type {CartApiQueryFragment} from 'storefrontapi.generated';
|
|
2
2
|
import type {CartLayout} from '~/components/CartMain';
|
|
3
3
|
import {CartForm, Money, type OptimisticCart} from '@shopify/hydrogen';
|
|
4
|
-
import {useEffect, useRef} from 'react';
|
|
4
|
+
import {useEffect, useId, useRef, useState} from 'react';
|
|
5
5
|
import {useFetcher} from 'react-router';
|
|
6
6
|
|
|
7
7
|
type CartSummaryProps = {
|
|
@@ -12,11 +12,16 @@ type CartSummaryProps = {
|
|
|
12
12
|
export function CartSummary({cart, layout}: CartSummaryProps) {
|
|
13
13
|
const className =
|
|
14
14
|
layout === 'page' ? 'cart-summary-page' : 'cart-summary-aside';
|
|
15
|
+
const summaryId = useId();
|
|
16
|
+
const discountsHeadingId = useId();
|
|
17
|
+
const discountCodeInputId = useId();
|
|
18
|
+
const giftCardHeadingId = useId();
|
|
19
|
+
const giftCardInputId = useId();
|
|
15
20
|
|
|
16
21
|
return (
|
|
17
|
-
<div aria-labelledby=
|
|
18
|
-
<h4>Totals</h4>
|
|
19
|
-
<dl className="cart-subtotal">
|
|
22
|
+
<div aria-labelledby={summaryId} className={className}>
|
|
23
|
+
<h4 id={summaryId}>Totals</h4>
|
|
24
|
+
<dl role="group" className="cart-subtotal">
|
|
20
25
|
<dt>Subtotal</dt>
|
|
21
26
|
<dd>
|
|
22
27
|
{cart?.cost?.subtotalAmount?.amount ? (
|
|
@@ -26,8 +31,16 @@ export function CartSummary({cart, layout}: CartSummaryProps) {
|
|
|
26
31
|
)}
|
|
27
32
|
</dd>
|
|
28
33
|
</dl>
|
|
29
|
-
<CartDiscounts
|
|
30
|
-
|
|
34
|
+
<CartDiscounts
|
|
35
|
+
discountCodes={cart?.discountCodes}
|
|
36
|
+
discountsHeadingId={discountsHeadingId}
|
|
37
|
+
discountCodeInputId={discountCodeInputId}
|
|
38
|
+
/>
|
|
39
|
+
<CartGiftCard
|
|
40
|
+
giftCardCodes={cart?.appliedGiftCards}
|
|
41
|
+
giftCardHeadingId={giftCardHeadingId}
|
|
42
|
+
giftCardInputId={giftCardInputId}
|
|
43
|
+
/>
|
|
31
44
|
<CartCheckoutActions checkoutUrl={cart?.checkoutUrl} />
|
|
32
45
|
</div>
|
|
33
46
|
);
|
|
@@ -48,8 +61,12 @@ function CartCheckoutActions({checkoutUrl}: {checkoutUrl?: string}) {
|
|
|
48
61
|
|
|
49
62
|
function CartDiscounts({
|
|
50
63
|
discountCodes,
|
|
64
|
+
discountsHeadingId,
|
|
65
|
+
discountCodeInputId,
|
|
51
66
|
}: {
|
|
52
67
|
discountCodes?: CartApiQueryFragment['discountCodes'];
|
|
68
|
+
discountsHeadingId: string;
|
|
69
|
+
discountCodeInputId: string;
|
|
53
70
|
}) {
|
|
54
71
|
const codes: string[] =
|
|
55
72
|
discountCodes
|
|
@@ -57,13 +74,17 @@ function CartDiscounts({
|
|
|
57
74
|
?.map(({code}) => code) || [];
|
|
58
75
|
|
|
59
76
|
return (
|
|
60
|
-
<
|
|
77
|
+
<section aria-label="Discounts">
|
|
61
78
|
{/* Have existing discount, display it with a remove option */}
|
|
62
79
|
<dl hidden={!codes.length}>
|
|
63
80
|
<div>
|
|
64
|
-
<dt>
|
|
81
|
+
<dt id={discountsHeadingId}>Discounts</dt>
|
|
65
82
|
<UpdateDiscountForm>
|
|
66
|
-
<div
|
|
83
|
+
<div
|
|
84
|
+
className="cart-discount"
|
|
85
|
+
role="group"
|
|
86
|
+
aria-labelledby={discountsHeadingId}
|
|
87
|
+
>
|
|
67
88
|
<code>{codes?.join(', ')}</code>
|
|
68
89
|
|
|
69
90
|
<button type="submit" aria-label="Remove discount">
|
|
@@ -77,11 +98,11 @@ function CartDiscounts({
|
|
|
77
98
|
{/* Show an input to apply a discount */}
|
|
78
99
|
<UpdateDiscountForm discountCodes={codes}>
|
|
79
100
|
<div>
|
|
80
|
-
<label htmlFor=
|
|
101
|
+
<label htmlFor={discountCodeInputId} className="sr-only">
|
|
81
102
|
Discount code
|
|
82
103
|
</label>
|
|
83
104
|
<input
|
|
84
|
-
id=
|
|
105
|
+
id={discountCodeInputId}
|
|
85
106
|
type="text"
|
|
86
107
|
name="discountCode"
|
|
87
108
|
placeholder="Discount code"
|
|
@@ -92,7 +113,7 @@ function CartDiscounts({
|
|
|
92
113
|
</button>
|
|
93
114
|
</div>
|
|
94
115
|
</UpdateDiscountForm>
|
|
95
|
-
</
|
|
116
|
+
</section>
|
|
96
117
|
);
|
|
97
118
|
}
|
|
98
119
|
|
|
@@ -118,52 +139,110 @@ function UpdateDiscountForm({
|
|
|
118
139
|
|
|
119
140
|
function CartGiftCard({
|
|
120
141
|
giftCardCodes,
|
|
142
|
+
giftCardHeadingId,
|
|
143
|
+
giftCardInputId,
|
|
121
144
|
}: {
|
|
122
145
|
giftCardCodes: CartApiQueryFragment['appliedGiftCards'] | undefined;
|
|
146
|
+
giftCardHeadingId: string;
|
|
147
|
+
giftCardInputId: string;
|
|
123
148
|
}) {
|
|
124
149
|
const giftCardCodeInput = useRef<HTMLInputElement>(null);
|
|
150
|
+
const removeButtonRefs = useRef<Map<string, HTMLButtonElement>>(new Map());
|
|
151
|
+
const previousCardIdsRef = useRef<string[]>([]);
|
|
125
152
|
const giftCardAddFetcher = useFetcher({key: 'gift-card-add'});
|
|
153
|
+
const [removedCardIndex, setRemovedCardIndex] = useState<number | null>(null);
|
|
126
154
|
|
|
127
155
|
useEffect(() => {
|
|
128
156
|
if (giftCardAddFetcher.data) {
|
|
129
|
-
giftCardCodeInput.current
|
|
157
|
+
if (giftCardCodeInput.current !== null) {
|
|
158
|
+
giftCardCodeInput.current.value = '';
|
|
159
|
+
}
|
|
130
160
|
}
|
|
131
161
|
}, [giftCardAddFetcher.data]);
|
|
132
162
|
|
|
163
|
+
useEffect(() => {
|
|
164
|
+
const currentCardIds = giftCardCodes?.map((card) => card.id) || [];
|
|
165
|
+
|
|
166
|
+
if (removedCardIndex !== null && giftCardCodes) {
|
|
167
|
+
const focusTargetIndex = Math.min(
|
|
168
|
+
removedCardIndex,
|
|
169
|
+
giftCardCodes.length - 1,
|
|
170
|
+
);
|
|
171
|
+
const focusTargetCard = giftCardCodes[focusTargetIndex];
|
|
172
|
+
const focusButton = focusTargetCard
|
|
173
|
+
? removeButtonRefs.current.get(focusTargetCard.id)
|
|
174
|
+
: null;
|
|
175
|
+
|
|
176
|
+
if (focusButton) {
|
|
177
|
+
focusButton.focus();
|
|
178
|
+
} else if (giftCardCodeInput.current) {
|
|
179
|
+
giftCardCodeInput.current.focus();
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
setRemovedCardIndex(null);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
previousCardIdsRef.current = currentCardIds;
|
|
186
|
+
}, [giftCardCodes, removedCardIndex]);
|
|
187
|
+
|
|
188
|
+
const handleRemoveClick = (cardId: string) => {
|
|
189
|
+
const index = previousCardIdsRef.current.indexOf(cardId);
|
|
190
|
+
if (index !== -1) {
|
|
191
|
+
setRemovedCardIndex(index);
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
|
|
133
195
|
return (
|
|
134
|
-
<
|
|
196
|
+
<section aria-label="Gift cards">
|
|
135
197
|
{giftCardCodes && giftCardCodes.length > 0 && (
|
|
136
198
|
<dl>
|
|
137
|
-
<dt>Applied Gift Card(s)</dt>
|
|
199
|
+
<dt id={giftCardHeadingId}>Applied Gift Card(s)</dt>
|
|
138
200
|
{giftCardCodes.map((giftCard) => (
|
|
139
|
-
<
|
|
140
|
-
<
|
|
201
|
+
<dd key={giftCard.id} className="cart-discount">
|
|
202
|
+
<RemoveGiftCardForm
|
|
203
|
+
giftCardId={giftCard.id}
|
|
204
|
+
lastCharacters={giftCard.lastCharacters}
|
|
205
|
+
onRemoveClick={() => handleRemoveClick(giftCard.id)}
|
|
206
|
+
buttonRef={(el: HTMLButtonElement | null) => {
|
|
207
|
+
if (el) {
|
|
208
|
+
removeButtonRefs.current.set(giftCard.id, el);
|
|
209
|
+
} else {
|
|
210
|
+
removeButtonRefs.current.delete(giftCard.id);
|
|
211
|
+
}
|
|
212
|
+
}}
|
|
213
|
+
>
|
|
141
214
|
<code>***{giftCard.lastCharacters}</code>
|
|
142
215
|
|
|
143
216
|
<Money data={giftCard.amountUsed} />
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
</div>
|
|
147
|
-
</RemoveGiftCardForm>
|
|
217
|
+
</RemoveGiftCardForm>
|
|
218
|
+
</dd>
|
|
148
219
|
))}
|
|
149
220
|
</dl>
|
|
150
221
|
)}
|
|
151
222
|
|
|
152
223
|
<AddGiftCardForm fetcherKey="gift-card-add">
|
|
153
224
|
<div>
|
|
225
|
+
<label htmlFor={giftCardInputId} className="sr-only">
|
|
226
|
+
Gift card code
|
|
227
|
+
</label>
|
|
154
228
|
<input
|
|
229
|
+
id={giftCardInputId}
|
|
155
230
|
type="text"
|
|
156
231
|
name="giftCardCode"
|
|
157
232
|
placeholder="Gift card code"
|
|
158
233
|
ref={giftCardCodeInput}
|
|
159
234
|
/>
|
|
160
235
|
|
|
161
|
-
<button
|
|
236
|
+
<button
|
|
237
|
+
type="submit"
|
|
238
|
+
disabled={giftCardAddFetcher.state !== 'idle'}
|
|
239
|
+
aria-label="Apply gift card code"
|
|
240
|
+
>
|
|
162
241
|
Apply
|
|
163
242
|
</button>
|
|
164
243
|
</div>
|
|
165
244
|
</AddGiftCardForm>
|
|
166
|
-
</
|
|
245
|
+
</section>
|
|
167
246
|
);
|
|
168
247
|
}
|
|
169
248
|
|
|
@@ -187,10 +266,16 @@ function AddGiftCardForm({
|
|
|
187
266
|
|
|
188
267
|
function RemoveGiftCardForm({
|
|
189
268
|
giftCardId,
|
|
269
|
+
lastCharacters,
|
|
190
270
|
children,
|
|
271
|
+
onRemoveClick,
|
|
272
|
+
buttonRef,
|
|
191
273
|
}: {
|
|
192
274
|
giftCardId: string;
|
|
275
|
+
lastCharacters: string;
|
|
193
276
|
children: React.ReactNode;
|
|
277
|
+
onRemoveClick?: () => void;
|
|
278
|
+
buttonRef?: (el: HTMLButtonElement | null) => void;
|
|
194
279
|
}) {
|
|
195
280
|
return (
|
|
196
281
|
<CartForm
|
|
@@ -201,6 +286,15 @@ function RemoveGiftCardForm({
|
|
|
201
286
|
}}
|
|
202
287
|
>
|
|
203
288
|
{children}
|
|
289
|
+
|
|
290
|
+
<button
|
|
291
|
+
type="submit"
|
|
292
|
+
aria-label={`Remove gift card ending in ${lastCharacters}`}
|
|
293
|
+
onClick={onRemoveClick}
|
|
294
|
+
ref={buttonRef}
|
|
295
|
+
>
|
|
296
|
+
Remove
|
|
297
|
+
</button>
|
|
204
298
|
</CartForm>
|
|
205
299
|
);
|
|
206
300
|
}
|
|
@@ -136,7 +136,7 @@ function SearchToggle() {
|
|
|
136
136
|
);
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
-
function CartBadge({count}: {count: number
|
|
139
|
+
function CartBadge({count}: {count: number}) {
|
|
140
140
|
const {open} = useAside();
|
|
141
141
|
const {publish, shop, cart, prevCart} = useAnalytics();
|
|
142
142
|
|
|
@@ -154,14 +154,14 @@ function CartBadge({count}: {count: number | null}) {
|
|
|
154
154
|
} as CartViewPayload);
|
|
155
155
|
}}
|
|
156
156
|
>
|
|
157
|
-
Cart
|
|
157
|
+
Cart <span aria-label={`(items: ${count})`}>{count}</span>
|
|
158
158
|
</a>
|
|
159
159
|
);
|
|
160
160
|
}
|
|
161
161
|
|
|
162
162
|
function CartToggle({cart}: Pick<HeaderProps, 'cart'>) {
|
|
163
163
|
return (
|
|
164
|
-
<Suspense fallback={<CartBadge count={
|
|
164
|
+
<Suspense fallback={<CartBadge count={0} />}>
|
|
165
165
|
<Await resolve={cart}>
|
|
166
166
|
<CartBanner />
|
|
167
167
|
</Await>
|
|
@@ -2,15 +2,17 @@ import * as React from 'react';
|
|
|
2
2
|
import {Pagination} from '@shopify/hydrogen';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* <PaginatedResourceSection
|
|
5
|
+
* <PaginatedResourceSection> encapsulates the previous and next pagination behaviors throughout your application.
|
|
6
6
|
*/
|
|
7
7
|
export function PaginatedResourceSection<NodesType>({
|
|
8
8
|
connection,
|
|
9
9
|
children,
|
|
10
|
+
ariaLabel,
|
|
10
11
|
resourcesClassName,
|
|
11
12
|
}: {
|
|
12
13
|
connection: React.ComponentProps<typeof Pagination<NodesType>>['connection'];
|
|
13
14
|
children: React.FunctionComponent<{node: NodesType; index: number}>;
|
|
15
|
+
ariaLabel?: string;
|
|
14
16
|
resourcesClassName?: string;
|
|
15
17
|
}) {
|
|
16
18
|
return (
|
|
@@ -23,15 +25,33 @@ export function PaginatedResourceSection<NodesType>({
|
|
|
23
25
|
return (
|
|
24
26
|
<div>
|
|
25
27
|
<PreviousLink>
|
|
26
|
-
{isLoading ?
|
|
28
|
+
{isLoading ? (
|
|
29
|
+
'Loading...'
|
|
30
|
+
) : (
|
|
31
|
+
<span>
|
|
32
|
+
<span aria-hidden="true">↑</span> Load previous
|
|
33
|
+
</span>
|
|
34
|
+
)}
|
|
27
35
|
</PreviousLink>
|
|
28
36
|
{resourcesClassName ? (
|
|
29
|
-
<div
|
|
37
|
+
<div
|
|
38
|
+
aria-label={ariaLabel}
|
|
39
|
+
className={resourcesClassName}
|
|
40
|
+
role={ariaLabel ? 'region' : undefined}
|
|
41
|
+
>
|
|
42
|
+
{resourcesMarkup}
|
|
43
|
+
</div>
|
|
30
44
|
) : (
|
|
31
45
|
resourcesMarkup
|
|
32
46
|
)}
|
|
33
47
|
<NextLink>
|
|
34
|
-
{isLoading ?
|
|
48
|
+
{isLoading ? (
|
|
49
|
+
'Loading...'
|
|
50
|
+
) : (
|
|
51
|
+
<span>
|
|
52
|
+
Load more <span aria-hidden="true">↓</span>
|
|
53
|
+
</span>
|
|
54
|
+
)}
|
|
35
55
|
</NextLink>
|
|
36
56
|
</div>
|
|
37
57
|
);
|
|
@@ -9,7 +9,7 @@ export function ProductPrice({
|
|
|
9
9
|
compareAtPrice?: MoneyV2 | null;
|
|
10
10
|
}) {
|
|
11
11
|
return (
|
|
12
|
-
<div className="product-price">
|
|
12
|
+
<div aria-label="Price" className="product-price" role="group">
|
|
13
13
|
{compareAtPrice ? (
|
|
14
14
|
<div className="product-price-on-sale">
|
|
15
15
|
{price ? <Money data={price} /> : null}
|
|
@@ -1,13 +1,8 @@
|
|
|
1
1
|
import type {Route} from './+types/[robots.txt]';
|
|
2
|
-
import {parseGid} from '@shopify/hydrogen';
|
|
3
2
|
|
|
4
|
-
export
|
|
3
|
+
export function loader({request}: Route.LoaderArgs) {
|
|
5
4
|
const url = new URL(request.url);
|
|
6
|
-
|
|
7
|
-
const {shop} = await context.storefront.query(ROBOTS_QUERY);
|
|
8
|
-
|
|
9
|
-
const shopId = parseGid(shop.id).id;
|
|
10
|
-
const body = robotsTxtData({url: url.origin, shopId});
|
|
5
|
+
const body = robotsTxtData({url: url.origin});
|
|
11
6
|
|
|
12
7
|
return new Response(body, {
|
|
13
8
|
status: 200,
|
|
@@ -19,35 +14,31 @@ export async function loader({request, context}: Route.LoaderArgs) {
|
|
|
19
14
|
});
|
|
20
15
|
}
|
|
21
16
|
|
|
22
|
-
function robotsTxtData({url
|
|
17
|
+
function robotsTxtData({url}: {url?: string}) {
|
|
23
18
|
const sitemapUrl = url ? `${url}/sitemap.xml` : undefined;
|
|
24
19
|
|
|
25
20
|
return `
|
|
26
21
|
User-agent: *
|
|
27
|
-
${generalDisallowRules({sitemapUrl
|
|
22
|
+
${generalDisallowRules({sitemapUrl})}
|
|
28
23
|
|
|
29
24
|
# Google adsbot ignores robots.txt unless specifically named!
|
|
30
25
|
User-agent: adsbot-google
|
|
31
|
-
Disallow: /
|
|
32
|
-
Disallow: /
|
|
33
|
-
Disallow: /
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
${shopId ? `Disallow: /${shopId}/orders` : ''}
|
|
37
|
-
Disallow: /*?*oseid=*
|
|
38
|
-
Disallow: /*preview_theme_id*
|
|
39
|
-
Disallow: /*preview_script_id*
|
|
26
|
+
Disallow: /cart
|
|
27
|
+
Disallow: /account
|
|
28
|
+
Disallow: /search
|
|
29
|
+
Allow: /search/
|
|
30
|
+
Disallow: /search/?*
|
|
40
31
|
|
|
41
32
|
User-agent: Nutch
|
|
42
33
|
Disallow: /
|
|
43
34
|
|
|
44
35
|
User-agent: AhrefsBot
|
|
45
36
|
Crawl-delay: 10
|
|
46
|
-
${generalDisallowRules({sitemapUrl
|
|
37
|
+
${generalDisallowRules({sitemapUrl})}
|
|
47
38
|
|
|
48
39
|
User-agent: AhrefsSiteAudit
|
|
49
40
|
Crawl-delay: 10
|
|
50
|
-
${generalDisallowRules({sitemapUrl
|
|
41
|
+
${generalDisallowRules({sitemapUrl})}
|
|
51
42
|
|
|
52
43
|
User-agent: MJ12bot
|
|
53
44
|
Crawl-Delay: 10
|
|
@@ -61,21 +52,8 @@ Crawl-delay: 1
|
|
|
61
52
|
* This function generates disallow rules that generally follow what Shopify's
|
|
62
53
|
* Online Store has as defaults for their robots.txt
|
|
63
54
|
*/
|
|
64
|
-
function generalDisallowRules({
|
|
65
|
-
|
|
66
|
-
sitemapUrl,
|
|
67
|
-
}: {
|
|
68
|
-
shopId?: string;
|
|
69
|
-
sitemapUrl?: string;
|
|
70
|
-
}) {
|
|
71
|
-
return `Disallow: /admin
|
|
72
|
-
Disallow: /cart
|
|
73
|
-
Disallow: /orders
|
|
74
|
-
Disallow: /checkouts/
|
|
75
|
-
Disallow: /checkout
|
|
76
|
-
${shopId ? `Disallow: /${shopId}/checkouts` : ''}
|
|
77
|
-
${shopId ? `Disallow: /${shopId}/orders` : ''}
|
|
78
|
-
Disallow: /carts
|
|
55
|
+
function generalDisallowRules({sitemapUrl}: {sitemapUrl?: string}) {
|
|
56
|
+
return `Disallow: /cart
|
|
79
57
|
Disallow: /account
|
|
80
58
|
Disallow: /collections/*sort_by*
|
|
81
59
|
Disallow: /*/collections/*sort_by*
|
|
@@ -85,33 +63,16 @@ Disallow: /collections/*%2b*
|
|
|
85
63
|
Disallow: /*/collections/*+*
|
|
86
64
|
Disallow: /*/collections/*%2B*
|
|
87
65
|
Disallow: /*/collections/*%2b*
|
|
88
|
-
Disallow:
|
|
66
|
+
Disallow: /*/collections/*filter*&*filter*
|
|
89
67
|
Disallow: /blogs/*+*
|
|
90
68
|
Disallow: /blogs/*%2B*
|
|
91
69
|
Disallow: /blogs/*%2b*
|
|
92
70
|
Disallow: /*/blogs/*+*
|
|
93
71
|
Disallow: /*/blogs/*%2B*
|
|
94
72
|
Disallow: /*/blogs/*%2b*
|
|
95
|
-
Disallow: /*?*oseid=*
|
|
96
|
-
Disallow: /*preview_theme_id*
|
|
97
|
-
Disallow: /*preview_script_id*
|
|
98
73
|
Disallow: /policies/
|
|
99
|
-
Disallow: /*/*?*ls=*&ls=*
|
|
100
|
-
Disallow: /*/*?*ls%3D*%3Fls%3D*
|
|
101
|
-
Disallow: /*/*?*ls%3d*%3fls%3d*
|
|
102
74
|
Disallow: /search
|
|
103
75
|
Allow: /search/
|
|
104
76
|
Disallow: /search/?*
|
|
105
|
-
Disallow: /apple-app-site-association
|
|
106
|
-
Disallow: /.well-known/shopify/monorail
|
|
107
77
|
${sitemapUrl ? `Sitemap: ${sitemapUrl}` : ''}`;
|
|
108
78
|
}
|
|
109
|
-
|
|
110
|
-
const ROBOTS_QUERY = `#graphql
|
|
111
|
-
query StoreRobots($country: CountryCode, $language: LanguageCode)
|
|
112
|
-
@inContext(country: $country, language: $language) {
|
|
113
|
-
shop {
|
|
114
|
-
id
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
` as const;
|
|
@@ -83,7 +83,11 @@ function FeaturedCollection({
|
|
|
83
83
|
>
|
|
84
84
|
{image && (
|
|
85
85
|
<div className="featured-collection-image">
|
|
86
|
-
<Image
|
|
86
|
+
<Image
|
|
87
|
+
data={image}
|
|
88
|
+
sizes="100vw"
|
|
89
|
+
alt={image.altText || collection.title}
|
|
90
|
+
/>
|
|
87
91
|
</div>
|
|
88
92
|
)}
|
|
89
93
|
<h1>{collection.title}</h1>
|
|
@@ -97,8 +101,11 @@ function RecommendedProducts({
|
|
|
97
101
|
products: Promise<RecommendedProductsQuery | null>;
|
|
98
102
|
}) {
|
|
99
103
|
return (
|
|
100
|
-
<
|
|
101
|
-
|
|
104
|
+
<section
|
|
105
|
+
className="recommended-products"
|
|
106
|
+
aria-labelledby="recommended-products"
|
|
107
|
+
>
|
|
108
|
+
<h2 id="recommended-products">Recommended Products</h2>
|
|
102
109
|
<Suspense fallback={<div>Loading...</div>}>
|
|
103
110
|
<Await resolve={products}>
|
|
104
111
|
{(response) => (
|
|
@@ -113,7 +120,7 @@ function RecommendedProducts({
|
|
|
113
120
|
</Await>
|
|
114
121
|
</Suspense>
|
|
115
122
|
<br />
|
|
116
|
-
</
|
|
123
|
+
</section>
|
|
117
124
|
);
|
|
118
125
|
}
|
|
119
126
|
|
|
@@ -3,7 +3,7 @@ import type {Route} from './+types/account.$';
|
|
|
3
3
|
|
|
4
4
|
// fallback wild card for all unauthenticated routes in account section
|
|
5
5
|
export async function loader({context}: Route.LoaderArgs) {
|
|
6
|
-
context.customerAccount.handleAuthStatus();
|
|
6
|
+
await context.customerAccount.handleAuthStatus();
|
|
7
7
|
|
|
8
8
|
return redirect('/account');
|
|
9
9
|
}
|
|
@@ -32,7 +32,7 @@ export const meta: Route.MetaFunction = () => {
|
|
|
32
32
|
};
|
|
33
33
|
|
|
34
34
|
export async function loader({context}: Route.LoaderArgs) {
|
|
35
|
-
context.customerAccount.handleAuthStatus();
|
|
35
|
+
await context.customerAccount.handleAuthStatus();
|
|
36
36
|
|
|
37
37
|
return {};
|
|
38
38
|
}
|
|
@@ -468,7 +468,7 @@ export function AddressForm({
|
|
|
468
468
|
/>
|
|
469
469
|
<label htmlFor="territoryCode">Country Code*</label>
|
|
470
470
|
<input
|
|
471
|
-
aria-label="
|
|
471
|
+
aria-label="Country code"
|
|
472
472
|
autoComplete="country"
|
|
473
473
|
defaultValue={address?.territoryCode ?? ''}
|
|
474
474
|
id="territoryCode"
|