@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.
- package/dist/assets/hydrogen/starter/.graphqlrc.ts +4 -2
- package/dist/assets/hydrogen/starter/CHANGELOG.md +142 -0
- package/dist/assets/hydrogen/starter/app/components/CartLineItem.tsx +67 -38
- package/dist/assets/hydrogen/starter/app/components/CartMain.tsx +45 -7
- package/dist/assets/hydrogen/starter/app/components/CartSummary.tsx +20 -38
- package/dist/assets/hydrogen/starter/app/lib/fragments.ts +8 -0
- package/dist/assets/hydrogen/starter/app/routes/account_.login.tsx +10 -0
- package/dist/assets/hydrogen/starter/app/routes/cart.tsx +2 -6
- package/dist/assets/hydrogen/starter/app/styles/app.css +21 -1
- package/dist/assets/hydrogen/starter/eslint.config.js +1 -0
- package/dist/assets/hydrogen/starter/package.json +7 -6
- package/dist/assets/hydrogen/starter/server.ts +5 -7
- package/dist/assets/hydrogen/starter/storefrontapi.generated.d.ts +89 -0
- package/dist/{chunk-2LZQLWDR.js → chunk-YKN6SEJT.js} +246 -246
- package/dist/create-app.js +2 -2
- package/dist/{error-handler-XRI3ZDLO.js → error-handler-2JZDGPAN.js} +1 -1
- package/package.json +1 -1
|
@@ -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
|
-
|
|
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
|
-
}
|
|
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 {
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
<
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
|
|
70
|
-
<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
|
-
<
|
|
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
|
|
|
81
|
-
<button type="submit"
|
|
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
|
-
|
|
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
|
-
</
|
|
165
|
+
</AddGiftCardForm>
|
|
170
166
|
</div>
|
|
171
167
|
);
|
|
172
168
|
}
|
|
173
169
|
|
|
174
|
-
function
|
|
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.
|
|
190
|
-
inputs={{
|
|
191
|
-
giftCardCodes: giftCardCodes || [],
|
|
192
|
-
}}
|
|
181
|
+
action={CartForm.ACTIONS.GiftCardCodesAdd}
|
|
193
182
|
>
|
|
194
|
-
{
|
|
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.
|
|
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
|
-
|
|
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;
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "skeleton",
|
|
3
3
|
"private": true,
|
|
4
4
|
"sideEffects": false,
|
|
5
|
-
"version": "2025.
|
|
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.
|
|
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.
|
|
24
|
-
"react-router-dom": "7.
|
|
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.
|
|
32
|
-
"@react-router/fs-routes": "7.
|
|
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",
|