@shopify/create-hydrogen 5.0.26 → 5.0.28

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.
@@ -6,7 +6,7 @@ import {getSchema} from '@shopify/hydrogen-codegen';
6
6
  * @see https://the-guild.dev/graphql/config/docs/user/usage
7
7
  * @type {IGraphQLConfig}
8
8
  */
9
- export default {
9
+ const graphqlConfig: IGraphQLConfig = {
10
10
  projects: {
11
11
  default: {
12
12
  schema: getSchema('storefront'),
@@ -24,4 +24,6 @@ export default {
24
24
 
25
25
  // Add your own GraphQL projects here for CMS, Shopify Admin API, etc.
26
26
  },
27
- } as IGraphQLConfig;
27
+ };
28
+
29
+ export default graphqlConfig;
@@ -1,5 +1,147 @@
1
1
  # skeleton
2
2
 
3
+ ## 2025.10.1
4
+
5
+ ### Major Changes
6
+
7
+ - Update Storefront API and Customer Account API to version 2025-10 ([#3430](https://github.com/Shopify/hydrogen/pull/3430)) by [@kdaviduik](https://github.com/kdaviduik)
8
+
9
+ ### Patch Changes
10
+
11
+ - Add support for Bun's text-based lockfile (`bun.lock`) introduced in Bun 1.2, and npm's shrinkwrap lockfile (`npm-shrinkwrap.json`), as alternatives to their respective primary lockfiles (`bun.lockb` and `package-lock.json`). ([#3430](https://github.com/Shopify/hydrogen/pull/3430)) by [@kdaviduik](https://github.com/kdaviduik)
12
+
13
+ - Add `cartGiftCardCodesAdd` mutation ([#3430](https://github.com/Shopify/hydrogen/pull/3430)) by [@kdaviduik](https://github.com/kdaviduik)
14
+
15
+ ## New Feature: cartGiftCardCodesAdd
16
+
17
+ The skeleton template has been updated to use the new `cartGiftCardCodesAdd` mutation:
18
+ - Removed `UpdateGiftCardForm` component from `CartSummary.tsx`
19
+ - Added `AddGiftCardForm` component using `CartForm.ACTIONS.GiftCardCodesAdd`
20
+
21
+ If you customized the gift card form in your project, you may want to migrate to the new `Add` action for simpler code.
22
+
23
+ ## Usage
24
+
25
+ ```typescript
26
+ import {CartForm} from '@shopify/hydrogen';
27
+
28
+ <CartForm action={CartForm.ACTIONS.GiftCardCodesAdd} inputs={{giftCardCodes: ['CODE1', 'CODE2']}}>
29
+ <button>Add Gift Cards</button>
30
+ </CartForm>
31
+ ```
32
+
33
+ Or with createCartHandler:
34
+
35
+ ```typescript
36
+ const cart = createCartHandler({storefront, getCartId, setCartId});
37
+ await cart.addGiftCardCodes(['SUMMER2025', 'WELCOME10']);
38
+ ```
39
+
40
+ - Add support for nested cart line items (warranties, gift wrapping, etc.) ([#3430](https://github.com/Shopify/hydrogen/pull/3430)) by [@kdaviduik](https://github.com/kdaviduik)
41
+
42
+ Storefront API 2025-10 introduces `parentRelationship` on cart line items, enabling parent-child relationships for add-ons. This update displays nested line items in the cart.
43
+
44
+ ### Changes
45
+ - Updates GraphQL fragments to include `parentRelationship` and `lineComponents` fields
46
+ - Updates `CartMain` and `CartLineItem` to render child line items with visual hierarchy
47
+
48
+ ### Note
49
+
50
+ This update focuses on **displaying** nested line items. To add both a product and its child (e.g., warranty) in a single action:
51
+
52
+ ```tsx
53
+ <AddToCartButton
54
+ lines={[
55
+ {merchandiseId: 'gid://shopify/ProductVariant/laptop-456', quantity: 1},
56
+ {
57
+ merchandiseId: 'gid://shopify/ProductVariant/warranty-123',
58
+ quantity: 1,
59
+ parent: {merchandiseId: 'gid://shopify/ProductVariant/laptop-456'},
60
+ },
61
+ ]}
62
+ >
63
+ Add to Cart with Warranty
64
+ </AddToCartButton>
65
+ ```
66
+
67
+ - Updated dependencies [[`722915130410086bc7af22215ba57ee77aa14156`](https://github.com/Shopify/hydrogen/commit/722915130410086bc7af22215ba57ee77aa14156)]:
68
+ - @shopify/hydrogen@2025.10.1
69
+
70
+ ## 2025.10.0
71
+
72
+ ### Patch Changes
73
+
74
+ - Updated dependencies [[`cd653456fbd1e7e1ab1f6fecff04c89a74b6cad9`](https://github.com/Shopify/hydrogen/commit/cd653456fbd1e7e1ab1f6fecff04c89a74b6cad9), [`24d26ad94e90ab0a859c274838f7f31e75a7808c`](https://github.com/Shopify/hydrogen/commit/24d26ad94e90ab0a859c274838f7f31e75a7808c), [`13a6f8987ea20d33a30a9c0329d7c11528b892ea`](https://github.com/Shopify/hydrogen/commit/13a6f8987ea20d33a30a9c0329d7c11528b892ea), [`403c1f5b6e266c3dfad30f7cfed229e3304570b0`](https://github.com/Shopify/hydrogen/commit/403c1f5b6e266c3dfad30f7cfed229e3304570b0), [`38f8a79625838a9cd4520b20c0db2e5d331f7d26`](https://github.com/Shopify/hydrogen/commit/38f8a79625838a9cd4520b20c0db2e5d331f7d26)]:
75
+ - @shopify/hydrogen@2026.0.0
76
+
77
+ ## 2025.7.3
78
+
79
+ ### Patch Changes
80
+
81
+ - Support OAuth parameters via URL query strings in skeleton login route ([#3391](https://github.com/Shopify/hydrogen/pull/3391)) by [@kdaviduik](https://github.com/kdaviduik)
82
+
83
+ The skeleton template's login route (`account_.login.tsx`) now reads OAuth parameters from the URL and forwards them to `customerAccount.login()`. This enables deep linking to the login page with pre-configured authentication options.
84
+
85
+ ### Supported Query Parameters
86
+
87
+ | Query Parameter | Description |
88
+ | ----------------- | ---------------------------------------------------------------------------------- |
89
+ | `acr_values` | Direct users to a specific login method (e.g., `provider:google` for social login) |
90
+ | `login_hint` | Pre-fill the email address field |
91
+ | `login_hint_mode` | When set to `submit` with `login_hint`, auto-submits the login form |
92
+ | `locale` | Display the login page in a specific language (e.g., `fr`, `zh-CN`) |
93
+
94
+ ### Usage Examples
95
+
96
+ ```
97
+ /account/login?login_hint=user@example.com
98
+ /account/login?login_hint=user@example.com&login_hint_mode=submit
99
+ /account/login?acr_values=provider:google
100
+ /account/login?locale=fr
101
+ ```
102
+
103
+ - Updated dependencies [[`7c077f5f21a595c0355873ac8073b716dfeaf4d0`](https://github.com/Shopify/hydrogen/commit/7c077f5f21a595c0355873ac8073b716dfeaf4d0), [`7c077f5f21a595c0355873ac8073b716dfeaf4d0`](https://github.com/Shopify/hydrogen/commit/7c077f5f21a595c0355873ac8073b716dfeaf4d0), [`7c077f5f21a595c0355873ac8073b716dfeaf4d0`](https://github.com/Shopify/hydrogen/commit/7c077f5f21a595c0355873ac8073b716dfeaf4d0)]:
104
+ - @shopify/hydrogen@2025.8.0
105
+
106
+ ## 2025.7.2
107
+
108
+ ### Patch Changes
109
+
110
+ - Updated dependencies [[`6d22e45d29e89a7a8dddfe3c06459d89e4590483`](https://github.com/Shopify/hydrogen/commit/6d22e45d29e89a7a8dddfe3c06459d89e4590483), [`702f966c8e60eba7434363c9012f12bed92a8e4d`](https://github.com/Shopify/hydrogen/commit/702f966c8e60eba7434363c9012f12bed92a8e4d)]:
111
+ - @shopify/hydrogen@2025.7.2
112
+
113
+ ## 2025.7.1
114
+
115
+ ### Patch Changes
116
+
117
+ - Update React Router to 7.12.0 with stabilized future flags ([#3346](https://github.com/Shopify/hydrogen/pull/3346)) by [@kdaviduik](https://github.com/kdaviduik)
118
+
119
+ This release uses React Router's newly stabilized future flags (`v8_splitRouteModules`, `v8_middleware`) instead of their unstable counterparts
120
+
121
+ - Moved server build in `server.ts` from a dynamic import to a static import to speed up first rendering during local development (2s => 200ms). ([#3331](https://github.com/Shopify/hydrogen/pull/3331)) by [@frandiox](https://github.com/frandiox)
122
+
123
+ ```diff
124
+ // server.ts
125
+
126
+ +import * as serverBuild from 'virtual:react-router/server-build';
127
+
128
+ const handleRequest = createRequestHandler({
129
+ - build: await import('virtual:react-router/server-build'),
130
+ + build: serverBuild,
131
+ ```
132
+
133
+ Updated ESLint config to allow `virtual:` imports:
134
+
135
+ ```diff
136
+ // eslint.config.js
137
+
138
+ rules: {
139
+ + 'import/no-unresolved': ['error', {ignore: ['^virtual:']}],
140
+ ```
141
+
142
+ - Updated dependencies [[`264e13349168f17cc1f096c84135d13d38cfc8df`](https://github.com/Shopify/hydrogen/commit/264e13349168f17cc1f096c84135d13d38cfc8df), [`6d5e3d371e7e02e15be17029a0f34daae53a978e`](https://github.com/Shopify/hydrogen/commit/6d5e3d371e7e02e15be17029a0f34daae53a978e), [`ee00f1025867c40d5f67fa89d4ffb215bf280e8f`](https://github.com/Shopify/hydrogen/commit/ee00f1025867c40d5f67fa89d4ffb215bf280e8f), [`5a38948133766e358c5f357f52562f6fdcfe7969`](https://github.com/Shopify/hydrogen/commit/5a38948133766e358c5f357f52562f6fdcfe7969), [`264e13349168f17cc1f096c84135d13d38cfc8df`](https://github.com/Shopify/hydrogen/commit/264e13349168f17cc1f096c84135d13d38cfc8df)]:
143
+ - @shopify/hydrogen@2025.7.1
144
+
3
145
  ## 2025.7.0
4
146
 
5
147
  ### Major Changes
@@ -1,69 +1,98 @@
1
1
  import type {CartLineUpdateInput} from '@shopify/hydrogen/storefront-api-types';
2
- import type {CartLayout} from '~/components/CartMain';
2
+ import type {CartLayout, LineItemChildrenMap} from '~/components/CartMain';
3
3
  import {CartForm, Image, type OptimisticCartLine} from '@shopify/hydrogen';
4
4
  import {useVariantUrl} from '~/lib/variants';
5
5
  import {Link} from 'react-router';
6
6
  import {ProductPrice} from './ProductPrice';
7
7
  import {useAside} from './Aside';
8
- import type {CartApiQueryFragment} from 'storefrontapi.generated';
8
+ import type {
9
+ CartApiQueryFragment,
10
+ CartLineFragment,
11
+ } from 'storefrontapi.generated';
9
12
 
10
- type CartLine = OptimisticCartLine<CartApiQueryFragment>;
13
+ export type CartLine = OptimisticCartLine<CartApiQueryFragment>;
11
14
 
12
15
  /**
13
16
  * A single line item in the cart. It displays the product image, title, price.
14
17
  * It also provides controls to update the quantity or remove the line item.
18
+ * If the line is a parent line that has child components (like warranties or gift wrapping), they are
19
+ * rendered nested below the parent line.
15
20
  */
16
21
  export function CartLineItem({
17
22
  layout,
18
23
  line,
24
+ childrenMap,
19
25
  }: {
20
26
  layout: CartLayout;
21
27
  line: CartLine;
28
+ childrenMap: LineItemChildrenMap;
22
29
  }) {
23
30
  const {id, merchandise} = line;
24
31
  const {product, title, image, selectedOptions} = merchandise;
25
32
  const lineItemUrl = useVariantUrl(product.handle, selectedOptions);
26
33
  const {close} = useAside();
34
+ const lineItemChildren = childrenMap[id];
35
+ const childrenLabelId = `cart-line-children-${id}`;
27
36
 
28
37
  return (
29
38
  <li key={id} className="cart-line">
30
- {image && (
31
- <Image
32
- alt={title}
33
- aspectRatio="1/1"
34
- data={image}
35
- height={100}
36
- loading="lazy"
37
- width={100}
38
- />
39
- )}
39
+ <div className="cart-line-inner">
40
+ {image && (
41
+ <Image
42
+ alt={title}
43
+ aspectRatio="1/1"
44
+ data={image}
45
+ height={100}
46
+ loading="lazy"
47
+ width={100}
48
+ />
49
+ )}
40
50
 
41
- <div>
42
- <Link
43
- prefetch="intent"
44
- to={lineItemUrl}
45
- onClick={() => {
46
- if (layout === 'aside') {
47
- close();
48
- }
49
- }}
50
- >
51
- <p>
52
- <strong>{product.title}</strong>
53
- </p>
54
- </Link>
55
- <ProductPrice price={line?.cost?.totalAmount} />
56
- <ul>
57
- {selectedOptions.map((option) => (
58
- <li key={option.name}>
59
- <small>
60
- {option.name}: {option.value}
61
- </small>
62
- </li>
63
- ))}
64
- </ul>
65
- <CartLineQuantity line={line} />
51
+ <div>
52
+ <Link
53
+ prefetch="intent"
54
+ to={lineItemUrl}
55
+ onClick={() => {
56
+ if (layout === 'aside') {
57
+ close();
58
+ }
59
+ }}
60
+ >
61
+ <p>
62
+ <strong>{product.title}</strong>
63
+ </p>
64
+ </Link>
65
+ <ProductPrice price={line?.cost?.totalAmount} />
66
+ <ul>
67
+ {selectedOptions.map((option) => (
68
+ <li key={option.name}>
69
+ <small>
70
+ {option.name}: {option.value}
71
+ </small>
72
+ </li>
73
+ ))}
74
+ </ul>
75
+ <CartLineQuantity line={line} />
76
+ </div>
66
77
  </div>
78
+
79
+ {lineItemChildren ? (
80
+ <div>
81
+ <p id={childrenLabelId} className="sr-only">
82
+ Line items with {product.title}
83
+ </p>
84
+ <ul aria-labelledby={childrenLabelId} className="cart-line-children">
85
+ {lineItemChildren.map((childLine) => (
86
+ <CartLineItem
87
+ childrenMap={childrenMap}
88
+ key={childLine.id}
89
+ line={childLine}
90
+ layout={layout}
91
+ />
92
+ ))}
93
+ </ul>
94
+ </div>
95
+ ) : null}
67
96
  </li>
68
97
  );
69
98
  }
@@ -1,8 +1,8 @@
1
- import {useOptimisticCart} from '@shopify/hydrogen';
1
+ import {useOptimisticCart, type OptimisticCartLine} 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';
5
- import {CartLineItem} from '~/components/CartLineItem';
5
+ import {CartLineItem, type CartLine} from '~/components/CartLineItem';
6
6
  import {CartSummary} from './CartSummary';
7
7
 
8
8
  export type CartLayout = 'page' | 'aside';
@@ -12,6 +12,26 @@ export type CartMainProps = {
12
12
  layout: CartLayout;
13
13
  };
14
14
 
15
+ export type LineItemChildrenMap = {[parentId: string]: CartLine[]};
16
+ /** Returns a map of all line items and their children. */
17
+ function getLineItemChildrenMap(lines: CartLine[]): LineItemChildrenMap {
18
+ const children: LineItemChildrenMap = {};
19
+ for (const line of lines) {
20
+ if ('parentRelationship' in line && line.parentRelationship?.parent) {
21
+ const parentId = line.parentRelationship.parent.id;
22
+ if (!children[parentId]) children[parentId] = [];
23
+ children[parentId].push(line);
24
+ }
25
+ if ('lineComponents' in line) {
26
+ const children = getLineItemChildrenMap(line.lineComponents);
27
+ for (const [parentId, childIds] of Object.entries(children)) {
28
+ if (!children[parentId]) children[parentId] = [];
29
+ children[parentId].push(...childIds);
30
+ }
31
+ }
32
+ }
33
+ return children;
34
+ }
15
35
  /**
16
36
  * The main cart component that displays the cart items and summary.
17
37
  * It is used by both the /cart route and the cart aside dialog.
@@ -27,16 +47,34 @@ export function CartMain({layout, cart: originalCart}: CartMainProps) {
27
47
  Boolean(cart?.discountCodes?.filter((code) => code.applicable)?.length);
28
48
  const className = `cart-main ${withDiscount ? 'with-discount' : ''}`;
29
49
  const cartHasItems = cart?.totalQuantity ? cart.totalQuantity > 0 : false;
50
+ const childrenMap = getLineItemChildrenMap(cart?.lines?.nodes ?? []);
30
51
 
31
52
  return (
32
53
  <div className={className}>
33
54
  <CartEmpty hidden={linesCount} layout={layout} />
34
55
  <div className="cart-details">
35
- <div aria-labelledby="cart-lines">
36
- <ul>
37
- {(cart?.lines?.nodes ?? []).map((line) => (
38
- <CartLineItem key={line.id} line={line} layout={layout} />
39
- ))}
56
+ <p id="cart-lines" className="sr-only">
57
+ Line items
58
+ </p>
59
+ <div>
60
+ <ul aria-labelledby="cart-lines">
61
+ {(cart?.lines?.nodes ?? []).map((line) => {
62
+ // we do not render non-parent lines at the root of the cart
63
+ if (
64
+ 'parentRelationship' in line &&
65
+ line.parentRelationship?.parent
66
+ ) {
67
+ return null;
68
+ }
69
+ return (
70
+ <CartLineItem
71
+ key={line.id}
72
+ line={line}
73
+ layout={layout}
74
+ childrenMap={childrenMap}
75
+ />
76
+ );
77
+ })}
40
78
  </ul>
41
79
  </div>
42
80
  {cartHasItems && <CartSummary cart={cart} layout={layout} />}
@@ -3,7 +3,6 @@ import type {CartLayout} from '~/components/CartMain';
3
3
  import {CartForm, Money, type OptimisticCart} from '@shopify/hydrogen';
4
4
  import {useEffect, useRef} from 'react';
5
5
  import {useFetcher} from 'react-router';
6
- import type {FetcherWithComponents} from 'react-router';
7
6
 
8
7
  type CartSummaryProps = {
9
8
  cart: OptimisticCart<CartApiQueryFragment | null>;
@@ -67,7 +66,9 @@ function CartDiscounts({
67
66
  <div className="cart-discount">
68
67
  <code>{codes?.join(', ')}</code>
69
68
  &nbsp;
70
- <button>Remove</button>
69
+ <button type="submit" aria-label="Remove discount">
70
+ Remove
71
+ </button>
71
72
  </div>
72
73
  </UpdateDiscountForm>
73
74
  </div>
@@ -76,9 +77,19 @@ function CartDiscounts({
76
77
  {/* Show an input to apply a discount */}
77
78
  <UpdateDiscountForm discountCodes={codes}>
78
79
  <div>
79
- <input type="text" name="discountCode" placeholder="Discount code" />
80
+ <label htmlFor="discount-code-input" className="sr-only">
81
+ Discount code
82
+ </label>
83
+ <input
84
+ id="discount-code-input"
85
+ type="text"
86
+ name="discountCode"
87
+ placeholder="Discount code"
88
+ />
80
89
  &nbsp;
81
- <button type="submit">Apply</button>
90
+ <button type="submit" aria-label="Apply discount code">
91
+ Apply
92
+ </button>
82
93
  </div>
83
94
  </UpdateDiscountForm>
84
95
  </div>
@@ -110,27 +121,17 @@ function CartGiftCard({
110
121
  }: {
111
122
  giftCardCodes: CartApiQueryFragment['appliedGiftCards'] | undefined;
112
123
  }) {
113
- const appliedGiftCardCodes = useRef<string[]>([]);
114
124
  const giftCardCodeInput = useRef<HTMLInputElement>(null);
115
125
  const giftCardAddFetcher = useFetcher({key: 'gift-card-add'});
116
126
 
117
- // Clear the gift card code input after the gift card is added
118
127
  useEffect(() => {
119
128
  if (giftCardAddFetcher.data) {
120
129
  giftCardCodeInput.current!.value = '';
121
130
  }
122
131
  }, [giftCardAddFetcher.data]);
123
132
 
124
- function saveAppliedCode(code: string) {
125
- const formattedCode = code.replace(/\s/g, ''); // Remove spaces
126
- if (!appliedGiftCardCodes.current.includes(formattedCode)) {
127
- appliedGiftCardCodes.current.push(formattedCode);
128
- }
129
- }
130
-
131
133
  return (
132
134
  <div>
133
- {/* Display applied gift cards with individual remove buttons */}
134
135
  {giftCardCodes && giftCardCodes.length > 0 && (
135
136
  <dl>
136
137
  <dt>Applied Gift Card(s)</dt>
@@ -148,12 +149,7 @@ function CartGiftCard({
148
149
  </dl>
149
150
  )}
150
151
 
151
- {/* Show an input to apply a gift card */}
152
- <UpdateGiftCardForm
153
- giftCardCodes={appliedGiftCardCodes.current}
154
- saveAppliedCode={saveAppliedCode}
155
- fetcherKey="gift-card-add"
156
- >
152
+ <AddGiftCardForm fetcherKey="gift-card-add">
157
153
  <div>
158
154
  <input
159
155
  type="text"
@@ -166,19 +162,15 @@ function CartGiftCard({
166
162
  Apply
167
163
  </button>
168
164
  </div>
169
- </UpdateGiftCardForm>
165
+ </AddGiftCardForm>
170
166
  </div>
171
167
  );
172
168
  }
173
169
 
174
- function UpdateGiftCardForm({
175
- giftCardCodes,
176
- saveAppliedCode,
170
+ function AddGiftCardForm({
177
171
  fetcherKey,
178
172
  children,
179
173
  }: {
180
- giftCardCodes?: string[];
181
- saveAppliedCode?: (code: string) => void;
182
174
  fetcherKey?: string;
183
175
  children: React.ReactNode;
184
176
  }) {
@@ -186,18 +178,9 @@ function UpdateGiftCardForm({
186
178
  <CartForm
187
179
  fetcherKey={fetcherKey}
188
180
  route="/cart"
189
- action={CartForm.ACTIONS.GiftCardCodesUpdate}
190
- inputs={{
191
- giftCardCodes: giftCardCodes || [],
192
- }}
181
+ action={CartForm.ACTIONS.GiftCardCodesAdd}
193
182
  >
194
- {(fetcher: FetcherWithComponents<any>) => {
195
- const code = fetcher.formData?.get('giftCardCode');
196
- if (code && saveAppliedCode) {
197
- saveAppliedCode(code as string);
198
- }
199
- return children;
200
- }}
183
+ {children}
201
184
  </CartForm>
202
185
  );
203
186
  }
@@ -221,4 +204,3 @@ function RemoveGiftCardForm({
221
204
  </CartForm>
222
205
  );
223
206
  }
224
-
@@ -54,6 +54,11 @@ export const CART_QUERY_FRAGMENT = `#graphql
54
54
  }
55
55
  }
56
56
  }
57
+ parentRelationship {
58
+ parent {
59
+ id
60
+ }
61
+ }
57
62
  }
58
63
  fragment CartLineComponent on ComponentizableCartLine {
59
64
  id
@@ -104,6 +109,9 @@ export const CART_QUERY_FRAGMENT = `#graphql
104
109
  }
105
110
  }
106
111
  }
112
+ lineComponents {
113
+ ...CartLine
114
+ }
107
115
  }
108
116
  fragment CartApiQuery on Cart {
109
117
  updatedAt
@@ -1,7 +1,17 @@
1
1
  import type {Route} from './+types/account_.login';
2
2
 
3
3
  export async function loader({request, context}: Route.LoaderArgs) {
4
+ const url = new URL(request.url);
5
+ const acrValues = url.searchParams.get('acr_values') || undefined;
6
+ const loginHint = url.searchParams.get('login_hint') || undefined;
7
+ const loginHintMode = url.searchParams.get('login_hint_mode') || undefined;
8
+ const locale = url.searchParams.get('locale') || undefined;
9
+
4
10
  return context.customerAccount.login({
5
11
  countryCode: context.storefront.i18n.country,
12
+ acrValues,
13
+ loginHint,
14
+ loginHintMode,
15
+ locale,
6
16
  });
7
17
  }
@@ -52,18 +52,14 @@ export async function action({request, context}: Route.ActionArgs) {
52
52
  result = await cart.updateDiscountCodes(discountCodes);
53
53
  break;
54
54
  }
55
- case CartForm.ACTIONS.GiftCardCodesUpdate: {
55
+ case CartForm.ACTIONS.GiftCardCodesAdd: {
56
56
  const formGiftCardCode = inputs.giftCardCode;
57
57
 
58
- // User inputted gift card code
59
58
  const giftCardCodes = (
60
59
  formGiftCardCode ? [formGiftCardCode] : []
61
60
  ) as string[];
62
61
 
63
- // Combine gift card codes already applied on cart
64
- giftCardCodes.push(...inputs.giftCardCodes);
65
-
66
- result = await cart.updateGiftCardCodes(giftCardCodes);
62
+ result = await cart.addGiftCardCodes(giftCardCodes);
67
63
  break;
68
64
  }
69
65
  case CartForm.ACTIONS.GiftCardCodesRemove: {
@@ -90,6 +90,18 @@ aside li {
90
90
  margin-bottom: 0.125rem;
91
91
  }
92
92
 
93
+ .sr-only {
94
+ position: absolute;
95
+ width: 1px;
96
+ height: 1px;
97
+ padding: 0;
98
+ margin: -1px;
99
+ overflow: hidden;
100
+ clip-path: inset(50%);
101
+ white-space: nowrap;
102
+ border-width: 0;
103
+ }
104
+
93
105
  .overlay {
94
106
  background: rgba(0, 0, 0, 0.2);
95
107
  bottom: 0;
@@ -250,10 +262,13 @@ button.reset:hover:not(:has(> *)) {
250
262
  }
251
263
 
252
264
  .cart-line {
253
- display: flex;
254
265
  padding: 0.75rem 0;
255
266
  }
256
267
 
268
+ .cart-line-inner {
269
+ display: flex;
270
+ }
271
+
257
272
  .cart-line img {
258
273
  height: 100%;
259
274
  display: block;
@@ -277,6 +292,11 @@ button.reset:hover:not(:has(> *)) {
277
292
  display: flex;
278
293
  }
279
294
 
295
+ /* Child line components (warranties, gift wrapping, etc.) */
296
+ .cart-line-children {
297
+ padding-left: 2rem;
298
+ }
299
+
280
300
  .cart-discount {
281
301
  align-items: center;
282
302
  display: flex;
@@ -211,6 +211,7 @@ export default [
211
211
  '@typescript-eslint/no-floating-promises': 'error',
212
212
  '@typescript-eslint/no-misused-promises': 'error',
213
213
  'react/prop-types': 'off',
214
+ 'import/no-unresolved': ['error', {ignore: ['^virtual:']}],
214
215
  },
215
216
  },
216
217
  {
@@ -2,7 +2,7 @@
2
2
  "name": "skeleton",
3
3
  "private": true,
4
4
  "sideEffects": false,
5
- "version": "2025.7.0",
5
+ "version": "2025.10.1",
6
6
  "type": "module",
7
7
  "scripts": {
8
8
  "build": "shopify hydrogen build --codegen",
@@ -14,22 +14,22 @@
14
14
  },
15
15
  "prettier": "@shopify/prettier-config",
16
16
  "dependencies": {
17
- "@shopify/hydrogen": "2025.7.0",
17
+ "@shopify/hydrogen": "2025.10.1",
18
18
  "graphql": "^16.10.0",
19
19
  "graphql-tag": "^2.12.6",
20
20
  "isbot": "^5.1.22",
21
21
  "react": "18.3.1",
22
22
  "react-dom": "18.3.1",
23
- "react-router": "7.9.2",
24
- "react-router-dom": "7.9.2"
23
+ "react-router": "7.12.0",
24
+ "react-router-dom": "7.12.0"
25
25
  },
26
26
  "devDependencies": {
27
27
  "@eslint/compat": "^1.2.5",
28
28
  "@eslint/eslintrc": "^3.2.0",
29
29
  "@eslint/js": "^9.18.0",
30
30
  "@graphql-codegen/cli": "5.0.2",
31
- "@react-router/dev": "7.9.2",
32
- "@react-router/fs-routes": "7.9.2",
31
+ "@react-router/dev": "7.12.0",
32
+ "@react-router/fs-routes": "7.12.0",
33
33
  "@shopify/cli": "3.85.4",
34
34
  "@shopify/hydrogen-codegen": "^0.3.3",
35
35
  "@shopify/mini-oxygen": "^4.0.0",
@@ -51,6 +51,7 @@
51
51
  "eslint-plugin-react": "^7.37.4",
52
52
  "eslint-plugin-react-hooks": "^5.1.0",
53
53
  "globals": "^15.14.0",
54
+ "graphql-config": "^5.0.3",
54
55
  "prettier": "^3.4.2",
55
56
  "typescript": "^5.9.2",
56
57
  "vite": "^6.2.4",