Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
29 changes: 29 additions & 0 deletions e-commerce-app/src/api/productProjectionApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
import { IGetAllProductsRequest } from '../types/slicesTypes/productsApiTypes';
import { ISearchProductsResponse } from '../types/slicesTypes/productProjectionsApiTypes';

export const productProjectionApi = createApi({
reducerPath: 'productProjectionApi',
baseQuery: fetchBaseQuery({
baseUrl: `${process.env.REACT_APP_CTP_API_URL}/${process.env.REACT_APP_CTP_PROJECT_KEY}/product-projections`,
}),
endpoints: (build) => ({
searchProducts: build.mutation<ISearchProductsResponse, IGetAllProductsRequest>({
query(queryObject) {
return {
url: '/search',
method: 'GET',
params: {
limit: 12,
...queryObject.params,
},
headers: {
Authorization: `Bearer ${queryObject.token}`,
},
};
},
}),
}),
});

export const { useSearchProductsMutation } = productProjectionApi;
4 changes: 2 additions & 2 deletions e-commerce-app/src/api/productsApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export const productsApi = createApi({
baseUrl: `${process.env.REACT_APP_CTP_API_URL}/${process.env.REACT_APP_CTP_PROJECT_KEY}/products`,
}),
endpoints: (build) => ({
getAllProducts: build.query<IGetAllProductsResponse, IGetAllProductsRequest>({
getAllProducts: build.mutation<IGetAllProductsResponse, IGetAllProductsRequest>({
query(queryObject) {
return {
url: '',
Expand Down Expand Up @@ -43,4 +43,4 @@ export const productsApi = createApi({
}),
});

export const { useGetAllProductsQuery, useGetProductByIdQuery } = productsApi;
export const { useGetAllProductsMutation, useGetProductByIdQuery } = productsApi;
24 changes: 22 additions & 2 deletions e-commerce-app/src/pages/ProductsPage/ProductsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,23 @@ import { ProductsList } from '../../components/ProductsList/ProductsList';
import SearchIcon from '@mui/icons-material/Search';
import styles from './ProductsPage.module.scss';
import ProductsFilterForm from '../../components/ProductsFilterForm/ProductsFilterForm';
import { useDispatch } from 'react-redux';
import { SubmitHandler, useForm } from 'react-hook-form';
import { ISearchProductForm } from '../../types/searchProductsTypes/searchFormTypes';
import { getQueryText, setQueryText } from '../../store/slices/queryParamsSlice';
import { useAppSelector } from '../../store/hooks';

export const ProductsPage: React.FC = () => {
const dispatch = useDispatch();
const searchQueryText = useAppSelector(getQueryText);
const { register, handleSubmit } = useForm<ISearchProductForm>({
defaultValues: {
query: searchQueryText,
},
});
const submitHandler: SubmitHandler<ISearchProductForm> = (data) => {
dispatch(setQueryText(data.query || ''));
};
return (
<Grid container spacing={2}>
<Grid item xs={12}>
Expand All @@ -15,14 +30,19 @@ export const ProductsPage: React.FC = () => {
<img className={styles.top__img} src={ProductsPageImg} alt="img1" width="100%" />
</Grid>
<Grid item sm={12} md={10} m={'auto'}>
<Paper component="form" className={styles.top__form}>
<Paper
component="form"
className={styles.top__form}
onSubmit={handleSubmit(submitHandler)}
>
<InputBase
sx={{ ml: 1, flex: 1 }}
placeholder="Search plant"
{...register('query', {})}
inputProps={{ 'aria-label': 'search google maps' }}
/>
<IconButton
type="button"
type="submit"
sx={{ p: '10px', backgroundColor: 'lightgreen' }}
aria-label="search"
>
Expand Down
108 changes: 91 additions & 17 deletions e-commerce-app/src/requestsComponents/ProductsQuery/ProductsQuery.tsx
Original file line number Diff line number Diff line change
@@ -1,40 +1,114 @@
import React, { JSX, useEffect } from 'react';
import { useGetAllProductsQuery } from '../../api/productsApi';
import React, { JSX, useEffect, useState } from 'react';
import { useGetAllProductsMutation } from '../../api/productsApi';
import { useAppDispatch, useAppSelector } from '../../store/hooks';
import { getAccessToken } from '../../store/slices/userSlice';
import LoadingProgress from '../../components/LoadingProgress/LoadingProgress';
import { ProductsPage } from '../../pages/ProductsPage/ProductsPage';
import { setProducts, startLoadingProducts } from '../../store/slices/productsSlice';
import { getQueryLimit, getQueryOffset, getQueryText } from '../../store/slices/queryParamsSlice';
import { IBaseQueryParams } from '../../types/slicesTypes/baseApiRequestsTypes';
import { useSearchProductsMutation } from '../../api/productProjectionApi';
import { makeProductSliceObjectFromSearchApiRequest } from '../../utils/makeProductSliceObjectFromSearchApiRequest';

const ProductsQuery = (): JSX.Element => {
const accessToken = useAppSelector(getAccessToken);
const dispatch = useAppDispatch();
const accessToken = useAppSelector(getAccessToken);
const searchQueryText = useAppSelector(getQueryText);
const searchQueryLimit = useAppSelector(getQueryLimit);
const searchQueryOffset = useAppSelector(getQueryOffset);

const { isLoading, isSuccess, isError, data } = useGetAllProductsQuery({
token: accessToken as string,
const [params, setParams] = useState<IBaseQueryParams>({
limit: searchQueryLimit || 12,
});

useEffect(() => {
if (isLoading) {
if (searchQueryOffset) {
setParams((prevState) => ({
...prevState,
offset: searchQueryOffset,
}));
}
}, [searchQueryOffset]);

useEffect(() => {
if (searchQueryText) {
setParams((prevState) => ({
...prevState,
['text.en']: searchQueryText,
fuzzy: searchQueryText.length > 2,
fuzzyLevel: searchQueryText.length <= 2 ? 0 : 1,
}));
} else {
setParams((prevState) => {
const newObj = {
...prevState,
};
delete newObj['text.en'];
delete newObj.fuzzyLevel;
delete newObj.fuzzy;
return newObj;
});
}
}, [searchQueryText]);

const [
getAllProducts,
{
isLoading: isLoadingProducts,
isSuccess: isSuccessProducts,
isError: isErrorProducts,
data: dataProducts,
},
] = useGetAllProductsMutation();
const [
searchProducts,
{
isLoading: isLoadingSearch,
isSuccess: isSuccessSearch,
isError: isErrorSearch,
data: dataSearch,
},
] = useSearchProductsMutation();

useEffect(() => {
if (params['text.en']?.length && params['text.en']?.length > 0) {
searchProducts({
token: accessToken as string,
params,
});
} else {
getAllProducts({
token: accessToken as string,
params,
});
}
}, [params['text.en']]);

useEffect(() => {
if (isLoadingProducts || isLoadingSearch) {
dispatch(startLoadingProducts());
}
}, [isLoading]);
}, [isLoadingProducts, isLoadingSearch]);

useEffect(() => {
if (!isSuccess) return;
if (data && 'results' in data) {
dispatch(setProducts(data));
if (isSuccessSearch && params['text.en']) {
if (dataSearch && 'results' in dataSearch) {
const pushingObject = makeProductSliceObjectFromSearchApiRequest(dataSearch);
dispatch(setProducts(pushingObject));
}
return;
}
}, [isSuccess, data]);
if (isSuccessProducts) {
if (dataProducts && 'results' in dataProducts) {
dispatch(setProducts(dataProducts));
}
}
}, [isSuccessProducts, dataProducts, isSuccessSearch, dataSearch]);

if (isLoading || isError) {
if (isLoadingProducts || isErrorProducts || isLoadingSearch || isErrorSearch) {
return <LoadingProgress />;
}

if (isSuccess) {
return <ProductsPage />;
}

return <LoadingProgress />;
return <ProductsPage />;
};
export default ProductsQuery;
2 changes: 1 addition & 1 deletion e-commerce-app/src/routes/AppRoutes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ const router = createHashRouter(
<Route path="/basket" element={<BasketPage />} />
<Route element={<PrivateRoute />}>
<Route path="/user" element={<UserPage />}>
{/*TODO : add redirect to the user page */}
{/* TODO : add redirect to the user page */}
<Route path={':customerId'} element={<HomePage />}></Route>
</Route>
</Route>
Expand Down
10 changes: 9 additions & 1 deletion e-commerce-app/src/store/slices/productsSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,18 @@ export const productsSlice = createSlice({
state.products = action.payload.results;
state.fetching = false;
},
resetProducts: (state) => {
state.fetching = false;
state.products = [];
state.total = 0;
state.limit = 0;
state.offset = 0;
state.count = 0;
},
},
});

export const getProducts = (state: RootStateType) => state.products.products;
export const isFetchingProducts = (state: RootStateType) => state.products.fetching;
export const ProductsReducer = productsSlice.reducer;
export const { startLoadingProducts, setProducts } = productsSlice.actions;
export const { startLoadingProducts, setProducts, resetProducts } = productsSlice.actions;
33 changes: 33 additions & 0 deletions e-commerce-app/src/store/slices/queryParamsSlice.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { IQueryParamsFromSlice } from '../../types/slicesTypes/queryParamsSliceTypes';
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { RootStateType } from '../store';

const initialState: IQueryParamsFromSlice = {
limit: 12,
offset: 0,
sort: '',
text: '',
};

export const queryParamsSlice = createSlice({
initialState,
name: 'queryParamsSlice',
reducers: {
setQueryOffset: (state, action: PayloadAction<number>) => {
state.offset = action.payload;
},
setQuerySort: (state, action: PayloadAction<string>) => {
state.sort = action.payload;
},
setQueryText: (state, action: PayloadAction<string>) => {
state.text = action.payload;
}
}
});

export const getQueryOffset = (state: RootStateType) => state.queryParams.offset;
export const getQuerySort = (state: RootStateType) => state.queryParams.sort;
export const getQueryText = (state: RootStateType) => state.queryParams.text;
export const getQueryLimit = (state: RootStateType) => state.queryParams.limit;
export const QueryParamsReducer = queryParamsSlice.reducer;
export const { setQueryOffset, setQuerySort, setQueryText } = queryParamsSlice.actions;
6 changes: 6 additions & 0 deletions e-commerce-app/src/store/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,24 @@ import { productsApi } from '../api/productsApi';
import { categoriesApi } from '../api/categoriesApi';
import { CategoriesReducer } from './slices/categoriesSlice';
import { ProductsReducer } from './slices/productsSlice';
import { productProjectionApi } from '../api/productProjectionApi';
import { QueryParamsReducer } from './slices/queryParamsSlice';
import { taxApi } from '../api/taxApi';
import { TaxesReducer } from './slices/taxesSlice';


export const store = configureStore({
reducer: {
[authApi.reducerPath]: authApi.reducer,
[myCustomerApi.reducerPath]: myCustomerApi.reducer,
[productsApi.reducerPath]: productsApi.reducer,
[categoriesApi.reducerPath]: categoriesApi.reducer,
[taxApi.reducerPath]: taxApi.reducer,
[productProjectionApi.reducerPath]: productProjectionApi.reducer,
user: UserReducer,
categories: CategoriesReducer,
products: ProductsReducer,
queryParams: QueryParamsReducer,
taxes: TaxesReducer,
},
middleware: (getDefaultMiddleware) =>
Expand All @@ -27,6 +32,7 @@ export const store = configureStore({
myCustomerApi.middleware,
productsApi.middleware,
categoriesApi.middleware,
productProjectionApi.middleware,
taxApi.middleware,
]),
});
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export interface ISearchProductForm {
query: string
}
7 changes: 7 additions & 0 deletions e-commerce-app/src/types/slicesTypes/baseApiRequestsTypes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export interface IBaseQueryParams {
limit?: number;
offset?: number;
['text.en']?: string;
fuzzy?: boolean;
fuzzyLevel?: 0 | 1 | 2
}
13 changes: 13 additions & 0 deletions e-commerce-app/src/types/slicesTypes/productProjectionsApiTypes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { IBaseGetAllQueryResponse, ICategoryTypeResponse } from './baseApiResponsesTypes';
import { IProductApiDescriptionResponse } from './productsApiTypes';

export interface ISearchApiResponse extends IProductApiDescriptionResponse {
id: string;
key: string;
productType: ICategoryTypeResponse;
taxCategory: ICategoryTypeResponse;
}

export interface ISearchProductsResponse extends IBaseGetAllQueryResponse<ISearchApiResponse>{
results: ISearchApiResponse[];
}
6 changes: 2 additions & 4 deletions e-commerce-app/src/types/slicesTypes/productsApiTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
ICategoryTypeResponse,
IMetaDescriptionProductResponse,
} from './baseApiResponsesTypes';
import { IBaseQueryParams } from './baseApiRequestsTypes';

export interface IAttributeProductApiResponse {
name: string;
Expand Down Expand Up @@ -73,10 +74,7 @@ export interface IGetAllProductsResponse extends IBaseGetAllQueryResponse<IProdu

export interface IGetAllProductsRequest {
token: string;
params?: {
limit?: number;
offset?: number;
};
params?: IBaseQueryParams;
}

export interface IGetProductByIdRequest {
Expand Down
6 changes: 6 additions & 0 deletions e-commerce-app/src/types/slicesTypes/queryParamsSliceTypes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export interface IQueryParamsFromSlice {
limit: number;
offset: number;
text: string;
sort: string;
}
Loading