-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #351 from kirill-k88/enhancement_289_favorite-page
Enhancement 289 favorite page
- Loading branch information
Showing
14 changed files
with
361 additions
and
34 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
import { FAVORITE_PRODUCTS_LIMIT } from '@/shared/constants/constants' | ||
import { SESSION_STORAGE } from '@/shared/constants/sessionStorage' | ||
|
||
import type { 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) | ||
|
||
sessionStorage.setItem(SESSION_STORAGE.FAVORITE, JSON.stringify(favoriteProducts)) | ||
window.dispatchEvent(new Event('storage')) | ||
} | ||
} | ||
|
||
/** | ||
* Ф-я удаляет продукт из массива избранных продуктов в 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)) | ||
window.dispatchEvent(new Event('storage')) | ||
} | ||
} | ||
|
||
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) | ||
} | ||
|
||
/** | ||
* Функция возвращает список избранных товаров 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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import { useEffect, useState } from 'react' | ||
|
||
import { getFavoriteProductsFromStorage } from '../functions/functions' | ||
import type { TProduct } from '../types/types' | ||
|
||
/** | ||
* Hook для получения продуктов из избранного | ||
* | ||
* @returns {TProduct[]} состояние favoriteProducts с массивом продуктов в избранном | ||
*/ | ||
export const useFavorite = () => { | ||
const [favoriteProducts, setFavoriteProducts] = useState<TProduct[]>([]) | ||
|
||
useEffect(() => { | ||
setFavoriteProducts(getFavoriteProductsFromStorage()) | ||
window.addEventListener('storage', handleStorage) | ||
|
||
return () => { | ||
window.removeEventListener('storage', handleStorage) | ||
} | ||
}, []) | ||
|
||
const handleStorage = () => { | ||
setFavoriteProducts(getFavoriteProductsFromStorage()) | ||
} | ||
|
||
return favoriteProducts | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<boolean>(isInFavoriteProducts(product)) | ||
|
||
useEffect(() => { | ||
setIsLiked(isInFavoriteProducts(product)) | ||
}, [product]) | ||
|
||
const handleLike = () => { | ||
if (!isLiked) { | ||
addToFavoriteProducts(product) | ||
setIsLiked(true) | ||
} else { | ||
removeFromFavoriteProducts(product) | ||
setIsLiked(false) | ||
} | ||
} | ||
|
||
return { isLiked, handleLike } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
export interface IObjectWithImage { | ||
image: string | ||
index?: number | ||
} | ||
|
||
export type TImgList = Array<IObjectWithImage> | ||
|
||
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 | ||
} | ||
|
||
export type TProductSchema = { | ||
product: TProduct | ||
isLoading?: boolean | ||
error?: string | string[] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,28 @@ | ||
.heading { | ||
align-self: flex-start; | ||
@use '../../shared/styles/utils/mixins' as media; | ||
|
||
.pageDescriptor { | ||
width: 100%; | ||
display: flex; | ||
flex-direction: column; | ||
gap: 10px; | ||
} | ||
|
||
.favoritePage__container { | ||
width: 100%; | ||
display: flex; | ||
justify-content: start; | ||
align-items: start; | ||
gap: 10px; | ||
|
||
@include media.respond-to('middle') { | ||
flex-direction: column; | ||
gap: 10px; | ||
} | ||
} | ||
|
||
.favoritePage__list { | ||
max-width: calc(292px * 3 + 60px); | ||
display: flex; | ||
flex-wrap: wrap; | ||
gap: 30px; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,19 +1,97 @@ | ||
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 Subheading from '@/shared/ui/Subheading/Subheading' | ||
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<boolean>(false) | ||
const [isModalClosing, setIsModalClosing] = useState<boolean>(false) | ||
const [user, setUser] = useState<string>('Elon Musk') // TODO получать пользователя из редакса | ||
|
||
const handleClick = () => { | ||
setIsModalOpen(true) | ||
} | ||
|
||
const changeModalState = () => { | ||
setIsModalOpen(!isModalOpen) | ||
} | ||
|
||
const handleLogOut = () => { | ||
setUser('') | ||
} | ||
|
||
const handleKeyUp = (e: KeyboardEvent<HTMLDivElement>) => { | ||
if (e.code === 'Enter' || e.code === 'Space') { | ||
e.preventDefault() | ||
handleLogOut() | ||
} | ||
} | ||
|
||
return ( | ||
<WrapperForMainContent> | ||
<Heading className={styles.heading}>Избранные товары</Heading> | ||
<Subheading>В разработке</Subheading> | ||
<div className={styles.pageDescriptor}> | ||
<Heading>Избранные товары</Heading> | ||
<Breadcrumbs links={links} /> | ||
</div> | ||
<div className={styles.favoritePage__container}> | ||
{isScreenMd ? ( | ||
<SideBarMenu user={user} handleLogOut={handleLogOut} /> | ||
) : ( | ||
<SideBarButton onClick={handleClick} /> | ||
)} | ||
<section className={styles.favoritePage__list}> | ||
<ProductsList | ||
items={{ | ||
category_name: '', | ||
count: favoriteProducts.length, | ||
next: '', | ||
previous: '', | ||
results: favoriteProducts | ||
}} | ||
cardView={ECardView.GRID} | ||
/> | ||
</section> | ||
</div> | ||
{isModalOpen && ( | ||
<Modal | ||
isModalOpen={isModalOpen} | ||
onClose={changeModalState} | ||
isModalClosing={isModalClosing} | ||
setIsModalClosing={setIsModalClosing}> | ||
<Suspense fallback={<Spinner />}> | ||
<SideBarMenuModal | ||
handleClose={changeModalState} | ||
onKeyUp={handleKeyUp} | ||
handleLogOut={handleLogOut} | ||
user={user} | ||
/> | ||
</Suspense> | ||
</Modal> | ||
)} | ||
</WrapperForMainContent> | ||
) | ||
} | ||
|
||
export default FavoritesPage |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
export enum SESSION_STORAGE { | ||
VIEWED = 'viewedProducts' | ||
VIEWED = 'viewedProducts', | ||
FAVORITE = 'favoriteProducts' | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.