Skip to content

Commit

Permalink
api: Интеграция с бэком для отправки поискового запроса (#202)
Browse files Browse the repository at this point in the history
* api: Интеграция с бэком для отправки поискового запроса #173

* hotfix: исправлена опечатка в SroreProvider, fix ESLint issues
  • Loading branch information
Segodnya authored Jan 26, 2024
1 parent 5367826 commit 4e089c4
Show file tree
Hide file tree
Showing 25 changed files with 158 additions and 20 deletions.
2 changes: 1 addition & 1 deletion config/storybook/decorators/StoreDecorator.js
Original file line number Diff line number Diff line change
@@ -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) =>
( <StoreProvider initialState={initialState}>
Expand Down
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -7,6 +8,7 @@ export interface StateSchema {
login: LoginSchema
storeReviews: StoreReviewsSchema
category: CategorySchema
searchResult: SearchResultSchema
}

export interface ThunkExtraArg {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<StateSchema> = {
const rootReducer: ReducersMapObject<RootState> = {
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
}
Expand Down
File renamed without changes.
5 changes: 5 additions & 0 deletions src/app/router/AppRouter/ui/AppRouter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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([
{
Expand Down Expand Up @@ -48,6 +49,10 @@ export const AppRouter = createBrowserRouter([
{
path: Routes.CART,
element: <CartPage />
},
{
path: Routes.SEARCH,
element: <SearchResultsPage />
}
]
}
Expand Down
2 changes: 1 addition & 1 deletion src/components/header/header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
2 changes: 1 addition & 1 deletion src/entities/Category/selectors/categorySelectors.ts
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion src/entities/Category/slice/categorySlice.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { RootState } from '@/app/providers/StoreProvider/config/store'

export const selectSearchResult = (state: RootState) => state.searchResult
55 changes: 55 additions & 0 deletions src/features/SearchProduct/slice/searchProductSlice.ts
Original file line number Diff line number Diff line change
@@ -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<SearchPayload, string, ThunkConfig<ApiError>>(
'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
8 changes: 8 additions & 0 deletions src/features/SearchProduct/types/types.ts
Original file line number Diff line number Diff line change
@@ -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[]
}
17 changes: 14 additions & 3 deletions src/features/SearchProduct/ui/SearchProduct.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -17,6 +19,7 @@ const SearchProduct = () => {
const [resultData, setResultData] = useState<TResultData>({ 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
Expand All @@ -26,6 +29,15 @@ const SearchProduct = () => {
setQuery(value)
}

const formSubmitHandler = (event: React.FormEvent<HTMLFormElement>) => {
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

Expand Down Expand Up @@ -54,17 +66,16 @@ const SearchProduct = () => {
}, [])

return (
<form className={styles.form}>
<form className={styles.form} onSubmit={formSubmitHandler}>
<Input
name="search"
placeholder="Поиск по товарам и категориям"
customSize={InputSize.M}
theme={InputTheme.ACCENT}
onChange={inputEventHandler}
/>
{/* @TODO: Добавить onClick-интеграцию с бэком для отправки поискового запроса
https://github.com/Studio-Yandex-Practicum/maxboom_frontend/issues/173 */}
<Button
type="submit"
theme={ButtonTheme.PRIMARY}
design={ButtonDesign.SQUARE}
size={ButtonSize.XS}
Expand Down
2 changes: 1 addition & 1 deletion src/features/login/model/selectors/getUserAuthStatus.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { StateSchema } from '@/app/providers/SroreProvider'
import { StateSchema } from '@/app/providers/StoreProvider'

export const getUserAuthStatus = (state: StateSchema) => state.login.isAuth
export const getLoadingAuthStatus = (state: StateSchema) => state.login.isLoading
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { createAsyncThunk } from '@reduxjs/toolkit'
import { ThunkConfig } from '@/app/providers/SroreProvider/config/StateSchema'
import { ThunkConfig } from '@/app/providers/StoreProvider/config/StateSchema'
import { ApiError, ApiErrorTypes, ApiRoutes } from '@/shared/api/types'
import { LoginAuthData, LoginTokenData } from '../../../model/types/types'
import { apiErrorIdentify } from '@/shared/api/apiErrorIdentify'
Expand Down
2 changes: 1 addition & 1 deletion src/features/login/model/services/logout/logout.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { createAsyncThunk } from '@reduxjs/toolkit'
import { ThunkConfig } from '@/app/providers/SroreProvider/config/StateSchema'
import { ThunkConfig } from '@/app/providers/StoreProvider/config/StateSchema'
import { ApiError, ApiErrorTypes, ApiRoutes } from '@/shared/api/types'
import { apiErrorIdentify } from '@/shared/api/apiErrorIdentify'
import { LOCAL_STORAGE_TOKEN_KEY } from '@/shared/constants/localStorage'
Expand Down
2 changes: 1 addition & 1 deletion src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from 'react'
import ReactDOM from 'react-dom/client'
import './app/styles/index.scss'
import App from './app/App'
import { StoreProvider } from '@/app/providers/SroreProvider'
import { StoreProvider } from '@/app/providers/StoreProvider'

const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement)

Expand Down
1 change: 0 additions & 1 deletion src/pages/ProductsPage/ProductsPage.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { useState } from 'react'
import { useParams } from 'react-router'
import Modal from '@/shared/ui/Modal/Modal'
import WrapperForMainContent from '@/components/WrapperForMainContent/WrapperForMainContent'
import { CategoryList } from '@/components/CategoryList/CategoryList'
Expand Down
3 changes: 3 additions & 0 deletions src/pages/SearchResultsPage/SearchResultsPage.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.heading {
align-self: flex-start;
}
48 changes: 48 additions & 0 deletions src/pages/SearchResultsPage/SearchResultsPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { useEffect } from 'react'
import { useSelector, useDispatch } from 'react-redux'
import { useParams } from 'react-router-dom'
import WrapperForMainContent from '@/components/WrapperForMainContent/WrapperForMainContent'
import Heading from '@/shared/ui/Heading/Heading'
import Subheading from '@/shared/ui/Subheading/Subheading'
import styles from './SearchResultsPage.module.scss'
import { search } from '@/features/SearchProduct/slice/searchProductSlice'
import { selectSearchResult } from '@/features/SearchProduct/selectors/searchProductSelectors'
import { AnyAction, ThunkDispatch } from '@reduxjs/toolkit'
import { RootState } from '@/app/providers/StoreProvider/config/store'
import { ThunkExtraArg } from '@/app/providers/StoreProvider/config/StateSchema'

/**
* Страница с результатами поискового запроса
* @TODO Сверстать страницу SearchResults, разбить на компоненты
* https://github.com/Studio-Yandex-Practicum/maxboom_frontend/issues/201
*/
const SearchResultsPage = () => {
const { query } = useParams()
const searchResult = useSelector(selectSearchResult)
const dispatch = useDispatch<ThunkDispatch<RootState, ThunkExtraArg, AnyAction>>()

useEffect(() => {
if (query && query.length > 0) {
dispatch(search(query))
}
}, [query, dispatch])

return (
<WrapperForMainContent>
<Heading className={styles.heading}>Результаты поиска</Heading>
<Subheading>В разработке</Subheading>
<ul>
{searchResult &&
searchResult.categories.map(category => {
return <li key={category.slug}>{category.name}</li>
})}
{searchResult &&
searchResult.products.map(product => {
return <li key={product.slug}>{product.name}</li>
})}
</ul>
</WrapperForMainContent>
)
}

export default SearchResultsPage
1 change: 1 addition & 0 deletions src/shared/api/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export enum ApiRoutes {
LOGIN = 'token/login',
LOGOUT = 'token/logout',
SEARCH = 'search',
STORE_REVIEWS = 'store-reviews',
CATEGORIES = 'catalogue/category'
}
Expand Down
3 changes: 2 additions & 1 deletion src/shared/config/routerConfig/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@ export enum Routes {
COMPARE = '/compare',
FAVORITES = '/favorites',
LOGIN = '/login',
BRANDS = '/brands'
BRANDS = '/brands',
SEARCH = '/search/:query'
}
2 changes: 1 addition & 1 deletion src/shared/libs/hooks/store.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useDispatch } from 'react-redux'
import type { AppDispatch } from '@/app/providers/SroreProvider/config/store'
import type { AppDispatch } from '@/app/providers/StoreProvider/config/store'

// export type AppStoreState = ReturnType<typeof store.getState>
export const useAppDispatch: () => AppDispatch = useDispatch
Expand Down
2 changes: 1 addition & 1 deletion src/widgets/ReviewsBlock/model/selectors/selectors.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { StateSchema } from '@/app/providers/SroreProvider'
import { StateSchema } from '@/app/providers/StoreProvider'

export const getStoreReviewsSelector = (state: StateSchema) => {
return state.storeReviews.reviews
Expand Down
2 changes: 1 addition & 1 deletion src/widgets/ReviewsBlock/model/services/getStoreReviews.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { createAsyncThunk } from '@reduxjs/toolkit'
import { ThunkConfig } from '@/app/providers/SroreProvider/config/StateSchema'
import { ThunkConfig } from '@/app/providers/StoreProvider/config/StateSchema'
import { ApiError, ApiErrorTypes, ApiRoutes } from '@/shared/api/types'
import { apiErrorIdentify } from '@/shared/api/apiErrorIdentify'
import { StoreReviewData } from '../types/types'
Expand Down

0 comments on commit 4e089c4

Please sign in to comment.