@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,45 @@
1
+ # Hydrogen template: Skeleton
2
+
3
+ Hydrogen is Shopify’s stack for headless commerce. Hydrogen is designed to dovetail with [Remix](https://remix.run/), Shopify’s full stack web framework. This template contains a **minimal setup** of components, queries and tooling to get started with Hydrogen.
4
+
5
+ [Check out Hydrogen docs](https://shopify.dev/custom-storefronts/hydrogen)
6
+ [Get familiar with Remix](https://remix.run/docs/en/v1)
7
+
8
+ ## What's included
9
+
10
+ - Remix
11
+ - Hydrogen
12
+ - Oxygen
13
+ - Vite
14
+ - Shopify CLI
15
+ - ESLint
16
+ - Prettier
17
+ - GraphQL generator
18
+ - TypeScript and JavaScript flavors
19
+ - Minimal setup of components and routes
20
+
21
+ ## Getting started
22
+
23
+ **Requirements:**
24
+
25
+ - Node.js version 18.0.0 or higher
26
+
27
+ ```bash
28
+ npm create @shopify/hydrogen@latest
29
+ ```
30
+
31
+ ## Building for production
32
+
33
+ ```bash
34
+ npm run build
35
+ ```
36
+
37
+ ## Local development
38
+
39
+ ```bash
40
+ npm run dev
41
+ ```
42
+
43
+ ## Setup for using Customer Account API (`/account` section)
44
+
45
+ Follow step 1 and 2 of <https://shopify.dev/docs/custom-storefronts/building-with-the-customer-account-api/hydrogen#step-1-set-up-a-public-domain-for-local-development>
@@ -0,0 +1,28 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="none">
2
+ <style>
3
+ .stroke {
4
+ stroke: #000;
5
+ }
6
+ .fill {
7
+ fill: #000;
8
+ }
9
+ @media (prefers-color-scheme: dark) {
10
+ .stroke {
11
+ stroke: #fff;
12
+ }
13
+ .fill {
14
+ fill: #fff;
15
+ }
16
+ }
17
+ </style>
18
+ <path
19
+ class="stroke"
20
+ fill-rule="evenodd"
21
+ d="M16.1 16.04 1 8.02 6.16 5.3l5.82 3.09 4.88-2.57-5.82-3.1L16.21 0l15.1 8.02-5.17 2.72-5.5-2.91-4.88 2.57 5.5 2.92-5.16 2.72Z"
22
+ />
23
+ <path
24
+ class="fill"
25
+ fill-rule="evenodd"
26
+ d="M16.1 32 1 23.98l5.16-2.72 5.82 3.08 4.88-2.57-5.82-3.08 5.17-2.73 15.1 8.02-5.17 2.72-5.5-2.92-4.88 2.58 5.5 2.92L16.1 32Z"
27
+ />
28
+ </svg>
@@ -0,0 +1,37 @@
1
+ import {type FetcherWithComponents} from '@remix-run/react';
2
+ import {CartForm, type OptimisticCartLineInput} from '@shopify/hydrogen';
3
+
4
+ export function AddToCartButton({
5
+ analytics,
6
+ children,
7
+ disabled,
8
+ lines,
9
+ onClick,
10
+ }: {
11
+ analytics?: unknown;
12
+ children: React.ReactNode;
13
+ disabled?: boolean;
14
+ lines: Array<OptimisticCartLineInput>;
15
+ onClick?: () => void;
16
+ }) {
17
+ return (
18
+ <CartForm route="/cart" inputs={{lines}} action={CartForm.ACTIONS.LinesAdd}>
19
+ {(fetcher: FetcherWithComponents<any>) => (
20
+ <>
21
+ <input
22
+ name="analytics"
23
+ type="hidden"
24
+ value={JSON.stringify(analytics)}
25
+ />
26
+ <button
27
+ type="submit"
28
+ onClick={onClick}
29
+ disabled={disabled ?? fetcher.state !== 'idle'}
30
+ >
31
+ {children}
32
+ </button>
33
+ </>
34
+ )}
35
+ </CartForm>
36
+ );
37
+ }
@@ -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
+ }
@@ -0,0 +1,150 @@
1
+ import type {CartLineUpdateInput} from '@shopify/hydrogen/storefront-api-types';
2
+ import type {CartLayout} from '~/components/CartMain';
3
+ import {CartForm, Image, type OptimisticCartLine} from '@shopify/hydrogen';
4
+ import {useVariantUrl} from '~/lib/variants';
5
+ import {Link} from '@remix-run/react';
6
+ import {ProductPrice} from './ProductPrice';
7
+ import {useAside} from './Aside';
8
+
9
+ /**
10
+ * A single line item in the cart. It displays the product image, title, price.
11
+ * It also provides controls to update the quantity or remove the line item.
12
+ */
13
+ export function CartLineItem({
14
+ layout,
15
+ line,
16
+ }: {
17
+ layout: CartLayout;
18
+ line: OptimisticCartLine;
19
+ }) {
20
+ const {id, merchandise} = line;
21
+ const {product, title, image, selectedOptions} = merchandise;
22
+ const lineItemUrl = useVariantUrl(product.handle, selectedOptions);
23
+ const {close} = useAside();
24
+
25
+ return (
26
+ <li key={id} className="cart-line">
27
+ {image && (
28
+ <Image
29
+ alt={title}
30
+ aspectRatio="1/1"
31
+ data={image}
32
+ height={100}
33
+ loading="lazy"
34
+ width={100}
35
+ />
36
+ )}
37
+
38
+ <div>
39
+ <Link
40
+ prefetch="intent"
41
+ to={lineItemUrl}
42
+ onClick={() => {
43
+ if (layout === 'aside') {
44
+ close();
45
+ }
46
+ }}
47
+ >
48
+ <p>
49
+ <strong>{product.title}</strong>
50
+ </p>
51
+ </Link>
52
+ <ProductPrice price={line?.cost?.totalAmount} />
53
+ <ul>
54
+ {selectedOptions.map((option) => (
55
+ <li key={option.name}>
56
+ <small>
57
+ {option.name}: {option.value}
58
+ </small>
59
+ </li>
60
+ ))}
61
+ </ul>
62
+ <CartLineQuantity line={line} />
63
+ </div>
64
+ </li>
65
+ );
66
+ }
67
+
68
+ /**
69
+ * Provides the controls to update the quantity of a line item in the cart.
70
+ * These controls are disabled when the line item is new, and the server
71
+ * hasn't yet responded that it was successfully added to the cart.
72
+ */
73
+ function CartLineQuantity({line}: {line: OptimisticCartLine}) {
74
+ if (!line || typeof line?.quantity === 'undefined') return null;
75
+ const {id: lineId, quantity, isOptimistic} = line;
76
+ const prevQuantity = Number(Math.max(0, quantity - 1).toFixed(0));
77
+ const nextQuantity = Number((quantity + 1).toFixed(0));
78
+
79
+ return (
80
+ <div className="cart-line-quantity">
81
+ <small>Quantity: {quantity} &nbsp;&nbsp;</small>
82
+ <CartLineUpdateButton lines={[{id: lineId, quantity: prevQuantity}]}>
83
+ <button
84
+ aria-label="Decrease quantity"
85
+ disabled={quantity <= 1 || !!isOptimistic}
86
+ name="decrease-quantity"
87
+ value={prevQuantity}
88
+ >
89
+ <span>&#8722; </span>
90
+ </button>
91
+ </CartLineUpdateButton>
92
+ &nbsp;
93
+ <CartLineUpdateButton lines={[{id: lineId, quantity: nextQuantity}]}>
94
+ <button
95
+ aria-label="Increase quantity"
96
+ name="increase-quantity"
97
+ value={nextQuantity}
98
+ disabled={!!isOptimistic}
99
+ >
100
+ <span>&#43;</span>
101
+ </button>
102
+ </CartLineUpdateButton>
103
+ &nbsp;
104
+ <CartLineRemoveButton lineIds={[lineId]} disabled={!!isOptimistic} />
105
+ </div>
106
+ );
107
+ }
108
+
109
+ /**
110
+ * A button that removes a line item from the cart. It is disabled
111
+ * when the line item is new, and the server hasn't yet responded
112
+ * that it was successfully added to the cart.
113
+ */
114
+ function CartLineRemoveButton({
115
+ lineIds,
116
+ disabled,
117
+ }: {
118
+ lineIds: string[];
119
+ disabled: boolean;
120
+ }) {
121
+ return (
122
+ <CartForm
123
+ route="/cart"
124
+ action={CartForm.ACTIONS.LinesRemove}
125
+ inputs={{lineIds}}
126
+ >
127
+ <button disabled={disabled} type="submit">
128
+ Remove
129
+ </button>
130
+ </CartForm>
131
+ );
132
+ }
133
+
134
+ function CartLineUpdateButton({
135
+ children,
136
+ lines,
137
+ }: {
138
+ children: React.ReactNode;
139
+ lines: CartLineUpdateInput[];
140
+ }) {
141
+ return (
142
+ <CartForm
143
+ route="/cart"
144
+ action={CartForm.ACTIONS.LinesUpdate}
145
+ inputs={{lines}}
146
+ >
147
+ {children}
148
+ </CartForm>
149
+ );
150
+ }
@@ -0,0 +1,68 @@
1
+ import {type OptimisticCartLine, useOptimisticCart} from '@shopify/hydrogen';
2
+ import {Link} from '@remix-run/react';
3
+ import type {CartApiQueryFragment} from 'storefrontapi.generated';
4
+ import {useAside} from '~/components/Aside';
5
+ import {CartLineItem} from '~/components/CartLineItem';
6
+ import {CartSummary} from './CartSummary';
7
+
8
+ export type CartLayout = 'page' | 'aside';
9
+
10
+ export type CartMainProps = {
11
+ cart: CartApiQueryFragment | null;
12
+ layout: CartLayout;
13
+ };
14
+
15
+ /**
16
+ * The main cart component that displays the cart items and summary.
17
+ * It is used by both the /cart route and the cart aside dialog.
18
+ */
19
+ export function CartMain({layout, cart: originalCart}: CartMainProps) {
20
+ // The useOptimisticCart hook applies pending actions to the cart
21
+ // so the user immediately sees feedback when they modify the cart.
22
+ const cart = useOptimisticCart(originalCart);
23
+
24
+ const linesCount = Boolean(cart?.lines?.nodes?.length || 0);
25
+ const withDiscount =
26
+ cart &&
27
+ Boolean(cart?.discountCodes?.filter((code) => code.applicable)?.length);
28
+ const className = `cart-main ${withDiscount ? 'with-discount' : ''}`;
29
+ const cartHasItems = cart?.totalQuantity! > 0;
30
+
31
+ return (
32
+ <div className={className}>
33
+ <CartEmpty hidden={linesCount} layout={layout} />
34
+ <div className="cart-details">
35
+ <div aria-labelledby="cart-lines">
36
+ <ul>
37
+ {(cart?.lines?.nodes ?? []).map((line: OptimisticCartLine) => (
38
+ <CartLineItem key={line.id} line={line} layout={layout} />
39
+ ))}
40
+ </ul>
41
+ </div>
42
+ {cartHasItems && <CartSummary cart={cart} layout={layout} />}
43
+ </div>
44
+ </div>
45
+ );
46
+ }
47
+
48
+ function CartEmpty({
49
+ hidden = false,
50
+ }: {
51
+ hidden: boolean;
52
+ layout?: CartMainProps['layout'];
53
+ }) {
54
+ const {close} = useAside();
55
+ return (
56
+ <div hidden={hidden}>
57
+ <br />
58
+ <p>
59
+ Looks like you haven&rsquo;t added anything yet, let&rsquo;s get you
60
+ started!
61
+ </p>
62
+ <br />
63
+ <Link to="/collections" onClick={close} prefetch="viewport">
64
+ Continue shopping →
65
+ </Link>
66
+ </div>
67
+ );
68
+ }
@@ -0,0 +1,101 @@
1
+ import type {CartApiQueryFragment} from 'storefrontapi.generated';
2
+ import type {CartLayout} from '~/components/CartMain';
3
+ import {CartForm, Money, type OptimisticCart} from '@shopify/hydrogen';
4
+
5
+ type CartSummaryProps = {
6
+ cart: OptimisticCart<CartApiQueryFragment | null>;
7
+ layout: CartLayout;
8
+ };
9
+
10
+ export function CartSummary({cart, layout}: CartSummaryProps) {
11
+ const className =
12
+ layout === 'page' ? 'cart-summary-page' : 'cart-summary-aside';
13
+
14
+ return (
15
+ <div aria-labelledby="cart-summary" className={className}>
16
+ <h4>Totals</h4>
17
+ <dl className="cart-subtotal">
18
+ <dt>Subtotal</dt>
19
+ <dd>
20
+ {cart.cost?.subtotalAmount?.amount ? (
21
+ <Money data={cart.cost?.subtotalAmount} />
22
+ ) : (
23
+ '-'
24
+ )}
25
+ </dd>
26
+ </dl>
27
+ <CartDiscounts discountCodes={cart.discountCodes} />
28
+ <CartCheckoutActions checkoutUrl={cart.checkoutUrl} />
29
+ </div>
30
+ );
31
+ }
32
+ function CartCheckoutActions({checkoutUrl}: {checkoutUrl?: string}) {
33
+ if (!checkoutUrl) return null;
34
+
35
+ return (
36
+ <div>
37
+ <a href={checkoutUrl} target="_self">
38
+ <p>Continue to Checkout &rarr;</p>
39
+ </a>
40
+ <br />
41
+ </div>
42
+ );
43
+ }
44
+
45
+ function CartDiscounts({
46
+ discountCodes,
47
+ }: {
48
+ discountCodes?: CartApiQueryFragment['discountCodes'];
49
+ }) {
50
+ const codes: string[] =
51
+ discountCodes
52
+ ?.filter((discount) => discount.applicable)
53
+ ?.map(({code}) => code) || [];
54
+
55
+ return (
56
+ <div>
57
+ {/* Have existing discount, display it with a remove option */}
58
+ <dl hidden={!codes.length}>
59
+ <div>
60
+ <dt>Discount(s)</dt>
61
+ <UpdateDiscountForm>
62
+ <div className="cart-discount">
63
+ <code>{codes?.join(', ')}</code>
64
+ &nbsp;
65
+ <button>Remove</button>
66
+ </div>
67
+ </UpdateDiscountForm>
68
+ </div>
69
+ </dl>
70
+
71
+ {/* Show an input to apply a discount */}
72
+ <UpdateDiscountForm discountCodes={codes}>
73
+ <div>
74
+ <input type="text" name="discountCode" placeholder="Discount code" />
75
+ &nbsp;
76
+ <button type="submit">Apply</button>
77
+ </div>
78
+ </UpdateDiscountForm>
79
+ </div>
80
+ );
81
+ }
82
+
83
+ function UpdateDiscountForm({
84
+ discountCodes,
85
+ children,
86
+ }: {
87
+ discountCodes?: string[];
88
+ children: React.ReactNode;
89
+ }) {
90
+ return (
91
+ <CartForm
92
+ route="/cart"
93
+ action={CartForm.ACTIONS.DiscountCodesUpdate}
94
+ inputs={{
95
+ discountCodes: discountCodes || [],
96
+ }}
97
+ >
98
+ {children}
99
+ </CartForm>
100
+ );
101
+ }
@@ -0,0 +1,129 @@
1
+ import {Suspense} from 'react';
2
+ import {Await, NavLink} from '@remix-run/react';
3
+ import type {FooterQuery, HeaderQuery} from 'storefrontapi.generated';
4
+
5
+ interface FooterProps {
6
+ footer: Promise<FooterQuery | null>;
7
+ header: HeaderQuery;
8
+ publicStoreDomain: string;
9
+ }
10
+
11
+ export function Footer({
12
+ footer: footerPromise,
13
+ header,
14
+ publicStoreDomain,
15
+ }: FooterProps) {
16
+ return (
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>
32
+ );
33
+ }
34
+
35
+ function FooterMenu({
36
+ menu,
37
+ primaryDomainUrl,
38
+ publicStoreDomain,
39
+ }: {
40
+ menu: FooterQuery['menu'];
41
+ primaryDomainUrl: FooterProps['header']['shop']['primaryDomain']['url'];
42
+ publicStoreDomain: string;
43
+ }) {
44
+ return (
45
+ <nav className="footer-menu" role="navigation">
46
+ {(menu || FALLBACK_FOOTER_MENU).items.map((item) => {
47
+ if (!item.url) return null;
48
+ // if the url is internal, we strip the domain
49
+ const url =
50
+ item.url.includes('myshopify.com') ||
51
+ item.url.includes(publicStoreDomain) ||
52
+ item.url.includes(primaryDomainUrl)
53
+ ? new URL(item.url).pathname
54
+ : item.url;
55
+ const isExternal = !url.startsWith('/');
56
+ return isExternal ? (
57
+ <a href={url} key={item.id} rel="noopener noreferrer" target="_blank">
58
+ {item.title}
59
+ </a>
60
+ ) : (
61
+ <NavLink
62
+ end
63
+ key={item.id}
64
+ prefetch="intent"
65
+ style={activeLinkStyle}
66
+ to={url}
67
+ >
68
+ {item.title}
69
+ </NavLink>
70
+ );
71
+ })}
72
+ </nav>
73
+ );
74
+ }
75
+
76
+ const FALLBACK_FOOTER_MENU = {
77
+ id: 'gid://shopify/Menu/199655620664',
78
+ items: [
79
+ {
80
+ id: 'gid://shopify/MenuItem/461633060920',
81
+ resourceId: 'gid://shopify/ShopPolicy/23358046264',
82
+ tags: [],
83
+ title: 'Privacy Policy',
84
+ type: 'SHOP_POLICY',
85
+ url: '/policies/privacy-policy',
86
+ items: [],
87
+ },
88
+ {
89
+ id: 'gid://shopify/MenuItem/461633093688',
90
+ resourceId: 'gid://shopify/ShopPolicy/23358013496',
91
+ tags: [],
92
+ title: 'Refund Policy',
93
+ type: 'SHOP_POLICY',
94
+ url: '/policies/refund-policy',
95
+ items: [],
96
+ },
97
+ {
98
+ id: 'gid://shopify/MenuItem/461633126456',
99
+ resourceId: 'gid://shopify/ShopPolicy/23358111800',
100
+ tags: [],
101
+ title: 'Shipping Policy',
102
+ type: 'SHOP_POLICY',
103
+ url: '/policies/shipping-policy',
104
+ items: [],
105
+ },
106
+ {
107
+ id: 'gid://shopify/MenuItem/461633159224',
108
+ resourceId: 'gid://shopify/ShopPolicy/23358079032',
109
+ tags: [],
110
+ title: 'Terms of Service',
111
+ type: 'SHOP_POLICY',
112
+ url: '/policies/terms-of-service',
113
+ items: [],
114
+ },
115
+ ],
116
+ };
117
+
118
+ function activeLinkStyle({
119
+ isActive,
120
+ isPending,
121
+ }: {
122
+ isActive: boolean;
123
+ isPending: boolean;
124
+ }) {
125
+ return {
126
+ fontWeight: isActive ? 'bold' : undefined,
127
+ color: isPending ? 'grey' : 'white',
128
+ };
129
+ }