From 12011481d7534d2a3cca8dbcd7d188df2cef1936 Mon Sep 17 00:00:00 2001 From: iluvator Date: Sun, 24 Mar 2024 17:42:55 +0300 Subject: [PATCH] =?UTF-8?q?=D0=BF=D0=B5=D1=80=D0=B5=D0=BF=D0=B8=D1=81?= =?UTF-8?q?=D0=B0=D0=BB=20=D0=BF=D1=80=D0=BE=D0=B5=D0=BA=D1=82=20=D0=BD?= =?UTF-8?q?=D0=B0=20slice,=20=D0=B8=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 121 +++++++++----------- package.json | 9 +- src/components/app.tsx | 6 +- src/components/card.tsx | 11 +- src/components/header.tsx | 8 +- src/components/protected-route.tsx | 36 ++++-- src/const.ts | 9 +- src/mocks/comments.ts | 15 --- src/mocks/offers.ts | 172 ----------------------------- src/mocks/users.ts | 9 -- src/pages/favorite-page.tsx | 3 +- src/pages/main-page.tsx | 21 ++-- src/pages/offer-page.tsx | 24 ++-- src/store/action.ts | 31 ------ src/store/api-action.ts | 70 ++++++------ src/store/index.ts | 4 +- src/store/middlewares/redirect.ts | 8 +- src/store/reducer.ts | 61 ---------- src/store/rootReduser.ts | 16 +++ src/store/slice/favorite.ts | 41 +++++++ src/store/slice/offer.ts | 62 +++++++++++ src/store/slice/offers.ts | 69 ++++++++++++ src/store/slice/reviews.ts | 50 +++++++++ src/store/slice/user.ts | 68 ++++++++++++ 24 files changed, 478 insertions(+), 446 deletions(-) delete mode 100644 src/mocks/comments.ts delete mode 100644 src/mocks/offers.ts delete mode 100644 src/mocks/users.ts delete mode 100644 src/store/action.ts delete mode 100644 src/store/reducer.ts create mode 100644 src/store/rootReduser.ts create mode 100644 src/store/slice/favorite.ts create mode 100644 src/store/slice/offer.ts create mode 100644 src/store/slice/offers.ts create mode 100644 src/store/slice/reviews.ts create mode 100644 src/store/slice/user.ts diff --git a/package-lock.json b/package-lock.json index 2fdbfa2..2810f6f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,7 @@ "name": "six-cities", "version": "14.0.0", "dependencies": { - "@reduxjs/toolkit": "1.9.7", + "@reduxjs/toolkit": "2.2.1", "axios": "1.5.1", "clsx": "2.1.0", "dayjs": "1.11.10", @@ -18,12 +18,11 @@ "react": "18.2.0", "react-dom": "18.2.0", "react-helmet-async": "1.3.0", - "react-redux": "8.1.3", + "react-redux": "9.1.0", "react-router-dom": "6.16.0", "react-toastify": "10.0.5" }, "devDependencies": { - "@jedmao/redux-mock-store": "3.0.5", "@testing-library/jest-dom": "6.1.3", "@testing-library/react": "14.0.0", "@testing-library/user-event": "14.5.1", @@ -31,7 +30,7 @@ "@types/leaflet": "1.9.3", "@types/react": "18.2.25", "@types/react-dom": "18.2.11", - "@types/react-redux": "7.1.27", + "@types/react-redux": "7.1.33", "@types/testing-library__jest-dom": "5.14.9", "@typescript-eslint/eslint-plugin": "6.7.4", "@typescript-eslint/parser": "6.7.4", @@ -1042,15 +1041,6 @@ "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", "dev": true }, - "node_modules/@jedmao/redux-mock-store": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@jedmao/redux-mock-store/-/redux-mock-store-3.0.5.tgz", - "integrity": "sha512-zNcVCd5/ekSMdQWk64CqTPM24D9Lo59st9KvS+fljGpQXV4SliB7Vo0NFQIgvQJWPYeeobdngnrGy0XbCaARNw==", - "dev": true, - "peerDependencies": { - "redux": "^4" - } - }, "node_modules/@jest/expect-utils": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", @@ -1182,18 +1172,18 @@ } }, "node_modules/@reduxjs/toolkit": { - "version": "1.9.7", - "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.9.7.tgz", - "integrity": "sha512-t7v8ZPxhhKgOKtU+uyJT13lu4vL7az5aFi4IdoDs/eS548edn2M8Ik9h8fxgvMjGoAUVFSt6ZC1P5cWmQ014QQ==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.2.1.tgz", + "integrity": "sha512-8CREoqJovQW/5I4yvvijm/emUiCCmcs4Ev4XPWd4mizSO+dD3g5G6w34QK5AGeNrSH7qM8Fl66j4vuV7dpOdkw==", "dependencies": { - "immer": "^9.0.21", - "redux": "^4.2.1", - "redux-thunk": "^2.4.2", - "reselect": "^4.1.8" + "immer": "^10.0.3", + "redux": "^5.0.1", + "redux-thunk": "^3.1.0", + "reselect": "^5.0.1" }, "peerDependencies": { "react": "^16.9.0 || ^17.0.0 || ^18", - "react-redux": "^7.2.1 || ^8.0.2" + "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" }, "peerDependenciesMeta": { "react": { @@ -1409,6 +1399,7 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==", + "dev": true, "dependencies": { "@types/react": "*", "hoist-non-react-statics": "^3.3.0" @@ -1510,12 +1501,14 @@ "node_modules/@types/prop-types": { "version": "15.7.5", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", - "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==" + "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==", + "devOptional": true }, "node_modules/@types/react": { "version": "18.2.25", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.25.tgz", "integrity": "sha512-24xqse6+VByVLIr+xWaQ9muX1B4bXJKXBbjszbld/UEDslGLY53+ZucF44HCmLbMPejTzGG9XgR+3m2/Wqu1kw==", + "devOptional": true, "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -1526,15 +1519,15 @@ "version": "18.2.11", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.11.tgz", "integrity": "sha512-zq6Dy0EiCuF9pWFW6I6k6W2LdpUixLE4P6XjXU1QHLfak3GPACQfLwEuHzY5pOYa4hzj1d0GxX/P141aFjZsyg==", - "devOptional": true, + "dev": true, "dependencies": { "@types/react": "*" } }, "node_modules/@types/react-redux": { - "version": "7.1.27", - "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.27.tgz", - "integrity": "sha512-xj7d9z32p1K/eBmO+OEy+qfaWXtcPlN8f1Xk3Ne0p/ZRQ867RI5bQ/bpBtxbqU1AHNhKJSgGvld/P2myU2uYkg==", + "version": "7.1.33", + "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.33.tgz", + "integrity": "sha512-NF8m5AjWCkert+fosDsN3hAlHzpjSiXlVy9EgQEmLoBhaNXbmyeGs/aj5dQzKuF+/q+S7JQagorGDW8pJ28Hmg==", "dev": true, "dependencies": { "@types/hoist-non-react-statics": "^3.3.0", @@ -1543,10 +1536,20 @@ "redux": "^4.0.0" } }, + "node_modules/@types/react-redux/node_modules/redux": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", + "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.9.2" + } + }, "node_modules/@types/scheduler": { "version": "0.16.3", "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz", - "integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==" + "integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==", + "devOptional": true }, "node_modules/@types/semver": { "version": "7.5.3", @@ -2487,7 +2490,8 @@ "node_modules/csstype": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", - "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" + "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==", + "devOptional": true }, "node_modules/data-urls": { "version": "4.0.0", @@ -3705,6 +3709,7 @@ "version": "3.3.2", "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "dev": true, "dependencies": { "react-is": "^16.7.0" } @@ -3781,9 +3786,9 @@ } }, "node_modules/immer": { - "version": "9.0.21", - "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz", - "integrity": "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==", + "version": "10.0.4", + "resolved": "https://registry.npmjs.org/immer/-/immer-10.0.4.tgz", + "integrity": "sha512-cuBuGK40P/sk5IzWa9QPUaAdvPHjkk1c+xYsd9oZw+YQQEV+10G0P5uMpGctZZKnyQ+ibRO08bD25nWLmYi2pw==", "funding": { "type": "opencollective", "url": "https://opencollective.com/immer" @@ -5257,35 +5262,23 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, "node_modules/react-redux": { - "version": "8.1.3", - "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.1.3.tgz", - "integrity": "sha512-n0ZrutD7DaX/j9VscF+uTALI3oUPa/pO4Z3soOBIjuRn/FzVu6aehhysxZCLi6y7duMf52WNZGMl7CtuK5EnRw==", + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.1.0.tgz", + "integrity": "sha512-6qoDzIO+gbrza8h3hjMA9aq4nwVFCKFtY2iLxCtVT38Swyy2C/dJCGBXHeHLtx6qlg/8qzc2MrhOeduf5K32wQ==", "dependencies": { - "@babel/runtime": "^7.12.1", - "@types/hoist-non-react-statics": "^3.3.1", "@types/use-sync-external-store": "^0.0.3", - "hoist-non-react-statics": "^3.3.2", - "react-is": "^18.0.0", "use-sync-external-store": "^1.0.0" }, "peerDependencies": { - "@types/react": "^16.8 || ^17.0 || ^18.0", - "@types/react-dom": "^16.8 || ^17.0 || ^18.0", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0", - "react-native": ">=0.59", - "redux": "^4 || ^5.0.0-beta.0" + "@types/react": "^18.2.25", + "react": "^18.0", + "react-native": ">=0.69", + "redux": "^5.0.0" }, "peerDependenciesMeta": { "@types/react": { "optional": true }, - "@types/react-dom": { - "optional": true - }, - "react-dom": { - "optional": true - }, "react-native": { "optional": true }, @@ -5294,11 +5287,6 @@ } } }, - "node_modules/react-redux/node_modules/react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" - }, "node_modules/react-refresh": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz", @@ -5457,19 +5445,16 @@ } }, "node_modules/redux": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", - "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==", - "dependencies": { - "@babel/runtime": "^7.9.2" - } + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==" }, "node_modules/redux-thunk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.2.tgz", - "integrity": "sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", + "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", "peerDependencies": { - "redux": "^4" + "redux": "^5.0.0" } }, "node_modules/regenerator-runtime": { @@ -5543,9 +5528,9 @@ "dev": true }, "node_modules/reselect": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.8.tgz", - "integrity": "sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ==" + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.0.tgz", + "integrity": "sha512-aw7jcGLDpSgNDyWBQLv2cedml85qd95/iszJjN988zX1t7AVRJi19d9kto5+W7oCfQ94gyo40dVbT6g2k4/kXg==" }, "node_modules/resolve": { "version": "1.22.2", diff --git a/package.json b/package.json index f761356..2bdb7e2 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "test": "vitest --passWithNoTests" }, "dependencies": { - "@reduxjs/toolkit": "1.9.7", + "@reduxjs/toolkit": "2.2.1", "axios": "1.5.1", "clsx": "2.1.0", "dayjs": "1.11.10", @@ -20,12 +20,11 @@ "react": "18.2.0", "react-dom": "18.2.0", "react-helmet-async": "1.3.0", - "react-redux": "8.1.3", + "react-redux": "9.1.0", "react-router-dom": "6.16.0", "react-toastify": "10.0.5" }, "devDependencies": { - "@jedmao/redux-mock-store": "3.0.5", "@testing-library/jest-dom": "6.1.3", "@testing-library/react": "14.0.0", "@testing-library/user-event": "14.5.1", @@ -33,7 +32,7 @@ "@types/leaflet": "1.9.3", "@types/react": "18.2.25", "@types/react-dom": "18.2.11", - "@types/react-redux": "7.1.27", + "@types/react-redux": "7.1.33", "@types/testing-library__jest-dom": "5.14.9", "@typescript-eslint/eslint-plugin": "6.7.4", "@typescript-eslint/parser": "6.7.4", @@ -61,4 +60,4 @@ "last 1 safari version" ] } -} +} \ No newline at end of file diff --git a/src/components/app.tsx b/src/components/app.tsx index e986d4a..7782ce1 100644 --- a/src/components/app.tsx +++ b/src/components/app.tsx @@ -3,7 +3,7 @@ import OfferPage from '../pages/offer-page'; import FavoritePage from '../pages/favorite-page'; import LoginPage from '../pages/login-page'; import { Route, Routes } from 'react-router-dom'; -import { AppRoute, AuthorizationStatus } from '../const'; +import { AppRoute } from '../const'; import NotFoundPage from '../pages/not-found-page/not-found-page'; import ProtectedRoute from './protected-route'; import HistoryRouter from './history-route'; @@ -24,11 +24,11 @@ export default function App() { /> } + element={ } /> } + element={} /> { - dispatch(chooseId(offer.id)); - }; const onPointCardMouseOver = () => { handelPointCardMouseOver(offer); @@ -39,7 +32,7 @@ export default function Card({ offer, optionCard, handelPointCardMouseOver }: TC }
- + Place image
@@ -58,7 +51,7 @@ export default function Card({ offer, optionCard, handelPointCardMouseOver }: TC

- {offer.title} + {offer.title}

{offer.type}

diff --git a/src/components/header.tsx b/src/components/header.tsx index db32636..79a0045 100644 --- a/src/components/header.tsx +++ b/src/components/header.tsx @@ -5,6 +5,7 @@ import { useEffect } from 'react'; import { checkAuthAction, fetchUserAction } from '../store/api-action'; import { logoutAction } from '../store/api-action'; import { AuthorizationStatus } from '../const'; +import { userSelector } from '../store/slice/user'; type THeaderProps = { navigation: boolean; @@ -12,13 +13,14 @@ type THeaderProps = { export default function Header({ navigation }: THeaderProps) { const dispatch = useAppDispatch(); - const favorite = useAppSelector((state) => state.favorite); + // const favorite = useAppSelector((state) => state.favorite); + const favorite = []; useEffect(() => { dispatch(fetchUserAction()); dispatch(checkAuthAction()); }, [dispatch]); - const authorizationStatus = useAppSelector((state) => state.authorizationStatus); - const user = useAppSelector((state) => state.dataUser); + const authorizationStatus = useAppSelector(userSelector.authorizationStatus); + const user = useAppSelector(userSelector.dataUser); const logoutAccount = () => { dispatch(logoutAction()); diff --git a/src/components/protected-route.tsx b/src/components/protected-route.tsx index ce27004..f9b23b3 100644 --- a/src/components/protected-route.tsx +++ b/src/components/protected-route.tsx @@ -1,18 +1,36 @@ -import { Navigate } from 'react-router-dom'; +import { Location, Navigate, useLocation } from 'react-router-dom'; import { AppRoute } from '../const'; -import { AuthorizationStatus } from '../const'; import { useAppSelector } from '../hooks'; +import { userSelector } from '../store/slice/user'; type TProtectedRouteProps = { children: JSX.Element; + onlyUnAuth?: boolean; } -export default function ProtectedRoute({ children }: TProtectedRouteProps): JSX.Element { - const authorizationStatus = useAppSelector((store) => store.authorizationStatus); - return ( - authorizationStatus === AuthorizationStatus.AUTH - ? children - : - ); +type FromState = { + from?: Location; +} + +export default function ProtectedRoute({ children, onlyUnAuth }: TProtectedRouteProps): JSX.Element { + const user = useAppSelector(userSelector.dataUser); + const location: Location = useLocation() as Location; + + if (onlyUnAuth && user) { + const from = location.state?.from || { pathname: AppRoute.Main }; + return ; + } + + if (!onlyUnAuth && !user) { + return ; + } + + // return ( + // authorizationStatus === AuthorizationStatus.AUTH + // ? children + // : + // ); + + return children; } diff --git a/src/const.ts b/src/const.ts index 6b5b5a5..70a5fba 100644 --- a/src/const.ts +++ b/src/const.ts @@ -59,6 +59,13 @@ const AuthorizationStatus = { UN_KNOWN: 'UNKNOWN', }; +const RequestStatus = { + SUCCESS: 'success', + LOADING: 'loading', + FAILED: 'failed', + NONE: 'none' +}; + const URL_MARKER_DEFAULT = '../markup/img/pin.svg'; const URL_MARKER_CURRENT = '../markup/img/pin-active.svg'; @@ -66,4 +73,4 @@ const URL_MARKER_CURRENT = '../markup/img/pin-active.svg'; const CountStar: number = 5; -export { CountStar, AppRoute, AuthorizationStatus, OptionCard, URL_MARKER_DEFAULT, URL_MARKER_CURRENT, OptionListCard, MapSize, ListSort, ListLocation as LocationCity, APIRoute }; +export { CountStar, AppRoute, RequestStatus, AuthorizationStatus, OptionCard, URL_MARKER_DEFAULT, URL_MARKER_CURRENT, OptionListCard, MapSize, ListSort, ListLocation as LocationCity, APIRoute }; diff --git a/src/mocks/comments.ts b/src/mocks/comments.ts deleted file mode 100644 index b873063..0000000 --- a/src/mocks/comments.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Comment } from '../types/comment'; - -export const Comments: Comment[] = [ - { - id: 'b67ddfd5-b953-4a30-8c8d-bd083cd6b62a', - date: '2019-05-08T14:13:56.569Z', - user: { - name: 'Oliver Conner', - avatarUrl: 'https://url-to-image/image.png', - isPro: false - }, - comment: 'A quiet cozy and picturesque that hides behind a a river by the unique lightness of Amsterdam.', - rating: 4 - } -]; diff --git a/src/mocks/offers.ts b/src/mocks/offers.ts deleted file mode 100644 index 4fa62f2..0000000 --- a/src/mocks/offers.ts +++ /dev/null @@ -1,172 +0,0 @@ -import { Offer } from '../types/offer'; - -export const offers: Offer[] = [ - { - id: '6af6f711-c28d-4121-82cd-e0b462a27f00', - title: 'Beautiful & luxurious studio at great location', - type: 'apartment', - price: 120, - city: { - name: 'Amsterdam', - location: { - latitude: 52.35514938496378, - longitude: 4.673877537499948, - zoom: 8 - } - }, - location: { - latitude: 52.35514938496378, - longitude: 4.673877537499948, - zoom: 8 - }, - isFavorite: true, - isPremium: false, - rating: 3.5, - previewImage: 'https://url-to-image/image.png', - description: 'A quiet cozy and picturesque that hides behind a a river by the unique lightness of Amsterdam.', - bedrooms: 3, - goods: ['Heating'], - host: { - name: 'Oliver Conner', - avatarUrl: 'https://url-to-image/image.png', - isPro: false, - }, - images: ['https://url-to-image/image.png'], - maxAdults: 4, - }, - { - id: '70faa463-6bf2-40ec-bca9-4c5d055c5c7f', - title: 'Canal View Prinsengracht', - description: 'Design interior in most sympathetic area! Complitely renovated, well-equipped, cosy studio in idyllic, over 100 years old wooden house. Calm street, fast connection to center and airport.', - type: 'hotel', - price: 217, - images: [ - 'https://15.design.htmlacademy.pro/static/hotel/2.jpg', - 'https://15.design.htmlacademy.pro/static/hotel/20.jpg', - 'https://15.design.htmlacademy.pro/static/hotel/9.jpg', - 'https://15.design.htmlacademy.pro/static/hotel/3.jpg', - 'https://15.design.htmlacademy.pro/static/hotel/10.jpg', - 'https://15.design.htmlacademy.pro/static/hotel/15.jpg' - ], - city: { - name: 'Paris', - location: { - latitude: 48.85661, - longitude: 2.351499, - zoom: 13 - } - }, - location: { - latitude: 48.868610000000004, - longitude: 2.342499, - zoom: 16 - }, - goods: [ - 'Air conditioning', - 'Washer', - 'Washing machine', - 'Fridge' - ], - host: { - isPro: true, - name: 'Angelina', - avatarUrl: 'https://15.design.htmlacademy.pro/static/host/avatar-angelina.jpg' - }, - isPremium: false, - isFavorite: false, - rating: 1.5, - bedrooms: 5, - maxAdults: 6 - }, - { - id: '70faa463-6bf2-40ec-ba9-4c5d055c5c7f', - title: 'Waterfront with extraordinary view', - description: 'Design interior in most sympathetic area! Complitely renovated, well-equipped, cosy studio in idyllic, over 100 years old wooden house. Calm street, fast connection to center and airport.', - type: 'hotel', - price: 217, - images: [ - 'https://15.design.htmlacademy.pro/static/hotel/2.jpg', - 'https://15.design.htmlacademy.pro/static/hotel/20.jpg', - 'https://15.design.htmlacademy.pro/static/hotel/9.jpg', - 'https://15.design.htmlacademy.pro/static/hotel/3.jpg', - 'https://15.design.htmlacademy.pro/static/hotel/10.jpg', - 'https://15.design.htmlacademy.pro/static/hotel/15.jpg' - ], - city: { - name: 'Paris', - location: { - latitude: 48.84661, - longitude: 2.352499, - zoom: 13 - } - }, - location: { - latitude: 48.858610000000006, - longitude: 2.330499, - zoom: 16 - }, - goods: [ - 'Air conditioning', - 'Washer', - 'Washing machine', - 'Fridge' - ], - host: { - isPro: true, - name: 'Angelina', - avatarUrl: 'https://15.design.htmlacademy.pro/static/host/avatar-angelina.jpg' - }, - isPremium: true, - isFavorite: true, - rating: 1.5, - bedrooms: 5, - maxAdults: 6 - }, - { - id: '4367aa15-eb08-49cd-ba6e-f32ac3e95aff', - title: 'The house among olive ', - description: 'I rent out a very sunny and bright apartment only 7 minutes walking distance to the metro station. The apartment has a spacious living room with a kitchen, one bedroom and a bathroom with mit bath. A terrace can be used in summer.', - type: 'apartment', - price: 441, - images: [ - 'https://15.design.htmlacademy.pro/static/hotel/10.jpg', - 'https://15.design.htmlacademy.pro/static/hotel/4.jpg', - 'https://15.design.htmlacademy.pro/static/hotel/13.jpg', - 'https://15.design.htmlacademy.pro/static/hotel/19.jpg', - 'https://15.design.htmlacademy.pro/static/hotel/14.jpg', - 'https://15.design.htmlacademy.pro/static/hotel/5.jpg' - ], - city: { - name: 'Paris', - location: { - latitude: 48.85761, - longitude: 2.351559, - zoom: 13 - } - }, - location: { - latitude: 48.834610000000005, - longitude: 2.335499, - zoom: 16 - }, - goods: [ - 'Towels', - 'Washer', - 'Breakfast', - 'Coffee machine', - 'Fridge', - 'Baby seat' - ], - host: { - isPro: true, - name: 'Angelina', - avatarUrl: 'https://15.design.htmlacademy.pro/static/host/avatar-angelina.jpg' - }, - isPremium: false, - isFavorite: true, - rating: 4.1, - bedrooms: 3, - maxAdults: 6 - } -]; - diff --git a/src/mocks/users.ts b/src/mocks/users.ts deleted file mode 100644 index 2095ef4..0000000 --- a/src/mocks/users.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { User } from '../types/user'; - -export const user: User = { - name: 'Oliver Conner', - avatarUrl: 'https://url-to-image/image.png', - isPro: false, - email: 'Oliver.conner@gmail.com', - token: 'T2xpdmVyLmNvbm5lckBnbWFpbC5jb20=' -}; diff --git a/src/pages/favorite-page.tsx b/src/pages/favorite-page.tsx index 5438637..ce35079 100644 --- a/src/pages/favorite-page.tsx +++ b/src/pages/favorite-page.tsx @@ -9,6 +9,7 @@ import { useAppSelector } from '../hooks'; import { useAppDispatch } from '../hooks'; import { useEffect } from 'react'; import { fetchFavoriteAction } from '../store/api-action'; +import { favoriteSelectors } from '../store/slice/favorite'; export default function FavoritePage() { @@ -16,7 +17,7 @@ export default function FavoritePage() { useEffect(() => { dispatch(fetchFavoriteAction()); }, [dispatch]); - const dataFavorite = useAppSelector((store) => store.favorite); + const dataFavorite = useAppSelector(favoriteSelectors.favorite); const favorites = getFavoritesByLocation(dataFavorite); return ( diff --git a/src/pages/main-page.tsx b/src/pages/main-page.tsx index efe410f..424a851 100644 --- a/src/pages/main-page.tsx +++ b/src/pages/main-page.tsx @@ -9,21 +9,22 @@ import { OfferPreviews } from '../types/offer-preview'; import { LocationCity } from '../const'; import ListLocation from '../components/list-location'; import { useAppDispatch, useAppSelector } from '../hooks'; -import { selectCity, sortOffer } from '../store/action'; import PlacesOptions from '../components/places-options'; import Loader from '../components/loader/loader'; import { fetchOffersAction } from '../store/api-action'; - +import { offersSelectors } from '../store/slice/offers'; +import { selectCity, sortOffer } from '../store/slice/offers'; export default function MainPage() { - const selectOffers = useAppSelector((state) => state.offers); - const currentCity = useAppSelector((state) => state.city); + const selectOffers = useAppSelector(offersSelectors.offers); + const currentCity = useAppSelector(offersSelectors.city); const dispatch = useAppDispatch(); - const isOffersDataLoading = useAppSelector((state) => state.isOfferDataLoadingStatus); + const statusOffersDataLoading = useAppSelector(offersSelectors.isOffersDataLoading); useEffect(() => { dispatch(fetchOffersAction()); - }, [dispatch]); + dispatch(selectCity(currentCity)); + }, [currentCity, dispatch]); const [selectedOffer, setSelectedOffer] = useState( null @@ -32,6 +33,9 @@ export default function MainPage() { const [isOpenSort, setIsOpenSort] = useState( false ); + const [sortName, setSortName] = useState( + 'popular' + ); const [selectedCity, setSelectedCity] = useState({ name: 'Paris', @@ -47,6 +51,7 @@ export default function MainPage() { const handelSortOfferClick = (sortType: string) => { dispatch(sortOffer(sortType)); setIsOpenSort(!isOpenSort); + setSortName(sortType); }; const handelOpenPlacesClick = () => { @@ -74,7 +79,7 @@ export default function MainPage() { }; - if (isOffersDataLoading) { + if (statusOffersDataLoading) { return ; } @@ -96,7 +101,7 @@ export default function MainPage() {
Sort by - Popular + {sortName} diff --git a/src/pages/offer-page.tsx b/src/pages/offer-page.tsx index c2f6222..28f5899 100644 --- a/src/pages/offer-page.tsx +++ b/src/pages/offer-page.tsx @@ -11,22 +11,28 @@ import { OptionListCard } from '../const'; import ListCards from '../components/list-cards'; import { OfferPreviews } from '../types/offer-preview'; import { useAppSelector } from '../hooks'; -import { fetchCommentsAction, fetchOfferAction } from '../store/api-action'; +import { fetchCommentsAction, fetchOfferAction, fetchOfferNearbyAction } from '../store/api-action'; import { useAppDispatch } from '../hooks'; import { useParams } from 'react-router-dom'; import Loader from '../components/loader/loader'; +import { offerSelector } from '../store/slice/offer'; +import { offersSelectors } from '../store/slice/offers'; +import { reviewsSelector } from '../store/slice/reviews'; export default function OfferPage() { const dispatch = useAppDispatch(); const { offerId } = useParams(); - const isOffersDataLoading = useAppSelector((state) => state.isOfferDataLoadingStatus); + const isOffersDataLoading = useAppSelector(offersSelectors.isOffersDataLoading); useEffect(() => { - dispatch(fetchCommentsAction(offerId as string)); - dispatch(fetchOfferAction(offerId as string)); + Promise.all([ + dispatch(fetchCommentsAction(offerId as string)), + dispatch(fetchOfferAction(offerId as string)), + dispatch(fetchOfferNearbyAction(offerId as string)) + ]); }, [dispatch, offerId]); - const offer = useAppSelector((state) => state.currentOffer); - const offersNearby = useAppSelector((state) => state.offers); - const comments = useAppSelector((state) => state.comments); + const offer = useAppSelector(offerSelector.currentOffer); + const comments = useAppSelector(reviewsSelector.comments); + const nearby = useAppSelector(offerSelector.nearby); const [selectedOffer, setSelectedOffer] = useState( null ); @@ -132,13 +138,13 @@ export default function OfferPage() {
- +

Other places in the neighbourhood

- +
diff --git a/src/store/action.ts b/src/store/action.ts deleted file mode 100644 index 6a97c14..0000000 --- a/src/store/action.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { createAction } from '@reduxjs/toolkit'; -import { Offer } from '../types/offer'; -import { OfferPreviews } from '../types/offer-preview'; -import { Comment } from '../types/comment'; -import { TUser } from '../types/user'; -import { TAuthorizationStatus } from '../types/authorization-status'; -import { AppRoute } from '../const'; - -const selectCity = createAction('mainPage/selectCity'); - -const sortOffer = createAction('mainPage/sortOffer'); - -const loadOffer = createAction('data/loadOffers'); - -const loadDataUser = createAction('data/loadDataUser'); - -const loadComments = createAction('data/loadComments'); - -const loadFavorite = createAction('data/loadFavorite'); - -const chooseOffer = createAction('mainPage/chooseOffer'); - -const chooseId = createAction('mainPage/chooseId'); - -const requireAuthorization = createAction('user/requireAuthorization'); - -const setOfferDataLoadingStatus = createAction('data/setOfferDataLoadingStatus'); - -const redirectToRoute = createAction('mainPage/redirectToRoute'); - -export { selectCity, redirectToRoute, loadDataUser, sortOffer, requireAuthorization, loadOffer, setOfferDataLoadingStatus, loadComments, loadFavorite, chooseOffer, chooseId }; diff --git a/src/store/api-action.ts b/src/store/api-action.ts index bc96067..137de6d 100644 --- a/src/store/api-action.ts +++ b/src/store/api-action.ts @@ -5,51 +5,57 @@ import { Offer } from '../types/offer'; import { OfferPreviews } from '../types/offer-preview'; import { APIRoute, AppRoute } from '../const'; import { Comment } from '../types/comment'; -import { loadOffer, redirectToRoute, setOfferDataLoadingStatus, loadDataUser, loadComments, loadFavorite, requireAuthorization, chooseOffer } from './action'; -import { AuthorizationStatus } from '../const'; +import { userAction } from './slice/user'; import { UserData } from '../types/user-data'; import { AuthData } from '../types/auth-data'; import { saveToken } from '../service/token'; import { TUser } from '../types/user'; -export const fetchOffersAction = createAsyncThunk( 'data/fetchOffers', - async (_arg, { dispatch, extra: api }) => { - dispatch(setOfferDataLoadingStatus(true)); - const { data } = await api.get(APIRoute.OFFERS); - dispatch(setOfferDataLoadingStatus(false)); - dispatch(loadOffer(data)); + async (_arg, { extra: api }) => { + const { data } = await api.get(APIRoute.OFFERS); + return data; }, ); -export const fetchCommentsAction = createAsyncThunk( 'data/loadComments', - async (id, { dispatch, extra: api }) => { + async (id, { extra: api }) => { const { data } = await api.get(`${APIRoute.COMMENTS}/${id}`); - dispatch(setOfferDataLoadingStatus(false)); - dispatch(loadComments(data)); + return data; }, ); -export const fetchOfferAction = createAsyncThunk( 'data/chooseOffer', - async (id, { dispatch, extra: api }) => { - dispatch(setOfferDataLoadingStatus(true)); + async (id, { extra: api }) => { const { data } = await api.get(`${APIRoute.OFFERS}/${id}`); - dispatch(setOfferDataLoadingStatus(false)); - dispatch(chooseOffer(data)); + return data; + }, +); + +export const fetchOfferNearbyAction = createAsyncThunk( + 'data/chooseOfferNearby', + async (id, { extra: api }) => { + const { data } = await api.get(`${APIRoute.OFFERS}/${id}/nearby`); + return data; }, ); @@ -59,13 +65,9 @@ export const checkAuthAction = createAsyncThunk( 'data/checkAuth', - async (_arg, { dispatch, extra: api }) => { - try { - await api.get(APIRoute.LOGIN); - dispatch(requireAuthorization(AuthorizationStatus.AUTH)); - } catch { - dispatch(requireAuthorization(AuthorizationStatus.NO_AUTH)); - } + async (_arg, { extra: api }) => { + await api.get(APIRoute.LOGIN); + }, ); @@ -78,8 +80,7 @@ export const loginAction = createAsyncThunk { const { data: { token } } = await api.post(APIRoute.LOGIN, { email, password }); saveToken(token); - dispatch(requireAuthorization(AuthorizationStatus.AUTH)); - dispatch(redirectToRoute(AppRoute.Main)); + dispatch(userAction.redirectToRoute(AppRoute.Main)); }, ); @@ -89,35 +90,32 @@ export const logoutAction = createAsyncThunk( 'data/logout', - async (_arg, { dispatch, extra: api }) => { + async (_arg, { extra: api }) => { await api.delete(APIRoute.LOGOUT); - dispatch(requireAuthorization(AuthorizationStatus.NO_AUTH)); }, ); -export const fetchUserAction = createAsyncThunk( 'data/fetchUser', - async (_arg, { dispatch, extra: api }) => { + async (_arg, { extra: api }) => { const { data } = await api.get(APIRoute.LOGIN); - dispatch(loadDataUser(data)); + return data; }, ); -export const fetchFavoriteAction = createAsyncThunk( 'data/fetchFavorite', - async (_arg, { dispatch, extra: api }) => { - dispatch(setOfferDataLoadingStatus(true)); + async (_arg, { extra: api }) => { const { data } = await api.get(APIRoute.FAVORITE); - dispatch(setOfferDataLoadingStatus(false)); - dispatch(loadFavorite(data)); + return data; }, ); diff --git a/src/store/index.ts b/src/store/index.ts index 4671011..8bada9d 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -1,18 +1,18 @@ import { configureStore } from '@reduxjs/toolkit'; -import { reducer } from './reducer'; import { createAPI } from '../service/api'; import { redirect } from './middlewares/redirect'; +import rootReducer from './rootReduser'; const api = createAPI(); const store = configureStore({ - reducer, middleware: (getDefaultMiddleware) => getDefaultMiddleware({ thunk: { extraArgument: api, }, }).concat(redirect), + reducer: rootReducer }); export { store }; diff --git a/src/store/middlewares/redirect.ts b/src/store/middlewares/redirect.ts index 8087b2d..c0044a1 100644 --- a/src/store/middlewares/redirect.ts +++ b/src/store/middlewares/redirect.ts @@ -1,16 +1,16 @@ import { PayloadAction } from '@reduxjs/toolkit'; import browserHistory from '../../browser-history'; import { Middleware } from 'redux'; -import { reducer } from '../reducer'; -import { redirectToRoute } from '../action'; +import rootReducer from '../rootReduser'; +import { userAction } from '../slice/user'; -type Reducer = ReturnType; +type Reducer = ReturnType; export const redirect: Middleware = () => (next) => (action: PayloadAction) => { - if (action.type === redirectToRoute.type) { + if (action.type === userAction.redirectToRoute.type) { browserHistory.push(action.payload); } diff --git a/src/store/reducer.ts b/src/store/reducer.ts deleted file mode 100644 index 765cca0..0000000 --- a/src/store/reducer.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { createReducer } from '@reduxjs/toolkit'; -import { selectCity, sortOffer, loadDataUser, loadOffer, loadFavorite, requireAuthorization, setOfferDataLoadingStatus, loadComments, chooseOffer, chooseId } from './action'; -import { sortingOffers } from '../hooks/sort'; -import { LocationCity } from '../const'; -import { Offer } from '../types/offer'; -import { OfferPreviews } from '../types/offer-preview'; -import { TUser } from '../types/user'; -import { AuthorizationStatus } from '../const'; -import { Comment } from '../types/comment'; - -const initialState = { - city: LocationCity.PARIS, - offers: [], - initialOffers: [], - authorizationStatus: AuthorizationStatus.UN_KNOWN, - favorite: [], - isOfferDataLoadingStatus: false, - comments: [], - dataUser: null, - currentOffer: null, - idOffer: null, -}; - -const reducer = createReducer(initialState, (builder) => { - builder - .addCase(selectCity, (state, action) => { - state.city = action.payload; - state.offers = state.initialOffers.filter((offer) => offer.city.name === state.city); - }) - .addCase(sortOffer, (state, action) => { - state.offers = sortingOffers(action.payload, state.offers, state.initialOffers); - }) - .addCase(loadOffer, (state, action) => { - state.initialOffers = action.payload; - state.offers = state.initialOffers.filter((offer) => offer.city.name === state.city); - }) - .addCase(setOfferDataLoadingStatus, (state, action) => { - state.isOfferDataLoadingStatus = action.payload; - }) - .addCase(chooseId, (state, action) => { - state.idOffer = action.payload; - }) - .addCase(loadComments, (state, action) => { - state.comments = action.payload; - }) - .addCase(chooseOffer, (state, action) => { - state.currentOffer = action.payload; - }) - .addCase(loadDataUser, (state, action) => { - state.dataUser = action.payload; - }) - .addCase(loadFavorite, (state, action) => { - state.favorite = action.payload; - }) - .addCase(requireAuthorization, (state, action) => { - state.authorizationStatus = action.payload; - }); -}); - -export { reducer }; - diff --git a/src/store/rootReduser.ts b/src/store/rootReduser.ts new file mode 100644 index 0000000..3aba15e --- /dev/null +++ b/src/store/rootReduser.ts @@ -0,0 +1,16 @@ +import { combineReducers } from '@reduxjs/toolkit'; +import { offersSlice } from './slice/offers'; +import { offerSlice } from './slice/offer'; +import { favoriteSlice } from './slice/favorite'; +import { reviewsSlice } from './slice/reviews'; +import { userSlice } from './slice/user'; + +const rootReducer = combineReducers({ + [offersSlice.name]: offersSlice.reducer, + [offerSlice.name]: offerSlice.reducer, + [reviewsSlice.name]: reviewsSlice.reducer, + [userSlice.name]: userSlice.reducer, + [favoriteSlice.name]: favoriteSlice.reducer +}); + +export default rootReducer; diff --git a/src/store/slice/favorite.ts b/src/store/slice/favorite.ts new file mode 100644 index 0000000..6d601fe --- /dev/null +++ b/src/store/slice/favorite.ts @@ -0,0 +1,41 @@ +import { createSlice } from '@reduxjs/toolkit'; +import { fetchFavoriteAction } from '../api-action'; +import { OfferPreviews } from '../../types/offer-preview'; + +type TInitialState = { + isOfferDataLoadingStatus: boolean; + favorite: OfferPreviews[]; +} + +const initialState: TInitialState = { + isOfferDataLoadingStatus: false, + favorite: [], +}; + +const favoriteSlice = createSlice({ + extraReducers(builder) { + builder + .addCase(fetchFavoriteAction.pending, (state) => { + state.isOfferDataLoadingStatus = true; + }) + .addCase(fetchFavoriteAction.fulfilled, (state, action) => { + state.favorite = action.payload; + state.isOfferDataLoadingStatus = false; + }) + .addCase(fetchFavoriteAction.rejected, (state) => { + state.isOfferDataLoadingStatus = false; + }); + }, + initialState, + name: 'favorite', + reducers: { + + }, + selectors: { + favorite: (state) => state.favorite, + } +}); + +const favoriteSelectors = favoriteSlice.selectors; + +export { favoriteSlice, favoriteSelectors }; diff --git a/src/store/slice/offer.ts b/src/store/slice/offer.ts new file mode 100644 index 0000000..423fd3e --- /dev/null +++ b/src/store/slice/offer.ts @@ -0,0 +1,62 @@ +import { createSlice } from '@reduxjs/toolkit'; +import { fetchOfferAction } from '../api-action'; +import { Offer } from '../../types/offer'; +import { RequestStatus } from '../../const'; +import { fetchOfferNearbyAction } from '../api-action'; +import { OfferPreviews } from '../../types/offer-preview'; + +type TInitialState = { + status: string; + isOfferDataLoadingStatus: boolean; + nearby: OfferPreviews[]; + currentOffer: Offer | null; +} + +const initialState: TInitialState = { + status: RequestStatus.NONE, + isOfferDataLoadingStatus: false, + nearby: [], + currentOffer: null, +}; + +const offerSlice = createSlice({ + extraReducers: (builder) => { + builder + .addCase(fetchOfferAction.pending, (state) => { + state.isOfferDataLoadingStatus = true; + state.status = RequestStatus.LOADING; + }) + .addCase(fetchOfferAction.fulfilled, (state, action) => { + state.isOfferDataLoadingStatus = false; + state.currentOffer = action.payload; + state.status = RequestStatus.SUCCESS; + }) + .addCase(fetchOfferAction.rejected, (state) => { + state.isOfferDataLoadingStatus = false; + state.status = RequestStatus.FAILED; + }) + .addCase(fetchOfferNearbyAction.fulfilled, (state, action) => { + state.isOfferDataLoadingStatus = false; + state.nearby = action.payload; + }); + }, + initialState, + name: 'offer', + reducers: { + clear(state) { + state.currentOffer = null; + }, + }, + selectors: { + currentOffer: (state: TInitialState) => state.currentOffer, + nearby: (state: TInitialState) => state.nearby, + } +}); + +const offerAction = { ...offerSlice.actions, fetchOfferAction }; + +const offerSelector = offerSlice.selectors; + +export { offerAction, offerSelector, offerSlice }; + + diff --git a/src/store/slice/offers.ts b/src/store/slice/offers.ts new file mode 100644 index 0000000..293dc00 --- /dev/null +++ b/src/store/slice/offers.ts @@ -0,0 +1,69 @@ +import { createSlice } from '@reduxjs/toolkit'; +import type { PayloadAction } from '@reduxjs/toolkit'; +import { LocationCity, RequestStatus } from '../../const'; +import { OfferPreviews } from '../../types/offer-preview'; +import { sortingOffers } from '../../hooks/sort'; +import { fetchOffersAction } from '../api-action'; + +type TInitialState = { + city: string; + offers: OfferPreviews[]; + initialOffers: OfferPreviews[]; + status: string; + isOfferDataLoadingStatus: boolean; +} + +const initialState: TInitialState = { + city: LocationCity.PARIS, + offers: [], + initialOffers: [], + status: RequestStatus.NONE, + isOfferDataLoadingStatus: false, +}; + +const offersSlice = createSlice({ + extraReducers: (builder) => { + builder + .addCase(fetchOffersAction.pending, (state) => { + state.isOfferDataLoadingStatus = true; + state.status = RequestStatus.LOADING; + }) + .addCase(fetchOffersAction.fulfilled, (state, action) => { + state.isOfferDataLoadingStatus = false; + state.initialOffers = action.payload; + state.offers = state.initialOffers.filter((offer) => offer.city.name === state.city); + state.status = RequestStatus.SUCCESS; + }) + .addCase(fetchOffersAction.rejected, (state) => { + state.isOfferDataLoadingStatus = false; + state.status = RequestStatus.FAILED; + }); + }, + initialState, + name: 'offers', + reducers: { + selectCity: (state, action: PayloadAction) => { + state.city = action.payload; + state.offers = state.initialOffers.filter((offer) => offer.city.name === state.city); + }, + sortOffer: (state, action: PayloadAction) => { + state.offers = sortingOffers(action.payload, state.offers, state.initialOffers); + }, + loadOffer: (state, action: PayloadAction) => { + state.initialOffers = action.payload; + state.offers = state.initialOffers.filter((offer) => offer.city.name === state.city); + }, + }, + selectors: { + isOffersDataLoading: (state: TInitialState) => state.isOfferDataLoadingStatus, + city: (state: TInitialState) => state.city, + offers: (state: TInitialState) => state.offers, + status: (state: TInitialState) => state.status, + } +}); + +const { selectCity, sortOffer, loadOffer } = offersSlice.actions; + +const offersSelectors = offersSlice.selectors; + +export { selectCity, sortOffer, loadOffer, offersSlice, offersSelectors }; diff --git a/src/store/slice/reviews.ts b/src/store/slice/reviews.ts new file mode 100644 index 0000000..8d6ab5a --- /dev/null +++ b/src/store/slice/reviews.ts @@ -0,0 +1,50 @@ +import { createSlice } from '@reduxjs/toolkit'; +import { fetchCommentsAction } from '../api-action'; +import { RequestStatus } from '../../const'; +import { Comment } from '../../types/comment'; + +type TInitialState = { + status: string; + isOfferDataLoadingStatus: boolean; + comments: Comment[]; +} + +const initialState: TInitialState = { + status: RequestStatus.NONE, + isOfferDataLoadingStatus: false, + comments: [], +}; + +const reviewsSlice = createSlice({ + extraReducers: (builder) => { + builder + .addCase(fetchCommentsAction.pending, (state) => { + state.status = RequestStatus.LOADING; + }) + .addCase(fetchCommentsAction.fulfilled, (state, action) => { + state.status = RequestStatus.SUCCESS; + state.isOfferDataLoadingStatus = false; + state.comments = action.payload; + }) + .addCase(fetchCommentsAction.rejected, (state) => { + state.isOfferDataLoadingStatus = false; + state.status = RequestStatus.FAILED; + }); + }, + initialState, + name: 'reviews', + reducers: { + clear(state) { + state.comments = []; + } + }, + selectors: { + comments: (state: TInitialState) => state.comments, + } +}); + +const reviewsAction = { ...reviewsSlice.actions, fetchCommentsAction }; + +const reviewsSelector = reviewsSlice.selectors; + +export { reviewsAction, reviewsSelector, reviewsSlice }; diff --git a/src/store/slice/user.ts b/src/store/slice/user.ts new file mode 100644 index 0000000..811d450 --- /dev/null +++ b/src/store/slice/user.ts @@ -0,0 +1,68 @@ +import { PayloadAction, createSlice } from '@reduxjs/toolkit'; +import { AppRoute, AuthorizationStatus, RequestStatus } from '../../const'; +import { fetchUserAction, loginAction, logoutAction } from '../api-action'; +import { TUser } from '../../types/user'; + +type TInitialState = { + status: string; + isOfferDataLoadingStatus: boolean; + authorizationStatus: string; + dataUser: TUser | null; +} + +const initialState: TInitialState = { + status: RequestStatus.NONE, + authorizationStatus: AuthorizationStatus.UN_KNOWN, + isOfferDataLoadingStatus: false, + dataUser: null, +}; + +const userSlice = createSlice({ + extraReducers: (builder) => { + builder + .addCase(loginAction.pending, (state) => { + state.isOfferDataLoadingStatus = true; + }) + .addCase(loginAction.fulfilled, (state) => { + state.isOfferDataLoadingStatus = false; + }) + .addCase(logoutAction.pending, (state) => { + state.isOfferDataLoadingStatus = true; + }) + .addCase(logoutAction.fulfilled, (state) => { + state.isOfferDataLoadingStatus = false; + state.authorizationStatus = AuthorizationStatus.NO_AUTH; + }) + .addCase(fetchUserAction.pending, (state) => { + state.isOfferDataLoadingStatus = true; + }) + .addCase(fetchUserAction.fulfilled, (state, action) => { + state.isOfferDataLoadingStatus = false; + state.authorizationStatus = AuthorizationStatus.AUTH; + state.dataUser = action.payload; + }) + .addCase(fetchUserAction.rejected, (state) => { + state.isOfferDataLoadingStatus = false; + state.authorizationStatus = AuthorizationStatus.NO_AUTH; + }); + }, + initialState, + name: 'user', + reducers: { + redirectToRoute: (state, action: PayloadAction) => { + + }, + }, + selectors: { + dataUser: (state: TInitialState) => state.dataUser, + authorizationStatus: (state: TInitialState) => state.authorizationStatus, + } +}); + +const userAction = userSlice.actions; + +const userSelector = userSlice.selectors; + +export { userAction, userSelector, userSlice }; + +