mirror of
https://github.com/vercel/commerce.git
synced 2025-07-22 20:26:49 +00:00
feat: adding more information warranty, part number, sku, speciall offers
Signed-off-by: Chloe <pinkcloudvnn@gmail.com>
This commit is contained in:
@@ -39,29 +39,33 @@ export function Gallery({ images }: { images: { src: string; altText: string }[]
|
||||
priority={true}
|
||||
/>
|
||||
)}
|
||||
|
||||
{images.length > 1 ? (
|
||||
<div className="absolute bottom-[15%] flex w-full justify-center">
|
||||
<div className="mx-auto flex h-11 items-center rounded-full border border-white bg-neutral-50/80 text-neutral-500 backdrop-blur dark:border-black dark:bg-neutral-900/80">
|
||||
<Link
|
||||
aria-label="Previous product image"
|
||||
href={previousUrl}
|
||||
className={buttonClassName}
|
||||
scroll={false}
|
||||
>
|
||||
<ArrowLeftIcon className="h-5" />
|
||||
</Link>
|
||||
<div className="mx-1 h-6 w-px bg-neutral-500"></div>
|
||||
<Link
|
||||
aria-label="Next product image"
|
||||
href={nextUrl}
|
||||
className={buttonClassName}
|
||||
scroll={false}
|
||||
>
|
||||
<ArrowRightIcon className="h-5" />
|
||||
</Link>
|
||||
<>
|
||||
<div className="absolute bottom-[15%] flex w-full justify-center">
|
||||
<div className="mx-auto mb-3 flex h-11 items-center rounded-full border border-white bg-neutral-50/80 text-neutral-500 backdrop-blur dark:border-black dark:bg-neutral-900/80">
|
||||
<Link
|
||||
aria-label="Previous product image"
|
||||
href={previousUrl}
|
||||
className={buttonClassName}
|
||||
scroll={false}
|
||||
>
|
||||
<ArrowLeftIcon className="h-5" />
|
||||
</Link>
|
||||
<div className="mx-1 h-6 w-px bg-neutral-500"></div>
|
||||
<Link
|
||||
aria-label="Next product image"
|
||||
href={nextUrl}
|
||||
className={buttonClassName}
|
||||
scroll={false}
|
||||
>
|
||||
<ArrowRightIcon className="h-5" />
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p className="absolute bottom-[5%] flex w-full justify-center text-sm text-neutral-500">
|
||||
Representative Image
|
||||
</p>
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
|
@@ -1,10 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import { InformationCircleIcon } from '@heroicons/react/24/outline';
|
||||
import { Checkbox } from 'components/checkbox';
|
||||
import CoreCharge from 'components/core-charge';
|
||||
import Price from 'components/price';
|
||||
import Tooltip from 'components/tooltip';
|
||||
import { Money, ProductVariant } from 'lib/shopify/types';
|
||||
import { useSearchParams } from 'next/navigation';
|
||||
|
||||
@@ -20,32 +17,28 @@ const PriceWithCoreCharge = ({ variants, defaultPrice }: PriceWithCoreChargeProp
|
||||
(option) => option.value === searchParams.get(option.name.toLowerCase())
|
||||
)
|
||||
);
|
||||
console.log({ variant });
|
||||
|
||||
const price = variant?.price.amount || defaultPrice.amount;
|
||||
|
||||
return (
|
||||
<div className="mr-auto flex w-auto flex-row flex-wrap items-center gap-3 text-sm">
|
||||
<Price
|
||||
amount={variant?.price.amount || defaultPrice.amount}
|
||||
currencyCode={variant?.price.currencyCode || defaultPrice.currencyCode}
|
||||
className="text-lg font-semibold"
|
||||
/>
|
||||
<CoreCharge variant={variant} />
|
||||
{variant?.coreCharge?.amount && variant.waiverAvailable ? (
|
||||
<div className="mt-1 flex w-full items-center space-x-3">
|
||||
<Checkbox id="payCoreCharge" />
|
||||
<label htmlFor="payCoreCharge" className="text-md flex items-center gap-1 leading-none">
|
||||
Pay a core charge of
|
||||
<Price
|
||||
amount={variant.coreCharge.amount}
|
||||
currencyCode={variant.coreCharge.currencyCode}
|
||||
/>
|
||||
<span data-tooltip-id="payCoreCharge">
|
||||
<InformationCircleIcon className="ml-1 h-4 w-4 text-gray-500" />
|
||||
</span>
|
||||
</label>
|
||||
<Tooltip id="payCoreCharge">Select this if you do not have a core to return</Tooltip>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<>
|
||||
<div className="mb-4">
|
||||
{variant && (
|
||||
<div className="flex flex-row items-center space-x-3 text-sm text-neutral-700">
|
||||
{variant.sku && <span>SKU: {variant.sku}</span>}
|
||||
{variant.barcode && <span>Part Number: {variant.barcode}</span>}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="mr-auto flex w-auto flex-row flex-wrap items-center gap-3 text-sm">
|
||||
<Price
|
||||
amount={price}
|
||||
currencyCode={variant?.price.currencyCode || defaultPrice.currencyCode}
|
||||
className="text-2xl font-semibold"
|
||||
/>
|
||||
<CoreCharge variant={variant} />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
@@ -3,13 +3,15 @@ import Prose from 'components/prose';
|
||||
import { Product } from 'lib/shopify/types';
|
||||
import { Suspense } from 'react';
|
||||
import PriceWithCoreCharge from './price-with-core-charge';
|
||||
import SpecialOffer from './special-offer';
|
||||
import { VariantSelector } from './variant-selector';
|
||||
import Warranty from './warranty';
|
||||
|
||||
export function ProductDescription({ product }: { product: Product }) {
|
||||
return (
|
||||
<>
|
||||
<div className="mb-6 flex flex-col border-b pb-6 dark:border-neutral-700">
|
||||
<h1 className="mb-3 text-4xl font-bold">{product.title}</h1>
|
||||
<div className="mb-5 flex flex-col dark:border-neutral-700">
|
||||
<h1 className="mb-3 text-2xl font-bold">{product.title}</h1>
|
||||
<PriceWithCoreCharge
|
||||
variants={product.variants}
|
||||
defaultPrice={product.priceRange.minVariantPrice}
|
||||
@@ -21,14 +23,21 @@ export function ProductDescription({ product }: { product: Product }) {
|
||||
|
||||
{product.descriptionHtml ? (
|
||||
<Prose
|
||||
className="mb-6 text-sm leading-tight dark:text-white/[60%]"
|
||||
className="mb-4 text-sm leading-tight dark:text-white/[60%]"
|
||||
html={product.descriptionHtml}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
<div className="mb-4 border-t py-6 dark:border-neutral-700">
|
||||
<Warranty productType={product.productType} />
|
||||
</div>
|
||||
|
||||
<Suspense fallback={null}>
|
||||
<AddToCart variants={product.variants} availableForSale={product.availableForSale} />
|
||||
</Suspense>
|
||||
<div className="mt-4 border-t pt-4">
|
||||
<SpecialOffer />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
27
components/product/special-offer.tsx
Normal file
27
components/product/special-offer.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import { CurrencyDollarIcon, ShieldCheckIcon, UsersIcon } from '@heroicons/react/24/outline';
|
||||
import { TruckIcon } from '@heroicons/react/24/solid';
|
||||
|
||||
const SpecialOffer = () => {
|
||||
return (
|
||||
<>
|
||||
<div className="mb-3 text-base font-medium tracking-tight">Special Offers</div>
|
||||
<div className="flex flex-col space-y-2 pl-2 tracking-normal text-neutral-800">
|
||||
<p className="flex items-center gap-3">
|
||||
<TruckIcon className="h-5 w-5 text-secondary" /> Flat Rate Shipping (Commercial Address)
|
||||
</p>
|
||||
<p className="flex items-center gap-3">
|
||||
<ShieldCheckIcon className="h-5 w-5 text-secondary" /> Up to 5 Years Unlimited Miles
|
||||
Warranty
|
||||
</p>
|
||||
<p className="flex items-center gap-3">
|
||||
<UsersIcon className="h-5 w-5 text-secondary" /> Excellent Customer Support
|
||||
</p>
|
||||
<p className="flex items-center gap-3">
|
||||
<CurrencyDollarIcon className="h-5 w-5 text-secondary" /> No Core Charge for 30 days
|
||||
</p>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default SpecialOffer;
|
@@ -39,8 +39,8 @@ export function VariantSelector({
|
||||
}));
|
||||
|
||||
return options.map((option) => (
|
||||
<dl className="mb-8" key={option.id}>
|
||||
<dt className="mb-4 text-sm uppercase tracking-wide">{option.name}</dt>
|
||||
<dl className="mb-6" key={option.id}>
|
||||
<dt className="mb-4 text-sm font-medium tracking-wide">{option.name}</dt>
|
||||
<dd className="flex flex-wrap gap-3">
|
||||
{option.values.map((value) => {
|
||||
const optionNameLowerCase = option.name.toLowerCase();
|
||||
|
57
components/product/warranty-selector.tsx
Normal file
57
components/product/warranty-selector.tsx
Normal file
@@ -0,0 +1,57 @@
|
||||
'use client';
|
||||
|
||||
import Price from 'components/price';
|
||||
import { cn } from 'lib/utils';
|
||||
import { ReactNode, useState } from 'react';
|
||||
|
||||
const options = ['Included', 'Premium Labor', '+1 Year'] as const;
|
||||
type Option = (typeof options)[number];
|
||||
|
||||
const plans: Array<{
|
||||
key: Option;
|
||||
template: ReactNode;
|
||||
price: number;
|
||||
}> = [
|
||||
{
|
||||
template: (
|
||||
<>
|
||||
<span>Included</span>
|
||||
<span>3-Year Warranty</span>
|
||||
</>
|
||||
),
|
||||
price: 0,
|
||||
key: 'Included'
|
||||
},
|
||||
{
|
||||
template: <span>Premium Labor</span>,
|
||||
price: 150,
|
||||
key: 'Premium Labor'
|
||||
},
|
||||
{
|
||||
template: <span>+1 Year</span>,
|
||||
price: 100,
|
||||
key: '+1 Year'
|
||||
}
|
||||
];
|
||||
const WarrantySelector = () => {
|
||||
const [selectedOptions, setSelectedOptions] = useState<Option>('Included');
|
||||
return (
|
||||
<ul className="flex min-h-16 flex-row space-x-4 pt-2">
|
||||
{plans.map((plan) => (
|
||||
<li
|
||||
key={plan.key}
|
||||
onClick={() => setSelectedOptions(plan.key)}
|
||||
className={cn(
|
||||
'flex w-32 cursor-pointer flex-col items-center justify-center space-y-2 rounded-md border p-2 text-xs font-medium',
|
||||
{ 'ring-2 ring-secondary': plan.key === selectedOptions }
|
||||
)}
|
||||
>
|
||||
{plan.template}
|
||||
<Price amount={String(plan.price)} currencyCode="USD" />
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
};
|
||||
|
||||
export default WarrantySelector;
|
30
components/product/warranty.tsx
Normal file
30
components/product/warranty.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import { ShieldCheckIcon } from '@heroicons/react/24/outline';
|
||||
import Link from 'next/link';
|
||||
import WarrantySelector from './warranty-selector';
|
||||
|
||||
type WarrantyProps = {
|
||||
productType: string | null;
|
||||
};
|
||||
|
||||
const Warranty = ({ productType }: WarrantyProps) => {
|
||||
return (
|
||||
<div className="flex flex-col 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 {productType ?? 'product'}</span>
|
||||
</div>
|
||||
<div className="mb-1 flex flex-row items-center space-x-3 divide-x divide-gray-400 leading-none">
|
||||
<span>Extended Warranty</span>
|
||||
<Link href="#" className="pl-2 text-blue-800 hover:underline">
|
||||
What's Included
|
||||
</Link>
|
||||
<Link href="#" className="pl-2 text-blue-800 hover:underline">
|
||||
Terms & Conditions
|
||||
</Link>
|
||||
</div>
|
||||
<WarrantySelector />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Warranty;
|
Reference in New Issue
Block a user