mirror of
https://github.com/vercel/commerce.git
synced 2025-05-12 20:57:51 +00:00
feat: adding more information warranty, part number, sku, speciall offers
Signed-off-by: Chloe <pinkcloudvnn@gmail.com>
This commit is contained in:
parent
e3f564ca77
commit
59c3f07beb
@ -84,8 +84,8 @@ export default async function ProductPage({ params }: { params: { handle: string
|
|||||||
/>
|
/>
|
||||||
<div className="mx-auto max-w-screen-2xl px-4">
|
<div className="mx-auto max-w-screen-2xl px-4">
|
||||||
<BreadcrumbComponent type="product" handle={product.handle} />
|
<BreadcrumbComponent type="product" handle={product.handle} />
|
||||||
<div className="my-3 flex flex-col rounded-lg border border-neutral-200 bg-white p-8 md:p-12 lg:flex-row lg:gap-8 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-8 md:p-10 lg:flex-row lg:gap-8 lg:space-x-3 dark:border-neutral-800 dark:bg-black">
|
||||||
<div className="h-full w-full basis-full lg:basis-4/6">
|
<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="relative aspect-square h-full max-h-[550px] w-full overflow-hidden" />
|
||||||
@ -100,7 +100,7 @@ export default async function ProductPage({ params }: { params: { handle: string
|
|||||||
</Suspense>
|
</Suspense>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="basis-full lg:basis-2/6">
|
<div className="basis-full lg:basis-5/12">
|
||||||
<ProductDescription product={product} />
|
<ProductDescription product={product} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -11,12 +11,10 @@ type CoreChargeBadgeProps = {
|
|||||||
|
|
||||||
const CoreChargeBadge = ({ variants, selectedOptions }: CoreChargeBadgeProps) => {
|
const CoreChargeBadge = ({ variants, selectedOptions }: CoreChargeBadgeProps) => {
|
||||||
const selectedOptionsMap = new Map(selectedOptions.map((option) => [option.name, option.value]));
|
const selectedOptionsMap = new Map(selectedOptions.map((option) => [option.name, option.value]));
|
||||||
console.log({ selectedOptionsMap, variants });
|
|
||||||
const variant = variants.find((variant: ProductVariant) =>
|
const variant = variants.find((variant: ProductVariant) =>
|
||||||
variant.selectedOptions.every((option) => option.value === selectedOptionsMap.get(option.name))
|
variant.selectedOptions.every((option) => option.value === selectedOptionsMap.get(option.name))
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log({ variant });
|
|
||||||
return <CoreCharge variant={variant} sm />;
|
return <CoreCharge variant={variant} sm />;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -183,7 +183,7 @@ export default function CartModal({ cart }: { cart: Cart | undefined }) {
|
|||||||
</div>
|
</div>
|
||||||
<a
|
<a
|
||||||
href={cart.checkoutUrl}
|
href={cart.checkoutUrl}
|
||||||
className="block w-full rounded-full bg-blue-600 p-3 text-center text-sm font-medium text-white opacity-90 hover:opacity-100"
|
className="block w-full rounded-full bg-secondary p-3 text-center text-sm font-medium text-white opacity-90 hover:opacity-100"
|
||||||
>
|
>
|
||||||
Proceed to Checkout
|
Proceed to Checkout
|
||||||
</a>
|
</a>
|
||||||
|
@ -11,35 +11,36 @@ type CoreChargeProps = {
|
|||||||
const CoreCharge = ({ variant, sm = false }: CoreChargeProps) => {
|
const CoreCharge = ({ variant, sm = false }: CoreChargeProps) => {
|
||||||
if (!variant || !variant.coreCharge?.amount || variant.waiverAvailable) return null;
|
if (!variant || !variant.coreCharge?.amount || variant.waiverAvailable) return null;
|
||||||
|
|
||||||
const originalPrice = String(Number(variant.price.amount) - Number(variant.coreCharge.amount));
|
|
||||||
|
|
||||||
const coreChargeDisplay = (
|
const coreChargeDisplay = (
|
||||||
<Price amount={variant.coreCharge.amount} currencyCode={variant.price.currencyCode} />
|
<Price amount={variant.coreCharge.amount} currencyCode={variant.price.currencyCode} />
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const originalPrice = String(Number(variant.price.amount) - Number(variant.coreCharge.amount));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-2 rounded-md bg-gray-100 px-3 py-1 text-sm">
|
<div className="flex items-center gap-2 rounded-md bg-gray-100 px-3 py-1 text-sm">
|
||||||
<ArrowPathIcon className="h-4 w-4" />
|
<ArrowPathIcon className="h-3 w-3" />
|
||||||
<span className="flex items-center gap-1" data-tooltip-id="core-charge-explanation">
|
<span
|
||||||
|
className="flex items-center gap-1"
|
||||||
|
data-tooltip-id={!sm ? 'core-charge-explanation' : undefined}
|
||||||
|
>
|
||||||
{sm ? coreChargeDisplay : <>Core Charge: {coreChargeDisplay}</>}
|
{sm ? coreChargeDisplay : <>Core Charge: {coreChargeDisplay}</>}
|
||||||
</span>
|
</span>
|
||||||
{sm ? null : (
|
<Tooltip id="core-charge-explanation" className="z-20 max-w-72">
|
||||||
<Tooltip id="core-charge-explanation" className="max-w-64">
|
<p className="flex flex-wrap items-center gap-1 text-sm">
|
||||||
<p className="flex flex-wrap items-center gap-1">
|
The core charge of {coreChargeDisplay} is a refundable deposit that is added to the price
|
||||||
The core charge of {coreChargeDisplay} is a refundable deposit that is added to the
|
of the part.
|
||||||
price of the part.
|
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p className="text-sm">
|
||||||
This charge ensures that the old, worn-out part is returned to the supplier for proper
|
This charge ensures that the old, worn-out part is returned to the supplier for proper
|
||||||
disposal or recycling.
|
disposal or recycling.
|
||||||
</p>
|
</p>
|
||||||
<p className="flex flex-wrap items-center gap-1">
|
<p className="flex flex-wrap items-center gap-1 text-sm">
|
||||||
When you return the old part, you'll receive a refund of the core charge, making
|
When you return the old part, you'll receive a refund of the core charge, making the
|
||||||
the final price of the part{' '}
|
final price of the part
|
||||||
<Price amount={originalPrice} currencyCode={variant.price.currencyCode} />
|
<Price amount={originalPrice} currencyCode={variant.price.currencyCode} />
|
||||||
</p>
|
</p>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -39,10 +39,10 @@ export function Gallery({ images }: { images: { src: string; altText: string }[]
|
|||||||
priority={true}
|
priority={true}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{images.length > 1 ? (
|
{images.length > 1 ? (
|
||||||
|
<>
|
||||||
<div className="absolute bottom-[15%] flex w-full justify-center">
|
<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">
|
<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
|
<Link
|
||||||
aria-label="Previous product image"
|
aria-label="Previous product image"
|
||||||
href={previousUrl}
|
href={previousUrl}
|
||||||
@ -62,6 +62,10 @@ export function Gallery({ images }: { images: { src: string; altText: string }[]
|
|||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<p className="absolute bottom-[5%] flex w-full justify-center text-sm text-neutral-500">
|
||||||
|
Representative Image
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -1,10 +1,7 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { InformationCircleIcon } from '@heroicons/react/24/outline';
|
|
||||||
import { Checkbox } from 'components/checkbox';
|
|
||||||
import CoreCharge from 'components/core-charge';
|
import CoreCharge from 'components/core-charge';
|
||||||
import Price from 'components/price';
|
import Price from 'components/price';
|
||||||
import Tooltip from 'components/tooltip';
|
|
||||||
import { Money, ProductVariant } from 'lib/shopify/types';
|
import { Money, ProductVariant } from 'lib/shopify/types';
|
||||||
import { useSearchParams } from 'next/navigation';
|
import { useSearchParams } from 'next/navigation';
|
||||||
|
|
||||||
@ -20,32 +17,28 @@ const PriceWithCoreCharge = ({ variants, defaultPrice }: PriceWithCoreChargeProp
|
|||||||
(option) => option.value === searchParams.get(option.name.toLowerCase())
|
(option) => option.value === searchParams.get(option.name.toLowerCase())
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
console.log({ variant });
|
|
||||||
|
const price = variant?.price.amount || defaultPrice.amount;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
|
<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">
|
<div className="mr-auto flex w-auto flex-row flex-wrap items-center gap-3 text-sm">
|
||||||
<Price
|
<Price
|
||||||
amount={variant?.price.amount || defaultPrice.amount}
|
amount={price}
|
||||||
currencyCode={variant?.price.currencyCode || defaultPrice.currencyCode}
|
currencyCode={variant?.price.currencyCode || defaultPrice.currencyCode}
|
||||||
className="text-lg font-semibold"
|
className="text-2xl font-semibold"
|
||||||
/>
|
/>
|
||||||
<CoreCharge variant={variant} />
|
<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>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -3,13 +3,15 @@ import Prose from 'components/prose';
|
|||||||
import { Product } from 'lib/shopify/types';
|
import { Product } from 'lib/shopify/types';
|
||||||
import { Suspense } from 'react';
|
import { Suspense } from 'react';
|
||||||
import PriceWithCoreCharge from './price-with-core-charge';
|
import PriceWithCoreCharge from './price-with-core-charge';
|
||||||
|
import SpecialOffer from './special-offer';
|
||||||
import { VariantSelector } from './variant-selector';
|
import { VariantSelector } from './variant-selector';
|
||||||
|
import Warranty from './warranty';
|
||||||
|
|
||||||
export function ProductDescription({ product }: { product: Product }) {
|
export function ProductDescription({ product }: { product: Product }) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="mb-6 flex flex-col border-b pb-6 dark:border-neutral-700">
|
<div className="mb-5 flex flex-col dark:border-neutral-700">
|
||||||
<h1 className="mb-3 text-4xl font-bold">{product.title}</h1>
|
<h1 className="mb-3 text-2xl font-bold">{product.title}</h1>
|
||||||
<PriceWithCoreCharge
|
<PriceWithCoreCharge
|
||||||
variants={product.variants}
|
variants={product.variants}
|
||||||
defaultPrice={product.priceRange.minVariantPrice}
|
defaultPrice={product.priceRange.minVariantPrice}
|
||||||
@ -21,14 +23,21 @@ export function ProductDescription({ product }: { product: Product }) {
|
|||||||
|
|
||||||
{product.descriptionHtml ? (
|
{product.descriptionHtml ? (
|
||||||
<Prose
|
<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}
|
html={product.descriptionHtml}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
|
<div className="mb-4 border-t py-6 dark:border-neutral-700">
|
||||||
|
<Warranty productType={product.productType} />
|
||||||
|
</div>
|
||||||
|
|
||||||
<Suspense fallback={null}>
|
<Suspense fallback={null}>
|
||||||
<AddToCart variants={product.variants} availableForSale={product.availableForSale} />
|
<AddToCart variants={product.variants} availableForSale={product.availableForSale} />
|
||||||
</Suspense>
|
</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) => (
|
return options.map((option) => (
|
||||||
<dl className="mb-8" key={option.id}>
|
<dl className="mb-6" key={option.id}>
|
||||||
<dt className="mb-4 text-sm uppercase tracking-wide">{option.name}</dt>
|
<dt className="mb-4 text-sm font-medium tracking-wide">{option.name}</dt>
|
||||||
<dd className="flex flex-wrap gap-3">
|
<dd className="flex flex-wrap gap-3">
|
||||||
{option.values.map((value) => {
|
{option.values.map((value) => {
|
||||||
const optionNameLowerCase = option.name.toLowerCase();
|
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;
|
@ -36,6 +36,8 @@ const productFragment = /* GraphQL */ `
|
|||||||
id
|
id
|
||||||
title
|
title
|
||||||
availableForSale
|
availableForSale
|
||||||
|
barcode
|
||||||
|
sku
|
||||||
selectedOptions {
|
selectedOptions {
|
||||||
name
|
name
|
||||||
value
|
value
|
||||||
@ -68,6 +70,9 @@ const productFragment = /* GraphQL */ `
|
|||||||
}
|
}
|
||||||
tags
|
tags
|
||||||
updatedAt
|
updatedAt
|
||||||
|
productType: metafield(namespace: "custom", key: "product_type") {
|
||||||
|
value
|
||||||
|
}
|
||||||
}
|
}
|
||||||
${imageFragment}
|
${imageFragment}
|
||||||
${seoFragment}
|
${seoFragment}
|
||||||
|
@ -193,12 +193,12 @@ const reshapeProduct = (product: ShopifyProduct, filterHiddenProducts: boolean =
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { images, variants, ...rest } = product;
|
const { images, variants, productType, ...rest } = product;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...rest,
|
...rest,
|
||||||
images: reshapeImages(images, product.title),
|
images: reshapeImages(images, product.title),
|
||||||
variants: reshapeVariants(removeEdgesAndNodes(variants))
|
variants: reshapeVariants(removeEdgesAndNodes(variants)),
|
||||||
|
productType: productType?.value ?? null
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -62,9 +62,10 @@ export type Page = {
|
|||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Product = Omit<ShopifyProduct, 'variants' | 'images'> & {
|
export type Product = Omit<ShopifyProduct, 'variants' | 'images' | 'productType'> & {
|
||||||
variants: ProductVariant[];
|
variants: ProductVariant[];
|
||||||
images: Image[];
|
images: Image[];
|
||||||
|
productType: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ProductOption = {
|
export type ProductOption = {
|
||||||
@ -84,6 +85,8 @@ export type ProductVariant = {
|
|||||||
price: Money;
|
price: Money;
|
||||||
coreCharge: Money | null;
|
coreCharge: Money | null;
|
||||||
waiverAvailable: boolean | null;
|
waiverAvailable: boolean | null;
|
||||||
|
barcode: string | null;
|
||||||
|
sku: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ShopifyProductVariant = Omit<ProductVariant, 'coreCharge' | 'waiverAvailable'> & {
|
export type ShopifyProductVariant = Omit<ProductVariant, 'coreCharge' | 'waiverAvailable'> & {
|
||||||
@ -140,6 +143,9 @@ export type ShopifyProduct = {
|
|||||||
handle: string;
|
handle: string;
|
||||||
}[];
|
}[];
|
||||||
};
|
};
|
||||||
|
productType: {
|
||||||
|
value: string;
|
||||||
|
} | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ShopifyCartOperation = {
|
export type ShopifyCartOperation = {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user