add PDP content

Signed-off-by: Chloe <pinkcloudvnn@gmail.com>
This commit is contained in:
Chloe 2024-07-07 11:09:24 +07:00
parent fab2a5e967
commit cc2c79764d
No known key found for this signature in database
GPG Key ID: CFD53CE570D42DF5
15 changed files with 76 additions and 270 deletions

View File

@ -5,6 +5,7 @@ import { Suspense } from 'react';
import BreadcrumbComponent from 'components/breadcrumb';
import { GridTileImage } from 'components/grid/tile';
import Footer from 'components/layout/footer';
import AdditionalInformation from 'components/product/additional-information';
import { Gallery } from 'components/product/gallery';
import { ProductDescription } from 'components/product/product-description';
import { HIDDEN_PRODUCT_TAG } from 'lib/constants';
@ -49,7 +50,13 @@ export async function generateMetadata({
};
}
export default async function ProductPage({ params }: { params: { handle: string } }) {
export default async function ProductPage({
params,
searchParams
}: {
params: { handle: string };
searchParams?: { [key: string]: string | string[] | undefined };
}) {
const product = await getProduct(params.handle);
if (!product) return notFound();
@ -86,6 +93,9 @@ export default async function ProductPage({ params }: { params: { handle: string
<div className="my-3 flex flex-col space-x-0 lg:flex-row lg:gap-8 lg:space-x-3">
<div className="h-full w-full basis-full lg:basis-7/12">
<ProductDescription product={product} />
<Suspense>
<AdditionalInformation product={product} searchParams={searchParams} />
</Suspense>
</div>
<div className="hidden lg:block lg:basis-5/12">
@ -95,7 +105,7 @@ export default async function ProductPage({ params }: { params: { handle: string
}
>
<Gallery
images={product.images.map((image: Image) => ({
images={product.images.slice(0, 5).map((image: Image) => ({
src: image.url,
altText: image.altText
}))}

View File

@ -1,7 +1,7 @@
'use client';
import Price from 'components/price';
import { useDebounce } from 'hooks';
import { useDebounce } from 'hooks/use-debounce';
import { Filter } from 'lib/shopify/types';
import { createUrl } from 'lib/utils';
import get from 'lodash.get';

View File

@ -40,7 +40,7 @@ const RichTextBlock = ({ block }: { block: Content }) => {
}
return (
<p className="text-blue-200">
<p className="text-black-700">
{block.children.map((child, index) => (
<RichTextBlock key={index} block={child} />
))}
@ -50,7 +50,7 @@ const RichTextBlock = ({ block }: { block: Content }) => {
const RichTextDisplay = ({ contentBlocks }: { contentBlocks: Content[] }) => {
return (
<div className="flex w-full flex-col gap-2">
<div className="flex w-full flex-col gap-4">
{contentBlocks.map((block, index) => (
<RichTextBlock key={index} block={block} />
))}

View File

@ -8,7 +8,7 @@ const TextBlock = ({ block }: { block: Metaobject }) => {
<div className="flex flex-col gap-8">
<div className="flex flex-col gap-5 px-4 md:px-0">
{block.title && (
<h3 className="text-xl font-semibold leading-6 text-gray-900">{block.title}</h3>
<h3 className="text-xl font-bold leading-6 text-black-700">{block.title}</h3>
)}
<RichTextDisplay contentBlocks={content.children} />

View File

@ -1,14 +1,38 @@
import PageContent from 'components/page/page-content';
import { getMetaobject, getMetaobjectsByIds } from 'lib/shopify';
import { Product } from 'lib/shopify/types';
import Details from './details';
import ShippingPolicy from './shipping-policy';
import WarrantyPolicy from './warranty-policy';
import { getSelectedProductVariant } from 'lib/utils';
const AdditionalInformation = async ({
product,
searchParams
}: {
product: Product;
searchParams?: { [key: string]: string | string[] | undefined };
}) => {
const selectedVariant = getSelectedProductVariant({ product, searchParams });
if (!selectedVariant) return null;
const pdpContent = await getMetaobject({
handle: {
handle: `${selectedVariant.condition}-${product.productType}`.toLowerCase(),
type: 'pdp_content'
}
});
if (!pdpContent) return null;
const contentIds = pdpContent.content ? JSON.parse(pdpContent.content) : [];
const pageContent = await getMetaobjectsByIds(contentIds);
const AdditionalInformation = ({ product }: { product: Product }) => {
return (
<div className="my-5 w-full divide-y">
<Details product={product} />
<WarrantyPolicy />
<ShippingPolicy />
<div className="my-6 w-full divide-y">
{pageContent.map((block) => (
<div key={block.id} className="py-5">
<PageContent block={block} />
</div>
))}
</div>
);
};

View File

@ -1,82 +0,0 @@
'use client';
import clsx from 'clsx';
import Price from 'components/price';
import { Product } from 'lib/shopify/types';
import { useSearchParams } from 'next/navigation';
import DisclosureSection from './disclosure-section';
const Details = ({ product }: { product: Product }) => {
const searchParams = useSearchParams();
const variants = product.variants;
const variant = variants.find((variant) =>
variant.selectedOptions.every(
(option) => option.value === searchParams.get(option.name.toLowerCase())
)
);
const details = [
...(product.transmissionTag
? [
{
title: 'Transmission Tag',
value: product.transmissionTag.join()
}
]
: []),
...(product.transmissionCode
? [
{
title: 'Transmission Code',
value: product.transmissionCode.join()
}
]
: []),
...(product.transmissionSpeeds
? [
{
title: 'Transmission Speeds',
value: product.transmissionSpeeds.map((speed) => `${speed}-Speed`).join()
}
]
: [])
];
return (
<DisclosureSection title="Product Details" defaultOpen>
<div className="flex w-full items-center p-1">
<span className="basis-2/5">Condition</span>
<span>{variant?.condition || 'N/A'}</span>
</div>
<div className="flex w-full items-center bg-gray-100 p-1">
<span className="basis-2/5">Price</span>
<Price
amount={variant?.price.amount || product.priceRange.minVariantPrice.amount}
currencyCode={
variant?.price.currencyCode || product.priceRange.minVariantPrice.currencyCode
}
/>
</div>
<div className="flex w-full items-center p-1">
<span className="basis-2/5">Warranty</span>
<span />
</div>
<div className="flex w-full items-center bg-gray-100 p-1">
<span className="basis-2/5">Cylinders</span>
<span>{product.engineCylinders?.map((cylinder) => `${cylinder} Cylinders`).join()}</span>
</div>
{details.map(({ title, value }, index) => (
<div
key={index}
className={clsx('flex w-full items-center p-1', { 'bg-gray-100': index % 2 !== 0 })}
>
<span className="basis-2/5">{title}</span>
<span>{value}</span>
</div>
))}
</DisclosureSection>
);
};
export default Details;

View File

@ -1,25 +0,0 @@
'use client';
import { Disclosure, DisclosureButton, DisclosurePanel } from '@headlessui/react';
import { ChevronDownIcon } from '@heroicons/react/24/outline';
import { ReactNode } from 'react';
type DisclosureProps = {
children: ReactNode;
defaultOpen?: boolean;
title: string;
};
const DisclosureSection = ({ children, title, defaultOpen }: DisclosureProps) => {
return (
<Disclosure as="div" className="p-3" defaultOpen={defaultOpen}>
<DisclosureButton className="group flex w-full items-center justify-between">
<span className="font-medium">{title}</span>
<ChevronDownIcon className="size-4 group-data-[open]:rotate-180" />
</DisclosureButton>
<DisclosurePanel className="mt-2 py-2 text-sm">{children}</DisclosurePanel>
</Disclosure>
);
};
export default DisclosureSection;

View File

@ -2,7 +2,6 @@ import { AddToCart } from 'components/cart/add-to-cart';
import Prose from 'components/prose';
import { Product } from 'lib/shopify/types';
import { Suspense } from 'react';
import AdditionalInformation from './additional-information';
import CoreCharge from './core-charge';
import Delivery from './delivery';
import PriceSummary from './price-summary';
@ -56,7 +55,6 @@ export function ProductDescription({ product }: { product: Product }) {
<AddToCart variants={product.variants} availableForSale={product.availableForSale} />
</Suspense>
<SpecialOffer />
<AdditionalInformation product={product} />
</>
);
}

View File

@ -1,44 +0,0 @@
import DisclosureSection from './disclosure-section';
const { SITE_NAME } = process.env;
const ShippingPolicy = () => {
return (
<DisclosureSection title="Shipping & returns">
<p>
At {SITE_NAME}, we offer a Flat Rate Shipping (Commercial address) service as long as the
delivery address is in a commercially zoned location. Unfortunately, residential and home
businesses are not considered commercial addresses. A business or commercial address
location must be able to receive freight without the requirement of prior appointment setup
or notification. This location should also have the capability of unloading the
remanufactured transmission with a forklift from the delivery truck. If you don&apos;t have
a commercial or business address that meets these specifications, you should ship it
directly to the dealership or repair shop that is performing the repairs to ensure you enjoy
Flat Rate Shipping (Commercial address). Residential delivery or Liftgate service will
result in additional $99 fee.
</p>
<p className="my-3">
After placing the order for a remanufactured transmission, most customers will receive it
within 7-14 business days not including holidays or weekends. Please keep in mind that
certain locations (remote areas) and locations in Colorado, Utah, New York, Oregon, and
California may require an additional delivery fee. In either case, we will always ship your
remanufactured transmission out as soon as possible. Because of weather conditions,
increasing order volumes, and conditions outside of our control, all shipping times are
estimates, not guarantees. It&apos;s important to note that {SITE_NAME} will not be liable
for any extra fees the carrier may levy due to storage or redelivery. While every
transmission from {SITE_NAME} has been rigorously inspected and tested prior to being
shipped, damage may occur during transportation.
</p>
<p>
As such, we strongly suggest you carefully inspect your transmission upon receipt. If you
notice any missing parts, wrong parts, or damage, you should report it prior to signing any
delivery documentation. It&quot;s imperative to report missing parts, damage, or wrong parts
at the time of delivery. If you fail to do so prior to signing your shipping documents,
responsibility will be placed on the purchaser or receiver. For clarity,
&quot;purchaser&quot; refers to any representative of the company designated to sign for the
delivery of the remanufactured transmission.
</p>
</DisclosureSection>
);
};
export default ShippingPolicy;

View File

@ -1,102 +0,0 @@
import {
ArrowPathIcon,
ArrowsRightLeftIcon,
CurrencyDollarIcon,
FlagIcon
} from '@heroicons/react/24/outline';
import DisclosureSection from './disclosure-section';
const { SITE_NAME } = process.env;
const WarrantyPolicy = () => {
return (
<DisclosureSection title="Warranty">
<div className="mb-3 font-medium">Year 2001 and Newer</div>
<div className="flex items-center p-1">
<span className="basis-1/2">Personal/Individual Transmission Warranty</span>
<span>60 Months/ Unlimited Mileage</span>
</div>
<div className="flex items-center bg-gray-100 p-1">
<span className="basis-1/2">Commercial Transmissions Warranty</span>
<span>Prior to 03/01/2020 18 Months/ 100,000 Miles</span>
</div>
<div className="flex items-center p-1">
<span className="basis-1/2">Commercial Transmissions Warranty</span>
<span>Effective 03/01/2020 36 Months/ Unlimited Mileage</span>
</div>
<div className="flex items-center bg-gray-100 p-1">
<span className="basis-1/2">Continuously Variable Transmission (CVT) Warranty</span>
<span>36 Months/ Unlimited Mileage</span>
</div>
<div className="flex items-center p-1">
<span className="basis-1/2">Manual Transmission Warranty</span>
<span>36 Months/ Unlimited Miles</span>
</div>
<div className="my-3 font-medium">Year 2000 and Older</div>
<div className="flex items-center p-1">
<span className="basis-1/2">Personal/Individual Transmission Warranty</span>
<span>36 Months/ Unlimited Mileage</span>
</div>
<div className="flex items-center bg-gray-100 p-1">
<span className="basis-1/2">Commercial Transmissions Warranty</span>
<span>18 Months/ 100,000 Miles</span>
</div>
<div className="flex items-center p-1">
<span className="basis-1/2">Commercial Transmissions Warranty</span>
<span>36 Months/ Unlimited Mileage</span>
</div>
<div className="flex items-center bg-gray-100 p-1">
<span className="basis-1/2">Continuously Variable Transmission (CVT) Warranty</span>
<span>36 Months/ Unlimited Miles</span>
</div>
<div className="my-5">
<div className="mb-1 flex items-center gap-2 font-medium">
<ArrowsRightLeftIcon className="size-4 text-primary" />
Easy, Hassle-Free, Transferable Warranty
</div>
<p>
At {SITE_NAME}, we offer an easy, transferable, hassle-free warranty. Instead of being
associated only with you, the warranty is attached to your Vehicle Identification Number.
As such, the warranty is transferable with vehicle ownership, which means you never have
to worry about any paperwork or fees involved. Please note, that the used parts warranty
is not transferable.
</p>
</div>
<div className="my-5">
<div className="mb-1 flex items-center gap-2 font-medium">
<FlagIcon className="size-4 text-primary" />
Nationwide Coverage
</div>
<p>
Whether you&apos;re in California, Chicago, New York, Florida, or anywhere in between, you
are covered with a nationwide warranty. This warranty covers you anywhere in the
continental U.S.
</p>
</div>
<div className="my-5">
<div className="mb-1 flex items-center gap-2 font-medium">
<ArrowPathIcon className="size-4 text-primary" />
Instant Replacement
</div>
<p>
With instant replacement, your replacement transmission will be sent out as soon as you
submit your claim. This way you can spend less time waiting and more time doing whatever
needs to be done.
</p>
</div>
<div className="my-5">
<div className="mb-1 flex items-center gap-2 font-medium">
<CurrencyDollarIcon className="size-4 text-primary" />
Paid Parts & Labor
</div>
<p>
When you have your work performed in a certified shop, your {SITE_NAME} warranty will pay
for parts and labor at $50 an hour, which is the Mitchell labor reimbursement rate.
</p>
</div>
</DisclosureSection>
);
};
export default WarrantyPolicy;

View File

@ -9,6 +9,7 @@ const productFragment = /* GraphQL */ `
title
description
descriptionHtml
productType
options {
id
name

View File

@ -522,6 +522,7 @@ export type ShopifyProduct = {
title: string;
description: string;
descriptionHtml: string;
productType: string;
options: ProductOption[];
priceRange: {
maxVariantPrice: Money;

View File

@ -18,6 +18,9 @@ export const carPartPlanetColor = {
200: '#666C89',
500: '#2D3A7B',
600: '#111C55'
},
black: {
700: '#1A1A25'
}
};
@ -41,6 +44,9 @@ export const remanTransmissionColor = {
200: '#666C89',
500: '#2D3A7B',
600: '#111C55'
},
black: {
700: '#1A1A25'
}
};
@ -64,5 +70,8 @@ export const transmissionLocatorColor = {
200: '#666C89',
500: '#2D3A7B',
600: '#111C55'
},
black: {
700: '#1A1A25'
}
};

View File

@ -1,7 +1,7 @@
import clsx, { ClassValue } from 'clsx';
import { ReadonlyURLSearchParams } from 'next/navigation';
import { twMerge } from 'tailwind-merge';
import { Menu } from './shopify/types';
import { Menu, Product, ProductVariant } from './shopify/types';
export function cx(...args: ClassValue[]) {
return twMerge(clsx(...args));
@ -149,3 +149,19 @@ export const getCollectionUrl = (handle: string, includeSlashPrefix = true) => {
return includeSlashPrefix ? `/${rewriteUrl}` : rewriteUrl;
};
export const getSelectedProductVariant = ({
product,
searchParams
}: {
product: Product;
searchParams?: { [key: string]: string | string[] | undefined };
}) => {
const variant = product.variants.find((variant: ProductVariant) =>
variant.selectedOptions.every(
(option) => option.value === searchParams?.[option.name.toLowerCase()]
)
);
return variant || product.variants[0];
};