From 8f82f6299eb1cb7a832ea18f5aed8300c141bd5a Mon Sep 17 00:00:00 2001 From: Chloe <pinkcloudvnn@gmail.com> Date: Wed, 12 Jun 2024 19:14:57 +0700 Subject: [PATCH] feat: add more details to product tile Signed-off-by: Chloe <pinkcloudvnn@gmail.com> --- app/product/[handle]/page.tsx | 6 +- components/grid/three-items.tsx | 6 +- components/grid/tile.tsx | 85 ++++++++++++++-------- components/layout/products-list/index.tsx | 6 +- lib/shopify/fragments/product.ts | 6 ++ lib/shopify/index.ts | 3 +- lib/shopify/types.ts | 11 ++- lib/utils.ts | 2 +- public/icons/cylinder.png | Bin 0 -> 509 bytes public/icons/fuel.png | Bin 0 -> 584 bytes 10 files changed, 77 insertions(+), 48 deletions(-) create mode 100644 public/icons/cylinder.png create mode 100644 public/icons/fuel.png diff --git a/app/product/[handle]/page.tsx b/app/product/[handle]/page.tsx index 94a8d74f0..8a9ef69b5 100644 --- a/app/product/[handle]/page.tsx +++ b/app/product/[handle]/page.tsx @@ -132,11 +132,7 @@ async function RelatedProducts({ id }: { id: string }) { > <GridTileImage alt={product.title} - label={{ - title: product.title, - amount: product.priceRange.maxVariantPrice.amount, - currencyCode: product.priceRange.maxVariantPrice.currencyCode - }} + product={product} src={product.featuredImage?.url} fill sizes="(min-width: 1024px) 20vw, (min-width: 768px) 25vw, (min-width: 640px) 33vw, (min-width: 475px) 50vw, 100vw" diff --git a/components/grid/three-items.tsx b/components/grid/three-items.tsx index bfdd46f72..a36a051f6 100644 --- a/components/grid/three-items.tsx +++ b/components/grid/three-items.tsx @@ -23,11 +23,7 @@ function ThreeItemGridItem({ } priority={priority} alt={item.title} - label={{ - title: item.title as string, - amount: item.priceRange.maxVariantPrice.amount, - currencyCode: item.priceRange.maxVariantPrice.currencyCode - }} + product={item} href={`/product/${item.handle}`} /> </div> diff --git a/components/grid/tile.tsx b/components/grid/tile.tsx index 08d5ff34e..a8abf656f 100644 --- a/components/grid/tile.tsx +++ b/components/grid/tile.tsx @@ -1,25 +1,23 @@ import { ArrowRightIcon, PhotoIcon } from '@heroicons/react/24/solid'; import clsx from 'clsx'; import Price from 'components/price'; +import { Product } from 'lib/shopify/types'; import Image from 'next/image'; import Link from 'next/link'; export function GridTileImage({ active, - label, + product, href, - place = 'grid', ...props }: { active?: boolean; - label?: { - title: string; - amount: string; - currencyCode: string; - }; - place?: 'grid' | 'gallery'; + product: Product; href: string; } & React.ComponentProps<typeof Image>) { + const metafieldKeys = ['engineCylinders', 'fuelType'] as Partial<keyof Product>[]; + const shouldShowDescription = metafieldKeys.some((key) => product[key]); + return ( <div className="flex h-full flex-col rounded-b border bg-white"> <div className="grow"> @@ -43,30 +41,57 @@ export function GridTileImage({ )} </div> </div> - <div className="flex flex-col gap-2 divide-y px-4"> - {label && ( - <h3 className="mt-4 text-sm font-semibold leading-6 text-gray-800">{label.title}</h3> - )} - {label && ( - <div className="flex w-full justify-end py-2"> - <Price - className="text-lg font-medium text-gray-900" - amount={label.amount} - currencyCode={label.currencyCode} - /> - </div> - )} + <h3 className="mt-4 px-4 pb-2 text-sm font-semibold leading-6 text-gray-800"> + {product.title} + </h3> + </div> + <div className="px-4"> + {shouldShowDescription && ( + <div className="flex items-center justify-center gap-x-7 border-t py-3"> + {product.engineCylinders?.length ? ( + <div className="flex flex-col items-center gap-2"> + <Image + src="/icons/cylinder.png" + alt="Cylinder icon" + width={16} + height={16} + className="size-4" + sizes="16px" + /> + <span className="text-xs tracking-wide">{`${product.engineCylinders[0]} Cylinder`}</span> + </div> + ) : null} + {product.fuelType ? ( + <div className="flex flex-col items-center gap-2"> + <Image + src="/icons/fuel.png" + alt="Fuel icon" + width={16} + height={16} + className="size-4" + sizes="16px" + /> + <span className="text-xs tracking-wide">{product.fuelType}</span> + </div> + ) : null} + </div> + )} + <div className="flex justify-end border-t py-2"> + <Price + className="text-lg font-medium text-gray-900" + amount={product.priceRange.minVariantPrice.amount} + currencyCode={product.priceRange.minVariantPrice.currencyCode} + /> </div> </div> - {place === 'grid' && ( - <Link - href={href} - className="flex items-center justify-center gap-3 rounded-b bg-dark py-3 text-white" - > - <span className="text-sm font-medium tracking-wide">More details</span> - <ArrowRightIcon className="size-4" /> - </Link> - )} + + <Link + href={href} + className="flex items-center justify-center gap-3 rounded-b bg-dark py-3 text-white" + > + <span className="text-sm font-medium tracking-wide">More details</span> + <ArrowRightIcon className="size-4" /> + </Link> </div> ); } diff --git a/components/layout/products-list/index.tsx b/components/layout/products-list/index.tsx index b17d2ce13..1eb6fae6a 100644 --- a/components/layout/products-list/index.tsx +++ b/components/layout/products-list/index.tsx @@ -70,11 +70,7 @@ const ProductsList = ({ > <GridTileImage alt={product.title} - label={{ - title: product.title, - amount: product.priceRange.maxVariantPrice.amount, - currencyCode: product.priceRange.maxVariantPrice.currencyCode - }} + product={product} src={product.featuredImage?.url} fill sizes="(min-width: 768px) 33vw, (min-width: 640px) 50vw, 100vw" diff --git a/lib/shopify/fragments/product.ts b/lib/shopify/fragments/product.ts index d098b39c0..5f146e2f5 100644 --- a/lib/shopify/fragments/product.ts +++ b/lib/shopify/fragments/product.ts @@ -70,6 +70,12 @@ const productFragment = /* GraphQL */ ` featuredImage { ...image } + engineCylinders: metafield(namespace: "custom", key: "engine_cylinders") { + value + } + fuelType: metafield(namespace: "custom", key: "fuel") { + value + } images(first: 20) { edges { node { diff --git a/lib/shopify/index.ts b/lib/shopify/index.ts index c143d664f..1ffaae701 100644 --- a/lib/shopify/index.ts +++ b/lib/shopify/index.ts @@ -294,6 +294,8 @@ const reshapeProduct = (product: ShopifyProduct, filterHiddenProducts: boolean = const { images, variants, ...rest } = product; return { ...rest, + engineCylinders: parseMetaFieldValue<number[]>(product.engineCylinders), + fuelType: product.fuelType?.value || null, images: reshapeImages(images, product.title), variants: reshapeVariants(removeEdgesAndNodes(variants)) }; @@ -305,7 +307,6 @@ const reshapeProducts = (products: ShopifyProduct[]) => { for (const product of products) { if (product) { const reshapedProduct = reshapeProduct(product); - if (reshapedProduct) { reshapedProducts.push(reshapedProduct); } diff --git a/lib/shopify/types.ts b/lib/shopify/types.ts index d64376ba9..7443823c4 100644 --- a/lib/shopify/types.ts +++ b/lib/shopify/types.ts @@ -100,9 +100,14 @@ export type Metaobject = { [key: string]: string; }; -export type Product = Omit<ShopifyProduct, 'variants' | 'images'> & { +export type Product = Omit< + ShopifyProduct, + 'variants' | 'images' | 'fuelType' | 'engineCylinders' +> & { variants: ProductVariant[]; images: Image[]; + fuelType: string | null; + engineCylinders: number[] | null; }; export type ProductOption = { @@ -128,6 +133,8 @@ export type ProductVariant = { mileage: number | null; estimatedDelivery: string | null; condition: string | null; + engineCylinders: string | null; + fuelType: string | null; }; export type ShopifyCartProductVariant = { @@ -205,6 +212,8 @@ export type ShopifyProduct = { handle: string; }[]; }; + engineCylinders: { value: string } | null; + fuelType: { value: string } | null; }; export type ShopifyCartOperation = { diff --git a/lib/utils.ts b/lib/utils.ts index 8d800e3f6..de885097e 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -51,7 +51,7 @@ export function normalizeUrl(domain: string, url: string) { export const parseMetaFieldValue = <T>(field: { value: string } | null): T | null => { try { - return JSON.parse(field?.value || '{}'); + return field?.value ? JSON.parse(field.value) : null; } catch (error) { return null; } diff --git a/public/icons/cylinder.png b/public/icons/cylinder.png new file mode 100644 index 0000000000000000000000000000000000000000..0e7b1a279af5f54229103962749064e33afa88a4 GIT binary patch literal 509 zcmV<Z0RsMsP)<h;3K|Lk000e1NJLTq000*N000;W1^@s65qxHK00009a7bBm000XU z000XU0RWnu7ytkO0drDELIAGL9O(c600d`2O+f$vv5yP<VFdsH0g_2XK~#7F&6PVW z1Ys0~Ppm{E@$9x4k!a~eglr;u5uuVrO;9OFY^StVAwoekG%C?3P^m;gp(4meqVQT0 ziGp+U-^mP_9rG%D$(Nm(ojLc;oqu;yh)z8nqHpvO3DT^AAM}fkA|dSHBU(jsT3`Y( zv4b!0v*R3M-a#w9qdR2+?fuYq@H-eu6|gi|E}GN*AO%JckXgD<ul2%Jn)WG>r}qd+ zir#3D6tX-DjL|K6i4aKeHZ9U#dPUD@g-3xB?La_k=$;0*X~*`6W?bhLV1BXP2#CAm z7FX~&-GCXBu%N+R2uMmhe)0&er5WvBW?Tx=v8CF<#WKM{1DqNA2KUnkJz0FE?>@mc z%lKmy^BTBBduTfyhZ)DN2FuE5L`W?93A2o3SxxD-9M5RcNWnsuzUX#AyS;_^=MxU; zIwDX>o8Y`j<e{w5W0;3o2eZ`Dh{!N~hIvraFk_HAInnRsGmnT!sElG!_xoT*RZS<g zSSMnT)sRg(KnLlH9?KyC<P~-RXPuvF(f`ReTwZ-nu&+6x00000NkvXXu0mjf>_XM4 literal 0 HcmV?d00001 diff --git a/public/icons/fuel.png b/public/icons/fuel.png new file mode 100644 index 0000000000000000000000000000000000000000..14c9e56188cd646c618a6907829e0918c84788d1 GIT binary patch literal 584 zcmV-O0=NB%P)<h;3K|Lk000e1NJLTq000;O000;W1^@s6;CDUv00009a7bBm000XU z000XU0RWnu7ytkO0drDELIAGL9O(c600d`2O+f$vv5yP<VFdsH0o_SNK~#7Fy_L-? z1yLBrUtDBCH!LLODoaJmLQ$8k1y+&;!pg$e-p5~HVM#t?qm&KB!d+nFYe)IWlqd=} zl!Y6f(|K;jZO+`8+&uN0_sp4h&U4P0bIzoRvx1-rWQx09z%j7IhX%ki`0~M&sj;+# z`))7`KENEfVgn`>gBB113!p2R!8MZ`!3kfe6ldLmX_j~RUIU02@}-u&-2k~XOExuX zDyzUC?~gz~D2X>3uo+}C-h*cLXd6s|cZUvz!5ABlu&$c)f*KGDh_lKWSHPwr$D%>0 ztxk=;n4_vw#_5Kaql~8cS1M%uOfX$%IixNogH@WS;Xi=~2M(CKBrY-;+=4dID=B8k zvA__R7Z(Lwo^6r1f{an|p>&UVt>9Ah)QpP{c`@h%YoI{%*`!v^jCfQsgAf>Hxf7I$ zp8For`{2(8VbQyC9xSr)Zay246d2SqF4*dJ87m|O1{sV&TU`$0o20;CnQ_2YH_Uh_ z9u!FOTpf1%qQ8YYT`iW?UYHOM^0G`jz@F$86&AhA8^q&p(msOt4h<&cSfWoxb;4$W zQ-^l)8P#{Wv9}lhqp6c|Resj?nfyK_-J~{O5y%4<pdGw|b9<8N^`rRv(VOU!|I|0| WNrH>}EaNZ$0000<MNUMnLSTaMqy3Tq literal 0 HcmV?d00001