@shopify/create-hydrogen 4.3.13 → 5.0.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.
Files changed (127) hide show
  1. package/dist/assets/hydrogen/bundle/analyzer.html +2045 -0
  2. package/dist/assets/hydrogen/i18n/domains.ts +28 -0
  3. package/dist/assets/hydrogen/i18n/mock-i18n-types.ts +3 -0
  4. package/dist/assets/hydrogen/i18n/subdomains.ts +27 -0
  5. package/dist/assets/hydrogen/i18n/subfolders.ts +29 -0
  6. package/dist/assets/hydrogen/routes/locale-check.ts +16 -0
  7. package/dist/assets/hydrogen/starter/.eslintignore +5 -0
  8. package/dist/assets/hydrogen/starter/.eslintrc.cjs +19 -0
  9. package/dist/assets/hydrogen/starter/.graphqlrc.yml +12 -0
  10. package/dist/assets/hydrogen/starter/CHANGELOG.md +709 -0
  11. package/dist/assets/hydrogen/starter/README.md +45 -0
  12. package/dist/assets/hydrogen/starter/app/assets/favicon.svg +28 -0
  13. package/dist/assets/hydrogen/starter/app/components/AddToCartButton.tsx +37 -0
  14. package/dist/assets/hydrogen/starter/app/components/Aside.tsx +76 -0
  15. package/dist/assets/hydrogen/starter/app/components/CartLineItem.tsx +150 -0
  16. package/dist/assets/hydrogen/starter/app/components/CartMain.tsx +68 -0
  17. package/dist/assets/hydrogen/starter/app/components/CartSummary.tsx +101 -0
  18. package/dist/assets/hydrogen/starter/app/components/Footer.tsx +129 -0
  19. package/dist/assets/hydrogen/starter/app/components/Header.tsx +230 -0
  20. package/dist/assets/hydrogen/starter/app/components/PageLayout.tsx +126 -0
  21. package/dist/assets/hydrogen/starter/app/components/ProductForm.tsx +80 -0
  22. package/dist/assets/hydrogen/starter/app/components/ProductImage.tsx +23 -0
  23. package/dist/assets/hydrogen/starter/app/components/ProductPrice.tsx +27 -0
  24. package/dist/assets/hydrogen/starter/app/components/Search.tsx +514 -0
  25. package/dist/assets/hydrogen/starter/app/entry.client.tsx +12 -0
  26. package/dist/assets/hydrogen/starter/app/entry.server.tsx +47 -0
  27. package/dist/assets/hydrogen/starter/app/graphql/customer-account/CustomerAddressMutations.ts +61 -0
  28. package/dist/assets/hydrogen/starter/app/graphql/customer-account/CustomerDetailsQuery.ts +40 -0
  29. package/dist/assets/hydrogen/starter/app/graphql/customer-account/CustomerOrderQuery.ts +87 -0
  30. package/dist/assets/hydrogen/starter/app/graphql/customer-account/CustomerOrdersQuery.ts +58 -0
  31. package/dist/assets/hydrogen/starter/app/graphql/customer-account/CustomerUpdateMutation.ts +24 -0
  32. package/dist/assets/hydrogen/starter/app/lib/fragments.ts +174 -0
  33. package/dist/assets/hydrogen/starter/app/lib/search.ts +29 -0
  34. package/dist/assets/hydrogen/starter/app/lib/session.ts +72 -0
  35. package/dist/assets/hydrogen/starter/app/lib/variants.ts +46 -0
  36. package/dist/assets/hydrogen/starter/app/root.tsx +191 -0
  37. package/dist/assets/hydrogen/starter/app/routes/$.tsx +11 -0
  38. package/dist/assets/hydrogen/starter/app/routes/[robots.txt].tsx +118 -0
  39. package/dist/assets/hydrogen/starter/app/routes/[sitemap.xml].tsx +177 -0
  40. package/dist/assets/hydrogen/starter/app/routes/_index.tsx +182 -0
  41. package/dist/assets/hydrogen/starter/app/routes/account.$.tsx +8 -0
  42. package/dist/assets/hydrogen/starter/app/routes/account._index.tsx +5 -0
  43. package/dist/assets/hydrogen/starter/app/routes/account.addresses.tsx +513 -0
  44. package/dist/assets/hydrogen/starter/app/routes/account.orders.$id.tsx +195 -0
  45. package/dist/assets/hydrogen/starter/app/routes/account.orders._index.tsx +107 -0
  46. package/dist/assets/hydrogen/starter/app/routes/account.profile.tsx +136 -0
  47. package/dist/assets/hydrogen/starter/app/routes/account.tsx +88 -0
  48. package/dist/assets/hydrogen/starter/app/routes/account_.authorize.tsx +5 -0
  49. package/dist/assets/hydrogen/starter/app/routes/account_.login.tsx +5 -0
  50. package/dist/assets/hydrogen/starter/app/routes/account_.logout.tsx +10 -0
  51. package/dist/assets/hydrogen/starter/app/routes/api.predictive-search.tsx +318 -0
  52. package/dist/assets/hydrogen/starter/app/routes/blogs.$blogHandle.$articleHandle.tsx +113 -0
  53. package/dist/assets/hydrogen/starter/app/routes/blogs.$blogHandle._index.tsx +188 -0
  54. package/dist/assets/hydrogen/starter/app/routes/blogs._index.tsx +119 -0
  55. package/dist/assets/hydrogen/starter/app/routes/cart.$lines.tsx +69 -0
  56. package/dist/assets/hydrogen/starter/app/routes/cart.tsx +102 -0
  57. package/dist/assets/hydrogen/starter/app/routes/collections.$handle.tsx +225 -0
  58. package/dist/assets/hydrogen/starter/app/routes/collections._index.tsx +146 -0
  59. package/dist/assets/hydrogen/starter/app/routes/collections.all.tsx +185 -0
  60. package/dist/assets/hydrogen/starter/app/routes/discount.$code.tsx +47 -0
  61. package/dist/assets/hydrogen/starter/app/routes/pages.$handle.tsx +84 -0
  62. package/dist/assets/hydrogen/starter/app/routes/policies.$handle.tsx +93 -0
  63. package/dist/assets/hydrogen/starter/app/routes/policies._index.tsx +63 -0
  64. package/dist/assets/hydrogen/starter/app/routes/products.$handle.tsx +299 -0
  65. package/dist/assets/hydrogen/starter/app/routes/search.tsx +177 -0
  66. package/dist/assets/hydrogen/starter/app/styles/app.css +486 -0
  67. package/dist/assets/hydrogen/starter/app/styles/reset.css +129 -0
  68. package/dist/assets/hydrogen/starter/customer-accountapi.generated.d.ts +509 -0
  69. package/dist/assets/hydrogen/starter/env.d.ts +54 -0
  70. package/dist/assets/hydrogen/starter/package.json +50 -0
  71. package/dist/assets/hydrogen/starter/public/.gitkeep +0 -0
  72. package/dist/assets/hydrogen/starter/server.ts +119 -0
  73. package/dist/assets/hydrogen/starter/storefrontapi.generated.d.ts +1211 -0
  74. package/dist/assets/hydrogen/starter/tsconfig.json +23 -0
  75. package/dist/assets/hydrogen/starter/vite.config.ts +41 -0
  76. package/dist/assets/hydrogen/tailwind/package.json +8 -0
  77. package/dist/assets/hydrogen/tailwind/tailwind.css +6 -0
  78. package/dist/assets/hydrogen/vanilla-extract/package.json +8 -0
  79. package/dist/assets/hydrogen/virtual-routes/assets/debug-network.css +592 -0
  80. package/dist/assets/hydrogen/virtual-routes/assets/favicon-dark.svg +20 -0
  81. package/dist/assets/hydrogen/virtual-routes/assets/favicon.svg +28 -0
  82. package/dist/assets/hydrogen/virtual-routes/assets/inter-variable-font.woff2 +0 -0
  83. package/dist/assets/hydrogen/virtual-routes/assets/jetbrainsmono-variable-font.woff2 +0 -0
  84. package/dist/assets/hydrogen/virtual-routes/assets/styles.css +238 -0
  85. package/dist/assets/hydrogen/virtual-routes/components/FlameChartWrapper.jsx +123 -0
  86. package/dist/assets/hydrogen/virtual-routes/components/HydrogenLogoBaseBW.jsx +32 -0
  87. package/dist/assets/hydrogen/virtual-routes/components/HydrogenLogoBaseColor.jsx +47 -0
  88. package/dist/assets/hydrogen/virtual-routes/components/IconBanner.jsx +292 -0
  89. package/dist/assets/hydrogen/virtual-routes/components/IconClose.jsx +38 -0
  90. package/dist/assets/hydrogen/virtual-routes/components/IconDiscard.jsx +44 -0
  91. package/dist/assets/hydrogen/virtual-routes/components/IconError.jsx +61 -0
  92. package/dist/assets/hydrogen/virtual-routes/components/IconGithub.jsx +23 -0
  93. package/dist/assets/hydrogen/virtual-routes/components/IconTwitter.jsx +21 -0
  94. package/dist/assets/hydrogen/virtual-routes/components/PageLayout.jsx +7 -0
  95. package/dist/assets/hydrogen/virtual-routes/components/RequestDetails.jsx +178 -0
  96. package/dist/assets/hydrogen/virtual-routes/components/RequestTable.jsx +91 -0
  97. package/dist/assets/hydrogen/virtual-routes/components/RequestWaterfall.jsx +151 -0
  98. package/dist/assets/hydrogen/virtual-routes/lib/useDebugNetworkServer.jsx +178 -0
  99. package/dist/assets/hydrogen/virtual-routes/routes/graphiql.jsx +5 -0
  100. package/dist/assets/hydrogen/virtual-routes/routes/index.jsx +265 -0
  101. package/dist/assets/hydrogen/virtual-routes/routes/subrequest-profiler.jsx +243 -0
  102. package/dist/assets/hydrogen/virtual-routes/virtual-root.jsx +64 -0
  103. package/dist/assets/hydrogen/vite/package.json +14 -0
  104. package/dist/assets/hydrogen/vite/vite.config.js +41 -0
  105. package/dist/chokidar-2CKIHN27.js +12 -0
  106. package/dist/chunk-EO6F7WJJ.js +2 -0
  107. package/dist/chunk-FB327AH7.js +5 -0
  108. package/dist/chunk-FJPX4XUR.js +2 -0
  109. package/dist/chunk-JKOXGRAA.js +10 -0
  110. package/dist/chunk-LNQWGFTB.js +45 -0
  111. package/dist/chunk-M6JXYI3V.js +23 -0
  112. package/dist/chunk-MNT4XW23.js +2 -0
  113. package/dist/chunk-N7HFZHSO.js +1145 -0
  114. package/dist/chunk-PMDMUCNY.js +2 -0
  115. package/dist/chunk-QGLB6FFL.js +3 -0
  116. package/dist/chunk-VMIOG46Y.js +2 -0
  117. package/dist/create-app.js +1867 -34
  118. package/dist/del-CZGKV5SQ.js +11 -0
  119. package/dist/devtools-ZCRGQE64.js +8 -0
  120. package/dist/error-handler-GEQXZJ25.js +2 -0
  121. package/dist/lib-NJYCLW6W.js +22 -0
  122. package/dist/morph-ZJCCGFNC.js +30499 -0
  123. package/dist/multipart-parser-6HGDQWV7.js +3 -0
  124. package/dist/open-OD6DRFEG.js +2 -0
  125. package/dist/out-7KAQXZLP.js +2 -0
  126. package/dist/yoga.wasm +0 -0
  127. package/package.json +7 -3
