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">
<BreadcrumbComponent type="product" handle={product.handle} />
</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">
<Suspense
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

View File

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

View File

@ -1,9 +1,11 @@
'use client';
import { ArrowPathRoundedSquareIcon } from '@heroicons/react/24/outline';
import Price from 'components/price';
import { CORE_VARIANT_ID_KEY, CORE_WAIVER } from 'lib/constants';
import { CoreChargeOption, ProductVariant } from 'lib/shopify/types';
import { cn, createUrl } from 'lib/utils';
import Link from 'next/link';
import { usePathname, useRouter, useSearchParams } from 'next/navigation';
type CoreChargeProps = {
@ -34,13 +36,13 @@ const CoreCharge = ({ variants }: CoreChargeProps) => {
const coreChargeOptions = [
waiverAvailable && {
label: 'Core Waiver',
label: 'Waive Core',
value: CORE_WAIVER,
price: { amount: 0, currencyCode: variant?.price.currencyCode }
},
coreVariantId &&
coreCharge && {
label: 'Core Charge',
label: 'Pay Core Upfront',
value: coreVariantId,
price: coreCharge
}
@ -52,25 +54,35 @@ const CoreCharge = ({ variants }: CoreChargeProps) => {
return (
<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">
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
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">
{coreChargeOptions.map((option) => (
<li className="flex w-32" key={option.value}>
<button
onClick={() => handleSelectCoreChargeOption(option.value)}
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} />
</button>
</li>

View File

@ -28,7 +28,7 @@ export function Gallery({ images }: { images: { src: string; altText: string }[]
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] && (
<Image
className="h-full w-full object-contain"
@ -70,7 +70,7 @@ export function Gallery({ images }: { images: { src: string; altText: string }[]
</div>
{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) => {
const isActive = index === imageIndex;
const imageSearchParams = new URLSearchParams(searchParams.toString());
@ -78,7 +78,7 @@ export function Gallery({ images }: { images: { src: string; altText: string }[]
imageSearchParams.set('image', index.toString());
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
aria-label="Enlarge product image"
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 (
<>
<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
variants={product.variants}
defaultPrice={product.priceRange.minVariantPrice}
@ -33,11 +37,11 @@ export function ProductDescription({ product }: { product: Product }) {
/>
) : 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} />
</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 />
</div>

View File

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

View File

@ -13,22 +13,17 @@ const plans: Array<{
price: number;
}> = [
{
template: (
<>
<span>Included</span>
<span>3-Year Warranty</span>
</>
),
template: <span className="font-bold">3-Year Warranty</span>,
price: 0,
key: 'Included'
},
{
template: <span>Premium Labor</span>,
template: <span className="font-bold">Premium Labor</span>,
price: 150,
key: 'Premium Labor'
},
{
template: <span>+1 Year</span>,
template: <span className="font-bold">+1 Year</span>,
price: 100,
key: '+1 Year'
}
@ -42,9 +37,9 @@ const WarrantySelector = () => {
<button
onClick={() => setSelectedOptions(plan.key)}
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,19 +5,22 @@ import WarrantySelector from './warranty-selector';
const Warranty = () => {
return (
<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">
<ShieldCheckIcon className="h-7 w-7" />
<span> Protect your product</span>
<div className="mb-3 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">
<ShieldCheckIcon className="h-5 w-5" />
<span>Warranty</span>
</div>
<div className="mb-1 flex flex-row items-center space-x-1 divide-x divide-gray-400 leading-none lg:space-x-3">
<span>Extended Warranty</span>
<Link href="#" className="pl-2 text-blue-800 hover:underline">
<div className="pl-2">
<Link href="#" className="text-xs text-blue-800 hover:underline lg:text-sm">
What&apos;s Included
</Link>
<Link href="#" className="pl-2 text-blue-800 hover:underline">
</div>
<div className="pl-2">
<Link href="#" className="text-xs text-blue-800 hover:underline lg:text-sm">
Terms & Conditions
</Link>
</div>
</div>
<WarrantySelector />
</div>
);