@shopify/cli-hydrogen 8.0.4 → 8.1.1

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 (162) hide show
  1. package/dist/{lib/setups/i18n/templates → assets/hydrogen/i18n}/domains.ts +1 -1
  2. package/dist/assets/hydrogen/i18n/mock-i18n-types.ts +3 -0
  3. package/dist/{lib/setups/i18n/templates → assets/hydrogen/i18n}/subdomains.ts +1 -1
  4. package/dist/{lib/setups/i18n/templates → assets/hydrogen/i18n}/subfolders.ts +1 -1
  5. package/dist/{generator-templates → assets/hydrogen}/starter/.eslintrc.cjs +1 -0
  6. package/dist/{generator-templates → assets/hydrogen}/starter/CHANGELOG.md +31 -0
  7. package/dist/assets/hydrogen/starter/app/components/Aside.tsx +76 -0
  8. package/dist/{generator-templates → assets/hydrogen}/starter/app/components/Cart.tsx +38 -14
  9. package/dist/{generator-templates → assets/hydrogen}/starter/app/components/Footer.tsx +30 -13
  10. package/dist/{generator-templates → assets/hydrogen}/starter/app/components/Header.tsx +52 -12
  11. package/dist/{generator-templates/starter/app/components/Layout.tsx → assets/hydrogen/starter/app/components/PageLayout.tsx} +38 -28
  12. package/dist/{generator-templates → assets/hydrogen}/starter/app/components/Search.tsx +5 -3
  13. package/dist/{generator-templates → assets/hydrogen}/starter/app/entry.server.tsx +8 -2
  14. package/dist/{generator-templates → assets/hydrogen}/starter/app/lib/fragments.ts +70 -0
  15. package/dist/assets/hydrogen/starter/app/root.tsx +204 -0
  16. package/dist/{generator-templates → assets/hydrogen}/starter/app/routes/_index.tsx +62 -25
  17. package/dist/{generator-templates → assets/hydrogen}/starter/app/routes/account.addresses.tsx +0 -1
  18. package/dist/{generator-templates → assets/hydrogen}/starter/app/routes/account.orders.$id.tsx +2 -2
  19. package/dist/{generator-templates → assets/hydrogen}/starter/app/routes/account.profile.tsx +0 -1
  20. package/dist/{generator-templates → assets/hydrogen}/starter/app/routes/api.predictive-search.tsx +6 -1
  21. package/dist/{generator-templates → assets/hydrogen}/starter/app/routes/blogs.$blogHandle.$articleHandle.tsx +34 -8
  22. package/dist/{generator-templates → assets/hydrogen}/starter/app/routes/blogs.$blogHandle._index.tsx +36 -10
  23. package/dist/{generator-templates → assets/hydrogen}/starter/app/routes/blogs._index.tsx +35 -12
  24. package/dist/{generator-templates → assets/hydrogen}/starter/app/routes/cart.tsx +7 -7
  25. package/dist/{generator-templates → assets/hydrogen}/starter/app/routes/collections.$handle.tsx +49 -7
  26. package/dist/{generator-templates → assets/hydrogen}/starter/app/routes/collections._index.tsx +32 -6
  27. package/dist/{generator-templates → assets/hydrogen}/starter/app/routes/collections.all.tsx +31 -6
  28. package/dist/assets/hydrogen/starter/app/routes/pages.$handle.tsx +84 -0
  29. package/dist/{generator-templates → assets/hydrogen}/starter/app/routes/products.$handle.tsx +82 -17
  30. package/dist/{generator-templates → assets/hydrogen}/starter/app/routes/search.tsx +4 -1
  31. package/dist/{generator-templates → assets/hydrogen}/starter/app/styles/app.css +22 -4
  32. package/dist/{generator-templates → assets/hydrogen}/starter/env.d.ts +1 -0
  33. package/dist/{generator-templates → assets/hydrogen}/starter/package.json +10 -10
  34. package/dist/{generator-templates → assets/hydrogen}/starter/storefrontapi.generated.d.ts +3 -1
  35. package/dist/{generator-templates → assets/hydrogen}/starter/vite.config.ts +15 -0
  36. package/dist/{virtual-routes → assets/hydrogen/virtual-routes}/routes/index.jsx +0 -10
  37. package/dist/{generator-templates/assets → assets/hydrogen}/vite/vite.config.js +15 -0
  38. package/dist/commands/hydrogen/build.js +119 -36
  39. package/dist/commands/hydrogen/debug/cpu.js +74 -64
  40. package/dist/commands/hydrogen/deploy.js +4 -4
  41. package/dist/commands/hydrogen/dev.js +133 -51
  42. package/dist/{init.d.ts → commands/hydrogen/init.d.ts} +5 -5
  43. package/dist/commands/hydrogen/init.js +17 -8
  44. package/dist/commands/hydrogen/preview.js +101 -13
  45. package/dist/commands/hydrogen/setup/vite.js +2 -2
  46. package/dist/commands/hydrogen/setup.js +1 -1
  47. package/dist/commands/hydrogen/upgrade.js +53 -50
  48. package/dist/index.d.ts +403 -0
  49. package/dist/index.js +61 -0
  50. package/dist/lib/build.js +48 -37
  51. package/dist/lib/bundle/analyzer.js +2 -4
  52. package/dist/lib/check-version.js +38 -18
  53. package/dist/lib/classic-compiler/build.js +11 -4
  54. package/dist/lib/classic-compiler/debug-cpu.js +52 -0
  55. package/dist/lib/classic-compiler/dev.js +12 -5
  56. package/dist/lib/codegen.js +34 -9
  57. package/dist/lib/cpu-profiler.js +29 -12
  58. package/dist/lib/defer.js +13 -7
  59. package/dist/lib/deps-optimizer.js +146 -0
  60. package/dist/lib/flags.js +2 -7
  61. package/dist/lib/format-code.js +1 -2
  62. package/dist/lib/import-utils.js +22 -0
  63. package/dist/lib/live-reload.js +15 -5
  64. package/dist/lib/log.js +24 -2
  65. package/dist/lib/mini-oxygen/index.js +6 -2
  66. package/dist/lib/mini-oxygen/node.js +18 -4
  67. package/dist/lib/mini-oxygen/workerd.js +18 -5
  68. package/dist/lib/onboarding/local.js +1 -1
  69. package/dist/lib/onboarding/setup-template.mocks.js +7 -11
  70. package/dist/lib/remix-config.js +11 -9
  71. package/dist/lib/remix-version-check.js +8 -13
  72. package/dist/lib/resource-cleanup.js +13 -0
  73. package/dist/lib/setups/css/assets.js +3 -3
  74. package/dist/lib/setups/css/css-modules.js +2 -2
  75. package/dist/lib/setups/css/postcss.js +2 -2
  76. package/dist/lib/setups/css/tailwind.js +2 -2
  77. package/dist/lib/setups/css/vanilla-extract.js +2 -2
  78. package/dist/lib/setups/i18n/index.js +3 -5
  79. package/dist/lib/setups/routes/generate.js +17 -16
  80. package/dist/lib/template-diff.js +120 -42
  81. package/dist/lib/template-downloader.js +6 -11
  82. package/dist/lib/transpile/morph/typedefs.js +17 -0
  83. package/dist/lib/virtual-routes.js +2 -2
  84. package/dist/lib/vite-config.js +2 -1
  85. package/oclif.manifest.json +89 -1
  86. package/package.json +13 -9
  87. package/dist/generator-templates/starter/app/components/Aside.tsx +0 -47
  88. package/dist/generator-templates/starter/app/lib/root-data.ts +0 -11
  89. package/dist/generator-templates/starter/app/root.tsx +0 -227
  90. package/dist/generator-templates/starter/app/routes/pages.$handle.tsx +0 -56
  91. package/dist/lib/setups/i18n/mock-i18n-types.js +0 -1
  92. package/dist/lib/setups/i18n/templates/domains.js +0 -14
  93. package/dist/lib/setups/i18n/templates/subdomains.js +0 -14
  94. package/dist/lib/setups/i18n/templates/subfolders.js +0 -13
  95. package/dist/lib/setups/routes/templates/locale-check.js +0 -9
  96. package/dist/virtual-routes/components/IconDiscord.jsx +0 -21
  97. /package/dist/{lib/bundle/bundle-analyzer.html → assets/hydrogen/bundle/analyzer.html} +0 -0
  98. /package/dist/{generator-templates/assets → assets/hydrogen}/css-modules/package.json +0 -0
  99. /package/dist/{generator-templates/assets → assets/hydrogen}/postcss/package.json +0 -0
  100. /package/dist/{generator-templates/assets → assets/hydrogen}/postcss/postcss.config.js +0 -0
  101. /package/dist/{lib/setups/routes/templates → assets/hydrogen/routes}/locale-check.ts +0 -0
  102. /package/dist/{generator-templates → assets/hydrogen}/starter/.eslintignore +0 -0
  103. /package/dist/{generator-templates → assets/hydrogen}/starter/.graphqlrc.yml +0 -0
  104. /package/dist/{generator-templates → assets/hydrogen}/starter/README.md +0 -0
  105. /package/dist/{generator-templates → assets/hydrogen}/starter/app/assets/favicon.svg +0 -0
  106. /package/dist/{generator-templates → assets/hydrogen}/starter/app/entry.client.tsx +0 -0
  107. /package/dist/{generator-templates → assets/hydrogen}/starter/app/graphql/customer-account/CustomerAddressMutations.ts +0 -0
  108. /package/dist/{generator-templates → assets/hydrogen}/starter/app/graphql/customer-account/CustomerDetailsQuery.ts +0 -0
  109. /package/dist/{generator-templates → assets/hydrogen}/starter/app/graphql/customer-account/CustomerOrderQuery.ts +0 -0
  110. /package/dist/{generator-templates → assets/hydrogen}/starter/app/graphql/customer-account/CustomerOrdersQuery.ts +0 -0
  111. /package/dist/{generator-templates → assets/hydrogen}/starter/app/graphql/customer-account/CustomerUpdateMutation.ts +0 -0
  112. /package/dist/{generator-templates → assets/hydrogen}/starter/app/lib/search.ts +0 -0
  113. /package/dist/{generator-templates → assets/hydrogen}/starter/app/lib/session.ts +0 -0
  114. /package/dist/{generator-templates → assets/hydrogen}/starter/app/lib/variants.ts +0 -0
  115. /package/dist/{generator-templates → assets/hydrogen}/starter/app/routes/$.tsx +0 -0
  116. /package/dist/{generator-templates → assets/hydrogen}/starter/app/routes/[robots.txt].tsx +0 -0
  117. /package/dist/{generator-templates → assets/hydrogen}/starter/app/routes/[sitemap.xml].tsx +0 -0
  118. /package/dist/{generator-templates → assets/hydrogen}/starter/app/routes/account.$.tsx +0 -0
  119. /package/dist/{generator-templates → assets/hydrogen}/starter/app/routes/account._index.tsx +0 -0
  120. /package/dist/{generator-templates → assets/hydrogen}/starter/app/routes/account.orders._index.tsx +0 -0
  121. /package/dist/{generator-templates → assets/hydrogen}/starter/app/routes/account.tsx +0 -0
  122. /package/dist/{generator-templates → assets/hydrogen}/starter/app/routes/account_.authorize.tsx +0 -0
  123. /package/dist/{generator-templates → assets/hydrogen}/starter/app/routes/account_.login.tsx +0 -0
  124. /package/dist/{generator-templates → assets/hydrogen}/starter/app/routes/account_.logout.tsx +0 -0
  125. /package/dist/{generator-templates → assets/hydrogen}/starter/app/routes/cart.$lines.tsx +0 -0
  126. /package/dist/{generator-templates → assets/hydrogen}/starter/app/routes/discount.$code.tsx +0 -0
  127. /package/dist/{generator-templates → assets/hydrogen}/starter/app/routes/policies.$handle.tsx +0 -0
  128. /package/dist/{generator-templates → assets/hydrogen}/starter/app/routes/policies._index.tsx +0 -0
  129. /package/dist/{generator-templates → assets/hydrogen}/starter/app/styles/reset.css +0 -0
  130. /package/dist/{generator-templates → assets/hydrogen}/starter/customer-accountapi.generated.d.ts +0 -0
  131. /package/dist/{generator-templates → assets/hydrogen}/starter/public/.gitkeep +0 -0
  132. /package/dist/{generator-templates → assets/hydrogen}/starter/server.ts +0 -0
  133. /package/dist/{generator-templates → assets/hydrogen}/starter/tsconfig.json +0 -0
  134. /package/dist/{generator-templates/assets → assets/hydrogen}/tailwind/package.json +0 -0
  135. /package/dist/{generator-templates/assets → assets/hydrogen}/tailwind/postcss.config.js +0 -0
  136. /package/dist/{generator-templates/assets → assets/hydrogen}/tailwind/tailwind.config.js +0 -0
  137. /package/dist/{generator-templates/assets → assets/hydrogen}/tailwind/tailwind.css +0 -0
  138. /package/dist/{generator-templates/assets → assets/hydrogen}/vanilla-extract/package.json +0 -0
  139. /package/dist/{virtual-routes → assets/hydrogen/virtual-routes}/assets/debug-network.css +0 -0
  140. /package/dist/{virtual-routes → assets/hydrogen/virtual-routes}/assets/favicon-dark.svg +0 -0
  141. /package/dist/{virtual-routes → assets/hydrogen/virtual-routes}/assets/favicon.svg +0 -0
  142. /package/dist/{virtual-routes → assets/hydrogen/virtual-routes}/assets/inter-variable-font.woff2 +0 -0
  143. /package/dist/{virtual-routes → assets/hydrogen/virtual-routes}/assets/jetbrainsmono-variable-font.woff2 +0 -0
  144. /package/dist/{virtual-routes → assets/hydrogen/virtual-routes}/assets/styles.css +0 -0
  145. /package/dist/{virtual-routes → assets/hydrogen/virtual-routes}/components/FlameChartWrapper.jsx +0 -0
  146. /package/dist/{virtual-routes → assets/hydrogen/virtual-routes}/components/HydrogenLogoBaseBW.jsx +0 -0
  147. /package/dist/{virtual-routes → assets/hydrogen/virtual-routes}/components/HydrogenLogoBaseColor.jsx +0 -0
  148. /package/dist/{virtual-routes → assets/hydrogen/virtual-routes}/components/IconBanner.jsx +0 -0
  149. /package/dist/{virtual-routes → assets/hydrogen/virtual-routes}/components/IconClose.jsx +0 -0
  150. /package/dist/{virtual-routes → assets/hydrogen/virtual-routes}/components/IconDiscard.jsx +0 -0
  151. /package/dist/{virtual-routes → assets/hydrogen/virtual-routes}/components/IconError.jsx +0 -0
  152. /package/dist/{virtual-routes → assets/hydrogen/virtual-routes}/components/IconGithub.jsx +0 -0
  153. /package/dist/{virtual-routes → assets/hydrogen/virtual-routes}/components/IconTwitter.jsx +0 -0
  154. /package/dist/{virtual-routes → assets/hydrogen/virtual-routes}/components/Layout.jsx +0 -0
  155. /package/dist/{virtual-routes → assets/hydrogen/virtual-routes}/components/RequestDetails.jsx +0 -0
  156. /package/dist/{virtual-routes → assets/hydrogen/virtual-routes}/components/RequestTable.jsx +0 -0
  157. /package/dist/{virtual-routes → assets/hydrogen/virtual-routes}/components/RequestWaterfall.jsx +0 -0
  158. /package/dist/{virtual-routes → assets/hydrogen/virtual-routes}/lib/useDebugNetworkServer.jsx +0 -0
  159. /package/dist/{virtual-routes → assets/hydrogen/virtual-routes}/routes/graphiql.jsx +0 -0
  160. /package/dist/{virtual-routes → assets/hydrogen/virtual-routes}/routes/subrequest-profiler.jsx +0 -0
  161. /package/dist/{virtual-routes → assets/hydrogen/virtual-routes}/virtual-root.jsx +0 -0
  162. /package/dist/{generator-templates/assets → assets/hydrogen}/vite/package.json +0 -0
