mirror of
https://github.com/vercel/commerce.git
synced 2025-05-12 12:47:50 +00:00
fix: create side dialog for content inside PDP
Signed-off-by: Chloe <pinkcloudvnn@gmail.com>
This commit is contained in:
parent
7c60e4e7f4
commit
4684d54ac3
@ -86,11 +86,11 @@ export default function CategorySearchPage(props: {
|
||||
return (
|
||||
<>
|
||||
<div className="mb-2">
|
||||
<Suspense fallback={<BreadcrumbHome />}>
|
||||
<Suspense fallback={<BreadcrumbHome />} key={props.params.collection}>
|
||||
<Breadcrumb type="collection" handle={props.params.collection} />
|
||||
</Suspense>
|
||||
</div>
|
||||
<Suspense fallback={<HeaderPlaceholder />}>
|
||||
<Suspense fallback={<HeaderPlaceholder />} key={props.params.collection}>
|
||||
<Header collection={props.params.collection} />
|
||||
</Suspense>
|
||||
<div className="my-3">
|
||||
@ -98,7 +98,7 @@ export default function CategorySearchPage(props: {
|
||||
<YMMFilters />
|
||||
</Suspense>
|
||||
</div>
|
||||
<Suspense fallback={<ProductsGridPlaceholder />}>
|
||||
<Suspense fallback={<ProductsGridPlaceholder />} key={props.params.collection}>
|
||||
<CategoryPage {...props} />
|
||||
</Suspense>
|
||||
</>
|
||||
|
@ -5,9 +5,9 @@ import FiltersList from './filters-list';
|
||||
const YMMFiltersContainer = ({ children }: { children: ReactNode }) => {
|
||||
return (
|
||||
<div className="rounded border bg-white px-6 pb-5 pt-4">
|
||||
<h5 className="mb-3 text-xl font-semibold leading-tight tracking-tight text-neutral-700">
|
||||
<p className="mb-3 text-xl font-semibold leading-tight tracking-tight text-neutral-700">
|
||||
Find Your Car Part
|
||||
</h5>
|
||||
</p>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
|
@ -47,7 +47,10 @@ const Filters = ({ filters, defaultOpen = true }: { filters: Filter[]; defaultOp
|
||||
>
|
||||
<DisclosureButton className="group flex items-center justify-between">
|
||||
<div className="text-sm font-medium text-gray-900">{label}</div>
|
||||
<ChevronDownIcon className="size-4 group-data-[open]:rotate-180" />
|
||||
<ChevronDownIcon
|
||||
aria-label="Toggle opening filters block"
|
||||
className="size-4 group-data-[open]:rotate-180"
|
||||
/>
|
||||
</DisclosureButton>
|
||||
<DisclosurePanel className="flex-grow space-y-3 overflow-auto pb-1 pl-1 pt-2">
|
||||
{type === FilterType.PRICE_RANGE ? (
|
||||
|
@ -2,11 +2,12 @@
|
||||
|
||||
import { ArrowPathRoundedSquareIcon } from '@heroicons/react/24/outline';
|
||||
import Price from 'components/price';
|
||||
import SideDialog from 'components/side-dialog';
|
||||
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';
|
||||
import { useState } from 'react';
|
||||
|
||||
type CoreChargeProps = {
|
||||
variants: ProductVariant[];
|
||||
@ -16,6 +17,7 @@ const CoreCharge = ({ variants }: CoreChargeProps) => {
|
||||
const pathname = usePathname();
|
||||
const router = useRouter();
|
||||
|
||||
const [isOpenDialog, setIsOpenDialog] = useState(false);
|
||||
const optionSearchParams = new URLSearchParams(searchParams);
|
||||
const coreVariantIdSearchParam = optionSearchParams.get(CORE_VARIANT_ID_KEY);
|
||||
|
||||
@ -52,6 +54,9 @@ const CoreCharge = ({ variants }: CoreChargeProps) => {
|
||||
handleSelectCoreChargeOption((coreChargeOptions[0] as CoreChargeOption).value);
|
||||
}
|
||||
|
||||
const openDialog = () => setIsOpenDialog(true);
|
||||
const closeDialog = () => setIsOpenDialog(false);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col text-xs lg:text-sm">
|
||||
<div className="mb-1 flex flex-row items-center space-x-1 divide-x divide-gray-400 leading-none lg:space-x-3">
|
||||
@ -59,17 +64,21 @@ const CoreCharge = ({ variants }: CoreChargeProps) => {
|
||||
<ArrowPathRoundedSquareIcon className="h-5 w-5" />
|
||||
<span> Core charge </span>
|
||||
</div>
|
||||
<Link href="#" className="pl-2 text-blue-800 hover:underline">
|
||||
<button className="pl-2 text-blue-800 hover:underline" onClick={openDialog}>
|
||||
Understanding Core Charges and Returns
|
||||
</Link>
|
||||
</button>
|
||||
|
||||
<SideDialog title="Core Charges and Returns" onClose={closeDialog} open={isOpenDialog}>
|
||||
<div className="mt-5 flex h-full flex-col overflow-hidden">
|
||||
<p className="text-sm tracking-tight">
|
||||
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'll receive a refund of
|
||||
the core charge.
|
||||
</p>
|
||||
</div>
|
||||
</SideDialog>
|
||||
</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'll receive a refund of the core charge.
|
||||
</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}>
|
||||
|
@ -4,7 +4,7 @@ import { Product } from 'lib/shopify/types';
|
||||
import { Suspense } from 'react';
|
||||
import CoreCharge from './core-charge';
|
||||
import SpecialOffer from './special-offer';
|
||||
import VariantPrice from './vairant-price';
|
||||
import VariantDetails from './vairant-details';
|
||||
import { VariantSelector } from './variant-selector';
|
||||
import Warranty from './warranty';
|
||||
|
||||
@ -13,11 +13,8 @@ export function ProductDescription({ product }: { product: Product }) {
|
||||
<>
|
||||
<div className="mb-5 flex flex-col dark:border-neutral-700">
|
||||
<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
|
||||
|
||||
<VariantDetails
|
||||
variants={product.variants}
|
||||
defaultPrice={product.priceRange.minVariantPrice}
|
||||
/>
|
||||
|
37
components/product/vairant-details.tsx
Normal file
37
components/product/vairant-details.tsx
Normal file
@ -0,0 +1,37 @@
|
||||
'use client';
|
||||
|
||||
import Price from 'components/price';
|
||||
import { Money, ProductVariant } from 'lib/shopify/types';
|
||||
import { useSearchParams } from 'next/navigation';
|
||||
|
||||
type VariantDetailsProps = {
|
||||
variants: ProductVariant[];
|
||||
defaultPrice: Money;
|
||||
};
|
||||
|
||||
const VariantDetails = ({ variants, defaultPrice }: VariantDetailsProps) => {
|
||||
const searchParams = useSearchParams();
|
||||
const variant = variants.find((variant: ProductVariant) =>
|
||||
variant.selectedOptions.every(
|
||||
(option) => option.value === searchParams.get(option.name.toLowerCase())
|
||||
)
|
||||
);
|
||||
|
||||
const price = variant?.price.amount || defaultPrice.amount;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="mb-5 flex items-center justify-start gap-x-2">
|
||||
<p className="text-sm">SKU: {variant?.sku || 'N/A'}</p>
|
||||
<p className="text-sm">Condition: {variant?.condition || 'N/A'}</p>
|
||||
</div>
|
||||
<Price
|
||||
amount={price}
|
||||
currencyCode={variant?.price.currencyCode || defaultPrice.currencyCode}
|
||||
className="text-2xl font-semibold"
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default VariantDetails;
|
@ -1,31 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import Price from 'components/price';
|
||||
import { Money, ProductVariant } from 'lib/shopify/types';
|
||||
import { useSearchParams } from 'next/navigation';
|
||||
|
||||
type PriceWithCoreChargeProps = {
|
||||
variants: ProductVariant[];
|
||||
defaultPrice: Money;
|
||||
};
|
||||
|
||||
const VariantPrice = ({ variants, defaultPrice }: PriceWithCoreChargeProps) => {
|
||||
const searchParams = useSearchParams();
|
||||
const variant = variants.find((variant: ProductVariant) =>
|
||||
variant.selectedOptions.every(
|
||||
(option) => option.value === searchParams.get(option.name.toLowerCase())
|
||||
)
|
||||
);
|
||||
|
||||
const price = variant?.price.amount || defaultPrice.amount;
|
||||
|
||||
return (
|
||||
<Price
|
||||
amount={price}
|
||||
currencyCode={variant?.price.currencyCode || defaultPrice.currencyCode}
|
||||
className="text-2xl font-semibold"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default VariantPrice;
|
@ -1,8 +1,13 @@
|
||||
'use client';
|
||||
|
||||
import { ShieldCheckIcon } from '@heroicons/react/24/outline';
|
||||
import Link from 'next/link';
|
||||
import SideDialog from 'components/side-dialog';
|
||||
import { useState } from 'react';
|
||||
import WarrantySelector from './warranty-selector';
|
||||
|
||||
const Warranty = () => {
|
||||
const [openingDialog, setOpeningDialog] = useState<'included' | 'terms-conditions' | null>(null);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col text-xs lg:text-sm">
|
||||
<div className="mb-3 flex flex-row items-center space-x-1 divide-x divide-gray-400 leading-none lg:space-x-3">
|
||||
@ -11,14 +16,34 @@ const Warranty = () => {
|
||||
<span>Warranty</span>
|
||||
</div>
|
||||
<div className="pl-2">
|
||||
<Link href="#" className="text-xs text-blue-800 hover:underline lg:text-sm">
|
||||
<button
|
||||
onClick={() => setOpeningDialog('included')}
|
||||
className="text-xs text-blue-800 hover:underline lg:text-sm"
|
||||
>
|
||||
What's Included
|
||||
</Link>
|
||||
</button>
|
||||
<SideDialog
|
||||
title="What's Included"
|
||||
onClose={() => setOpeningDialog(null)}
|
||||
open={openingDialog === 'included'}
|
||||
>
|
||||
<p>Warranty Included</p>
|
||||
</SideDialog>
|
||||
</div>
|
||||
<div className="pl-2">
|
||||
<Link href="#" className="text-xs text-blue-800 hover:underline lg:text-sm">
|
||||
<button
|
||||
onClick={() => setOpeningDialog('terms-conditions')}
|
||||
className="text-xs text-blue-800 hover:underline lg:text-sm"
|
||||
>
|
||||
Terms & Conditions
|
||||
</Link>
|
||||
</button>
|
||||
<SideDialog
|
||||
title="Terms & Conditions"
|
||||
onClose={() => setOpeningDialog(null)}
|
||||
open={openingDialog === 'terms-conditions'}
|
||||
>
|
||||
<p>Terms & Conditions</p>
|
||||
</SideDialog>
|
||||
</div>
|
||||
</div>
|
||||
<WarrantySelector />
|
||||
|
@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { Popover, Transition } from '@headlessui/react';
|
||||
import { Popover, PopoverButton, PopoverPanel, Transition } from '@headlessui/react';
|
||||
import { ArrowRightIcon } from '@heroicons/react/16/solid';
|
||||
import { Menu } from 'lib/shopify/types';
|
||||
import { Fragment } from 'react';
|
||||
@ -13,9 +13,9 @@ type ProfilePopoverProps = {
|
||||
const ProfilePopover = ({ menu }: ProfilePopoverProps) => {
|
||||
return (
|
||||
<Popover className="relative">
|
||||
<Popover.Button className="flex">
|
||||
<PopoverButton aria-label="Open Profile Menu" className="flex">
|
||||
<OpenProfile />
|
||||
</Popover.Button>
|
||||
</PopoverButton>
|
||||
<Transition
|
||||
as={Fragment}
|
||||
enter="transition ease-out duration-200"
|
||||
@ -25,7 +25,7 @@ const ProfilePopover = ({ menu }: ProfilePopoverProps) => {
|
||||
leaveFrom="opacity-100 translate-y-0"
|
||||
leaveTo="opacity-0 translate-y-1"
|
||||
>
|
||||
<Popover.Panel className="absolute -right-10 z-10 mt-2 w-72 max-w-lg px-4 sm:px-0 lg:right-0">
|
||||
<PopoverPanel className="absolute -right-10 z-10 mt-2 w-72 max-w-lg px-4 sm:px-0 lg:right-0">
|
||||
<div className="flex flex-col gap-2 overflow-hidden rounded-md bg-white px-4 py-3 text-black shadow-xl ring-1 ring-black/5">
|
||||
<span className="text-sm font-medium">My Account</span>
|
||||
<a
|
||||
@ -49,7 +49,7 @@ const ProfilePopover = ({ menu }: ProfilePopoverProps) => {
|
||||
</ul>
|
||||
) : null}
|
||||
</div>
|
||||
</Popover.Panel>
|
||||
</PopoverPanel>
|
||||
</Transition>
|
||||
</Popover>
|
||||
);
|
||||
|
53
components/side-dialog.tsx
Normal file
53
components/side-dialog.tsx
Normal file
@ -0,0 +1,53 @@
|
||||
'use client';
|
||||
import { Dialog, DialogPanel, Transition, TransitionChild } from '@headlessui/react';
|
||||
import { XMarkIcon } from '@heroicons/react/24/outline';
|
||||
import { Fragment } from 'react';
|
||||
|
||||
type SideDialogProps = {
|
||||
title: string;
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
const SideDialog = ({ title, children, open, onClose }: SideDialogProps) => {
|
||||
return (
|
||||
<Transition show={open} as={Fragment}>
|
||||
<Dialog onClose={onClose} className="relative z-50">
|
||||
<TransitionChild
|
||||
as={Fragment}
|
||||
enter="transition-all ease-in-out duration-300"
|
||||
enterFrom="opacity-0 backdrop-blur-none"
|
||||
enterTo="opacity-100 backdrop-blur-[.5px]"
|
||||
leave="transition-all ease-in-out duration-200"
|
||||
leaveFrom="opacity-100 backdrop-blur-[.5px]"
|
||||
leaveTo="opacity-0 backdrop-blur-none"
|
||||
>
|
||||
<div className="fixed inset-0 bg-black/30" aria-hidden="true" />
|
||||
</TransitionChild>
|
||||
<TransitionChild
|
||||
as={Fragment}
|
||||
enter="transition-all ease-in-out duration-300"
|
||||
enterFrom="translate-x-full"
|
||||
enterTo="translate-x-0"
|
||||
leave="transition-all ease-in-out duration-200"
|
||||
leaveFrom="translate-x-0"
|
||||
leaveTo="translate-x-full"
|
||||
>
|
||||
<DialogPanel className="fixed bottom-0 right-0 top-0 flex h-full w-full flex-col border-l border-neutral-200 bg-white/80 p-6 text-black backdrop-blur-xl md:w-[500px]">
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="text-lg font-semibold">{title}</p>
|
||||
|
||||
<button aria-label="Close cart" onClick={onClose} className="text-black">
|
||||
<XMarkIcon className="h-6" />
|
||||
</button>
|
||||
</div>
|
||||
{children}
|
||||
</DialogPanel>
|
||||
</TransitionChild>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
);
|
||||
};
|
||||
|
||||
export default SideDialog;
|
Loading…
x
Reference in New Issue
Block a user