Skip to content

Commit

Permalink
add and render ImageText section in skeleton homepage
Browse files Browse the repository at this point in the history
  • Loading branch information
juanpprieto committed Jul 27, 2023
1 parent 98bcfc7 commit ba8fc2e
Show file tree
Hide file tree
Showing 4 changed files with 132 additions and 212 deletions.
5 changes: 4 additions & 1 deletion templates/skeleton/.env
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
# These variables are only available locally in MiniOxygen

# SESSION_SECRET="foobar"
# PUBLIC_STORE_DOMAIN="mock.shop"
SESSION_SECRET="foobar"
PUBLIC_STORE_DOMAIN="mock.shop"
PUBLIC_STOREFRONT_API_TOKEN="3b580e70970c4528da70c98e097c2fa0"
PUBLIC_STORE_DOMAIN="hydrogen-preview.myshopify.com"
152 changes: 20 additions & 132 deletions templates/skeleton/app/routes/_index.tsx
Original file line number Diff line number Diff line change
@@ -1,145 +1,33 @@
import type {V2_MetaFunction} from '@shopify/remix-oxygen';
import {defer, type LoaderArgs} from '@shopify/remix-oxygen';
import {Await, useLoaderData, Link} from '@remix-run/react';
import {Suspense} from 'react';
import {Image, Money} from '@shopify/hydrogen';
import type {
FeaturedCollectionFragment,
RecommendedProductsQuery,
} from 'storefrontapi.generated';
import {json, type LoaderArgs} from '@shopify/remix-oxygen';
import {useLoaderData} from '@remix-run/react';
import {ImageText} from '../sections/ImageText';

export const meta: V2_MetaFunction = () => {
return [{title: 'Hydrogen | Home'}];
};

export async function loader({context}: LoaderArgs) {
const {storefront} = context;
const {collections} = await storefront.query(FEATURED_COLLECTION_QUERY);
const featuredCollection = collections.nodes[0];
const recommendedProducts = storefront.query(RECOMMENDED_PRODUCTS_QUERY);

return defer({featuredCollection, recommendedProducts});
// For now we query for an individual section. In the future we'll want to
// query for all sections on the page via a route metaobject query that holds
// an array of sections for a given route
const {section} = await context.storefront.query<{
// TODO: this type should be auto code-generated from the CLI based
section: Parameters<typeof ImageText>[0];
}>(ImageText.section.query, {
variables: {
handle: 'section-image-text-default',
},
});

return json({
imageText: section,
});
}

export default function Homepage() {
const data = useLoaderData<typeof loader>();
return (
<div className="home">
<FeaturedCollection collection={data.featuredCollection} />
<RecommendedProducts products={data.recommendedProducts} />
</div>
);
}

function FeaturedCollection({
collection,
}: {
collection: FeaturedCollectionFragment;
}) {
const image = collection.image;
const {imageText} = useLoaderData<typeof loader>();
return (
<Link
className="featured-collection"
to={`/collections/${collection.handle}`}
>
{image && (
<div className="featured-collection-image">
<Image data={image} sizes="100vw" />
</div>
)}
<h1>{collection.title}</h1>
</Link>
<div className="home">{imageText && <ImageText {...imageText} />}</div>
);
}

function RecommendedProducts({
products,
}: {
products: Promise<RecommendedProductsQuery>;
}) {
return (
<div className="recommended-products">
<h2>Recommended Products</h2>
<Suspense fallback={<div>Loading...</div>}>
<Await resolve={products}>
{({products}) => (
<div className="recommended-products-grid">
{products.nodes.map((product) => (
<Link
key={product.id}
className="recommended-product"
to={`/products/${product.handle}`}
>
<Image
data={product.images.nodes[0]}
aspectRatio="1/1"
sizes="(min-width: 45em) 20vw, 50vw"
/>
<h4>{product.title}</h4>
<small>
<Money data={product.priceRange.minVariantPrice} />
</small>
</Link>
))}
</div>
)}
</Await>
</Suspense>
<br />
</div>
);
}

const FEATURED_COLLECTION_QUERY = `#graphql
fragment FeaturedCollection on Collection {
id
title
image {
id
url
altText
width
height
}
handle
}
query FeaturedCollection($country: CountryCode, $language: LanguageCode)
@inContext(country: $country, language: $language) {
collections(first: 1, sortKey: UPDATED_AT, reverse: true) {
nodes {
...FeaturedCollection
}
}
}
` as const;

