Skip to content

Commit

Permalink
Adds basic layout for builder keyboard page
Browse files Browse the repository at this point in the history
  • Loading branch information
benjaminsehl committed May 22, 2024
1 parent a7553ce commit fce0c83
Show file tree
Hide file tree
Showing 5 changed files with 324 additions and 1 deletion.
1 change: 0 additions & 1 deletion app/root.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,6 @@ export function ErrorBoundary() {
</Layout>
<ScrollRestoration nonce={nonce} />
<Scripts nonce={nonce} />
<LiveReload nonce={nonce} />
</body>
</html>
);
Expand Down
194 changes: 194 additions & 0 deletions app/routes/products.builder-keyboard/route.jsx
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 */
27 changes: 27 additions & 0 deletions app/routes/products.builder-keyboard/sections/cta.jsx
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 app/routes/products.builder-keyboard/sections/features.jsx
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>
);
}
34 changes: 34 additions & 0 deletions app/routes/products.builder-keyboard/sections/hero.jsx
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>
);
}

0 comments on commit fce0c83

Please sign in to comment.