feat: change price by product variation

This commit is contained in:
paolosantarsiero 2024-12-29 18:43:35 +01:00
parent 87dd5ef8e8
commit e7be2b4695
5 changed files with 34 additions and 25 deletions

View File

@ -100,7 +100,7 @@ export default async function ProductPage(props: { params: Promise<{ name: strin
</Suspense> </Suspense>
)} )}
<Suspense fallback={null}> <Suspense fallback={null}>
<ProductDescription product={product} /> <ProductDescription product={product} variations={variations} />
</Suspense> </Suspense>
<AddToCart product={product} variations={variations}/> <AddToCart product={product} variations={variations}/>
</div> </div>

View File

@ -4,7 +4,6 @@ import { PlusIcon } from '@heroicons/react/24/outline';
import clsx from 'clsx'; import clsx from 'clsx';
import { useProduct } from 'components/product/product-context'; import { useProduct } from 'components/product/product-context';
import { Product, ProductVariations } from 'lib/woocomerce/models/product'; import { Product, ProductVariations } from 'lib/woocomerce/models/product';
import { useMemo } from 'react';
import { useCart } from './cart-context'; import { useCart } from './cart-context';
function SubmitButton({disabled = false}: {disabled: boolean}) { function SubmitButton({disabled = false}: {disabled: boolean}) {
@ -24,20 +23,8 @@ function SubmitButton({disabled = false}: {disabled: boolean}) {
export function AddToCart({ product, variations }: { product: Product, variations?: ProductVariations[] }) { export function AddToCart({ product, variations }: { product: Product, variations?: ProductVariations[] }) {
const { setNewCart } = useCart(); const { setNewCart } = useCart();
const { state } = useProduct(); const { state } = useProduct();
const productVariant = variations?.find((variation) => variation.id.toString() === state.variation);
const variation = productVariant?.attributes.map((attr) => ({ attribute: attr.name, value: attr.option })) || [];
const productVariant = useMemo(() => {
const keys = Object.keys(state).filter((key) => key !== 'id' && key !== 'image').map((key) => ({
attribute: key.toLowerCase(),
value: state[key]
}));
const productExist = variations?.find((variation) => {
const attributes = variation.attributes.map((attr) => ({name: attr.name, option: attr.option})) || [];
return attributes.every((attribute) => attribute.option === keys.find((key) => key.attribute === attribute.name)?.value);
});
return productExist ? keys : [];
}, [state, variations]);
return ( return (
<form <form
@ -46,7 +33,7 @@ export function AddToCart({ product, variations }: { product: Product, variation
const cart = await ( const cart = await (
await fetch('/api/cart', { await fetch('/api/cart', {
method: 'POST', method: 'POST',
body: JSON.stringify({ id: product.id, quantity: 1, variation: productVariant }) body: JSON.stringify({ id: product.id, quantity: 1, variation })
}) })
).json(); ).json();
setNewCart(cart); setNewCart(cart);
@ -55,7 +42,7 @@ export function AddToCart({ product, variations }: { product: Product, variation
} }
}} }}
> >
<SubmitButton disabled={variations?.length && !productVariant.length ? true : false}/> <SubmitButton disabled={variations?.length && !product ? true : false}/>
</form> </form>
); );
} }

View File

@ -7,12 +7,15 @@ type ProductState = {
[key: string]: string; [key: string]: string;
} & { } & {
image?: string; image?: string;
}; } & {
variation?: string;
}
type ProductContextType = { type ProductContextType = {
state: ProductState; state: ProductState;
updateOption: (name: string, value: string) => ProductState; updateOption: (name: string, value: string) => ProductState;
updateImage: (index: string) => ProductState; updateImage: (index: string) => ProductState;
updateVariation: (variation: string) => ProductState;
}; };
const ProductContext = createContext<ProductContextType | undefined>(undefined); const ProductContext = createContext<ProductContextType | undefined>(undefined);
@ -48,11 +51,18 @@ export function ProductProvider({ children }: { children: React.ReactNode }) {
return { ...state, ...newState }; return { ...state, ...newState };
}; };
const updateVariation = (variation: string) => {
const newState = { variation };
setOptimisticState(newState);
return { ...state, ...newState };
}
const value = useMemo( const value = useMemo(
() => ({ () => ({
state, state,
updateOption, updateOption,
updateImage, updateImage,
updateVariation
}), }),
[state] [state]
); );

View File

@ -1,14 +1,19 @@
'use client';
import Price from 'components/price'; import Price from 'components/price';
import Prose from 'components/prose'; import Prose from 'components/prose';
import { Product } from 'lib/woocomerce/models/product'; import { Product, ProductVariations } from 'lib/woocomerce/models/product';
import { useProduct } from './product-context';
export function ProductDescription({ product, variations }: { product: Product, variations?: ProductVariations[] }) {
const { state } = useProduct();
const productVariant = variations?.find((variation) => variation.id.toString() === state.variation);
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-6 flex flex-col border-b pb-6 dark:border-neutral-700">
<h1 className="mb-2 text-5xl font-medium">{product.name}</h1> <h1 className="mb-2 text-5xl font-medium">{product.name}</h1>
<div className="mr-auto w-auto rounded-full bg-blue-600 p-2 text-sm text-white"> <div className="mr-auto w-auto rounded-full bg-blue-600 p-2 text-sm text-white">
<Price amount={product.price} currencyCode="EUR" /> <Price amount={productVariant ? productVariant.price : product.price} currencyCode="EUR" />
</div> </div>
</div> </div>
{product.description ? ( {product.description ? (

View File

@ -35,12 +35,19 @@ export function VariantSelector({
const optionNameLowerCase = option?.name?.toLowerCase(); const optionNameLowerCase = option?.name?.toLowerCase();
// The option is active if it's in the selected options. // The option is active if it's in the selected options.
const isActive = optionNameLowerCase ? state[optionNameLowerCase] === value : false; const isActive = optionNameLowerCase ? state[optionNameLowerCase] === value : false;
if (!optionNameLowerCase) return null;
return ( return (
<button <button
formAction={() => { formAction={() => {
const newState = updateOption(optionNameLowerCase, value); if (!optionNameLowerCase) return;
let newState = updateOption(optionNameLowerCase, value);
const keys = Object.keys(newState).filter((key) => key !== 'id' && key !== 'image' && key !== 'variation');
const variant = variations.find((variation) => {
return variation?.attributes?.every((attr) => attr.name && keys.includes(attr.name) && newState[attr.name] === attr.option);
})?.id?.toString();
if (variant) {
newState = {...newState, variation: variant};
}
updateURL(newState); updateURL(newState);
}} }}
key={value} key={value}