const RECOMMENDED_PRODUCTS_QUERY = `#graphql
fragment RecommendedProduct on Product {
id
title
handle
priceRange {
minVariantPrice {
amount
currencyCode
}
}
images(first: 1) {
nodes {
id
url
altText
width
height
}
}
}
query RecommendedProducts ($country: CountryCode, $language: LanguageCode)
@inContext(country: $country, language: $language) {
products(first: 4, sortKey: UPDATED_AT, reverse: true) {
nodes {
...RecommendedProduct
}
}
}
` as const;
108 changes: 108 additions & 0 deletions templates/skeleton/app/sections/ImageText.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import {defineSection, Image} from '@shopify/hydrogen';
import type {MediaImage} from '@shopify/hydrogen/storefront-api-types';

// TODO: these types should be auto code-generated from the CLI based
// on the passed schema and the generated query.
type SectionImageText = {
name: 'Image Text';
type: 'section_image_text';
heading: {
value: string;
};
image: {
reference?: MediaImage;
};
};

export function ImageText({heading, image}: SectionImageText) {
return (
<section className="section_image_text">
<h1>{heading.value}</h1>
{image.reference?.image ? (
<Image sizes="100vw" data={image.reference.image} />
) : null}
</section>
);
}

ImageText.section = defineSection({
name: 'Image Text',
type: 'image_text',
fields: [
{
name: 'Text',
key: 'heading',
type: 'single_line_text_field',
default: 'Image Text Heading',
required: true,
},
{
name: 'Image',
key: 'image',
type: 'file_reference',
required: true,
default: {
altText: null,
url: 'https://placehold.co/1920x1080.jpg',
width: 1920,
height: 1080,
},
},
],
});

/*
* The generated query for this section looks like:
* --------------------------------------------------------------------------------------
query SectionImageText($handle: String!) {
section: metaobject(handle: {handle: $handle, type: "section_image_text"}) {
id
handle
type
heading: field(key: "heading") {
value
}
image: field(key: "image") {
reference {
...MediaImageFragment
}
}
}
}
fragment MediaImageFragment on MediaImage {
__typename
image {
altText
url
width
height
}
}
*/

/*
* The query response would look like
--------------------------------------------------------------------------------------
*
{
"id": "gid://shopify/Metaobject/602701880",
"handle": "section-image-text-default",
"type": "section_image_text",
"heading": {
"value": "Image Text Heading"
},
"image": {
"reference": {
"__typename": "MediaImage",
"image": {
"altText": null,
"url": "https://cdn.shopify.com/s/files/1/0551/4566/0472/files/Main-the-Inferno_23782978-cfe9-42ca-b5ef-9f04284b1872.jpg?v=1684520797",
"width": 3908,
"height": 3908
}
}
}
}
*/
79 changes: 0 additions & 79 deletions templates/skeleton/storefrontapi.generated.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,77 +150,6 @@ export type SitemapQuery = {
};
};

export type FeaturedCollectionFragment = Pick<
StorefrontAPI.Collection,
'id' | 'title' | 'handle'
> & {
image?: StorefrontAPI.Maybe<
Pick<StorefrontAPI.Image, 'id' | 'url' | 'altText' | 'width' | 'height'>
>;
};

export type FeaturedCollectionQueryVariables = StorefrontAPI.Exact<{
country?: StorefrontAPI.InputMaybe<StorefrontAPI.CountryCode>;
language?: StorefrontAPI.InputMaybe<StorefrontAPI.LanguageCode>;
}>;

export type FeaturedCollectionQuery = {
collections: {
nodes: Array<
Pick<StorefrontAPI.Collection, 'id' | 'title' | 'handle'> & {
image?: StorefrontAPI.Maybe<
Pick<
StorefrontAPI.Image,
'id' | 'url' | 'altText' | 'width' | 'height'
>
>;
}
>;
};
};

export type RecommendedProductFragment = Pick<
StorefrontAPI.Product,
'id' | 'title' | 'handle'
> & {
priceRange: {
minVariantPrice: Pick<StorefrontAPI.MoneyV2, 'amount' | 'currencyCode'>;
};
images: {
nodes: Array<
Pick<StorefrontAPI.Image, 'id' | 'url' | 'altText' | 'width' | 'height'>
>;
};
};

