From d38a20cbaf78e2132292f4cfa5eacf5c48862c95 Mon Sep 17 00:00:00 2001 From: Chloe Date: Fri, 14 Jun 2024 14:25:27 +0700 Subject: [PATCH 1/7] feat: add helpful links Signed-off-by: Chloe --- app/search/[collection]/page.tsx | 2 + components/layout/search/helpful-links.tsx | 44 ++++++++++++++++++++++ lib/shopify/index.ts | 4 +- lib/shopify/queries/collection.ts | 3 ++ lib/shopify/types.ts | 4 +- 5 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 components/layout/search/helpful-links.tsx diff --git a/app/search/[collection]/page.tsx b/app/search/[collection]/page.tsx index f0504c129..1a10b32ef 100644 --- a/app/search/[collection]/page.tsx +++ b/app/search/[collection]/page.tsx @@ -14,6 +14,7 @@ import FiltersContainer, { import MobileFilters from 'components/layout/search/filters/mobile-filters'; import SubMenu from 'components/layout/search/filters/sub-menu'; import Header, { HeaderPlaceholder } from 'components/layout/search/header'; +import HelpfulLinks from 'components/layout/search/helpful-links'; import ProductsGridPlaceholder from 'components/layout/search/placeholder'; import SortingMenu from 'components/layout/search/sorting-menu'; import { Suspense } from 'react'; @@ -92,6 +93,7 @@ export default async function CategorySearchPage(props: {

Filters

} key={`filters-${props.params.collection}`}> +
diff --git a/components/layout/search/helpful-links.tsx b/components/layout/search/helpful-links.tsx new file mode 100644 index 000000000..546fa2d15 --- /dev/null +++ b/components/layout/search/helpful-links.tsx @@ -0,0 +1,44 @@ +import { getCollection, getMetaobjectsByIds } from 'lib/shopify'; +import Link from 'next/link'; + +const LinkItem = async ({ + collectionLinkId, + anchorText +}: { + collectionLinkId: string; + anchorText: string; +}) => { + const collection = await getCollection({ id: collectionLinkId }); + + if (!collection) return null; + + return ( + + {anchorText} + + ); +}; +const HelpfulLinks = async ({ collection }: { collection: string }) => { + const collectionData = await getCollection({ handle: collection }); + if (!collectionData || !collectionData.helpfulLinks) return null; + + const helpfulLinks = await getMetaobjectsByIds(collectionData.helpfulLinks); + + return ( +
+
Helpful links
+ +
+ {helpfulLinks.map((link) => ( + + ))} +
+
+ ); +}; + +export default HelpfulLinks; diff --git a/lib/shopify/index.ts b/lib/shopify/index.ts index 1ffaae701..a44007ba9 100644 --- a/lib/shopify/index.ts +++ b/lib/shopify/index.ts @@ -173,6 +173,7 @@ const reshapeCollection = (collection: ShopifyCollection): Collection | undefine return { ...collection, + helpfulLinks: parseMetaFieldValue(collection.helpfulLinks), path: `/search/${collection.handle}` }; }; @@ -495,7 +496,8 @@ export async function getCollections(): Promise { description: 'All products' }, path: '/search', - updatedAt: new Date().toISOString() + updatedAt: new Date().toISOString(), + helpfulLinks: null }, // Filter out the `hidden` collections. // Collections that start with `hidden-*` need to be hidden on the search page. diff --git a/lib/shopify/queries/collection.ts b/lib/shopify/queries/collection.ts index 57596d005..f59aec8f1 100644 --- a/lib/shopify/queries/collection.ts +++ b/lib/shopify/queries/collection.ts @@ -9,6 +9,9 @@ const collectionFragment = /* GraphQL */ ` seo { ...seo } + helpfulLinks: metafield(namespace: "custom", key: "helpful_links") { + value + } updatedAt } ${seoFragment} diff --git a/lib/shopify/types.ts b/lib/shopify/types.ts index 7443823c4..e8b86f15e 100644 --- a/lib/shopify/types.ts +++ b/lib/shopify/types.ts @@ -31,8 +31,9 @@ export type CartItem = { coreCharge?: CartItem; }; -export type Collection = ShopifyCollection & { +export type Collection = Omit & { path: string; + helpfulLinks: string[] | null; }; export type Image = { @@ -186,6 +187,7 @@ export type ShopifyCollection = { description: string; seo: SEO; updatedAt: string; + helpfulLinks: { value: string } | null; }; export type ShopifyProduct = { From a27b02f953085f45b1dc61281c71523cef5bccd3 Mon Sep 17 00:00:00 2001 From: Chloe Date: Fri, 14 Jun 2024 15:08:26 +0700 Subject: [PATCH 2/7] fix: implement price setup Signed-off-by: Chloe --- components/grid/tile.tsx | 62 ++++++++++++++++++++++++++++++++++------ lib/constants.ts | 5 ++++ 2 files changed, 59 insertions(+), 8 deletions(-) diff --git a/components/grid/tile.tsx b/components/grid/tile.tsx index a8abf656f..7a53e6730 100644 --- a/components/grid/tile.tsx +++ b/components/grid/tile.tsx @@ -1,10 +1,52 @@ import { ArrowRightIcon, PhotoIcon } from '@heroicons/react/24/solid'; import clsx from 'clsx'; import Price from 'components/price'; -import { Product } from 'lib/shopify/types'; +import { CONDITIONS } from 'lib/constants'; +import { Product, ProductVariant } from 'lib/shopify/types'; import Image from 'next/image'; import Link from 'next/link'; +const PriceSection = ({ variants }: { variants: ProductVariant[] }) => { + const usedVariants = variants.filter((variant) => variant.condition === CONDITIONS.Used); + + const minUsedVariantPrice = usedVariants.length + ? usedVariants.reduce( + (min, variant) => Math.min(min, Number(variant.price.amount)), + Number(usedVariants[0]?.price.amount) + ) + : null; + + const remanVariants = variants.filter( + (variant) => variant.condition === CONDITIONS.Remanufactured + ); + + const minRemanufacturedPrice = remanVariants.length + ? remanVariants.reduce( + (min, variant) => Math.min(min, Number(variant.price.amount)), + Number(remanVariants[0]?.price.amount) + ) + : null; + + const currencyCode = variants[0]?.price.currencyCode || 'USD'; + + return ( +
+ {typeof minUsedVariantPrice === 'number' && ( +
+ {CONDITIONS.Used} + +
+ )} + {typeof minRemanufacturedPrice === 'number' && ( +
+ {CONDITIONS.Remanufactured} + +
+ )} +
+ ); +}; + export function GridTileImage({ active, product, @@ -17,7 +59,7 @@ export function GridTileImage({ } & React.ComponentProps) { const metafieldKeys = ['engineCylinders', 'fuelType'] as Partial[]; const shouldShowDescription = metafieldKeys.some((key) => product[key]); - + const variantsWithCondition = product.variants.filter((variant) => variant.condition !== null); return (
@@ -76,12 +118,16 @@ export function GridTileImage({ ) : null}
)} -
- +
+ {variantsWithCondition.length ? ( + + ) : ( + + )}
diff --git a/lib/constants.ts b/lib/constants.ts index 33fd75c8c..81e896f7f 100644 --- a/lib/constants.ts +++ b/lib/constants.ts @@ -46,3 +46,8 @@ export const MODEL_FILTER_ID = 'filter.p.m.custom.make_model_composite'; export const YEAR_FILTER_ID = 'filter.p.m.custom.make_model_year_composite'; export const PRODUCT_METAFIELD_PREFIX = 'filter.p.m'; export const VARIANT_METAFIELD_PREFIX = 'filter.v.m'; + +export const CONDITIONS = { + Used: 'Used', + Remanufactured: 'Remanufactured' +}; From c5af5b3069c582b11d61a07b1becfcb81fb0cd10 Mon Sep 17 00:00:00 2001 From: Chloe Date: Fri, 14 Jun 2024 16:50:46 +0700 Subject: [PATCH 3/7] feat: add faqs section Signed-off-by: Chloe --- app/globals.css | 13 ++++++ app/layout.tsx | 2 +- app/page.tsx | 2 +- app/search/[collection]/page.tsx | 67 ++++++++++++++++------------- app/search/layout.tsx | 5 ++- components/faq.tsx | 7 +-- components/home-page/about.tsx | 4 +- components/home-page/why-choose.tsx | 3 +- 8 files changed, 59 insertions(+), 44 deletions(-) diff --git a/app/globals.css b/app/globals.css index 21bbc4a01..dd16d793c 100644 --- a/app/globals.css +++ b/app/globals.css @@ -13,3 +13,16 @@ input, button { @apply focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-neutral-400 focus-visible:ring-offset-2 focus-visible:ring-offset-neutral-50 dark:focus-visible:ring-neutral-600 dark:focus-visible:ring-offset-neutral-900; } + +@layer utilities { + /* Hide scrollbar for WebKit browsers */ + .hide-scrollbar::-webkit-scrollbar { + display: none; + } + + /* Hide scrollbar for Firefox */ + .hide-scrollbar { + -ms-overflow-style: none; /* Internet Explorer 10+ */ + scrollbar-width: none; /* Firefox */ + } +} diff --git a/app/layout.tsx b/app/layout.tsx index a91f6da81..df9c59c6f 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -35,7 +35,7 @@ export const metadata = { export default async function RootLayout({ children }: { children: ReactNode }) { return ( - +
diff --git a/app/page.tsx b/app/page.tsx index 453a47708..a71d8df8f 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -34,7 +34,7 @@ export default async function HomePage() { - + diff --git a/app/search/[collection]/page.tsx b/app/search/[collection]/page.tsx index 1a10b32ef..573ba682b 100644 --- a/app/search/[collection]/page.tsx +++ b/app/search/[collection]/page.tsx @@ -59,7 +59,7 @@ async function CategoryPage({ } />
- + {products.length === 0 ? (

{`No products found in this collection`}

) : ( @@ -81,38 +81,43 @@ export default async function CategorySearchPage(props: { searchParams?: { [key: string]: string | string[] | undefined }; }) { return ( -
- -
-
- } key={`breadcrumb-${props.params.collection}`}> - - -
- } key={`header-${props.params.collection}`}> -
- - - } - key={`products-${props.params.collection}`} - > - -
-
+ ); } diff --git a/app/search/layout.tsx b/app/search/layout.tsx index a3ebd12a5..f99d3a361 100644 --- a/app/search/layout.tsx +++ b/app/search/layout.tsx @@ -1,12 +1,15 @@ +import FAQ from 'components/faq'; import Footer from 'components/layout/footer'; import { Suspense } from 'react'; export default function SearchLayout({ children }: { children: React.ReactNode }) { return ( <> -
+
{children}
+ +