mirror of
https://github.com/vercel/commerce.git
synced 2025-05-12 04:37:51 +00:00
feat: add inlinking transmission code block
Signed-off-by: Chloe <pinkcloudvnn@gmail.com>
This commit is contained in:
parent
020b21892d
commit
87a6fe188c
31
app/search/[collection]/loading.tsx
Normal file
31
app/search/[collection]/loading.tsx
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import BreadcrumbHome from 'components/breadcrumb/breadcrumb-home';
|
||||||
|
import { YMMFiltersPlaceholder } from 'components/filters';
|
||||||
|
import { FiltersListPlaceholder } from 'components/layout/search/filters/filters-container';
|
||||||
|
import { HeaderPlaceholder } from 'components/layout/search/header';
|
||||||
|
import ProductsGridPlaceholder from 'components/layout/search/placeholder';
|
||||||
|
|
||||||
|
const Loading = () => {
|
||||||
|
return (
|
||||||
|
<div className="mx-auto mt-6 max-w-screen-2xl px-8 pb-10">
|
||||||
|
<div className="grid lg:grid-cols-3 lg:gap-x-10 xl:grid-cols-4">
|
||||||
|
<aside className="hidden lg:block">
|
||||||
|
<div className="mb-5">
|
||||||
|
<YMMFiltersPlaceholder />
|
||||||
|
</div>
|
||||||
|
<h3 className="sr-only">Filters</h3>
|
||||||
|
<FiltersListPlaceholder />
|
||||||
|
</aside>
|
||||||
|
<div className="lg:col-span-2 xl:col-span-3">
|
||||||
|
<div className="mb-2">
|
||||||
|
<BreadcrumbHome />
|
||||||
|
</div>
|
||||||
|
<HeaderPlaceholder />
|
||||||
|
|
||||||
|
<ProductsGridPlaceholder />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Loading;
|
@ -18,6 +18,9 @@ import Header, { HeaderPlaceholder } from 'components/layout/search/header';
|
|||||||
import HelpfulLinks from 'components/layout/search/helpful-links';
|
import HelpfulLinks from 'components/layout/search/helpful-links';
|
||||||
import ProductsGridPlaceholder from 'components/layout/search/placeholder';
|
import ProductsGridPlaceholder from 'components/layout/search/placeholder';
|
||||||
import SortingMenu from 'components/layout/search/sorting-menu';
|
import SortingMenu from 'components/layout/search/sorting-menu';
|
||||||
|
import TransmissionCode from 'components/transmission-codes';
|
||||||
|
import TransmissionModels from 'components/transmission-model';
|
||||||
|
import { MAKE_FILTER_ID } from 'lib/constants';
|
||||||
import { Suspense } from 'react';
|
import { Suspense } from 'react';
|
||||||
|
|
||||||
export async function generateMetadata({
|
export async function generateMetadata({
|
||||||
@ -79,6 +82,7 @@ export default async function CategorySearchPage(props: {
|
|||||||
params: { collection: string };
|
params: { collection: string };
|
||||||
searchParams?: { [key: string]: string | string[] | undefined };
|
searchParams?: { [key: string]: string | string[] | undefined };
|
||||||
}) {
|
}) {
|
||||||
|
const collectionHandle = props.params.collection;
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="mx-auto mt-6 max-w-screen-2xl px-8 pb-10">
|
<div className="mx-auto mt-6 max-w-screen-2xl px-8 pb-10">
|
||||||
@ -90,44 +94,49 @@ export default async function CategorySearchPage(props: {
|
|||||||
</Suspense>
|
</Suspense>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<SubMenu collection={props.params.collection} />
|
<SubMenu collection={collectionHandle} />
|
||||||
<h3 className="sr-only">Filters</h3>
|
<h3 className="sr-only">Filters</h3>
|
||||||
<Suspense
|
<Suspense fallback={<FiltersListPlaceholder />} key={`filters-${collectionHandle}`}>
|
||||||
fallback={<FiltersListPlaceholder />}
|
<FiltersContainer searchParams={props.searchParams} collection={collectionHandle} />
|
||||||
key={`filters-${props.params.collection}`}
|
<HelpfulLinks collection={collectionHandle} />
|
||||||
>
|
|
||||||
<FiltersContainer
|
|
||||||
searchParams={props.searchParams}
|
|
||||||
collection={props.params.collection}
|
|
||||||
/>
|
|
||||||
<HelpfulLinks collection={props.params.collection} />
|
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</aside>
|
</aside>
|
||||||
<div className="lg:col-span-2 xl:col-span-3">
|
<div className="lg:col-span-2 xl:col-span-3">
|
||||||
<div className="mb-2">
|
<div className="mb-2">
|
||||||
<Suspense fallback={<BreadcrumbHome />} key={`breadcrumb-${props.params.collection}`}>
|
<Suspense fallback={<BreadcrumbHome />} key={`breadcrumb-${collectionHandle}`}>
|
||||||
<Breadcrumb type="collection" handle={props.params.collection} />
|
<Breadcrumb type="collection" handle={collectionHandle} />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</div>
|
</div>
|
||||||
<Suspense fallback={<HeaderPlaceholder />} key={`header-${props.params.collection}`}>
|
<Suspense fallback={<HeaderPlaceholder />} key={`header-${collectionHandle}`}>
|
||||||
<Header collection={props.params.collection} />
|
<Header collection={collectionHandle} />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
|
|
||||||
<Suspense
|
<Suspense fallback={<ProductsGridPlaceholder />} key={`products-${collectionHandle}`}>
|
||||||
fallback={<ProductsGridPlaceholder />}
|
|
||||||
key={`products-${props.params.collection}`}
|
|
||||||
>
|
|
||||||
<CategoryPage {...props} />
|
<CategoryPage {...props} />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<FAQ handle="plp-faqs" />
|
<FAQ handle="plp-faqs" />
|
||||||
|
{collectionHandle.startsWith('transmissions') && (
|
||||||
|
<>
|
||||||
|
<Suspense>
|
||||||
|
<TransmissionCode
|
||||||
|
collectionHandle={collectionHandle}
|
||||||
|
make={props.searchParams?.[MAKE_FILTER_ID] as string | undefined}
|
||||||
|
/>
|
||||||
|
</Suspense>
|
||||||
|
<Suspense>
|
||||||
|
<TransmissionModels
|
||||||
|
collectionHandle={collectionHandle}
|
||||||
|
make={props.searchParams?.[MAKE_FILTER_ID] as string | undefined}
|
||||||
|
/>
|
||||||
|
</Suspense>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
<Suspense>
|
<Suspense>
|
||||||
<Manufacturers
|
<Manufacturers
|
||||||
variant={
|
variant={(collectionHandle as string).includes('engines') ? 'engines' : 'transmissions'}
|
||||||
(props.params.collection as string).includes('engines') ? 'engines' : 'transmissions'
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</>
|
</>
|
||||||
|
@ -54,7 +54,7 @@ const ProductsList = ({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Grid className="hide-scrollbar max-h-[1000px] grid-cols-1 overflow-y-auto border-b border-gray-100 pb-4 sm:grid-cols-2 sm:gap-x-8 lg:grid-cols-3">
|
<Grid className="hide-scrollbar max-h-[1000px] grid-cols-1 overflow-y-auto border-b border-gray-100 pb-4 sm:grid-cols-2 sm:gap-x-8 lg:grid-cols-3">
|
||||||
{products.map((product) => (
|
{products.map((product, index) => (
|
||||||
<Grid.Item key={product.handle} className="animate-fadeIn">
|
<Grid.Item key={product.handle} className="animate-fadeIn">
|
||||||
<GridTileImage
|
<GridTileImage
|
||||||
alt={product.title}
|
alt={product.title}
|
||||||
@ -63,6 +63,7 @@ const ProductsList = ({
|
|||||||
fill
|
fill
|
||||||
sizes="(min-width: 768px) 33vw, (min-width: 640px) 50vw, 100vw"
|
sizes="(min-width: 768px) 33vw, (min-width: 640px) 50vw, 100vw"
|
||||||
href={`/product/${product.handle}`}
|
href={`/product/${product.handle}`}
|
||||||
|
priority={index > 10 ? false : true}
|
||||||
/>
|
/>
|
||||||
</Grid.Item>
|
</Grid.Item>
|
||||||
))}
|
))}
|
||||||
|
@ -36,7 +36,7 @@ const ManufacturersGrid = ({ manufacturers, variant = 'home' }: ManufacturersGri
|
|||||||
)}
|
)}
|
||||||
{variant === 'home' && <ButtonGroup manufacturer={manufacturer} />}
|
{variant === 'home' && <ButtonGroup manufacturer={manufacturer} />}
|
||||||
</div>
|
</div>
|
||||||
))}{' '}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<hr className="my-10 w-full" />
|
<hr className="my-10 w-full" />
|
||||||
</>
|
</>
|
||||||
|
52
components/transmission-codes/index.tsx
Normal file
52
components/transmission-codes/index.tsx
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import { StarIcon } from '@heroicons/react/24/outline';
|
||||||
|
import Tag from 'components/tag';
|
||||||
|
import { MAKE_FILTER_ID, TRANSMISSION_CODE_FILTER_ID } from 'lib/constants';
|
||||||
|
import { getProductFilters } from 'lib/shopify';
|
||||||
|
import { getCollectionUrl } from 'lib/utils';
|
||||||
|
import Link from 'next/link';
|
||||||
|
|
||||||
|
const TransmissionCode = async ({
|
||||||
|
collectionHandle,
|
||||||
|
make
|
||||||
|
}: {
|
||||||
|
collectionHandle: string;
|
||||||
|
make?: string;
|
||||||
|
}) => {
|
||||||
|
const transmissionCodes = await getProductFilters(
|
||||||
|
{ collection: collectionHandle, make },
|
||||||
|
TRANSMISSION_CODE_FILTER_ID
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!transmissionCodes || transmissionCodes.values.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="px-6 pt-20">
|
||||||
|
<div className="mx-auto flex max-w-7xl flex-col gap-3">
|
||||||
|
<Tag text="Get Started" />
|
||||||
|
<h3 className="mb-3 text-3xl font-semibold lg:text-4xl">{`Browse By Transmission Code`}</h3>
|
||||||
|
<div className="h-auto max-h-[700px] w-full overflow-auto rounded px-10 py-6 shadow">
|
||||||
|
<p className="flex items-center gap-2">
|
||||||
|
<StarIcon className="size-4" />
|
||||||
|
<span className="font-medium text-blue-800">Popular Transmission Codes</span>
|
||||||
|
</p>
|
||||||
|
<div className="mt-6 grid grid-cols-2 gap-x-12 gap-y-5 md:grid-cols-3 md:gap-y-8 lg:grid-cols-4 xl:grid-cols-5">
|
||||||
|
{transmissionCodes.values.map((transmissionCode) => (
|
||||||
|
<Link
|
||||||
|
href={`${getCollectionUrl(collectionHandle)}?${TRANSMISSION_CODE_FILTER_ID}=${transmissionCode.value}${make ? `&${MAKE_FILTER_ID}=${make}` : ''}`}
|
||||||
|
key={transmissionCode.id}
|
||||||
|
>
|
||||||
|
<div className="rounded border border-primary px-2 py-1 text-sm">
|
||||||
|
{transmissionCode.label}
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TransmissionCode;
|
55
components/transmission-model/index.tsx
Normal file
55
components/transmission-model/index.tsx
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
import { GlobeAltIcon } from '@heroicons/react/24/outline';
|
||||||
|
import { MAKE_FILTER_ID, MODEL_FILTER_ID } from 'lib/constants';
|
||||||
|
import { getProductFilters } from 'lib/shopify';
|
||||||
|
import { getCollectionUrl } from 'lib/utils';
|
||||||
|
import Link from 'next/link';
|
||||||
|
|
||||||
|
const TransmissionModels = async ({
|
||||||
|
collectionHandle,
|
||||||
|
make
|
||||||
|
}: {
|
||||||
|
collectionHandle: string;
|
||||||
|
make?: string;
|
||||||
|
}) => {
|
||||||
|
// eg: collectionHandle = transmission-bmw-x5
|
||||||
|
const makeFromCollectionHandle = collectionHandle.split('-')[1];
|
||||||
|
|
||||||
|
if (!makeFromCollectionHandle && !make) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const transmissionModels = await getProductFilters(
|
||||||
|
{ collection: collectionHandle, make },
|
||||||
|
MODEL_FILTER_ID
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!transmissionModels || transmissionModels.values.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="px-6 pt-20">
|
||||||
|
<div className="mx-auto max-w-7xl">
|
||||||
|
<h3 className="mb-6 text-3xl font-semibold lg:text-4xl">Browse By Transmission Models</h3>
|
||||||
|
<div className="h-auto max-h-[700px] w-full overflow-auto rounded px-10 py-6 shadow">
|
||||||
|
<p className="flex items-center gap-2">
|
||||||
|
<GlobeAltIcon className="size-4" />
|
||||||
|
<span className="font-medium text-blue-800">Models</span>
|
||||||
|
</p>
|
||||||
|
<div className="mt-6 grid grid-cols-2 gap-x-12 gap-y-5 md:grid-cols-3 md:gap-y-8 lg:grid-cols-4 xl:grid-cols-5">
|
||||||
|
{transmissionModels.values.map((model) => (
|
||||||
|
<Link
|
||||||
|
href={`${getCollectionUrl(collectionHandle)}?${MODEL_FILTER_ID}=${model.value}${make ? `&${MAKE_FILTER_ID}=${make}` : ''}`}
|
||||||
|
key={model.id}
|
||||||
|
>
|
||||||
|
<div className="rounded border border-primary px-2 py-1 text-sm">{model.label}</div>
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TransmissionModels;
|
@ -42,6 +42,7 @@ export const SHOPIFY_GRAPHQL_ADMIN_ADMIN_API_ENDPOINT = '/admin/api/2024-04/grap
|
|||||||
export const CORE_WAIVER = 'core-waiver';
|
export const CORE_WAIVER = 'core-waiver';
|
||||||
export const CORE_VARIANT_ID_KEY = 'coreVariantId';
|
export const CORE_VARIANT_ID_KEY = 'coreVariantId';
|
||||||
|
|
||||||
|
export const TRANSMISSION_CODE_FILTER_ID = 'filter.p.m.custom.transmission_code';
|
||||||
export const AVAILABILITY_FILTER_ID = 'filter.v.availability';
|
export const AVAILABILITY_FILTER_ID = 'filter.v.availability';
|
||||||
export const PRICE_FILTER_ID = 'filter.v.price';
|
export const PRICE_FILTER_ID = 'filter.v.price';
|
||||||
export const MAKE_FILTER_ID = 'filter.p.m.custom.make_composite';
|
export const MAKE_FILTER_ID = 'filter.p.m.custom.make_composite';
|
||||||
|
@ -38,7 +38,8 @@ import { getCartQuery } from './queries/cart';
|
|||||||
import {
|
import {
|
||||||
getCollectionProductsQuery,
|
getCollectionProductsQuery,
|
||||||
getCollectionQuery,
|
getCollectionQuery,
|
||||||
getCollectionsQuery
|
getCollectionsQuery,
|
||||||
|
getTransmissionCodesQuery
|
||||||
} from './queries/collection';
|
} from './queries/collection';
|
||||||
import { getCustomerQuery } from './queries/customer';
|
import { getCustomerQuery } from './queries/customer';
|
||||||
import { getMenuQuery } from './queries/menu';
|
import { getMenuQuery } from './queries/menu';
|
||||||
@ -373,10 +374,10 @@ const reshapeCollections = (collections: ShopifyCollection[]) => {
|
|||||||
return reshapedCollections;
|
return reshapedCollections;
|
||||||
};
|
};
|
||||||
|
|
||||||
const reshapeFilters = (filters: ShopifyFilter[]): Filter[] => {
|
const reshapeFilters = (filters: ShopifyFilter[], excludeYMM = true): Filter[] => {
|
||||||
const reshapedFilters = [];
|
const reshapedFilters = [];
|
||||||
const excludedYMMFilters = filters.filter(
|
const excludedYMMFilters = filters.filter((filter) =>
|
||||||
(filter) => ![MODEL_FILTER_ID, MAKE_FILTER_ID, YEAR_FILTER_ID].includes(filter.id)
|
excludeYMM ? ![MODEL_FILTER_ID, MAKE_FILTER_ID, YEAR_FILTER_ID].includes(filter.id) : true
|
||||||
);
|
);
|
||||||
for (const filter of excludedYMMFilters) {
|
for (const filter of excludedYMMFilters) {
|
||||||
const values = filter.values
|
const values = filter.values
|
||||||
@ -1174,3 +1175,32 @@ export const getFile = async (id: string) => {
|
|||||||
|
|
||||||
return res.body.data.node;
|
return res.body.data.node;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export async function getProductFilters(
|
||||||
|
{ collection, make }: { collection: string; make?: string },
|
||||||
|
filterId: string
|
||||||
|
): Promise<Filter | null | undefined> {
|
||||||
|
const [namespace, metafieldKey] = MAKE_FILTER_ID.split('.').slice(-2);
|
||||||
|
|
||||||
|
const res = await shopifyFetch<ShopifyCollectionProductsOperation>({
|
||||||
|
query: getTransmissionCodesQuery,
|
||||||
|
tags: [TAGS.collections, TAGS.products],
|
||||||
|
variables: {
|
||||||
|
handle: collection,
|
||||||
|
...(make
|
||||||
|
? {
|
||||||
|
filters: [{ productMetafield: { namespace, key: metafieldKey, value: make } }]
|
||||||
|
}
|
||||||
|
: {})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!res.body.data.collection) {
|
||||||
|
console.log(`No collection found for \`${collection}\``);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const filters = res.body.data.collection.products.filters;
|
||||||
|
const selectedFilters = filters.find((filter) => filter.id === filterId);
|
||||||
|
return selectedFilters ? reshapeFilters([selectedFilters], false)[0] : null;
|
||||||
|
}
|
||||||
|
@ -78,3 +78,22 @@ export const getCollectionProductsQuery = /* GraphQL */ `
|
|||||||
}
|
}
|
||||||
${productFragment}
|
${productFragment}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const getTransmissionCodesQuery = /* GraphQL */ `
|
||||||
|
query getTransmissionCodes($handle: String!, $filters: [ProductFilter!]) {
|
||||||
|
collection(handle: $handle) {
|
||||||
|
products(first: 1, filters: $filters) {
|
||||||
|
filters {
|
||||||
|
id
|
||||||
|
label
|
||||||
|
type
|
||||||
|
values {
|
||||||
|
id
|
||||||
|
input
|
||||||
|
label
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user