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