From 75b5e3623a62fa47af04618fec659ef0d73146b7 Mon Sep 17 00:00:00 2001 From: MarkMelior Date: Sun, 21 Apr 2024 19:15:17 +0300 Subject: [PATCH] backup commit --- app/shop/page.tsx | 2 + json-server/db.json | 57 ----- json-server/index.js | 58 ----- .../StoreProvider/config/RootState.ts | 2 + src/db.ts | 12 +- src/entities/Product/index.ts | 23 +- .../selector/getProductData/getProductData.ts | 3 + .../getProductError/getProductError.ts | 3 + .../getProductIsLoading.ts | 4 + .../model/services/fetchProductData.ts | 18 ++ .../Product/model/slice/productSlice.ts | 38 +++ src/entities/Product/model/types/product.ts | 11 +- src/features/Auth/index.ts | 6 +- .../service/loginByEmail/loginByEmail.test.ts | 41 ++++ .../loginByEmail.ts} | 15 +- .../loginByUsername/loginByUsername.test.ts | 42 ---- .../Auth/model/slice/loginSlice.test.ts | 2 +- src/features/Auth/model/slice/loginSlice.ts | 14 +- src/features/Auth/model/types/loginState.ts | 2 +- ...ogin.module.scss => ModalAuth.module.scss} | 0 .../{ModalLogin.tsx => ModalAuth.tsx} | 26 +- src/features/Sorts/ui/Sorts.tsx | 228 +++++++++--------- .../DynamicModuleLoader.tsx | 1 + src/views/ShopPage/ShopPage.tsx | 126 +++------- src/views/ShopPage/ShopProducts.tsx | 84 +++++++ 25 files changed, 408 insertions(+), 410 deletions(-) delete mode 100644 json-server/db.json delete mode 100644 json-server/index.js create mode 100644 src/entities/Product/model/selector/getProductData/getProductData.ts create mode 100644 src/entities/Product/model/selector/getProductError/getProductError.ts create mode 100644 src/entities/Product/model/selector/getProductIsLoading/getProductIsLoading.ts create mode 100644 src/entities/Product/model/services/fetchProductData.ts create mode 100644 src/entities/Product/model/slice/productSlice.ts create mode 100644 src/features/Auth/model/service/loginByEmail/loginByEmail.test.ts rename src/features/Auth/model/service/{loginByUsername/loginByUsername.ts => loginByEmail/loginByEmail.ts} (64%) delete mode 100644 src/features/Auth/model/service/loginByUsername/loginByUsername.test.ts rename src/features/Auth/ui/ModalLogin/{ModalLogin.module.scss => ModalAuth.module.scss} (100%) rename src/features/Auth/ui/ModalLogin/{ModalLogin.tsx => ModalAuth.tsx} (89%) create mode 100644 src/views/ShopPage/ShopProducts.tsx diff --git a/app/shop/page.tsx b/app/shop/page.tsx index ca5a5fa..322c800 100644 --- a/app/shop/page.tsx +++ b/app/shop/page.tsx @@ -1,3 +1,5 @@ +'use client'; + import { ShopPage } from '@/views'; import { FC } from 'react'; diff --git a/json-server/db.json b/json-server/db.json deleted file mode 100644 index ebf9f84..0000000 --- a/json-server/db.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "products": [ - { - "id": 1, - "src": "/", - "links": [ - { - "src": "exampleLink1", - "market": "ozon" - }, - { - "src": "exampleLink2", - "market": "yandex" - } - ], - "images": ["cat.png", "cat.png", "cat.png"], - "title": "Example Title 1", - "rating": 4.5, - "reviewCount": 100, - "price": 50, - "oldPrice": 60 - } - ], - "users": [ - { - "id": 1, - "username": "admin", - "password": "123" - } - ], - "reviews": [ - { - "id": 1, - "userId": 1, - "rating": 5, - "comment": "good", - "passed": true - } - ], - "profile": { - "id": 1, - "email": "mark.melior@yandex.com", - "phone": "9834956634", - "first": "Mark", - "last": "Melior", - "age": 19, - "currency": "USD", - "country": "Russia", - "city": "Krasnodar", - "username": "MarkMelior", - "avatar": "ava.jpg", - "sex": "male", - "status": "online", - "favorites": [1], - "history": [] - } -} diff --git a/json-server/index.js b/json-server/index.js deleted file mode 100644 index 9296199..0000000 --- a/json-server/index.js +++ /dev/null @@ -1,58 +0,0 @@ -const fs = require('fs'); -const jsonServer = require('json-server'); -const path = require('path'); - -const server = jsonServer.create(); - -const router = jsonServer.router(path.resolve(__dirname, 'db.json')); - -server.use(jsonServer.defaults({})); -server.use(jsonServer.bodyParser); - -// Нужно для небольшой задержки, чтобы запрос проходил не мгновенно, имитация реального апи -server.use(async (req, res, next) => { - await new Promise((res) => { - setTimeout(res, 800); - }); - next(); -}); - -// Эндпоинт для логина -server.post('/login', (req, res) => { - try { - const { username, password } = req.body; - const db = JSON.parse( - fs.readFileSync(path.resolve(__dirname, 'db.json'), 'UTF-8'), - ); - const { users = [] } = db; - - const userFromBd = users.find( - (user) => user.username === username && user.password === password, - ); - - if (userFromBd) { - return res.json(userFromBd); - } - - return res.status(403).json({ message: 'User not found' }); - } catch (e) { - console.log(e); - return res.status(500).json({ message: e.message }); - } -}); - -// проверяем, авторизован ли пользователь -server.use((req, res, next) => { - if (!req.headers.authorization) { - return res.status(403).json({ message: 'AUTH ERROR' }); - } - - next(); -}); - -server.use(router); - -// запуск сервера -server.listen(8000, () => { - console.log('server is running on 8000 port'); -}); diff --git a/src/app/providers/StoreProvider/config/RootState.ts b/src/app/providers/StoreProvider/config/RootState.ts index aa64a2a..652d521 100644 --- a/src/app/providers/StoreProvider/config/RootState.ts +++ b/src/app/providers/StoreProvider/config/RootState.ts @@ -1,3 +1,4 @@ +import { ProductState } from '@/entities/Product'; import { ProfileState } from '@/entities/Profile'; import { UserState } from '@/entities/User'; import { LoginState } from '@/features/Auth'; @@ -21,6 +22,7 @@ export interface RootState { loginForm?: LoginState; search?: SearchState; profile?: ProfileState; + product?: ProductState; } export type RootStateKey = keyof RootState; diff --git a/src/db.ts b/src/db.ts index 6b760a9..91d5e24 100644 --- a/src/db.ts +++ b/src/db.ts @@ -1,7 +1,7 @@ // ! IS TEMPORARY DATA FOR TESTING -import { ProductDataProps } from './entities/Product/model/types/product'; +import { Product } from './entities/Product'; -export const productData: ProductDataProps[] = [ +export const productData: Product[] = [ { id: 1, images: [ @@ -11,7 +11,7 @@ export const productData: ProductDataProps[] = [ ], title: 'Ого! Это я недавно такие наушники купил', creativity: 5, - filter: ['joke'], + filters: ['joke'], characteristics: { Версия: ['global', 'Ростест (EAC)'], Конфигурация: ['4/128 ГБ', '6/128 ГБ', '8/256 ГБ'], @@ -49,7 +49,7 @@ export const productData: ProductDataProps[] = [ images: ['6665452272.webp', '6665452284.webp'], title: 'Невероятный накачанный Максим гигабайт', creativity: 5, - filter: ['female', 'love'], + filters: ['female', 'love'], characteristics: { 'Характеристика 1': ['Значение характеристики 1'], }, @@ -70,7 +70,7 @@ export const productData: ProductDataProps[] = [ images: ['600014177060b0.webp'], title: 'Шашлык лютейший без шампура понял?', creativity: 5, - filter: ['kid'], + filters: ['kid'], characteristics: { 'Характеристика 1': ['Значение характеристики 1'], }, @@ -91,7 +91,7 @@ export const productData: ProductDataProps[] = [ images: ['100025078688b2.webp'], title: 'Ого! Это я недавно такие наушники купил', creativity: 5, - filter: ['year'], + filters: ['year'], characteristics: { 'Характеристика 1': ['Значение характеристики 1'], }, diff --git a/src/entities/Product/index.ts b/src/entities/Product/index.ts index a51ac68..2ca62a8 100644 --- a/src/entities/Product/index.ts +++ b/src/entities/Product/index.ts @@ -1,12 +1,29 @@ import { Card } from '@nextui-org/react'; +import { getProductData } from './model/selector/getProductData/getProductData'; +import { getProductError } from './model/selector/getProductError/getProductError'; +import { getProductIsLoading } from './model/selector/getProductIsLoading/getProductIsLoading'; import { MarketType, MarketsProductData, - ProductDataProps, + Product, + ProductState, } from './model/types/product'; import { CardWide } from './ui/CardWide/CardWide'; import { Cards, CardsProps } from './ui/Cards/Cards'; -export { Card, CardWide, Cards }; +export { + Card, + CardWide, + Cards, + getProductData, + getProductError, + getProductIsLoading, +}; -export type { CardsProps, MarketType, MarketsProductData, ProductDataProps }; +export type { + CardsProps, + MarketType, + MarketsProductData, + Product, + ProductState, +}; diff --git a/src/entities/Product/model/selector/getProductData/getProductData.ts b/src/entities/Product/model/selector/getProductData/getProductData.ts new file mode 100644 index 0000000..e132e79 --- /dev/null +++ b/src/entities/Product/model/selector/getProductData/getProductData.ts @@ -0,0 +1,3 @@ +import { RootState } from '@/app/providers/StoreProvider'; + +export const getProductData = (state: RootState) => state.product?.data; diff --git a/src/entities/Product/model/selector/getProductError/getProductError.ts b/src/entities/Product/model/selector/getProductError/getProductError.ts new file mode 100644 index 0000000..09a33f7 --- /dev/null +++ b/src/entities/Product/model/selector/getProductError/getProductError.ts @@ -0,0 +1,3 @@ +import { RootState } from '@/app/providers/StoreProvider'; + +export const getProductError = (state: RootState) => state.product?.error; diff --git a/src/entities/Product/model/selector/getProductIsLoading/getProductIsLoading.ts b/src/entities/Product/model/selector/getProductIsLoading/getProductIsLoading.ts new file mode 100644 index 0000000..ea07507 --- /dev/null +++ b/src/entities/Product/model/selector/getProductIsLoading/getProductIsLoading.ts @@ -0,0 +1,4 @@ +import { RootState } from '@/app/providers/StoreProvider'; + +export const getProductIsLoading = (state: RootState) => + state.product?.isLoading; diff --git a/src/entities/Product/model/services/fetchProductData.ts b/src/entities/Product/model/services/fetchProductData.ts new file mode 100644 index 0000000..6ff7041 --- /dev/null +++ b/src/entities/Product/model/services/fetchProductData.ts @@ -0,0 +1,18 @@ +import { ThunkConfig } from '@/app/providers/StoreProvider'; +import { createAsyncThunk } from '@reduxjs/toolkit'; +import { Product } from '../..'; + +export const fetchProductData = createAsyncThunk< + Product[], + void, + ThunkConfig +>('products/fetchProductData', async (_, thunkAPI) => { + try { + const response = await thunkAPI.extra.api.get('/products'); + + return response.data; + } catch (e) { + console.log(e); + return thunkAPI.rejectWithValue('error'); + } +}); diff --git a/src/entities/Product/model/slice/productSlice.ts b/src/entities/Product/model/slice/productSlice.ts new file mode 100644 index 0000000..58db6a8 --- /dev/null +++ b/src/entities/Product/model/slice/productSlice.ts @@ -0,0 +1,38 @@ +import type { PayloadAction } from '@reduxjs/toolkit'; +import { createSlice } from '@reduxjs/toolkit'; +import { fetchProductData } from '../services/fetchProductData'; +import { Product, ProductState } from '../types/product'; + +export const productInitialState: ProductState = { + readonly: true, + isLoading: false, + error: undefined, + data: undefined, +}; + +export const productSlice = createSlice({ + name: 'product', + initialState: productInitialState, + reducers: {}, + extraReducers: (builder) => { + builder + .addCase(fetchProductData.pending, (state) => { + state.error = undefined; + state.isLoading = true; + }) + .addCase( + fetchProductData.fulfilled, + (state, action: PayloadAction) => { + state.isLoading = false; + state.data = action.payload; + }, + ) + .addCase(fetchProductData.rejected, (state, action) => { + state.isLoading = false; + state.error = action.payload; + }); + }, +}); + +export const { actions: productActions } = productSlice; +export const { reducer: productReducer } = productSlice; diff --git a/src/entities/Product/model/types/product.ts b/src/entities/Product/model/types/product.ts index 9efd8da..a0a1aca 100644 --- a/src/entities/Product/model/types/product.ts +++ b/src/entities/Product/model/types/product.ts @@ -1,12 +1,12 @@ import { FilterSortProps } from '@/features/Sorts'; import { Currency } from '@/shared/types/localization'; -export interface ProductDataProps { +export interface Product { id: number; images: string[]; title: string; creativity: number; - filter: FilterSortProps[]; + filters: FilterSortProps[]; characteristics: Record>; markets: MarketsProductData[]; description?: string; @@ -28,3 +28,10 @@ export type MarketType = | 'aliexpress' | 'wildberries' | 'sber'; + +export interface ProductState { + data?: Product[]; + isLoading: boolean; + error?: string; + readonly: boolean; +} diff --git a/src/features/Auth/index.ts b/src/features/Auth/index.ts index 1f82f3a..9f791d8 100644 --- a/src/features/Auth/index.ts +++ b/src/features/Auth/index.ts @@ -1,7 +1,7 @@ -import { loginByUsername } from './model/service/loginByUsername/loginByUsername'; +import { loginByEmail } from './model/service/loginByEmail/loginByEmail'; import { loginActions, loginReducer } from './model/slice/loginSlice'; import { LoginState } from './model/types/loginState'; -import ModalLogin from './ui/ModalLogin/ModalLogin'; +import ModalLogin from './ui/ModalLogin/ModalAuth'; -export { ModalLogin, loginActions, loginByUsername, loginReducer }; +export { ModalLogin, loginActions, loginByEmail, loginReducer }; export type { LoginState }; diff --git a/src/features/Auth/model/service/loginByEmail/loginByEmail.test.ts b/src/features/Auth/model/service/loginByEmail/loginByEmail.test.ts new file mode 100644 index 0000000..3f12c8b --- /dev/null +++ b/src/features/Auth/model/service/loginByEmail/loginByEmail.test.ts @@ -0,0 +1,41 @@ +// import { User, userActions } from '@/entities/User'; +// import { TestAsyncThunk } from '@/shared/lib/tests'; +// import { loginByEmail } from './loginByEmail'; + +// describe('loginByUsername.test', () => { +// test('success login', async () => { +// const userValue: User = { +// id: 1, +// email: 'markus@e.com', +// }; + +// const thunk = new TestAsyncThunk(loginByEmail); +// thunk.api.post.mockReturnValue(Promise.resolve({ data: userValue })); +// const result = await thunk.callThunk({ +// email: 'markus@e.com', +// password: 'pass', +// }); + +// expect(thunk.dispatch).toHaveBeenCalledWith( +// userActions.setAuthData(userValue), +// ); +// expect(thunk.dispatch).toHaveBeenCalledTimes(3); +// expect(thunk.api.post).toHaveBeenCalled(); +// expect(result.meta.requestStatus).toBe('fulfilled'); +// expect(result.payload).toEqual(userValue); +// }); + +// test('error login', async () => { +// const thunk = new TestAsyncThunk(loginByEmail); +// thunk.api.post.mockReturnValue(Promise.resolve({ status: 403 })); +// const result = await thunk.callThunk({ +// password: 'pass', +// username: 'MarkMelior', +// }); + +// expect(thunk.dispatch).toHaveBeenCalledTimes(2); +// expect(thunk.api.post).toHaveBeenCalled(); +// expect(result.meta.requestStatus).toBe('rejected'); +// expect(result.payload).toBe('Вы ввели неверную почту или пароль'); +// }); +// }); diff --git a/src/features/Auth/model/service/loginByUsername/loginByUsername.ts b/src/features/Auth/model/service/loginByEmail/loginByEmail.ts similarity index 64% rename from src/features/Auth/model/service/loginByUsername/loginByUsername.ts rename to src/features/Auth/model/service/loginByEmail/loginByEmail.ts index cd49bbc..b55a340 100644 --- a/src/features/Auth/model/service/loginByUsername/loginByUsername.ts +++ b/src/features/Auth/model/service/loginByEmail/loginByEmail.ts @@ -3,20 +3,20 @@ import { User, userActions } from '@/entities/User'; import { LocalstorageKeys } from '@/shared/types/localstorage'; import { createAsyncThunk } from '@reduxjs/toolkit'; -interface LoginByUsernameProps { - username: string; +interface LoginByEmailProps { + email: string; password: string; } -export const loginByUsername = createAsyncThunk< +export const loginByEmail = createAsyncThunk< User, - LoginByUsernameProps, + LoginByEmailProps, ThunkConfig >( - 'login/loginByUsername', + 'login/loginByEmail', async (authData, { dispatch, extra, rejectWithValue }) => { try { - const response = await extra.api.post('/login', authData); + const response = await extra.api.post('/auth/login', authData); if (!response.data) throw new Error(); @@ -28,8 +28,7 @@ export const loginByUsername = createAsyncThunk< return response.data; } catch (e) { - console.log(e); - return rejectWithValue('Вы ввели неверную почту или пароль'); + return rejectWithValue(e.response.data?.message); } }, ); diff --git a/src/features/Auth/model/service/loginByUsername/loginByUsername.test.ts b/src/features/Auth/model/service/loginByUsername/loginByUsername.test.ts deleted file mode 100644 index 66d513e..0000000 --- a/src/features/Auth/model/service/loginByUsername/loginByUsername.test.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { User, userActions } from '@/entities/User'; -import { TestAsyncThunk } from '@/shared/lib/tests'; -import { loginByUsername } from './loginByUsername'; - -describe('loginByUsername.test', () => { - test('success login', async () => { - const userValue: User = { - id: 1, - username: 'MarkMelior', - email: 'email@email.com', - }; - - const thunk = new TestAsyncThunk(loginByUsername); - thunk.api.post.mockReturnValue(Promise.resolve({ data: userValue })); - const result = await thunk.callThunk({ - password: 'pass', - username: 'MarkMelior', - }); - - expect(thunk.dispatch).toHaveBeenCalledWith( - userActions.setAuthData(userValue), - ); - expect(thunk.dispatch).toHaveBeenCalledTimes(3); - expect(thunk.api.post).toHaveBeenCalled(); - expect(result.meta.requestStatus).toBe('fulfilled'); - expect(result.payload).toEqual(userValue); - }); - - test('error login', async () => { - const thunk = new TestAsyncThunk(loginByUsername); - thunk.api.post.mockReturnValue(Promise.resolve({ status: 403 })); - const result = await thunk.callThunk({ - password: 'pass', - username: 'MarkMelior', - }); - - expect(thunk.dispatch).toHaveBeenCalledTimes(2); - expect(thunk.api.post).toHaveBeenCalled(); - expect(result.meta.requestStatus).toBe('rejected'); - expect(result.payload).toBe('Вы ввели неверную почту или пароль'); - }); -}); diff --git a/src/features/Auth/model/slice/loginSlice.test.ts b/src/features/Auth/model/slice/loginSlice.test.ts index 6a25aaa..7303cd7 100644 --- a/src/features/Auth/model/slice/loginSlice.test.ts +++ b/src/features/Auth/model/slice/loginSlice.test.ts @@ -5,7 +5,7 @@ describe('loginSlice.test', () => { test('test set username', () => { const state: Partial = { username: 'Foren' }; expect( - loginReducer(state as LoginState, loginActions.setUsername('MarkMelior')), + loginReducer(state as LoginState, loginActions.setEmail('MarkMelior')), ).toEqual({ username: 'MarkMelior' }); }); diff --git a/src/features/Auth/model/slice/loginSlice.ts b/src/features/Auth/model/slice/loginSlice.ts index d4d1e31..1ceb208 100644 --- a/src/features/Auth/model/slice/loginSlice.ts +++ b/src/features/Auth/model/slice/loginSlice.ts @@ -1,10 +1,10 @@ import type { PayloadAction } from '@reduxjs/toolkit'; import { createSlice } from '@reduxjs/toolkit'; -import { loginByUsername } from '../..'; +import { loginByEmail } from '../service/loginByEmail/loginByEmail'; import { LoginState } from '../types/loginState'; export const loginInitialState: LoginState = { - username: '', + email: '', password: '', isLoading: false, }; @@ -13,8 +13,8 @@ export const loginSlice = createSlice({ name: 'login', initialState: loginInitialState, reducers: { - setUsername: (state, action: PayloadAction) => { - state.username = action.payload; + setEmail: (state, action: PayloadAction) => { + state.email = action.payload; }, setPassword: (state, action: PayloadAction) => { state.password = action.payload; @@ -22,14 +22,14 @@ export const loginSlice = createSlice({ }, extraReducers: (builder) => { builder - .addCase(loginByUsername.pending, (state) => { + .addCase(loginByEmail.pending, (state) => { state.error = undefined; state.isLoading = true; }) - .addCase(loginByUsername.fulfilled, (state, action) => { + .addCase(loginByEmail.fulfilled, (state, action) => { state.isLoading = false; }) - .addCase(loginByUsername.rejected, (state, action) => { + .addCase(loginByEmail.rejected, (state, action) => { state.isLoading = false; state.error = action.payload; }); diff --git a/src/features/Auth/model/types/loginState.ts b/src/features/Auth/model/types/loginState.ts index 2568a70..6a525de 100644 --- a/src/features/Auth/model/types/loginState.ts +++ b/src/features/Auth/model/types/loginState.ts @@ -1,5 +1,5 @@ export interface LoginState { - username: string; + email: string; password: string; isLoading: boolean; error?: string; diff --git a/src/features/Auth/ui/ModalLogin/ModalLogin.module.scss b/src/features/Auth/ui/ModalLogin/ModalAuth.module.scss similarity index 100% rename from src/features/Auth/ui/ModalLogin/ModalLogin.module.scss rename to src/features/Auth/ui/ModalLogin/ModalAuth.module.scss diff --git a/src/features/Auth/ui/ModalLogin/ModalLogin.tsx b/src/features/Auth/ui/ModalLogin/ModalAuth.tsx similarity index 89% rename from src/features/Auth/ui/ModalLogin/ModalLogin.tsx rename to src/features/Auth/ui/ModalLogin/ModalAuth.tsx index e0ab38b..1eb9fc1 100644 --- a/src/features/Auth/ui/ModalLogin/ModalLogin.tsx +++ b/src/features/Auth/ui/ModalLogin/ModalAuth.tsx @@ -10,7 +10,6 @@ import { useAppDispatch } from '@/shared/lib/hooks'; import { Button } from '@/shared/ui/Button'; import { Input } from '@/shared/ui/Input'; import { - Checkbox, Modal, ModalBody, ModalContent, @@ -25,9 +24,9 @@ import { usePathname, useSearchParams } from 'next/navigation'; import { FC, useCallback, useMemo, useState } from 'react'; import { useSelector } from 'react-redux'; import { getLoginState } from '../../model/selector/getLoginState/getLoginState'; -import { loginByUsername } from '../../model/service/loginByUsername/loginByUsername'; +import { loginByEmail } from '../../model/service/loginByEmail/loginByEmail'; import { loginActions, loginReducer } from '../../model/slice/loginSlice'; -import cls from './ModalLogin.module.scss'; +import cls from './ModalAuth.module.scss'; export interface ModalLoginProps { isOpen: boolean; @@ -43,12 +42,12 @@ const ModalLogin: FC = ({ isOpen, onOpenChange }) => { const isRegistration = searchParams.get('state') === 'sign-up'; const dispatch = useAppDispatch(); - const { password, username, error, isLoading } = + const { password, email, error, isLoading } = useSelector(getLoginState) || {}; const onChangeUsername = useCallback( (value: string) => { - dispatch(loginActions.setUsername(value)); + dispatch(loginActions.setEmail(value)); }, [dispatch], ); @@ -63,7 +62,7 @@ const ModalLogin: FC = ({ isOpen, onOpenChange }) => { const [showNotification, setShowNotification] = useState(false); const onLoginClick = useCallback(async () => { - const result = await dispatch(loginByUsername({ username, password })); + const result = await dispatch(loginByEmail({ email, password })); if (result.meta.requestStatus === 'fulfilled') { onOpenChange(false); @@ -71,7 +70,7 @@ const ModalLogin: FC = ({ isOpen, onOpenChange }) => { // store.reducerManager.remove('loginForm'); // dispatch({ type: `@DESTROY loginForm reducer` }); } - }, [dispatch, onOpenChange, password, username]); + }, [dispatch, email, onOpenChange, password]); const renderLogin = useMemo(() => { return ( @@ -88,12 +87,13 @@ const ModalLogin: FC = ({ isOpen, onOpenChange }) => { } - placeholder='Введите логин' + type='text' + placeholder='Введите email' variant='bordered' onChange={(e) => { onChangeUsername(e.target.value); }} - value={username} + value={email} /> } @@ -105,7 +105,7 @@ const ModalLogin: FC = ({ isOpen, onOpenChange }) => { }} value={password} /> -
+ {/*
= ({ isOpen, onOpenChange }) => { -
+
*/} ); - }, [username, password, onChangeUsername, onChangePassword]); + }, [email, password, onChangeUsername, onChangePassword]); const renderRegister = useMemo(() => { return ( @@ -219,7 +219,7 @@ const ModalLogin: FC = ({ isOpen, onOpenChange }) => { {showNotification && ( setShowNotification(false)} diff --git a/src/features/Sorts/ui/Sorts.tsx b/src/features/Sorts/ui/Sorts.tsx index 34651dc..0c6b7ec 100644 --- a/src/features/Sorts/ui/Sorts.tsx +++ b/src/features/Sorts/ui/Sorts.tsx @@ -1,7 +1,6 @@ 'use client'; import { productData } from '@/db'; -import { DynamicModuleLoader, ReducersList } from '@/shared/lib/components'; import { Button } from '@/shared/ui/Button'; import { Input } from '@/shared/ui/Input'; import { Slider } from '@nextui-org/react'; @@ -9,7 +8,7 @@ import { useRouter } from 'next/navigation'; import { FC, useCallback } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { getSort } from '../model/selector/getSort/getSort'; -import { sortActions, sortReducer } from '../model/slice/sortSlice'; +import { sortActions } from '../model/slice/sortSlice'; import { SortButtons } from './SortButtons'; import cls from './Sorts.module.scss'; @@ -36,11 +35,6 @@ export const Sorts: FC = () => { const dispatch = useDispatch(); const sort = useSelector(getSort); - // ! fix rerender and error: Cannot access 'sortReducer' before initialization - const initialReducers: ReducersList = { - sort: sortReducer, - }; - const setMinPrice = useCallback( (value: number) => { dispatch(sortActions.setMinPrice(value)); @@ -69,125 +63,119 @@ export const Sorts: FC = () => { }, [sort, router]); return ( - -
- -
-
-
Категория
-
-
- -
+
+ +
+
+
Категория
+
+
+
-
-
-
Кому?
-
-
- -
+
+
+
+
Кому?
+
+
+
-
-
-
Бюджет
-
-
- - setMinPrice(parseInt(value, 10)) - } - endContent={ -
- -
- } - /> - - setMaxPrice(parseInt(value, 10)) - } - endContent={ -
- -
- } - /> -
- { - if (typeof value === 'number') { - setMinPrice(value); - } else if (Array.isArray(value)) { - const [start, end] = value; - setMinPrice(start); - setMaxPrice(end); - } - }} - className='max-w-md' +
+
+
+
Бюджет
+
+
+ setMinPrice(parseInt(value, 10))} + endContent={ +
+ +
+ } + /> + setMaxPrice(parseInt(value, 10))} + endContent={ +
+ +
+ } /> -
- - - -
-
-
-
Возраст
-
-
- -
+ { + if (typeof value === 'number') { + setMinPrice(value); + } else if (Array.isArray(value)) { + const [start, end] = value; + setMinPrice(start); + setMaxPrice(end); + } + }} + className='max-w-md' + /> +
+ + +
-
-
-
Сортировка
-
-
- -
+
+
+
+
Возраст
+
+
+ +
+
+
+
+
Сортировка
+
+
+
- +
); }; diff --git a/src/shared/lib/components/DynamicModuleLoader/DynamicModuleLoader.tsx b/src/shared/lib/components/DynamicModuleLoader/DynamicModuleLoader.tsx index 2b7190c..3bd2697 100644 --- a/src/shared/lib/components/DynamicModuleLoader/DynamicModuleLoader.tsx +++ b/src/shared/lib/components/DynamicModuleLoader/DynamicModuleLoader.tsx @@ -27,6 +27,7 @@ export const DynamicModuleLoader: FC = ({ useEffect(() => { Object.entries(reducers).forEach(([name, reducer]) => { + console.log(name); store.reducerManager.add(name as RootStateKey, reducer); dispatch({ type: `@INIT ${name} reducer` }); }); diff --git a/src/views/ShopPage/ShopPage.tsx b/src/views/ShopPage/ShopPage.tsx index 93e87b3..e0b64c3 100644 --- a/src/views/ShopPage/ShopPage.tsx +++ b/src/views/ShopPage/ShopPage.tsx @@ -1,103 +1,50 @@ 'use client'; -import { productData } from '@/db'; -import { Cards } from '@/entities/Product'; +import { productReducer } from '@/entities/Product/model/slice/productSlice'; import { Sorts } from '@/features/Sorts'; -import { getSortSearchparams } from '@/features/Sorts/model/features/getSortSearchparams'; +import { sortReducer } from '@/features/Sorts/model/slice/sortSlice'; +import { DynamicModuleLoader, ReducersList } from '@/shared/lib/components'; import { Blackhole } from '@/shared/ui/Blackhole'; import { NavigationPanel } from '@/widgets/NavigationPanel'; import { TopPage } from '@/widgets/TopPage'; import { Image } from '@nextui-org/react'; import cn from 'clsx'; -import { FC, memo, useMemo } from 'react'; +import { FC, memo } from 'react'; import cls from './ShopPage.module.scss'; +import { ShopProducts } from './ShopProducts'; -export const ShopPage: FC = memo(() => { - const { age, category, maxPrice, sex, minPrice, sorting } = - getSortSearchparams(); - - // const { age, category, maxPrice, sex, minPrice, sorting } = - // useSelector(getSort); - - /** - * Фильтрация продуктов по заданным критериям - */ - const filteredProducts = useMemo(() => { - return productData.filter(({ markets, filter }) => { - // Проверка соответствия цены критериям - const meetsPriceCriteria = - markets[0].price >= minPrice && markets[0].price <= maxPrice; - // Проверка соответствия категории критериям - const meetsCategoryCriteria = !category.some((cat) => - filter.includes(cat), - ); - // Проверка соответствия пола критериям - const meetsSexCriteria = !sex.some((s) => filter.includes(s)); - // Проверка соответствия возраста критериям - const meetsAgeCriteria = !age.some((a) => filter.includes(a)); - - return ( - meetsPriceCriteria && - meetsCategoryCriteria && - meetsSexCriteria && - meetsAgeCriteria - ); - }); - }, [minPrice, maxPrice, category, sex, age]); - - /** - * Сортировка отфильтрованных продуктов - */ - const sortedProducts = useMemo(() => { - return filteredProducts.slice().sort((a, b) => { - if (sorting === 'popular') { - // Сортировка по популярности (количеству отзывов) - return b.markets[0].reviewCount - a.markets[0].reviewCount; - } else if (sorting === 'rating') { - // Сортировка по рейтингу - return b.markets[0].rating - a.markets[0].rating; - } else if (sorting === 'creativity') { - // Сортировка по креативности - return b.creativity - a.creativity; - } else if (sorting === 'expensive') { - // Сортировка по убыванию цены - return b.markets[0].price - a.markets[0].price; - } else if (sorting === 'cheap') { - // Сортировка по возрастанию цены - return a.markets[0].price - b.markets[0].price; - } else if (sorting === 'new') { - // Сортировка по новизне - return productData.indexOf(b) - productData.indexOf(a); // Последние в массиве идут первыми - } - return 0; // Возвращаем 0, если сортировка не требуется или неизвестен тип сортировки - }); - }, [filteredProducts, sorting]); +const initialReducers: ReducersList = { + sort: sortReducer, + product: productReducer, +}; +export const ShopPage: FC = memo(() => { return ( <> - - } - /> -
- - -
-
- -
- {/*
+ + + } + /> +
+ + +
+
+ +
+ {/*