Skip to content

Commit

Permalink
Merge pull request #38 from buildheadless/thomas/bhead-33-feattheme-a…
Browse files Browse the repository at this point in the history
…dd-collection-list-section

feat:(theme) Add collection list section
  • Loading branch information
thomasKn authored Dec 3, 2023
2 parents 89782cd + 1707a54 commit eb58057
Show file tree
Hide file tree
Showing 20 changed files with 274 additions and 79 deletions.
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 23 additions & 0 deletions templates/hydrogen-theme/app/components/CollectionCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import {Link} from '@remix-run/react';
import {Image} from '@shopify/hydrogen';
import type {CollectionsQuery} from 'storefrontapi.generated';

export function CollectionCard(props: {
collection: CollectionsQuery['collections']['nodes'][0];
className?: string;
}) {
const {collection} = props;

return (
<div className="overflow-hidden rounded-lg border">
<Link to={`/collections/${collection.handle}`}>
{collection.image && (
<Image aspectRatio="16/9" sizes="33vw" data={collection.image} />
)}
<div className="p-3">
<div className="text-lg">{collection.title}</div>
</div>
</Link>
</div>
);
}
19 changes: 19 additions & 0 deletions templates/hydrogen-theme/app/components/CollectionListGrid.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import type {CollectionsQuery} from 'storefrontapi.generated';
import {flattenConnection} from '@shopify/hydrogen';
import {CollectionCard} from './CollectionCard';

