Merge remote-tracking branch 'origin' into CPP-153

This commit is contained in:
tedraykov
2024-06-20 14:52:30 +03:00
39 changed files with 1035 additions and 149 deletions

View File

@@ -0,0 +1,16 @@
import { Product } from 'lib/shopify/types';
import Details from './details';
import ShippingPolicy from './shipping-policy';
import WarrantyPolicy from './warranty-policy';
const AdditionalInformation = ({ product }: { product: Product }) => {
return (
<div className="my-5 w-full divide-y">
<Details product={product} />
<WarrantyPolicy />
<ShippingPolicy />
</div>
);
};
export default AdditionalInformation;

View File

@@ -93,8 +93,8 @@ const CoreCharge = ({ variants }: CoreChargeProps) => {
period, you will never need to pay the core charge.
</p>
<p className="text-sm">
If you don't manage to return the old part within the 30-day period, we will then
charge you the core charge. This keeps more money in your pocket upfront.
If you don&apos;t manage to return the old part within the 30-day period, we will
then charge you the core charge. This keeps more money in your pocket upfront.
</p>
</section>

View File

@@ -0,0 +1,114 @@
'use client';
import { TruckIcon } from '@heroicons/react/24/outline';
import Price from 'components/price';
import SideDialog from 'components/side-dialog';
import { DELIVERY_OPTION_KEY } from 'lib/constants';
import { cn, createUrl } from 'lib/utils';
import { usePathname, useRouter, useSearchParams } from 'next/navigation';
import { ReactNode, useState } from 'react';
const options = ['Commercial', 'Residential'] as const;
type Option = (typeof options)[number];
export const deliveryOptions: Array<{
key: Option;
template: ReactNode;
price: number;
}> = [
{
template: <span className="font-bold">Commercial</span>,
price: 299,
key: 'Commercial'
},
{
template: <span className="font-bold">Residential</span>,
price: 398,
key: 'Residential'
}
];
const Delivery = () => {
const searchParams = useSearchParams();
const pathname = usePathname();
const router = useRouter();
const [openingDialog, setOpeningDialog] = useState<'information' | 'terms-conditions' | null>(
null
);
const newSearchParams = new URLSearchParams(searchParams.toString());
const selectedDeliveryOption = newSearchParams.get(DELIVERY_OPTION_KEY);
const handleSelectDelivery = (option: Option) => {
newSearchParams.set(DELIVERY_OPTION_KEY, option);
const newUrl = createUrl(pathname, newSearchParams);
router.replace(newUrl, { scroll: false });
};
if (!selectedDeliveryOption) {
handleSelectDelivery(options[0]);
}
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">
<div className="flex flex-row items-center space-x-2 text-base font-medium">
<TruckIcon className="h-5 w-5" />
<span>Delivery</span>
</div>
<div className="pl-2">
<button
onClick={() => setOpeningDialog('information')}
className="text-xs text-blue-800 hover:underline lg:text-sm"
>
Information
</button>
<SideDialog
title="Information"
onClose={() => setOpeningDialog(null)}
open={openingDialog === 'information'}
>
<p>Information</p>
</SideDialog>
</div>
<div className="pl-2">
<button
onClick={() => setOpeningDialog('terms-conditions')}
className="text-xs text-blue-800 hover:underline lg:text-sm"
>
Terms & Conditions
</button>
<SideDialog
title="Terms & Conditions"
onClose={() => setOpeningDialog(null)}
open={openingDialog === 'terms-conditions'}
>
<p>Terms & Conditions</p>
</SideDialog>
</div>
</div>
<ul className="flex min-h-16 flex-row space-x-4 pt-2">
{deliveryOptions.map((option) => (
<li className="flex w-32" key={option.key}>
<button
onClick={() => handleSelectDelivery(option.key)}
className={cn(
'font-base flex w-full flex-col flex-wrap items-center justify-center space-y-0.5 rounded border text-center text-xs',
{
'border-0 ring-2 ring-secondary': selectedDeliveryOption === option.key
}
)}
>
{option.template}
<Price amount={String(option.price)} currencyCode="USD" />
</button>
</li>
))}
</ul>
</div>
);
};
export default Delivery;

View File

@@ -0,0 +1,82 @@
'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

@@ -0,0 +1,25 @@
'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