@@ -0,0 +1,87 @@
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
+ statusPageUrl
49
+ processedAt
50
+ fulfillments(first: 1) {
51
+ nodes {
52
+ status
53
+ }
54
+ }
55
+ totalTax {
56
+ ...OrderMoney
57
+ }
58
+ totalPrice {
59
+ ...OrderMoney
60
+ }
61
+ subtotal {
62
+ ...OrderMoney
63
+ }
64
+ shippingAddress {
65
+ name
66
+ formatted(withName: true)
67
+ formattedArea
68
+ }
69
+ discountApplications(first: 100) {
70
+ nodes {
71
+ ...DiscountApplication
72
+ }
73
+ }
74
+ lineItems(first: 100) {
75
+ nodes {
76
+ ...OrderLineItemFull
77
+ }
78
+ }
79
+ }
80
+ query Order($orderId: ID!) {
81
+ order(id: $orderId) {
82
+ ... on Order {
83
+ ...Order
84
+ }
85
+ }
86
+ }
87
+ ` as const;
@@ -0,0 +1,58 @@
1
+ // 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
+ fulfillments(first: 1) {
10
+ nodes {
11
+ status
12
+ }
13
+ }
14
+ id
15
+ number
16
+ processedAt
17
+ }
18
+ ` as const;
19
+
20
+ // https://shopify.dev/docs/api/customer/latest/objects/Customer
21
+ export const CUSTOMER_ORDERS_FRAGMENT = `#graphql
22
+ fragment CustomerOrders on Customer {
23
+ orders(
24
+ sortKey: PROCESSED_AT,
25
+ reverse: true,
26
+ first: $first,
27
+ last: $last,
28
+ before: $startCursor,
29
+ after: $endCursor
30
+ ) {
31
+ nodes {
32
+ ...OrderItem
33
+ }
34
+ pageInfo {
35
+ hasPreviousPage
36
+ hasNextPage
37
+ endCursor
38
+ startCursor
39
+ }
40
+ }
41
+ }
42
+ ${ORDER_ITEM_FRAGMENT}
43
+ ` as const;
44
+
45
+ // https://shopify.dev/docs/api/customer/latest/queries/customer
46
+ export const CUSTOMER_ORDERS_QUERY = `#graphql
47
+ ${CUSTOMER_ORDERS_FRAGMENT}
48
+ query CustomerOrders(
49
+ $endCursor: String
50
+ $first: Int
51
+ $last: Int
52
+ $startCursor: String
53
+ ) {
54
+ customer {
55
+ ...CustomerOrders
56
+ }
57
+ }
58
+ ` as const;
@@ -0,0 +1,24 @@
1
+ export const CUSTOMER_UPDATE_MUTATION = `#graphql
2
+ # https://shopify.dev/docs/api/customer/latest/mutations/customerUpdate
3
+ mutation customerUpdate(
4
+ $customer: CustomerUpdateInput!
5
+ ){
6
+ customerUpdate(input: $customer) {
7
+ customer {
8
+ firstName
9
+ lastName
10
+ emailAddress {
11
+ emailAddress
12
+ }
13
+ phoneNumber {
14
+ phoneNumber
15
+ }
16
+ }
17
+ userErrors {
18
+ code
19
+ field
20
+ message
21
+ }
22
+ }
23
+ }
24
+ ` as const;
@@ -0,0 +1,174 @@
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 CartApiQuery on Cart {
59
+ updatedAt
60
+ id
61
+ checkoutUrl
62
+ totalQuantity
63
+ buyerIdentity {
64
+ countryCode
65
+ customer {
66
+ id
67
+ email
68
+ firstName
69
+ lastName
70
+ displayName
71
+ }
72
+ email
73
+ phone
74
+ }
75
+ lines(first: $numCartLines) {
76
+ nodes {
77
+ ...CartLine
78
+ }
79
+ }
80
+ cost {
81
+ subtotalAmount {
82
+ ...Money
83
+ }
84
+ totalAmount {
85
+ ...Money
86
+ }
87
+ totalDutyAmount {
88
+ ...Money
89
+ }
90
+ totalTaxAmount {
91
+ ...Money
92
+ }
93
+ }
94
+ note
95
+ attributes {
96
+ key
97
+ value
98
+ }
99
+ discountCodes {
100
+ code
101
+ applicable
102
+ }
103
+ }
104
+ ` as const;
105
+
106
+ const MENU_FRAGMENT = `#graphql
107
+ fragment MenuItem on MenuItem {
108
+ id
109
+ resourceId
110
+ tags
111
+ title
112
+ type
113
+ url
114
+ }
115
+ fragment ChildMenuItem on MenuItem {
116
+ ...MenuItem
117
+ }
118
+ fragment ParentMenuItem on MenuItem {
119
+ ...MenuItem
120
+ items {
121
+ ...ChildMenuItem
122
+ }
123
+ }
124
+ fragment Menu on Menu {
125
+ id
126
+ items {
127
+ ...ParentMenuItem
128
+ }
129
+ }
130
+ ` as const;
131
+
132
+ export const HEADER_QUERY = `#graphql
133
+ fragment Shop on Shop {
134
+ id
135
+ name
136
+ description
137
+ primaryDomain {
138
+ url
139
+ }
140
+ brand {
141
+ logo {
142
+ image {
143
+ url
144
+ }
145
+ }
146
+ }
147
+ }
148
+ query Header(
149
+ $country: CountryCode
150
+ $headerMenuHandle: String!
151
+ $language: LanguageCode
152
+ ) @inContext(language: $language, country: $country) {
153
+ shop {
154
+ ...Shop
155
+ }
156
+ menu(handle: $headerMenuHandle) {
157
+ ...Menu
158
+ }
159
+ }
160
+ ${MENU_FRAGMENT}
161
+ ` as const;
162
+
163
+ export const FOOTER_QUERY = `#graphql
164
+ query Footer(
165
+ $country: CountryCode
166
+ $footerMenuHandle: String!
167
+ $language: LanguageCode
168
+ ) @inContext(language: $language, country: $country) {
169
+ menu(handle: $footerMenuHandle) {
170
+ ...Menu
171
+ }
172
+ }
173
+ ${MENU_FRAGMENT}
174
+ ` as const;
@@ -0,0 +1,29 @@
1
+ import type {
2
+ PredictiveQueryFragment,
3
+ SearchProductFragment,
4
+ PredictiveProductFragment,
5
+ PredictiveCollectionFragment,
6
+ PredictivePageFragment,
7
+ PredictiveArticleFragment,
8
+ } from 'storefrontapi.generated';
9
+
10
+ export function applyTrackingParams(
11
+ resource:
12
+ | PredictiveQueryFragment
13
+ | SearchProductFragment
14
+ | PredictiveProductFragment
15
+ | PredictiveCollectionFragment
16
+ | PredictiveArticleFragment
17
+ | PredictivePageFragment,
18
+ params?: string,
19
+ ) {
20
+ if (params) {
21
+ return resource?.trackingParameters
22
+ ? `?${params}&${resource.trackingParameters}`
23
+ : `?${params}`;
24
+ } else {
25
+ return resource?.trackingParameters
26
+ ? `?${resource.trackingParameters}`
27
+ : '';
28
+ }
29
+ }
@@ -0,0 +1,72 @@
1
+ import type {HydrogenSession} from '@shopify/hydrogen';
2
+ import {
3
+ createCookieSessionStorage,
4
+ type SessionStorage,
5
+ type Session,
6
+ } from '@shopify/remix-oxygen';
7
+
8
+ /**
9
+ * This is a custom session implementation for your Hydrogen shop.
10
+ * Feel free to customize it to your needs, add helper methods, or
11
+ * swap out the cookie-based implementation with something else!
12
+ */
13
+ export class AppSession implements HydrogenSession {
14
+ public isPending = false;
15
+
16
+ #sessionStorage;
17
+ #session;
18
+
19
+ constructor(sessionStorage: SessionStorage, session: Session) {
20
+ this.#sessionStorage = sessionStorage;
21
+ this.#session = session;
22
+ }
23
+
24
+ static async init(request: Request, secrets: string[]) {
25
+ const storage = createCookieSessionStorage({
26
+ cookie: {
27
+ name: 'session',
28
+ httpOnly: true,
29
+ path: '/',
30
+ sameSite: 'lax',
31
+ secrets,
32
+ },
33
+ });
34
+
35
+ const session = await storage
36
+ .getSession(request.headers.get('Cookie'))
37
+ .catch(() => storage.getSession());
38
+
39
+ return new this(storage, session);
40
+ }
41
+
42
+ get has() {
43
+ return this.#session.has;
44
+ }
45
+
46
+ get get() {
47
+ return this.#session.get;
48
+ }
49
+
50
+ get flash() {
51
+ return this.#session.flash;
52
+ }
53
+
54
+ get unset() {
55
+ this.isPending = true;
56
+ return this.#session.unset;
57
+ }
58
+
59
+ get set() {
60
+ this.isPending = true;
61
+ return this.#session.set;
62
+ }
63
+
64
+ destroy() {
65
+ return this.#sessionStorage.destroySession(this.#session);
66
+ }
67
+
68
+ commit() {
69
+ this.isPending = false;
70
+ return this.#sessionStorage.commitSession(this.#session);
71
+ }
72
+ }
@@ -0,0 +1,46 @@
1
+ import {useLocation} from '@remix-run/react';
2
+ import type {SelectedOption} from '@shopify/hydrogen/storefront-api-types';
3
+ import {useMemo} from 'react';
4
+
5
+ export function useVariantUrl(
6
+ handle: string,
7
+ selectedOptions: SelectedOption[],
8
+ ) {
9
+ const {pathname} = useLocation();
10
+
11
+ return useMemo(() => {
12
+ return getVariantUrl({
13
+ handle,
14
+ pathname,
15
+ searchParams: new URLSearchParams(),
16
+ selectedOptions,
17
+ });
18
+ }, [handle, selectedOptions, pathname]);
19
+ }
20
+
21
+ export function getVariantUrl({
22
+ handle,
23
+ pathname,
24
+ searchParams,
25
+ selectedOptions,
26
+ }: {
27
+ handle: string;
28
+ pathname: string;
29
+ searchParams: URLSearchParams;
30
+ selectedOptions: SelectedOption[];
31
+ }) {
32
+ const match = /(\/[a-zA-Z]{2}-[a-zA-Z]{2}\/)/g.exec(pathname);
33
+ const isLocalePathname = match && match.length > 0;
34
+
35
+ const path = isLocalePathname
36
+ ? `${match![0]}products/${handle}`
37
+ : `/products/${handle}`;
38
+
39
+ selectedOptions.forEach((option) => {
40
+ searchParams.set(option.name, option.value);
41
+ });
42
+
43
+ const searchString = searchParams.toString();
44
+
45
+ return path + (searchString ? '?' + searchParams.toString() : '');
46
+ }
@@ -0,0 +1,191 @@
1
+ import {useNonce, getShopAnalytics, Analytics} from '@shopify/hydrogen';
2
+ import {defer, type LoaderFunctionArgs} from '@shopify/remix-oxygen';
3
+ import {
4
+ Links,
5
+ Meta,
6
+ Outlet,
7
+ Scripts,
8
+ useRouteError,
9
+ useRouteLoaderData,
10
+ ScrollRestoration,
11
+ isRouteErrorResponse,
12
+ type ShouldRevalidateFunction,
13
+ } from '@remix-run/react';
14
+ import favicon from '~/assets/favicon.svg';
15
+ import resetStyles from '~/styles/reset.css?url';
16
+ import appStyles from '~/styles/app.css?url';
17
+ import {PageLayout} from '~/components/PageLayout';
18
+ import {FOOTER_QUERY, HEADER_QUERY} from '~/lib/fragments';
19
+
20
+ export type RootLoader = typeof loader;
21
+
22
+ /**
23
+ * This is important to avoid re-fetching root queries on sub-navigations
24
+ */
25
+ export const shouldRevalidate: ShouldRevalidateFunction = ({
26
+ formMethod,
27
+ currentUrl,
28
+ nextUrl,
29
+ }) => {
30
+ // revalidate when a mutation is performed e.g add to cart, login...
31
+ if (formMethod && formMethod !== 'GET') {
32
+ return true;
33
+ }
34
+
35
+ // revalidate when manually revalidating via useRevalidator
36
+ if (currentUrl.toString() === nextUrl.toString()) {
37
+ return true;
38
+ }
39
+
40
+ return false;
41
+ };
42
+
43
+ export function links() {
44
+ return [
45
+ {rel: 'stylesheet', href: resetStyles},
46
+ {rel: 'stylesheet', href: appStyles},
47
+ {
48
+ rel: 'preconnect',
49
+ href: 'https://cdn.shopify.com',
50
+ },
51
+ {
52
+ rel: 'preconnect',
53
+ href: 'https://shop.app',
54
+ },
55
+ {rel: 'icon', type: 'image/svg+xml', href: favicon},
56
+ ];
57
+ }
58
+
59
+ export async function loader(args: LoaderFunctionArgs) {
60
+ // Start fetching non-critical data without blocking time to first byte
61
+ const deferredData = loadDeferredData(args);
62
+
63
+ // Await the critical data required to render initial state of the page
64
+ const criticalData = await loadCriticalData(args);
65
+
66
+ const {storefront, env} = args.context;
67
+
68
+ return defer({
69
+ ...deferredData,
70
+ ...criticalData,
71
+ publicStoreDomain: env.PUBLIC_STORE_DOMAIN,
72
+ shop: getShopAnalytics({
73
+ storefront,
74
+ publicStorefrontId: env.PUBLIC_STOREFRONT_ID,
75
+ }),
76
+ consent: {
77
+ checkoutDomain: env.PUBLIC_CHECKOUT_DOMAIN,
78
+ storefrontAccessToken: env.PUBLIC_STOREFRONT_API_TOKEN,
79
+ },
80
+ });
81
+ }
82
+
83
+ /**
84
+ * Load data necessary for rendering content above the fold. This is the critical data
85
+ * needed to render the page. If it's unavailable, the whole page should 400 or 500 error.
86
+ */
87
+ async function loadCriticalData({context}: LoaderFunctionArgs) {
88
+ const {storefront} = context;
89
+
90
+ const [header] = await Promise.all([
91
+ storefront.query(HEADER_QUERY, {
92
+ cache: storefront.CacheLong(),
93
+ variables: {
94
+ headerMenuHandle: 'main-menu', // Adjust to your header menu handle
95
+ },
96
+ }),
97
+ // Add other queries here, so that they are loaded in parallel
98
+ ]);
99
+
100
+ return {
101
+ header,
102
+ };
103
+ }
104
+
105
+ /**
106
+ * Load data for rendering content below the fold. This data is deferred and will be
107
+ * fetched after the initial page load. If it's unavailable, the page should still 200.
108
+ * Make sure to not throw any errors here, as it will cause the page to 500.
109
+ */
110
+ function loadDeferredData({context}: LoaderFunctionArgs) {
111
+ const {storefront, customerAccount, cart} = context;
112
+
113
+ // defer the footer query (below the fold)
114
+ const footer = storefront
115
+ .query(FOOTER_QUERY, {
116
+ cache: storefront.CacheLong(),
117
+ variables: {
118
+ footerMenuHandle: 'footer', // Adjust to your footer menu handle
119
+ },
120
+ })
121
+ .catch((error) => {
122
+ // Log query errors, but don't throw them so the page can still render
123
+ console.error(error);
124
+ return null;
125
+ });
126
+ return {
127
+ cart: cart.get(),
128
+ isLoggedIn: customerAccount.isLoggedIn(),
129
+ footer,
130
+ };
131
+ }
132
+
133
+ export function Layout({children}: {children?: React.ReactNode}) {
134
+ const nonce = useNonce();
135
+ const data = useRouteLoaderData<RootLoader>('root');
136
+
137
+ return (
138
+ <html lang="en">
139
+ <head>
140
+ <meta charSet="utf-8" />
141
+ <meta name="viewport" content="width=device-width,initial-scale=1" />
142
+ <Meta />
143
+ <Links />
144
+ </head>
145
+ <body>
146
+ {data ? (
147
+ <Analytics.Provider
148
+ cart={data.cart}
149
+ shop={data.shop}
150
+ consent={data.consent}
151
+ >
152
+ <PageLayout {...data}>{children}</PageLayout>
153
+ </Analytics.Provider>
154
+ ) : (
155
+ children
156
+ )}
157
+ <ScrollRestoration nonce={nonce} />
158
+ <Scripts nonce={nonce} />
159
+ </body>
160
+ </html>
161
+ );
162
+ }
163
+
164
+ export default function App() {
165
+ return <Outlet />;
166
+ }
167
+
168
+ export function ErrorBoundary() {
169
+ const error = useRouteError();
170
+ let errorMessage = 'Unknown error';
171
+ let errorStatus = 500;
172
+
173
+ if (isRouteErrorResponse(error)) {
174
+ errorMessage = error?.data?.message ?? error.data;
175
+ errorStatus = error.status;
176
+ } else if (error instanceof Error) {
177
+ errorMessage = error.message;
178
+ }
179
+
180
+ return (
181
+ <div className="route-error">
182
+ <h1>Oops</h1>
183
+ <h2>{errorStatus}</h2>
184
+ {errorMessage && (
185
+ <fieldset>
186
+ <pre>{errorMessage}</pre>
187
+ </fieldset>
188
+ )}
189
+ </div>
190
+ );
191
+ }
@@ -0,0 +1,11 @@
1
+ import type {LoaderFunctionArgs} from '@shopify/remix-oxygen';
2
+
3
+ export async function loader({request}: LoaderFunctionArgs) {
4
+ throw new Response(`${new URL(request.url).pathname} not found`, {
5
+ status: 404,
6
+ });
7
+ }
8
+
9
+ export default function CatchAllPage() {
10
+ return null;
11
+ }