From 4af5653aa693e37b3c37e887ee1570216a3cb99d Mon Sep 17 00:00:00 2001 From: Ilya Shcherbakov <86749581+LikeKugi@users.noreply.github.com> Date: Thu, 31 Aug 2023 22:21:32 +0300 Subject: [PATCH 1/5] feat: add api for taxes and taxes slice --- e-commerce-app/src/api/taxApi.ts | 24 +++++++++++++ .../CategoriesQuery/CategoriesQuery.tsx | 29 +++++++++------ e-commerce-app/src/store/slices/taxesSlice.ts | 36 +++++++++++++++++++ e-commerce-app/src/store/store.ts | 5 +++ e-commerce-app/src/types/addressesTypes.ts | 2 ++ .../src/types/slicesTypes/taxApiTypes.ts | 15 ++++++++ .../src/types/slicesTypes/taxSliceTypes.ts | 10 ++++++ 7 files changed, 110 insertions(+), 11 deletions(-) create mode 100644 e-commerce-app/src/api/taxApi.ts create mode 100644 e-commerce-app/src/store/slices/taxesSlice.ts create mode 100644 e-commerce-app/src/types/slicesTypes/taxApiTypes.ts create mode 100644 e-commerce-app/src/types/slicesTypes/taxSliceTypes.ts diff --git a/e-commerce-app/src/api/taxApi.ts b/e-commerce-app/src/api/taxApi.ts new file mode 100644 index 0000000..cee26f0 --- /dev/null +++ b/e-commerce-app/src/api/taxApi.ts @@ -0,0 +1,24 @@ +import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'; +import { IGetAllTaxesResponse } from '../types/slicesTypes/taxApiTypes'; + +export const taxApi = createApi({ + reducerPath: 'taxApi', + baseQuery: fetchBaseQuery({ + baseUrl: `${process.env.REACT_APP_CTP_API_URL}/${process.env.REACT_APP_CTP_PROJECT_KEY}/tax-categories`, + }), + endpoints: (build) => ({ + getAllTaxes: build.query({ + query(token: string) { + return { + url: '', + method: 'GET', + headers: { + Authorization: `Bearer ${token}`, + }, + }; + } + }) + }) +}); + +export const { useGetAllTaxesQuery } = taxApi; diff --git a/e-commerce-app/src/requestsComponents/CategoriesQuery/CategoriesQuery.tsx b/e-commerce-app/src/requestsComponents/CategoriesQuery/CategoriesQuery.tsx index 204567a..f19c90d 100644 --- a/e-commerce-app/src/requestsComponents/CategoriesQuery/CategoriesQuery.tsx +++ b/e-commerce-app/src/requestsComponents/CategoriesQuery/CategoriesQuery.tsx @@ -5,28 +5,35 @@ import LoadingProgress from '../../components/LoadingProgress/LoadingProgress'; import { Outlet } from 'react-router-dom'; import { useGetAllCategoriesQuery } from '../../api/categoriesApi'; import { setCategories } from '../../store/slices/categoriesSlice'; +import { useGetAllTaxesQuery } from '../../api/taxApi'; +import { setTaxes } from '../../store/slices/taxesSlice'; const CategoriesQuery = (): JSX.Element => { const accessToken = useAppSelector(getAccessToken); const dispatch = useAppDispatch(); - const { isLoading, isError, isSuccess, data } = useGetAllCategoriesQuery(accessToken as string); + const { isLoading: isLoadingCategories, isError: isErrorCategories, isSuccess: isSuccessCategories, data: dataCategories } = useGetAllCategoriesQuery(accessToken as string); + const {isLoading: isLoadingTaxes, isError: isErrorTaxes, isSuccess: isSuccessTaxes, data: dataTaxes} = useGetAllTaxesQuery(accessToken as string); useEffect(() => { - if (!isSuccess) return; - if (data && 'results' in data) { - dispatch(setCategories(data)); + if (!isSuccessCategories) return; + if (dataCategories && 'results' in dataCategories) { + dispatch(setCategories(dataCategories)); } - }, [isSuccess, data]); + }, [isSuccessCategories, dataCategories]); - if (isLoading || isError) { + useEffect(() => { + if (!isSuccessCategories) return; + if (dataTaxes && 'results' in dataTaxes) { + dispatch(setTaxes(dataTaxes)); + } + }, [isSuccessTaxes, dataTaxes]); + + + if (isLoadingCategories || isErrorCategories || isLoadingTaxes || isErrorTaxes) { return ; } - return ( - <> - - - ); + return ; }; export default CategoriesQuery; diff --git a/e-commerce-app/src/store/slices/taxesSlice.ts b/e-commerce-app/src/store/slices/taxesSlice.ts new file mode 100644 index 0000000..66c9be9 --- /dev/null +++ b/e-commerce-app/src/store/slices/taxesSlice.ts @@ -0,0 +1,36 @@ +import { ITaxFromSlice } from '../../types/slicesTypes/taxSliceTypes'; +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; +import { IGetAllTaxesResponse } from '../../types/slicesTypes/taxApiTypes'; +import { RootStateType } from '../store'; + +const initialState: ITaxFromSlice = { + fetching: false, + limit: 0, + count: 0, + offset: 0, + total: 0, + taxes: [], +}; + +export const taxesSlice = createSlice({ + initialState, + name: 'taxesSlice', + reducers: { + startLoadingTaxes: (state) => { + state.fetching = true; + }, + setTaxes: (state, action: PayloadAction) => { + state.total = action.payload.total; + state.count = action.payload.count; + state.offset = action.payload.offset; + state.limit = action.payload.limit; + state.taxes = action.payload.results; + state.fetching = false; + } + } +}); + + +export const getTaxes = (state: RootStateType) => state.taxes.taxes; +export const TaxesReducer = taxesSlice.reducer; +export const { startLoadingTaxes, setTaxes } = taxesSlice.actions; diff --git a/e-commerce-app/src/store/store.ts b/e-commerce-app/src/store/store.ts index cd0568d..581afb0 100644 --- a/e-commerce-app/src/store/store.ts +++ b/e-commerce-app/src/store/store.ts @@ -6,6 +6,8 @@ import { productsApi } from '../api/productsApi'; import { categoriesApi } from '../api/categoriesApi'; import { CategoriesReducer } from './slices/categoriesSlice'; import { ProductsReducer } from './slices/productsSlice'; +import { taxApi } from '../api/taxApi'; +import { TaxesReducer } from './slices/taxesSlice'; export const store = configureStore({ reducer: { @@ -13,9 +15,11 @@ export const store = configureStore({ [myCustomerApi.reducerPath]: myCustomerApi.reducer, [productsApi.reducerPath]: productsApi.reducer, [categoriesApi.reducerPath]: categoriesApi.reducer, + [taxApi.reducerPath]: taxApi.reducer, user: UserReducer, categories: CategoriesReducer, products: ProductsReducer, + taxes: TaxesReducer, }, middleware: (getDefaultMiddleware) => getDefaultMiddleware({}).concat([ @@ -23,6 +27,7 @@ export const store = configureStore({ myCustomerApi.middleware, productsApi.middleware, categoriesApi.middleware, + taxApi.middleware, ]), }); diff --git a/e-commerce-app/src/types/addressesTypes.ts b/e-commerce-app/src/types/addressesTypes.ts index f1d9c70..436947d 100644 --- a/e-commerce-app/src/types/addressesTypes.ts +++ b/e-commerce-app/src/types/addressesTypes.ts @@ -8,3 +8,5 @@ export interface IMyCustomerApiAddressRequest { export interface IMyCustomerAddressResponse extends IMyCustomerApiAddressRequest { id: string; } + +export type CountriesType = 'US' | 'DE' | 'CA' | 'PL' | string; diff --git a/e-commerce-app/src/types/slicesTypes/taxApiTypes.ts b/e-commerce-app/src/types/slicesTypes/taxApiTypes.ts new file mode 100644 index 0000000..3ca84b0 --- /dev/null +++ b/e-commerce-app/src/types/slicesTypes/taxApiTypes.ts @@ -0,0 +1,15 @@ +import { CountriesType } from '../addressesTypes'; +import { IBaseGetAllQueryResponse } from './baseApiResponsesTypes'; + +export interface ITaxApiResponse { + amount: number, + country: CountriesType + id: string + includedInPrice: boolean + name: string + subRates: [] +} + +export interface IGetAllTaxesResponse extends IBaseGetAllQueryResponse { + results: ITaxApiResponse[]; +} diff --git a/e-commerce-app/src/types/slicesTypes/taxSliceTypes.ts b/e-commerce-app/src/types/slicesTypes/taxSliceTypes.ts new file mode 100644 index 0000000..3505615 --- /dev/null +++ b/e-commerce-app/src/types/slicesTypes/taxSliceTypes.ts @@ -0,0 +1,10 @@ +import { ITaxApiResponse } from './taxApiTypes'; + +export interface ITaxFromSlice { + fetching: boolean; + taxes: ITaxApiResponse[]; + total: number; + limit: number; + offset: number; + count: number; +} From fe1d7195af827bc87faee6cb81bfcde3009e75e3 Mon Sep 17 00:00:00 2001 From: Ilya Shcherbakov <86749581+LikeKugi@users.noreply.github.com> Date: Thu, 31 Aug 2023 22:21:51 +0300 Subject: [PATCH 2/5] feat: add api for taxes and taxes slice --- .../CategoriesQuery/CategoriesQuery.tsx | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/e-commerce-app/src/requestsComponents/CategoriesQuery/CategoriesQuery.tsx b/e-commerce-app/src/requestsComponents/CategoriesQuery/CategoriesQuery.tsx index f19c90d..7a1af49 100644 --- a/e-commerce-app/src/requestsComponents/CategoriesQuery/CategoriesQuery.tsx +++ b/e-commerce-app/src/requestsComponents/CategoriesQuery/CategoriesQuery.tsx @@ -12,8 +12,18 @@ const CategoriesQuery = (): JSX.Element => { const accessToken = useAppSelector(getAccessToken); const dispatch = useAppDispatch(); - const { isLoading: isLoadingCategories, isError: isErrorCategories, isSuccess: isSuccessCategories, data: dataCategories } = useGetAllCategoriesQuery(accessToken as string); - const {isLoading: isLoadingTaxes, isError: isErrorTaxes, isSuccess: isSuccessTaxes, data: dataTaxes} = useGetAllTaxesQuery(accessToken as string); + const { + isLoading: isLoadingCategories, + isError: isErrorCategories, + isSuccess: isSuccessCategories, + data: dataCategories, + } = useGetAllCategoriesQuery(accessToken as string); + const { + isLoading: isLoadingTaxes, + isError: isErrorTaxes, + isSuccess: isSuccessTaxes, + data: dataTaxes, + } = useGetAllTaxesQuery(accessToken as string); useEffect(() => { if (!isSuccessCategories) return; @@ -29,7 +39,6 @@ const CategoriesQuery = (): JSX.Element => { } }, [isSuccessTaxes, dataTaxes]); - if (isLoadingCategories || isErrorCategories || isLoadingTaxes || isErrorTaxes) { return ; } From 380b117a07474d2e8c9bc3e909a202e267925be1 Mon Sep 17 00:00:00 2001 From: evgueniazet Date: Thu, 31 Aug 2023 15:41:17 +0200 Subject: [PATCH 3/5] feat: add hover for cards --- .../ProductCard/ProductCard.module.scss | 59 +++++++++++-------- 1 file changed, 34 insertions(+), 25 deletions(-) diff --git a/e-commerce-app/src/components/ProductCard/ProductCard.module.scss b/e-commerce-app/src/components/ProductCard/ProductCard.module.scss index 0d48bbf..966520d 100644 --- a/e-commerce-app/src/components/ProductCard/ProductCard.module.scss +++ b/e-commerce-app/src/components/ProductCard/ProductCard.module.scss @@ -1,28 +1,37 @@ .card { - display: flex; - width: 200px; - flex-direction: column; - align-items: center; - justify-content: space-between; - padding: 1rem; - border: 2px green double; - - &__text { - height: 100%; display: flex; + width: 200px; + flex-direction: column; align-items: center; - flex-wrap: wrap; - } - &__title { - width: 100%; - text-align: center; - color: cadetblue; - font-weight: 900; - } - &__price { - padding-top: 1rem; - width: 50%; - font-weight: 700; - text-align: center; - } -} + justify-content: space-between; + padding: 1rem; + border: 2px green double; + transition: box-shadow .3s ease-in-out; + cursor: pointer; + + &:hover { + box-shadow: 8px 10px 5px 2px #14763033; + transition: box-shadow .3s ease-in-out; + } + + &__text { + height: 100%; + display: flex; + align-items: center; + flex-wrap: wrap; + } + + &__title { + width: 100%; + text-align: center; + color: cadetblue; + font-weight: 900; + } + + &__price { + padding-top: 1rem; + width: 50%; + font-weight: 700; + text-align: center; + } +} \ No newline at end of file From 649968c2dbfe520a141317107fe182111fe4fd34 Mon Sep 17 00:00:00 2001 From: evgueniazet Date: Thu, 31 Aug 2023 15:42:19 +0200 Subject: [PATCH 4/5] fix: remove hover on cards --- .../src/components/ProductCard/ProductCard.module.scss | 7 ------- 1 file changed, 7 deletions(-) diff --git a/e-commerce-app/src/components/ProductCard/ProductCard.module.scss b/e-commerce-app/src/components/ProductCard/ProductCard.module.scss index 966520d..767ff31 100644 --- a/e-commerce-app/src/components/ProductCard/ProductCard.module.scss +++ b/e-commerce-app/src/components/ProductCard/ProductCard.module.scss @@ -6,13 +6,6 @@ justify-content: space-between; padding: 1rem; border: 2px green double; - transition: box-shadow .3s ease-in-out; - cursor: pointer; - - &:hover { - box-shadow: 8px 10px 5px 2px #14763033; - transition: box-shadow .3s ease-in-out; - } &__text { height: 100%; From a88dd223ad2fb3dfff2b5a173ac891284a147075 Mon Sep 17 00:00:00 2001 From: evgueniazet Date: Thu, 31 Aug 2023 23:38:49 +0200 Subject: [PATCH 5/5] feat: add sales for products --- .../pages/ProductPage/ProductPage.module.scss | 123 +++++++++--------- .../src/pages/ProductPage/ProductPage.tsx | 52 ++++++-- .../src/types/slicesTypes/taxApiTypes.ts | 2 +- 3 files changed, 106 insertions(+), 71 deletions(-) diff --git a/e-commerce-app/src/pages/ProductPage/ProductPage.module.scss b/e-commerce-app/src/pages/ProductPage/ProductPage.module.scss index 6ce6001..99478e9 100644 --- a/e-commerce-app/src/pages/ProductPage/ProductPage.module.scss +++ b/e-commerce-app/src/pages/ProductPage/ProductPage.module.scss @@ -1,82 +1,89 @@ .left { - display: flex; - align-items: center; - flex: 1; - gap: 15px; - - .images { + display: flex; + align-items: center; flex: 1; - min-width: max-content; + gap: 15px; - img { - width: 100%; - height: 100px; - margin-bottom: 15px; + .images { + flex: 1; + min-width: max-content; + img { + width: 100%; + height: 100px; + margin-bottom: 15px; + + } } - } } .img { - &_small { - object-fit: contain; - cursor: pointer; - } + &_small { + object-fit: contain; + cursor: pointer; + } - &_big { - width: 100%; - object-fit: contain; - max-height: 50vh; - cursor: pointer; - } + &_big { + width: 100%; + object-fit: contain; + max-height: 50vh; + cursor: pointer; + } } .price { - font-weight: 500; - color: green; + font-weight: 500; + color: green; + + &_full { + text-decoration: line-through; + color: green; + text-decoration-color: gray; + } } .quantity { - display: flex; - align-items: center; - gap: 20px; + display: flex; + align-items: center; + gap: 20px; - button { - background-color: greenyellow; - cursor: pointer; - } + button { + background-color: greenyellow; + cursor: pointer; + } } .btn { - button { - width: 250px; - padding: 10px; - display: flex; - justify-content: center; - font-weight: 700; - background-color: green; - color: white; - border: 1px solid green; - border-radius: 10px; - cursor: pointer; + button { + width: 250px; + padding: 10px; + display: flex; + justify-content: center; + font-weight: 700; + background-color: green; + color: white; + border: 1px solid green; + border-radius: 10px; + cursor: pointer; - &:hover { - color: green; - border: 1px solid green; + &:hover { + color: green; + border: 1px solid green; + } } - } } .modal { - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - border: 2px solid #000; - max-height: 70vh; - max-width: 70vw; - &__img { - max-height: 60vh; - object-fit: contain; - } -} + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + border: 2px solid #000; + max-height: 70vh; + max-width: 70vw; + + &__img { + max-height: 60vh; + object-fit: contain; + } +} \ No newline at end of file diff --git a/e-commerce-app/src/pages/ProductPage/ProductPage.tsx b/e-commerce-app/src/pages/ProductPage/ProductPage.tsx index 0020aa3..a544cbd 100644 --- a/e-commerce-app/src/pages/ProductPage/ProductPage.tsx +++ b/e-commerce-app/src/pages/ProductPage/ProductPage.tsx @@ -14,6 +14,8 @@ import { useParams } from 'react-router-dom'; import { useAppSelector } from '../../store/hooks'; import { getAccessToken } from '../../store/slices/userSlice'; import LoadingProgress from '../../components/LoadingProgress/LoadingProgress'; +import { getTaxes } from '../../store/slices/taxesSlice'; +import { ITaxApiResponse } from '../../types/slicesTypes/taxApiTypes'; const style = { bgcolor: 'background.paper', @@ -29,14 +31,24 @@ const styleArrows = { export const ProductPage: FC = () => { const { productId } = useParams(); const authToken = useAppSelector(getAccessToken); + const taxesArray = useAppSelector(getTaxes); + const { data, isSuccess, isLoading, isFetching } = useGetProductByIdQuery({ productId: productId as string, token: authToken as string, }); + const [tax, setTax] = useState(0); + useEffect(() => { if (isSuccess) { - console.log(data); + taxesArray + .filter((item) => item.id === data.taxCategory.id) + .flatMap((elem) => elem.rates) + .filter((rate: ITaxApiResponse) => rate.country === 'DE') + .forEach((rate: ITaxApiResponse) => { + setTax(rate.amount); + }); } }, [isSuccess]); @@ -59,18 +71,19 @@ export const ProductPage: FC = () => { const description = data.masterData.current.metaDescription.en; const currencyCommon = data.masterData.current.masterVariant.prices[0].value.currencyCode; + const priceNumber = + data.masterData.current.masterVariant.prices[0].value.centAmount / + 10 ** data.masterData.current.masterVariant.prices[0].value.fractionDigits; const priceCommon = new Intl.NumberFormat('en-IN', { style: 'currency', currency: currencyCommon, - }).format( - data.masterData.current.masterVariant.prices[0].value.centAmount / - 10 ** data.masterData.current.masterVariant.prices[0].value.fractionDigits, - ); + }).format(priceNumber); + + const discountPrice = new Intl.NumberFormat('en-IN', { + style: 'currency', + currency: currencyCommon, + }).format(priceNumber - priceNumber * tax); - console.log( - data.masterData.current.masterVariant.prices[0].value.centAmount, - 10 ** data.masterData.current.masterVariant.prices[0].value.fractionDigits, - ); return ( @@ -148,9 +161,24 @@ export const ProductPage: FC = () => { {title} - - {priceCommon} - + + {tax !== 0 ? ( + <> + + Discount price:{discountPrice} + + + Full price:{priceCommon} + + + Tax: {`${tax * 100} %`} + + + ) : ( + + Price:{priceCommon} + + )} {description} diff --git a/e-commerce-app/src/types/slicesTypes/taxApiTypes.ts b/e-commerce-app/src/types/slicesTypes/taxApiTypes.ts index 3ca84b0..66e8651 100644 --- a/e-commerce-app/src/types/slicesTypes/taxApiTypes.ts +++ b/e-commerce-app/src/types/slicesTypes/taxApiTypes.ts @@ -7,7 +7,7 @@ export interface ITaxApiResponse { id: string includedInPrice: boolean name: string - subRates: [] + rates: [] } export interface IGetAllTaxesResponse extends IBaseGetAllQueryResponse {