From d89a084b7b27389103eb91c4bdfd350c6d0474c2 Mon Sep 17 00:00:00 2001 From: kirill-k88 Date: Thu, 4 Apr 2024 15:47:16 +0300 Subject: [PATCH 1/9] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB?= =?UTF-8?q?=20api=20=D0=B4=D0=BB=D1=8F=20=D0=BF=D0=BE=D0=BB=D1=83=D1=87?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=BA=D0=BE=D1=80=D0=B7=D0=B8=D0=BD?= =?UTF-8?q?=D1=8B=20=D0=B8=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D1=8F=20=D0=B2=20=D0=BA=D0=BE=D1=80=D0=B7=D0=B8?= =?UTF-8?q?=D0=BD=D1=83.=20=D0=9D=D0=B0=D1=87=D0=B0=D0=BB=20=D0=BF=D0=B8?= =?UTF-8?q?=D1=81=D0=B0=D1=82=D1=8C=20=D0=BB=D0=BE=D0=B3=D0=B8=D0=BA=D1=83?= =?UTF-8?q?=20=D0=B4=D0=BB=D1=8F=20=D0=BA=D0=BD=D0=BE=D0=BF=D0=BE=D0=BA=20?= =?UTF-8?q?=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F=20?= =?UTF-8?q?=D0=B2=20=D0=BA=D0=BE=D1=80=D0=B7=D0=B8=D0=BD=D1=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../StoreProvider/config/StateSchema.ts | 2 + .../providers/StoreProvider/config/store.ts | 4 +- .../CartEntity/model/slice/cartSlice.ts | 77 +++++++++++++++++++ src/entities/CartEntity/model/types/types.ts | 26 +++++++ src/pages/MainPage/MainPage.tsx | 11 +++ src/shared/api/types.ts | 3 +- src/widgets/Product/Product.tsx | 20 ++++- 7 files changed, 138 insertions(+), 5 deletions(-) create mode 100644 src/entities/CartEntity/model/slice/cartSlice.ts create mode 100644 src/entities/CartEntity/model/types/types.ts diff --git a/src/app/providers/StoreProvider/config/StateSchema.ts b/src/app/providers/StoreProvider/config/StateSchema.ts index 295f2608..1aef4578 100644 --- a/src/app/providers/StoreProvider/config/StateSchema.ts +++ b/src/app/providers/StoreProvider/config/StateSchema.ts @@ -15,6 +15,7 @@ import { ICategoryProductsSchema } from '@/pages/ProductsPage/types/types' import { IFeedbackSchema } from '@/pages/FeedbackPage/model/types/types' import { ICategorySchema, IMainCategorySchema } from '@/widgets/CategoryList/types/types' import type { IFeedbackFormSchema } from '@/widgets/FeedbackForm/model/scheme/feedbackFormSliceSchema' +import { ICartSchema } from '@/entities/CartEntity/model/types/types' export interface StateSchema { login: LoginSchema @@ -36,6 +37,7 @@ export interface StateSchema { categorySlug: CategorySlug categoryBranches: ICategorySchema getCategories: IMainCategorySchema + cart: ICartSchema } export interface ThunkExtraArg { diff --git a/src/app/providers/StoreProvider/config/store.ts b/src/app/providers/StoreProvider/config/store.ts index 731ea8ba..203f0dd4 100644 --- a/src/app/providers/StoreProvider/config/store.ts +++ b/src/app/providers/StoreProvider/config/store.ts @@ -20,6 +20,7 @@ import { categoryBranchesReducer } from '@/widgets/CategoryList/slice/pageCatego import { getCategoriesReducer } from '@/widgets/CategoryList/slice/pageCategoriesSlice' import { feedbackReducer } from '@/pages/FeedbackPage/model/slice/feedbackSlice' import { feedbackFormReducer } from '@/widgets/FeedbackForm/model/slice/feedbackFormSlice' +import { cartReducer } from '@/entities/CartEntity/model/slice/cartSlice' export type RootState = StateSchema @@ -42,7 +43,8 @@ const rootReducer: ReducersMapObject = { categoryId: categoryIdSliceReducer, categorySlug: categorySlugSliceReducer, categoryBranches: categoryBranchesReducer, - getCategories: getCategoriesReducer + getCategories: getCategoriesReducer, + cart: cartReducer } export function createReduxStore(initialState: RootState) { diff --git a/src/entities/CartEntity/model/slice/cartSlice.ts b/src/entities/CartEntity/model/slice/cartSlice.ts new file mode 100644 index 00000000..f8e280e6 --- /dev/null +++ b/src/entities/CartEntity/model/slice/cartSlice.ts @@ -0,0 +1,77 @@ +import { createAsyncThunk, createSlice } from '@reduxjs/toolkit' + +import { ThunkConfig } from '@/app/providers/StoreProvider/config/StateSchema' +import { apiErrorIdentify } from '@/shared/api/apiErrorIdentify' +import { rejectedPayloadHandle } from '@/shared/api/rejectedPayloadHandle' +import { ApiError, ApiErrorTypes, ApiRoutes } from '@/shared/api/types' + +import { IAddedProduct, ICart, ICartSchema } from '../types/types' + +export const getCart = createAsyncThunk>( + 'cart/getCart', + async (_, thunkAPI) => { + const { rejectWithValue, extra } = thunkAPI + try { + const { data } = await extra.api.get(`api/${ApiRoutes.CART}/`) + return data + } catch (error) { + return rejectWithValue(apiErrorIdentify(error, ApiErrorTypes.DATA_EMPTY_ERROR)) + } + } +) + +export const addToCart = createAsyncThunk>( + 'cart/addToCart', + async (addedProduct, thunkAPI) => { + const { rejectWithValue, extra } = thunkAPI + try { + await extra.api.post(`api/${ApiRoutes.CART}/`, addedProduct) + } catch (error) { + return rejectWithValue(apiErrorIdentify(error, ApiErrorTypes.DATA_EMPTY_ERROR)) + } + } +) + +const initialState: ICartSchema = { + isLoading: false, + error: null, + cart: { + id: 0, + products: [], + user: 0, + cart_full_price: 0 + } +} + +export const cartSlice = createSlice({ + name: 'cart', + initialState, + reducers: {}, + extraReducers: builder => { + builder + .addCase(getCart.pending, state => { + state.isLoading = true + }) + .addCase(getCart.fulfilled, (state, { payload }) => { + state.isLoading = false + state.cart = payload + }) + .addCase(getCart.rejected, (state, { payload }) => { + state.isLoading = false + state.error = rejectedPayloadHandle(payload) + }), + builder + .addCase(addToCart.pending, state => { + state.isLoading = true + }) + .addCase(addToCart.fulfilled, state => { + state.isLoading = false + }) + .addCase(addToCart.rejected, (state, { payload }) => { + state.isLoading = false + state.error = rejectedPayloadHandle(payload) + }) + } +}) + +export const { actions: cartActions, reducer: cartReducer } = cartSlice diff --git a/src/entities/CartEntity/model/types/types.ts b/src/entities/CartEntity/model/types/types.ts new file mode 100644 index 00000000..8b37b1f5 --- /dev/null +++ b/src/entities/CartEntity/model/types/types.ts @@ -0,0 +1,26 @@ +export interface ICartSchema { + isLoading?: boolean + error?: string | string[] | null + cart: ICart +} + +export interface ICart { + id: number + products: ICartProduct[] + user: number + cart_full_price: number +} + +export interface ICartProduct { + name: string + image: 'string' + price: number + amount: number + full_price: number +} + +export interface IAddedProduct { + product: number + cart: number + amount: number +} diff --git a/src/pages/MainPage/MainPage.tsx b/src/pages/MainPage/MainPage.tsx index 1a8e1b0e..8996e05d 100644 --- a/src/pages/MainPage/MainPage.tsx +++ b/src/pages/MainPage/MainPage.tsx @@ -1,4 +1,9 @@ +import { useEffect } from 'react' +import { useDispatch } from 'react-redux' + +import { AppDispatch } from '@/app/providers/StoreProvider/config/store' import WrapperForMainContent from '@/components/WrapperForMainContent/WrapperForMainContent' +import { getCart } from '@/entities/CartEntity/model/slice/cartSlice' import { TEXT_CUSTOMERS_ABOUT_US, LINK_REVIEWS_ALL } from '@/shared/constants/constants' import Advantages from '@/widgets/Advantages/ui/Advantages/Advantages' import ArticleBlock from '@/widgets/ArticleBlock/ArticleBlock' @@ -14,6 +19,12 @@ import Subscribe from '@/widgets/Subscribe/Subscribe' import { ViewedProducts } from '@/widgets/ViewedProducts/ViewedProducts' const MainPage = () => { + const dispatch = useDispatch() + + useEffect(() => { + dispatch(getCart()) + }, []) + return ( <> diff --git a/src/shared/api/types.ts b/src/shared/api/types.ts index bbea8187..c25686e2 100644 --- a/src/shared/api/types.ts +++ b/src/shared/api/types.ts @@ -10,7 +10,8 @@ export enum ApiRoutes { BLOG_POSTS = 'shopblog/posts', CORE_BASE = 'core/base', STORIES = 'stories', - PRODUCT = 'catalogue' + PRODUCT = 'catalogue', + CART = 'cart' } export enum ApiErrorTypes { diff --git a/src/widgets/Product/Product.tsx b/src/widgets/Product/Product.tsx index 57c15ba4..c8f83cb0 100644 --- a/src/widgets/Product/Product.tsx +++ b/src/widgets/Product/Product.tsx @@ -1,6 +1,10 @@ import { type FC, useState } from 'react' +import { useDispatch, useSelector } from 'react-redux' +import { StateSchema } from '@/app/providers/StoreProvider' +import { AppDispatch } from '@/app/providers/StoreProvider/config/store' import IconCart from '@/assets/icons/IconCart.svg' +import { addToCart } from '@/entities/CartEntity/model/slice/cartSlice' import { CardPreviewHeader } from '@/features/CardPreviewHeader/CardPreviewHeader' import { ProductAvailability } from '@/features/ProductAvailability/ProductAvailability' import { ProductImgCarousel } from '@/features/ProductImgCarousel/ProductImgCarousel' @@ -16,11 +20,20 @@ import { PopupImg } from './ui/PopupImg/PopupImg' * @param product TProductProps - информация о выбранном товаре */ export const Product: FC = ({ product }) => { + const dispatch = useDispatch() + const cart = useSelector((store: StateSchema) => store.cart) + const [isLiked, setIsLiked] = useState(false) const [isInCompared, setIsInCompared] = useState(false) const [isInCart, setIsInCart] = useState(false) const [showPopup, setShowPopup] = useState(false) + const addThisToCart = () => { + if (product.id) { + dispatch(addToCart({ product: product.id, cart: cart.cart.id, amount: 1 })) + } + } + const handleLike = () => { setIsLiked(!isLiked) } @@ -31,9 +44,12 @@ export const Product: FC = ({ product }) => { const handleAddToCart = () => { setIsInCart(!isInCart) + addThisToCart() } - const handleQuickPurchase = () => {} + const handleQuickPurchase = () => { + console.log(cart) + } return (
@@ -49,8 +65,6 @@ export const Product: FC = ({ product }) => { />
- {/* @TODO: Завести shared/ui-компоненты под типографику - https://github.com/Studio-Yandex-Practicum/maxboom_frontend/issues/77 */}
{`${product.price} ₽`} From 9c64d948676c2af992d92c83a0a6c04f7f3d7975 Mon Sep 17 00:00:00 2001 From: kirill-k88 Date: Thu, 4 Apr 2024 20:21:08 +0300 Subject: [PATCH 2/9] =?UTF-8?q?=D0=9F=D0=B5=D1=80=D0=B5=D0=BD=D0=B5=D1=81?= =?UTF-8?q?=20=D0=B7=D0=B0=D0=BF=D1=80=D0=BE=D1=81=20=D0=BA=D0=BE=D1=80?= =?UTF-8?q?=D0=B7=D0=B8=D0=BD=D1=8B=20=D0=B2=20App?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/App.tsx | 3 +++ src/pages/MainPage/MainPage.tsx | 11 ----------- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/src/app/App.tsx b/src/app/App.tsx index 89f712ad..79be2acd 100644 --- a/src/app/App.tsx +++ b/src/app/App.tsx @@ -1,6 +1,7 @@ import { useEffect } from 'react' import { RouterProvider } from 'react-router-dom' +import { getCart } from '@/entities/CartEntity/model/slice/cartSlice' import { loginActions } from '@/features/login/model/slice/loginSlice' import { $api } from '@/shared/api/api' import { tokenFromStorageGet } from '@/shared/libs/helpers/localStorageHandler' @@ -17,6 +18,8 @@ function App() { dispatch(loginActions.initAuth(token)) $api.addToken(token) } + + dispatch(getCart()) }, [dispatch]) return } diff --git a/src/pages/MainPage/MainPage.tsx b/src/pages/MainPage/MainPage.tsx index 8996e05d..1a8e1b0e 100644 --- a/src/pages/MainPage/MainPage.tsx +++ b/src/pages/MainPage/MainPage.tsx @@ -1,9 +1,4 @@ -import { useEffect } from 'react' -import { useDispatch } from 'react-redux' - -import { AppDispatch } from '@/app/providers/StoreProvider/config/store' import WrapperForMainContent from '@/components/WrapperForMainContent/WrapperForMainContent' -import { getCart } from '@/entities/CartEntity/model/slice/cartSlice' import { TEXT_CUSTOMERS_ABOUT_US, LINK_REVIEWS_ALL } from '@/shared/constants/constants' import Advantages from '@/widgets/Advantages/ui/Advantages/Advantages' import ArticleBlock from '@/widgets/ArticleBlock/ArticleBlock' @@ -19,12 +14,6 @@ import Subscribe from '@/widgets/Subscribe/Subscribe' import { ViewedProducts } from '@/widgets/ViewedProducts/ViewedProducts' const MainPage = () => { - const dispatch = useDispatch() - - useEffect(() => { - dispatch(getCart()) - }, []) - return ( <> From 397b762d097b2796798e28d8b59b0c3b682308cb Mon Sep 17 00:00:00 2001 From: kirill-k88 Date: Wed, 10 Apr 2024 21:23:34 +0300 Subject: [PATCH 3/9] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D0=B2=20=D0=BA=D0=BE=D1=80=D0=B7=D0=B8?= =?UTF-8?q?=D0=BD=D1=83=20=D0=BD=D0=B0=20=D1=81=D1=82=D1=80=D0=B0=D0=BD?= =?UTF-8?q?=D0=B8=D1=86=D0=B5=20=D1=82=D0=BE=D0=B2=D0=B0=D1=80=D0=B0.=20?= =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB=20=D0=B2=20Axios=20?= =?UTF-8?q?=D0=BE=D0=B1=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=BA=D1=83=20=D0=BA?= =?UTF-8?q?=D1=83=D0=BA=D0=B8.=20=D0=98=D0=B7=D0=BC=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D0=BB=20=D0=BD=D0=B0=D1=81=D1=82=D1=80=D0=BE=D0=B9=D0=BA=D0=B8?= =?UTF-8?q?=20=D1=81=D0=B1=D0=BE=D1=80=D0=BA=D0=B8=20=D0=B2=20webpack,=20?= =?UTF-8?q?=D1=83=D0=B2=D0=B5=D0=BB=D0=B8=D1=87=D0=B8=D0=BB=20=D1=80=D0=B0?= =?UTF-8?q?=D0=B7=D0=BC=D0=B5=D1=80=20=D1=84=D0=B0=D0=B9=D0=BB=D0=BE=D0=B2?= =?UTF-8?q?=20=D0=B2=20=D0=B1=D0=B0=D0=BD=D0=B4=D0=BB=D0=B5,=20=D0=BF?= =?UTF-8?q?=D0=BE=D1=81=D0=BA=D0=BE=D0=BB=D1=8C=D0=BA=D1=83=20=D1=84=D0=B0?= =?UTF-8?q?=D0=B9=D0=BB=D1=8B=20=D0=B2=20=D0=BF=D1=80=D0=BE=D0=B5=D0=BA?= =?UTF-8?q?=D1=82=D0=B5=20=D0=B1=D0=BE=D0=BB=D1=8C=D1=88=D0=B8=D0=B5=20?= =?UTF-8?q?=D0=B8=20=D0=B8=D0=BD=D0=B0=D1=87=D0=B5=20=D0=BD=D0=B5=20=D1=81?= =?UTF-8?q?=D0=BE=D0=B1=D0=B8=D1=80=D0=B0=D0=B5=D1=82=D1=81=D1=8F.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/shared/api/api.ts | 1 + src/widgets/Product/Product.tsx | 11 +++++++++-- webpack.config.js | 5 +++++ 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/shared/api/api.ts b/src/shared/api/api.ts index ff6c7543..ca516002 100644 --- a/src/shared/api/api.ts +++ b/src/shared/api/api.ts @@ -7,6 +7,7 @@ export interface ApiInstance extends AxiosInstance { export const $api: ApiInstance = Object.create( axios.create({ + withCredentials: true, baseURL: __API__ }), { diff --git a/src/widgets/Product/Product.tsx b/src/widgets/Product/Product.tsx index c8f83cb0..cbb0c764 100644 --- a/src/widgets/Product/Product.tsx +++ b/src/widgets/Product/Product.tsx @@ -1,5 +1,6 @@ import { type FC, useState } from 'react' import { useDispatch, useSelector } from 'react-redux' +import { useNavigate } from 'react-router' import { StateSchema } from '@/app/providers/StoreProvider' import { AppDispatch } from '@/app/providers/StoreProvider/config/store' @@ -8,6 +9,7 @@ import { addToCart } from '@/entities/CartEntity/model/slice/cartSlice' import { CardPreviewHeader } from '@/features/CardPreviewHeader/CardPreviewHeader' import { ProductAvailability } from '@/features/ProductAvailability/ProductAvailability' import { ProductImgCarousel } from '@/features/ProductImgCarousel/ProductImgCarousel' +import { Routes } from '@/shared/config/routerConfig/routes' import { Button, ButtonSize, ButtonTheme } from '@/shared/ui/Button/Button' import Paragraph from '@/shared/ui/Paragraph/Paragraph' @@ -20,6 +22,7 @@ import { PopupImg } from './ui/PopupImg/PopupImg' * @param product TProductProps - информация о выбранном товаре */ export const Product: FC = ({ product }) => { + const navigate = useNavigate() const dispatch = useDispatch() const cart = useSelector((store: StateSchema) => store.cart) @@ -43,8 +46,12 @@ export const Product: FC = ({ product }) => { } const handleAddToCart = () => { - setIsInCart(!isInCart) - addThisToCart() + if (!isInCart) { + addThisToCart() + setIsInCart(true) + } else { + navigate(Routes.CART) + } } const handleQuickPurchase = () => { diff --git a/webpack.config.js b/webpack.config.js index 785a6af2..b0000d4c 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -115,6 +115,11 @@ const config = { alias: { '@': path.resolve(__dirname, 'src'), }, + }, + performance: { + //TODO подобрать минимальное значение + maxEntrypointSize: 3512000, + maxAssetSize: 3512000 } } From a5111f244a285ba47a14a8cd082acc2fdbf3eda9 Mon Sep 17 00:00:00 2001 From: kirill-k88 Date: Thu, 11 Apr 2024 09:44:22 +0300 Subject: [PATCH 4/9] =?UTF-8?q?=D0=97=D0=B0=D1=87=D0=B8=D0=BD=D0=B8=D0=BB?= =?UTF-8?q?=20=D0=BF=D0=B5=D1=80=D0=B5=D0=B7=D0=B0=D0=BF=D1=80=D0=BE=D1=81?= =?UTF-8?q?=20=D0=BA=D0=BE=D1=80=D0=B7=D0=B8=D0=BD=D1=8B=20=D0=BF=D0=BE?= =?UTF-8?q?=D1=81=D0=BB=D0=B5=20=D0=B4=D0=BE=D0=B0=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=B2=20=D0=BA=D0=BE=D1=80=D0=B7?= =?UTF-8?q?=D0=B8=D0=BD=D1=83=20=D0=BD=D0=BE=D0=B2=D0=BE=D0=B3=D0=BE=20?= =?UTF-8?q?=D1=82=D0=BE=D0=B2=D0=B0=D1=80=D0=B0.=20=D0=A7=D1=83=D1=82?= =?UTF-8?q?=D1=8C=20=D0=B8=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=B8=D0=BB=20?= =?UTF-8?q?=D1=81=D1=82=D0=B8=D0=BB=D0=B8=20=D0=B4=D0=BB=D1=8F=20=D0=B0?= =?UTF-8?q?=D0=B4=D0=B0=D0=BF=D1=82=D0=B8=D0=B2=D0=BD=D0=BE=D1=81=D1=82?= =?UTF-8?q?=D0=B8=20=D0=B2=D0=B5=D1=80=D1=81=D1=82=D0=BA=D0=B8.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CartEntity/model/functions/cartHelper.ts | 28 +++++++++++++ .../CartEntity/model/slice/cartSlice.ts | 2 + src/entities/CartEntity/model/types/types.ts | 26 ++++++++++++ .../ProductImgCarousel.module.scss | 4 +- .../PreviewCarousel.module.scss | 42 +++++++++++++++++-- .../ui/PreviewCarousel/PreviewCarousel.tsx | 4 +- src/widgets/Product/Product.module.scss | 13 +++++- src/widgets/Product/Product.tsx | 10 +++-- 8 files changed, 116 insertions(+), 13 deletions(-) create mode 100644 src/entities/CartEntity/model/functions/cartHelper.ts diff --git a/src/entities/CartEntity/model/functions/cartHelper.ts b/src/entities/CartEntity/model/functions/cartHelper.ts new file mode 100644 index 00000000..cbed5fb2 --- /dev/null +++ b/src/entities/CartEntity/model/functions/cartHelper.ts @@ -0,0 +1,28 @@ +import { ICartProduct } from '../types/types' + +/** + * Функция проверки наличия товара в корзине + * + * @param {string} slug Slug товара + * @param {ICartProduct[]} cartProducts Массив товаров в корзине + * @returns {boolean} true, если товар есть в корзине + */ +export const isInCartBySlug = (slug: string, cartProducts: ICartProduct[]): boolean => { + console.log(slug, cartProducts) + if (cartProducts.length === 0) return false + + return cartProducts.some(p => p.product.slug === slug) +} + +/** + * Функция проверки наличия товара в корзине + * + * @param {number} id Id товара + * @param {ICartProduct[]} cartProducts Массив товаров в корзине + * @returns {boolean} true, если товар есть в корзине + */ +export const isInCartById = (id: number, cartProducts: ICartProduct[]): boolean => { + if (cartProducts.length === 0) return false + + return cartProducts.some(p => p.product.id === id) +} diff --git a/src/entities/CartEntity/model/slice/cartSlice.ts b/src/entities/CartEntity/model/slice/cartSlice.ts index f8e280e6..5cc570b2 100644 --- a/src/entities/CartEntity/model/slice/cartSlice.ts +++ b/src/entities/CartEntity/model/slice/cartSlice.ts @@ -26,6 +26,8 @@ export const addToCart = createAsyncThunk + +type TProduct = { + label_popular: boolean + label_hit: boolean + id: number + category: string + brand: string + price: number + name: string + slug: string + description: string + code: number + wb_urls: string + quantity: number + is_deleted: boolean + wholesale: number + images: TImgList +} diff --git a/src/features/ProductImgCarousel/ProductImgCarousel.module.scss b/src/features/ProductImgCarousel/ProductImgCarousel.module.scss index 04a28524..064e3ce8 100644 --- a/src/features/ProductImgCarousel/ProductImgCarousel.module.scss +++ b/src/features/ProductImgCarousel/ProductImgCarousel.module.scss @@ -6,7 +6,7 @@ display: flex; background-color: var.$white; - @include media.respond-to('middle') { + @include media.respond-to('large') { flex-direction: column-reverse; align-items: center; } @@ -29,7 +29,7 @@ border-top: 2px solid var.$bg-subscribe; border-bottom: 2px solid var.$bg-subscribe; - @include media.respond-to('middle') { + @include media.respond-to('large') { border-top: none; border-bottom: none; } diff --git a/src/features/ProductImgCarousel/ui/PreviewCarousel/PreviewCarousel.module.scss b/src/features/ProductImgCarousel/ui/PreviewCarousel/PreviewCarousel.module.scss index 03d2d04e..8921e281 100644 --- a/src/features/ProductImgCarousel/ui/PreviewCarousel/PreviewCarousel.module.scss +++ b/src/features/ProductImgCarousel/ui/PreviewCarousel/PreviewCarousel.module.scss @@ -10,8 +10,9 @@ flex-direction: column; overflow: hidden; - @include media.respond-to('middle') { + @include media.respond-to('large') { flex-direction: row; + justify-content: center; width: 100%; height: 160px; } @@ -24,8 +25,9 @@ flex-direction: column; overflow: hidden; - @include media.respond-to('middle') { + @include media.respond-to('large') { flex-direction: row; + justify-content: center; width: 80%; height: 100%; } @@ -33,6 +35,7 @@ &__imageframe { width: 100%; + max-width: 160px; min-height: 110px; box-sizing: border-box; border-top: 2px solid var.$bg-subscribe; @@ -43,15 +46,22 @@ justify-content: center; align-items: center; cursor: pointer; + transition: background-color 0.3s; &_active { background-color: var.$body-bg; } - @include media.respond-to('middle') { + @include media.respond-to('large') { border-right: none; border-bottom: 2px solid var.$bg-subscribe; } + + &:last-child { + @include media.respond-to('large') { + border-right: 2px solid var.$bg-subscribe; + } + } } &__image { @@ -76,18 +86,42 @@ display: none; } - @include media.respond-to('middle') { + @include media.respond-to('large') { width: 20%; height: 100%; + flex-direction: row-reverse; } } &__button { width: 50%; height: 100%; + transition: background-color 0.3s; &:hover { background-color: var.$body-bg; } + + @include media.respond-to('middle') { + padding: 1px; + } + + &_arrowDown { + @include media.respond-to('large') { + transform: rotate(270deg); + } + @include media.respond-to('middle') { + transform: rotate(270deg) scale(0.5); + } + } + + &_arrowUp { + @include media.respond-to('large') { + transform: rotate(270deg); + } + @include media.respond-to('middle') { + transform: rotate(270deg) scale(0.5); + } + } } } diff --git a/src/features/ProductImgCarousel/ui/PreviewCarousel/PreviewCarousel.tsx b/src/features/ProductImgCarousel/ui/PreviewCarousel/PreviewCarousel.tsx index 7407374d..5f982df9 100644 --- a/src/features/ProductImgCarousel/ui/PreviewCarousel/PreviewCarousel.tsx +++ b/src/features/ProductImgCarousel/ui/PreviewCarousel/PreviewCarousel.tsx @@ -98,7 +98,7 @@ export const PreviewCarousel: FC = ({ imgList, curImg, se size={ButtonSize.M} className={styles.previewcarousel__button} onClick={onNextHandle}> - +
diff --git a/src/widgets/Product/Product.module.scss b/src/widgets/Product/Product.module.scss index bfca0ac2..0fafb20d 100644 --- a/src/widgets/Product/Product.module.scss +++ b/src/widgets/Product/Product.module.scss @@ -7,8 +7,9 @@ margin: 0 auto; display: flex; justify-content: start; + gap: 10px; - @include media.respond-to('middle') { + @include media.respond-to('large') { flex-direction: column-reverse; height: auto; } @@ -24,7 +25,7 @@ border: 2px solid var.$bg-subscribe; box-sizing: border-box; - @include media.respond-to('middle') { + @include media.respond-to('large') { width: 100%; } } @@ -34,6 +35,10 @@ min-height: 300px; margin-top: 25px; border-bottom: 2px solid var.$bg-subscribe; + + @include media.respond-to('large') { + border-bottom: none; + } } &__buysection { @@ -100,5 +105,9 @@ background: var.$theme-primary-color; color: var.$white; } + + @include media.respond-to('middle') { + min-width: 80px; + } } } diff --git a/src/widgets/Product/Product.tsx b/src/widgets/Product/Product.tsx index cbb0c764..d1c1f183 100644 --- a/src/widgets/Product/Product.tsx +++ b/src/widgets/Product/Product.tsx @@ -1,10 +1,11 @@ -import { type FC, useState } from 'react' +import { type FC, useState, useEffect } from 'react' import { useDispatch, useSelector } from 'react-redux' import { useNavigate } from 'react-router' import { StateSchema } from '@/app/providers/StoreProvider' import { AppDispatch } from '@/app/providers/StoreProvider/config/store' import IconCart from '@/assets/icons/IconCart.svg' +import { isInCartBySlug } from '@/entities/CartEntity/model/functions/cartHelper' import { addToCart } from '@/entities/CartEntity/model/slice/cartSlice' import { CardPreviewHeader } from '@/features/CardPreviewHeader/CardPreviewHeader' import { ProductAvailability } from '@/features/ProductAvailability/ProductAvailability' @@ -31,6 +32,10 @@ export const Product: FC = ({ product }) => { const [isInCart, setIsInCart] = useState(false) const [showPopup, setShowPopup] = useState(false) + useEffect(() => { + setIsInCart(isInCartBySlug(product.slug, cart.cart.products)) + }, [product.slug, cart.cart.products]) + const addThisToCart = () => { if (product.id) { dispatch(addToCart({ product: product.id, cart: cart.cart.id, amount: 1 })) @@ -48,7 +53,6 @@ export const Product: FC = ({ product }) => { const handleAddToCart = () => { if (!isInCart) { addThisToCart() - setIsInCart(true) } else { navigate(Routes.CART) } @@ -96,7 +100,7 @@ export const Product: FC = ({ product }) => { theme={ButtonTheme.SECONDARY} size={ButtonSize.S} onClick={handleQuickPurchase}> - Быстрый заказ{' '} + Быстрый заказ
From c4d606a5740976bb253784be1ace272a1dbe7c55 Mon Sep 17 00:00:00 2001 From: kirill-k88 Date: Fri, 12 Apr 2024 20:08:13 +0300 Subject: [PATCH 5/9] =?UTF-8?q?=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB?= =?UTF-8?q?=20=D0=B8=D0=BC=D0=BF=D0=BE=D1=80=D1=82=20=D1=82=D0=B8=D0=BF?= =?UTF-8?q?=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/entities/CartEntity/model/functions/cartHelper.ts | 2 +- src/entities/CartEntity/model/slice/cartSlice.ts | 4 ++-- src/widgets/Product/Product.tsx | 2 +- src/widgets/Product/ui/PopupImg/PopupImg.tsx | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/entities/CartEntity/model/functions/cartHelper.ts b/src/entities/CartEntity/model/functions/cartHelper.ts index cbed5fb2..4f5f7548 100644 --- a/src/entities/CartEntity/model/functions/cartHelper.ts +++ b/src/entities/CartEntity/model/functions/cartHelper.ts @@ -1,4 +1,4 @@ -import { ICartProduct } from '../types/types' +import type { ICartProduct } from '../types/types' /** * Функция проверки наличия товара в корзине diff --git a/src/entities/CartEntity/model/slice/cartSlice.ts b/src/entities/CartEntity/model/slice/cartSlice.ts index 5cc570b2..10dee68e 100644 --- a/src/entities/CartEntity/model/slice/cartSlice.ts +++ b/src/entities/CartEntity/model/slice/cartSlice.ts @@ -1,11 +1,11 @@ import { createAsyncThunk, createSlice } from '@reduxjs/toolkit' -import { ThunkConfig } from '@/app/providers/StoreProvider/config/StateSchema' +import type { ThunkConfig } from '@/app/providers/StoreProvider/config/StateSchema' import { apiErrorIdentify } from '@/shared/api/apiErrorIdentify' import { rejectedPayloadHandle } from '@/shared/api/rejectedPayloadHandle' import { ApiError, ApiErrorTypes, ApiRoutes } from '@/shared/api/types' -import { IAddedProduct, ICart, ICartSchema } from '../types/types' +import type { IAddedProduct, ICart, ICartSchema } from '../types/types' export const getCart = createAsyncThunk>( 'cart/getCart', diff --git a/src/widgets/Product/Product.tsx b/src/widgets/Product/Product.tsx index d1c1f183..406787d4 100644 --- a/src/widgets/Product/Product.tsx +++ b/src/widgets/Product/Product.tsx @@ -14,7 +14,7 @@ import { Routes } from '@/shared/config/routerConfig/routes' import { Button, ButtonSize, ButtonTheme } from '@/shared/ui/Button/Button' import Paragraph from '@/shared/ui/Paragraph/Paragraph' -import { TProductProps } from './model/types/productTypes' +import type { TProductProps } from './model/types/productTypes' import styles from './Product.module.scss' import { PopupImg } from './ui/PopupImg/PopupImg' diff --git a/src/widgets/Product/ui/PopupImg/PopupImg.tsx b/src/widgets/Product/ui/PopupImg/PopupImg.tsx index 2b634269..8ee60004 100644 --- a/src/widgets/Product/ui/PopupImg/PopupImg.tsx +++ b/src/widgets/Product/ui/PopupImg/PopupImg.tsx @@ -1,9 +1,9 @@ -import { FC, useMemo, useState } from 'react' +import { type FC, useMemo, useState } from 'react' import { ImgCarousel } from '@/features/ProductImgCarousel/ui/ImgCarousel/ImgCarousel' import { bodyScrollControl } from '@/shared/libs/helpers/popupHelper' -import { TPopupImgProps } from '../../model/types/productTypes' +import type { TPopupImgProps } from '../../model/types/productTypes' import styles from './PopupImg.module.scss' From f000c3973fb78baf22c0509aef469dc66f4e6ecb Mon Sep 17 00:00:00 2001 From: kirill-k88 Date: Wed, 17 Apr 2024 20:17:07 +0300 Subject: [PATCH 6/9] =?UTF-8?q?=D0=94=D0=B0=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB?= =?UTF-8?q?=20api=20=D0=B4=D0=BB=D1=8F=20=D0=BA=D0=BD=D0=BE=D0=BF=D0=BE?= =?UTF-8?q?=D0=BA=20=D0=B8=20=D1=87=D1=83=D1=82=D1=8C=20=D0=BF=D0=BE=D0=B4?= =?UTF-8?q?=D0=BF=D1=80=D0=B0=D0=B2=D0=B8=D0=BB=20=D1=81=D1=82=D0=B8=D0=BB?= =?UTF-8?q?=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/App.tsx | 2 +- .../StoreProvider/config/StateSchema.ts | 2 + .../providers/StoreProvider/config/store.ts | 2 + .../{cartSlice.ts => cartEntitySlice.ts} | 14 +++--- src/entities/CartEntity/model/types/types.ts | 6 +-- .../WidgetButtonsPurchase.module.scss | 12 +++++- .../WidgetButtonsPurchase.tsx | 11 +++-- src/widgets/Product/Product.tsx | 4 +- .../CardPreview/CardPreview.module.scss | 17 ++++++++ .../ProductItem/CardPreview/CardPreview.tsx | 43 +++++++++++++++---- .../CardPreview/CardPrewiew.stories.tsx | 3 +- .../ProductItem/ProductItem.stories.tsx | 4 +- src/widgets/ProductItem/ProductItem.tsx | 36 ++++++++++++++-- src/widgets/ProductsList/ProductsList.tsx | 1 + src/widgets/ViewedProducts/ViewedProducts.tsx | 1 + 15 files changed, 125 insertions(+), 33 deletions(-) rename src/entities/CartEntity/model/slice/{cartSlice.ts => cartEntitySlice.ts} (82%) diff --git a/src/app/App.tsx b/src/app/App.tsx index 79be2acd..4f28eeef 100644 --- a/src/app/App.tsx +++ b/src/app/App.tsx @@ -1,7 +1,7 @@ import { useEffect } from 'react' import { RouterProvider } from 'react-router-dom' -import { getCart } from '@/entities/CartEntity/model/slice/cartSlice' +import { getCart } from '@/entities/CartEntity/model/slice/cartEntitySlice' import { loginActions } from '@/features/login/model/slice/loginSlice' import { $api } from '@/shared/api/api' import { tokenFromStorageGet } from '@/shared/libs/helpers/localStorageHandler' diff --git a/src/app/providers/StoreProvider/config/StateSchema.ts b/src/app/providers/StoreProvider/config/StateSchema.ts index b82c22c4..b360dcb6 100644 --- a/src/app/providers/StoreProvider/config/StateSchema.ts +++ b/src/app/providers/StoreProvider/config/StateSchema.ts @@ -17,6 +17,7 @@ import { ICategorySchema, IMainCategorySchema } from '@/widgets/CategoryList/typ import { ICategoryFiltersSchema } from '@/components/Dropdown/types/types' import type { IFeedbackFormSchema } from '@/widgets/FeedbackForm/model/scheme/feedbackFormSliceSchema' import { ICartEntitySchema } from '@/entities/CartEntity/model/types/types' +import { ICartEntitySchema } from '@/entities/CartEntity/model/types/types' import { IAboutUsSchema } from '@/pages/AboutUsPage/model/types/types' import { ICartSchema } from '@/pages/CartPage/model/types' @@ -41,6 +42,7 @@ export interface StateSchema { categorySlug: CategorySlug categoryBranches: ICategorySchema getCategories: IMainCategorySchema + cartEntity: ICartEntitySchema cart: ICartSchema cartEntity: ICartEntitySchema categoryFilters: ICategoryFiltersSchema diff --git a/src/app/providers/StoreProvider/config/store.ts b/src/app/providers/StoreProvider/config/store.ts index 88926154..65f6fe1a 100644 --- a/src/app/providers/StoreProvider/config/store.ts +++ b/src/app/providers/StoreProvider/config/store.ts @@ -24,6 +24,7 @@ import { feedbackFormReducer } from '@/widgets/FeedbackForm/model/slice/feedback import { aboutUsReducer } from '@/pages/AboutUsPage/model/slice/aboutUsSlice' import { cartReducer } from '@/pages/CartPage/model/slice' import { cartEntityReducer } from '@/entities/CartEntity/model/slice/cartEntitySlice' +import { cartEntityReducer } from '@/entities/CartEntity/model/slice/cartEntitySlice' export type RootState = StateSchema @@ -48,6 +49,7 @@ const rootReducer: ReducersMapObject = { categorySlug: categorySlugSliceReducer, categoryBranches: categoryBranchesReducer, getCategories: getCategoriesReducer, + cartEntity: cartEntityReducer, cart: cartReducer, cartEntity: cartEntityReducer, categoryFilters: categoryFiltersSliceReducer diff --git a/src/entities/CartEntity/model/slice/cartSlice.ts b/src/entities/CartEntity/model/slice/cartEntitySlice.ts similarity index 82% rename from src/entities/CartEntity/model/slice/cartSlice.ts rename to src/entities/CartEntity/model/slice/cartEntitySlice.ts index 10dee68e..9dd574d4 100644 --- a/src/entities/CartEntity/model/slice/cartSlice.ts +++ b/src/entities/CartEntity/model/slice/cartEntitySlice.ts @@ -5,14 +5,14 @@ import { apiErrorIdentify } from '@/shared/api/apiErrorIdentify' import { rejectedPayloadHandle } from '@/shared/api/rejectedPayloadHandle' import { ApiError, ApiErrorTypes, ApiRoutes } from '@/shared/api/types' -import type { IAddedProduct, ICart, ICartSchema } from '../types/types' +import type { IAddedProduct, ICartEntity, ICartEntitySchema } from '../types/types' -export const getCart = createAsyncThunk>( +export const getCart = createAsyncThunk>( 'cart/getCart', async (_, thunkAPI) => { const { rejectWithValue, extra } = thunkAPI try { - const { data } = await extra.api.get(`api/${ApiRoutes.CART}/`) + const { data } = await extra.api.get(`api/${ApiRoutes.CART_LIST}/`) return data } catch (error) { return rejectWithValue(apiErrorIdentify(error, ApiErrorTypes.DATA_EMPTY_ERROR)) @@ -25,7 +25,7 @@ export const addToCart = createAsyncThunk { const { rejectWithValue, extra } = thunkAPI try { - await extra.api.post(`api/${ApiRoutes.CART}/`, addedProduct) + await extra.api.post(`api/${ApiRoutes.CART_LIST}/`, addedProduct) thunkAPI.dispatch(getCart()) } catch (error) { @@ -34,7 +34,7 @@ export const addToCart = createAsyncThunk = ({ theme={isInCart ? ButtonTheme.SUCCESS : ButtonTheme.PRIMARY} size={size} onClick={handleAddToCart}> - - Купить + + {isInCart ? 'Перейти в корзину' : 'Купить'} - diff --git a/src/widgets/Product/Product.tsx b/src/widgets/Product/Product.tsx index 406787d4..ddf5dc13 100644 --- a/src/widgets/Product/Product.tsx +++ b/src/widgets/Product/Product.tsx @@ -6,7 +6,7 @@ import { StateSchema } from '@/app/providers/StoreProvider' import { AppDispatch } from '@/app/providers/StoreProvider/config/store' import IconCart from '@/assets/icons/IconCart.svg' import { isInCartBySlug } from '@/entities/CartEntity/model/functions/cartHelper' -import { addToCart } from '@/entities/CartEntity/model/slice/cartSlice' +import { addToCart } from '@/entities/CartEntity/model/slice/cartEntitySlice' import { CardPreviewHeader } from '@/features/CardPreviewHeader/CardPreviewHeader' import { ProductAvailability } from '@/features/ProductAvailability/ProductAvailability' import { ProductImgCarousel } from '@/features/ProductImgCarousel/ProductImgCarousel' @@ -25,7 +25,7 @@ import { PopupImg } from './ui/PopupImg/PopupImg' export const Product: FC = ({ product }) => { const navigate = useNavigate() const dispatch = useDispatch() - const cart = useSelector((store: StateSchema) => store.cart) + const cart = useSelector((store: StateSchema) => store.cartEntity) const [isLiked, setIsLiked] = useState(false) const [isInCompared, setIsInCompared] = useState(false) diff --git a/src/widgets/ProductItem/CardPreview/CardPreview.module.scss b/src/widgets/ProductItem/CardPreview/CardPreview.module.scss index e22518fc..95a77ed1 100644 --- a/src/widgets/ProductItem/CardPreview/CardPreview.module.scss +++ b/src/widgets/ProductItem/CardPreview/CardPreview.module.scss @@ -46,3 +46,20 @@ display: flex; gap: 20px; } + +.customButton { + max-width: 200px; + height: 50px; + padding: 5px; + display: flex; + align-items: center; + justify-content: center; + gap: 5px; + cursor: pointer; + transition: background-color 0.2s ease-in-out; + + svg { + width: 25px; + height: 20px; + } +} diff --git a/src/widgets/ProductItem/CardPreview/CardPreview.tsx b/src/widgets/ProductItem/CardPreview/CardPreview.tsx index 3b307c46..78545619 100644 --- a/src/widgets/ProductItem/CardPreview/CardPreview.tsx +++ b/src/widgets/ProductItem/CardPreview/CardPreview.tsx @@ -1,11 +1,17 @@ -import { FC, lazy, useState, Suspense } from 'react' +import { FC, lazy, useState, Suspense, useEffect } from 'react' +import { useDispatch, useSelector } from 'react-redux' import { useNavigate } from 'react-router-dom' +import { StateSchema } from '@/app/providers/StoreProvider' +import { AppDispatch } from '@/app/providers/StoreProvider/config/store' +import IconCart from '@/assets/icons/IconCart.svg' +import { isInCartBySlug } from '@/entities/CartEntity/model/functions/cartHelper' +import { addToCart } from '@/entities/CartEntity/model/slice/cartEntitySlice' import { CardPreviewFooter } from '@/features/CardPreviewFooter/CardPreviewFooter' import { CardPreviewHeader } from '@/features/CardPreviewHeader/CardPreviewHeader' import { ProductAvailability } from '@/features/ProductAvailability/ProductAvailability' import { ProductImgCarousel } from '@/features/ProductImgCarousel/ProductImgCarousel' -import { TImgList } from '@/pages/ProductsPage/types/types' +import type { TImgList } from '@/pages/ProductsPage/types/types' import { Routes } from '@/shared/config/routerConfig/routes' import { Button, ButtonSize, ButtonTheme } from '@/shared/ui/Button/Button' import Modal from '@/shared/ui/Modal/Modal' @@ -24,6 +30,7 @@ type Props = { slug: string images: TImgList quantity: number + id: number } /** @@ -35,24 +42,40 @@ type Props = { * @param {TImgList} images - массив с изображениями; * @param {number} quantity - количество на склаладе (если > 0, то товар считается в наличии); */ -export const CardPreview: FC = ({ code, images, slug, brand, quantity, price }) => { +export const CardPreview: FC = ({ code, images, slug, brand, quantity, price, id }) => { + const navigate = useNavigate() + const dispatch = useDispatch() + const cart = useSelector((store: StateSchema) => store.cartEntity) + const [isInCart, setIsInCart] = useState(false) const [isLiked, setIsLiked] = useState(false) const [isInCompared, setIsInCompared] = useState(false) const [isModalOpen, setIsModalOpen] = useState(false) const [isModalClosing, setIsModalClosing] = useState(false) - const [showPopup, setShowPopup] = useState(false) + useEffect(() => { + setIsInCart(isInCartBySlug(slug, cart.cart.products)) + }, [slug, cart.cart.products]) + + const addThisToCart = () => { + if (id) { + dispatch(addToCart({ product: id, cart: cart.cart.id, amount: 1 })) + } + } + const handleAddToCart = () => { - setIsInCart(!isInCart) + if (!isInCart) { + addThisToCart() + } else { + navigate(Routes.CART) + } } const handleQuickPurchase = () => { setIsModalOpen(true) } - const navigate = useNavigate() const handleRedirect = () => { navigate(`${Routes.PRODUCT}/${slug}`) } @@ -108,11 +131,13 @@ export const CardPreview: FC = ({ code, images, slug, brand, quantity, pr diff --git a/src/widgets/ProductItem/CardPreview/CardPrewiew.stories.tsx b/src/widgets/ProductItem/CardPreview/CardPrewiew.stories.tsx index 81892145..cab8d8fb 100644 --- a/src/widgets/ProductItem/CardPreview/CardPrewiew.stories.tsx +++ b/src/widgets/ProductItem/CardPreview/CardPrewiew.stories.tsx @@ -19,6 +19,7 @@ export const Default: Story = { slug: '/test', quantity: 999, brand: 'MaxBoom', - price: 999 + price: 999, + id: 12345 } } diff --git a/src/widgets/ProductItem/ProductItem.stories.tsx b/src/widgets/ProductItem/ProductItem.stories.tsx index bb19406c..8b87631c 100644 --- a/src/widgets/ProductItem/ProductItem.stories.tsx +++ b/src/widgets/ProductItem/ProductItem.stories.tsx @@ -27,6 +27,7 @@ type TProductCard = { label_hit: boolean label_popular: boolean quantity: number + id: number } const Template: Story = args => @@ -43,7 +44,8 @@ Grid.args = { images: [{ image: image1 }, { image: image2 }, { image: image3 }, { image: image4 }, { image: image5 }], label_hit: true, label_popular: true, - quantity: 999 + quantity: 999, + id: 4567 } export const List = Template.bind({}) diff --git a/src/widgets/ProductItem/ProductItem.tsx b/src/widgets/ProductItem/ProductItem.tsx index c8c6c979..8050bd28 100644 --- a/src/widgets/ProductItem/ProductItem.tsx +++ b/src/widgets/ProductItem/ProductItem.tsx @@ -1,7 +1,12 @@ import classnames from 'classnames' -import { FC, useState } from 'react' -import { Link } from 'react-router-dom' +import { FC, useEffect, useState } from 'react' +import { useDispatch, useSelector } from 'react-redux' +import { Link, useNavigate } from 'react-router-dom' +import { StateSchema } from '@/app/providers/StoreProvider' +import { AppDispatch } from '@/app/providers/StoreProvider/config/store' +import { isInCartBySlug } from '@/entities/CartEntity/model/functions/cartHelper' +import { addToCart } from '@/entities/CartEntity/model/slice/cartEntitySlice' import { ProductAvailability } from '@/features/ProductAvailability/ProductAvailability' import { WidgetButtonsFunctions } from '@/features/WidgetButtonsFunctions/WidgetButtonsFunctions' import { WidgetButtonsPurchase } from '@/features/WidgetButtonsPurchase/WidgetButtonsPurchase' @@ -31,6 +36,7 @@ type TProductCard = { label_hit: boolean label_popular: boolean quantity: number + id: number } /** @@ -46,6 +52,7 @@ type TProductCard = { * @param {boolean} label_popular - лейбл Популярный на товаре; * @param {boolean} label_hit - лейбл Хит на товаре; * @param {number} quantity - количество на склаладе (если > 0, то товар считается в наличии); + * @param {number} id - id товара в backend; */ export const ProductItem: FC = ({ layout, @@ -58,8 +65,13 @@ export const ProductItem: FC = ({ images, label_popular, label_hit, - quantity + quantity, + id }) => { + const navigate = useNavigate() + const dispatch = useDispatch() + const cart = useSelector((store: StateSchema) => store.cartEntity) + const [isInCart, setIsInCart] = useState(false) const [isLiked, setIsLiked] = useState(false) const [isInCompared, setIsInCompared] = useState(false) @@ -67,11 +79,26 @@ export const ProductItem: FC = ({ const [isModalOpen, setIsModalOpen] = useState(false) const [isModalClosing, setIsModalClosing] = useState(false) + useEffect(() => { + setIsInCart(isInCartBySlug(slug, cart.cart.products)) + }, [slug, cart.cart.products]) + + const addThisToCart = () => { + if (id) { + dispatch(addToCart({ product: id, cart: cart.cart.id, amount: 1 })) + } + } + const changeModalState = () => { setIsModalOpen(!isModalOpen) } + const handleAddToCart = () => { - setIsInCart(!isInCart) + if (!isInCart) { + addThisToCart() + } else { + navigate(Routes.CART) + } } const handleLike = () => { @@ -91,6 +118,7 @@ export const ProductItem: FC = ({ isModalClosing={isModalClosing} setIsModalClosing={setIsModalClosing}> = ({ items, cardView }) => { return items.results.map(item => ( = ({ title, hasLabel }) => Date: Wed, 17 Apr 2024 20:20:57 +0300 Subject: [PATCH 7/9] =?UTF-8?q?=D0=A1=D0=BC=D0=B5=D1=80=D0=B4=D0=B6=D0=B8?= =?UTF-8?q?=D0=BB=20=D1=81=20master?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/providers/StoreProvider/config/StateSchema.ts | 2 -- src/app/providers/StoreProvider/config/store.ts | 2 -- 2 files changed, 4 deletions(-) diff --git a/src/app/providers/StoreProvider/config/StateSchema.ts b/src/app/providers/StoreProvider/config/StateSchema.ts index b360dcb6..b82c22c4 100644 --- a/src/app/providers/StoreProvider/config/StateSchema.ts +++ b/src/app/providers/StoreProvider/config/StateSchema.ts @@ -17,7 +17,6 @@ import { ICategorySchema, IMainCategorySchema } from '@/widgets/CategoryList/typ import { ICategoryFiltersSchema } from '@/components/Dropdown/types/types' import type { IFeedbackFormSchema } from '@/widgets/FeedbackForm/model/scheme/feedbackFormSliceSchema' import { ICartEntitySchema } from '@/entities/CartEntity/model/types/types' -import { ICartEntitySchema } from '@/entities/CartEntity/model/types/types' import { IAboutUsSchema } from '@/pages/AboutUsPage/model/types/types' import { ICartSchema } from '@/pages/CartPage/model/types' @@ -42,7 +41,6 @@ export interface StateSchema { categorySlug: CategorySlug categoryBranches: ICategorySchema getCategories: IMainCategorySchema - cartEntity: ICartEntitySchema cart: ICartSchema cartEntity: ICartEntitySchema categoryFilters: ICategoryFiltersSchema diff --git a/src/app/providers/StoreProvider/config/store.ts b/src/app/providers/StoreProvider/config/store.ts index 65f6fe1a..72f24b6e 100644 --- a/src/app/providers/StoreProvider/config/store.ts +++ b/src/app/providers/StoreProvider/config/store.ts @@ -24,7 +24,6 @@ import { feedbackFormReducer } from '@/widgets/FeedbackForm/model/slice/feedback import { aboutUsReducer } from '@/pages/AboutUsPage/model/slice/aboutUsSlice' import { cartReducer } from '@/pages/CartPage/model/slice' import { cartEntityReducer } from '@/entities/CartEntity/model/slice/cartEntitySlice' -import { cartEntityReducer } from '@/entities/CartEntity/model/slice/cartEntitySlice' export type RootState = StateSchema @@ -51,7 +50,6 @@ const rootReducer: ReducersMapObject = { getCategories: getCategoriesReducer, cartEntity: cartEntityReducer, cart: cartReducer, - cartEntity: cartEntityReducer, categoryFilters: categoryFiltersSliceReducer } From 44cc92a4429f67cef915eb4690596675d55d1068 Mon Sep 17 00:00:00 2001 From: kirill-k88 Date: Fri, 19 Apr 2024 20:31:01 +0300 Subject: [PATCH 8/9] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=20=D0=B7=D0=B0=D0=BC=D0=B5=D1=87=D0=B0=D0=BD=D0=B8=D1=8F?= =?UTF-8?q?=20=D0=BF=D0=BE=20=D0=BA=D0=BE=D0=B4-=D1=80=D0=B5=D0=B2=D1=8C?= =?UTF-8?q?=D1=8E.=20=D0=9D=D0=B5=D0=BC=D0=BD=D0=BE=D0=B3=D0=BE=20=D0=BF?= =?UTF-8?q?=D0=BE=D0=B4=D0=BF=D1=80=D0=B0=D0=B2=D0=B8=D0=BB=20=D1=81=D1=82?= =?UTF-8?q?=D0=B8=D0=BB=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CartEntity/model/functions/cartHelper.ts | 1 - .../CartEntity/model/hooks/cartHooks.ts | 21 ++++++++++++++++++ .../CartEntity/model/hooks/sliceHooks.ts | 5 +++++ .../WidgetButtonsPurchase.module.scss | 12 +++++++--- .../WidgetButtonsPurchase.tsx | 4 ++-- src/pages/AboutUsPage/AboutUsPage.tsx | 6 ++--- src/pages/FeedbackPage/FeedbackPage.tsx | 6 ++--- src/pages/ProductPage/ProductPage.tsx | 6 ++--- src/widgets/FeedbackForm/FeedbackForm.tsx | 6 ++--- src/widgets/Product/Product.tsx | 21 +++++++----------- .../CardPreview/CardPreview.module.scss | 3 ++- .../ProductItem/CardPreview/CardPreview.tsx | 21 ++++++------------ src/widgets/ProductItem/ProductItem.tsx | 22 +++++++------------ 13 files changed, 74 insertions(+), 60 deletions(-) create mode 100644 src/entities/CartEntity/model/hooks/cartHooks.ts create mode 100644 src/entities/CartEntity/model/hooks/sliceHooks.ts diff --git a/src/entities/CartEntity/model/functions/cartHelper.ts b/src/entities/CartEntity/model/functions/cartHelper.ts index 4f5f7548..5cc15680 100644 --- a/src/entities/CartEntity/model/functions/cartHelper.ts +++ b/src/entities/CartEntity/model/functions/cartHelper.ts @@ -8,7 +8,6 @@ import type { ICartProduct } from '../types/types' * @returns {boolean} true, если товар есть в корзине */ export const isInCartBySlug = (slug: string, cartProducts: ICartProduct[]): boolean => { - console.log(slug, cartProducts) if (cartProducts.length === 0) return false return cartProducts.some(p => p.product.slug === slug) diff --git a/src/entities/CartEntity/model/hooks/cartHooks.ts b/src/entities/CartEntity/model/hooks/cartHooks.ts new file mode 100644 index 00000000..e461a80c --- /dev/null +++ b/src/entities/CartEntity/model/hooks/cartHooks.ts @@ -0,0 +1,21 @@ +import { useEffect, useState } from 'react' + +import { isInCartBySlug } from '../functions/cartHelper' +import { ICartProduct } from '../types/types' + +/** + * Hook для отслеживания есть ли товар в корзине + * + * @param {string} slug - slug продукта + * @param {ICartProduct[]} productsInCart - массив продуктов в корзине + * @returns boolean - возвращает стейт isInCart со значением true/false + */ +export const useIsProductInCart = (slug: string, productsInCart: ICartProduct[]) => { + const [isInCart, setIsInCart] = useState(false) + + useEffect(() => { + setIsInCart(isInCartBySlug(slug, productsInCart)) + }, [slug, productsInCart]) + + return isInCart +} diff --git a/src/entities/CartEntity/model/hooks/sliceHooks.ts b/src/entities/CartEntity/model/hooks/sliceHooks.ts new file mode 100644 index 00000000..86a80d67 --- /dev/null +++ b/src/entities/CartEntity/model/hooks/sliceHooks.ts @@ -0,0 +1,5 @@ +import { useSelector } from 'react-redux' + +import { StateSchema } from '@/app/providers/StoreProvider' + +export const useCartSelector = () => useSelector((store: StateSchema) => store.cartEntity) diff --git a/src/features/WidgetButtonsPurchase/WidgetButtonsPurchase.module.scss b/src/features/WidgetButtonsPurchase/WidgetButtonsPurchase.module.scss index 0410f188..8f069f5f 100644 --- a/src/features/WidgetButtonsPurchase/WidgetButtonsPurchase.module.scss +++ b/src/features/WidgetButtonsPurchase/WidgetButtonsPurchase.module.scss @@ -1,11 +1,17 @@ +.customButtonsContainer { + display: flex; + justify-content: end; + gap: 5px; +} + .customButton { - max-width: 115px; - height: 50px; + max-width: 120px; + min-height: 50px; padding: 5px; display: flex; align-items: center; justify-content: center; - gap: 5px; + gap: 2px; cursor: pointer; transition: background-color 0.2s ease-in-out; diff --git a/src/features/WidgetButtonsPurchase/WidgetButtonsPurchase.tsx b/src/features/WidgetButtonsPurchase/WidgetButtonsPurchase.tsx index 7d9a50ea..a401df22 100644 --- a/src/features/WidgetButtonsPurchase/WidgetButtonsPurchase.tsx +++ b/src/features/WidgetButtonsPurchase/WidgetButtonsPurchase.tsx @@ -31,7 +31,7 @@ export const WidgetButtonsPurchase: FC = ({ const size = layout === ECardView.COMPACT ? ButtonSize.S : ButtonSize.XS return ( - <> +
- +
) } diff --git a/src/pages/AboutUsPage/AboutUsPage.tsx b/src/pages/AboutUsPage/AboutUsPage.tsx index 2d705e10..51f298eb 100644 --- a/src/pages/AboutUsPage/AboutUsPage.tsx +++ b/src/pages/AboutUsPage/AboutUsPage.tsx @@ -1,10 +1,10 @@ import { useEffect } from 'react' -import { useDispatch, useSelector } from 'react-redux' +import { useSelector } from 'react-redux' import { Link } from 'react-router-dom' -import { AppDispatch } from '@/app/providers/StoreProvider/config/store' import WrapperForMainContent from '@/components/WrapperForMainContent/WrapperForMainContent' import AboutUs from '@/entities/AboutUs' +import { useAppDispatch } from '@/shared/libs/hooks/store' import Heading, { HeadingType } from '@/shared/ui/Heading/Heading' import Subheading from '@/shared/ui/Subheading/Subheading' @@ -13,7 +13,7 @@ import { getAboutUsSelector } from './model/selectors/selectors' import { getAboutUs } from './model/services/getAboutUs' const AboutUsPage = () => { - const dispatch = useDispatch() + const dispatch = useAppDispatch() const aboutUs = useSelector(getAboutUsSelector) useEffect(() => { diff --git a/src/pages/FeedbackPage/FeedbackPage.tsx b/src/pages/FeedbackPage/FeedbackPage.tsx index 53962551..c47b6016 100644 --- a/src/pages/FeedbackPage/FeedbackPage.tsx +++ b/src/pages/FeedbackPage/FeedbackPage.tsx @@ -1,10 +1,10 @@ import { useEffect, useState } from 'react' -import { useDispatch, useSelector } from 'react-redux' +import { useSelector } from 'react-redux' import { StateSchema } from '@/app/providers/StoreProvider' -import { AppDispatch } from '@/app/providers/StoreProvider/config/store' import WrapperForMainContent from '@/components/WrapperForMainContent/WrapperForMainContent' import { bodyScrollControl } from '@/shared/libs/helpers/popupHelper' +import { useAppDispatch } from '@/shared/libs/hooks/store' import { useResize } from '@/shared/libs/hooks/useResize' import { Button, ButtonSize, ButtonTheme } from '@/shared/ui/Button/Button' import Heading, { HeadingType } from '@/shared/ui/Heading/Heading' @@ -21,7 +21,7 @@ import { FeedbackFormPopup } from './ui/FeedbackFormPopup/FeedbackFormPopup' * */ export const FeedbackPage = () => { - const dispatch = useDispatch() + const dispatch = useAppDispatch() const feedback = useSelector((store: StateSchema) => store.feedback) const { isScreenLg } = useResize() const [showPopup, setShowPopup] = useState(false) diff --git a/src/pages/ProductPage/ProductPage.tsx b/src/pages/ProductPage/ProductPage.tsx index 11ba00f2..2bb35386 100644 --- a/src/pages/ProductPage/ProductPage.tsx +++ b/src/pages/ProductPage/ProductPage.tsx @@ -1,11 +1,11 @@ import { useEffect } from 'react' -import { useDispatch, useSelector } from 'react-redux' +import { useSelector } from 'react-redux' import { useParams } from 'react-router' import { StateSchema } from '@/app/providers/StoreProvider' -import { AppDispatch } from '@/app/providers/StoreProvider/config/store' import { PageDescription } from '@/components/PageDescription/PageDescription' import WrapperForMainContent from '@/components/WrapperForMainContent/WrapperForMainContent' +import { useAppDispatch } from '@/shared/libs/hooks/store' import Advantages from '@/widgets/Advantages/ui/Advantages/Advantages' import { Product } from '@/widgets/Product/Product' import { ProductInfo } from '@/widgets/ProductInfo/ProductInfo' @@ -19,7 +19,7 @@ import { getProduct } from './model/slice/productSlice' * @ /slug - идентификатор товара в backend передаваемый в url */ export const ProductPage = () => { - const dispatch = useDispatch() + const dispatch = useAppDispatch() const productStore = useSelector((store: StateSchema) => store.product) const { slug } = useParams() diff --git a/src/widgets/FeedbackForm/FeedbackForm.tsx b/src/widgets/FeedbackForm/FeedbackForm.tsx index acc9b1a6..00050378 100644 --- a/src/widgets/FeedbackForm/FeedbackForm.tsx +++ b/src/widgets/FeedbackForm/FeedbackForm.tsx @@ -1,9 +1,9 @@ import { Formik, Field, Form, FormikHelpers } from 'formik' import { FC, useEffect, useState } from 'react' -import { useDispatch, useSelector } from 'react-redux' +import { useSelector } from 'react-redux' import type { StateSchema } from '@/app/providers/StoreProvider' -import type { AppDispatch } from '@/app/providers/StoreProvider/config/store' +import { useAppDispatch } from '@/shared/libs/hooks/store' import { Button, ButtonSize, ButtonTheme } from '@/shared/ui/Button/Button' import { FormMsg } from '@/shared/ui/FormMsg/FormMsg' import Heading from '@/shared/ui/Heading/Heading' @@ -24,7 +24,7 @@ import { RequiredFieldTitle } from './ui/RequiredFieldTitle/RequiredFieldTitle' export const FeedbackForm: FC = () => { const [showMsg, setShowMsg] = useState(false) const [showApiErrorMsg, setShowApiErrorMsg] = useState(false) - const dispatch = useDispatch() + const dispatch = useAppDispatch() const feedbackForm = useSelector((store: StateSchema) => store.feedbackForm) const onSubmit = (values: IFeedbackFormValues, formikHelpers: FormikHelpers) => { diff --git a/src/widgets/Product/Product.tsx b/src/widgets/Product/Product.tsx index ddf5dc13..80af1477 100644 --- a/src/widgets/Product/Product.tsx +++ b/src/widgets/Product/Product.tsx @@ -1,16 +1,15 @@ -import { type FC, useState, useEffect } from 'react' -import { useDispatch, useSelector } from 'react-redux' +import { type FC, useState } from 'react' import { useNavigate } from 'react-router' -import { StateSchema } from '@/app/providers/StoreProvider' -import { AppDispatch } from '@/app/providers/StoreProvider/config/store' import IconCart from '@/assets/icons/IconCart.svg' -import { isInCartBySlug } from '@/entities/CartEntity/model/functions/cartHelper' +import { useIsProductInCart } from '@/entities/CartEntity/model/hooks/cartHooks' +import { useCartSelector } from '@/entities/CartEntity/model/hooks/sliceHooks' import { addToCart } from '@/entities/CartEntity/model/slice/cartEntitySlice' import { CardPreviewHeader } from '@/features/CardPreviewHeader/CardPreviewHeader' import { ProductAvailability } from '@/features/ProductAvailability/ProductAvailability' import { ProductImgCarousel } from '@/features/ProductImgCarousel/ProductImgCarousel' import { Routes } from '@/shared/config/routerConfig/routes' +import { useAppDispatch } from '@/shared/libs/hooks/store' import { Button, ButtonSize, ButtonTheme } from '@/shared/ui/Button/Button' import Paragraph from '@/shared/ui/Paragraph/Paragraph' @@ -24,18 +23,14 @@ import { PopupImg } from './ui/PopupImg/PopupImg' */ export const Product: FC = ({ product }) => { const navigate = useNavigate() - const dispatch = useDispatch() - const cart = useSelector((store: StateSchema) => store.cartEntity) + const dispatch = useAppDispatch() + const cart = useCartSelector() const [isLiked, setIsLiked] = useState(false) const [isInCompared, setIsInCompared] = useState(false) - const [isInCart, setIsInCart] = useState(false) + const isInCart = useIsProductInCart(product.slug, cart.cart.products) const [showPopup, setShowPopup] = useState(false) - useEffect(() => { - setIsInCart(isInCartBySlug(product.slug, cart.cart.products)) - }, [product.slug, cart.cart.products]) - const addThisToCart = () => { if (product.id) { dispatch(addToCart({ product: product.id, cart: cart.cart.id, amount: 1 })) @@ -59,7 +54,7 @@ export const Product: FC = ({ product }) => { } const handleQuickPurchase = () => { - console.log(cart) + //TODO реализовать форму быстрого заказа } return ( diff --git a/src/widgets/ProductItem/CardPreview/CardPreview.module.scss b/src/widgets/ProductItem/CardPreview/CardPreview.module.scss index 95a77ed1..78dad8d1 100644 --- a/src/widgets/ProductItem/CardPreview/CardPreview.module.scss +++ b/src/widgets/ProductItem/CardPreview/CardPreview.module.scss @@ -2,6 +2,7 @@ .modal-card { margin: 0 50px; + padding: 0 10px; display: flex; align-items: normal; gap: 18px; @@ -49,7 +50,7 @@ .customButton { max-width: 200px; - height: 50px; + min-height: 50px; padding: 5px; display: flex; align-items: center; diff --git a/src/widgets/ProductItem/CardPreview/CardPreview.tsx b/src/widgets/ProductItem/CardPreview/CardPreview.tsx index 78545619..903991cb 100644 --- a/src/widgets/ProductItem/CardPreview/CardPreview.tsx +++ b/src/widgets/ProductItem/CardPreview/CardPreview.tsx @@ -1,11 +1,9 @@ -import { FC, lazy, useState, Suspense, useEffect } from 'react' -import { useDispatch, useSelector } from 'react-redux' +import { type FC, lazy, useState, Suspense } from 'react' import { useNavigate } from 'react-router-dom' -import { StateSchema } from '@/app/providers/StoreProvider' -import { AppDispatch } from '@/app/providers/StoreProvider/config/store' import IconCart from '@/assets/icons/IconCart.svg' -import { isInCartBySlug } from '@/entities/CartEntity/model/functions/cartHelper' +import { useIsProductInCart } from '@/entities/CartEntity/model/hooks/cartHooks' +import { useCartSelector } from '@/entities/CartEntity/model/hooks/sliceHooks' import { addToCart } from '@/entities/CartEntity/model/slice/cartEntitySlice' import { CardPreviewFooter } from '@/features/CardPreviewFooter/CardPreviewFooter' import { CardPreviewHeader } from '@/features/CardPreviewHeader/CardPreviewHeader' @@ -13,6 +11,7 @@ import { ProductAvailability } from '@/features/ProductAvailability/ProductAvail import { ProductImgCarousel } from '@/features/ProductImgCarousel/ProductImgCarousel' import type { TImgList } from '@/pages/ProductsPage/types/types' import { Routes } from '@/shared/config/routerConfig/routes' +import { useAppDispatch } from '@/shared/libs/hooks/store' import { Button, ButtonSize, ButtonTheme } from '@/shared/ui/Button/Button' import Modal from '@/shared/ui/Modal/Modal' import Paragraph from '@/shared/ui/Paragraph/Paragraph' @@ -44,20 +43,16 @@ type Props = { */ export const CardPreview: FC = ({ code, images, slug, brand, quantity, price, id }) => { const navigate = useNavigate() - const dispatch = useDispatch() - const cart = useSelector((store: StateSchema) => store.cartEntity) + const dispatch = useAppDispatch() + const cart = useCartSelector() - const [isInCart, setIsInCart] = useState(false) + const isInCart = useIsProductInCart(slug, cart.cart.products) const [isLiked, setIsLiked] = useState(false) const [isInCompared, setIsInCompared] = useState(false) const [isModalOpen, setIsModalOpen] = useState(false) const [isModalClosing, setIsModalClosing] = useState(false) const [showPopup, setShowPopup] = useState(false) - useEffect(() => { - setIsInCart(isInCartBySlug(slug, cart.cart.products)) - }, [slug, cart.cart.products]) - const addThisToCart = () => { if (id) { dispatch(addToCart({ product: id, cart: cart.cart.id, amount: 1 })) @@ -121,8 +116,6 @@ export const CardPreview: FC = ({ code, images, slug, brand, quantity, pr />
- {/* @TODO: Завести shared/ui-компоненты под типографику - https://github.com/Studio-Yandex-Practicum/maxboom_frontend/issues/77 */} {price} ₽ {quantity} или более {price} ₽ diff --git a/src/widgets/ProductItem/ProductItem.tsx b/src/widgets/ProductItem/ProductItem.tsx index 8050bd28..87bab645 100644 --- a/src/widgets/ProductItem/ProductItem.tsx +++ b/src/widgets/ProductItem/ProductItem.tsx @@ -1,18 +1,17 @@ import classnames from 'classnames' -import { FC, useEffect, useState } from 'react' -import { useDispatch, useSelector } from 'react-redux' +import { type FC, useState } from 'react' import { Link, useNavigate } from 'react-router-dom' -import { StateSchema } from '@/app/providers/StoreProvider' -import { AppDispatch } from '@/app/providers/StoreProvider/config/store' -import { isInCartBySlug } from '@/entities/CartEntity/model/functions/cartHelper' +import { useIsProductInCart } from '@/entities/CartEntity/model/hooks/cartHooks' +import { useCartSelector } from '@/entities/CartEntity/model/hooks/sliceHooks' import { addToCart } from '@/entities/CartEntity/model/slice/cartEntitySlice' import { ProductAvailability } from '@/features/ProductAvailability/ProductAvailability' import { WidgetButtonsFunctions } from '@/features/WidgetButtonsFunctions/WidgetButtonsFunctions' import { WidgetButtonsPurchase } from '@/features/WidgetButtonsPurchase/WidgetButtonsPurchase' -import { TImgList } from '@/pages/ProductsPage/types/types' +import type { TImgList } from '@/pages/ProductsPage/types/types' import { Routes } from '@/shared/config/routerConfig/routes' import { handleCutDescription } from '@/shared/libs/helpers/handleCutDescription' +import { useAppDispatch } from '@/shared/libs/hooks/store' import { ECardView } from '@/shared/model/types/common' import Heading, { HeadingType } from '@/shared/ui/Heading/Heading' import Modal from '@/shared/ui/Modal/Modal' @@ -69,20 +68,15 @@ export const ProductItem: FC = ({ id }) => { const navigate = useNavigate() - const dispatch = useDispatch() - const cart = useSelector((store: StateSchema) => store.cartEntity) + const dispatch = useAppDispatch() + const cart = useCartSelector() - const [isInCart, setIsInCart] = useState(false) + const isInCart = useIsProductInCart(slug, cart.cart.products) const [isLiked, setIsLiked] = useState(false) const [isInCompared, setIsInCompared] = useState(false) - const [isModalOpen, setIsModalOpen] = useState(false) const [isModalClosing, setIsModalClosing] = useState(false) - useEffect(() => { - setIsInCart(isInCartBySlug(slug, cart.cart.products)) - }, [slug, cart.cart.products]) - const addThisToCart = () => { if (id) { dispatch(addToCart({ product: id, cart: cart.cart.id, amount: 1 })) From ffc0d4bcf05be9987fed37f8e439190253fac6cf Mon Sep 17 00:00:00 2001 From: kirill-k88 Date: Mon, 22 Apr 2024 14:49:19 +0300 Subject: [PATCH 9/9] =?UTF-8?q?=D0=92=D1=8B=D0=BD=D0=B5=D1=81=20=D0=B2?= =?UTF-8?q?=D1=81=D0=BF=D0=BE=D0=BC=D0=BE=D0=B3=D0=B0=D1=82=D0=B5=D0=BB?= =?UTF-8?q?=D1=8C=D0=BD=D1=8B=D0=B5=20=D1=84=D1=83=D0=BD=D0=BA=D1=86=D0=B8?= =?UTF-8?q?=D0=B8=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D1=8F=20=D0=B2=20=D0=BA=D0=BE=D1=80=D0=B7=D0=B8=D0=BD=D1=83=20?= =?UTF-8?q?=D1=81=20=D1=85=D1=83=D0=BA.=20=D0=BF=D0=BE=D0=BF=D1=80=D0=B0?= =?UTF-8?q?=D0=B2=D0=B8=D0=BB=20=D0=B4=D0=BE=D0=BA=D1=83=D0=BC=D0=B5=D0=BD?= =?UTF-8?q?=D1=82=D0=B0=D1=86=D0=B8=D1=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CartEntity/model/hooks/cartHooks.ts | 38 +++++++++++++++---- src/widgets/Product/Product.tsx | 29 ++------------ .../ProductItem/CardPreview/CardPreview.tsx | 25 ++---------- src/widgets/ProductItem/ProductItem.tsx | 27 ++----------- 4 files changed, 40 insertions(+), 79 deletions(-) diff --git a/src/entities/CartEntity/model/hooks/cartHooks.ts b/src/entities/CartEntity/model/hooks/cartHooks.ts index e461a80c..c50c7a4a 100644 --- a/src/entities/CartEntity/model/hooks/cartHooks.ts +++ b/src/entities/CartEntity/model/hooks/cartHooks.ts @@ -1,21 +1,45 @@ import { useEffect, useState } from 'react' +import { useNavigate } from 'react-router' + +import { Routes } from '@/shared/config/routerConfig/routes' +import { useAppDispatch } from '@/shared/libs/hooks/store' import { isInCartBySlug } from '../functions/cartHelper' -import { ICartProduct } from '../types/types' +import { addToCart } from '../slice/cartEntitySlice' + +import { useCartSelector } from './sliceHooks' /** * Hook для отслеживания есть ли товар в корзине * * @param {string} slug - slug продукта - * @param {ICartProduct[]} productsInCart - массив продуктов в корзине - * @returns boolean - возвращает стейт isInCart со значением true/false + * @param {number} id - id продукта + * @returns {object} - {isInCart, handleAddToCart} возвращает стейт isInCart со значением true/false и функцию добавления товара в текущую корзину */ -export const useIsProductInCart = (slug: string, productsInCart: ICartProduct[]) => { +export const useProductInCart = (slug: string, id: number) => { + const cart = useCartSelector() + const dispatch = useAppDispatch() + const navigate = useNavigate() + const [isInCart, setIsInCart] = useState(false) useEffect(() => { - setIsInCart(isInCartBySlug(slug, productsInCart)) - }, [slug, productsInCart]) + setIsInCart(isInCartBySlug(slug, cart.cart.products)) + }, [slug, cart.cart.products]) + + const addThisToCart = () => { + if (id) { + dispatch(addToCart({ product: id, cart: cart.cart.id, amount: 1 })) + } + } + + const handleAddToCart = () => { + if (!isInCart) { + addThisToCart() + } else { + navigate(Routes.CART) + } + } - return isInCart + return { isInCart, handleAddToCart } } diff --git a/src/widgets/Product/Product.tsx b/src/widgets/Product/Product.tsx index 80af1477..94b93de2 100644 --- a/src/widgets/Product/Product.tsx +++ b/src/widgets/Product/Product.tsx @@ -1,15 +1,10 @@ import { type FC, useState } from 'react' -import { useNavigate } from 'react-router' import IconCart from '@/assets/icons/IconCart.svg' -import { useIsProductInCart } from '@/entities/CartEntity/model/hooks/cartHooks' -import { useCartSelector } from '@/entities/CartEntity/model/hooks/sliceHooks' -import { addToCart } from '@/entities/CartEntity/model/slice/cartEntitySlice' +import { useProductInCart } from '@/entities/CartEntity/model/hooks/cartHooks' import { CardPreviewHeader } from '@/features/CardPreviewHeader/CardPreviewHeader' import { ProductAvailability } from '@/features/ProductAvailability/ProductAvailability' import { ProductImgCarousel } from '@/features/ProductImgCarousel/ProductImgCarousel' -import { Routes } from '@/shared/config/routerConfig/routes' -import { useAppDispatch } from '@/shared/libs/hooks/store' import { Button, ButtonSize, ButtonTheme } from '@/shared/ui/Button/Button' import Paragraph from '@/shared/ui/Paragraph/Paragraph' @@ -19,24 +14,14 @@ import { PopupImg } from './ui/PopupImg/PopupImg' /** * Контейнер для карточки товара на странице товара - * @param product TProductProps - информация о выбранном товаре + * @param {TProductProps} product - информация о выбранном товаре */ export const Product: FC = ({ product }) => { - const navigate = useNavigate() - const dispatch = useAppDispatch() - const cart = useCartSelector() - const [isLiked, setIsLiked] = useState(false) const [isInCompared, setIsInCompared] = useState(false) - const isInCart = useIsProductInCart(product.slug, cart.cart.products) + const { isInCart, handleAddToCart } = useProductInCart(product.slug, product.id) const [showPopup, setShowPopup] = useState(false) - const addThisToCart = () => { - if (product.id) { - dispatch(addToCart({ product: product.id, cart: cart.cart.id, amount: 1 })) - } - } - const handleLike = () => { setIsLiked(!isLiked) } @@ -45,14 +30,6 @@ export const Product: FC = ({ product }) => { setIsInCompared(!isInCompared) } - const handleAddToCart = () => { - if (!isInCart) { - addThisToCart() - } else { - navigate(Routes.CART) - } - } - const handleQuickPurchase = () => { //TODO реализовать форму быстрого заказа } diff --git a/src/widgets/ProductItem/CardPreview/CardPreview.tsx b/src/widgets/ProductItem/CardPreview/CardPreview.tsx index 903991cb..cb999475 100644 --- a/src/widgets/ProductItem/CardPreview/CardPreview.tsx +++ b/src/widgets/ProductItem/CardPreview/CardPreview.tsx @@ -2,16 +2,13 @@ import { type FC, lazy, useState, Suspense } from 'react' import { useNavigate } from 'react-router-dom' import IconCart from '@/assets/icons/IconCart.svg' -import { useIsProductInCart } from '@/entities/CartEntity/model/hooks/cartHooks' -import { useCartSelector } from '@/entities/CartEntity/model/hooks/sliceHooks' -import { addToCart } from '@/entities/CartEntity/model/slice/cartEntitySlice' +import { useProductInCart } from '@/entities/CartEntity/model/hooks/cartHooks' import { CardPreviewFooter } from '@/features/CardPreviewFooter/CardPreviewFooter' import { CardPreviewHeader } from '@/features/CardPreviewHeader/CardPreviewHeader' import { ProductAvailability } from '@/features/ProductAvailability/ProductAvailability' import { ProductImgCarousel } from '@/features/ProductImgCarousel/ProductImgCarousel' import type { TImgList } from '@/pages/ProductsPage/types/types' import { Routes } from '@/shared/config/routerConfig/routes' -import { useAppDispatch } from '@/shared/libs/hooks/store' import { Button, ButtonSize, ButtonTheme } from '@/shared/ui/Button/Button' import Modal from '@/shared/ui/Modal/Modal' import Paragraph from '@/shared/ui/Paragraph/Paragraph' @@ -40,33 +37,17 @@ type Props = { * @param {string} slug - URL для страницы товара; * @param {TImgList} images - массив с изображениями; * @param {number} quantity - количество на склаладе (если > 0, то товар считается в наличии); + * @param {number} id - id товара в Backend; */ export const CardPreview: FC = ({ code, images, slug, brand, quantity, price, id }) => { const navigate = useNavigate() - const dispatch = useAppDispatch() - const cart = useCartSelector() - - const isInCart = useIsProductInCart(slug, cart.cart.products) + const { isInCart, handleAddToCart } = useProductInCart(slug, id) const [isLiked, setIsLiked] = useState(false) const [isInCompared, setIsInCompared] = useState(false) const [isModalOpen, setIsModalOpen] = useState(false) const [isModalClosing, setIsModalClosing] = useState(false) const [showPopup, setShowPopup] = useState(false) - const addThisToCart = () => { - if (id) { - dispatch(addToCart({ product: id, cart: cart.cart.id, amount: 1 })) - } - } - - const handleAddToCart = () => { - if (!isInCart) { - addThisToCart() - } else { - navigate(Routes.CART) - } - } - const handleQuickPurchase = () => { setIsModalOpen(true) } diff --git a/src/widgets/ProductItem/ProductItem.tsx b/src/widgets/ProductItem/ProductItem.tsx index 87bab645..92c02fa4 100644 --- a/src/widgets/ProductItem/ProductItem.tsx +++ b/src/widgets/ProductItem/ProductItem.tsx @@ -1,17 +1,14 @@ import classnames from 'classnames' import { type FC, useState } from 'react' -import { Link, useNavigate } from 'react-router-dom' +import { Link } from 'react-router-dom' -import { useIsProductInCart } from '@/entities/CartEntity/model/hooks/cartHooks' -import { useCartSelector } from '@/entities/CartEntity/model/hooks/sliceHooks' -import { addToCart } from '@/entities/CartEntity/model/slice/cartEntitySlice' +import { useProductInCart } from '@/entities/CartEntity/model/hooks/cartHooks' import { ProductAvailability } from '@/features/ProductAvailability/ProductAvailability' import { WidgetButtonsFunctions } from '@/features/WidgetButtonsFunctions/WidgetButtonsFunctions' import { WidgetButtonsPurchase } from '@/features/WidgetButtonsPurchase/WidgetButtonsPurchase' import type { TImgList } from '@/pages/ProductsPage/types/types' import { Routes } from '@/shared/config/routerConfig/routes' import { handleCutDescription } from '@/shared/libs/helpers/handleCutDescription' -import { useAppDispatch } from '@/shared/libs/hooks/store' import { ECardView } from '@/shared/model/types/common' import Heading, { HeadingType } from '@/shared/ui/Heading/Heading' import Modal from '@/shared/ui/Modal/Modal' @@ -67,34 +64,16 @@ export const ProductItem: FC = ({ quantity, id }) => { - const navigate = useNavigate() - const dispatch = useAppDispatch() - const cart = useCartSelector() - - const isInCart = useIsProductInCart(slug, cart.cart.products) + const { isInCart, handleAddToCart } = useProductInCart(slug, id) const [isLiked, setIsLiked] = useState(false) const [isInCompared, setIsInCompared] = useState(false) const [isModalOpen, setIsModalOpen] = useState(false) const [isModalClosing, setIsModalClosing] = useState(false) - const addThisToCart = () => { - if (id) { - dispatch(addToCart({ product: id, cart: cart.cart.id, amount: 1 })) - } - } - const changeModalState = () => { setIsModalOpen(!isModalOpen) } - const handleAddToCart = () => { - if (!isInCart) { - addThisToCart() - } else { - navigate(Routes.CART) - } - } - const handleLike = () => { setIsLiked(!isLiked) }