Skip to content

Commit

Permalink
building out cart update endpoint with edge cases • typing enhancements
Browse files Browse the repository at this point in the history
  • Loading branch information
AnselmMarie committed Sep 8, 2024
1 parent 60f0479 commit 2aa2e7f
Show file tree
Hide file tree
Showing 22 changed files with 184 additions and 53 deletions.
2 changes: 1 addition & 1 deletion apps/api/src/api/v1/cart/controllers/cart.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const getCartController = async (req: Request<null, null, null, null>, res: Resp
};

interface CartBodyProps {
id: any;
id: string;
}

const updateCartController = async (
Expand Down
8 changes: 5 additions & 3 deletions apps/api/src/api/v1/cart/database/cart.database.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import { CartDataApi } from '@pokemon-pet-shop/typing';

export let cartDatabase = { data: [], total: 0, counter: 0 };

export const getCartDataCall = async () => {
export const getCartDataCall = async (): Promise<CartDataApi> => {
return new Promise((resolve) => {
setTimeout(() => {
resolve(cartDatabase);
}, 500);
});
};

export const updateCartDataCall = async ({ data, counter, total }) => {
export const updateCartDataCall = async ({ data, counter, total }): Promise<CartDataApi> => {
return new Promise((resolve) => {
setTimeout(() => {
cartDatabase.data = data;
Expand All @@ -19,7 +21,7 @@ export const updateCartDataCall = async ({ data, counter, total }) => {
});
};

export const clearCartDataCall = async () => {
export const clearCartDataCall = async (): Promise<CartDataApi> => {
return new Promise((resolve) => {
setTimeout(() => {
cartDatabase = { data: [], total: 0, counter: 0 };
Expand Down
38 changes: 20 additions & 18 deletions apps/api/src/api/v1/cart/services/cart.service.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,16 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import { CartApiPayload, CartDataApi } from '@pokemon-pet-shop/typing';

import { errFormatResponseUtil } from '../../../../utils/err.format.response.util';
import { getPokemonSpeciesService } from '../../pokemon';
import { getPokemonDetailService } from '../../pokemon/services/pokemon.detail.service';
import { clearCartDataCall, getCartDataCall, updateCartDataCall } from '../database/cart.database';

interface CartDataProps {
id: string;
name: string;
price: number;
quantity: number;
image: string;
isLegendary: boolean;
isMythical: boolean;
}

const doesItemExistKeyFn = (data, id) => {
const cartLength = data.length;
let key = null;
let counter = null;
let total = null;
let total = 0;

for (let loop = 0; loop < cartLength; loop++) {
const el = data[loop];
Expand All @@ -26,8 +19,9 @@ const doesItemExistKeyFn = (data, id) => {
key = loop;
}
counter = counter + el?.quantity;
total = total + el?.price;
total = el?.price * el?.quantity + total;
}

return {
key,
counter,
Expand All @@ -51,16 +45,24 @@ const getSpeciesDetail = async (id: string) => {
});
};

const updateCartService = async (payload) => {
const currentCartData: any = await getCartDataCall();
const updateCartService = async (payload: CartApiPayload) => {
const currentCartData: CartDataApi = await getCartDataCall();
const data = currentCartData.data;
const { key, counter, total } = doesItemExistKeyFn(data, payload?.id);
const currentObj = data[key];

if (currentObj && payload.removeFromCart && currentObj.quantity === 1) {
throw errFormatResponseUtil({
status: 400,
statusText: `This action can't be completed with this endpoint.`,
message: 'We cannot complete this cart action at this moment.',
});
}

if (key !== null) {
const currentObj = data[key];
currentObj.quantity = currentObj.quantity + 1;
currentCartData.counter = counter + 1;
currentCartData.total = total + 500;
currentObj.quantity = payload.addToCart ? currentObj.quantity + 1 : currentObj.quantity - 1;
currentCartData.counter = payload.addToCart ? counter + 1 : counter - 1;
currentCartData.total = payload.addToCart ? total + 500 : total - 500;

return currentObj;
}
Expand Down
8 changes: 6 additions & 2 deletions apps/api/src/utils/err.format.response.util.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
export const errFormatResponseUtil = (err) => {
return { message: err?.statusText, status: err?.status };
return { message: err?.message, statusText: err?.statusText, status: err?.status };
};

export const errFormat500ResponseUtil = () => {
return { statusText: 'The server was unable to complete your request.', status: 500 };
return {
message: 'The server was unable to complete your request.',
statusText: '',
status: 500,
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
.cartContent {
padding: 15px;
height: calc(100% - 60px);
overflow-x: scroll;
}

.middleRow {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ReactElement } from 'react';

import { useGetCart } from '@pokemon-pet-shop/services';
import { CartDataContentApi } from '@pokemon-pet-shop/typing';
import {
UiElementLayout,
UiIcon,
Expand All @@ -12,14 +12,15 @@ import {
} from '@pokemon-pet-shop/ui';

import styles from './cart.modal.module.css';
import useCartModalLogic from './use.cart.modal.logic';

const CartModal = (): ReactElement => {
const { data } = useGetCart();
const { data, onHandleRemoveFromCart, onHandleAddToCart } = useCartModalLogic();

return (
<UiElementLayout className={styles.modal}>
<UiElementLayout className={styles.cartContent}>
{(data?.data || [])?.map((el: any, i: number) => {
{(data?.data || [])?.map((el: CartDataContentApi, i: number) => {
return (
<UiElementLayout key={i} className={styles?.cartWrapper}>
<UiElementLayout className={styles.cardContentRow}>
Expand All @@ -39,7 +40,19 @@ const CartModal = (): ReactElement => {
<UiTypography typographyType={TypographyTypeEnum.SPAN} className={styles.price}>
{el?.price}
</UiTypography>
<UiElementLayout>
<UiIcon
icon={IconTypeEnum.ICON_MINUS}
onClick={() => onHandleRemoveFromCart(el?.id)}
/>
<UiTypography>{el?.quantity}</UiTypography>
<UiIcon
icon={IconTypeEnum.ICON_PLUS}
onClick={() => onHandleAddToCart(el?.id)}
/>
</UiElementLayout>
</UiElementLayout>

<UiIcon icon={IconTypeEnum.ICON_TRASH} />
</UiElementLayout>

Expand Down
2 changes: 1 addition & 1 deletion libs/features/src/lib/cart/cart.modal/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
import UiCartModal from './cart.modal';
import UiCartModal from './cart.modal.view';

export { UiCartModal };
37 changes: 37 additions & 0 deletions libs/features/src/lib/cart/cart.modal/use.cart.modal.logic.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { useGetCart, useUpdateCart } from '@pokemon-pet-shop/services';
import { CartDataApi, GenericNonReturnType } from '@pokemon-pet-shop/typing';

interface UseCartModalReturn {
data: CartDataApi;
onHandleRemoveFromCart: GenericNonReturnType;
onHandleAddToCart: GenericNonReturnType;
}

const useCartModalLogic = (): UseCartModalReturn => {
const { data } = useGetCart();
const updateCartMutation = useUpdateCart();

const handleAddToCart = (id: string) => {
updateCartMutation.mutate({
id,
addToCart: true,
removeFromCart: false,
});
};

const handleRemoveFromCart = (id: string) => {
updateCartMutation.mutate({
id,
addToCart: false,
removeFromCart: true,
});
};

return {
data,
onHandleRemoveFromCart: handleRemoveFromCart,
onHandleAddToCart: handleAddToCart,
};
};

export default useCartModalLogic;
1 change: 0 additions & 1 deletion libs/features/src/lib/navigation/header/header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ const Header = () => {
data: data,
classNameShadow: '',
classNameModal: '',
isModalShown: false,
headlineType: ModalHeadlineTypeEnum.RELATIVE,
modalAlignment: AlignmentEnum.RIGHT,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ const PokemonCard = ({ data = {} }: CardProps): ReactElement => {
data: data,
classNameShadow: '',
classNameModal: '',
isModalShown: false,
headlineType: ModalHeadlineTypeEnum.ABSOLUTE,
modalAlignment: AlignmentEnum.CENTER,
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { useUpdateCart } from '@pokemon-pet-shop/services';
import { PokemonListApi } from '@pokemon-pet-shop/typing';
import { GenericNonReturnType, PokemonListApi } from '@pokemon-pet-shop/typing';

import { usePokemonTheme } from '../hooks/use.pokemone.theme.logic';

interface UseCardReturn {
getThemeClass: any;
onHandleUpdateCartSubmit: any;
onHandleUpdateCartSubmit: GenericNonReturnType;
}

const usePokemonCard = (data: PokemonListApi): UseCardReturn => {
Expand All @@ -15,7 +15,7 @@ const usePokemonCard = (data: PokemonListApi): UseCardReturn => {
const handleUpdateCartSubmit = () => {
updateCartMutation.mutate({
id: data?.id,
quantity: 1,
addToCart: true,
});
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { useGetPokemonSpecies, useUpdateCart } from '@pokemon-pet-shop/services';
import { PokemonListApi, PokemonSpeciesApi } from '@pokemon-pet-shop/typing';
import { GenericNonReturnType, PokemonListApi, PokemonSpeciesApi } from '@pokemon-pet-shop/typing';

interface PokemonDetailModalReturn {
speciesData: PokemonSpeciesApi;
isError: boolean;
isLoading: boolean;
isFetching: boolean;
onHandleUpdateCartSubmit: any;
onHandleUpdateCartSubmit: GenericNonReturnType;
}

const usePokemonDetailModalLogic = (modalData: PokemonListApi): PokemonDetailModalReturn => {
Expand All @@ -17,7 +17,7 @@ const usePokemonDetailModalLogic = (modalData: PokemonListApi): PokemonDetailMod
const handleUpdateCartSubmit = () => {
updateCartMutation.mutate({
id: modalData?.id,
quantity: 1,
addToCart: true,
});
};

Expand Down
20 changes: 5 additions & 15 deletions libs/store/src/lib/cart.store.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,20 @@
import { PokemonListApi, PokemonSpeciesApi } from '@pokemon-pet-shop/typing';
import { CartDataContentApi, PokemonListApi, PokemonSpeciesApi } from '@pokemon-pet-shop/typing';
import { create } from 'zustand';

interface CartDataProps {
interface CartDataApi {
modalData: PokemonListApi;
speciesData: PokemonSpeciesApi;
}

interface CartContentProps {
id: string;
name: string;
price: number;
quantity: number;
image: string;
isLegendary: boolean;
isMythical: boolean;
}

const doesItemExistFn = (currentData: Array<CartContentProps>, outsideData: PokemonListApi) => {
return currentData.find((el: CartContentProps) => {
const doesItemExistFn = (currentData: Array<CartDataContentApi>, outsideData: PokemonListApi) => {
return currentData.find((el: CartDataContentApi) => {
return String(outsideData?.id) === el?.id;
});
};

const cartStore = (set: any, get: any) => ({
cartContents: [],
addQuantity: ({ modalData, speciesData }: CartDataProps): void => {
addQuantity: ({ modalData, speciesData }: CartDataApi): void => {
const currentData = get().cartContents;
const doesItemExist = doesItemExistFn(currentData, modalData);

Expand Down
3 changes: 1 addition & 2 deletions libs/store/src/lib/modal.store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ interface ModalOptions {
title?: string;
data?: unknown;
headlineType?: ModalHeadlineTypeEnum;
modalAlignment?: any;
modalAlignment?: AlignmentEnum;
}

interface setModalStoreProps {
Expand All @@ -31,7 +31,6 @@ const modalStore = (set: any) => ({
modalOptions: {
classNameShadow: '',
classNameModal: '',
isModalShown: false,
title: '',
data: null,
headlineType: ModalHeadlineTypeEnum.RELATIVE,
Expand Down
1 change: 1 addition & 0 deletions libs/typing/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@ export * from './lib-base/interface';
export * from './lib-base/interface/pokemon.list.api.interface';
export * from './lib-base/interface/pokemon.species.api.interface';
export * from './lib-base/interface/pokemon.detail.api.interface';
export * from './lib-base/interface/cart.api.interface';
export * from './lib-base/types';
export * from './lib-base/enum';
21 changes: 21 additions & 0 deletions libs/typing/src/lib-base/interface/cart.api.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
export interface CartApiPayload {
id: string;
addToCart?: boolean;
removeFromCart?: boolean;
}

export interface CartDataContentApi {
id: string;
name: string;
price: number;
quantity: number;
image: string;
isLegendary: boolean;
isMythical: boolean;
}

export interface CartDataApi {
data: CartDataContentApi[];
counter: number;
total: number;
}
2 changes: 2 additions & 0 deletions libs/ui/src/lib-base/ui/icon/icon.enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,6 @@ export enum IconTypeEnum {
ICON_X = 'IconX',
ICON_MENU = 'IconMenu',
ICON_TRASH = 'IconTrash',
ICON_MINUS = 'IconMinus',
ICON_PLUS = 'IconPlus',
}
4 changes: 4 additions & 0 deletions libs/ui/src/lib-base/ui/icon/icon.util.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { IconCaretDown } from './icons/icon.caret.down';
import { IconMenu } from './icons/icon.menu';
import { IconMinus } from './icons/icon.minus';
import { IconMoon } from './icons/icon.moon';
import { IconPlus } from './icons/icon.plus';
import { IconSearch } from './icons/icon.search';
import { IconShoppingCart } from './icons/icon.shopping.cart';
import { IconSun } from './icons/icon.sun';
Expand All @@ -16,4 +18,6 @@ export const IconMap = {
IconMenu,
IconShoppingCart,
IconTrash,
IconMinus,
IconPlus,
};
Loading

0 comments on commit 2aa2e7f

Please sign in to comment.