hydrogen-forge 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +212 -0
- package/dist/commands/add.d.ts +7 -0
- package/dist/commands/add.d.ts.map +1 -0
- package/dist/commands/add.js +123 -0
- package/dist/commands/add.js.map +1 -0
- package/dist/commands/create.d.ts +8 -0
- package/dist/commands/create.d.ts.map +1 -0
- package/dist/commands/create.js +160 -0
- package/dist/commands/create.js.map +1 -0
- package/dist/commands/setup-mcp.d.ts +7 -0
- package/dist/commands/setup-mcp.d.ts.map +1 -0
- package/dist/commands/setup-mcp.js +179 -0
- package/dist/commands/setup-mcp.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +50 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/generators.d.ts +6 -0
- package/dist/lib/generators.d.ts.map +1 -0
- package/dist/lib/generators.js +470 -0
- package/dist/lib/generators.js.map +1 -0
- package/dist/lib/utils.d.ts +17 -0
- package/dist/lib/utils.d.ts.map +1 -0
- package/dist/lib/utils.js +101 -0
- package/dist/lib/utils.js.map +1 -0
- package/package.json +54 -0
- package/templates/starter/.env.example +21 -0
- package/templates/starter/.graphqlrc.ts +27 -0
- package/templates/starter/README.md +117 -0
- package/templates/starter/app/assets/favicon.svg +28 -0
- package/templates/starter/app/components/AddToCartButton.tsx +102 -0
- package/templates/starter/app/components/Aside.tsx +136 -0
- package/templates/starter/app/components/CartLineItem.tsx +229 -0
- package/templates/starter/app/components/CartMain.tsx +131 -0
- package/templates/starter/app/components/CartSummary.tsx +315 -0
- package/templates/starter/app/components/CollectionFilters.tsx +330 -0
- package/templates/starter/app/components/CollectionGrid.tsx +141 -0
- package/templates/starter/app/components/Footer.tsx +218 -0
- package/templates/starter/app/components/Header.tsx +296 -0
- package/templates/starter/app/components/PageLayout.tsx +174 -0
- package/templates/starter/app/components/PaginatedResourceSection.tsx +41 -0
- package/templates/starter/app/components/ProductCard.tsx +151 -0
- package/templates/starter/app/components/ProductForm.tsx +156 -0
- package/templates/starter/app/components/ProductGallery.tsx +164 -0
- package/templates/starter/app/components/ProductGrid.tsx +64 -0
- package/templates/starter/app/components/ProductImage.tsx +23 -0
- package/templates/starter/app/components/ProductItem.tsx +44 -0
- package/templates/starter/app/components/ProductPrice.tsx +97 -0
- package/templates/starter/app/components/SearchDialog.tsx +599 -0
- package/templates/starter/app/components/SearchForm.tsx +68 -0
- package/templates/starter/app/components/SearchFormPredictive.tsx +76 -0
- package/templates/starter/app/components/SearchResults.tsx +161 -0
- package/templates/starter/app/components/SearchResultsPredictive.tsx +461 -0
- package/templates/starter/app/entry.client.tsx +21 -0
- package/templates/starter/app/entry.server.tsx +53 -0
- package/templates/starter/app/graphql/customer-account/CustomerAddressMutations.ts +64 -0
- package/templates/starter/app/graphql/customer-account/CustomerDetailsQuery.ts +40 -0
- package/templates/starter/app/graphql/customer-account/CustomerOrderQuery.ts +90 -0
- package/templates/starter/app/graphql/customer-account/CustomerOrdersQuery.ts +63 -0
- package/templates/starter/app/graphql/customer-account/CustomerUpdateMutation.ts +25 -0
- package/templates/starter/app/lib/context.ts +60 -0
- package/templates/starter/app/lib/fragments.ts +234 -0
- package/templates/starter/app/lib/orderFilters.ts +90 -0
- package/templates/starter/app/lib/redirect.ts +23 -0
- package/templates/starter/app/lib/search.ts +79 -0
- package/templates/starter/app/lib/session.ts +72 -0
- package/templates/starter/app/lib/variants.ts +46 -0
- package/templates/starter/app/root.tsx +209 -0
- package/templates/starter/app/routes/$.tsx +11 -0
- package/templates/starter/app/routes/[robots.txt].tsx +117 -0
- package/templates/starter/app/routes/[sitemap.xml].tsx +16 -0
- package/templates/starter/app/routes/_index.tsx +167 -0
- package/templates/starter/app/routes/account.$.tsx +9 -0
- package/templates/starter/app/routes/account._index.tsx +5 -0
- package/templates/starter/app/routes/account.addresses.tsx +516 -0
- package/templates/starter/app/routes/account.orders.$id.tsx +222 -0
- package/templates/starter/app/routes/account.orders._index.tsx +222 -0
- package/templates/starter/app/routes/account.profile.tsx +133 -0
- package/templates/starter/app/routes/account.tsx +97 -0
- package/templates/starter/app/routes/account_.authorize.tsx +5 -0
- package/templates/starter/app/routes/account_.login.tsx +7 -0
- package/templates/starter/app/routes/account_.logout.tsx +11 -0
- package/templates/starter/app/routes/api.$version.[graphql.json].tsx +14 -0
- package/templates/starter/app/routes/blogs.$blogHandle.$articleHandle.tsx +129 -0
- package/templates/starter/app/routes/blogs.$blogHandle._index.tsx +175 -0
- package/templates/starter/app/routes/blogs._index.tsx +109 -0
- package/templates/starter/app/routes/cart.$lines.tsx +70 -0
- package/templates/starter/app/routes/cart.tsx +117 -0
- package/templates/starter/app/routes/collections.$handle.tsx +161 -0
- package/templates/starter/app/routes/collections._index.tsx +133 -0
- package/templates/starter/app/routes/collections.all.tsx +122 -0
- package/templates/starter/app/routes/discount.$code.tsx +48 -0
- package/templates/starter/app/routes/pages.$handle.tsx +88 -0
- package/templates/starter/app/routes/policies.$handle.tsx +93 -0
- package/templates/starter/app/routes/policies._index.tsx +69 -0
- package/templates/starter/app/routes/products.$handle.tsx +232 -0
- package/templates/starter/app/routes/search.tsx +426 -0
- package/templates/starter/app/routes/sitemap.$type.$page[.xml].tsx +23 -0
- package/templates/starter/app/routes.ts +9 -0
- package/templates/starter/app/styles/app.css +574 -0
- package/templates/starter/app/styles/reset.css +139 -0
- package/templates/starter/app/styles/tailwind.css +116 -0
- package/templates/starter/customer-accountapi.generated.d.ts +543 -0
- package/templates/starter/env.d.ts +7 -0
- package/templates/starter/eslint.config.js +247 -0
- package/templates/starter/guides/predictiveSearch/predictiveSearch.jpg +0 -0
- package/templates/starter/guides/predictiveSearch/predictiveSearch.md +394 -0
- package/templates/starter/guides/search/search.jpg +0 -0
- package/templates/starter/guides/search/search.md +335 -0
- package/templates/starter/package.json +71 -0
- package/templates/starter/postcss.config.js +6 -0
- package/templates/starter/public/.gitkeep +0 -0
- package/templates/starter/react-router.config.ts +13 -0
- package/templates/starter/server.ts +59 -0
- package/templates/starter/storefrontapi.generated.d.ts +1264 -0
- package/templates/starter/tailwind.config.js +83 -0
- package/templates/starter/tsconfig.json +67 -0
- package/templates/starter/vite.config.ts +32 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import {HydratedRouter} from 'react-router/dom';
|
|
2
|
+
import {startTransition, StrictMode} from 'react';
|
|
3
|
+
import {hydrateRoot} from 'react-dom/client';
|
|
4
|
+
import {NonceProvider} from '@shopify/hydrogen';
|
|
5
|
+
|
|
6
|
+
if (!window.location.origin.includes('webcache.googleusercontent.com')) {
|
|
7
|
+
startTransition(() => {
|
|
8
|
+
// Extract nonce from existing script tags
|
|
9
|
+
const existingNonce =
|
|
10
|
+
document.querySelector<HTMLScriptElement>('script[nonce]')?.nonce;
|
|
11
|
+
|
|
12
|
+
hydrateRoot(
|
|
13
|
+
document,
|
|
14
|
+
<StrictMode>
|
|
15
|
+
<NonceProvider value={existingNonce}>
|
|
16
|
+
<HydratedRouter />
|
|
17
|
+
</NonceProvider>
|
|
18
|
+
</StrictMode>,
|
|
19
|
+
);
|
|
20
|
+
});
|
|
21
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import {ServerRouter} from 'react-router';
|
|
2
|
+
import {isbot} from 'isbot';
|
|
3
|
+
import {renderToReadableStream} from 'react-dom/server';
|
|
4
|
+
import {
|
|
5
|
+
createContentSecurityPolicy,
|
|
6
|
+
type HydrogenRouterContextProvider,
|
|
7
|
+
} from '@shopify/hydrogen';
|
|
8
|
+
import type {EntryContext} from 'react-router';
|
|
9
|
+
|
|
10
|
+
export default async function handleRequest(
|
|
11
|
+
request: Request,
|
|
12
|
+
responseStatusCode: number,
|
|
13
|
+
responseHeaders: Headers,
|
|
14
|
+
reactRouterContext: EntryContext,
|
|
15
|
+
context: HydrogenRouterContextProvider,
|
|
16
|
+
) {
|
|
17
|
+
const {nonce, header, NonceProvider} = createContentSecurityPolicy({
|
|
18
|
+
shop: {
|
|
19
|
+
checkoutDomain: context.env.PUBLIC_CHECKOUT_DOMAIN,
|
|
20
|
+
storeDomain: context.env.PUBLIC_STORE_DOMAIN,
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const body = await renderToReadableStream(
|
|
25
|
+
<NonceProvider>
|
|
26
|
+
<ServerRouter
|
|
27
|
+
context={reactRouterContext}
|
|
28
|
+
url={request.url}
|
|
29
|
+
nonce={nonce}
|
|
30
|
+
/>
|
|
31
|
+
</NonceProvider>,
|
|
32
|
+
{
|
|
33
|
+
nonce,
|
|
34
|
+
signal: request.signal,
|
|
35
|
+
onError(error) {
|
|
36
|
+
console.error(error);
|
|
37
|
+
responseStatusCode = 500;
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
if (isbot(request.headers.get('user-agent'))) {
|
|
43
|
+
await body.allReady;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
responseHeaders.set('Content-Type', 'text/html');
|
|
47
|
+
responseHeaders.set('Content-Security-Policy', header);
|
|
48
|
+
|
|
49
|
+
return new Response(body, {
|
|
50
|
+
headers: responseHeaders,
|
|
51
|
+
status: responseStatusCode,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// NOTE: https://shopify.dev/docs/api/customer/latest/mutations/customerAddressUpdate
|
|
2
|
+
export const UPDATE_ADDRESS_MUTATION = `#graphql
|
|
3
|
+
mutation customerAddressUpdate(
|
|
4
|
+
$address: CustomerAddressInput!
|
|
5
|
+
$addressId: ID!
|
|
6
|
+
$defaultAddress: Boolean
|
|
7
|
+
$language: LanguageCode
|
|
8
|
+
) @inContext(language: $language) {
|
|
9
|
+
customerAddressUpdate(
|
|
10
|
+
address: $address
|
|
11
|
+
addressId: $addressId
|
|
12
|
+
defaultAddress: $defaultAddress
|
|
13
|
+
) {
|
|
14
|
+
customerAddress {
|
|
15
|
+
id
|
|
16
|
+
}
|
|
17
|
+
userErrors {
|
|
18
|
+
code
|
|
19
|
+
field
|
|
20
|
+
message
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
` as const;
|
|
25
|
+
|
|
26
|
+
// NOTE: https://shopify.dev/docs/api/customer/latest/mutations/customerAddressDelete
|
|
27
|
+
export const DELETE_ADDRESS_MUTATION = `#graphql
|
|
28
|
+
mutation customerAddressDelete(
|
|
29
|
+
$addressId: ID!
|
|
30
|
+
$language: LanguageCode
|
|
31
|
+
) @inContext(language: $language) {
|
|
32
|
+
customerAddressDelete(addressId: $addressId) {
|
|
33
|
+
deletedAddressId
|
|
34
|
+
userErrors {
|
|
35
|
+
code
|
|
36
|
+
field
|
|
37
|
+
message
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
` as const;
|
|
42
|
+
|
|
43
|
+
// NOTE: https://shopify.dev/docs/api/customer/latest/mutations/customerAddressCreate
|
|
44
|
+
export const CREATE_ADDRESS_MUTATION = `#graphql
|
|
45
|
+
mutation customerAddressCreate(
|
|
46
|
+
$address: CustomerAddressInput!
|
|
47
|
+
$defaultAddress: Boolean
|
|
48
|
+
$language: LanguageCode
|
|
49
|
+
) @inContext(language: $language) {
|
|
50
|
+
customerAddressCreate(
|
|
51
|
+
address: $address
|
|
52
|
+
defaultAddress: $defaultAddress
|
|
53
|
+
) {
|
|
54
|
+
customerAddress {
|
|
55
|
+
id
|
|
56
|
+
}
|
|
57
|
+
userErrors {
|
|
58
|
+
code
|
|
59
|
+
field
|
|
60
|
+
message
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
` as const;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// NOTE: https://shopify.dev/docs/api/customer/latest/objects/Customer
|
|
2
|
+
export const CUSTOMER_FRAGMENT = `#graphql
|
|
3
|
+
fragment Customer on Customer {
|
|
4
|
+
id
|
|
5
|
+
firstName
|
|
6
|
+
lastName
|
|
7
|
+
defaultAddress {
|
|
8
|
+
...Address
|
|
9
|
+
}
|
|
10
|
+
addresses(first: 6) {
|
|
11
|
+
nodes {
|
|
12
|
+
...Address
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
fragment Address on CustomerAddress {
|
|
17
|
+
id
|
|
18
|
+
formatted
|
|
19
|
+
firstName
|
|
20
|
+
lastName
|
|
21
|
+
company
|
|
22
|
+
address1
|
|
23
|
+
address2
|
|
24
|
+
territoryCode
|
|
25
|
+
zoneCode
|
|
26
|
+
city
|
|
27
|
+
zip
|
|
28
|
+
phoneNumber
|
|
29
|
+
}
|
|
30
|
+
` as const;
|
|
31
|
+
|
|
32
|
+
// NOTE: https://shopify.dev/docs/api/customer/latest/queries/customer
|
|
33
|
+
export const CUSTOMER_DETAILS_QUERY = `#graphql
|
|
34
|
+
query CustomerDetails($language: LanguageCode) @inContext(language: $language) {
|
|
35
|
+
customer {
|
|
36
|
+
...Customer
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
${CUSTOMER_FRAGMENT}
|
|
40
|
+
` as const;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// NOTE: https://shopify.dev/docs/api/customer/latest/queries/order
|
|
2
|
+
export const CUSTOMER_ORDER_QUERY = `#graphql
|
|
3
|
+
fragment OrderMoney on MoneyV2 {
|
|
4
|
+
amount
|
|
5
|
+
currencyCode
|
|
6
|
+
}
|
|
7
|
+
fragment DiscountApplication on DiscountApplication {
|
|
8
|
+
value {
|
|
9
|
+
__typename
|
|
10
|
+
... on MoneyV2 {
|
|
11
|
+
...OrderMoney
|
|
12
|
+
}
|
|
13
|
+
... on PricingPercentageValue {
|
|
14
|
+
percentage
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
fragment OrderLineItemFull on LineItem {
|
|
19
|
+
id
|
|
20
|
+
title
|
|
21
|
+
quantity
|
|
22
|
+
price {
|
|
23
|
+
...OrderMoney
|
|
24
|
+
}
|
|
25
|
+
discountAllocations {
|
|
26
|
+
allocatedAmount {
|
|
27
|
+
...OrderMoney
|
|
28
|
+
}
|
|
29
|
+
discountApplication {
|
|
30
|
+
...DiscountApplication
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
totalDiscount {
|
|
34
|
+
...OrderMoney
|
|
35
|
+
}
|
|
36
|
+
image {
|
|
37
|
+
altText
|
|
38
|
+
height
|
|
39
|
+
url
|
|
40
|
+
id
|
|
41
|
+
width
|
|
42
|
+
}
|
|
43
|
+
variantTitle
|
|
44
|
+
}
|
|
45
|
+
fragment Order on Order {
|
|
46
|
+
id
|
|
47
|
+
name
|
|
48
|
+
confirmationNumber
|
|
49
|
+
statusPageUrl
|
|
50
|
+
fulfillmentStatus
|
|
51
|
+
processedAt
|
|
52
|
+
fulfillments(first: 1) {
|
|
53
|
+
nodes {
|
|
54
|
+
status
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
totalTax {
|
|
58
|
+
...OrderMoney
|
|
59
|
+
}
|
|
60
|
+
totalPrice {
|
|
61
|
+
...OrderMoney
|
|
62
|
+
}
|
|
63
|
+
subtotal {
|
|
64
|
+
...OrderMoney
|
|
65
|
+
}
|
|
66
|
+
shippingAddress {
|
|
67
|
+
name
|
|
68
|
+
formatted(withName: true)
|
|
69
|
+
formattedArea
|
|
70
|
+
}
|
|
71
|
+
discountApplications(first: 100) {
|
|
72
|
+
nodes {
|
|
73
|
+
...DiscountApplication
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
lineItems(first: 100) {
|
|
77
|
+
nodes {
|
|
78
|
+
...OrderLineItemFull
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
query Order($orderId: ID!, $language: LanguageCode)
|
|
83
|
+
@inContext(language: $language) {
|
|
84
|
+
order(id: $orderId) {
|
|
85
|
+
... on Order {
|
|
86
|
+
...Order
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
` as const;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// NOTE: https://shopify.dev/docs/api/customer/latest/objects/Order
|
|
2
|
+
export const ORDER_ITEM_FRAGMENT = `#graphql
|
|
3
|
+
fragment OrderItem on Order {
|
|
4
|
+
totalPrice {
|
|
5
|
+
amount
|
|
6
|
+
currencyCode
|
|
7
|
+
}
|
|
8
|
+
financialStatus
|
|
9
|
+
fulfillmentStatus
|
|
10
|
+
fulfillments(first: 1) {
|
|
11
|
+
nodes {
|
|
12
|
+
status
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
id
|
|
16
|
+
number
|
|
17
|
+
confirmationNumber
|
|
18
|
+
processedAt
|
|
19
|
+
}
|
|
20
|
+
` as const;
|
|
21
|
+
|
|
22
|
+
// NOTE: https://shopify.dev/docs/api/customer/latest/objects/Customer
|
|
23
|
+
export const CUSTOMER_ORDERS_FRAGMENT = `#graphql
|
|
24
|
+
fragment CustomerOrders on Customer {
|
|
25
|
+
orders(
|
|
26
|
+
sortKey: PROCESSED_AT,
|
|
27
|
+
reverse: true,
|
|
28
|
+
first: $first,
|
|
29
|
+
last: $last,
|
|
30
|
+
before: $startCursor,
|
|
31
|
+
after: $endCursor,
|
|
32
|
+
query: $query
|
|
33
|
+
) {
|
|
34
|
+
nodes {
|
|
35
|
+
...OrderItem
|
|
36
|
+
}
|
|
37
|
+
pageInfo {
|
|
38
|
+
hasPreviousPage
|
|
39
|
+
hasNextPage
|
|
40
|
+
endCursor
|
|
41
|
+
startCursor
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
${ORDER_ITEM_FRAGMENT}
|
|
46
|
+
` as const;
|
|
47
|
+
|
|
48
|
+
// NOTE: https://shopify.dev/docs/api/customer/latest/queries/customer
|
|
49
|
+
export const CUSTOMER_ORDERS_QUERY = `#graphql
|
|
50
|
+
${CUSTOMER_ORDERS_FRAGMENT}
|
|
51
|
+
query CustomerOrders(
|
|
52
|
+
$endCursor: String
|
|
53
|
+
$first: Int
|
|
54
|
+
$last: Int
|
|
55
|
+
$startCursor: String
|
|
56
|
+
$query: String
|
|
57
|
+
$language: LanguageCode
|
|
58
|
+
) @inContext(language: $language) {
|
|
59
|
+
customer {
|
|
60
|
+
...CustomerOrders
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
` as const;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// NOTE: https://shopify.dev/docs/api/customer/latest/mutations/customerUpdate
|
|
2
|
+
export const CUSTOMER_UPDATE_MUTATION = `#graphql
|
|
3
|
+
mutation customerUpdate(
|
|
4
|
+
$customer: CustomerUpdateInput!
|
|
5
|
+
$language: LanguageCode
|
|
6
|
+
) @inContext(language: $language) {
|
|
7
|
+
customerUpdate(input: $customer) {
|
|
8
|
+
customer {
|
|
9
|
+
firstName
|
|
10
|
+
lastName
|
|
11
|
+
emailAddress {
|
|
12
|
+
emailAddress
|
|
13
|
+
}
|
|
14
|
+
phoneNumber {
|
|
15
|
+
phoneNumber
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
userErrors {
|
|
19
|
+
code
|
|
20
|
+
field
|
|
21
|
+
message
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
` as const;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import {createHydrogenContext} from '@shopify/hydrogen';
|
|
2
|
+
import {AppSession} from '~/lib/session';
|
|
3
|
+
import {CART_QUERY_FRAGMENT} from '~/lib/fragments';
|
|
4
|
+
|
|
5
|
+
// Define the additional context object
|
|
6
|
+
const additionalContext = {
|
|
7
|
+
// Additional context for custom properties, CMS clients, 3P SDKs, etc.
|
|
8
|
+
// These will be available as both context.propertyName and context.get(propertyContext)
|
|
9
|
+
// Example of complex objects that could be added:
|
|
10
|
+
// cms: await createCMSClient(env),
|
|
11
|
+
// reviews: await createReviewsClient(env),
|
|
12
|
+
} as const;
|
|
13
|
+
|
|
14
|
+
// Automatically augment HydrogenAdditionalContext with the additional context type
|
|
15
|
+
type AdditionalContextType = typeof additionalContext;
|
|
16
|
+
|
|
17
|
+
declare global {
|
|
18
|
+
interface HydrogenAdditionalContext extends AdditionalContextType {}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Creates Hydrogen context for React Router 7.9.x
|
|
23
|
+
* Returns HydrogenRouterContextProvider with hybrid access patterns
|
|
24
|
+
* */
|
|
25
|
+
export async function createHydrogenRouterContext(
|
|
26
|
+
request: Request,
|
|
27
|
+
env: Env,
|
|
28
|
+
executionContext: ExecutionContext,
|
|
29
|
+
) {
|
|
30
|
+
/**
|
|
31
|
+
* Open a cache instance in the worker and a custom session instance.
|
|
32
|
+
*/
|
|
33
|
+
if (!env?.SESSION_SECRET) {
|
|
34
|
+
throw new Error('SESSION_SECRET environment variable is not set');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const waitUntil = executionContext.waitUntil.bind(executionContext);
|
|
38
|
+
const [cache, session] = await Promise.all([
|
|
39
|
+
caches.open('hydrogen'),
|
|
40
|
+
AppSession.init(request, [env.SESSION_SECRET]),
|
|
41
|
+
]);
|
|
42
|
+
|
|
43
|
+
const hydrogenContext = createHydrogenContext(
|
|
44
|
+
{
|
|
45
|
+
env,
|
|
46
|
+
request,
|
|
47
|
+
cache,
|
|
48
|
+
waitUntil,
|
|
49
|
+
session,
|
|
50
|
+
// Or detect from URL path based on locale subpath, cookies, or any other strategy
|
|
51
|
+
i18n: {language: 'EN', country: 'US'},
|
|
52
|
+
cart: {
|
|
53
|
+
queryFragment: CART_QUERY_FRAGMENT,
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
additionalContext,
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
return hydrogenContext;
|
|
60
|
+
}
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
// NOTE: https://shopify.dev/docs/api/storefront/latest/queries/cart
|
|
2
|
+
export const CART_QUERY_FRAGMENT = `#graphql
|
|
3
|
+
fragment Money on MoneyV2 {
|
|
4
|
+
currencyCode
|
|
5
|
+
amount
|
|
6
|
+
}
|
|
7
|
+
fragment CartLine on CartLine {
|
|
8
|
+
id
|
|
9
|
+
quantity
|
|
10
|
+
attributes {
|
|
11
|
+
key
|
|
12
|
+
value
|
|
13
|
+
}
|
|
14
|
+
cost {
|
|
15
|
+
totalAmount {
|
|
16
|
+
...Money
|
|
17
|
+
}
|
|
18
|
+
amountPerQuantity {
|
|
19
|
+
...Money
|
|
20
|
+
}
|
|
21
|
+
compareAtAmountPerQuantity {
|
|
22
|
+
...Money
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
merchandise {
|
|
26
|
+
... on ProductVariant {
|
|
27
|
+
id
|
|
28
|
+
availableForSale
|
|
29
|
+
compareAtPrice {
|
|
30
|
+
...Money
|
|
31
|
+
}
|
|
32
|
+
price {
|
|
33
|
+
...Money
|
|
34
|
+
}
|
|
35
|
+
requiresShipping
|
|
36
|
+
title
|
|
37
|
+
image {
|
|
38
|
+
id
|
|
39
|
+
url
|
|
40
|
+
altText
|
|
41
|
+
width
|
|
42
|
+
height
|
|
43
|
+
|
|
44
|
+
}
|
|
45
|
+
product {
|
|
46
|
+
handle
|
|
47
|
+
title
|
|
48
|
+
id
|
|
49
|
+
vendor
|
|
50
|
+
}
|
|
51
|
+
selectedOptions {
|
|
52
|
+
name
|
|
53
|
+
value
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
fragment CartLineComponent on ComponentizableCartLine {
|
|
59
|
+
id
|
|
60
|
+
quantity
|
|
61
|
+
attributes {
|
|
62
|
+
key
|
|
63
|
+
value
|
|
64
|
+
}
|
|
65
|
+
cost {
|
|
66
|
+
totalAmount {
|
|
67
|
+
...Money
|
|
68
|
+
}
|
|
69
|
+
amountPerQuantity {
|
|
70
|
+
...Money
|
|
71
|
+
}
|
|
72
|
+
compareAtAmountPerQuantity {
|
|
73
|
+
...Money
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
merchandise {
|
|
77
|
+
... on ProductVariant {
|
|
78
|
+
id
|
|
79
|
+
availableForSale
|
|
80
|
+
compareAtPrice {
|
|
81
|
+
...Money
|
|
82
|
+
}
|
|
83
|
+
price {
|
|
84
|
+
...Money
|
|
85
|
+
}
|
|
86
|
+
requiresShipping
|
|
87
|
+
title
|
|
88
|
+
image {
|
|
89
|
+
id
|
|
90
|
+
url
|
|
91
|
+
altText
|
|
92
|
+
width
|
|
93
|
+
height
|
|
94
|
+
}
|
|
95
|
+
product {
|
|
96
|
+
handle
|
|
97
|
+
title
|
|
98
|
+
id
|
|
99
|
+
vendor
|
|
100
|
+
}
|
|
101
|
+
selectedOptions {
|
|
102
|
+
name
|
|
103
|
+
value
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
fragment CartApiQuery on Cart {
|
|
109
|
+
updatedAt
|
|
110
|
+
id
|
|
111
|
+
appliedGiftCards {
|
|
112
|
+
id
|
|
113
|
+
lastCharacters
|
|
114
|
+
amountUsed {
|
|
115
|
+
...Money
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
checkoutUrl
|
|
119
|
+
totalQuantity
|
|
120
|
+
buyerIdentity {
|
|
121
|
+
countryCode
|
|
122
|
+
customer {
|
|
123
|
+
id
|
|
124
|
+
email
|
|
125
|
+
firstName
|
|
126
|
+
lastName
|
|
127
|
+
displayName
|
|
128
|
+
}
|
|
129
|
+
email
|
|
130
|
+
phone
|
|
131
|
+
}
|
|
132
|
+
lines(first: $numCartLines) {
|
|
133
|
+
nodes {
|
|
134
|
+
...CartLine
|
|
135
|
+
}
|
|
136
|
+
nodes {
|
|
137
|
+
...CartLineComponent
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
cost {
|
|
141
|
+
subtotalAmount {
|
|
142
|
+
...Money
|
|
143
|
+
}
|
|
144
|
+
totalAmount {
|
|
145
|
+
...Money
|
|
146
|
+
}
|
|
147
|
+
totalDutyAmount {
|
|
148
|
+
...Money
|
|
149
|
+
}
|
|
150
|
+
totalTaxAmount {
|
|
151
|
+
...Money
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
note
|
|
155
|
+
attributes {
|
|
156
|
+
key
|
|
157
|
+
value
|
|
158
|
+
}
|
|
159
|
+
discountCodes {
|
|
160
|
+
code
|
|
161
|
+
applicable
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
` as const;
|
|
165
|
+
|
|
166
|
+
const MENU_FRAGMENT = `#graphql
|
|
167
|
+
fragment MenuItem on MenuItem {
|
|
168
|
+
id
|
|
169
|
+
resourceId
|
|
170
|
+
tags
|
|
171
|
+
title
|
|
172
|
+
type
|
|
173
|
+
url
|
|
174
|
+
}
|
|
175
|
+
fragment ChildMenuItem on MenuItem {
|
|
176
|
+
...MenuItem
|
|
177
|
+
}
|
|
178
|
+
fragment ParentMenuItem on MenuItem {
|
|
179
|
+
...MenuItem
|
|
180
|
+
items {
|
|
181
|
+
...ChildMenuItem
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
fragment Menu on Menu {
|
|
185
|
+
id
|
|
186
|
+
items {
|
|
187
|
+
...ParentMenuItem
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
` as const;
|
|
191
|
+
|
|
192
|
+
export const HEADER_QUERY = `#graphql
|
|
193
|
+
fragment Shop on Shop {
|
|
194
|
+
id
|
|
195
|
+
name
|
|
196
|
+
description
|
|
197
|
+
primaryDomain {
|
|
198
|
+
url
|
|
199
|
+
}
|
|
200
|
+
brand {
|
|
201
|
+
logo {
|
|
202
|
+
image {
|
|
203
|
+
url
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
query Header(
|
|
209
|
+
$country: CountryCode
|
|
210
|
+
$headerMenuHandle: String!
|
|
211
|
+
$language: LanguageCode
|
|
212
|
+
) @inContext(language: $language, country: $country) {
|
|
213
|
+
shop {
|
|
214
|
+
...Shop
|
|
215
|
+
}
|
|
216
|
+
menu(handle: $headerMenuHandle) {
|
|
217
|
+
...Menu
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
${MENU_FRAGMENT}
|
|
221
|
+
` as const;
|
|
222
|
+
|
|
223
|
+
export const FOOTER_QUERY = `#graphql
|
|
224
|
+
query Footer(
|
|
225
|
+
$country: CountryCode
|
|
226
|
+
$footerMenuHandle: String!
|
|
227
|
+
$language: LanguageCode
|
|
228
|
+
) @inContext(language: $language, country: $country) {
|
|
229
|
+
menu(handle: $footerMenuHandle) {
|
|
230
|
+
...Menu
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
${MENU_FRAGMENT}
|
|
234
|
+
` as const;
|