@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,113 @@
1
+ import {defer, type LoaderFunctionArgs} from '@shopify/remix-oxygen';
2
+ import {useLoaderData, type MetaFunction} from '@remix-run/react';
3
+ import {Image} from '@shopify/hydrogen';
4
+
5
+ export const meta: MetaFunction<typeof loader> = ({data}) => {
6
+ return [{title: `Hydrogen | ${data?.article.title ?? ''} article`}];
7
+ };
8
+
9
+ export async function loader(args: LoaderFunctionArgs) {
10
+ // Start fetching non-critical data without blocking time to first byte
11
+ const deferredData = loadDeferredData(args);
12
+
13
+ // Await the critical data required to render initial state of the page
14
+ const criticalData = await loadCriticalData(args);
15
+
16
+ return defer({...deferredData, ...criticalData});
17
+ }
18
+
19
+ /**
20
+ * Load data necessary for rendering content above the fold. This is the critical data
21
+ * needed to render the page. If it's unavailable, the whole page should 400 or 500 error.
22
+ */
23
+ async function loadCriticalData({context, params}: LoaderFunctionArgs) {
24
+ const {blogHandle, articleHandle} = params;
25
+
26
+ if (!articleHandle || !blogHandle) {
27
+ throw new Response('Not found', {status: 404});
28
+ }
29
+
30
+ const [{blog}] = await Promise.all([
31
+ context.storefront.query(ARTICLE_QUERY, {
32
+ variables: {blogHandle, articleHandle},
33
+ }),
34
+ // Add other queries here, so that they are loaded in parallel
35
+ ]);
36
+
37
+ if (!blog?.articleByHandle) {
38
+ throw new Response(null, {status: 404});
39
+ }
40
+
41
+ const article = blog.articleByHandle;
42
+
43
+ return {article};
44
+ }
45
+
46
+ /**
47
+ * Load data for rendering content below the fold. This data is deferred and will be
48
+ * fetched after the initial page load. If it's unavailable, the page should still 200.
49
+ * Make sure to not throw any errors here, as it will cause the page to 500.
50
+ */
51
+ function loadDeferredData({context}: LoaderFunctionArgs) {
52
+ return {};
53
+ }
54
+
55
+ export default function Article() {
56
+ const {article} = useLoaderData<typeof loader>();
57
+ const {title, image, contentHtml, author} = article;
58
+
59
+ const publishedDate = new Intl.DateTimeFormat('en-US', {
60
+ year: 'numeric',
61
+ month: 'long',
62
+ day: 'numeric',
63
+ }).format(new Date(article.publishedAt));
64
+
65
+ return (
66
+ <div className="article">
67
+ <h1>
68
+ {title}
69
+ <div>
70
+ {publishedDate} &middot; {author?.name}
71
+ </div>
72
+ </h1>
73
+
74
+ {image && <Image data={image} sizes="90vw" loading="eager" />}
75
+ <div
76
+ dangerouslySetInnerHTML={{__html: contentHtml}}
77
+ className="article"
78
+ />
79
+ </div>
80
+ );
81
+ }
82
+
83
+ // NOTE: https://shopify.dev/docs/api/storefront/latest/objects/blog#field-blog-articlebyhandle
84
+ const ARTICLE_QUERY = `#graphql
85
+ query Article(
86
+ $articleHandle: String!
87
+ $blogHandle: String!
88
+ $country: CountryCode
89
+ $language: LanguageCode
90
+ ) @inContext(language: $language, country: $country) {
91
+ blog(handle: $blogHandle) {
92
+ articleByHandle(handle: $articleHandle) {
93
+ title
94
+ contentHtml
95
+ publishedAt
96
+ author: authorV2 {
97
+ name
98
+ }
99
+ image {
100
+ id
101
+ altText
102
+ url
103
+ width
104
+ height
105
+ }
106
+ seo {
107
+ description
108
+ title
109
+ }
110
+ }
111
+ }
112
+ }
113
+ ` as const;
@@ -0,0 +1,188 @@
1
+ import {defer, type LoaderFunctionArgs} from '@shopify/remix-oxygen';
2
+ import {Link, useLoaderData, type MetaFunction} from '@remix-run/react';
3
+ import {Image, Pagination, getPaginationVariables} from '@shopify/hydrogen';
4
+ import type {ArticleItemFragment} from 'storefrontapi.generated';
5
+
6
+ export const meta: MetaFunction<typeof loader> = ({data}) => {
7
+ return [{title: `Hydrogen | ${data?.blog.title ?? ''} blog`}];
8
+ };
9
+
10
+ export async function loader(args: LoaderFunctionArgs) {
11
+ // Start fetching non-critical data without blocking time to first byte
12
+ const deferredData = loadDeferredData(args);
13
+
14
+ // Await the critical data required to render initial state of the page
15
+ const criticalData = await loadCriticalData(args);
16
+
17
+ return defer({...deferredData, ...criticalData});
18
+ }
19
+
20
+ /**
21
+ * Load data necessary for rendering content above the fold. This is the critical data
22
+ * needed to render the page. If it's unavailable, the whole page should 400 or 500 error.
23
+ */
24
+ async function loadCriticalData({
25
+ context,
26
+ request,
27
+ params,
28
+ }: LoaderFunctionArgs) {
29
+ const paginationVariables = getPaginationVariables(request, {
30
+ pageBy: 4,
31
+ });
32
+
33
+ if (!params.blogHandle) {
34
+ throw new Response(`blog not found`, {status: 404});
35
+ }
36
+
37
+ const [{blog}] = await Promise.all([
38
+ context.storefront.query(BLOGS_QUERY, {
39
+ variables: {
40
+ blogHandle: params.blogHandle,
41
+ ...paginationVariables,
42
+ },
43
+ }),
44
+ // Add other queries here, so that they are loaded in parallel
45
+ ]);
46
+
47
+ if (!blog?.articles) {
48
+ throw new Response('Not found', {status: 404});
49
+ }
50
+
51
+ return {blog};
52
+ }
53
+
54
+ /**
55
+ * Load data for rendering content below the fold. This data is deferred and will be
56
+ * fetched after the initial page load. If it's unavailable, the page should still 200.
57
+ * Make sure to not throw any errors here, as it will cause the page to 500.
58
+ */
59
+ function loadDeferredData({context}: LoaderFunctionArgs) {
60
+ return {};
61
+ }
62
+
63
+ export default function Blog() {
64
+ const {blog} = useLoaderData<typeof loader>();
65
+ const {articles} = blog;
66
+
67
+ return (
68
+ <div className="blog">
69
+ <h1>{blog.title}</h1>
70
+ <div className="blog-grid">
71
+ <Pagination connection={articles}>
72
+ {({nodes, isLoading, PreviousLink, NextLink}) => {
73
+ return (
74
+ <>
75
+ <PreviousLink>
76
+ {isLoading ? 'Loading...' : <span>↑ Load previous</span>}
77
+ </PreviousLink>
78
+ {nodes.map((article, index) => {
79
+ return (
80
+ <ArticleItem
81
+ article={article}
82
+ key={article.id}
83
+ loading={index < 2 ? 'eager' : 'lazy'}
84
+ />
85
+ );
86
+ })}
87
+ <NextLink>
88
+ {isLoading ? 'Loading...' : <span>Load more ↓</span>}
89
+ </NextLink>
90
+ </>
91
+ );
92
+ }}
93
+ </Pagination>
94
+ </div>
95
+ </div>
96
+ );
97
+ }
98
+
99
+ function ArticleItem({
100
+ article,
101
+ loading,
102
+ }: {
103
+ article: ArticleItemFragment;
104
+ loading?: HTMLImageElement['loading'];
105
+ }) {
106
+ const publishedAt = new Intl.DateTimeFormat('en-US', {
107
+ year: 'numeric',
108
+ month: 'long',
109
+ day: 'numeric',
110
+ }).format(new Date(article.publishedAt!));
111
+ return (
112
+ <div className="blog-article" key={article.id}>
113
+ <Link to={`/blogs/${article.blog.handle}/${article.handle}`}>
114
+ {article.image && (
115
+ <div className="blog-article-image">
116
+ <Image
117
+ alt={article.image.altText || article.title}
118
+ aspectRatio="3/2"
119
+ data={article.image}
120
+ loading={loading}
121
+ sizes="(min-width: 768px) 50vw, 100vw"
122
+ />
123
+ </div>
124
+ )}
125
+ <h3>{article.title}</h3>
126
+ <small>{publishedAt}</small>
127
+ </Link>
128
+ </div>
129
+ );
130
+ }
131
+
132
+ // NOTE: https://shopify.dev/docs/api/storefront/latest/objects/blog
133
+ const BLOGS_QUERY = `#graphql
134
+ query Blog(
135
+ $language: LanguageCode
136
+ $blogHandle: String!
137
+ $first: Int
138
+ $last: Int
139
+ $startCursor: String
140
+ $endCursor: String
141
+ ) @inContext(language: $language) {
142
+ blog(handle: $blogHandle) {
143
+ title
144
+ seo {
145
+ title
146
+ description
147
+ }
148
+ articles(
149
+ first: $first,
150
+ last: $last,
151
+ before: $startCursor,
152
+ after: $endCursor
153
+ ) {
154
+ nodes {
155
+ ...ArticleItem
156
+ }
157
+ pageInfo {
158
+ hasPreviousPage
159
+ hasNextPage
160
+ hasNextPage
161
+ endCursor
162
+ startCursor
163
+ }
164
+
165
+ }
166
+ }
167
+ }
168
+ fragment ArticleItem on Article {
169
+ author: authorV2 {
170
+ name
171
+ }
172
+ contentHtml
173
+ handle
174
+ id
175
+ image {
176
+ id
177
+ altText
178
+ url
179
+ width
180
+ height
181
+ }
182
+ publishedAt
183
+ title
184
+ blog {
185
+ handle
186
+ }
187
+ }
188
+ ` as const;
@@ -0,0 +1,119 @@
1
+ import {defer, type LoaderFunctionArgs} from '@shopify/remix-oxygen';
2
+ import {Link, useLoaderData, type MetaFunction} from '@remix-run/react';
3
+ import {Pagination, getPaginationVariables} from '@shopify/hydrogen';
4
+
5
+ export const meta: MetaFunction = () => {
6
+ return [{title: `Hydrogen | Blogs`}];
7
+ };
8
+
9
+ export async function loader(args: LoaderFunctionArgs) {
10
+ // Start fetching non-critical data without blocking time to first byte
11
+ const deferredData = loadDeferredData(args);
12
+
13
+ // Await the critical data required to render initial state of the page
14
+ const criticalData = await loadCriticalData(args);
15
+
16
+ return defer({...deferredData, ...criticalData});
17
+ }
18
+
19
+ /**
20
+ * Load data necessary for rendering content above the fold. This is the critical data
21
+ * needed to render the page. If it's unavailable, the whole page should 400 or 500 error.
22
+ */
23
+ async function loadCriticalData({context, request}: LoaderFunctionArgs) {
24
+ const paginationVariables = getPaginationVariables(request, {
25
+ pageBy: 10,
26
+ });
27
+
28
+ const [{blogs}] = await Promise.all([
29
+ context.storefront.query(BLOGS_QUERY, {
30
+ variables: {
31
+ ...paginationVariables,
32
+ },
33
+ }),
34
+ // Add other queries here, so that they are loaded in parallel
35
+ ]);
36
+
37
+ return {blogs};
38
+ }
39
+
40
+ /**
41
+ * Load data for rendering content below the fold. This data is deferred and will be
42
+ * fetched after the initial page load. If it's unavailable, the page should still 200.
43
+ * Make sure to not throw any errors here, as it will cause the page to 500.
44
+ */
45
+ function loadDeferredData({context}: LoaderFunctionArgs) {
46
+ return {};
47
+ }
48
+
49
+ export default function Blogs() {
50
+ const {blogs} = useLoaderData<typeof loader>();
51
+
52
+ return (
53
+ <div className="blogs">
54
+ <h1>Blogs</h1>
55
+ <div className="blogs-grid">
56
+ <Pagination connection={blogs}>
57
+ {({nodes, isLoading, PreviousLink, NextLink}) => {
58
+ return (
59
+ <>
60
+ <PreviousLink>
61
+ {isLoading ? 'Loading...' : <span>↑ Load previous</span>}
62
+ </PreviousLink>
63
+ {nodes.map((blog) => {
64
+ return (
65
+ <Link
66
+ className="blog"
67
+ key={blog.handle}
68
+ prefetch="intent"
69
+ to={`/blogs/${blog.handle}`}
70
+ >
71
+ <h2>{blog.title}</h2>
72
+ </Link>
73
+ );
74
+ })}
75
+ <NextLink>
76
+ {isLoading ? 'Loading...' : <span>Load more ↓</span>}
77
+ </NextLink>
78
+ </>
79
+ );
80
+ }}
81
+ </Pagination>
82
+ </div>
83
+ </div>
84
+ );
85
+ }
86
+
87
+ // NOTE: https://shopify.dev/docs/api/storefront/latest/objects/blog
88
+ const BLOGS_QUERY = `#graphql
89
+ query Blogs(
90
+ $country: CountryCode
91
+ $endCursor: String
92
+ $first: Int
93
+ $language: LanguageCode
94
+ $last: Int
95
+ $startCursor: String
96
+ ) @inContext(country: $country, language: $language) {
97
+ blogs(
98
+ first: $first,
99
+ last: $last,
100
+ before: $startCursor,
101
+ after: $endCursor
102
+ ) {
103
+ pageInfo {
104
+ hasNextPage
105
+ hasPreviousPage
106
+ startCursor
107
+ endCursor
108
+ }
109
+ nodes {
110
+ title
111
+ handle
112
+ seo {
113
+ title
114
+ description
115
+ }
116
+ }
117
+ }
118
+ }
119
+ ` as const;
@@ -0,0 +1,69 @@
1
+ import {redirect, type LoaderFunctionArgs} from '@shopify/remix-oxygen';
2
+
3
+ /**
4
+ * Automatically creates a new cart based on the URL and redirects straight to checkout.
5
+ * Expected URL structure:
6
+ * ```js
7
+ * /cart/<variant_id>:<quantity>
8
+ *
9
+ * ```
10
+ *
11
+ * More than one `<variant_id>:<quantity>` separated by a comma, can be supplied in the URL, for
12
+ * carts with more than one product variant.
13
+ *
14
+ * @example
15
+ * Example path creating a cart with two product variants, different quantities, and a discount code in the querystring:
16
+ * ```js
17
+ * /cart/41007289663544:1,41007289696312:2?discount=HYDROBOARD
18
+ *
19
+ * ```
20
+ */
21
+ export async function loader({request, context, params}: LoaderFunctionArgs) {
22
+ const {cart} = context;
23
+ const {lines} = params;
24
+ if (!lines) return redirect('/cart');
25
+ const linesMap = lines.split(',').map((line) => {
26
+ const lineDetails = line.split(':');
27
+ const variantId = lineDetails[0];
28
+ const quantity = parseInt(lineDetails[1], 10);
29
+
30
+ return {
31
+ merchandiseId: `gid://shopify/ProductVariant/${variantId}`,
32
+ quantity,
33
+ };
34
+ });
35
+
36
+ const url = new URL(request.url);
37
+ const searchParams = new URLSearchParams(url.search);
38
+
39
+ const discount = searchParams.get('discount');
40
+ const discountArray = discount ? [discount] : [];
41
+
42
+ // create a cart
43
+ const result = await cart.create({
44
+ lines: linesMap,
45
+ discountCodes: discountArray,
46
+ });
47
+
48
+ const cartResult = result.cart;
49
+
50
+ if (result.errors?.length || !cartResult) {
51
+ throw new Response('Link may be expired. Try checking the URL.', {
52
+ status: 410,
53
+ });
54
+ }
55
+
56
+ // Update cart id in cookie
57
+ const headers = cart.setCartId(cartResult.id);
58
+
59
+ // redirect to checkout
60
+ if (cartResult.checkoutUrl) {
61
+ return redirect(cartResult.checkoutUrl, {headers});
62
+ } else {
63
+ throw new Error('No checkout URL found');
64
+ }
65
+ }
66
+
67
+ export default function Component() {
68
+ return null;
69
+ }
@@ -0,0 +1,102 @@
1
+ import {Await, type MetaFunction, useRouteLoaderData} from '@remix-run/react';
2
+ import {Suspense} from 'react';
3
+ import type {CartQueryDataReturn} from '@shopify/hydrogen';
4
+ import {CartForm} from '@shopify/hydrogen';
5
+ import {json, type ActionFunctionArgs} from '@shopify/remix-oxygen';
6
+ import {CartMain} from '~/components/CartMain';
7
+ import type {RootLoader} from '~/root';
8
+
9
+ export const meta: MetaFunction = () => {
10
+ return [{title: `Hydrogen | Cart`}];
11
+ };
12
+
13
+ export async function action({request, context}: ActionFunctionArgs) {
14
+ const {cart} = context;
15
+
16
+ const formData = await request.formData();
17
+
18
+ const {action, inputs} = CartForm.getFormInput(formData);
19
+
20
+ if (!action) {
21
+ throw new Error('No action provided');
22
+ }
23
+
24
+ let status = 200;
25
+ let result: CartQueryDataReturn;
26
+
27
+ switch (action) {
28
+ case CartForm.ACTIONS.LinesAdd:
29
+ result = await cart.addLines(inputs.lines);
30
+ break;
31
+ case CartForm.ACTIONS.LinesUpdate:
32
+ result = await cart.updateLines(inputs.lines);
33
+ break;
34
+ case CartForm.ACTIONS.LinesRemove:
35
+ result = await cart.removeLines(inputs.lineIds);
36
+ break;
37
+ case CartForm.ACTIONS.DiscountCodesUpdate: {
38
+ const formDiscountCode = inputs.discountCode;
39
+
40
+ // User inputted discount code
41
+ const discountCodes = (
42
+ formDiscountCode ? [formDiscountCode] : []
43
+ ) as string[];
44
+
45
+ // Combine discount codes already applied on cart
46
+ discountCodes.push(...inputs.discountCodes);
47
+
48
+ result = await cart.updateDiscountCodes(discountCodes);
49
+ break;
50
+ }
51
+ case CartForm.ACTIONS.BuyerIdentityUpdate: {
52
+ result = await cart.updateBuyerIdentity({
53
+ ...inputs.buyerIdentity,
54
+ });
55
+ break;
56
+ }
57
+ default:
58
+ throw new Error(`${action} cart action is not defined`);
59
+ }
60
+
61
+ const cartId = result?.cart?.id;
62
+ const headers = cartId ? cart.setCartId(result.cart.id) : new Headers();
63
+ const {cart: cartResult, errors} = result;
64
+
65
+ const redirectTo = formData.get('redirectTo') ?? null;
66
+ if (typeof redirectTo === 'string') {
67
+ status = 303;
68
+ headers.set('Location', redirectTo);
69
+ }
70
+
71
+ return json(
72
+ {
73
+ cart: cartResult,
74
+ errors,
75
+ analytics: {
76
+ cartId,
77
+ },
78
+ },
79
+ {status, headers},
80
+ );
81
+ }
82
+
83
+ export default function Cart() {
84
+ const rootData = useRouteLoaderData<RootLoader>('root');
85
+ if (!rootData) return null;
86
+
87
+ return (
88
+ <div className="cart">
89
+ <h1>Cart</h1>
90
+ <Suspense fallback={<p>Loading cart ...</p>}>
91
+ <Await
92
+ resolve={rootData.cart}
93
+ errorElement={<div>An error occurred</div>}
94
+ >
95
+ {(cart) => {
96
+ return <CartMain layout="page" cart={cart} />;
97
+ }}
98
+ </Await>
99
+ </Suspense>
100
+ </div>
101
+ );
102
+ }