@@ -0,0 +1,73 @@
'use client';
import Price from 'components/price';
import { CORE_VARIANT_ID_KEY, CORE_WAIVER, DELIVERY_OPTION_KEY } from 'lib/constants';
import { Money, ProductVariant } from 'lib/shopify/types';
import { useSearchParams } from 'next/navigation';
import { deliveryOptions } from './delivery';
type PriceSummaryProps = {
variants: ProductVariant[];
defaultPrice: Money;
};
const PriceSummary = ({ variants, defaultPrice }: PriceSummaryProps) => {
const searchParams = useSearchParams();
const variant = variants.find((variant) =>
variant.selectedOptions.every(
(option) => option.value === searchParams.get(option.name.toLowerCase())
)
);
const price = variant?.price.amount || defaultPrice.amount;
const selectedCoreChargeOption = searchParams.get(CORE_VARIANT_ID_KEY);
const selectedDeliveryOption = searchParams.get(DELIVERY_OPTION_KEY);
const deliveryPrice =
deliveryOptions.find((option) => option.key === selectedDeliveryOption)?.price ?? 0;
const currencyCode = variant?.price.currencyCode || defaultPrice.currencyCode;
const corePrice = selectedCoreChargeOption === CORE_WAIVER ? 0 : variant?.coreCharge?.amount ?? 0;
const totalPrice = Number(price) + deliveryPrice + Number(corePrice);
return (
<div className="mb-3 flex flex-col gap-2">
<div className="flex flex-row items-center justify-between">
<span className="text-xl font-semibold">Our Price</span>
<Price amount={price} currencyCode={currencyCode} className="text-2xl font-semibold" />
</div>
<div className="flex flex-row items-center justify-between">
<span className="text-sm text-gray-400">{`Core Charge ${selectedCoreChargeOption === CORE_WAIVER ? '(Waived for 30 days)' : ''}`}</span>
{selectedCoreChargeOption === CORE_WAIVER ? (
<span className="text-sm text-gray-400">{`+$0.00`}</span>
) : (
<Price
amount={variant?.coreCharge?.amount ?? '0'}
currencyCode={currencyCode}
className="text-sm text-gray-400"
prefix="+"
/>
)}
</div>
<div className="flex flex-row items-center justify-between">
<span className="text-sm text-gray-400">{`Flat Rate Shipping (${selectedDeliveryOption} address)`}</span>
<Price
amount={String(deliveryPrice)}
currencyCode={currencyCode}
className="text-sm text-gray-400"
prefix="+"
/>
</div>
<hr />
<div className="flex flex-row items-center justify-between">
<span className="text-sm text-gray-400">To Pay Today</span>
<Price
amount={String(totalPrice)}
currencyCode={currencyCode}
className="text-sm text-gray-400"
/>
</div>
</div>
);
};
export default PriceSummary;

View File

@@ -2,7 +2,11 @@ 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';
import ProductDetails from './product-details';
import SpecialOffer from './special-offer';
import VariantDetails from './vairant-details';
import { VariantSelector } from './variant-selector';
@@ -11,7 +15,7 @@ import Warranty from './warranty';
export function ProductDescription({ product }: { product: Product }) {
return (
<>
<div className="mb-5 flex flex-col dark:border-neutral-700">
<div className="mb-4 flex flex-col">
<h1 className="text-xl font-bold md:text-2xl">{product.title}</h1>
<VariantDetails
@@ -34,6 +38,7 @@ export function ProductDescription({ product }: { product: Product }) {
/>
) : null}
<ProductDetails product={product} />
<div className="mb-2 border-t py-4 dark:border-neutral-700">
<CoreCharge variants={product.variants} />
</div>
@@ -42,12 +47,16 @@ export function ProductDescription({ product }: { product: Product }) {
<Warranty />
</div>
<div className="mb-2 border-t py-4 dark:border-neutral-700">
<Delivery />
</div>
<PriceSummary variants={product.variants} defaultPrice={product.priceRange.minVariantPrice} />
<Suspense fallback={null}>
<AddToCart variants={product.variants} availableForSale={product.availableForSale} />
</Suspense>
<div className="mt-4 border-t pt-4">
<SpecialOffer />
</div>
<SpecialOffer />
<AdditionalInformation product={product} />
</>
);
}

View File

@@ -0,0 +1,51 @@
import {
BeakerIcon,
BoltIcon,
CogIcon,
CpuChipIcon,
CubeTransparentIcon
} from '@heroicons/react/24/outline';
import { Product } from 'lib/shopify/types';
const ProductDetails = ({ product }: { product: Product }) => {
return (
<div className="mb-3 flex flex-col gap-3">
<span className="font-medium">Details</span>
<div className="grid grid-cols-4 gap-y-3 text-sm">
{product.transmissionType && (
<div className="flex flex-row items-center gap-2">
<CubeTransparentIcon className="size-4 text-primary" />
{product.transmissionType}
</div>
)}
{product.transmissionSpeeds && product.transmissionSpeeds.length && (
<div className="flex flex-row items-center gap-2">
<BoltIcon className="size-4 text-primary" />
{`${product.transmissionSpeeds[0]}-Speed`}
</div>
)}
{product.driveType && (
<div className="flex flex-row items-center gap-2">
<CogIcon className="size-4 text-primary" />
{product.driveType}
</div>
)}
{product.engineCylinders?.length && (
<div className="flex flex-row items-center gap-2">
<BeakerIcon className="size-4 text-primary" />
{`${product.engineCylinders[0]} Cylinders`}
</div>
)}
{product.transmissionCode?.length && (
<div className="flex flex-row items-center gap-2">
<CpuChipIcon className="size-4 text-primary" />
{product.transmissionCode[0]}
</div>
)}
</div>
</div>
);
};
export default ProductDetails;

