-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adds basic layout for builder keyboard page
- Loading branch information
1 parent
a7553ce
commit fce0c83
Showing
5 changed files
with
324 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,194 @@ | ||
import {defer, redirect} from '@shopify/remix-oxygen'; | ||
import {getSelectedProductOptions} from '@shopify/hydrogen'; | ||
import {getVariantUrl} from '~/lib/variants'; | ||
import Hero from './sections/hero'; | ||
import Features from './sections/features'; | ||
import CTA from './sections/cta'; | ||
|
||
export const meta = ({data}) => { | ||
return [{title: `Hydrogen | ${data?.product.title ?? ''}`}]; | ||
}; | ||
|
||
export async function loader({params, request, context}) { | ||
// const {handle} = params; | ||
const handle = 'builders-tote'; | ||
const {storefront} = context; | ||
|
||
const {product} = await storefront.query(PRODUCT_QUERY, { | ||
variables: { | ||
handle, | ||
selectedOptions: getSelectedProductOptions(request), | ||
}, | ||
}); | ||
|
||
if (!product?.id) { | ||
throw new Response(null, {status: 404}); | ||
} | ||
|
||
// TODO: Package up all this stuff into a utility function | ||
// setDefaultSelectedVariant(product, 'first'); Also handle "available" for first available variant as well | ||
const firstVariant = product.variants.nodes[0]; | ||
const firstVariantIsDefault = Boolean( | ||
firstVariant.selectedOptions.find( | ||
(option) => option.name === 'Title' && option.value === 'Default Title', | ||
), | ||
); | ||
|
||
if (firstVariantIsDefault) { | ||
product.selectedVariant = firstVariant; | ||
} else { | ||
if (!product.selectedVariant) { | ||
throw function ({product, request}) { | ||
const url = new URL(request.url); | ||
const firstVariant = product.variants.nodes[0]; | ||
|
||
return redirect( | ||
getVariantUrl({ | ||
pathname: url.pathname, | ||
handle: product.handle, | ||
selectedOptions: firstVariant.selectedOptions, | ||
searchParams: new URLSearchParams(url.search), | ||
}), | ||
{ | ||
status: 302, | ||
}, | ||
); | ||
}; | ||
} | ||
} | ||
// END TODO | ||
|
||
const variants = storefront.query(VARIANTS_QUERY, { | ||
variables: {handle}, | ||
}); | ||
|
||
return defer({product, variants}); | ||
} | ||
|
||
export default function Product() { | ||
/** @type {LoaderReturnData} */ | ||
return ( | ||
<> | ||
<Hero /> | ||
<Features /> | ||
<CTA /> | ||
</> | ||
); | ||
} | ||
|
||
/*********************** | ||
* DATA | ||
***********************/ | ||
|
||
const PRODUCT_VARIANT_FRAGMENT = `#graphql | ||
fragment ProductVariant on ProductVariant { | ||
availableForSale | ||
compareAtPrice { | ||
amount | ||
currencyCode | ||
} | ||
id | ||
image { | ||
__typename | ||
id | ||
url | ||
altText | ||
width | ||
height | ||
} | ||
price { | ||
amount | ||
currencyCode | ||
} | ||
product { | ||
title | ||
handle | ||
} | ||
selectedOptions { | ||
name | ||
value | ||
} | ||
sku | ||
title | ||
unitPrice { | ||
amount | ||
currencyCode | ||
} | ||
} | ||
`; | ||
|
||
const PRODUCT_FRAGMENT = `#graphql | ||
fragment Product on Product { | ||
id | ||
title | ||
vendor | ||
handle | ||
description | ||
options { | ||
name | ||
values | ||
} | ||
selectedVariant: variantBySelectedOptions(selectedOptions: $selectedOptions, ignoreUnknownOptions: true, caseInsensitiveMatch: true) { | ||
...ProductVariant | ||
} | ||
variants(first: 1) { | ||
nodes { | ||
...ProductVariant | ||
} | ||
} | ||
seo { | ||
description | ||
title | ||
} | ||
} | ||
${PRODUCT_VARIANT_FRAGMENT} | ||
`; | ||
|
||
const PRODUCT_QUERY = `#graphql | ||
query Product( | ||
$country: CountryCode | ||
$handle: String! | ||
$language: LanguageCode | ||
$selectedOptions: [SelectedOptionInput!]! | ||
) @inContext(country: $country, language: $language) { | ||
product(handle: $handle) { | ||
...Product | ||
} | ||
} | ||
${PRODUCT_FRAGMENT} | ||
`; | ||
|
||
const PRODUCT_VARIANTS_FRAGMENT = `#graphql | ||
fragment ProductVariants on Product { | ||
variants(first: 250) { | ||
nodes { | ||
...ProductVariant | ||
} | ||
} | ||
} | ||
${PRODUCT_VARIANT_FRAGMENT} | ||
`; | ||
|
||
const VARIANTS_QUERY = `#graphql | ||
query ProductVariants( | ||
$country: CountryCode | ||
$language: LanguageCode | ||
$handle: String! | ||
) @inContext(country: $country, language: $language) { | ||
product(handle: $handle) { | ||
...ProductVariants | ||
} | ||
} | ||
${PRODUCT_VARIANTS_FRAGMENT} | ||
`; | ||
|
||
/** @typedef {import('@shopify/remix-oxygen').LoaderFunctionArgs} LoaderFunctionArgs */ | ||
/** @template T @typedef {import('@remix-run/react').MetaFunction<T>} MetaFunction */ | ||
/** @typedef {import('@remix-run/react').FetcherWithComponents} FetcherWithComponents */ | ||
/** @typedef {import('storefrontapi.generated').ProductFragment} ProductFragment */ | ||
/** @typedef {import('storefrontapi.generated').ProductVariantsQuery} ProductVariantsQuery */ | ||
/** @typedef {import('storefrontapi.generated').ProductVariantFragment} ProductVariantFragment */ | ||
/** @typedef {import('@shopify/hydrogen').VariantOption} VariantOption */ | ||
/** @typedef {import('@shopify/hydrogen/storefront-api-types').CartLineInput} CartLineInput */ | ||
/** @typedef {import('@shopify/hydrogen/storefront-api-types').SelectedOption} SelectedOption */ | ||
/** @typedef {import('@shopify/remix-oxygen').SerializeFrom<typeof loader>} LoaderReturnData */ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import {Button} from '@h2/Button'; | ||
import Link from '@h2/Link'; | ||
import {Heading, Text} from '@h2/Text'; | ||
import {Background, Flex, Section} from '@h2/new/Layout'; | ||
|
||
export default function CTA() { | ||
return ( | ||
<Section className="bg-accent aspect-video"> | ||
<Flex> | ||
<Heading> | ||
<span className="font-display"> | ||
<span>CTRL</span> YOUR PRODUCTIVITY, | ||
</span> | ||
<br /> | ||
<span className="font-sans"> | ||
<span>ALT</span> YOUR WORK GAME. | ||
</span> | ||
</Heading> | ||
<Button>Pre-order</Button> | ||
<Text> | ||
See <Link to="/">FAQs</Link> for more details on pre-orders | ||
</Text> | ||
</Flex> | ||
<Background></Background> | ||
</Section> | ||
); | ||
} |
69 changes: 69 additions & 0 deletions
69
app/routes/products.builder-keyboard/sections/features.jsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
import {Heading, Text} from '@h2/Text'; | ||
import {Flex, Grid, Section} from '@h2/new/Layout'; | ||
import {Image} from '@shopify/hydrogen'; | ||
|
||
export default function Features() { | ||
return ( | ||
<Section className="text-white bg-darkGray"> | ||
<Flex direction="down" gap={9}> | ||
<Grid columns={9}> | ||
<div className="col-span-2"> | ||
<Heading>01</Heading> | ||
</div> | ||
<div className="col-span-3"> | ||
<Heading>Type at lightning speed</Heading> | ||
<Text> | ||
Type at lightning speed Build faster than ever with the Shopify | ||
Keyboard thanks to its ultra-slim design, featuring low-profile | ||
switches (0.2 ms latency). These switches are 31% thinner than | ||
those found on average mechanical keyboards, allowing you to type, | ||
work, and play faster. | ||
</Text> | ||
<Text> | ||
Listen to the smooth sound of the Gatreon low-profile red switch. | ||
</Text> | ||
</div> | ||
<div className="col-span-3 col-start-7"> | ||
<Image /> | ||
</div> | ||
</Grid> | ||
<Grid columns={9}> | ||
<div className="col-span-2"> | ||
<Heading>02</Heading> | ||
</div> | ||
<div className="col-span-3"> | ||
<Heading>Illuminate your work station</Heading> | ||
<Text> | ||
Pair your keyboard’s backlights with your preferences. Whether | ||
you’re coding, gaming, or creating—adjust the hue, saturation, and | ||
brightness of your backlight using a range of over 22 dynamic RGB | ||
settings. | ||
</Text> | ||
</div> | ||
<div className="col-span-3 col-start-7"> | ||
<Image /> | ||
</div> | ||
</Grid> | ||
<Grid columns={9}> | ||
<div className="col-span-2"> | ||
<Heading>03</Heading> | ||
</div> | ||
<div className="col-span-3"> | ||
<Heading>Wired or wireless? Connect your way</Heading> | ||
<Text> | ||
Whether you’re updating inventory on your laptop or shipping code | ||
on your desktop—seamlessly transition between platforms. The | ||
Shopify Keyboard can pair with up to 3 Bluetooth devices, but if | ||
you prefer to connect via cable, make the switch in seconds with | ||
the toggle function. Whatever your preference, experience a | ||
keyboard that adapts to your needs. | ||
</Text> | ||
</div> | ||
<div className="col-span-3 col-start-7"> | ||
<Image /> | ||
</div> | ||
</Grid> | ||
</Flex> | ||
</Section> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
import {Button} from '@h2/Button'; | ||
import Link from '@h2/Link'; | ||
import {Price} from '@h2/Price'; | ||
import {Heading, Text} from '@h2/Text'; | ||
import {Background, Flex, Section} from '@h2/new/Layout'; | ||
import {useLoaderData} from '@remix-run/react'; | ||
import {Image} from '@shopify/hydrogen'; | ||
|
||
export default function Hero() { | ||
const {product} = useLoaderData(); | ||
const {selectedVariant} = product; | ||
return ( | ||
<Section className="text-white bg-darkGray"> | ||
<Flex direction="down" justify="between"> | ||
<Flex direction="down" gap={6}> | ||
<Text>Coming Soon</Text> | ||
<Heading> | ||
Builder <span className="font-display">Keyboard</span> | ||
</Heading> | ||
<Price variant={selectedVariant} /> | ||
</Flex> | ||
<Flex direction="down" gap={4}> | ||
<Button>Pre-order</Button> | ||
<Text width="narrow"> | ||
See <Link to="/">FAQs</Link> for more details on pre-orders | ||
</Text> | ||
</Flex> | ||
</Flex> | ||
<Background columns={1}> | ||
<Image /> | ||
</Background> | ||
</Section> | ||
); | ||
} |