export function CollectionListGrid(props: {
collections?: CollectionsQuery['collections'];
}) {
const collections = flattenConnection(props.collections);

return collections?.length > 0 ? (
<ul className="grid grid-cols-3 gap-10">
{collections.map((collection) => (
<li key={collection.id}>
<CollectionCard collection={collection} />
</li>
))}
</ul>
) : null;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import type {TypeFromSelection} from 'groqd';

import type {COLLECTION_LIST_SECTION_FRAGMENT} from '~/qroq/sections';
import type {loader as indexLoader} from '../../routes/_index';
import type {SectionDefaultProps} from '~/lib/type';
import {Await, useLoaderData} from '@remix-run/react';
import {Suspense} from 'react';
import {CollectionListGrid} from '../CollectionListGrid';

type CollectionListSectionProps = TypeFromSelection<
typeof COLLECTION_LIST_SECTION_FRAGMENT
>;

export function CollectionListSection(
props: SectionDefaultProps & {data: CollectionListSectionProps},
) {
const loaderData = useLoaderData<typeof indexLoader>();
const collectionListPromise = loaderData?.collectionListPromise;
const sectionGids = props.data.collections
?.map((collection) => collection.store.gid)
.join(',');

return collectionListPromise ? (
<Suspense fallback={<div className="container">Loading...</div>}>
<Await resolve={collectionListPromise}>
{(data) => {
// Resolve the collection list data from Shopify with the gids from Sanity
const collections = data.find(({collections}) => {
const gids = collections.nodes
.map((collection) => collection.id)
.join(',');
return sectionGids === gids ? collections : null;
})?.collections;

return (
<div className="container">
<CollectionListGrid collections={collections} />
</div>
);
}}
</Await>
</Suspense>
) : null;
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {Await, useLoaderData} from '@remix-run/react';

import type {FEATURED_COLLECTION_SECTION_FRAGMENT} from '~/qroq/sections';
import type {loader as indexLoader} from '../../routes/_index';
import type {SectionDefaultProps} from '~/lib/type';
import {ProductCardGrid} from '../ProductCardGrid';
import {flattenConnection} from '@shopify/hydrogen';

Expand All @@ -12,15 +13,21 @@ type FeaturedCollectionSectionProps = TypeFromSelection<
>;

export function FeaturedCollectionSection(
props: FeaturedCollectionSectionProps,
props: SectionDefaultProps & {data: FeaturedCollectionSectionProps},
) {
const loaderData = useLoaderData<typeof indexLoader>();
const featuredCollectionPromise = loaderData?.featuredCollectionPromise;
const gid = props.data?.collection?.store.gid;

return featuredCollectionPromise ? (
<Suspense fallback={<div className="container">Loading...</div>}>
<Await resolve={featuredCollectionPromise}>
{({collection}) => {
{(data) => {
// Resolve the collection data from Shopify with the gid from Sanity
const collection = data.find(
({collection}) => gid?.includes(collection?.id!),
)?.collection;

const products =
collection?.products?.nodes &&
collection?.products?.nodes?.length > 1
Expand Down
7 changes: 4 additions & 3 deletions templates/hydrogen-theme/app/graphql/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,10 +122,11 @@ export const COLLECTIONS_QUERY = `#graphql
$language: LanguageCode
$first: Int
$last: Int
$query: String
$startCursor: String
$endCursor: String
) @inContext(country: $country, language: $language) {
collections(first: $first, last: $last, before: $startCursor, after: $endCursor) {
collections(first: $first, last: $last, before: $startCursor, after: $endCursor, query: $query) {
nodes {
id
title
Expand Down Expand Up @@ -223,12 +224,12 @@ export const COLLECTION_QUERY = `#graphql

export const FEATURED_COLLECTION_QUERY = `#graphql
query FeaturedCollection(
$handle: String!
$id: ID!
$country: CountryCode
$language: LanguageCode
$first: Int
) @inContext(country: $country, language: $language) {
collection(handle: $handle) {
collection(id: $id) {
id
handle
title
Expand Down
1 change: 1 addition & 0 deletions templates/hydrogen-theme/app/hooks/useSanityData.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export function useSanityData<T extends Initial>(initial: T) {
const params = sanity?.params;
const query = sanity?.query || '';

// Todo: find a way to avoid using useQuery hook in production when STEGA is disabled
const {data, loading, sourceMap} = useQuery(query || '', params, {
initial: initial as any,
});
Expand Down
64 changes: 55 additions & 9 deletions templates/hydrogen-theme/app/lib/resolveShopifyPromises.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import type {Storefront} from '@shopify/hydrogen';
import {parseGid, type Storefront} from '@shopify/hydrogen';
import type {InferType} from 'groqd';

import type {FeaturedCollectionQuery} from 'storefrontapi.generated';
import type {
CollectionsQuery,
FeaturedCollectionQuery,
} from 'storefrontapi.generated';
import type {PAGE_QUERY} from '~/qroq/queries';
import {FEATURED_COLLECTION_QUERY} from '~/graphql/queries';
import {COLLECTIONS_QUERY, FEATURED_COLLECTION_QUERY} from '~/graphql/queries';

type SanityPageData = InferType<typeof PAGE_QUERY>;
type PromiseResolverArgs = {
Expand All @@ -25,35 +28,78 @@ export function resolveShopifyPromises({
storefront,
});

return {featuredCollectionPromise};
const collectionListPromise = resolveCollectionListPromise({
document,
storefront,
});

return {featuredCollectionPromise, collectionListPromise};
}

function resolveFeaturedCollectionPromise({
document,
storefront,
}: PromiseResolverArgs) {
let featuredCollectionPromise: Promise<FeaturedCollectionQuery> | undefined;
const promises: Promise<FeaturedCollectionQuery>[] = [];

document.data?.sections?.forEach((section) => {
if (section._type === 'featuredCollectionSection') {
const collectionHandle = section.collection?.store.slug.current;
const gid = section.collection?.store.gid;

if (!collectionHandle) {
if (!gid) {
return undefined;
}

const promise = storefront.query(FEATURED_COLLECTION_QUERY, {
variables: {
handle: collectionHandle,
id: gid,
first: 4,
country: storefront.i18n.country,
language: storefront.i18n.language,
},
});

featuredCollectionPromise = promise;
promises.push(promise);
}
});

const featuredCollectionPromise = Promise.all(promises);

return featuredCollectionPromise;
}

function resolveCollectionListPromise({
document,
storefront,
}: PromiseResolverArgs) {
const promises: Promise<CollectionsQuery>[] = [];

document.data?.sections?.forEach((section) => {
if (section._type === 'collectionListSection') {
const first = section.collections?.length;
const ids = section.collections?.map(
(collection) => parseGid(collection.store.gid).id,
);
const query = ids?.map((id) => `(id:${id})`).join(' OR ');

if (!ids?.length || !first) {
return undefined;
}

const promise = storefront.query(COLLECTIONS_QUERY, {
variables: {
first,
query,
country: storefront.i18n.country,
language: storefront.i18n.language,
},
});

promises.push(promise);
}
});

const collectionListPromise = Promise.all(promises);

return collectionListPromise;
}
5 changes: 5 additions & 0 deletions templates/hydrogen-theme/app/lib/sectionRelsolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ export const sections: {
}),
),
),
collectionListSection: lazy(() =>
import('../components/sections/CollectionListSection').then((module) => ({
default: module.CollectionListSection,
})),
),
ctaSection: lazy(() =>
import('../components/sections/CtaSection').then((module) => ({
default: module.CtaSection,
Expand Down
25 changes: 21 additions & 4 deletions templates/hydrogen-theme/app/qroq/sections.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,26 @@ export const FEATURED_COLLECTION_SECTION_FRAGMENT = {
.deref()
.grab({
store: q('store').grab({
slug: q.object({
current: q.string(),
_type: q.string(),
}),
gid: q.string(),
}),
})
.nullable(),
settings: SECTION_SETTINGS_FRAGMENT,
} satisfies Selection;

/*
|--------------------------------------------------------------------------
| Collection List Section
|--------------------------------------------------------------------------
*/
export const COLLECTION_LIST_SECTION_FRAGMENT = {
_type: z.enum(['collectionListSection']),
_key: q.string().nullable(),
collections: q('collections[]', {isArray: true})
.deref()
.grab({
store: q('store').grab({
gid: q.string(),
}),
})
.nullable(),
Expand Down Expand Up @@ -106,6 +122,7 @@ export const CTA_SECTION_FRAGMENT = {
export const SECTIONS_LIST_SELECTION = {
"_type == 'imageBannerSection'": IMAGE_BANNER_SECTION_FRAGMENT,
"_type == 'featuredCollectionSection'": FEATURED_COLLECTION_SECTION_FRAGMENT,
"_type == 'collectionListSection'": COLLECTION_LIST_SECTION_FRAGMENT,
"_type == 'ctaSection'": CTA_SECTION_FRAGMENT,
};

Expand Down
10 changes: 6 additions & 4 deletions templates/hydrogen-theme/app/routes/($locale).$.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,11 @@ export async function loader({context, params, request}: LoaderFunctionArgs) {
params: queryParams,
});

const {featuredCollectionPromise} = resolveShopifyPromises({
document: page,
storefront,
});
const {featuredCollectionPromise, collectionListPromise} =
resolveShopifyPromises({
document: page,
storefront,
});

if (!page.data) {
throw new Response(null, {
Expand All @@ -42,6 +43,7 @@ export async function loader({context, params, request}: LoaderFunctionArgs) {
return defer({
page,
featuredCollectionPromise,
collectionListPromise,
...sanityPreviewPayload({
query: PAGE_QUERY.query,
params: queryParams,
Expand Down
Loading

0 comments on commit eb58057

Please sign in to comment.