diff --git a/components/cart/add-to-cart.tsx b/components/cart/add-to-cart.tsx index 2cb9ad384..09ecf98f2 100644 --- a/components/cart/add-to-cart.tsx +++ b/components/cart/add-to-cart.tsx @@ -10,10 +10,10 @@ import { useFormState, useFormStatus } from 'react-dom'; function SubmitButton({ availableForSale, - selectedVariantId + disabled }: { availableForSale: boolean; - selectedVariantId: string | undefined; + disabled: boolean; }) { const { pending } = useFormStatus(); const buttonClasses = @@ -28,7 +28,7 @@ function SubmitButton({ ); } - if (!selectedVariantId) { + if (disabled) { return ( + + ) : null} + {coreCharge && variant?.coreVariantId ? ( +
  • + +
  • + ) : null} + + + ); +}; + +export default CoreCharge; diff --git a/components/product/price-with-core-charge.tsx b/components/product/price-with-core-charge.tsx deleted file mode 100644 index a689f66a9..000000000 --- a/components/product/price-with-core-charge.tsx +++ /dev/null @@ -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 ( - <> -
    - {variant && ( -
    - {variant.sku && SKU: {variant.sku}} - {variant.barcode && Part Number: {variant.barcode}} -
    - )} -
    -
    - - -
    - - ); -}; - -export default PriceWithCoreCharge; diff --git a/components/product/product-description.tsx b/components/product/product-description.tsx index d30c5c239..a5318563b 100644 --- a/components/product/product-description.tsx +++ b/components/product/product-description.tsx @@ -2,8 +2,9 @@ import { AddToCart } from 'components/cart/add-to-cart'; import Prose from 'components/prose'; import { Product } from 'lib/shopify/types'; import { Suspense } from 'react'; -import PriceWithCoreCharge from './price-with-core-charge'; +import CoreCharge from './core-charge'; import SpecialOffer from './special-offer'; +import VariantPrice from './vairant-price'; import { VariantSelector } from './variant-selector'; import Warranty from './warranty'; @@ -12,7 +13,7 @@ export function ProductDescription({ product }: { product: Product }) { <>

    {product.title}

    - @@ -28,6 +29,10 @@ export function ProductDescription({ product }: { product: Product }) { /> ) : null} +
    + +
    +
    diff --git a/components/product/vairant-price.tsx b/components/product/vairant-price.tsx new file mode 100644 index 000000000..74b5361a2 --- /dev/null +++ b/components/product/vairant-price.tsx @@ -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 ( + + ); +}; + +export default VariantPrice; diff --git a/components/product/variant-selector.tsx b/components/product/variant-selector.tsx index 4ffaca313..c9c48bad6 100644 --- a/components/product/variant-selector.tsx +++ b/components/product/variant-selector.tsx @@ -51,6 +51,7 @@ export function VariantSelector({ // Update the option params using the current option to reflect how the url *would* change, // if the option was clicked. optionSearchParams.set(optionNameLowerCase, value); + const optionUrl = createUrl(pathname, optionSearchParams); // In order to determine if an option is available for sale, we need to: diff --git a/components/product/warranty-selector.tsx b/components/product/warranty-selector.tsx index 3beaa5559..4e7d8dbc0 100644 --- a/components/product/warranty-selector.tsx +++ b/components/product/warranty-selector.tsx @@ -38,16 +38,19 @@ const WarrantySelector = () => { return (
      {plans.map((plan) => ( -
    • 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-center text-xs font-medium', - { 'ring-2 ring-secondary': plan.key === selectedOptions } - )} - > - {plan.template} - +
    • +
    • ))}
    diff --git a/lib/constants.ts b/lib/constants.ts index 0cea8491d..a9c11d2f6 100644 --- a/lib/constants.ts +++ b/lib/constants.ts @@ -29,3 +29,6 @@ export const TAGS = { export const HIDDEN_PRODUCT_TAG = 'nextjs-frontend-hidden'; export const DEFAULT_OPTION = 'Default Title'; export const SHOPIFY_GRAPHQL_API_ENDPOINT = '/api/2024-04/graphql.json'; + +export const CORE_WAIVER = 'core-waiver'; +export const CORE_VARIANT_ID_KEY = 'coreVariantId'; diff --git a/lib/shopify/fragments/product.ts b/lib/shopify/fragments/product.ts index 1539ba669..4e8e7c3b8 100644 --- a/lib/shopify/fragments/product.ts +++ b/lib/shopify/fragments/product.ts @@ -52,6 +52,9 @@ const productFragment = /* GraphQL */ ` waiverAvailable: metafield(namespace: "custom", key: "waiver_available") { value } + coreVariantId: metafield(namespace: "custom", key: "coreVariant") { + value + } } } } diff --git a/lib/shopify/index.ts b/lib/shopify/index.ts index 1b03a75c1..87dfd0d55 100644 --- a/lib/shopify/index.ts +++ b/lib/shopify/index.ts @@ -183,8 +183,9 @@ const reshapeImages = (images: Connection, productTitle: string) => { const reshapeVariants = (variants: ShopifyProductVariant[]): ProductVariant[] => { return variants.map((variant) => ({ ...variant, - coreCharge: parseMetaFieldValue(variant.coreCharge), - waiverAvailable: parseMetaFieldValue(variant.waiverAvailable) + waiverAvailable: parseMetaFieldValue(variant.waiverAvailable), + coreVariantId: variant.coreVariantId?.value || null, + coreCharge: parseMetaFieldValue(variant.coreCharge) })); }; @@ -404,6 +405,18 @@ export async function getProduct(handle: string): Promise { return reshapeProduct(res.body.data.product, false); } +export async function getProductVariant(handle: string): Promise { + const res = await shopifyFetch({ + query: getProductQuery, + tags: [TAGS.products], + variables: { + handle + } + }); + + return reshapeProduct(res.body.data.product, false); +} + export async function getProductRecommendations(productId: string): Promise { const res = await shopifyFetch({ query: getProductRecommendationsQuery, diff --git a/lib/shopify/types.ts b/lib/shopify/types.ts index 2fc96312c..4fd5b993b 100644 --- a/lib/shopify/types.ts +++ b/lib/shopify/types.ts @@ -87,11 +87,16 @@ export type ProductVariant = { waiverAvailable: boolean | null; barcode: string | null; sku: string | null; + coreVariantId: string | null; }; -export type ShopifyProductVariant = Omit & { - coreCharge: { value: string } | null; +export type ShopifyProductVariant = Omit< + ProductVariant, + 'coreCharge' | 'waiverAvailable' | 'coreVariantId' +> & { waiverAvailable: { value: string }; + coreVariantId: { value: string } | null; + coreCharge: { value: string } | null; }; export type SEO = {