From 2d2ed497cab221b5eaa1b22091c6d5e3e430bdb9 Mon Sep 17 00:00:00 2001 From: Primakov Alexandr Alexandrovich Date: Wed, 15 Jan 2025 09:24:56 +0300 Subject: [PATCH] init redux --- package-lock.json | 46 ++++++++++++++++++++++++++++++++++ package.json | 2 ++ src/__data__/actions/stars.ts | 23 +++++++++++++++++ src/__data__/const.ts | 3 +++ src/__data__/reducers/stars.ts | 38 ++++++++++++++++++++++++++++ src/__data__/store.ts | 12 +++++++++ src/app.tsx | 13 +++++++--- src/components/stars.tsx | 7 +++++- src/dashboard.tsx | 2 +- src/pages/friends.tsx | 12 +++++++-- stubs/json/users/success.json | 4 +-- 11 files changed, 153 insertions(+), 9 deletions(-) create mode 100644 src/__data__/actions/stars.ts create mode 100644 src/__data__/const.ts create mode 100644 src/__data__/reducers/stars.ts create mode 100644 src/__data__/store.ts diff --git a/package-lock.json b/package-lock.json index c5e670e..511ef69 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,7 +24,9 @@ "react-dom": "^18.3.1", "react-hook-form": "^7.53.1", "react-icons": "^5.3.0", + "react-redux": "^9.2.0", "react-router-dom": "^6.23.1", + "redux": "^5.0.1", "typescript-eslint": "^8.6.0" } }, @@ -2566,6 +2568,12 @@ "@types/react": "*" } }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", + "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==", + "license": "MIT" + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.6.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.6.0.tgz", @@ -8392,6 +8400,29 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "license": "MIT" }, + "node_modules/react-redux": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", + "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==", + "license": "MIT", + "dependencies": { + "@types/use-sync-external-store": "^0.0.6", + "use-sync-external-store": "^1.4.0" + }, + "peerDependencies": { + "@types/react": "^18.2.25 || ^19", + "react": "^18.0 || ^19", + "redux": "^5.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, "node_modules/react-remove-scroll": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.6.0.tgz", @@ -8564,6 +8595,12 @@ "recursive-watch": "bin.js" } }, + "node_modules/redux": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", + "license": "MIT" + }, "node_modules/reflect.getprototypeof": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.6.tgz", @@ -9976,6 +10013,15 @@ } } }, + "node_modules/use-sync-external-store": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.4.0.tgz", + "integrity": "sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/package.json b/package.json index dd08540..a9eba76 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,9 @@ "react-dom": "^18.3.1", "react-hook-form": "^7.53.1", "react-icons": "^5.3.0", + "react-redux": "^9.2.0", "react-router-dom": "^6.23.1", + "redux": "^5.0.1", "typescript-eslint": "^8.6.0" } } diff --git a/src/__data__/actions/stars.ts b/src/__data__/actions/stars.ts new file mode 100644 index 0000000..8061684 --- /dev/null +++ b/src/__data__/actions/stars.ts @@ -0,0 +1,23 @@ +import * as types from '../const' + +export const setStars = (userId: string, value: number) => ({ + type: types.SET_STARS, + payload: { + id: userId, + value, + }, +}) + +export const incrementStart = (userId: string) => ({ + type: types.INCREMENT_STARS, + payload: { + id: userId + } +}) + +export const decrementStart = (userId: string) => ({ + type: types.INCREMENT_STARS, + payload: { + id: userId + } +}) diff --git a/src/__data__/const.ts b/src/__data__/const.ts new file mode 100644 index 0000000..5396fb1 --- /dev/null +++ b/src/__data__/const.ts @@ -0,0 +1,3 @@ +export const SET_STARS = 'SET_STARS' +export const INCREMENT_STARS = 'INCREMENT_STARS' +export const DECREMENT_STARS = 'DECREMENT_STARS' diff --git a/src/__data__/reducers/stars.ts b/src/__data__/reducers/stars.ts new file mode 100644 index 0000000..a5c2b29 --- /dev/null +++ b/src/__data__/reducers/stars.ts @@ -0,0 +1,38 @@ +import * as types from '../const' + +const initialState = { + friends: {} as Record, + user: {}, +} + +export const starsReducer = (state = initialState, action) => { + switch (action.type) { + case types.SET_STARS: + return { + ...state, + friends: { + ...state.friends, + [action.payload.id]: action.payload.value, + }, + } + case types.INCREMENT_STARS: + return { + ...state, + friends: { + ...state.friends, + [action.payload.id]: state[action.payload.id] + 1, + }, + } + + case types.DECREMENT_STARS: + return { + ...state, + friends: { + ...state.friends, + [action.payload.id]: state[action.payload.id] - 1, + }, + } + + default: return state + } +} diff --git a/src/__data__/store.ts b/src/__data__/store.ts new file mode 100644 index 0000000..c6634b1 --- /dev/null +++ b/src/__data__/store.ts @@ -0,0 +1,12 @@ +import { combineReducers, legacy_createStore } from 'redux' + +import { starsReducer } from './reducers/stars' + +export const store = legacy_createStore( + combineReducers({ + stars: starsReducer, + }), +); + +(window as unknown as { redux: string }).redux = store as unknown as string + diff --git a/src/app.tsx b/src/app.tsx index 83362cf..e0dc98f 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -1,10 +1,12 @@ import React, { useCallback, useState } from 'react' import { BrowserRouter } from 'react-router-dom' import { ChakraProvider } from '@chakra-ui/react' +import { Provider } from 'react-redux' import { Dashboard } from './dashboard' import { stars as starsContext } from './__data__/context' import { users } from './__data__/users' +import { store } from './__data__/store' const App = () => { const [stars, setStar] = useState( @@ -14,8 +16,11 @@ const App = () => { ), ) - const updateStar = useCallback((userId: string, rate: number) => - setStar((state) => ({ ...state, [userId]: rate })), [setStar]) + const updateStar = useCallback( + (userId: string, rate: number) => + setStar((state) => ({ ...state, [userId]: rate })), + [setStar], + ) return ( { > - + + + diff --git a/src/components/stars.tsx b/src/components/stars.tsx index 18998ba..9f713b3 100644 --- a/src/components/stars.tsx +++ b/src/components/stars.tsx @@ -2,9 +2,12 @@ import { HStack, Icon } from '@chakra-ui/react' import { FaRegStar, FaStar } from 'react-icons/fa6' import React, { useMemo, useState } from 'react' import { getConfigValue } from '@brojs/cli' +import { useSelector } from 'react-redux' + import { stars } from '../__data__/context' import { useUsers } from '../hooks' + const useStars = (currentRated, starsCount) => { const [rated, setRated] = useState(currentRated) const stars = useMemo( @@ -34,7 +37,9 @@ export const Stars = ({ // stars, // setRated: starsSetRated // } = useStars(rated, count) - const { rate, setUserRate } = useUsers(state => state[userId].rated) + const { setUserRate } = useUsers(state => state[userId].rated) + const rate = useSelector((store) => store.stars.friends[userId]) ?? 0 + const handleStarsClick = (stars: number) => { setUserRate(userId, stars) fetch(getConfigValue('dry-wash.api.url') + '/user-rate', { diff --git a/src/dashboard.tsx b/src/dashboard.tsx index da843c6..58afcba 100644 --- a/src/dashboard.tsx +++ b/src/dashboard.tsx @@ -20,4 +20,4 @@ export const Dashboard = () => { } /> ) -} \ No newline at end of file +} diff --git a/src/pages/friends.tsx b/src/pages/friends.tsx index 197fb36..14fea81 100644 --- a/src/pages/friends.tsx +++ b/src/pages/friends.tsx @@ -1,13 +1,16 @@ import { HStack } from '@chakra-ui/react' import React, { memo, useEffect, useState } from 'react' +import { useDispatch } from 'react-redux' import { Profile } from '../components' import { users } from '../__data__/users' +import { setStars } from '../__data__/actions/stars' export const Friends = memo(() => { const [isLoading, setIsLoading] = useState(false) const [data, setData] = useState(null) const [error, setError] = useState(null) + const dispatch = useDispatch() useEffect(() => { const getUser = async () => { @@ -17,11 +20,16 @@ export const Friends = memo(() => { const response = await fetch('/api/users/') if (response.ok) { - setData((await response.json()).body) + const data = (await response.json()).body + setData(data) + + Object.keys(data).forEach(userId => { + dispatch(setStars(userId, data[userId].rated)) + }) } else { throw 'Что-то пошло не так' } - + } catch (e) { setError(e.message) } finally { diff --git a/stubs/json/users/success.json b/stubs/json/users/success.json index 77c509b..760d8f9 100644 --- a/stubs/json/users/success.json +++ b/stubs/json/users/success.json @@ -6,7 +6,7 @@ "name": "alexandr", "surname": null, "email": null, - "rated": 3, + "rated": 4, "avatar": "https://www.gravatar.com/avatar/6529e885535ef67a3fad810ad71167c2c03f79480936e9b3a714731753cbb47e?d=robohash", "friends": [ { @@ -24,7 +24,7 @@ "name": "not alexandr", "surname": null, "email": null, - "rated": 2, + "rated": 5, "avatar": "https://www.gravatar.com/avatar/6e?d=robohash", "friends": [ {