From 4e089c430f110dfb4373e7fba16509acdc6d6508 Mon Sep 17 00:00:00 2001 From: Kirill Kurentsov Date: Fri, 26 Jan 2024 16:47:06 +0300 Subject: [PATCH] =?UTF-8?q?api:=20=D0=98=D0=BD=D1=82=D0=B5=D0=B3=D1=80?= =?UTF-8?q?=D0=B0=D1=86=D0=B8=D1=8F=20=D1=81=20=D0=B1=D1=8D=D0=BA=D0=BE?= =?UTF-8?q?=D0=BC=20=D0=B4=D0=BB=D1=8F=20=D0=BE=D1=82=D0=BF=D1=80=D0=B0?= =?UTF-8?q?=D0=B2=D0=BA=D0=B8=20=D0=BF=D0=BE=D0=B8=D1=81=D0=BA=D0=BE=D0=B2?= =?UTF-8?q?=D0=BE=D0=B3=D0=BE=20=D0=B7=D0=B0=D0=BF=D1=80=D0=BE=D1=81=D0=B0?= =?UTF-8?q?=20(#202)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * api: Интеграция с бэком для отправки поискового запроса #173 * hotfix: исправлена опечатка в SroreProvider, fix ESLint issues --- config/storybook/decorators/StoreDecorator.js | 2 +- .../config/StateSchema.ts | 2 + .../config/store.ts | 10 ++-- .../{SroreProvider => StoreProvider}/index.ts | 0 .../ui/StoreProvider.tsx | 0 src/app/router/AppRouter/ui/AppRouter.tsx | 5 ++ src/components/header/header.tsx | 2 +- .../Category/selectors/categorySelectors.ts | 2 +- src/entities/Category/slice/categorySlice.ts | 2 +- .../selectors/searchProductSelectors.ts | 3 + .../SearchProduct/slice/searchProductSlice.ts | 55 +++++++++++++++++++ src/features/SearchProduct/types/types.ts | 8 +++ .../SearchProduct/ui/SearchProduct.tsx | 17 +++++- .../model/selectors/getUserAuthStatus.ts | 2 +- .../loginByUsername/loginByUsername.ts | 2 +- .../login/model/services/logout/logout.ts | 2 +- src/index.tsx | 2 +- src/pages/ProductsPage/ProductsPage.tsx | 1 - .../SearchResultsPage.module.scss | 3 + .../SearchResultsPage/SearchResultsPage.tsx | 48 ++++++++++++++++ src/shared/api/types.ts | 1 + src/shared/config/routerConfig/routes.ts | 3 +- src/shared/libs/hooks/store.ts | 2 +- .../ReviewsBlock/model/selectors/selectors.ts | 2 +- .../model/services/getStoreReviews.ts | 2 +- 25 files changed, 158 insertions(+), 20 deletions(-) rename src/app/providers/{SroreProvider => StoreProvider}/config/StateSchema.ts (82%) rename src/app/providers/{SroreProvider => StoreProvider}/config/store.ts (74%) rename src/app/providers/{SroreProvider => StoreProvider}/index.ts (100%) rename src/app/providers/{SroreProvider => StoreProvider}/ui/StoreProvider.tsx (100%) create mode 100644 src/features/SearchProduct/selectors/searchProductSelectors.ts create mode 100644 src/features/SearchProduct/slice/searchProductSlice.ts create mode 100644 src/features/SearchProduct/types/types.ts create mode 100644 src/pages/SearchResultsPage/SearchResultsPage.module.scss create mode 100644 src/pages/SearchResultsPage/SearchResultsPage.tsx diff --git a/config/storybook/decorators/StoreDecorator.js b/config/storybook/decorators/StoreDecorator.js index 3094a499..cb6e3cb8 100644 --- a/config/storybook/decorators/StoreDecorator.js +++ b/config/storybook/decorators/StoreDecorator.js @@ -1,4 +1,4 @@ -import {StoreProvider} from "../../../src/app/providers/SroreProvider/index.ts"; +import {StoreProvider} from "../../../src/app/providers/StoreProvider/index.ts"; export const StoreDecorator = (initialState) => (Story) => ( diff --git a/src/app/providers/SroreProvider/config/StateSchema.ts b/src/app/providers/StoreProvider/config/StateSchema.ts similarity index 82% rename from src/app/providers/SroreProvider/config/StateSchema.ts rename to src/app/providers/StoreProvider/config/StateSchema.ts index 62a2677f..ddee4ea2 100644 --- a/src/app/providers/SroreProvider/config/StateSchema.ts +++ b/src/app/providers/StoreProvider/config/StateSchema.ts @@ -1,4 +1,5 @@ import { CategorySchema } from '@/entities/Category/types/types' +import { SearchResultSchema } from '@/features/SearchProduct/types/types' import { LoginSchema } from '@/features/login/model/types/types' import { ApiInstance } from '@/shared/api/api' import { StoreReviewsSchema } from '@/widgets/ReviewsBlock/model/types/types' @@ -7,6 +8,7 @@ export interface StateSchema { login: LoginSchema storeReviews: StoreReviewsSchema category: CategorySchema + searchResult: SearchResultSchema } export interface ThunkExtraArg { diff --git a/src/app/providers/SroreProvider/config/store.ts b/src/app/providers/StoreProvider/config/store.ts similarity index 74% rename from src/app/providers/SroreProvider/config/store.ts rename to src/app/providers/StoreProvider/config/store.ts index 098ec015..927e75ac 100644 --- a/src/app/providers/SroreProvider/config/store.ts +++ b/src/app/providers/StoreProvider/config/store.ts @@ -3,17 +3,19 @@ import { loginReducer } from '@/features/login/model/slice/loginSlice' import { StateSchema, ThunkExtraArg } from './StateSchema' import { $api } from '@/shared/api/api' import categorySlice from '@/entities/Category/slice/categorySlice' +import searchProductSlice from '@/features/SearchProduct/slice/searchProductSlice' import { storeReviewsReducer } from '@/widgets/ReviewsBlock/model/slice/reviewsSlice' export type RootState = StateSchema -const rootReducer: ReducersMapObject = { +const rootReducer: ReducersMapObject = { login: loginReducer, - storeReviews: storeReviewsReducer, - category: categorySlice + category: categorySlice, + searchResult: searchProductSlice, + storeReviews: storeReviewsReducer } -export function createReduxStore(initialState: StateSchema) { +export function createReduxStore(initialState: RootState) { const extraArg: ThunkExtraArg = { api: $api } diff --git a/src/app/providers/SroreProvider/index.ts b/src/app/providers/StoreProvider/index.ts similarity index 100% rename from src/app/providers/SroreProvider/index.ts rename to src/app/providers/StoreProvider/index.ts diff --git a/src/app/providers/SroreProvider/ui/StoreProvider.tsx b/src/app/providers/StoreProvider/ui/StoreProvider.tsx similarity index 100% rename from src/app/providers/SroreProvider/ui/StoreProvider.tsx rename to src/app/providers/StoreProvider/ui/StoreProvider.tsx diff --git a/src/app/router/AppRouter/ui/AppRouter.tsx b/src/app/router/AppRouter/ui/AppRouter.tsx index 416bf6a7..28420af7 100644 --- a/src/app/router/AppRouter/ui/AppRouter.tsx +++ b/src/app/router/AppRouter/ui/AppRouter.tsx @@ -9,6 +9,7 @@ import LoginPage from '@/pages/LoginPage/LoginPage' import ComparePage from '@/pages/ComparePage/ComparePage' import FavoritesPage from '@/pages/FavoritesPage/FavoritesPage' import CartPage from '@/pages/CartPage/CartPage' +import SearchResultsPage from '@/pages/SearchResultsPage/SearchResultsPage' export const AppRouter = createBrowserRouter([ { @@ -48,6 +49,10 @@ export const AppRouter = createBrowserRouter([ { path: Routes.CART, element: + }, + { + path: Routes.SEARCH, + element: } ] } diff --git a/src/components/header/header.tsx b/src/components/header/header.tsx index 5518edd5..6fcef70a 100644 --- a/src/components/header/header.tsx +++ b/src/components/header/header.tsx @@ -17,7 +17,7 @@ import { linkItems } from '@/mockData/catalogListData' import styles from './header.module.scss' import { useDispatch, useSelector } from 'react-redux' import { fetchCategories } from '@/entities/Category/slice/categorySlice' -import { AppDispatch } from '@/app/providers/SroreProvider/config/store' +import { AppDispatch } from '@/app/providers/StoreProvider/config/store' import { selectCategories, selectDisplayedCategories } from '@/entities/Category/selectors/categorySelectors' import CatalogNodeItem from '@/widgets/CatalogNodeItem/CatalogNodeItem' import NavigationLink from '@/widgets/NavigationLink/NavigationLink' diff --git a/src/entities/Category/selectors/categorySelectors.ts b/src/entities/Category/selectors/categorySelectors.ts index 5f33d316..05535e0b 100644 --- a/src/entities/Category/selectors/categorySelectors.ts +++ b/src/entities/Category/selectors/categorySelectors.ts @@ -1,4 +1,4 @@ -import { RootState } from '@/app/providers/SroreProvider/config/store' +import { RootState } from '@/app/providers/StoreProvider/config/store' export const selectCategories = (state: RootState) => state.category.categories export const selectDisplayedCategories = (state: RootState) => state.category.displayedCategories diff --git a/src/entities/Category/slice/categorySlice.ts b/src/entities/Category/slice/categorySlice.ts index d25d5dc3..cc77499a 100644 --- a/src/entities/Category/slice/categorySlice.ts +++ b/src/entities/Category/slice/categorySlice.ts @@ -1,6 +1,6 @@ import { createSlice, createAsyncThunk } from '@reduxjs/toolkit' import { ApiError, ApiErrorTypes, ApiRoutes } from '@/shared/api/types' -import { ThunkConfig } from '@/app/providers/SroreProvider/config/StateSchema' +import { ThunkConfig } from '@/app/providers/StoreProvider/config/StateSchema' import { Category, CategorySchema } from '../types/types' import { apiErrorIdentify } from '@/shared/api/apiErrorIdentify' import { rejectedPayloadHandle } from '@/shared/api/rejectedPayloadHandle' diff --git a/src/features/SearchProduct/selectors/searchProductSelectors.ts b/src/features/SearchProduct/selectors/searchProductSelectors.ts new file mode 100644 index 00000000..1ae2fa5e --- /dev/null +++ b/src/features/SearchProduct/selectors/searchProductSelectors.ts @@ -0,0 +1,3 @@ +import { RootState } from '@/app/providers/StoreProvider/config/store' + +export const selectSearchResult = (state: RootState) => state.searchResult diff --git a/src/features/SearchProduct/slice/searchProductSlice.ts b/src/features/SearchProduct/slice/searchProductSlice.ts new file mode 100644 index 00000000..c8de78c1 --- /dev/null +++ b/src/features/SearchProduct/slice/searchProductSlice.ts @@ -0,0 +1,55 @@ +import { createSlice, createAsyncThunk } from '@reduxjs/toolkit' +import { ApiError, ApiErrorTypes, ApiRoutes } from '@/shared/api/types' +import { ThunkConfig } from '@/app/providers/StoreProvider/config/StateSchema' +import { SearchResultSchema } from '../types/types' +import { apiErrorIdentify } from '@/shared/api/apiErrorIdentify' +import { rejectedPayloadHandle } from '@/shared/api/rejectedPayloadHandle' +import { Category } from '@/entities/Category/types/types' +import { TProduct } from '@/shared/model/types/common' + +type SearchPayload = { + category: Category[] + product: { results: TProduct[] } +} + +const initialState: SearchResultSchema = { + categories: [], + products: [], + error: undefined +} + +export const search = createAsyncThunk>( + 'catalogue/search', + async (query: string, thunkAPI) => { + const { rejectWithValue, extra } = thunkAPI + + try { + const { data } = await extra.api.get(`api/${ApiRoutes.SEARCH}/?search=${query}`) + return data + } catch (error) { + return rejectWithValue(apiErrorIdentify(error, ApiErrorTypes.AUTH_ERROR)) as ApiError + } + } +) + +const searchProductSlice = createSlice({ + name: 'searchResult', + initialState, + reducers: {}, + extraReducers: builder => { + builder + .addCase(search.pending, state => { + state.error = undefined + }) + .addCase(search.fulfilled, (state, action) => { + const { category, product } = action.payload + state.categories = category || [] + state.products = product.results || [] + }) + .addCase(search.rejected, (state, { payload }) => { + state.error = rejectedPayloadHandle(payload) + }) + } +}) + +export default searchProductSlice.reducer diff --git a/src/features/SearchProduct/types/types.ts b/src/features/SearchProduct/types/types.ts new file mode 100644 index 00000000..6007aa82 --- /dev/null +++ b/src/features/SearchProduct/types/types.ts @@ -0,0 +1,8 @@ +import { Category } from '@/entities/Category/types/types' +import { TProduct } from '@/shared/model/types/common' + +export interface SearchResultSchema { + categories: Category[] + products: TProduct[] + error?: string | string[] +} diff --git a/src/features/SearchProduct/ui/SearchProduct.tsx b/src/features/SearchProduct/ui/SearchProduct.tsx index 2760a33c..bad5ff08 100644 --- a/src/features/SearchProduct/ui/SearchProduct.tsx +++ b/src/features/SearchProduct/ui/SearchProduct.tsx @@ -5,6 +5,8 @@ import SearchResult from '@/widgets/SearchResult/SearchResult' import { Input, InputSize, InputTheme } from '@/shared/ui/Input/Input' import { Button, ButtonDesign, ButtonSize, ButtonTheme } from '@/shared/ui/Button/Button' import styles from './SearchProduct.module.scss' +import { Routes } from '@/shared/config/routerConfig/routes' +import { useNavigate } from 'react-router-dom' // @TODO: Перевести форму на Formik + Yup // https://github.com/Studio-Yandex-Practicum/maxboom_frontend/issues/92 @@ -17,6 +19,7 @@ const SearchProduct = () => { const [resultData, setResultData] = useState({ data: [], success: false }) const [query, setQuery] = useState('') const searchResultRef = useRef(null) + const navigate = useNavigate() // @TODO: Добавить интеграцию с бэком - подсказки в поиске при вводе текста // https://github.com/Studio-Yandex-Practicum/maxboom_frontend/issues/172 @@ -26,6 +29,15 @@ const SearchProduct = () => { setQuery(value) } + const formSubmitHandler = (event: React.FormEvent) => { + event.preventDefault() + if (query.length > 0) { + const route = Routes.SEARCH.replace(':query', query) + navigate(route) + setVisibility(false) + } + } + const closeContextMenuHandler = (e: Event) => { const searchResultNode = searchResultRef.current @@ -54,7 +66,7 @@ const SearchProduct = () => { }, []) return ( -
+ { theme={InputTheme.ACCENT} onChange={inputEventHandler} /> - {/* @TODO: Добавить onClick-интеграцию с бэком для отправки поискового запроса - https://github.com/Studio-Yandex-Practicum/maxboom_frontend/issues/173 */}