diff --git a/components/cart/add-to-cart.tsx b/components/cart/add-to-cart.tsx
index b866b1714..9a0b92790 100644
--- a/components/cart/add-to-cart.tsx
+++ b/components/cart/add-to-cart.tsx
@@ -4,7 +4,7 @@ import { ShoppingCartIcon } from '@heroicons/react/24/outline';
import clsx from 'clsx';
import { addItem } from 'components/cart/actions';
import LoadingDots from 'components/loading-dots';
-import { CORE_VARIANT_ID_KEY } from 'lib/constants';
+import { CORE_VARIANT_ID_KEY, CORE_WAIVER } from 'lib/constants';
import { ProductVariant } from 'lib/shopify/types';
import { useSearchParams } from 'next/navigation';
import { useFormState, useFormStatus } from 'react-dom';
@@ -69,18 +69,20 @@ export function AddToCart({
}) {
const [message, formAction] = useFormState(addItem, null);
const searchParams = useSearchParams();
- const defaultVariantId = variants.length === 1 ? variants[0]?.id : undefined;
const variant = variants.find((variant: ProductVariant) =>
variant.selectedOptions.every(
(option) => option.value === searchParams.get(option.name.toLowerCase())
)
);
- const selectedVariantId = variant?.id || defaultVariantId;
+ const selectedVariantId = variant?.id;
const missingCoreVariantId = variant?.coreVariantId && !searchParams.has(CORE_VARIANT_ID_KEY);
const coreVariantId = searchParams.get(CORE_VARIANT_ID_KEY);
- const selectedVariantIds = [coreVariantId, selectedVariantId].filter(Boolean) as string[];
+ // remove special core-waiver value as it is not a valid variant
+ const selectedVariantIds = [coreVariantId, selectedVariantId]
+ .filter(Boolean)
+ .filter((value) => value !== CORE_WAIVER) as string[];
const actionWithVariant = formAction.bind(null, selectedVariantIds);
diff --git a/components/product/core-charge.tsx b/components/product/core-charge.tsx
index 74147ea0c..7b16f08cc 100644
--- a/components/product/core-charge.tsx
+++ b/components/product/core-charge.tsx
@@ -2,15 +2,14 @@
import Price from 'components/price';
import { CORE_VARIANT_ID_KEY, CORE_WAIVER } from 'lib/constants';
-import { Money, ProductVariant } from 'lib/shopify/types';
+import { CoreChargeOption, 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 CoreCharge = ({ variants }: CoreChargeProps) => {
const searchParams = useSearchParams();
const pathname = usePathname();
const router = useRouter();
@@ -24,35 +23,32 @@ const CoreCharge = ({ variants, defaultPrice }: CoreChargeProps) => {
)
);
- const { coreCharge, waiverAvailable } = variant ?? {};
+ const { coreCharge, waiverAvailable, coreVariantId } = 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 handleSelectCoreChargeOption = (coreVariantId: string) => {
+ optionSearchParams.set(CORE_VARIANT_ID_KEY, coreVariantId);
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 coreChargeOptions = [
+ waiverAvailable && {
+ label: 'Core Waiver',
+ value: CORE_WAIVER,
+ price: { amount: 0, currencyCode: variant?.price.currencyCode }
+ },
+ coreVariantId &&
+ coreCharge && {
+ label: 'Core Charge',
+ value: coreVariantId,
+ price: coreCharge
+ }
+ ].filter(Boolean) as CoreChargeOption[];
- const selectedPayCoreCharge = coreVariantIdSearchParam === variant?.coreVariantId;
- const selectedCoreWaiver = coreVariantIdSearchParam === CORE_WAIVER;
+ if (!optionSearchParams.has(CORE_VARIANT_ID_KEY) && coreChargeOptions.length > 0) {
+ handleSelectCoreChargeOption((coreChargeOptions[0] as CoreChargeOption).value);
+ }
return (
@@ -63,38 +59,22 @@ const CoreCharge = ({ variants, defaultPrice }: CoreChargeProps) => {
recycling. When you return the old part, you'll receive a refund of the core charge.
- {option.values.map((value) => {
- const optionNameLowerCase = option.name.toLowerCase();
+ const variantsById: Record = variants.reduce((acc, variant) => {
+ return { ...acc, [variant.id]: variant };
+ }, {});
- // Base option params on current params so we can preserve any other param state in the url.
- const optionSearchParams = new URLSearchParams(searchParams.toString());
+ // If a variant is not selected, we want to select the first available for sale variant as default
+ useEffect(() => {
+ const hasSelectedVariant = Array.from(searchParams.entries()).some(([key, value]) => {
+ return combinations.some((combination) => combination[key] === value);
+ });
- // Update the option params using the current option to reflect how the url *would* change,
- // if the option was clicked.
- optionSearchParams.set(optionNameLowerCase, value);
+ if (!hasSelectedVariant) {
+ const defaultVariant = variants.find((variant) => variant.availableForSale);
+ if (defaultVariant) {
+ const optionSearchParams = new URLSearchParams(searchParams.toString());
+ defaultVariant.selectedOptions.forEach((option) => {
+ optionSearchParams.set(option.name.toLowerCase(), option.value);
+ });
+ const defaultUrl = createUrl(pathname, optionSearchParams);
+ router.replace(defaultUrl, { scroll: false });
+ }
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
- const optionUrl = createUrl(pathname, optionSearchParams);
+ const hasNoOptionsOrJustOneOption =
+ !options.length || (options.length === 1 && options[0]?.values.length === 1);
- // In order to determine if an option is available for sale, we need to:
- //
- // 1. Filter out all other param state
- // 2. Filter out invalid options
- // 3. Check if the option combination is available for sale
- //
- // This is the "magic" that will cross check possible variant combinations and preemptively
- // disable combinations that are not available. For example, if the color gray is only available in size medium,
- // then all other sizes should be disabled.
- const filtered = Array.from(optionSearchParams.entries()).filter(([key, value]) =>
- options.find(
- (option) => option.name.toLowerCase() === key && option.values.includes(value)
- )
- );
- const isAvailableForSale = combinations.find((combination) =>
- filtered.every(
- ([key, value]) => combination[key] === value && combination.availableForSale
- )
- );
+ if (hasNoOptionsOrJustOneOption) {
+ return null;
+ }
- // The option is active if it's in the url params.
- const isActive = searchParams.get(optionNameLowerCase) === value;
+ const openModal = () => setIsOpen(true);
+ const closeModal = () => setIsOpen(false);
- return (
-
- );
- })}
-