Skip to content
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
d3c84ba
Add id and stock_level in VariantTransformer
noelreissig Jul 5, 2023
936faf6
Set chosenVariant in productStore to get the stock of the selected va…
noelreissig Jul 7, 2023
5be2f6f
Add info to chosenVariant and improve code explanation
noelreissig Jul 7, 2023
b903899
Control input buttons (arrow) as per stock and availability
noelreissig Jul 7, 2023
9fd8378
Control add to cart quantity vs. stock
noelreissig Jul 8, 2023
85a611f
Modify `Coming Soon` with `Out of stock`
noelreissig Jul 8, 2023
a818c79
Reviews functionality (#173)
FelipeCabreraB Jul 10, 2023
68950d4
Update ProductProp for matching options with variants by id
noelreissig Jul 10, 2023
8c87845
Temp - Working stock logic
JoaquinEtchegaray Jul 14, 2023
de55056
Fix TS issues
JoaquinEtchegaray Jul 19, 2023
fa42c98
Set the button as disable when Out of Stock
noelreissig Jul 19, 2023
3361086
Add id and stock_level in VariantTransformer
noelreissig Jul 5, 2023
81d7783
Set chosenVariant in productStore to get the stock of the selected va…
noelreissig Jul 7, 2023
631e72a
Add info to chosenVariant and improve code explanation
noelreissig Jul 7, 2023
d104ae6
Control input buttons (arrow) as per stock and availability
noelreissig Jul 7, 2023
4202615
Control add to cart quantity vs. stock
noelreissig Jul 8, 2023
c7ad5c1
Modify `Coming Soon` with `Out of stock`
noelreissig Jul 8, 2023
c6bb375
Update ProductProp for matching options with variants by id
noelreissig Jul 10, 2023
fdb035f
Temp - Working stock logic
JoaquinEtchegaray Jul 14, 2023
5520548
Fix TS issues
JoaquinEtchegaray Jul 19, 2023
8cb2c6f
Set the button as disable when Out of Stock
noelreissig Jul 19, 2023
2611d47
Merge branch 'variants-and-stock-refactor' of https://github.com/Comm…
JoaquinEtchegaray Jul 20, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/_components/Globals/Cart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ const Cart = ({ isCartOpen, setIsCartOpen }: Props) => {
</div>
{/* products sections */}
<div className="overflow-y-auto px-7 mb-auto">
{cart?.items.length === 0 || cart === null ? (
{cart?.items?.length === 0 || cart === null ? (
<p>There are no items in your cart yet!</p>
) : (
<>
Expand Down
20 changes: 17 additions & 3 deletions app/_hooks/useStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,25 @@ export function useGlobalState() {
****************************************************************************/
type StateProduct = {
chosenOptions: { [key: string]: string };
chosenOptionsId: string[];
chosenVariant: {
variantLabel: string | undefined;
variantId: string | undefined;
variantActive: boolean | undefined;
variantStock: number | undefined;
};
};

const stateProduct = atom({
chosenOptions: {}
const stateProduct = atom<StateProduct>({
chosenOptions: {},
chosenOptionsId: [],
chosenVariant: { variantId: '', variantActive: true, variantLabel: '', variantStock: 0 }
});

export function useProductState(): {
productState: StateProduct;
updateProductProp: (property: string, value: unknown) => void;
updateProductState: (newState: StateProduct) => void;
} {
const [productState, setProductState] = useAtom(stateProduct);

Expand All @@ -36,7 +46,11 @@ export function useProductState(): {
});
};

return { productState, updateProductProp };
const updateProductState = (newState: StateProduct) => {
setProductState(newState);
};

return { productState, updateProductProp, updateProductState };
}

/*****************************************************************************
Expand Down
6 changes: 4 additions & 2 deletions app/_lib/Store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ class Store {
* Convert Swell product options to generic format
****************************************************************************/
transformProductOptions(product: SwellProduct) {
return product.options.map((option) => ({
return product.options?.map((option) => ({
label: option.name,
active: option.active,
values: option.values
Expand All @@ -139,9 +139,11 @@ class Store {
****************************************************************************/
transformProductVariants(product: SwellProduct) {
return product.variants?.results?.map((variant) => ({
id: variant.id,
name: variant.name,
active: variant.active,
value_ids: variant.option_value_ids
value_ids: variant.option_value_ids,
stock_variant: variant.stock_level
}));
}

Expand Down
5 changes: 4 additions & 1 deletion app/_types/Generic/Products.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,12 @@ interface ProductOption {
}

interface Variant {
id: string;
name: string;
active: boolean;
value_ids: string[];
value_ids: Record<string, string> | string[];
stock_variant: number;
variantActive: boolean;
}

interface GenericProductsList {
Expand Down
1 change: 1 addition & 0 deletions app/_types/Swell/Product.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,4 +103,5 @@ interface SwellVariant {
date_updated: string;
sku?: string;
id: string;
stock_level: number;
}
2 changes: 2 additions & 0 deletions app/_types/Swell/swell-js_index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ declare module 'swell-js' {
variant: { name: string; id: string };
taxTotal: number;
product: Product;
variant_id: string;
}

export interface CartItemSnakeCase {
Expand All @@ -120,6 +121,7 @@ declare module 'swell-js' {
variant: { name: string; id: string };
tax_total: number;
product: Product;
variant_id: string;
}

export type CartItem = CartItemCamelCase | CartItemSnakeCase;
Expand Down
84 changes: 76 additions & 8 deletions app/products/[slug]/_components/ProductInfo/AddToCart.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use client';

import React, { useEffect, useState } from 'react';

import { IoIosArrowDown, IoIosArrowUp } from 'react-icons/io';

import 'react-toastify/dist/ReactToastify.css';
Expand Down Expand Up @@ -30,21 +31,37 @@ const AddToCart = ({ product, isAuthenticated }: ProductProp) => {
const [productAmount, setProductAmount] = useState(1);
const [pleaseSelectAllOptions, setPleaseSelectAllOptions] = useState('');
const [isSubmitting, setIsSubmitting] = useState(false);
const [isDisabled, setIsDisabled] = useState(false);

const { state } = useStore();
const { productState } = useProductState();
const { setCart } = useGlobalState();
const { cart, setCart } = useGlobalState();

const { chosenOptions, chosenVariant } = productState;

const selectedStock =
// if there is variant information, we use the variant stock, otherwise we use the general product stock
product.variants && product.variants?.length > 0 ? chosenVariant?.variantStock : product.stock;

const { chosenOptions } = productState;
const productHasVariant = product.variants && product.variants?.length > 0;

useEffect(() => {
product.options?.length === Object.keys(chosenOptions).length;
product.options?.length === Object.keys(chosenOptions).length && setPleaseSelectAllOptions('');

setProductAmount(1);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [chosenOptions, product.options?.length]);

useEffect(() => {
setIsDisabled(productAmount === selectedStock);

// eslint-disable-next-line react-hooks/exhaustive-deps
}, [product, productAmount, setProductAmount]);

const addProduct = async ({ product, quantity, toastifyMessage }: AddProductProps) => {
// Turn on spinner while waiting
setIsSubmitting(true);

// Add product to cart on Swell
await swell.cart
.addItem({
Expand Down Expand Up @@ -75,6 +92,26 @@ const AddToCart = ({ product, isAuthenticated }: ProductProp) => {
};

const handleAddToCart = () => {
// to check if the product (with no variant) is in the cart
const productInCart = cart && cart?.items?.find((item) => item.product_id.includes(product.id));
// to check if the variant is in the cart
const variantInCart =
cart &&
cart?.items?.find((item) =>
item.variant_id?.includes(chosenVariant.variantId ? chosenVariant.variantId : '')
);

// to check the stock of the product in the cart
if (productInCart && product.stock && productInCart?.quantity + productAmount > product.stock) {
return notifyFailure("The amount selected exceeds the stock available. We're sorry.");
}

const variantStock = chosenVariant.variantStock ?? 0;

if (variantInCart && variantInCart?.quantity + productAmount > variantStock) {
return notifyFailure("The amount selected exceeds the stock available. We're sorry.");
}

!isSubmitting &&
void addProduct({
product: product,
Expand All @@ -85,7 +122,7 @@ const AddToCart = ({ product, isAuthenticated }: ProductProp) => {

const buttonLabel = () => {
if (product.stock === 0) {
return 'COMING SOON!';
return 'OUT OF STOCK';
}
if (isSubmitting) {
return <Spinner size={6} />;
Expand All @@ -96,6 +133,15 @@ const AddToCart = ({ product, isAuthenticated }: ProductProp) => {
return 'UNAVAILABLE';
};

const handleProductAmount = () => {
if (selectedStock) {
if (productAmount < selectedStock) {
setProductAmount(productAmount + 1);
}
// if there is no stock information, there is no limit
} else setProductAmount(productAmount + 1);
};

return (
<>
<div className="flex flex-wrap gap-4 py-5">
Expand All @@ -110,14 +156,32 @@ const AddToCart = ({ product, isAuthenticated }: ProductProp) => {
/>
<div className="flex flex-col">
<button
className="bg-gray hover:bg-gray-medium border border-gray border-b-0 hover:border-gray-300 p-1"
onClick={() => setProductAmount(productAmount + 1)}
className={`bg-gray hover:bg-gray-medium border border-gray border-b-0 hover:border-gray-300 p-1 ${
isDisabled ||
product.stock === 0 ||
(productHasVariant && !state.isVariantActive) ||
isSubmitting
? 'opacity-50 hover:bg-gray hover:border-gray-300'
: ''
}`}
disabled={
isDisabled ||
product.stock === 0 ||
(productHasVariant && !state.isVariantActive) ||
isSubmitting
}
onClick={() => handleProductAmount()}
>
<IoIosArrowUp />
</button>
<button
onClick={() => productAmount > 1 && setProductAmount(productAmount - 1)}
className="bg-gray hover:bg-gray-medium border border-t-0 border-gray hover:border-gray-300 p-1"
disabled={productHasVariant && !state.isVariantActive}
className={`bg-gray hover:bg-gray-medium border border-t-0 border-gray hover:border-gray-300 p-1 ${
productHasVariant && !state.isVariantActive
? 'opacity-50 hover:bg-gray hover:border-gray-300'
: ''
}`}
>
<IoIosArrowDown />
</button>
Expand All @@ -128,8 +192,12 @@ const AddToCart = ({ product, isAuthenticated }: ProductProp) => {
onClick={() => handleAddToCart()}
label={buttonLabel()}
variant="fill"
disabled={
product.stock === 0 || (productHasVariant && !state.isVariantActive) || isSubmitting
}
className={`font-bold py-3 px-5 md:min-w-[240px] ${
state.isVariantActive || (product.options?.length === 0 && product.stock !== 0)
(state.isVariantActive && productHasVariant) ||
(product.options?.length === 0 && product?.stock !== 0)
? 'bg-black font-quicksand border text-white duration-200 cursor-pointer hover:bg-white hover:text-black'
: 'bg-gray-medium text-white font-quicksand border border-gray-medium'
}`}
Expand Down
3 changes: 2 additions & 1 deletion app/products/[slug]/_components/ProductInfo/ProductInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,12 @@ interface ProductProp {

const ProductInfo = async ({ product }: ProductProp) => {
const auth = await isAuthenticated();
const productHasVariant = product.variants && product.variants?.length > 0;

return (
<div className="w-full space-y-2 mt-5 md:mt-0">
<ProductTitle title={product.name} />
<ProductStock stock={product.stock} />
{!productHasVariant && <ProductStock stock={product.stock} />}
<ProductRating rating={3} />
<ProductPriceOptions price={product.price} salePrice={product.salePrice} />
<ProductOptions product={product} />
Expand Down
Loading