feat: playing around with styles and layout on PDP

Signed-off-by: jrphilo <james.philo@me.com>
This commit is contained in:
jrphilo 2024-05-12 13:39:14 +02:00
parent 80db45a522
commit 598c1ad53e
No known key found for this signature in database
GPG Key ID: A8BAD7933F97046F
9 changed files with 98 additions and 51 deletions

View File

@ -86,11 +86,11 @@ export default async function ProductPage({ params }: { params: { handle: string
<div className="hidden lg:block"> <div className="hidden lg:block">
<BreadcrumbComponent type="product" handle={product.handle} /> <BreadcrumbComponent type="product" handle={product.handle} />
</div> </div>
<div className="my-3 flex flex-col space-x-0 rounded-lg border border-neutral-200 bg-white p-8 md:p-10 lg:flex-row lg:gap-8 lg:space-x-3 dark:border-neutral-800 dark:bg-black"> <div className="my-3 flex flex-col space-x-0 rounded-lg border border-neutral-200 bg-white p-6 dark:border-neutral-800 dark:bg-black md:p-10 lg:flex-row lg:gap-8 lg:space-x-3">
<div className="h-full w-full basis-full lg:basis-7/12"> <div className="h-full w-full basis-full lg:basis-7/12">
<Suspense <Suspense
fallback={ fallback={
<div className="relative aspect-square h-full max-h-[550px] w-full overflow-hidden" /> <div className="aspect-square relative h-full max-h-[550px] w-full overflow-hidden" />
} }
> >
<Gallery <Gallery

View File

@ -12,17 +12,28 @@ const Price = ({
currencyCode: string; currencyCode: string;
currencyCodeClassName?: string; currencyCodeClassName?: string;
showCurrency?: boolean; showCurrency?: boolean;
} & React.ComponentProps<'p'>) => ( } & React.ComponentProps<'p'>) => {
<p suppressHydrationWarning={true} className={className}> // Convert string to float and check if it is zero
{`${new Intl.NumberFormat(undefined, { const price = parseFloat(amount);
style: 'currency',
currency: currencyCode, // Return 'Included' if price is 0
currencyDisplay: 'narrowSymbol' if (price === 0) {
}).format(parseFloat(amount))}`} return <p className={className}>Included</p>;
{showCurrency && ( }
<span className={clsx('ml-1 inline', currencyCodeClassName)}>{`${currencyCode}`}</span>
)} // Otherwise, format and display the price
</p> return (
); <p suppressHydrationWarning={true} className={className}>
{new Intl.NumberFormat(undefined, {
style: 'currency',
currency: currencyCode,
currencyDisplay: 'narrowSymbol'
}).format(price)}
{showCurrency && (
<span className={clsx('ml-1 inline', currencyCodeClassName)}>{currencyCode}</span>
)}
</p>
);
};
export default Price; export default Price;

View File