@@ -1,4 +1,4 @@
1
- import type {LanguageCode, CountryCode} from '../mock-i18n-types.js';
1
+ import type {LanguageCode, CountryCode} from './mock-i18n-types.js';
2
2
 
3
3
  export type I18nLocale = {language: LanguageCode; country: CountryCode};
4
4
 
@@ -0,0 +1,3 @@
1
+ // Mock types so we don't need to depend on Hydrogen React
2
+ export type CountryCode = 'US' | 'ES' | 'FR' | 'DE' | 'JP';
3
+ export type LanguageCode = 'EN' | 'ES' | 'FR' | 'DE' | 'JA';
@@ -1,4 +1,4 @@
1
- import type {LanguageCode, CountryCode} from '../mock-i18n-types.js';
1
+ import type {LanguageCode, CountryCode} from './mock-i18n-types.js';
2
2
 
3
3
  export type I18nLocale = {language: LanguageCode; country: CountryCode};
4
4
 
@@ -1,4 +1,4 @@
1
- import type {LanguageCode, CountryCode} from '../mock-i18n-types.js';
1
+ import type {LanguageCode, CountryCode} from './mock-i18n-types.js';
2
2
 
3
3
  export type I18nLocale = {
4
4
  language: LanguageCode;
@@ -14,5 +14,6 @@ module.exports = {
14
14
  'no-useless-escape': 'off',
15
15
  '@typescript-eslint/no-non-null-asserted-optional-chain': 'off',
16
16
  'no-case-declarations': 'off',
17
+ 'no-console': ['warn', {allow: ['warn', 'error']}],
17
18
  },
18
19
  };
