mirror of
https://github.com/vercel/commerce.git
synced 2025-05-13 13:17:51 +00:00
fix: update core charge appearance
Signed-off-by: Chloe <pinkcloudvnn@gmail.com>
This commit is contained in:
parent
3a3ff3798f
commit
3bf7fa5af9
@ -10,10 +10,10 @@ import { useFormState, useFormStatus } from 'react-dom';
|
|||||||
|
|
||||||
function SubmitButton({
|
function SubmitButton({
|
||||||
availableForSale,
|
availableForSale,
|
||||||
selectedVariantId
|
disabled
|
||||||
}: {
|
}: {
|
||||||
availableForSale: boolean;
|
availableForSale: boolean;
|
||||||
selectedVariantId: string | undefined;
|
disabled: boolean;
|
||||||
}) {
|
}) {
|
||||||
const { pending } = useFormStatus();
|
const { pending } = useFormStatus();
|
||||||
const buttonClasses =
|
const buttonClasses =
|
||||||
@ -28,7 +28,7 @@ function SubmitButton({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!selectedVariantId) {
|
if (disabled) {
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
aria-label="Please select an option"
|
aria-label="Please select an option"
|
||||||
@ -75,11 +75,15 @@ export function AddToCart({
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
const selectedVariantId = variant?.id || defaultVariantId;
|
const selectedVariantId = variant?.id || defaultVariantId;
|
||||||
|
const missingCoreVariantId = variant?.coreVariantId && !searchParams.has('coreVariantId');
|
||||||
const actionWithVariant = formAction.bind(null, selectedVariantId);
|
const actionWithVariant = formAction.bind(null, selectedVariantId);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form action={actionWithVariant}>
|
<form action={actionWithVariant}>
|
||||||
<SubmitButton availableForSale={availableForSale} selectedVariantId={selectedVariantId} />
|
<SubmitButton
|
||||||
|
availableForSale={availableForSale}
|
||||||
|
disabled={Boolean(!selectedVariantId || missingCoreVariantId)}
|
||||||
|
/>
|
||||||
<p aria-live="polite" className="sr-only" role="status">
|
<p aria-live="polite" className="sr-only" role="status">
|
||||||
{message}
|
{message}
|
||||||
</p>
|
</p>
|
||||||
|
@ -1,21 +0,0 @@
|
|||||||
import CoreCharge from 'components/core-charge';
|
|
||||||
import { ProductVariant } from 'lib/shopify/types';
|
|
||||||
|
|
||||||
type CoreChargeBadgeProps = {
|
|
||||||
selectedOptions: {
|
|
||||||
name: string;
|
|
||||||
value: string;
|
|
||||||
}[];
|
|
||||||
variants: ProductVariant[];
|
|
||||||
};
|
|
||||||
|
|
||||||
const CoreChargeBadge = ({ variants, selectedOptions }: CoreChargeBadgeProps) => {
|
|
||||||
const selectedOptionsMap = new Map(selectedOptions.map((option) => [option.name, option.value]));
|
|
||||||
const variant = variants.find((variant: ProductVariant) =>
|
|
||||||
variant.selectedOptions.every((option) => option.value === selectedOptionsMap.get(option.name))
|
|
||||||
);
|
|
||||||
|
|
||||||
return <CoreCharge variant={variant} sm />;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default CoreChargeBadge;
|
|
@ -10,7 +10,6 @@ import Image from 'next/image';
|
|||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { Fragment, useEffect, useRef, useState } from 'react';
|
import { Fragment, useEffect, useRef, useState } from 'react';
|
||||||
import CloseCart from './close-cart';
|
import CloseCart from './close-cart';
|
||||||
import CoreChargeBadge from './core-charge-badge';
|
|
||||||
import { DeleteItemButton } from './delete-item-button';
|
import { DeleteItemButton } from './delete-item-button';
|
||||||
import { EditItemQuantityButton } from './edit-item-quantity-button';
|
import { EditItemQuantityButton } from './edit-item-quantity-button';
|
||||||
import OpenCart from './open-cart';
|
import OpenCart from './open-cart';
|
||||||
@ -136,17 +135,11 @@ export default function CartModal({ cart }: { cart: Cart | undefined }) {
|
|||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-20 flex flex-col gap-2">
|
<div className="ml-20 flex flex-col gap-2">
|
||||||
<div className="flex flex-row items-center gap-2">
|
|
||||||
<Price
|
<Price
|
||||||
className="font-semibold"
|
className="font-semibold"
|
||||||
amount={item.cost.totalAmount.amount}
|
amount={item.cost.totalAmount.amount}
|
||||||
currencyCode={item.cost.totalAmount.currencyCode}
|
currencyCode={item.cost.totalAmount.currencyCode}
|
||||||
/>
|
/>
|
||||||
<CoreChargeBadge
|
|
||||||
variants={item.merchandise.product.variants}
|
|
||||||
selectedOptions={item.merchandise.selectedOptions}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="flex h-9 w-fit flex-row items-center rounded-sm border border-neutral-300 dark:border-neutral-700">
|
<div className="flex h-9 w-fit flex-row items-center rounded-sm border border-neutral-300 dark:border-neutral-700">
|
||||||
<EditItemQuantityButton item={item} type="minus" />
|
<EditItemQuantityButton item={item} type="minus" />
|
||||||
<p className="w-6 text-center">
|
<p className="w-6 text-center">
|
||||||
|
@ -1,48 +0,0 @@
|
|||||||
import { ArrowPathIcon } from '@heroicons/react/16/solid';
|
|
||||||
import { ProductVariant } from 'lib/shopify/types';
|
|
||||||
import Price from './price';
|
|
||||||
import Tooltip from './tooltip';
|
|
||||||
|
|
||||||
type CoreChargeProps = {
|
|
||||||
variant?: ProductVariant;
|
|
||||||
sm?: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
const CoreCharge = ({ variant, sm = false }: CoreChargeProps) => {
|
|
||||||
if (!variant || !variant.coreCharge?.amount || variant.waiverAvailable) return null;
|
|
||||||
|
|
||||||
const coreChargeDisplay = (
|
|
||||||
<Price amount={variant.coreCharge.amount} currencyCode={variant.price.currencyCode} />
|
|
||||||
);
|
|
||||||
|
|
||||||
const originalPrice = String(Number(variant.price.amount) - Number(variant.coreCharge.amount));
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex items-center gap-2 rounded-md bg-gray-100 px-3 py-1 text-sm">
|
|
||||||
<ArrowPathIcon className="h-3 w-3" />
|
|
||||||
<span
|
|
||||||
className="flex items-center gap-1"
|
|
||||||
data-tooltip-id={!sm ? 'core-charge-explanation' : undefined}
|
|
||||||
>
|
|
||||||
{sm ? coreChargeDisplay : <>Core Charge: {coreChargeDisplay}</>}
|
|
||||||
</span>
|
|
||||||
<Tooltip id="core-charge-explanation" className="z-20 max-w-72">
|
|
||||||
<p className="flex flex-wrap items-center gap-1 text-sm">
|
|
||||||
The core charge of {coreChargeDisplay} is a refundable deposit that is added to the price
|
|
||||||
of the part.
|
|
||||||
</p>
|
|
||||||
<p className="text-sm">
|
|
||||||
This charge ensures that the old, worn-out part is returned to the supplier for proper
|
|
||||||
disposal or recycling.
|
|
||||||
</p>
|
|
||||||
<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 the
|
|
||||||
final price of the part
|
|
||||||
<Price amount={originalPrice} currencyCode={variant.price.currencyCode} />
|
|
||||||
</p>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default CoreCharge;
|
|
103
components/product/core-charge.tsx
Normal file
103
components/product/core-charge.tsx
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import Price from 'components/price';
|
||||||
|
import { CORE_VARIANT_ID_KEY, CORE_WAIVER } from 'lib/constants';
|
||||||
|
import { Money, ProductVariant } from 'lib/shopify/types';
|
||||||
|
import { cn, createUrl } from 'lib/utils';
|
||||||
|
import { usePathname, useRouter, useSearchParams } from 'next/navigation';
|
||||||
|
|
||||||
|
type CoreChargeProps = {
|
||||||
|
variants: ProductVariant[];
|
||||||
|
defaultPrice: Money;
|
||||||
|
};
|
||||||
|
const CoreCharge = ({ variants, defaultPrice }: CoreChargeProps) => {
|
||||||
|
const searchParams = useSearchParams();
|
||||||
|
const pathname = usePathname();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const optionSearchParams = new URLSearchParams(searchParams);
|
||||||
|
const coreVariantIdSearchParam = optionSearchParams.get(CORE_VARIANT_ID_KEY);
|
||||||
|
|
||||||
|
const variant = variants.find((variant: ProductVariant) =>
|
||||||
|
variant.selectedOptions.every(
|
||||||
|
(option) => option.value === optionSearchParams.get(option.name.toLowerCase())
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
const { coreCharge, waiverAvailable } = variant ?? {};
|
||||||
|
|
||||||
|
const handleSelectCoreChargeOption = (action: 'add' | 'remove') => {
|
||||||
|
if (action === 'add' && variant?.coreVariantId) {
|
||||||
|
optionSearchParams.set(CORE_VARIANT_ID_KEY, variant.coreVariantId);
|
||||||
|
} else if (action === 'remove') {
|
||||||
|
optionSearchParams.set(CORE_VARIANT_ID_KEY, CORE_WAIVER);
|
||||||
|
}
|
||||||
|
|
||||||
|
const newUrl = createUrl(pathname, optionSearchParams);
|
||||||
|
router.replace(newUrl, { scroll: false });
|
||||||
|
};
|
||||||
|
|
||||||
|
// if the selected variant has changed, and the core change variant id is not the same as the selected variant id
|
||||||
|
// or if users have selected the core waiver but the selected variant does not have a waiver available
|
||||||
|
// we remove the core charge from the url
|
||||||
|
if (
|
||||||
|
variant?.coreVariantId &&
|
||||||
|
optionSearchParams.has(CORE_VARIANT_ID_KEY) &&
|
||||||
|
(coreVariantIdSearchParam !== CORE_WAIVER || !variant.waiverAvailable) &&
|
||||||
|
coreVariantIdSearchParam !== variant.coreVariantId
|
||||||
|
) {
|
||||||
|
optionSearchParams.delete(CORE_VARIANT_ID_KEY);
|
||||||
|
const newUrl = createUrl(pathname, optionSearchParams);
|
||||||
|
router.replace(newUrl, { scroll: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedPayCoreCharge = coreVariantIdSearchParam === variant?.coreVariantId;
|
||||||
|
const selectedCoreWaiver = coreVariantIdSearchParam === CORE_WAIVER;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col text-xs lg:text-sm">
|
||||||
|
<div className="mb-2 text-base font-medium">Core Charge</div>
|
||||||
|
<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">
|
||||||
|
{waiverAvailable ? (
|
||||||
|
<li className="flex w-32">
|
||||||
|
<button
|
||||||
|
onClick={() => handleSelectCoreChargeOption('remove')}
|
||||||
|
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',
|
||||||
|
{
|
||||||
|
'ring-2 ring-secondary': selectedCoreWaiver
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<span>Core Waiver</span>
|
||||||
|
<Price amount="0" currencyCode={defaultPrice.currencyCode} />
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
) : null}
|
||||||
|
{coreCharge && variant?.coreVariantId ? (
|
||||||
|
<li className="flex w-32">
|
||||||
|
<button
|
||||||
|
onClick={() => handleSelectCoreChargeOption('add')}
|
||||||
|
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',
|
||||||
|
{
|
||||||
|
'ring-2 ring-secondary': selectedPayCoreCharge
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<span>Core Charge</span>
|
||||||
|
<Price amount={coreCharge.amount} currencyCode={coreCharge.currencyCode} />
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
) : null}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CoreCharge;
|
@ -1,45 +0,0 @@
|
|||||||
'use client';
|
|
||||||
|
|
||||||
import CoreCharge from 'components/core-charge';
|
|
||||||
import Price from 'components/price';
|
|
||||||
import { Money, ProductVariant } from 'lib/shopify/types';
|
|
||||||
import { useSearchParams } from 'next/navigation';
|
|
||||||
|
|
||||||
type PriceWithCoreChargeProps = {
|
|
||||||
variants: ProductVariant[];
|
|
||||||
defaultPrice: Money;
|
|
||||||
};
|
|
||||||
|
|
||||||
const PriceWithCoreCharge = ({ 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 (
|
|
||||||
<>
|
|
||||||
<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>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default PriceWithCoreCharge;
|
|
@ -2,8 +2,9 @@ import { AddToCart } from 'components/cart/add-to-cart';
|
|||||||
import Prose from 'components/prose';
|
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 CoreCharge from './core-charge';
|
||||||
import SpecialOffer from './special-offer';
|
import SpecialOffer from './special-offer';
|
||||||
|
import VariantPrice from './vairant-price';
|
||||||
import { VariantSelector } from './variant-selector';
|
import { VariantSelector } from './variant-selector';
|
||||||
import Warranty from './warranty';
|
import Warranty from './warranty';
|
||||||
|
|
||||||
@ -12,7 +13,7 @@ export function ProductDescription({ product }: { product: Product }) {
|
|||||||
<>
|
<>
|
||||||
<div className="mb-5 flex flex-col dark:border-neutral-700">
|
<div className="mb-5 flex flex-col dark:border-neutral-700">
|
||||||
<h1 className="mb-3 text-2xl font-bold">{product.title}</h1>
|
<h1 className="mb-3 text-2xl font-bold">{product.title}</h1>
|
||||||
<PriceWithCoreCharge
|
<VariantPrice
|
||||||
variants={product.variants}
|
variants={product.variants}
|
||||||
defaultPrice={product.priceRange.minVariantPrice}
|
defaultPrice={product.priceRange.minVariantPrice}
|
||||||
/>
|
/>
|
||||||
@ -28,6 +29,10 @@ export function ProductDescription({ product }: { product: Product }) {
|
|||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
|
<div className="mb-4 border-t pb-4 pt-6 dark:border-neutral-700">
|
||||||
|
<CoreCharge variants={product.variants} defaultPrice={product.priceRange.minVariantPrice} />
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="mb-4 border-t py-6 dark:border-neutral-700">
|
<div className="mb-4 border-t py-6 dark:border-neutral-700">
|
||||||
<Warranty productType={product.productType} />
|
<Warranty productType={product.productType} />
|
||||||
</div>
|
</div>
|
||||||
|
31
components/product/vairant-price.tsx
Normal file
31
components/product/vairant-price.tsx
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
'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;
|
@ -51,6 +51,7 @@ export function VariantSelector({
|
|||||||
// Update the option params using the current option to reflect how the url *would* change,
|
// Update the option params using the current option to reflect how the url *would* change,
|
||||||
// if the option was clicked.
|
// if the option was clicked.
|
||||||
optionSearchParams.set(optionNameLowerCase, value);
|
optionSearchParams.set(optionNameLowerCase, value);
|
||||||
|
|
||||||
const optionUrl = createUrl(pathname, optionSearchParams);
|
const optionUrl = createUrl(pathname, optionSearchParams);
|
||||||
|
|
||||||
// In order to determine if an option is available for sale, we need to:
|
// In order to determine if an option is available for sale, we need to:
|
||||||
|
@ -38,16 +38,19 @@ const WarrantySelector = () => {
|
|||||||
return (
|
return (
|
||||||
<ul className="flex min-h-16 flex-row space-x-4 pt-2">
|
<ul className="flex min-h-16 flex-row space-x-4 pt-2">
|
||||||
{plans.map((plan) => (
|
{plans.map((plan) => (
|
||||||
<li
|
<li key={plan.key} className="flex w-32">
|
||||||
key={plan.key}
|
<button
|
||||||
onClick={() => setSelectedOptions(plan.key)}
|
onClick={() => setSelectedOptions(plan.key)}
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex w-32 cursor-pointer flex-col items-center justify-center space-y-2 rounded-md border p-2 text-center text-xs font-medium',
|
'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',
|
||||||
{ 'ring-2 ring-secondary': plan.key === selectedOptions }
|
{
|
||||||
|
'ring-2 ring-secondary': plan.key === selectedOptions
|
||||||
|
}
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{plan.template}
|
{plan.template}
|
||||||
<Price amount={String(plan.price)} currencyCode="USD" />
|
<Price amount={String(plan.price)} currencyCode="USD" />
|
||||||
|
</button>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -29,3 +29,6 @@ export const TAGS = {
|
|||||||
export const HIDDEN_PRODUCT_TAG = 'nextjs-frontend-hidden';
|
export const HIDDEN_PRODUCT_TAG = 'nextjs-frontend-hidden';
|
||||||
export const DEFAULT_OPTION = 'Default Title';
|
export const DEFAULT_OPTION = 'Default Title';
|
||||||
export const SHOPIFY_GRAPHQL_API_ENDPOINT = '/api/2024-04/graphql.json';
|
export const SHOPIFY_GRAPHQL_API_ENDPOINT = '/api/2024-04/graphql.json';
|
||||||
|
|
||||||
|
export const CORE_WAIVER = 'core-waiver';
|
||||||
|
export const CORE_VARIANT_ID_KEY = 'coreVariantId';
|
||||||
|
@ -52,6 +52,9 @@ const productFragment = /* GraphQL */ `
|
|||||||
waiverAvailable: metafield(namespace: "custom", key: "waiver_available") {
|
waiverAvailable: metafield(namespace: "custom", key: "waiver_available") {
|
||||||
value
|
value
|
||||||
}
|
}
|
||||||
|
coreVariantId: metafield(namespace: "custom", key: "coreVariant") {
|
||||||
|
value
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -183,8 +183,9 @@ const reshapeImages = (images: Connection<Image>, productTitle: string) => {
|
|||||||
const reshapeVariants = (variants: ShopifyProductVariant[]): ProductVariant[] => {
|
const reshapeVariants = (variants: ShopifyProductVariant[]): ProductVariant[] => {
|
||||||
return variants.map((variant) => ({
|
return variants.map((variant) => ({
|
||||||
...variant,
|
...variant,
|
||||||
coreCharge: parseMetaFieldValue<Money>(variant.coreCharge),
|
waiverAvailable: parseMetaFieldValue<boolean>(variant.waiverAvailable),
|
||||||
waiverAvailable: parseMetaFieldValue<boolean>(variant.waiverAvailable)
|
coreVariantId: variant.coreVariantId?.value || null,
|
||||||
|
coreCharge: parseMetaFieldValue<Money>(variant.coreCharge)
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -404,6 +405,18 @@ export async function getProduct(handle: string): Promise<Product | undefined> {
|
|||||||
return reshapeProduct(res.body.data.product, false);
|
return reshapeProduct(res.body.data.product, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getProductVariant(handle: string): Promise<Product | undefined> {
|
||||||
|
const res = await shopifyFetch<ShopifyProductOperation>({
|
||||||
|
query: getProductQuery,
|
||||||
|
tags: [TAGS.products],
|
||||||
|
variables: {
|
||||||
|
handle
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return reshapeProduct(res.body.data.product, false);
|
||||||
|
}
|
||||||
|
|
||||||
export async function getProductRecommendations(productId: string): Promise<Product[]> {
|
export async function getProductRecommendations(productId: string): Promise<Product[]> {
|
||||||
const res = await shopifyFetch<ShopifyProductRecommendationsOperation>({
|
const res = await shopifyFetch<ShopifyProductRecommendationsOperation>({
|
||||||
query: getProductRecommendationsQuery,
|
query: getProductRecommendationsQuery,
|
||||||
|
@ -87,11 +87,16 @@ export type ProductVariant = {
|
|||||||
waiverAvailable: boolean | null;
|
waiverAvailable: boolean | null;
|
||||||
barcode: string | null;
|
barcode: string | null;
|
||||||
sku: string | null;
|
sku: string | null;
|
||||||
|
coreVariantId: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ShopifyProductVariant = Omit<ProductVariant, 'coreCharge' | 'waiverAvailable'> & {
|
export type ShopifyProductVariant = Omit<
|
||||||
coreCharge: { value: string } | null;
|
ProductVariant,
|
||||||
|
'coreCharge' | 'waiverAvailable' | 'coreVariantId'
|
||||||
|
> & {
|
||||||
waiverAvailable: { value: string };
|
waiverAvailable: { value: string };
|
||||||
|
coreVariantId: { value: string } | null;
|
||||||
|
coreCharge: { value: string } | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SEO = {
|
export type SEO = {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user