commerce/components/product/variant-selector.tsx
Samantha Kellow 08b3b820e4 Squashed commit of the following:
commit b10e930476d8bbe82f679f3ae8ed44cea733898f
Merge: a2c34aa 8531476
Author: Sammii <sammii.h@icloud.com>
Date:   Wed Apr 24 20:05:09 2024 +0100

    Merge pull request #1 from Sammii-HK/feat/restyle-store

    Feat/restyle store

commit 85314765721447f5612cb59b7e668653606e7b7b
Author: Samantha Kellow <sammii.h@icloud.com>
Date:   Wed Apr 24 20:03:12 2024 +0100

    responsive design app page carousel

commit d5563a7355c2581c29ebf3f11799e1047c0c29dd
Author: Samantha Kellow <sammii.h@icloud.com>
Date:   Wed Apr 24 19:57:47 2024 +0100

    updating mobile carousel tile size

commit 9976d0d427ea92dab510bface5da9a4e89feabee
Author: Samantha Kellow <sammii.h@icloud.com>
Date:   Wed Apr 24 19:30:07 2024 +0100

    updating combox import

commit f6a365df8239d26d5feb37cf53d43632ab7c90c4
Author: Samantha Kellow <sammii.h@icloud.com>
Date:   Wed Apr 24 19:16:08 2024 +0100

    styling label price

commit cf89e3b0648bb07ed038f872183bd7ecea064df0
Author: Samantha Kellow <sammii.h@icloud.com>
Date:   Wed Apr 24 19:08:16 2024 +0100

    styling accordians and drop downs

commit 49f3776ba8894d5fd2aa6b2fea0632f73d6ae093
Author: Samantha Kellow <sammii.h@icloud.com>
Date:   Wed Apr 24 18:53:14 2024 +0100

    updating search and carousel styling

commit 7666a25f918a1b53a088dd9240077af5b9013712
Author: Samantha Kellow <sammii.h@icloud.com>
Date:   Wed Apr 24 18:37:29 2024 +0100

    styling product description

commit b8280101ad367abfe48aed4fee8ef32ed1fd5d67
Author: Samantha Kellow <sammii.h@icloud.com>
Date:   Wed Apr 24 18:18:11 2024 +0100

    removing `-full` from all rounded elements

commit 77480edd79327898aa2c417d34d231db1d593736
Author: Samantha Kellow <sammii.h@icloud.com>
Date:   Wed Apr 24 18:15:27 2024 +0100

    styling cart component

commit 3ec0c4d5679b20c66442e5002225cbefe927a13e
Author: Samantha Kellow <sammii.h@icloud.com>
Date:   Wed Apr 24 18:06:53 2024 +0100

    updating styling

commit efcab218934934edea629406b823fd2b2ac3d193
Author: Samantha Kellow <sammii.h@icloud.com>
Date:   Wed Apr 24 18:06:43 2024 +0100

    updating carousel for correct types

commit b14934af8489a6294f1c47a3a41d40d0a7565550
Author: Samantha Kellow <sammii.h@icloud.com>
Date:   Wed Apr 24 17:57:31 2024 +0100

    styling tile

commit 0c72e76bad994317d5e39faedfc4aade9a368fe7
Author: Samantha Kellow <sammii.h@icloud.com>
Date:   Wed Apr 24 17:56:50 2024 +0100

    styling label

commit ca24a30543ddfbdd345ac405b14e0541a9af1020
Author: Samantha Kellow <sammii.h@icloud.com>
Date:   Wed Apr 24 17:54:54 2024 +0100

    update main app layout for dark view

commit 57c1c4cac9a15c2091f652f53f7bb85a6a5b613a
Author: Samantha Kellow <sammii.h@icloud.com>
Date:   Wed Apr 24 17:52:53 2024 +0100

    commenting out weight li on description content

commit d2f0ac2041b04117e24db0316b470646489aca07
Author: Samantha Kellow <sammii.h@icloud.com>
Date:   Wed Apr 24 17:52:02 2024 +0100

    updating carousel to new style

commit 545717006d7f25f36e71b67873d01316aeeafcf9
Author: Samantha Kellow <sammii.h@icloud.com>
Date:   Wed Apr 24 17:50:55 2024 +0100

    updating getAllLiveProducts to handle no param

commit 0bf97ecdf69b4f7a4aa00c090e09e613fa1aba8a
Author: Samantha Kellow <sammii.h@icloud.com>
Date:   Wed Apr 24 17:50:39 2024 +0100

    styling and adjusting footer & navbar

commit 18200b4e848affe01ca9014bdf8ac841ce176114
Author: Samantha Kellow <sammii.h@icloud.com>
Date:   Wed Apr 24 17:50:00 2024 +0100

    updating cart to bag

commit 82c30cdda8d38b3111b539a34127155bb82612d5
Author: Samantha Kellow <sammii.h@icloud.com>
Date:   Wed Apr 24 17:48:32 2024 +0100

    SKUs page file check

commit e88a45b9d51096b3250062708d5ef1a49138258c
Author: Samantha Kellow <sammii.h@icloud.com>
Date:   Wed Apr 24 17:47:30 2024 +0100

    updatibng Prose import path

commit 5e81519f983e58758d865b2905f279f34afb67a4
Author: Samantha Kellow <sammii.h@icloud.com>
Date:   Wed Apr 24 17:45:58 2024 +0100

    updating app page metadata

commit 9c9a5c035b245d8811a601ba2dd134c5c5f124d7
Author: Samantha Kellow <sammii.h@icloud.com>
Date:   Tue Apr 23 21:25:05 2024 +0100

    making borders muted