View File

@@ -0,0 +1,44 @@
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,28 +1,71 @@
import { CurrencyDollarIcon, ShieldCheckIcon, UsersIcon } from '@heroicons/react/24/outline';
import { TruckIcon } from '@heroicons/react/24/solid';
import {
ArrowPathIcon,
CurrencyDollarIcon,
ShieldCheckIcon,
StarIcon,
TruckIcon,
UsersIcon
} from '@heroicons/react/24/outline';
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 text-sm tracking-normal text-neutral-800 lg:text-base dark:text-white">
<p className="flex items-center gap-3">
<TruckIcon className="h-4 w-4 text-secondary lg:h-5 lg:w-5" /> Flat Rate Shipping
(Commercial Address)
</p>
<p className="flex items-center gap-3">
<ShieldCheckIcon className="h-4 w-4 text-secondary lg:h-5 lg:w-5" /> Up to 5 Years
Unlimited Miles Warranty
</p>
<p className="flex items-center gap-3">
<UsersIcon className="h-4 w-4 text-secondary lg:h-5 lg:w-5" /> Excellent Customer Support
</p>
<p className="flex items-center gap-3">
<CurrencyDollarIcon className="h-4 w-4 text-secondary lg:h-5 lg:w-5" /> No Core Charge for
30 days
</p>
<div className="mt-10 grid grid-cols-2 gap-y-5 xl:grid-cols-3">
<div className="flex items-start gap-3">
<TruckIcon className="size-12 text-primary" />
<div className="flex flex-col">
<span className="font-medium uppercase">Flat Rate Shipping</span>
<span className="text-sm font-light">
We offer a flat $299 shipping fee to commercial addresses
</span>
</div>
</div>
</>
<div className="flex items-start gap-3">
<CurrencyDollarIcon className="size-10 text-primary" />
<div className="flex flex-col">
<span className="font-medium uppercase">Best Price Guarantee</span>
<span className="text-sm font-light">
We will match or beat any competitor&apos;s pricing
</span>
</div>
</div>
<div className="flex items-start gap-3">
<ShieldCheckIcon className="size-8 text-primary" />
<div className="flex flex-col">
<span className="font-medium uppercase">Unbeatable Warranty</span>
<span className="text-sm font-light">Up to 5 years with unlimited miles</span>
</div>
</div>
<div className="flex items-start gap-3">
<UsersIcon className="size-10 text-primary" />
<div className="flex flex-col">
<span className="font-medium uppercase">Excellent Support</span>
<span className="text-sm font-light">
End-to-end, expert care from our customer service team
</span>
</div>
</div>
<div className="flex items-start gap-3">
<ArrowPathIcon className="size-10 text-primary" />
<div className="flex flex-col">
<span className="font-medium uppercase">Core Charge Waiver</span>
<span className="text-sm font-light">
Avoid the core charge by returning within 30 days
</span>
</div>
</div>
<div className="flex items-start gap-3">
<StarIcon className="size-10 text-primary" />
<div className="flex flex-col">
<span className="font-medium uppercase">Free Core Return</span>
<span className="text-sm font-light">
Unlike competitors, we pay for the return of your core
</span>
</div>
</div>
</div>
);
};

View File

@@ -1,5 +1,6 @@
'use client';
import { CheckCircleIcon } from '@heroicons/react/24/outline';
import Price from 'components/price';
import { Money, ProductVariant } from 'lib/shopify/types';
import { useSearchParams } from 'next/navigation';
@@ -20,17 +21,23 @@ const VariantDetails = ({ variants, defaultPrice }: VariantDetailsProps) => {
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>
<div className="mt-1">
<Price
amount={price}
currencyCode={variant?.price.currencyCode || defaultPrice.currencyCode}
className="text-2xl font-semibold"
/>
</>
<div className="mt-2 flex items-center justify-start gap-x-2">
{variant?.availableForSale ? (
<div className="flex items-center gap-1 text-sm text-green-500">
<CheckCircleIcon className="size-5" /> In Stock
</div>
) : (
<span className="text-sm text-red-600">Out of Stock</span>
)}
<p className="text-sm">Condition: {variant?.condition || 'N/A'}</p>
</div>
</div>
);
};

View File

@@ -0,0 +1,102 @@
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;