@shopify/create-hydrogen 1.0.0-alpha.23 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +256 -0
- package/bin/run.cmd +3 -0
- package/bin/run.js +7 -0
- package/dist/commands/init.js +157943 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/index.js +151598 -0
- package/dist/index.js.map +1 -0
- package/package.json +64 -16
- package/templates/template-hydrogen-default/.devcontainer/devcontainer.json +18 -0
- package/templates/template-hydrogen-default/.eslintrc.js +3 -0
- package/templates/template-hydrogen-default/.gitignore +7 -0
- package/{template-hydrogen → templates/template-hydrogen-default}/.stylelintrc.js +1 -1
- package/templates/template-hydrogen-default/.vscode/extensions.json +8 -0
- package/{template-hydrogen → templates/template-hydrogen-default}/README.md +11 -2
- package/{template-hydrogen → templates/template-hydrogen-default}/_gitignore +0 -0
- package/templates/template-hydrogen-default/index.html +20 -0
- package/templates/template-hydrogen-default/package.json.liquid +48 -0
- package/{template-hydrogen → templates/template-hydrogen-default}/postcss.config.js +0 -0
- package/templates/template-hydrogen-default/public/favicon.ico +0 -0
- package/{template-hydrogen → templates/template-hydrogen-default}/shopify.config.js +1 -2
- package/templates/template-hydrogen-default/src/App.server.jsx +27 -0
- package/templates/template-hydrogen-default/src/components/Button.client.jsx +65 -0
- package/templates/template-hydrogen-default/src/components/Cart.client.jsx +265 -0
- package/templates/template-hydrogen-default/src/components/CartIcon.jsx +33 -0
- package/templates/template-hydrogen-default/src/components/CartIconWithItems.client.jsx +28 -0
- package/templates/template-hydrogen-default/src/components/CartProvider.client.jsx +35 -0
- package/templates/template-hydrogen-default/src/components/CartToggle.client.jsx +30 -0
- package/templates/template-hydrogen-default/src/components/CartUIProvider.client.jsx +45 -0
- package/templates/template-hydrogen-default/src/components/CountrySelector.client.jsx +116 -0
- package/templates/template-hydrogen-default/src/components/DefaultSeo.server.jsx +36 -0
- package/templates/template-hydrogen-default/src/components/FeaturedCollection.jsx +26 -0
- package/templates/template-hydrogen-default/src/components/Footer.server.jsx +103 -0
- package/templates/template-hydrogen-default/src/components/Gallery.client.jsx +66 -0
- package/templates/template-hydrogen-default/src/components/Header.client.jsx +62 -0
- package/templates/template-hydrogen-default/src/components/Layout.server.jsx +86 -0
- package/templates/template-hydrogen-default/src/components/LoadMoreProducts.client.jsx +56 -0
- package/templates/template-hydrogen-default/src/components/LoadingFallback.jsx +26 -0
- package/templates/template-hydrogen-default/src/components/MobileCountrySelector.client.jsx +64 -0
- package/templates/template-hydrogen-default/src/components/MobileNavigation.client.jsx +98 -0
- package/templates/template-hydrogen-default/src/components/MoneyCompareAtPrice.client.jsx +14 -0
- package/templates/template-hydrogen-default/src/components/MoneyPrice.client.jsx +15 -0
- package/templates/template-hydrogen-default/src/components/Navigation.client.jsx +23 -0
- package/templates/template-hydrogen-default/src/components/NotFound.server.jsx +93 -0
- package/templates/template-hydrogen-default/src/components/OpenIcon.jsx +33 -0
- package/templates/template-hydrogen-default/src/components/ProductCard.jsx +50 -0
- package/templates/template-hydrogen-default/src/components/ProductDetails.client.jsx +233 -0
- package/{template-hydrogen → templates/template-hydrogen-default}/src/components/ProductOptions.client.jsx +7 -4
- package/templates/template-hydrogen-default/src/components/Welcome.server.jsx +188 -0
- package/{template-hydrogen → templates/template-hydrogen-default}/src/index.css +31 -0
- package/templates/template-hydrogen-default/src/pages/collections/[handle].server.jsx +105 -0
- package/templates/template-hydrogen-default/src/pages/index.server.jsx +241 -0
- package/{template-hydrogen → templates/template-hydrogen-default}/src/pages/pages/[handle].server.jsx +14 -5
- package/templates/template-hydrogen-default/src/pages/products/[handle].server.jsx +66 -0
- package/templates/template-hydrogen-default/src/pages/redirect.server.jsx +4 -0
- package/templates/template-hydrogen-default/src/pages/robots.txt.server.js +40 -0
- package/templates/template-hydrogen-default/src/pages/sitemap.xml.server.jsx +151 -0
- package/templates/template-hydrogen-default/src/routes/collections/[handle].server.jsx +105 -0
- package/templates/template-hydrogen-default/src/routes/index.server.jsx +241 -0
- package/templates/template-hydrogen-default/src/routes/pages/[handle].server.jsx +37 -0
- package/templates/template-hydrogen-default/src/routes/products/[handle].server.jsx +66 -0
- package/templates/template-hydrogen-default/src/routes/redirect.server.jsx +4 -0
- package/templates/template-hydrogen-default/src/routes/robots.txt.server.js +40 -0
- package/templates/template-hydrogen-default/src/routes/sitemap.xml.server.jsx +151 -0
- package/templates/template-hydrogen-default/tailwind.config.js +26 -0
- package/{template-hydrogen → templates/template-hydrogen-default}/vite.config.js +1 -0
- package/templates/template-hydrogen-minimal/README.md +8 -0
- package/templates/template-hydrogen-minimal/package.json +14 -0
- package/index.js +0 -201
- package/scripts/tmp-copy-template-from-dev.js +0 -21
- package/scripts/utils.js +0 -29
- package/template-hydrogen/.eslintrc.js +0 -41
- package/template-hydrogen/.vscode/extensions.json +0 -3
- package/template-hydrogen/Dockerfile +0 -15
- package/template-hydrogen/index.html +0 -14
- package/template-hydrogen/package.json +0 -43
- package/template-hydrogen/server.js +0 -40
- package/template-hydrogen/src/App.server.jsx +0 -42
- package/template-hydrogen/src/components/Cart.client.jsx +0 -287
- package/template-hydrogen/src/components/CartProvider.client.jsx +0 -15
- package/template-hydrogen/src/components/DefaultSeo.server.jsx +0 -22
- package/template-hydrogen/src/components/Footer.jsx +0 -12
- package/template-hydrogen/src/components/Gallery.client.jsx +0 -36
- package/template-hydrogen/src/components/Header.client.jsx +0 -46
- package/template-hydrogen/src/components/HightlightedProduct.client.jsx +0 -54
- package/template-hydrogen/src/components/Layout.client.jsx +0 -61
- package/template-hydrogen/src/components/MediaPlaceholder.jsx +0 -21
- package/template-hydrogen/src/components/NotFound.server.jsx +0 -104
- package/template-hydrogen/src/components/ProductCard.client.jsx +0 -39
- package/template-hydrogen/src/components/ProductDetails.client.jsx +0 -184
- package/template-hydrogen/src/components/Seo.client.jsx +0 -75
- package/template-hydrogen/src/entry-client.jsx +0 -12
- package/template-hydrogen/src/entry-server.jsx +0 -7
- package/template-hydrogen/src/favicon.svg +0 -15
- package/template-hydrogen/src/pages/Index.server.jsx +0 -186
- package/template-hydrogen/src/pages/blogs/[handle]/[articleHandle].server.jsx +0 -49
- package/template-hydrogen/src/pages/blogs/[handle].server.jsx +0 -76
- package/template-hydrogen/src/pages/collections/[handle].server.jsx +0 -69
- package/template-hydrogen/src/pages/products/[handle].server.jsx +0 -56
- package/template-hydrogen/src/pages/search.server.jsx +0 -107
- package/template-hydrogen/src/pages/sitemap.xml.server.jsx +0 -64
- package/template-hydrogen/tailwind.config.js +0 -9
- package/template-hydrogen/worker.js +0 -22
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import {useServerState} from '@shopify/hydrogen/client';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A client component that provides functionality to initially show a subset of products and a button to load more products
|
|
5
|
+
*/
|
|
6
|
+
export default function LoadMoreProducts({startingCount}) {
|
|
7
|
+
const {pending, serverState, setServerState} = useServerState();
|
|
8
|
+
|
|
9
|
+
return (
|
|
10
|
+
<div className="flex justify-center h-14">
|
|
11
|
+
{pending ? (
|
|
12
|
+
<SpinnerIcon />
|
|
13
|
+
) : (
|
|
14
|
+
<button
|
|
15
|
+
type="button"
|
|
16
|
+
disabled={pending}
|
|
17
|
+
className={`uppercase border-4 bg-white border-black text-black text-center px-5 py-3 font-mono font-bold drop-shadow-lg active:drop-shadow-none hover:bg-black hover:text-white hover:border-white ${
|
|
18
|
+
pending ? 'opacity-50' : undefined
|
|
19
|
+
}`}
|
|
20
|
+
onClick={() => {
|
|
21
|
+
setServerState(
|
|
22
|
+
'collectionProductCount',
|
|
23
|
+
serverState.collectionProductCount
|
|
24
|
+
? serverState.collectionProductCount + 24
|
|
25
|
+
: startingCount + 1,
|
|
26
|
+
);
|
|
27
|
+
}}
|
|
28
|
+
>
|
|
29
|
+
Load more
|
|
30
|
+
</button>
|
|
31
|
+
)}
|
|
32
|
+
</div>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function SpinnerIcon() {
|
|
37
|
+
return (
|
|
38
|
+
<svg
|
|
39
|
+
width="54"
|
|
40
|
+
height="54"
|
|
41
|
+
viewBox="0 0 54 54"
|
|
42
|
+
fill="none"
|
|
43
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
44
|
+
className="animate-spin"
|
|
45
|
+
>
|
|
46
|
+
<path
|
|
47
|
+
d="M43.6663 27.0002C43.6663 36.2049 36.2044 43.6668 26.9997 43.6668C17.7949 43.6668 10.333 36.2049 10.333 27.0002C10.333 17.7954 17.7949 10.3335 26.9997 10.3335C36.2044 10.3335 43.6663 17.7954 43.6663 27.0002ZM13.6663 27.0002C13.6663 34.364 19.6359 40.3335 26.9997 40.3335C34.3635 40.3335 40.333 34.364 40.333 27.0002C40.333 19.6364 34.3635 13.6668 26.9997 13.6668C19.6359 13.6668 13.6663 19.6364 13.6663 27.0002Z"
|
|
48
|
+
fill="#E6E7EB"
|
|
49
|
+
/>
|
|
50
|
+
<path
|
|
51
|
+
d="M26.9997 10.3332C29.1884 10.3332 31.3556 10.7643 33.3777 11.6018C35.3998 12.4394 37.2371 13.6671 38.7848 15.2147C40.3324 16.7624 41.5601 18.5997 42.3977 20.6218C43.2352 22.6439 43.6663 24.8111 43.6663 26.9998L40.333 26.9998C40.333 25.2489 39.9881 23.5151 39.3181 21.8974C38.648 20.2797 37.6659 18.8099 36.4278 17.5717C35.1896 16.3336 33.7198 15.3515 32.1021 14.6814C30.4844 14.0114 28.7506 13.6665 26.9997 13.6665L26.9997 10.3332Z"
|
|
52
|
+
fill="black"
|
|
53
|
+
/>
|
|
54
|
+
</svg>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import CartIcon from './CartIcon';
|
|
2
|
+
import OpenIcon from './OpenIcon';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A shared component and Suspense call that's used in `App.server.jsx` to let your app wait for code to load while declaring a loading state
|
|
6
|
+
*/
|
|
7
|
+
export default function LoadingFallback() {
|
|
8
|
+
return (
|
|
9
|
+
<header className="h-20 lg:h-32 max-w-screen text-gray-700">
|
|
10
|
+
<div className="fixed z-10 h-20 lg:h-32 w-full bg-white/90 border-b border-black border-opacity-5 px-6 md:px-8 md:py-6 lg:pt-8 lg:pb-0 mx-auto">
|
|
11
|
+
<div className="h-full flex lg:flex-col place-content-between">
|
|
12
|
+
<div className="text-center w-full flex justify-between items-center">
|
|
13
|
+
<div className="hidden lg:block w-16" />
|
|
14
|
+
<div className="lg:hidden flex justify-center items-center w-7 h-full">
|
|
15
|
+
<OpenIcon />
|
|
16
|
+
</div>
|
|
17
|
+
<p className="font-black uppercase text-3xl tracking-widest">
|
|
18
|
+
Snowdevil
|
|
19
|
+
</p>
|
|
20
|
+
<CartIcon />
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
24
|
+
</header>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import {useCallback} from 'react';
|
|
2
|
+
import {useAvailableCountries, useCountry} from '@shopify/hydrogen/client';
|
|
3
|
+
import {Listbox} from '@headlessui/react';
|
|
4
|
+
|
|
5
|
+
import {ArrowIcon, CheckIcon} from './CountrySelector.client';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A client component that selects the appropriate country to display for products on a mobile storefront
|
|
9
|
+
*/
|
|
10
|
+
export default function MobileCountrySelector() {
|
|
11
|
+
const countries = [...useAvailableCountries()].sort((a, b) =>
|
|
12
|
+
a.name.localeCompare(b.name),
|
|
13
|
+
);
|
|
14
|
+
const [selectedCountry, setSelectedCountry] = useCountry();
|
|
15
|
+
|
|
16
|
+
const setCountry = useCallback(
|
|
17
|
+
(isoCode) => {
|
|
18
|
+
setSelectedCountry(
|
|
19
|
+
countries.find((country) => country.isoCode === isoCode),
|
|
20
|
+
);
|
|
21
|
+
},
|
|
22
|
+
[countries, setSelectedCountry],
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<div className="mt-8 rounded border border-gray-200 w-full">
|
|
27
|
+
<Listbox onChange={setCountry}>
|
|
28
|
+
{({open}) => (
|
|
29
|
+
<>
|
|
30
|
+
<Listbox.Button className="w-full flex justify-between text-sm items-center py-5 px-7">
|
|
31
|
+
{selectedCountry.name}
|
|
32
|
+
<ArrowIcon isOpen={open} />
|
|
33
|
+
</Listbox.Button>
|
|
34
|
+
<Listbox.Options className="w-full px-3 pb-2 text-lg">
|
|
35
|
+
<Listbox.Option
|
|
36
|
+
disabled
|
|
37
|
+
className="font-medium px-4 pb-4 w-full text-left uppercase"
|
|
38
|
+
>
|
|
39
|
+
Country
|
|
40
|
+
</Listbox.Option>
|
|
41
|
+
{countries.map((country) => {
|
|
42
|
+
const isSelected = country.isoCode === selectedCountry.isoCode;
|
|
43
|
+
return (
|
|
44
|
+
<Listbox.Option key={country.isoCode} value={country.isoCode}>
|
|
45
|
+
{({active}) => (
|
|
46
|
+
<div
|
|
47
|
+
className={`py-2 px-4 rounded flex justify-between items-center text-left w-full cursor-pointer ${
|
|
48
|
+
active ? 'bg-gray-100' : null
|
|
49
|
+
}`}
|
|
50
|
+
>
|
|
51
|
+
{country.name}
|
|
52
|
+
{isSelected ? <CheckIcon /> : null}
|
|
53
|
+
</div>
|
|
54
|
+
)}
|
|
55
|
+
</Listbox.Option>
|
|
56
|
+
);
|
|
57
|
+
})}
|
|
58
|
+
</Listbox.Options>
|
|
59
|
+
</>
|
|
60
|
+
)}
|
|
61
|
+
</Listbox>
|
|
62
|
+
</div>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import {Fragment, useEffect, useState} from 'react';
|
|
2
|
+
import {Link} from '@shopify/hydrogen/client';
|
|
3
|
+
import {FocusTrap} from '@headlessui/react';
|
|
4
|
+
|
|
5
|
+
import MobileCountrySelector from './MobileCountrySelector.client';
|
|
6
|
+
import OpenIcon from './OpenIcon';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* A client component that defines the navigation for a mobile storefront
|
|
10
|
+
*/
|
|
11
|
+
export default function MobileNavigation({collections, isOpen, setIsOpen}) {
|
|
12
|
+
const OpenFocusTrap = isOpen ? FocusTrap : Fragment;
|
|
13
|
+
|
|
14
|
+
const [topScrollOffset, setTopScrollOffset] = useState(0);
|
|
15
|
+
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
if (isOpen) {
|
|
18
|
+
setTopScrollOffset(window.scrollY);
|
|
19
|
+
document.body.style.position = 'fixed';
|
|
20
|
+
} else {
|
|
21
|
+
document.body.style.position = '';
|
|
22
|
+
window.scrollTo(0, parseInt(topScrollOffset, 10));
|
|
23
|
+
}
|
|
24
|
+
}, [isOpen, topScrollOffset]);
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<div className="lg:hidden">
|
|
28
|
+
<OpenFocusTrap>
|
|
29
|
+
<button
|
|
30
|
+
type="button"
|
|
31
|
+
className="flex justify-center items-center w-7 h-full"
|
|
32
|
+
onClick={() => setIsOpen((previousIsOpen) => !previousIsOpen)}
|
|
33
|
+
>
|
|
34
|
+
{isOpen ? <CloseIcon /> : <OpenIcon />}
|
|
35
|
+
</button>
|
|
36
|
+
{isOpen ? (
|
|
37
|
+
<div className="fixed -left-0 top-20 w-full h-screen z-10 bg-gray-50 px-4 md:px-12 py-7">
|
|
38
|
+
<ul>
|
|
39
|
+
{collections.map((collection) => (
|
|
40
|
+
<li className="border-b border-gray-200" key={collection.id}>
|
|
41
|
+
<Link
|
|
42
|
+
className="group py-5 text-gray-700 flex items-center justify-between"
|
|
43
|
+
to={`/collections/${collection.handle}`}
|
|
44
|
+
onClick={() => setIsOpen(false)}
|
|
45
|
+
>
|
|
46
|
+
{collection.title}
|
|
47
|
+
<ArrowRightIcon className="hidden group-hover:block" />
|
|
48
|
+
</Link>
|
|
49
|
+
</li>
|
|
50
|
+
))}
|
|
51
|
+
</ul>
|
|
52
|
+
<MobileCountrySelector />
|
|
53
|
+
</div>
|
|
54
|
+
) : null}
|
|
55
|
+
</OpenFocusTrap>
|
|
56
|
+
</div>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function CloseIcon() {
|
|
61
|
+
return (
|
|
62
|
+
<svg
|
|
63
|
+
width="18"
|
|
64
|
+
height="18"
|
|
65
|
+
viewBox="0 0 18 18"
|
|
66
|
+
fill="none"
|
|
67
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
68
|
+
>
|
|
69
|
+
<path
|
|
70
|
+
d="M1 17L17 1M1 1L17 17"
|
|
71
|
+
stroke="black"
|
|
72
|
+
strokeWidth="2"
|
|
73
|
+
strokeLinecap="round"
|
|
74
|
+
strokeLinejoin="round"
|
|
75
|
+
/>
|
|
76
|
+
</svg>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function ArrowRightIcon({className}) {
|
|
81
|
+
return (
|
|
82
|
+
<svg
|
|
83
|
+
className={className}
|
|
84
|
+
width="7"
|
|
85
|
+
height="12"
|
|
86
|
+
viewBox="0 0 7 12"
|
|
87
|
+
fill="none"
|
|
88
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
89
|
+
>
|
|
90
|
+
<path
|
|
91
|
+
fillRule="evenodd"
|
|
92
|
+
clipRule="evenodd"
|
|
93
|
+
d="M0.999762 12C0.743762 12 0.487762 11.902 0.292762 11.707C-0.0982383 11.316 -0.0982383 10.684 0.292762 10.293L4.58576 6.00001L0.292762 1.70701C-0.0982383 1.31601 -0.0982383 0.684006 0.292762 0.293006C0.683762 -0.0979941 1.31576 -0.0979941 1.70676 0.293006L6.70676 5.29301C7.09776 5.68401 7.09776 6.31601 6.70676 6.70701L1.70676 11.707C1.51176 11.902 1.25576 12 0.999762 12Z"
|
|
94
|
+
fill="black"
|
|
95
|
+
/>
|
|
96
|
+
</svg>
|
|
97
|
+
);
|
|
98
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import {useMoney} from '@shopify/hydrogen/client';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A client component that renders a product's compare at price
|
|
5
|
+
*/
|
|
6
|
+
export default function MoneyCompareAtPrice({money}) {
|
|
7
|
+
const {amount, currencyNarrowSymbol} = useMoney(money);
|
|
8
|
+
return (
|
|
9
|
+
<span className="line-through text-lg mr-2.5 text-gray-500">
|
|
10
|
+
{currencyNarrowSymbol}
|
|
11
|
+
{amount}
|
|
12
|
+
</span>
|
|
13
|
+
);
|
|
14
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import {useMoney} from '@shopify/hydrogen/client';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A client component that defines the currency code, currency symbol, and amount of a product
|
|
5
|
+
*/
|
|
6
|
+
export default function MoneyPrice({money}) {
|
|
7
|
+
const {currencyCode, currencyNarrowSymbol, amount} = useMoney(money);
|
|
8
|
+
return (
|
|
9
|
+
<span className="text-black text-md">
|
|
10
|
+
{currencyCode}
|
|
11
|
+
{currencyNarrowSymbol}
|
|
12
|
+
{amount}
|
|
13
|
+
</span>
|
|
14
|
+
);
|
|
15
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import {Link} from '@shopify/hydrogen/client';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A client component that defines the navigation for a web storefront
|
|
5
|
+
*/
|
|
6
|
+
export default function Navigation({collections}) {
|
|
7
|
+
return (
|
|
8
|
+
<nav className="hidden lg:block text-center">
|
|
9
|
+
<ul className="md:flex items-center justify-center">
|
|
10
|
+
{collections.map((collection) => (
|
|
11
|
+
<li key={collection.id}>
|
|
12
|
+
<Link
|
|
13
|
+
to={`/collections/${collection.handle}`}
|
|
14
|
+
className="block p-4 hover:opacity-80"
|
|
15
|
+
>
|
|
16
|
+
{collection.title}
|
|
17
|
+
</Link>
|
|
18
|
+
</li>
|
|
19
|
+
))}
|
|
20
|
+
</ul>
|
|
21
|
+
</nav>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import {
|
|
2
|
+
useShopQuery,
|
|
3
|
+
ProductProviderFragment,
|
|
4
|
+
flattenConnection,
|
|
5
|
+
} from '@shopify/hydrogen';
|
|
6
|
+
import gql from 'graphql-tag';
|
|
7
|
+
|
|
8
|
+
import Layout from './Layout.server';
|
|
9
|
+
import Button from './Button.client';
|
|
10
|
+
import ProductCard from './ProductCard';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* A server component that defines the content to display when a page isn't found (404 error)
|
|
14
|
+
*/
|
|
15
|
+
function NotFoundHero() {
|
|
16
|
+
return (
|
|
17
|
+
<div className="py-10 border-b border-gray-200">
|
|
18
|
+
<div className="max-w-3xl text-center mx-4 md:mx-auto">
|
|
19
|
+
<h1 className="font-bold text-4xl md:text-5xl text-gray-900 mb-6 mt-6">
|
|
20
|
+
We've lost this page
|
|
21
|
+
</h1>
|
|
22
|
+
<p className="text-lg m-8 text-gray-500">
|
|
23
|
+
We couldn’t find the page you’re looking for. Try checking the URL or
|
|
24
|
+
heading back to the home page.
|
|
25
|
+
</p>
|
|
26
|
+
<Button
|
|
27
|
+
className="w-full md:mx-auto md:w-96"
|
|
28
|
+
url="/"
|
|
29
|
+
label="Take me to the home page"
|
|
30
|
+
/>
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export default function NotFound({country = {isoCode: 'US'}}) {
|
|
37
|
+
const {data} = useShopQuery({
|
|
38
|
+
query: QUERY,
|
|
39
|
+
variables: {
|
|
40
|
+
country: country.isoCode,
|
|
41
|
+
numProductMetafields: 0,
|
|
42
|
+
numProductVariants: 250,
|
|
43
|
+
numProductMedia: 0,
|
|
44
|
+
numProductVariantMetafields: 0,
|
|
45
|
+
numProductVariantSellingPlanAllocations: 0,
|
|
46
|
+
numProductSellingPlanGroups: 0,
|
|
47
|
+
numProductSellingPlans: 0,
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
const products = data ? flattenConnection(data.products) : [];
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<Layout>
|
|
54
|
+
<NotFoundHero />
|
|
55
|
+
<div className="my-8">
|
|
56
|
+
<p className="mb-8 text-lg text-black font-medium uppercase">
|
|
57
|
+
Products you might like
|
|
58
|
+
</p>
|
|
59
|
+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8 mb-16">
|
|
60
|
+
{products.map((product) => (
|
|
61
|
+
<div key={product.id}>
|
|
62
|
+
<ProductCard product={product} />
|
|
63
|
+
</div>
|
|
64
|
+
))}
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
</Layout>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const QUERY = gql`
|
|
72
|
+
query NotFoundProductDetails(
|
|
73
|
+
$country: CountryCode
|
|
74
|
+
$includeReferenceMetafieldDetails: Boolean = false
|
|
75
|
+
$numProductMetafields: Int!
|
|
76
|
+
$numProductVariants: Int!
|
|
77
|
+
$numProductMedia: Int!
|
|
78
|
+
$numProductVariantMetafields: Int!
|
|
79
|
+
$numProductVariantSellingPlanAllocations: Int!
|
|
80
|
+
$numProductSellingPlanGroups: Int!
|
|
81
|
+
$numProductSellingPlans: Int!
|
|
82
|
+
) @inContext(country: $country) {
|
|
83
|
+
products(first: 3) {
|
|
84
|
+
edges {
|
|
85
|
+
node {
|
|
86
|
+
...ProductProviderFragment
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
${ProductProviderFragment}
|
|
93
|
+
`;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A shared component that specifies the icon to render when opened
|
|
3
|
+
*/
|
|
4
|
+
export default function OpenIcon() {
|
|
5
|
+
return (
|
|
6
|
+
<svg
|
|
7
|
+
width="24"
|
|
8
|
+
height="20"
|
|
9
|
+
viewBox="0 0 24 20"
|
|
10
|
+
fill="none"
|
|
11
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
12
|
+
>
|
|
13
|
+
<path
|
|
14
|
+
fillRule="evenodd"
|
|
15
|
+
clipRule="evenodd"
|
|
16
|
+
d="M0.800049 1.9999C0.800049 1.11625 1.51639 0.399902 2.40005 0.399902H21.6C22.4837 0.399902 23.2001 1.11625 23.2001 1.9999C23.2001 2.88356 22.4837 3.5999 21.6 3.5999H2.40005C1.51639 3.5999 0.800049 2.88356 0.800049 1.9999Z"
|
|
17
|
+
fill="black"
|
|
18
|
+
/>
|
|
19
|
+
<path
|
|
20
|
+
fillRule="evenodd"
|
|
21
|
+
clipRule="evenodd"
|
|
22
|
+
d="M0.800049 9.9999C0.800049 9.11625 1.51639 8.3999 2.40005 8.3999H21.6C22.4837 8.3999 23.2001 9.11625 23.2001 9.9999C23.2001 10.8836 22.4837 11.5999 21.6 11.5999H2.40005C1.51639 11.5999 0.800049 10.8836 0.800049 9.9999Z"
|
|
23
|
+
fill="black"
|
|
24
|
+
/>
|
|
25
|
+
<path
|
|
26
|
+
fillRule="evenodd"
|
|
27
|
+
clipRule="evenodd"
|
|
28
|
+
d="M0.800049 17.9999C0.800049 17.1162 1.51639 16.3999 2.40005 16.3999H21.6C22.4837 16.3999 23.2001 17.1162 23.2001 17.9999C23.2001 18.8836 22.4837 19.5999 21.6 19.5999H2.40005C1.51639 19.5999 0.800049 18.8836 0.800049 17.9999Z"
|
|
29
|
+
fill="black"
|
|
30
|
+
/>
|
|
31
|
+
</svg>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import {Image, Link} from '@shopify/hydrogen';
|
|
2
|
+
|
|
3
|
+
import MoneyCompareAtPrice from './MoneyCompareAtPrice.client';
|
|
4
|
+
import MoneyPrice from './MoneyPrice.client';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A shared component that displays a single product to allow buyers to quickly identify a particular item of interest
|
|
8
|
+
*/
|
|
9
|
+
export default function ProductCard({product}) {
|
|
10
|
+
const selectedVariant = product.variants.edges[0].node;
|
|
11
|
+
|
|
12
|
+
if (selectedVariant == null) {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<div className="text-md mb-4 relative">
|
|
18
|
+
<Link to={`/products/${product.handle}`}>
|
|
19
|
+
<div className="rounded-lg border-2 border-gray-200 mb-2 relative flex items-center justify-center overflow-hidden object-cover h-96">
|
|
20
|
+
{selectedVariant.image ? (
|
|
21
|
+
<Image
|
|
22
|
+
className="bg-white absolute w-full h-full transition-all duration-500 ease-in-out transform bg-center bg-cover object-center object-contain hover:scale-110"
|
|
23
|
+
data={selectedVariant.image}
|
|
24
|
+
/>
|
|
25
|
+
) : null}
|
|
26
|
+
{!selectedVariant?.availableForSale && (
|
|
27
|
+
<div className="absolute top-3 left-3 rounded-3xl text-xs bg-black text-white py-3 px-4">
|
|
28
|
+
Out of stock
|
|
29
|
+
</div>
|
|
30
|
+
)}
|
|
31
|
+
</div>
|
|
32
|
+
|
|
33
|
+
<span className="text-black font-semibold mb-0.5">{product.title}</span>
|
|
34
|
+
|
|
35
|
+
{product.vendor && (
|
|
36
|
+
<p className="text-gray-900 font-medium text-sm mb-0.5">
|
|
37
|
+
{product.vendor}
|
|
38
|
+
</p>
|
|
39
|
+
)}
|
|
40
|
+
|
|
41
|
+
<div className="flex ">
|
|
42
|
+
{selectedVariant.compareAtPriceV2 && (
|
|
43
|
+
<MoneyCompareAtPrice money={selectedVariant.compareAtPriceV2} />
|
|
44
|
+
)}
|
|
45
|
+
<MoneyPrice money={selectedVariant.priceV2} />
|
|
46
|
+
</div>
|
|
47
|
+
</Link>
|
|
48
|
+
</div>
|
|
49
|
+
);
|
|
50
|
+
}
|