commit 8a40e08e41a7fdad172e5ffaebb7f791ec66f4e5
Author: Samantha Kellow <sammii.h@icloud.com>
Date:   Tue Apr 23 20:37:35 2024 +0100

    moving ui components to own folder 🧹
2024-04-25 14:58:44 +01:00

148 lines
5.6 KiB
TypeScript

'use client';
import clsx from 'clsx';
import { Money, ProductOption, ProductVariant } from 'lib/shopify/types';
import { createUrl } from 'lib/utils';
import Link from 'next/link';
import { usePathname, useRouter, useSearchParams } from 'next/navigation';
import { useEffect } from 'react';
type ParamsMap = {
[key: string]: string; // ie. { color: 'Red', size: 'Large', ... }
};
type OptimizedVariant = {
id: string;
availableForSale: boolean;
params: URLSearchParams;
price: Money;
[key: string]: string | boolean | URLSearchParams | Money; // ie. { color: 'Red', size: 'Large', ... }
};
export function VariantSelector({
options,
variants,
setSelectedVariant,
}: {
options: ProductOption[];
variants: ProductVariant[];
setSelectedVariant: (value: OptimizedVariant | undefined) => void,
}) {
const pathname = usePathname();
const currentParams = useSearchParams();
const router = useRouter();
const hasNoOptionsOrJustOneOption =
!options.length || (options.length === 1 && options[0]?.values.length === 1);
// Discard any unexpected options or values from url and create params map.
const paramsMap: ParamsMap = Object.fromEntries(
Array.from(currentParams.entries()).filter(([key, value]) =>
options.find((option) => option.name.toLowerCase() === key && option.values.includes(value))
)
);
// Optimize variants for easier lookups.
const optimizedVariants: OptimizedVariant[] = variants.map((variant) => {
const optimized: OptimizedVariant = {
id: variant.id,
availableForSale: variant.availableForSale,
params: new URLSearchParams(),
price: variant.price,
};
variant.selectedOptions.forEach((selectedOption) => {
const name = selectedOption.name.toLowerCase();
const value = selectedOption.value;
optimized[name] = value;
optimized.params.set(name, value);
});
return optimized;
});
// Find the first variant that is:
//
// 1. Available for sale
// 2. Matches all options specified in the url (note that this
// could be a partial match if some options are missing from the url).
//
// If no match (full or partial) is found, use the first variant that is
// available for sale.
const selectedVariant: OptimizedVariant | undefined =
optimizedVariants.find(
(variant) =>
variant.availableForSale &&
Object.entries(paramsMap).every(([key, value]) => variant[key] === value)
) || optimizedVariants.find((variant) => variant.availableForSale);
const selectedVariantParams = new URLSearchParams(selectedVariant?.params);
const currentUrl = createUrl(pathname, currentParams);
const selectedVariantUrl = createUrl(pathname, selectedVariantParams);
useEffect(() => {
setSelectedVariant(selectedVariant);
}, [selectedVariantUrl]);
if (hasNoOptionsOrJustOneOption) {
return null;
}
if (currentUrl !== selectedVariantUrl) {
router.replace(selectedVariantUrl);
}
return options.map((option) => (
<dl className="mb-8" key={option.id}>
<dt className="mb-4 text-sm uppercase tracking-wide">{option.name}</dt>
<dd className="flex flex-wrap gap-3">
{option.values.map((value) => {
// Base option params on selected variant params.
const optionParams = new URLSearchParams(selectedVariantParams);
// Update the params using the current option to reflect how the url would change.
optionParams.set(option.name.toLowerCase(), value);
const optionUrl = createUrl(pathname, optionParams);
// The option is active if it in the url params.
const isActive = selectedVariantParams.get(option.name.toLowerCase()) === value;
// The option is available for sale if it fully matches the variant in the option's url params.
// It's super important to note that this is the options params, *not* the selected variant's params.
// This is the "magic" that will cross check possible future variant combinations and preemptively
// disable combinations that are not possible.
const isAvailableForSale = optimizedVariants.find((a) =>
Array.from(optionParams.entries()).every(([key, value]) => a[key] === value)
)?.availableForSale;
const DynamicTag = isAvailableForSale ? Link : 'p';
const dynamicProps = {
...(isAvailableForSale && { scroll: false })
};
return (
<DynamicTag
key={value}
aria-disabled={!isAvailableForSale}
href={optionUrl}
title={`${option.name} ${value}${!isAvailableForSale ? ' (Out of Stock)' : ''}`}
className={clsx(
'flex min-w-[48px] items-center justify-center rounded border border-neutral-600 px-2 py-1 text-sm text-neutral-200 dark:border-neutral-800 dark:bg-neutral-900',
{
'cursor-default ring-1 ring-gray-600 bg-neutral-300 text-neutral-900': isActive,
'ring-1 ring-transparent transition duration-300 ease-in-out hover:scale-110 hover:ring-gray-600 ':
!isActive && isAvailableForSale,
'relative z-10 cursor-not-allowed overflow-hidden bg-neutral-100 text-neutral-500 ring-1 ring-neutral-300 before:absolute before:inset-x-0 before:-z-10 before:h-px before:-rotate-45 before:bg-neutral-300 before:transition-transform dark:bg-neutral-900 dark:text-neutral-400 dark:ring-neutral-700 before:dark:bg-neutral-700':
!isAvailableForSale
}
)}
{...dynamicProps}
>
{value}
</DynamicTag>
);
})}
</dd>
</dl>
));
}