@@ -1,5 +1,36 @@
1
1
  # skeleton
2
2
 
3
+ ## 2024.4.7
4
+
5
+ ### Patch Changes
6
+
7
+ - Fix paths on Windows. ([#2243](https://github.com/Shopify/hydrogen/pull/2243)) by [@michenly](https://github.com/michenly)
8
+
9
+ - Updated dependencies [[`31452380`](https://github.com/Shopify/hydrogen/commit/31452380340e079cd4ec1f8c10cdab5e5313e921)]:
10
+ - @shopify/hydrogen@2024.4.5
11
+ - @shopify/cli-hydrogen@8.1.1
12
+
13
+ ## 2024.4.6
14
+
15
+ ### Patch Changes
16
+
17
+ - Updated dependencies [[`707afb96`](https://github.com/Shopify/hydrogen/commit/707afb96fd1ef64a59a14182f60ca61718b372d1)]:
18
+ - @shopify/hydrogen@2024.4.4
19
+
20
+ ## 2024.4.5
21
+
22
+ ### Patch Changes
23
+
24
+ - Update remix to v2.9.2 ([#2135](https://github.com/Shopify/hydrogen/pull/2135)) by [@michenly](https://github.com/michenly)
25
+
26
+ - `<Analytics>` and `useAnalytics` are now stable. ([#2141](https://github.com/Shopify/hydrogen/pull/2141)) by [@wizardlyhel](https://github.com/wizardlyhel)
27
+
28
+ - Update the skeleton template to use React state in the aside dialogs ([#2088](https://github.com/Shopify/hydrogen/pull/2088)) by [@blittle](https://github.com/blittle)
29
+
30
+ - Updated dependencies [[`fe82119f`](https://github.com/Shopify/hydrogen/commit/fe82119f5e75df5a0f727bab6a2186e679abc73d), [`32d4c33e`](https://github.com/Shopify/hydrogen/commit/32d4c33e4421a9c56f62a8c392f5417edddd0402), [`8eea75ec`](https://github.com/Shopify/hydrogen/commit/8eea75ec5ead4de98d7d1b2baedce8511029bcae), [`27e51abf`](https://github.com/Shopify/hydrogen/commit/27e51abfc1f5444afa952c503886bfa12fc55c7e), [`f29c9085`](https://github.com/Shopify/hydrogen/commit/f29c9085eb1adbde9e01226484eba8a85b5074ed), [`7b838beb`](https://github.com/Shopify/hydrogen/commit/7b838beb7c43380ffc9c32c2bb9f34291912fa42), [`d702aec2`](https://github.com/Shopify/hydrogen/commit/d702aec2214646a78cdafc2c25d510489db73f6d), [`ca4cf045`](https://github.com/Shopify/hydrogen/commit/ca4cf045f7fb72ad98b62af7bd172ff8cf553de2), [`5a554b2e`](https://github.com/Shopify/hydrogen/commit/5a554b2e9d91894c2db8032f0c29666dce1ea3f2), [`27e51abf`](https://github.com/Shopify/hydrogen/commit/27e51abfc1f5444afa952c503886bfa12fc55c7e), [`5d6465b3`](https://github.com/Shopify/hydrogen/commit/5d6465b32d90052e5580fcb81d98427bcb8ad528), [`608389d6`](https://github.com/Shopify/hydrogen/commit/608389d6d69c6a9801935d528507c165d1dd4ccf), [`9dfd1cfe`](https://github.com/Shopify/hydrogen/commit/9dfd1cfeb3e96c6d3426427a4b5d97ef475dab6d), [`7def3e9f`](https://github.com/Shopify/hydrogen/commit/7def3e9fa6e28f4fde7af43e2f346aa32267c38e), [`65239c76`](https://github.com/Shopify/hydrogen/commit/65239c76ca1d0b294b59b1ad53624485859c4da5), [`ca7f2888`](https://github.com/Shopify/hydrogen/commit/ca7f28887367a4882e57a67c4e248b0f0bba1c9b)]:
31
+ - @shopify/hydrogen@2024.4.3
32
+ - @shopify/cli-hydrogen@8.1.0
33
+
3
34
  ## 2024.4.4
4
35
 
5
36
  ### Patch Changes
@@ -0,0 +1,76 @@
1
+ import {createContext, type ReactNode, useContext, useState} from 'react';
2
+
3
+ type AsideType = 'search' | 'cart' | 'mobile' | 'closed';
4
+ type AsideContextValue = {
5
+ type: AsideType;
6
+ open: (mode: AsideType) => void;
7
+ close: () => void;
8
+ };
9
+
10
+ /**
11
+ * A side bar component with Overlay
12
+ * @example
13
+ * ```jsx
14
+ * <Aside type="search" heading="SEARCH">
15
+ * <input type="search" />
16
+ * ...
17
+ * </Aside>
18
+ * ```
19
+ */
20
+ export function Aside({
21
+ children,
22
+ heading,
23
+ type,
24
+ }: {
25
+ children?: React.ReactNode;
26
+ type: AsideType;
27
+ heading: React.ReactNode;
28
+ }) {
29
+ const {type: activeType, close} = useAside();
30
+ const expanded = type === activeType;
31
+
32
+ return (
33
+ <div
34
+ aria-modal
35
+ className={`overlay ${expanded ? 'expanded' : ''}`}
36
+ role="dialog"
37
+ >
38
+ <button className="close-outside" onClick={close} />
39
+ <aside>
40
+ <header>
41
+ <h3>{heading}</h3>
42
+ <button className="close reset" onClick={close}>
43
+ &times;
44
+ </button>
45
+ </header>
46
+ <main>{children}</main>
47
+ </aside>
48
+ </div>
49
+ );
50
+ }
51
+
52
+ const AsideContext = createContext<AsideContextValue | null>(null);
53
+
54
+ Aside.Provider = function AsideProvider({children}: {children: ReactNode}) {
55
+ const [type, setType] = useState<AsideType>('closed');
56
+
57
+ return (
58
+ <AsideContext.Provider
59
+ value={{
60
+ type,
61
+ open: setType,
62
+ close: () => setType('closed'),
63
+ }}
64
+ >
65
+ {children}
66
+ </AsideContext.Provider>
67
+ );
68
+ };
69
+
70
+ export function useAside() {
71
+ const aside = useContext(AsideContext);
72
+ if (!aside) {
73
+ throw new Error('useAside must be used within an AsideProvider');
74
+ }
75
+ return aside;
76
+ }
@@ -1,17 +1,25 @@
1
- import {CartForm, Image, Money} from '@shopify/hydrogen';
1
+ import {
2
+ CartForm,
3
+ Image,
4
+ Money,
5
+ useOptimisticCart,
6
+ type OptimisticCart,
7
+ } from '@shopify/hydrogen';
2
8
  import type {CartLineUpdateInput} from '@shopify/hydrogen/storefront-api-types';
3
9
  import {Link} from '@remix-run/react';
4
10
  import type {CartApiQueryFragment} from 'storefrontapi.generated';
5
11
  import {useVariantUrl} from '~/lib/variants';
6
12
 
7
- type CartLine = CartApiQueryFragment['lines']['nodes'][0];
13
+ type CartLine = OptimisticCart<CartApiQueryFragment>['lines']['nodes'][0];
8
14
 
9
15
  type CartMainProps = {
10
16
  cart: CartApiQueryFragment | null;
11
17
  layout: 'page' | 'aside';
12
18
  };
13
19
 
14
- export function CartMain({layout, cart}: CartMainProps) {
20
+ export function CartMain({layout, cart: originalCart}: CartMainProps) {
21
+ const cart = useOptimisticCart(originalCart);
22
+
15
23
  const linesCount = Boolean(cart?.lines?.nodes?.length || 0);
16
24
  const withDiscount =
17
25
  cart &&
@@ -26,12 +34,18 @@ export function CartMain({layout, cart}: CartMainProps) {
26
34
  );
27
35
  }
28
36
 
29
- function CartDetails({layout, cart}: CartMainProps) {
37
+ function CartDetails({
38
+ layout,
39
+ cart,
40
+ }: {
41
+ cart: OptimisticCart<CartApiQueryFragment>;
42
+ layout: 'page' | 'aside';
43
+ }) {
30
44
  const cartHasItems = !!cart && cart.totalQuantity > 0;
31
45
 
32
46
  return (
33
47
  <div className="cart-details">
34
- <CartLines lines={cart?.lines} layout={layout} />
48
+ <CartLines lines={cart?.lines?.nodes} layout={layout} />
35
49
  {cartHasItems && (
36
50
  <CartSummary cost={cart.cost} layout={layout}>
37
51
  <CartDiscounts discountCodes={cart.discountCodes} />
@@ -47,14 +61,14 @@ function CartLines({
47
61
  layout,
48
62
  }: {
49
63
  layout: CartMainProps['layout'];
50
- lines: CartApiQueryFragment['lines'] | undefined;
64
+ lines: CartLine[];
51
65
  }) {
52
66
  if (!lines) return null;
53
67
 
54
68
  return (
55
69
  <div aria-labelledby="cart-lines">
56
70
  <ul>
57
- {lines.nodes.map((line) => (
71
+ {lines.map((line) => (
58
72
  <CartLineItem key={line.id} line={line} layout={layout} />
59
73
  ))}
60
74
  </ul>
@@ -160,21 +174,29 @@ export function CartSummary({
160
174
  );
161
175
  }
162
176
 
163
- function CartLineRemoveButton({lineIds}: {lineIds: string[]}) {
177
+ function CartLineRemoveButton({
178
+ lineIds,
179
+ disabled,
180
+ }: {
181
+ lineIds: string[];
182
+ disabled: boolean;
183
+ }) {
164
184
  return (
165
185
  <CartForm
166
186
  route="/cart"
167
187
  action={CartForm.ACTIONS.LinesRemove}
168
188
  inputs={{lineIds}}
169
189
  >
170
- <button type="submit">Remove</button>
190
+ <button disabled={disabled} type="submit">
191
+ Remove
192
+ </button>
171
193
  </CartForm>
172
194
  );
173
195
  }
174
196
 
175
197
  function CartLineQuantity({line}: {line: CartLine}) {
176
198
  if (!line || typeof line?.quantity === 'undefined') return null;
177
- const {id: lineId, quantity} = line;
199
+ const {id: lineId, quantity, isOptimistic} = line;
178
200
  const prevQuantity = Number(Math.max(0, quantity - 1).toFixed(0));
179
201
  const nextQuantity = Number((quantity + 1).toFixed(0));
180
202
 
@@ -184,7 +206,7 @@ function CartLineQuantity({line}: {line: CartLine}) {
184
206
  <CartLineUpdateButton lines={[{id: lineId, quantity: prevQuantity}]}>
185
207
  <button
186
208
  aria-label="Decrease quantity"
187
- disabled={quantity <= 1}
209
+ disabled={quantity <= 1 || !!isOptimistic}
188
210
  name="decrease-quantity"
189
211
  value={prevQuantity}
190
212
  >
@@ -197,12 +219,13 @@ function CartLineQuantity({line}: {line: CartLine}) {
197
219
  aria-label="Increase quantity"
198
220
  name="increase-quantity"
199
221
  value={nextQuantity}
222
+ disabled={!!isOptimistic}
200
223
  >
201
224
  <span>&#43;</span>
202
225
  </button>
203
226
  </CartLineUpdateButton>
204
227
  &nbsp;
205
- <CartLineRemoveButton lineIds={[lineId]} />
228
+ <CartLineRemoveButton lineIds={[lineId]} disabled={!!isOptimistic} />
206
229
  </div>
207
230
  );
208
231
  }
@@ -216,7 +239,8 @@ function CartLinePrice({
216
239
  priceType?: 'regular' | 'compareAt';
217
240
  [key: string]: any;
218
241
  }) {
219
- if (!line?.cost?.amountPerQuantity || !line?.cost?.totalAmount) return null;
242
+ if (!line?.cost?.amountPerQuantity || !line?.cost?.totalAmount)
243
+ return <div style={{visibility: 'hidden'}}>&nbsp;</div>;
220
244
 
221
245
  const moneyV2 =
222
246
  priceType === 'regular'
@@ -224,7 +248,7 @@ function CartLinePrice({
224
248
  : line.cost.compareAtAmountPerQuantity;
225
249
 
226
250
  if (moneyV2 == null) {
227
- return null;
251
+ return <div style={{visibility: 'hidden'}}>&nbsp;</div>;
228
252
  }
229
253
 
230
254
  return (
@@ -1,29 +1,46 @@
1
- import {NavLink} from '@remix-run/react';
1
+ import {Suspense} from 'react';
2
+ import {Await, NavLink} from '@remix-run/react';
2
3
  import type {FooterQuery, HeaderQuery} from 'storefrontapi.generated';
3
- import {useRootLoaderData} from '~/lib/root-data';
4
+
5
+ interface FooterProps {
6
+ footer: Promise<FooterQuery | null>;
7
+ header: HeaderQuery;
8
+ publicStoreDomain: string;
9
+ }
4
10
 
5
11
  export function Footer({
6
- menu,
7
- shop,
8
- }: FooterQuery & {shop: HeaderQuery['shop']}) {
12
+ footer: footerPromise,
13
+ header,
14
+ publicStoreDomain,
15
+ }: FooterProps) {
9
16
  return (
10
- <footer className="footer">
11
- {menu && shop?.primaryDomain?.url && (
12
- <FooterMenu menu={menu} primaryDomainUrl={shop.primaryDomain.url} />
13
- )}
14
- </footer>
17
+ <Suspense>
18
+ <Await resolve={footerPromise}>
19
+ {(footer) => (
20
+ <footer className="footer">
21
+ {footer?.menu && header.shop.primaryDomain?.url && (
22
+ <FooterMenu
23
+ menu={footer.menu}
24
+ primaryDomainUrl={header.shop.primaryDomain.url}
25
+ publicStoreDomain={publicStoreDomain}
26
+ />
27
+ )}
28
+ </footer>
29
+ )}
30
+ </Await>
31
+ </Suspense>
15
32
  );
16
33
  }
17
34
 
18
35
  function FooterMenu({
19
36
  menu,
20
37
  primaryDomainUrl,
38
+ publicStoreDomain,
21
39
  }: {
22
40
  menu: FooterQuery['menu'];
23
- primaryDomainUrl: HeaderQuery['shop']['primaryDomain']['url'];
41
+ primaryDomainUrl: FooterProps['header']['shop']['primaryDomain']['url'];
42
+ publicStoreDomain: string;
24
43
  }) {
25
- const {publicStoreDomain} = useRootLoaderData();
26
-
27
44
  return (
28
45
  <nav className="footer-menu" role="navigation">
29
46
  {(menu || FALLBACK_FOOTER_MENU).items.map((item) => {
@@ -1,14 +1,24 @@
1
- import {Await, NavLink} from '@remix-run/react';
2
1
  import {Suspense} from 'react';
3
- import type {HeaderQuery} from 'storefrontapi.generated';
4
- import type {LayoutProps} from './Layout';
5
- import {useRootLoaderData} from '~/lib/root-data';
2
+ import {Await, NavLink} from '@remix-run/react';
3
+ import {type CartViewPayload, useAnalytics} from '@shopify/hydrogen';
4
+ import type {HeaderQuery, CartApiQueryFragment} from 'storefrontapi.generated';
5
+ import {useAside} from '~/components/Aside';
6
6
 
7
- type HeaderProps = Pick<LayoutProps, 'header' | 'cart' | 'isLoggedIn'>;
7
+ interface HeaderProps {
8
+ header: HeaderQuery;
9
+ cart: Promise<CartApiQueryFragment | null>;
10
+ isLoggedIn: Promise<boolean>;
11
+ publicStoreDomain: string;
12
+ }
8
13
 
9
14
  type Viewport = 'desktop' | 'mobile';
10
15
 
11
- export function Header({header, isLoggedIn, cart}: HeaderProps) {
16
+ export function Header({
17
+ header,
18
+ isLoggedIn,
19
+ cart,
20
+ publicStoreDomain,
21
+ }: HeaderProps) {
12
22
  const {shop, menu} = header;
13
23
  return (
14
24
  <header className="header">
@@ -19,6 +29,7 @@ export function Header({header, isLoggedIn, cart}: HeaderProps) {
19
29
  menu={menu}
20
30
  viewport="desktop"
21
31
  primaryDomainUrl={header.shop.primaryDomain.url}
32
+ publicStoreDomain={publicStoreDomain}
22
33
  />
23
34
  <HeaderCtas isLoggedIn={isLoggedIn} cart={cart} />
24
35
  </header>
@@ -29,12 +40,13 @@ export function HeaderMenu({
29
40
  menu,
30
41
  primaryDomainUrl,
31
42
  viewport,
43
+ publicStoreDomain,
32
44
  }: {
33
45
  menu: HeaderProps['header']['menu'];
34
- primaryDomainUrl: HeaderQuery['shop']['primaryDomain']['url'];
46
+ primaryDomainUrl: HeaderProps['header']['shop']['primaryDomain']['url'];
35
47
  viewport: Viewport;
48
+ publicStoreDomain: HeaderProps['publicStoreDomain'];
36
49
  }) {
37
- const {publicStoreDomain} = useRootLoaderData();
38
50
  const className = `header-menu-${viewport}`;
39
51
 
40
52
  function closeAside(event: React.MouseEvent<HTMLAnchorElement>) {
@@ -106,19 +118,47 @@ function HeaderCtas({
106
118
  }
107
119
 
108
120
  function HeaderMenuMobileToggle() {
121
+ const {open} = useAside();
109
122
  return (
110
- <a className="header-menu-mobile-toggle" href="#mobile-menu-aside">
123
+ <button
124
+ className="header-menu-mobile-toggle reset"
125
+ onClick={() => open('mobile')}
126
+ >
111
127
  <h3>☰</h3>
112
- </a>
128
+ </button>
113
129
  );
114
130
  }
115
131
 
116
132
  function SearchToggle() {
117
- return <a href="#search-aside">Search</a>;
133
+ const {open} = useAside();
134
+ return (
135
+ <button className="reset" onClick={() => open('search')}>
136
+ Search
137
+ </button>
138
+ );
118
139
  }
119
140
 
120
141
  function CartBadge({count}: {count: number}) {
121
- return <a href="#cart-aside">Cart {count}</a>;
142
+ const {open} = useAside();
143
+ const {publish, shop, cart, prevCart} = useAnalytics();
144
+
145
+ return (
146
+ <a
147
+ href="/cart"
148
+ onClick={(e) => {
149
+ e.preventDefault();
150
+ open('cart');
151
+ publish('cart_viewed', {
152
+ cart,
153
+ prevCart,
154
+ shop,
155
+ url: window.location.href || '',
156
+ } as CartViewPayload);
157
+ }}
158
+ >
159
+ Cart {count}
160
+ </a>
161
+ );
122
162
  }
123
163
 
124
164
  function CartToggle({cart}: Pick<HeaderProps, 'cart'>) {
@@ -14,44 +14,53 @@ import {
14
14
  PredictiveSearchResults,
15
15
  } from '~/components/Search';
16
16
 
17
- export type LayoutProps = {
17
+ interface PageLayoutProps {
18
18
  cart: Promise<CartApiQueryFragment | null>;
19
- children?: React.ReactNode;
20
- footer: Promise<FooterQuery>;
19
+ footer: Promise<FooterQuery | null>;
21
20
  header: HeaderQuery;
22
21
  isLoggedIn: Promise<boolean>;
23
- };
22
+ publicStoreDomain: string;
23
+ children?: React.ReactNode;
24
+ }
24
25
 
25
- export function Layout({
26
+ export function PageLayout({
26
27
  cart,
27
28
  children = null,
28
29
  footer,
29
30
  header,
30
31
  isLoggedIn,
31
- }: LayoutProps) {
32
+ publicStoreDomain,
33
+ }: PageLayoutProps) {
32
34
  return (
33
- <>
35
+ <Aside.Provider>
34
36
  <CartAside cart={cart} />
35
37
  <SearchAside />
36
- <MobileMenuAside menu={header?.menu} shop={header?.shop} />
37
- {header && <Header header={header} cart={cart} isLoggedIn={isLoggedIn} />}
38
+ <MobileMenuAside header={header} publicStoreDomain={publicStoreDomain} />
39
+ {header && (
40
+ <Header
41
+ header={header}
42
+ cart={cart}
43
+ isLoggedIn={isLoggedIn}
44
+ publicStoreDomain={publicStoreDomain}
45
+ />
46
+ )}
38
47
  <main>{children}</main>
39
- <Suspense>
40
- <Await resolve={footer}>
41
- {(footer) => <Footer menu={footer?.menu} shop={header?.shop} />}
42
- </Await>
43
- </Suspense>
44
- </>
48
+ <Footer
49
+ footer={footer}
50
+ header={header}
51
+ publicStoreDomain={publicStoreDomain}
52
+ />
53
+ </Aside.Provider>
45
54
  );
46
55
  }
47
56
 
48
- function CartAside({cart}: {cart: LayoutProps['cart']}) {
57
+ function CartAside({cart}: {cart: PageLayoutProps['cart']}) {
49
58
  return (
50
- <Aside id="cart-aside" heading="CART">
59
+ <Aside type="cart" heading="CART">
51
60
  <Suspense fallback={<p>Loading cart ...</p>}>
52
61
  <Await resolve={cart}>
53
62
  {(cart) => {
54
- return <CartMain cart={cart} layout="aside" />;
63
+ return <CartMain cart={cart!} layout="aside" />;
55
64
  }}
56
65
  </Await>
57
66
  </Suspense>
@@ -61,7 +70,7 @@ function CartAside({cart}: {cart: LayoutProps['cart']}) {
61
70
 
62
71
  function SearchAside() {
63
72
  return (
64
- <Aside id="search-aside" heading="SEARCH">
73
+ <Aside type="search" heading="SEARCH">
65
74
  <div className="predictive-search">
66
75
  <br />
67
76
  <PredictiveSearchForm>
@@ -95,20 +104,21 @@ function SearchAside() {
95
104
  }
96
105
 
97
106
  function MobileMenuAside({
98
- menu,
99
- shop,
107
+ header,
108
+ publicStoreDomain,
100
109
  }: {
101
- menu: HeaderQuery['menu'];
102
- shop: HeaderQuery['shop'];
110
+ header: PageLayoutProps['header'];
111
+ publicStoreDomain: PageLayoutProps['publicStoreDomain'];
103
112
  }) {
104
113
  return (
105
- menu &&
106
- shop?.primaryDomain?.url && (
107
- <Aside id="mobile-menu-aside" heading="MENU">
114
+ header.menu &&
115
+ header.shop.primaryDomain?.url && (
116
+ <Aside type="mobile" heading="MENU">
108
117
  <HeaderMenu
109
- menu={menu}
118
+ menu={header.menu}
110
119
  viewport="mobile"
111
- primaryDomainUrl={shop.primaryDomain.url}
120
+ primaryDomainUrl={header.shop.primaryDomain.url}
121
+ publicStoreDomain={publicStoreDomain}
112
122
  />
113
123
  </Aside>
114
124
  )
@@ -16,6 +16,8 @@ import type {
16
16
  SearchQuery,
17
17
  } from 'storefrontapi.generated';
18
18
 
19
+ import type {PredictiveSearchAPILoader} from '../routes/api.predictive-search';
20
+
19
21
  type PredicticeSearchResultItemImage =
20
22
  | PredictiveCollectionFragment['image']
21
23
  | PredictiveArticleFragment['image']
@@ -257,7 +259,7 @@ export function NoSearchResults() {
257
259
 
258
260
  type ChildrenRenderProps = {
259
261
  fetchResults: (event: React.ChangeEvent<HTMLInputElement>) => void;
260
- fetcher: ReturnType<typeof useFetcher<NormalizedPredictiveSearchResults>>;
262
+ fetcher: ReturnType<typeof useFetcher<PredictiveSearchAPILoader>>;
261
263
  inputRef: React.MutableRefObject<HTMLInputElement | null>;
262
264
  };
263
265
 
@@ -278,7 +280,7 @@ export function PredictiveSearchForm({
278
280
  ...props
279
281
  }: SearchFromProps) {
280
282
  const params = useParams();
281
- const fetcher = useFetcher<NormalizedPredictiveSearchResults>({
283
+ const fetcher = useFetcher<PredictiveSearchAPILoader>({
282
284
  key: 'search',
283
285
  });
284
286
  const inputRef = useRef<HTMLInputElement | null>(null);
@@ -456,7 +458,7 @@ function SearchResultItem({goToSearchResult, item}: SearchResultItemProps) {
456
458
  type UseSearchReturn = NormalizedPredictiveSearch & {
457
459
  searchInputRef: React.MutableRefObject<HTMLInputElement | null>;
458
460
  searchTerm: React.MutableRefObject<string>;
459
- state: ReturnType<typeof useFetcher>['state'];
461
+ state: ReturnType<typeof useFetcher<PredictiveSearchAPILoader>>['state'];
460
462
  };
461
463
 
462
464
  function usePredictiveSearch(): UseSearchReturn {
@@ -1,4 +1,4 @@
1
- import type {EntryContext} from '@shopify/remix-oxygen';
1
+ import type {EntryContext, AppLoadContext} from '@shopify/remix-oxygen';
2
2
  import {RemixServer} from '@remix-run/react';
3
3
  import isbot from 'isbot';
4
4
  import {renderToReadableStream} from 'react-dom/server';
@@ -9,8 +9,14 @@ export default async function handleRequest(
9
9
  responseStatusCode: number,
10
10
  responseHeaders: Headers,
11
11
  remixContext: EntryContext,
12
+ context: AppLoadContext,
12
13
  ) {
13
- const {nonce, header, NonceProvider} = createContentSecurityPolicy();
14
+ const {nonce, header, NonceProvider} = createContentSecurityPolicy({
15
+ shop: {
16
+ checkoutDomain: context.env.PUBLIC_CHECKOUT_DOMAIN,
17
+ storeDomain: context.env.PUBLIC_STORE_DOMAIN,
18
+ }
19
+ });
14
20
 
15
21
  const body = await renderToReadableStream(
16
22
  <NonceProvider>