From e50190cf751ff9e351b2ebce9929e1c81624c4a3 Mon Sep 17 00:00:00 2001 From: kirill-k88 Date: Sun, 21 Apr 2024 12:55:43 +0300 Subject: [PATCH 1/7] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB?= =?UTF-8?q?=20breadcrumbs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/FavoritesPage/FavoritesPage.module.scss | 7 +++++-- src/pages/FavoritesPage/FavoritesPage.tsx | 11 ++++++++++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/pages/FavoritesPage/FavoritesPage.module.scss b/src/pages/FavoritesPage/FavoritesPage.module.scss index a97c1975..d4a58af9 100644 --- a/src/pages/FavoritesPage/FavoritesPage.module.scss +++ b/src/pages/FavoritesPage/FavoritesPage.module.scss @@ -1,3 +1,6 @@ -.heading { - align-self: flex-start; +.pageDescriptor { + width: 100%; + display: flex; + flex-direction: column; + gap: 10px; } diff --git a/src/pages/FavoritesPage/FavoritesPage.tsx b/src/pages/FavoritesPage/FavoritesPage.tsx index 2e426205..555b1ecc 100644 --- a/src/pages/FavoritesPage/FavoritesPage.tsx +++ b/src/pages/FavoritesPage/FavoritesPage.tsx @@ -1,4 +1,5 @@ import WrapperForMainContent from '@/components/WrapperForMainContent/WrapperForMainContent' +import Breadcrumbs from '@/shared/ui/Breadcrumbs/Breadcrumbs' import Heading from '@/shared/ui/Heading/Heading' import Subheading from '@/shared/ui/Subheading/Subheading' @@ -8,9 +9,17 @@ import styles from './FavoritesPage.module.scss' * Страница с избранными товарами */ const FavoritesPage = () => { + const links = [ + { heading: 'Главная', href: '/' }, + { heading: 'Избранные товары', href: '' } + ] + return ( - Избранные товары +
+ Избранные товары + +
В разработке
) From d5ba4315b61ae5d1d02bbd0504077e2f6bb604de Mon Sep 17 00:00:00 2001 From: kirill-k88 Date: Sun, 21 Apr 2024 14:43:33 +0300 Subject: [PATCH 2/7] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB?= =?UTF-8?q?=20=D1=81=D0=B5=D1=82=D0=BA=D1=83=20=D1=81=20=D0=B8=D0=B7=D0=B1?= =?UTF-8?q?=D0=B0=D1=80=D0=B0=D0=BD=D0=BD=D1=8B=D0=BC=D0=B8=20=D1=82=D0=BE?= =?UTF-8?q?=D0=B2=D0=B0=D1=80=D0=B0=D0=BC=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../FavoritesPage/FavoritesPage.module.scss | 7 ++++++ src/pages/FavoritesPage/FavoritesPage.tsx | 18 ++++++++++++-- .../model/functions/functions.ts | 17 +++++++++++++ src/pages/FavoritesPage/model/types/types.ts | 24 +++++++++++++++++++ src/shared/constants/sessionStorage.ts | 3 ++- 5 files changed, 66 insertions(+), 3 deletions(-) create mode 100644 src/pages/FavoritesPage/model/functions/functions.ts create mode 100644 src/pages/FavoritesPage/model/types/types.ts diff --git a/src/pages/FavoritesPage/FavoritesPage.module.scss b/src/pages/FavoritesPage/FavoritesPage.module.scss index d4a58af9..d4eddf68 100644 --- a/src/pages/FavoritesPage/FavoritesPage.module.scss +++ b/src/pages/FavoritesPage/FavoritesPage.module.scss @@ -4,3 +4,10 @@ flex-direction: column; gap: 10px; } + +.favoritePage_list { + max-width: calc(292px * 3 + 60px); + display: flex; + flex-wrap: wrap; + gap: 30px; +} diff --git a/src/pages/FavoritesPage/FavoritesPage.tsx b/src/pages/FavoritesPage/FavoritesPage.tsx index 555b1ecc..69174aac 100644 --- a/src/pages/FavoritesPage/FavoritesPage.tsx +++ b/src/pages/FavoritesPage/FavoritesPage.tsx @@ -1,9 +1,11 @@ import WrapperForMainContent from '@/components/WrapperForMainContent/WrapperForMainContent' +import { ECardView } from '@/shared/model/types/common' import Breadcrumbs from '@/shared/ui/Breadcrumbs/Breadcrumbs' import Heading from '@/shared/ui/Heading/Heading' -import Subheading from '@/shared/ui/Subheading/Subheading' +import { ProductsList } from '@/widgets/ProductsList/ProductsList' import styles from './FavoritesPage.module.scss' +import { getFavoriteProductsFromStorage } from './model/functions/functions' /** * Страница с избранными товарами @@ -13,6 +15,7 @@ const FavoritesPage = () => { { heading: 'Главная', href: '/' }, { heading: 'Избранные товары', href: '' } ] + const favoriteProducts = getFavoriteProductsFromStorage() return ( @@ -20,7 +23,18 @@ const FavoritesPage = () => { Избранные товары - В разработке +
+ +
) } diff --git a/src/pages/FavoritesPage/model/functions/functions.ts b/src/pages/FavoritesPage/model/functions/functions.ts new file mode 100644 index 00000000..ea7b3c21 --- /dev/null +++ b/src/pages/FavoritesPage/model/functions/functions.ts @@ -0,0 +1,17 @@ +import { SESSION_STORAGE } from '@/shared/constants/sessionStorage' + +import { TProduct } from '../types/types' + +/** + * Функция возвращает список избранных товаров favoriteProducts из текущей сессии session storage. + */ +export const getFavoriteProductsFromStorage = (): TProduct[] => { + /* const favoriteProductsStr = sessionStorage.getItem(SESSION_STORAGE.FAVORITE) || '[]' + const favoriteProducts: TProduct[] = JSON.parse(favoriteProductsStr) as TProduct[] + + return favoriteProducts */ + const viewedProductsStr = sessionStorage.getItem(SESSION_STORAGE.VIEWED) || '[]' + const viewedProducts: TProduct[] = JSON.parse(viewedProductsStr) as TProduct[] + + return viewedProducts +} diff --git a/src/pages/FavoritesPage/model/types/types.ts b/src/pages/FavoritesPage/model/types/types.ts new file mode 100644 index 00000000..ce05e6bf --- /dev/null +++ b/src/pages/FavoritesPage/model/types/types.ts @@ -0,0 +1,24 @@ +export interface IObjectWithImage { + image: string + index?: number +} + +export type TImgList = Array + +export 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/shared/constants/sessionStorage.ts b/src/shared/constants/sessionStorage.ts index df984640..6d99ccd5 100644 --- a/src/shared/constants/sessionStorage.ts +++ b/src/shared/constants/sessionStorage.ts @@ -1,3 +1,4 @@ export enum SESSION_STORAGE { - VIEWED = 'viewedProducts' + VIEWED = 'viewedProducts', + FAVORITE = 'favoriteProducts' } From c63e3113c84540432bc22b2814730c786a26e3ba Mon Sep 17 00:00:00 2001 From: kirill-k88 Date: Sun, 21 Apr 2024 17:25:18 +0300 Subject: [PATCH 3/7] =?UTF-8?q?=D0=A1=D0=B4=D0=B5=D0=BB=D0=B0=D0=BB=20?= =?UTF-8?q?=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20?= =?UTF-8?q?=D0=B2=20session=20storage=20=D0=B8=20=D1=83=D0=B4=D0=B0=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Favorite/model/functions/functions.ts | 61 +++++++++++++++++ .../Favorite/model/hooks/useFavorite.ts | 19 ++++++ .../Favorite}/model/types/types.ts | 6 ++ src/pages/FavoritesPage/FavoritesPage.tsx | 20 +++++- .../model/functions/functions.ts | 11 +--- src/shared/constants/constants.ts | 3 + src/widgets/Product/Product.tsx | 17 +++-- src/widgets/ProductItem/ProductItem.tsx | 65 ++++++++++++++++++- 8 files changed, 187 insertions(+), 15 deletions(-) create mode 100644 src/entities/Favorite/model/functions/functions.ts create mode 100644 src/entities/Favorite/model/hooks/useFavorite.ts rename src/{pages/FavoritesPage => entities/Favorite}/model/types/types.ts (80%) diff --git a/src/entities/Favorite/model/functions/functions.ts b/src/entities/Favorite/model/functions/functions.ts new file mode 100644 index 00000000..b9d57e6b --- /dev/null +++ b/src/entities/Favorite/model/functions/functions.ts @@ -0,0 +1,61 @@ +import { FAVORITE_PRODUCTS_LIMIT } from '@/shared/constants/constants' +import { SESSION_STORAGE } from '@/shared/constants/sessionStorage' + +import { TProduct } from '../types/types' + +/** + * Ф-я проверяет наличие продукта в массив избраных продуктов в session storage + * @param {TProduct} product продукт + * @return {boolean} true/false в зависимости от нахождения в избранном + */ +export const isInFavoriteProducts = (product: TProduct): boolean => { + const favoriteProductsStr = sessionStorage.getItem(SESSION_STORAGE.FAVORITE) || '[]' + const favoriteProducts: TProduct[] = JSON.parse(favoriteProductsStr) as TProduct[] + + if (product && product.slug && includesProduct(product, favoriteProducts)) { + return true + } + + return false +} + +/** + * Ф-я добавляет продукт в массив избраных продуктов в session storage + * @param {TProduct} product продукт + */ +export const addToFavoriteProducts = (product: TProduct): void => { + const favoriteProductsStr = sessionStorage.getItem(SESSION_STORAGE.FAVORITE) || '[]' + const favoriteProducts: TProduct[] = JSON.parse(favoriteProductsStr) as TProduct[] + + if (product && product.slug && !includesProduct(product, favoriteProducts)) { + if (favoriteProducts.length === FAVORITE_PRODUCTS_LIMIT) { + favoriteProducts.shift() + } + favoriteProducts.push(product) + console.log(favoriteProducts) + sessionStorage.setItem(SESSION_STORAGE.FAVORITE, JSON.stringify(favoriteProducts)) + } +} + +/** + * Ф-я удаляет продукт из массива избраных продуктов в session storage, если он в нем есть + * @param {TProduct} product продукт + */ +export const removeFromFavoriteProducts = (product: TProduct): void => { + const favoriteProductsStr = sessionStorage.getItem(SESSION_STORAGE.FAVORITE) || '[]' + const favoriteProducts: TProduct[] = JSON.parse(favoriteProductsStr) as TProduct[] + + if (product && product.slug && includesProduct(product, favoriteProducts)) { + favoriteProducts.splice(indexOfProduct(product, favoriteProducts), 1) + + sessionStorage.setItem(SESSION_STORAGE.FAVORITE, JSON.stringify(favoriteProducts)) + } +} + +function includesProduct(product: TProduct, favoriteProducts: TProduct[]): boolean { + return favoriteProducts.some(p => p.slug === product.slug) +} + +function indexOfProduct(product: TProduct, favoriteProducts: TProduct[]): number { + return favoriteProducts.findIndex(p => p.slug === product.slug) +} diff --git a/src/entities/Favorite/model/hooks/useFavorite.ts b/src/entities/Favorite/model/hooks/useFavorite.ts new file mode 100644 index 00000000..e57aadf6 --- /dev/null +++ b/src/entities/Favorite/model/hooks/useFavorite.ts @@ -0,0 +1,19 @@ +import { useEffect, useState } from 'react' + +import { isInFavoriteProducts } from '../functions/functions' +import { TProduct } from '../types/types' + +/** + * Hook для хранения состояния продукта в избранном + * @param {TProduct} product - продукт + * @returns {isLiked, setIsLiked} - состояние нахождения продукта в избранном + */ +export const useFavorite = (product: TProduct) => { + const [isLiked, setIsLiked] = useState(isInFavoriteProducts(product)) + + useEffect(() => { + setIsLiked(isInFavoriteProducts(product)) + }, [product]) + + return { isLiked, setIsLiked } +} diff --git a/src/pages/FavoritesPage/model/types/types.ts b/src/entities/Favorite/model/types/types.ts similarity index 80% rename from src/pages/FavoritesPage/model/types/types.ts rename to src/entities/Favorite/model/types/types.ts index ce05e6bf..c8b1d290 100644 --- a/src/pages/FavoritesPage/model/types/types.ts +++ b/src/entities/Favorite/model/types/types.ts @@ -22,3 +22,9 @@ export type TProduct = { wholesale: number images: TImgList } + +export type TProductSchema = { + product: TProduct + isLoading?: boolean + error?: string | string[] +} diff --git a/src/pages/FavoritesPage/FavoritesPage.tsx b/src/pages/FavoritesPage/FavoritesPage.tsx index 69174aac..9ecc4f33 100644 --- a/src/pages/FavoritesPage/FavoritesPage.tsx +++ b/src/pages/FavoritesPage/FavoritesPage.tsx @@ -1,4 +1,7 @@ +import { useEffect, useState } from 'react' + import WrapperForMainContent from '@/components/WrapperForMainContent/WrapperForMainContent' +import { TProduct } from '@/entities/Favorite/model/types/types' import { ECardView } from '@/shared/model/types/common' import Breadcrumbs from '@/shared/ui/Breadcrumbs/Breadcrumbs' import Heading from '@/shared/ui/Heading/Heading' @@ -11,11 +14,26 @@ import { getFavoriteProductsFromStorage } from './model/functions/functions' * Страница с избранными товарами */ const FavoritesPage = () => { + const [favoriteProducts, setFavoriteProducts] = useState([]) + const links = [ { heading: 'Главная', href: '/' }, { heading: 'Избранные товары', href: '' } ] - const favoriteProducts = getFavoriteProductsFromStorage() + + useEffect(() => { + setFavoriteProducts(getFavoriteProductsFromStorage()) + window.addEventListener('storage', handleStorage) + + return () => { + window.removeEventListener('storage', handleStorage) + } + }, []) + + const handleStorage = () => { + console.log('хэндал стораджа') + setFavoriteProducts(getFavoriteProductsFromStorage()) + } return ( diff --git a/src/pages/FavoritesPage/model/functions/functions.ts b/src/pages/FavoritesPage/model/functions/functions.ts index ea7b3c21..92ad6dd9 100644 --- a/src/pages/FavoritesPage/model/functions/functions.ts +++ b/src/pages/FavoritesPage/model/functions/functions.ts @@ -1,17 +1,12 @@ +import type { TProduct } from '@/entities/Favorite/model/types/types' import { SESSION_STORAGE } from '@/shared/constants/sessionStorage' -import { TProduct } from '../types/types' - /** * Функция возвращает список избранных товаров favoriteProducts из текущей сессии session storage. */ export const getFavoriteProductsFromStorage = (): TProduct[] => { - /* const favoriteProductsStr = sessionStorage.getItem(SESSION_STORAGE.FAVORITE) || '[]' + const favoriteProductsStr = sessionStorage.getItem(SESSION_STORAGE.FAVORITE) || '[]' const favoriteProducts: TProduct[] = JSON.parse(favoriteProductsStr) as TProduct[] - return favoriteProducts */ - const viewedProductsStr = sessionStorage.getItem(SESSION_STORAGE.VIEWED) || '[]' - const viewedProducts: TProduct[] = JSON.parse(viewedProductsStr) as TProduct[] - - return viewedProducts + return favoriteProducts } diff --git a/src/shared/constants/constants.ts b/src/shared/constants/constants.ts index ecb8f8cc..50185809 100644 --- a/src/shared/constants/constants.ts +++ b/src/shared/constants/constants.ts @@ -51,6 +51,9 @@ export const REDUCER_CATEGORIES_PRODUCTS = 'shopCategoriesProducts' //Product page export const VIEWED_PRODUCTS_LIMIT = 10 +//Favorite page +export const FAVORITE_PRODUCTS_LIMIT = 10 + //Feedback form export const NAME_LENGTH_MIN_LIMIT = 2 export const NAME_LENGTH_MAX_LIMIT = 30 diff --git a/src/widgets/Product/Product.tsx b/src/widgets/Product/Product.tsx index 57c15ba4..30adef09 100644 --- a/src/widgets/Product/Product.tsx +++ b/src/widgets/Product/Product.tsx @@ -1,6 +1,11 @@ import { type FC, useState } from 'react' import IconCart from '@/assets/icons/IconCart.svg' +import { + addToFavoriteProducts, + removeFromFavoriteProducts +} from '@/entities/Favorite/model/functions/functions' +import { useFavorite } from '@/entities/Favorite/model/hooks/useFavorite' import { CardPreviewHeader } from '@/features/CardPreviewHeader/CardPreviewHeader' import { ProductAvailability } from '@/features/ProductAvailability/ProductAvailability' import { ProductImgCarousel } from '@/features/ProductImgCarousel/ProductImgCarousel' @@ -16,13 +21,19 @@ import { PopupImg } from './ui/PopupImg/PopupImg' * @param product TProductProps - информация о выбранном товаре */ export const Product: FC = ({ product }) => { - const [isLiked, setIsLiked] = useState(false) + const { isLiked, setIsLiked } = useFavorite(product) const [isInCompared, setIsInCompared] = useState(false) const [isInCart, setIsInCart] = useState(false) const [showPopup, setShowPopup] = useState(false) const handleLike = () => { - setIsLiked(!isLiked) + if (!isLiked) { + addToFavoriteProducts(product) + setIsLiked(true) + } else { + removeFromFavoriteProducts(product) + setIsLiked(false) + } } const handleAddToCompared = () => { @@ -49,8 +60,6 @@ export const Product: FC = ({ product }) => { />
- {/* @TODO: Завести shared/ui-компоненты под типографику - https://github.com/Studio-Yandex-Practicum/maxboom_frontend/issues/77 */}
{`${product.price} ₽`} diff --git a/src/widgets/ProductItem/ProductItem.tsx b/src/widgets/ProductItem/ProductItem.tsx index c8c6c979..011cff37 100644 --- a/src/widgets/ProductItem/ProductItem.tsx +++ b/src/widgets/ProductItem/ProductItem.tsx @@ -2,6 +2,11 @@ import classnames from 'classnames' import { FC, useState } from 'react' import { Link } from 'react-router-dom' +import { + addToFavoriteProducts, + removeFromFavoriteProducts +} from '@/entities/Favorite/model/functions/functions' +import { useFavorite } from '@/entities/Favorite/model/hooks/useFavorite' import { ProductAvailability } from '@/features/ProductAvailability/ProductAvailability' import { WidgetButtonsFunctions } from '@/features/WidgetButtonsFunctions/WidgetButtonsFunctions' import { WidgetButtonsPurchase } from '@/features/WidgetButtonsPurchase/WidgetButtonsPurchase' @@ -61,7 +66,23 @@ export const ProductItem: FC = ({ quantity }) => { const [isInCart, setIsInCart] = useState(false) - const [isLiked, setIsLiked] = useState(false) + const { isLiked, setIsLiked } = useFavorite({ + id: 123, + category: '', + wb_urls: '', + is_deleted: false, + wholesale: 0, + name, + price, + brand, + slug, + description, + code, + images, + label_popular, + label_hit, + quantity + }) const [isInCompared, setIsInCompared] = useState(false) const [isModalOpen, setIsModalOpen] = useState(false) @@ -70,12 +91,52 @@ export const ProductItem: FC = ({ const changeModalState = () => { setIsModalOpen(!isModalOpen) } + const handleAddToCart = () => { setIsInCart(!isInCart) } const handleLike = () => { - setIsLiked(!isLiked) + if (!isLiked) { + //TODO часть свойств зглушка, доделать после мерджа с добавлением в корзину + addToFavoriteProducts({ + id: 123, + category: '', + wb_urls: '', + is_deleted: false, + wholesale: 0, + name, + price, + brand, + slug, + description, + code, + images, + label_popular, + label_hit, + quantity + }) + setIsLiked(true) + } else { + removeFromFavoriteProducts({ + id: 123, + category: '', + wb_urls: '', + is_deleted: false, + wholesale: 0, + name, + price, + brand, + slug, + description, + code, + images, + label_popular, + label_hit, + quantity + }) + setIsLiked(false) + } } const handleAddToCompared = () => { From 2f4e62b2ffa9cdc0af3df4aa1092d9dcacdfee4a Mon Sep 17 00:00:00 2001 From: kirill-k88 Date: Mon, 22 Apr 2024 20:23:13 +0300 Subject: [PATCH 4/7] =?UTF-8?q?=D0=94=D0=BE=D0=B0=D0=B1=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=20=D0=BF=D1=80=D0=BE=D1=81=D0=BB=D1=83=D1=88=D0=B8=D0=B2?= =?UTF-8?q?=D0=B0=D0=BD=D0=B8=D0=B5=20=D1=81=D0=BE=D0=B1=D1=8B=D1=82=D0=B8?= =?UTF-8?q?=D1=8F=20=D0=B8=D0=B7=D0=BC=D0=B5=D0=BD=D0=B5=D0=BD=D0=B8=D1=8F?= =?UTF-8?q?=20storage=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=D0=B8=D0=B7=D0=B1=D1=80=D0=B0=D0=BD=D0=BD?= =?UTF-8?q?=D0=BE=D0=B3=D0=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Favorite/model/functions/functions.ts | 22 +++++++++--- .../Favorite/model/hooks/useFavorite.ts | 29 ++++++++++------ .../Favorite/model/hooks/useWithFavorie.ts | 34 +++++++++++++++++++ src/pages/FavoritesPage/FavoritesPage.tsx | 21 ++---------- .../model/functions/functions.ts | 12 ------- src/widgets/Product/Product.tsx | 4 +-- src/widgets/ProductItem/ProductItem.tsx | 4 +-- 7 files changed, 77 insertions(+), 49 deletions(-) create mode 100644 src/entities/Favorite/model/hooks/useWithFavorie.ts delete mode 100644 src/pages/FavoritesPage/model/functions/functions.ts diff --git a/src/entities/Favorite/model/functions/functions.ts b/src/entities/Favorite/model/functions/functions.ts index b9d57e6b..76633d1e 100644 --- a/src/entities/Favorite/model/functions/functions.ts +++ b/src/entities/Favorite/model/functions/functions.ts @@ -4,7 +4,7 @@ import { SESSION_STORAGE } from '@/shared/constants/sessionStorage' import { TProduct } from '../types/types' /** - * Ф-я проверяет наличие продукта в массив избраных продуктов в session storage + * Ф-я проверяет наличие продукта в массив избранных продуктов в session storage * @param {TProduct} product продукт * @return {boolean} true/false в зависимости от нахождения в избранном */ @@ -20,7 +20,7 @@ export const isInFavoriteProducts = (product: TProduct): boolean => { } /** - * Ф-я добавляет продукт в массив избраных продуктов в session storage + * Ф-я добавляет продукт в массив избранных продуктов в session storage * @param {TProduct} product продукт */ export const addToFavoriteProducts = (product: TProduct): void => { @@ -32,13 +32,14 @@ export const addToFavoriteProducts = (product: TProduct): void => { favoriteProducts.shift() } favoriteProducts.push(product) - console.log(favoriteProducts) + sessionStorage.setItem(SESSION_STORAGE.FAVORITE, JSON.stringify(favoriteProducts)) + window.dispatchEvent(new Event('storage')) } } /** - * Ф-я удаляет продукт из массива избраных продуктов в session storage, если он в нем есть + * Ф-я удаляет продукт из массива избранных продуктов в session storage, если он в нем есть * @param {TProduct} product продукт */ export const removeFromFavoriteProducts = (product: TProduct): void => { @@ -49,6 +50,7 @@ export const removeFromFavoriteProducts = (product: TProduct): void => { favoriteProducts.splice(indexOfProduct(product, favoriteProducts), 1) sessionStorage.setItem(SESSION_STORAGE.FAVORITE, JSON.stringify(favoriteProducts)) + window.dispatchEvent(new Event('storage')) } } @@ -59,3 +61,15 @@ function includesProduct(product: TProduct, favoriteProducts: TProduct[]): boole function indexOfProduct(product: TProduct, favoriteProducts: TProduct[]): number { return favoriteProducts.findIndex(p => p.slug === product.slug) } + +/** + * Функция возвращает список избранных товаров favoriteProducts из текущей сессии session storage. + * + * @return {TProduct[]} - массив продуктов в избранном + */ +export const getFavoriteProductsFromStorage = (): TProduct[] => { + const favoriteProductsStr = sessionStorage.getItem(SESSION_STORAGE.FAVORITE) || '[]' + const favoriteProducts: TProduct[] = JSON.parse(favoriteProductsStr) as TProduct[] + + return favoriteProducts +} diff --git a/src/entities/Favorite/model/hooks/useFavorite.ts b/src/entities/Favorite/model/hooks/useFavorite.ts index e57aadf6..a22fcbd4 100644 --- a/src/entities/Favorite/model/hooks/useFavorite.ts +++ b/src/entities/Favorite/model/hooks/useFavorite.ts @@ -1,19 +1,28 @@ import { useEffect, useState } from 'react' -import { isInFavoriteProducts } from '../functions/functions' -import { TProduct } from '../types/types' +import { getFavoriteProductsFromStorage } from '../functions/functions' +import type { TProduct } from '../types/types' /** - * Hook для хранения состояния продукта в избранном - * @param {TProduct} product - продукт - * @returns {isLiked, setIsLiked} - состояние нахождения продукта в избранном + * Hook для получения продуктов из избранного + * + * @returns {TProduct[]} состояние favoriteProducts с массивом продуктов в избранном */ -export const useFavorite = (product: TProduct) => { - const [isLiked, setIsLiked] = useState(isInFavoriteProducts(product)) +export const useFavorite = () => { + const [favoriteProducts, setFavoriteProducts] = useState([]) useEffect(() => { - setIsLiked(isInFavoriteProducts(product)) - }, [product]) + setFavoriteProducts(getFavoriteProductsFromStorage()) + window.addEventListener('storage', handleStorage) - return { isLiked, setIsLiked } + return () => { + window.removeEventListener('storage', handleStorage) + } + }, []) + + const handleStorage = () => { + setFavoriteProducts(getFavoriteProductsFromStorage()) + } + + return favoriteProducts } diff --git a/src/entities/Favorite/model/hooks/useWithFavorie.ts b/src/entities/Favorite/model/hooks/useWithFavorie.ts new file mode 100644 index 00000000..b0adafcc --- /dev/null +++ b/src/entities/Favorite/model/hooks/useWithFavorie.ts @@ -0,0 +1,34 @@ +import { useEffect, useState } from 'react' + +import { + addToFavoriteProducts, + isInFavoriteProducts, + removeFromFavoriteProducts +} from '../functions/functions' +import type { TProduct } from '../types/types' + +/** + * Hook для добавления/удаления, проверки наличия продукта в избранном + * + * @param {TProduct} product - продукт + * @returns {object} - состояние isLiked нахождения продукта в избранном и функцию handleLike для добавления/удаления в/из избранное + */ +export const useWithFavorite = (product: TProduct) => { + const [isLiked, setIsLiked] = useState(isInFavoriteProducts(product)) + + useEffect(() => { + setIsLiked(isInFavoriteProducts(product)) + }, [product]) + + const handleLike = () => { + if (!isLiked) { + addToFavoriteProducts(product) + setIsLiked(true) + } else { + removeFromFavoriteProducts(product) + setIsLiked(false) + } + } + + return { isLiked, handleLike } +} diff --git a/src/pages/FavoritesPage/FavoritesPage.tsx b/src/pages/FavoritesPage/FavoritesPage.tsx index 9ecc4f33..68e93f94 100644 --- a/src/pages/FavoritesPage/FavoritesPage.tsx +++ b/src/pages/FavoritesPage/FavoritesPage.tsx @@ -1,40 +1,23 @@ -import { useEffect, useState } from 'react' - import WrapperForMainContent from '@/components/WrapperForMainContent/WrapperForMainContent' -import { TProduct } from '@/entities/Favorite/model/types/types' +import { useFavorite } from '@/entities/Favorite/model/hooks/useFavorite' import { ECardView } from '@/shared/model/types/common' import Breadcrumbs from '@/shared/ui/Breadcrumbs/Breadcrumbs' import Heading from '@/shared/ui/Heading/Heading' import { ProductsList } from '@/widgets/ProductsList/ProductsList' import styles from './FavoritesPage.module.scss' -import { getFavoriteProductsFromStorage } from './model/functions/functions' /** * Страница с избранными товарами */ const FavoritesPage = () => { - const [favoriteProducts, setFavoriteProducts] = useState([]) + const favoriteProducts = useFavorite() const links = [ { heading: 'Главная', href: '/' }, { heading: 'Избранные товары', href: '' } ] - useEffect(() => { - setFavoriteProducts(getFavoriteProductsFromStorage()) - window.addEventListener('storage', handleStorage) - - return () => { - window.removeEventListener('storage', handleStorage) - } - }, []) - - const handleStorage = () => { - console.log('хэндал стораджа') - setFavoriteProducts(getFavoriteProductsFromStorage()) - } - return (
diff --git a/src/pages/FavoritesPage/model/functions/functions.ts b/src/pages/FavoritesPage/model/functions/functions.ts deleted file mode 100644 index 92ad6dd9..00000000 --- a/src/pages/FavoritesPage/model/functions/functions.ts +++ /dev/null @@ -1,12 +0,0 @@ -import type { TProduct } from '@/entities/Favorite/model/types/types' -import { SESSION_STORAGE } from '@/shared/constants/sessionStorage' - -/** - * Функция возвращает список избранных товаров favoriteProducts из текущей сессии session storage. - */ -export const getFavoriteProductsFromStorage = (): TProduct[] => { - const favoriteProductsStr = sessionStorage.getItem(SESSION_STORAGE.FAVORITE) || '[]' - const favoriteProducts: TProduct[] = JSON.parse(favoriteProductsStr) as TProduct[] - - return favoriteProducts -} diff --git a/src/widgets/Product/Product.tsx b/src/widgets/Product/Product.tsx index fc10e853..119e96c1 100644 --- a/src/widgets/Product/Product.tsx +++ b/src/widgets/Product/Product.tsx @@ -2,7 +2,7 @@ import { type FC, useState } from 'react' import IconCart from '@/assets/icons/IconCart.svg' import { useProductInCart } from '@/entities/CartEntity/model/hooks/cartHooks' -import { useFavorite } from '@/entities/Favorite/model/hooks/useFavorite' +import { useWithFavorite } from '@/entities/Favorite/model/hooks/useWithFavorie' import { CardPreviewHeader } from '@/features/CardPreviewHeader/CardPreviewHeader' import { ProductAvailability } from '@/features/ProductAvailability/ProductAvailability' import { ProductImgCarousel } from '@/features/ProductImgCarousel/ProductImgCarousel' @@ -18,7 +18,7 @@ import { PopupImg } from './ui/PopupImg/PopupImg' * @param {TProductProps} product - информация о выбранном товаре */ export const Product: FC = ({ product }) => { - const { isLiked, handleLike } = useFavorite(product) + const { isLiked, handleLike } = useWithFavorite(product) const [isInCompared, setIsInCompared] = useState(false) const { isInCart, handleAddToCart } = useProductInCart(product.slug, product.id) const [showPopup, setShowPopup] = useState(false) diff --git a/src/widgets/ProductItem/ProductItem.tsx b/src/widgets/ProductItem/ProductItem.tsx index 97f9cc93..280f06ec 100644 --- a/src/widgets/ProductItem/ProductItem.tsx +++ b/src/widgets/ProductItem/ProductItem.tsx @@ -3,7 +3,7 @@ import { type FC, useState } from 'react' import { Link } from 'react-router-dom' import { useProductInCart } from '@/entities/CartEntity/model/hooks/cartHooks' -import { useFavorite } from '@/entities/Favorite/model/hooks/useFavorite' +import { useWithFavorite } from '@/entities/Favorite/model/hooks/useWithFavorie' import { ProductAvailability } from '@/features/ProductAvailability/ProductAvailability' import { WidgetButtonsFunctions } from '@/features/WidgetButtonsFunctions/WidgetButtonsFunctions' import { WidgetButtonsPurchase } from '@/features/WidgetButtonsPurchase/WidgetButtonsPurchase' @@ -69,7 +69,7 @@ export const ProductItem: FC = ({ const [isModalOpen, setIsModalOpen] = useState(false) const [isModalClosing, setIsModalClosing] = useState(false) const { isInCart, handleAddToCart } = useProductInCart(slug, id) - const { isLiked, handleLike } = useFavorite({ + const { isLiked, handleLike } = useWithFavorite({ id, category: '', wb_urls: '', From aa09b137d8baac57b650845415fa766e1f04e990 Mon Sep 17 00:00:00 2001 From: kirill-k88 Date: Mon, 6 May 2024 19:44:05 +0300 Subject: [PATCH 5/7] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB?= =?UTF-8?q?=20=D0=BC=D0=B5=D0=BD=D1=8E.=20=D0=9D=D0=B5=D0=BC=D0=BD=D0=BE?= =?UTF-8?q?=D0=B3=D0=BE=20=D0=B8=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=B8=D0=BB?= =?UTF-8?q?=20=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=B0.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/router/AppRouter/ui/AppRouter.tsx | 2 +- .../Favorite/model/functions/functions.ts | 2 +- .../FavoritesPage/FavoritesPage.module.scss | 17 +++- src/pages/FavoritesPage/FavoritesPage.tsx | 92 +++++++++++++++---- .../ProductItem/CardPreview/CardPreview.tsx | 51 ++++++++-- .../CardPreview/CardPrewiew.stories.tsx | 7 +- src/widgets/ProductItem/ProductItem.tsx | 4 + 7 files changed, 143 insertions(+), 32 deletions(-) diff --git a/src/app/router/AppRouter/ui/AppRouter.tsx b/src/app/router/AppRouter/ui/AppRouter.tsx index 1b5cc211..753d9b41 100644 --- a/src/app/router/AppRouter/ui/AppRouter.tsx +++ b/src/app/router/AppRouter/ui/AppRouter.tsx @@ -8,7 +8,7 @@ import ComparePage from '@/pages/ComparePage/ComparePage' import ContactsPage from '@/pages/ContactsPage/ContactsPage' import DeliveryPage from '@/pages/DeliveryPage/DeliveryPage' import ErrorPage from '@/pages/ErrorPage/ErrorPage' -import FavoritesPage from '@/pages/FavoritesPage/FavoritesPage' +import { FavoritesPage } from '@/pages/FavoritesPage/FavoritesPage' import { FeedbackPage } from '@/pages/FeedbackPage/FeedbackPage' import FormReturnPage from '@/pages/FormReturnPage/FormReturnPage' import HelpPage from '@/pages/HelpPage/HelpPage' diff --git a/src/entities/Favorite/model/functions/functions.ts b/src/entities/Favorite/model/functions/functions.ts index 76633d1e..825fac53 100644 --- a/src/entities/Favorite/model/functions/functions.ts +++ b/src/entities/Favorite/model/functions/functions.ts @@ -1,7 +1,7 @@ import { FAVORITE_PRODUCTS_LIMIT } from '@/shared/constants/constants' import { SESSION_STORAGE } from '@/shared/constants/sessionStorage' -import { TProduct } from '../types/types' +import type { TProduct } from '../types/types' /** * Ф-я проверяет наличие продукта в массив избранных продуктов в session storage diff --git a/src/pages/FavoritesPage/FavoritesPage.module.scss b/src/pages/FavoritesPage/FavoritesPage.module.scss index d4eddf68..3efd861c 100644 --- a/src/pages/FavoritesPage/FavoritesPage.module.scss +++ b/src/pages/FavoritesPage/FavoritesPage.module.scss @@ -1,3 +1,5 @@ +@use '../../shared/styles/utils/mixins' as media; + .pageDescriptor { width: 100%; display: flex; @@ -5,7 +7,20 @@ gap: 10px; } -.favoritePage_list { +.favoritePage__container { + width: 100%; + display: flex; + justify-content: start; + align-items: start; + gap: 10px; + + @include media.respond-to('large') { + flex-direction: column; + gap: 10px; + } +} + +.favoritePage__list { max-width: calc(292px * 3 + 60px); display: flex; flex-wrap: wrap; diff --git a/src/pages/FavoritesPage/FavoritesPage.tsx b/src/pages/FavoritesPage/FavoritesPage.tsx index 68e93f94..4df50a85 100644 --- a/src/pages/FavoritesPage/FavoritesPage.tsx +++ b/src/pages/FavoritesPage/FavoritesPage.tsx @@ -1,22 +1,55 @@ +import { FC, KeyboardEvent, Suspense, useState } from 'react' + import WrapperForMainContent from '@/components/WrapperForMainContent/WrapperForMainContent' import { useFavorite } from '@/entities/Favorite/model/hooks/useFavorite' +import SideBarButton from '@/entities/SideBarButton' +import SideBarMenuModal from '@/features/SideBarMenuModal' +import { Routes } from '@/shared/config/routerConfig/routes' +import { useResize } from '@/shared/libs/hooks/useResize' import { ECardView } from '@/shared/model/types/common' import Breadcrumbs from '@/shared/ui/Breadcrumbs/Breadcrumbs' import Heading from '@/shared/ui/Heading/Heading' +import Modal from '@/shared/ui/Modal/Modal' +import Spinner from '@/shared/ui/Spinner/Spinner' import { ProductsList } from '@/widgets/ProductsList/ProductsList' +import SideBarMenu from '@/widgets/SideBarMenu' import styles from './FavoritesPage.module.scss' +const links = [ + { heading: 'Главная', href: '/' }, + { heading: 'Личный Кабинет', href: Routes.LOGIN }, + { heading: 'Избранные товары', href: '' } +] + /** * Страница с избранными товарами */ -const FavoritesPage = () => { +export const FavoritesPage: FC = () => { const favoriteProducts = useFavorite() + const { isScreenMd } = useResize() + const [isModalOpen, setIsModalOpen] = useState(false) + const [isModalClosing, setIsModalClosing] = useState(false) + const [user, setUser] = useState('Elon Musk') // TODO получать пользователя из редакса + + const handleClick = () => { + setIsModalOpen(true) + } + + const changeModalState = () => { + setIsModalOpen(!isModalOpen) + } - const links = [ - { heading: 'Главная', href: '/' }, - { heading: 'Избранные товары', href: '' } - ] + const handleLogOut = () => { + setUser('') + } + + const handleKeyUp = (e: KeyboardEvent) => { + if (e.code === 'Enter' || e.code === 'Space') { + e.preventDefault() + handleLogOut() + } + } return ( @@ -24,20 +57,41 @@ const FavoritesPage = () => { Избранные товары
-
- -
+
+ {isScreenMd ? ( + + ) : ( + + )} +
+ +
+
+ {isModalOpen && ( + + }> + + + + )}
) } - -export default FavoritesPage diff --git a/src/widgets/ProductItem/CardPreview/CardPreview.tsx b/src/widgets/ProductItem/CardPreview/CardPreview.tsx index cb999475..e129b46e 100644 --- a/src/widgets/ProductItem/CardPreview/CardPreview.tsx +++ b/src/widgets/ProductItem/CardPreview/CardPreview.tsx @@ -3,6 +3,7 @@ import { useNavigate } from 'react-router-dom' import IconCart from '@/assets/icons/IconCart.svg' import { useProductInCart } from '@/entities/CartEntity/model/hooks/cartHooks' +import { useWithFavorite } from '@/entities/Favorite/model/hooks/useWithFavorie' import { CardPreviewFooter } from '@/features/CardPreviewFooter/CardPreviewFooter' import { CardPreviewHeader } from '@/features/CardPreviewHeader/CardPreviewHeader' import { ProductAvailability } from '@/features/ProductAvailability/ProductAvailability' @@ -20,33 +21,69 @@ import styles from './CardPreview.module.scss' const LazyQuickPurchaseForm = lazy(() => import('@/features/QuickPurchase/index')) type Props = { - code: number + name: string price: number brand: string slug: string + description: string + code: number images: TImgList + label_hit: boolean + label_popular: boolean quantity: number id: number } /** * Компонент с контентом поп-апа предварительного просмотра товара. - * @param {number} code - артикул товара; + * @param {string} name - название товара; * @param {number} price - цена; * @param {string} brand - производитель; * @param {string} slug - URL для страницы товара; + * @param {string} description - описание; + * @param {number} code - артикул; * @param {TImgList} images - массив с изображениями; + * @param {boolean} label_popular - лейбл Популярный на товаре; + * @param {boolean} label_hit - лейбл Хит на товаре; * @param {number} quantity - количество на склаладе (если > 0, то товар считается в наличии); - * @param {number} id - id товара в Backend; + * @param {number} id - id товара в backend; */ -export const CardPreview: FC = ({ code, images, slug, brand, quantity, price, id }) => { +export const CardPreview: FC = ({ + name, + price, + brand, + slug, + description, + code, + images, + label_popular, + label_hit, + quantity, + id +}) => { const navigate = useNavigate() 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 { isLiked, handleLike } = useWithFavorite({ + id, + category: '', + wb_urls: '', + is_deleted: false, + wholesale: 0, + name, + brand, + slug, + description, + price, + code, + images, + label_popular, + label_hit, + quantity + }) const handleQuickPurchase = () => { setIsModalOpen(true) @@ -56,10 +93,6 @@ export const CardPreview: FC = ({ code, images, slug, brand, quantity, pr navigate(`${Routes.PRODUCT}/${slug}`) } - const handleLike = () => { - setIsLiked(!isLiked) - } - const handleAddToCompared = () => { setIsInCompared(!isInCompared) } diff --git a/src/widgets/ProductItem/CardPreview/CardPrewiew.stories.tsx b/src/widgets/ProductItem/CardPreview/CardPrewiew.stories.tsx index cab8d8fb..7bf8de51 100644 --- a/src/widgets/ProductItem/CardPreview/CardPrewiew.stories.tsx +++ b/src/widgets/ProductItem/CardPreview/CardPrewiew.stories.tsx @@ -20,6 +20,11 @@ export const Default: Story = { quantity: 999, brand: 'MaxBoom', price: 999, - id: 12345 + id: 12345, + name: '3W Clinic Vitamin C Foam Cleansing Пенка для умывания с витамином С', + description: + 'Пенка для умывания с витамином С мягко осветляет и улучшает общий тон кожи, устраняет красноту, отбеливает нежелательную пигментацию и пост-акне, увлажняет и тонизирует.\n\nПенка деликатно очищает кожу от всех видов загрязнений, остатков макияжа, излишков кожного сала и пыли.\n\nВитамин С - обладает выраженным антиоксидантным действием, эффективно защищая кожу от повреждений ультрафиолетовым излучением, инициируют выработку собственного коллагена, способствуют повышению эластичности, упругости и увлажненности кожи.', + label_hit: false, + label_popular: false } } diff --git a/src/widgets/ProductItem/ProductItem.tsx b/src/widgets/ProductItem/ProductItem.tsx index 280f06ec..b785d025 100644 --- a/src/widgets/ProductItem/ProductItem.tsx +++ b/src/widgets/ProductItem/ProductItem.tsx @@ -111,6 +111,10 @@ export const ProductItem: FC = ({ slug={slug} images={images} quantity={quantity} + name={name} + description={description} + label_popular={label_popular} + label_hit={label_hit} /> )} From eaba26d19de3d058fea13e39a0baf96ee33e9f78 Mon Sep 17 00:00:00 2001 From: kirill-k88 Date: Mon, 6 May 2024 19:49:44 +0300 Subject: [PATCH 6/7] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=20=D0=BC=D0=B5=D0=B4=D0=B8=D0=B0=20=D0=B4=D0=BB=D1=8F=20?= =?UTF-8?q?=D1=81=D1=82=D0=B8=D0=BB=D0=B5=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/FavoritesPage/FavoritesPage.module.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/FavoritesPage/FavoritesPage.module.scss b/src/pages/FavoritesPage/FavoritesPage.module.scss index 3efd861c..ea75fadb 100644 --- a/src/pages/FavoritesPage/FavoritesPage.module.scss +++ b/src/pages/FavoritesPage/FavoritesPage.module.scss @@ -14,7 +14,7 @@ align-items: start; gap: 10px; - @include media.respond-to('large') { + @include media.respond-to('middle') { flex-direction: column; gap: 10px; } From 5723f7c11b74828625d95de5324ef9185ec873a9 Mon Sep 17 00:00:00 2001 From: kirill-k88 Date: Mon, 6 May 2024 20:21:32 +0300 Subject: [PATCH 7/7] =?UTF-8?q?=D0=9D=D0=B5=D0=B1=D0=BE=D0=BB=D1=8C=D1=88?= =?UTF-8?q?=D0=B0=D1=8F=20=D0=BF=D1=80=D0=B0=D0=B2=D0=BA=D0=B0=20Footer,?= =?UTF-8?q?=20=D0=B2=20=D0=BD=D0=B5=D0=BA=D0=BE=D1=82=D0=BE=D1=80=D1=8B?= =?UTF-8?q?=D1=85=20=D1=81=D0=B8=D1=82=D1=83=D0=B0=D1=86=D0=B8=D1=8F=D1=85?= =?UTF-8?q?=20=D0=B0=D0=B4=D0=B0=D0=BF=D1=82=D0=B8=D1=80=D0=BE=D0=B2=D0=B0?= =?UTF-8?q?=D0=BB=D0=BE=D1=81=D1=8C=20=D0=BD=D0=B5=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=B8=D0=BB=D1=8C=D0=BD=D0=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/widgets/Footer/Footer.module.scss | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/widgets/Footer/Footer.module.scss b/src/widgets/Footer/Footer.module.scss index 3006eab0..1e7f5c6e 100644 --- a/src/widgets/Footer/Footer.module.scss +++ b/src/widgets/Footer/Footer.module.scss @@ -3,14 +3,15 @@ .footer { background-color: var.$footer-bg; - width: calc(100vw - 20px); + width: 100%; display: flex; justify-content: center; color: var.$white; &__container { display: flex; - max-width: 100%; + width: 100%; + max-width: 1400px; flex-direction: column; } @@ -178,9 +179,9 @@ } &__bottom-wrapper { + width: 100%; display: flex; gap: 5px; - width: 1370px; height: 62px; align-items: center; justify-content: space-between;