@ -1,9 +1,11 @@
'use client'; 'use client';
import { ArrowPathRoundedSquareIcon } from '@heroicons/react/24/outline';
import Price from 'components/price'; import Price from 'components/price';
import { CORE_VARIANT_ID_KEY, CORE_WAIVER } from 'lib/constants'; import { CORE_VARIANT_ID_KEY, CORE_WAIVER } from 'lib/constants';
import { CoreChargeOption, ProductVariant } from 'lib/shopify/types'; import { CoreChargeOption, ProductVariant } from 'lib/shopify/types';
import { cn, createUrl } from 'lib/utils'; import { cn, createUrl } from 'lib/utils';
import Link from 'next/link';
import { usePathname, useRouter, useSearchParams } from 'next/navigation'; import { usePathname, useRouter, useSearchParams } from 'next/navigation';
type CoreChargeProps = { type CoreChargeProps = {
@ -34,13 +36,13 @@ const CoreCharge = ({ variants }: CoreChargeProps) => {
const coreChargeOptions = [ const coreChargeOptions = [
waiverAvailable && { waiverAvailable && {
label: 'Core Waiver', label: 'Waive Core',
value: CORE_WAIVER, value: CORE_WAIVER,
price: { amount: 0, currencyCode: variant?.price.currencyCode } price: { amount: 0, currencyCode: variant?.price.currencyCode }
}, },
coreVariantId && coreVariantId &&
coreCharge && { coreCharge && {
label: 'Core Charge', label: 'Pay Core Upfront',
value: coreVariantId, value: coreVariantId,
price: coreCharge price: coreCharge
} }
@ -52,25 +54,35 @@ const CoreCharge = ({ variants }: CoreChargeProps) => {
return ( return (
<div className="flex flex-col text-xs lg:text-sm"> <div className="flex flex-col text-xs lg:text-sm">
<div className="mb-2 text-base font-medium">Core Charge</div> <div className="mb-1 flex flex-row items-center space-x-1 divide-x divide-gray-400 leading-none lg:space-x-3">
<div className="flex flex-row items-center space-x-2 text-base font-medium">
<ArrowPathRoundedSquareIcon className="h-5 w-5" />
<span> Core charge </span>
</div>
<Link href="#" className="pl-2 text-blue-800 hover:underline">
Understanding Core Charges and Returns
</Link>
</div>
{/*
Plan is to move this to within the a modal tht opens when a user clicks on Understanding Core Charges and Returns
<p className="mb-2 text-sm tracking-tight text-neutral-500"> <p className="mb-2 text-sm tracking-tight text-neutral-500">
The core charge is a refundable deposit that is added to the price of the part. This charge The core charge is a refundable deposit that is added to the price of the part. This charge
ensures that the old, worn-out part is returned to the supplier for proper disposal or ensures that the old, worn-out part is returned to the supplier for proper disposal or
recycling. When you return the old part, you&apos;ll receive a refund of the core charge. recycling. When you return the old part, you&apos;ll receive a refund of the core charge.
</p> </p> */}
<ul className="flex min-h-16 flex-row space-x-4 pt-2"> <ul className="flex min-h-16 flex-row space-x-4 pt-2">
{coreChargeOptions.map((option) => ( {coreChargeOptions.map((option) => (
<li className="flex w-32" key={option.value}> <li className="flex w-32" key={option.value}>
<button <button
onClick={() => handleSelectCoreChargeOption(option.value)} onClick={() => handleSelectCoreChargeOption(option.value)}
className={cn( className={cn(
'flex w-full flex-col flex-wrap items-center justify-center space-y-2 rounded-md border p-2 text-center text-xs font-medium', 'font-base flex w-full flex-col flex-wrap items-center justify-center space-y-0.5 rounded border text-center text-xs',
{ {
'ring-2 ring-secondary': coreVariantIdSearchParam === option.value 'border-0 ring-2 ring-secondary': coreVariantIdSearchParam === option.value
} }
)} )}
> >
<span>{option.label}</span> <span className="font-bold">{option.label}</span>
<Price {...option.price} /> <Price {...option.price} />
</button> </button>
</li> </li>

View File

@ -28,7 +28,7 @@ export function Gallery({ images }: { images: { src: string; altText: string }[]
return ( return (
<> <>
<div className="relative aspect-1 h-full max-h-[550px] w-full overflow-hidden"> <div className="relative hidden aspect-1 h-full max-h-[550px] w-full overflow-hidden md:block">
{images[imageIndex] && ( {images[imageIndex] && (
<Image <Image
className="h-full w-full object-contain" className="h-full w-full object-contain"
@ -70,7 +70,7 @@ export function Gallery({ images }: { images: { src: string; altText: string }[]
</div> </div>
{images.length > 1 ? ( {images.length > 1 ? (
<ul className="my-12 flex items-center justify-center gap-2 overflow-auto py-1 lg:mb-0"> <ul className="mb-4 flex gap-2 overflow-auto py-1 sm:justify-start md:my-12 md:items-center md:justify-center lg:mb-0">
{images.map((image, index) => { {images.map((image, index) => {
const isActive = index === imageIndex; const isActive = index === imageIndex;
const imageSearchParams = new URLSearchParams(searchParams.toString()); const imageSearchParams = new URLSearchParams(searchParams.toString());
@ -78,7 +78,7 @@ export function Gallery({ images }: { images: { src: string; altText: string }[]
imageSearchParams.set('image', index.toString()); imageSearchParams.set('image', index.toString());
return ( return (
<li key={image.src} className="h-20 w-20"> <li key={image.src} className="h-16 w-16 md:h-20 md:w-20">
<Link <Link
aria-label="Enlarge product image" aria-label="Enlarge product image"
href={createUrl(pathname, imageSearchParams)} href={createUrl(pathname, imageSearchParams)}

View File

@ -0,0 +1,22 @@
const PriceInCart = ({ items }) => {
const totalPrice = items.reduce((sum, item) => sum + item.price, 0);
return (
<div className="mt-4 border-t border-neutral-300 p-4">
<ul>
{items.map((item, index) => (
<li key={index} className="flex justify-between">
<span>{item.label}</span>
<span>${item.price.toFixed(2)}</span>
</li>
))}
<li className="mt-2 flex justify-between border-t border-neutral-300 pt-2 font-bold">
<span>Total In Cart</span>
<span>${totalPrice.toFixed(2)}</span>
</li>
</ul>
</div>
);
};
export default PriceInCart;

View File

@ -12,7 +12,11 @@ export function ProductDescription({ product }: { product: Product }) {
return ( return (
<> <>
<div className="mb-5 flex flex-col dark:border-neutral-700"> <div className="mb-5 flex flex-col dark:border-neutral-700">
<h1 className="mb-3 text-2xl font-bold">{product.title}</h1> <h1 className="text-xl font-bold md:text-2xl">{product.title}</h1>
<div className="mb-5 flex items-center justify-start gap-x-2">
<p className="text-sm">SKU: 123456</p>
<p className="text-sm">Condition: Used</p>
</div>
<VariantPrice <VariantPrice
variants={product.variants} variants={product.variants}
defaultPrice={product.priceRange.minVariantPrice} defaultPrice={product.priceRange.minVariantPrice}
@ -33,11 +37,11 @@ export function ProductDescription({ product }: { product: Product }) {
/> />
) : null} ) : null}
<div className="mb-4 border-t pb-4 pt-6 dark:border-neutral-700"> <div className="mb-2 border-t py-4 dark:border-neutral-700">
<CoreCharge variants={product.variants} /> <CoreCharge variants={product.variants} />
</div> </div>
<div className="mb-4 border-t py-6 dark:border-neutral-700"> <div className="mb-2 border-t py-4 dark:border-neutral-700">
<Warranty /> <Warranty />
</div> </div>

View File

@ -75,8 +75,8 @@ export function VariantSelector({
const closeModal = () => setIsOpen(false); const closeModal = () => setIsOpen(false);
return ( return (
<div className="mb-6 flex flex-row gap-1 text-sm font-medium"> <div className="mb-6 flex flex-row gap-1 rounded-md border p-2 text-sm font-medium">
More Remanufactured and Used Options{' '} See more Remanufactured and Used Options{' '}
<button <button
className="flex flex-row gap-0.5 font-normal text-blue-800 hover:underline" className="flex flex-row gap-0.5 font-normal text-blue-800 hover:underline"
aria-label="Open variants selector" aria-label="Open variants selector"

View File

@ -13,22 +13,17 @@ const plans: Array<{
price: number; price: number;
}> = [ }> = [
{ {
template: ( template: <span className="font-bold">3-Year Warranty</span>,
<>
<span>Included</span>
<span>3-Year Warranty</span>
</>
),
price: 0, price: 0,
key: 'Included' key: 'Included'
}, },
{ {
template: <span>Premium Labor</span>, template: <span className="font-bold">Premium Labor</span>,
price: 150, price: 150,
key: 'Premium Labor' key: 'Premium Labor'
}, },
{ {
template: <span>+1 Year</span>, template: <span className="font-bold">+1 Year</span>,
price: 100, price: 100,
key: '+1 Year' key: '+1 Year'
} }
@ -42,9 +37,9 @@ const WarrantySelector = () => {
<button <button
onClick={() => setSelectedOptions(plan.key)} onClick={() => setSelectedOptions(plan.key)}
className={cn( className={cn(
'flex w-full flex-col flex-wrap items-center justify-center space-y-2 rounded-md border p-2 text-center text-xs font-medium', 'font-base flex w-full flex-col flex-wrap items-center justify-center space-y-0.5 rounded border text-center text-xs',
{ {
'ring-2 ring-secondary': plan.key === selectedOptions 'border-0 ring-2 ring-secondary': plan.key === selectedOptions
} }
)} )}
> >

View File

@ -5,18 +5,21 @@ import WarrantySelector from './warranty-selector';
const Warranty = () => { const Warranty = () => {
return ( return (
<div className="flex flex-col text-xs lg:text-sm"> <div className="flex flex-col text-xs lg:text-sm">
<div className="mb-3 flex flex-row items-center space-x-2 text-base font-medium"> <div className="mb-3 flex flex-row items-center space-x-1 divide-x divide-gray-400 leading-none lg:space-x-3">
<ShieldCheckIcon className="h-7 w-7" /> <div className="flex flex-row items-center space-x-2 text-base font-medium">
<span> Protect your product</span> <ShieldCheckIcon className="h-5 w-5" />
</div> <span>Warranty</span>
<div className="mb-1 flex flex-row items-center space-x-1 divide-x divide-gray-400 leading-none lg:space-x-3"> </div>
<span>Extended Warranty</span> <div className="pl-2">
<Link href="#" className="pl-2 text-blue-800 hover:underline"> <Link href="#" className="text-xs text-blue-800 hover:underline lg:text-sm">
What&apos;s Included What&apos;s Included
</Link> </Link>
<Link href="#" className="pl-2 text-blue-800 hover:underline"> </div>
Terms & Conditions <div className="pl-2">
</Link> <Link href="#" className="text-xs text-blue-800 hover:underline lg:text-sm">
Terms & Conditions
</Link>
</div>
</div> </div>
<WarrantySelector /> <WarrantySelector />
</div> </div>