export type RecommendedProductsQueryVariables = StorefrontAPI.Exact<{
country?: StorefrontAPI.InputMaybe<StorefrontAPI.CountryCode>;
language?: StorefrontAPI.InputMaybe<StorefrontAPI.LanguageCode>;
}>;

export type RecommendedProductsQuery = {
products: {
nodes: Array<
Pick<StorefrontAPI.Product, 'id' | 'title' | 'handle'> & {
priceRange: {
minVariantPrice: Pick<
StorefrontAPI.MoneyV2,
'amount' | 'currencyCode'
>;
};
images: {
nodes: Array<
Pick<
StorefrontAPI.Image,
'id' | 'url' | 'altText' | 'width' | 'height'
>
>;
};
}
>;
};
};

export type CustomerAddressUpdateMutationVariables = StorefrontAPI.Exact<{
address: StorefrontAPI.MailingAddressInput;
customerAccessToken: StorefrontAPI.Scalars['String'];
Expand Down Expand Up @@ -1783,14 +1712,6 @@ interface GeneratedQueryTypes {
return: SitemapQuery;
variables: SitemapQueryVariables;
};
'#graphql\n fragment FeaturedCollection on Collection {\n id\n title\n image {\n id\n url\n altText\n width\n height\n }\n handle\n }\n query FeaturedCollection($country: CountryCode, $language: LanguageCode)\n @inContext(country: $country, language: $language) {\n collections(first: 1, sortKey: UPDATED_AT, reverse: true) {\n nodes {\n ...FeaturedCollection\n }\n }\n }\n': {
return: FeaturedCollectionQuery;
variables: FeaturedCollectionQueryVariables;
};
'#graphql\n fragment RecommendedProduct on Product {\n id\n title\n handle\n priceRange {\n minVariantPrice {\n amount\n currencyCode\n }\n }\n images(first: 1) {\n nodes {\n id\n url\n altText\n width\n height\n }\n }\n }\n query RecommendedProducts ($country: CountryCode, $language: LanguageCode)\n @inContext(country: $country, language: $language) {\n products(first: 4, sortKey: UPDATED_AT, reverse: true) {\n nodes {\n ...RecommendedProduct\n }\n }\n }\n': {
return: RecommendedProductsQuery;
variables: RecommendedProductsQueryVariables;
};
'#graphql\n fragment OrderMoney on MoneyV2 {\n amount\n currencyCode\n }\n fragment AddressFull on MailingAddress {\n address1\n address2\n city\n company\n country\n countryCodeV2\n firstName\n formatted\n id\n lastName\n name\n phone\n province\n provinceCode\n zip\n }\n fragment DiscountApplication on DiscountApplication {\n value {\n __typename\n ... on MoneyV2 {\n ...OrderMoney\n }\n ... on PricingPercentageValue {\n percentage\n }\n }\n }\n fragment OrderLineProductVariant on ProductVariant {\n id\n image {\n altText\n height\n url\n id\n width\n }\n price {\n ...OrderMoney\n }\n product {\n handle\n }\n sku\n title\n }\n fragment OrderLineItemFull on OrderLineItem {\n title\n quantity\n discountAllocations {\n allocatedAmount {\n ...OrderMoney\n }\n discountApplication {\n ...DiscountApplication\n }\n }\n originalTotalPrice {\n ...OrderMoney\n }\n discountedTotalPrice {\n ...OrderMoney\n }\n variant {\n ...OrderLineProductVariant\n }\n }\n fragment Order on Order {\n id\n name\n orderNumber\n statusUrl\n processedAt\n fulfillmentStatus\n totalTaxV2 {\n ...OrderMoney\n }\n totalPriceV2 {\n ...OrderMoney\n }\n subtotalPriceV2 {\n ...OrderMoney\n }\n shippingAddress {\n ...AddressFull\n }\n discountApplications(first: 100) {\n nodes {\n ...DiscountApplication\n }\n }\n lineItems(first: 100) {\n nodes {\n ...OrderLineItemFull\n }\n }\n }\n query Order(\n $country: CountryCode\n $language: LanguageCode\n $orderId: ID!\n ) @inContext(country: $country, language: $language) {\n order: node(id: $orderId) {\n ... on Order {\n ...Order\n }\n }\n }\n': {
return: OrderQuery;
variables: OrderQueryVariables;
Expand Down

0 comments on commit ba8fc2e

Please sign in to comment.