diff --git a/package-lock.json b/package-lock.json index 4979841..188f2ad 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", "dayjs": "1.11.10", "history": "5.3.0", @@ -17,7 +17,6 @@ "react": "18.2.0", "react-dom": "18.2.0", "react-helmet-async": "1.3.0", - "react-redux": "8.1.3", "react-router-dom": "6.16.0", "vite-plugin-rewrite-all": "1.0.2" }, @@ -30,7 +29,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", @@ -1174,18 +1173,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": { @@ -1196,6 +1195,19 @@ } } }, + "node_modules/@reduxjs/toolkit/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==" + }, + "node_modules/@reduxjs/toolkit/node_modules/redux-thunk": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", + "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", + "peerDependencies": { + "redux": "^5.0.0" + } + }, "node_modules/@remix-run/router": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.9.0.tgz", @@ -1395,6 +1407,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==", + "devOptional": true, "dependencies": { "@types/react": "*", "hoist-non-react-statics": "^3.3.0" @@ -1496,12 +1509,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": "*", @@ -1518,9 +1533,9 @@ } }, "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", @@ -1532,7 +1547,8 @@ "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", @@ -1558,7 +1574,9 @@ "node_modules/@types/use-sync-external-store": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz", - "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==" + "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==", + "optional": true, + "peer": true }, "node_modules/@types/yargs": { "version": "17.0.28", @@ -2473,7 +2491,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", @@ -3683,6 +3702,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==", + "devOptional": true, "dependencies": { "react-is": "^16.7.0" } @@ -3759,9 +3779,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" @@ -5235,6 +5255,8 @@ "version": "8.1.3", "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.1.3.tgz", "integrity": "sha512-n0ZrutD7DaX/j9VscF+uTALI3oUPa/pO4Z3soOBIjuRn/FzVu6aehhysxZCLi6y7duMf52WNZGMl7CtuK5EnRw==", + "optional": true, + "peer": true, "dependencies": { "@babel/runtime": "^7.12.1", "@types/hoist-non-react-statics": "^3.3.1", @@ -5272,7 +5294,9 @@ "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==" + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "optional": true, + "peer": true }, "node_modules/react-refresh": { "version": "0.14.0", @@ -5423,18 +5447,11 @@ "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==", + "devOptional": true, "dependencies": { "@babel/runtime": "^7.9.2" } }, - "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==", - "peerDependencies": { - "redux": "^4" - } - }, "node_modules/regenerator-runtime": { "version": "0.13.11", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", @@ -5506,9 +5523,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", @@ -6199,6 +6216,8 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", + "optional": true, + "peer": true, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0" } diff --git a/package.json b/package.json index b7a9d16..92a986a 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", "dayjs": "1.11.10", "history": "5.3.0", @@ -19,7 +19,6 @@ "react": "18.2.0", "react-dom": "18.2.0", "react-helmet-async": "1.3.0", - "react-redux": "8.1.3", "react-router-dom": "6.16.0", "vite-plugin-rewrite-all": "1.0.2" }, @@ -32,7 +31,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", diff --git a/src/app.tsx b/src/app.tsx index 9d2f9d2..45c270a 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -7,15 +7,8 @@ import Offer from './pages/offer/offer.tsx'; import PrivateRoute from './components/private-route/private-route.tsx'; import Login from './pages/login/login.tsx'; import PublicRoute from './components/public-route/public-route.tsx'; -import {OfferShortInfo} from './types/offer.ts'; -interface AppProps { - offersCount: number; - offers: OfferShortInfo[]; - offersFavorites: OfferShortInfo[]; -} - -function App({offersCount, offers, offersFavorites}: AppProps) { +function App() { return ( @@ -27,8 +20,8 @@ function App({offersCount, offers, offersFavorites}: AppProps) { {CITIES.map((city) => ( } + path={AppRoute.Root + city.name} + element={
} /> ) )} @@ -48,7 +41,7 @@ function App({offersCount, offers, offersFavorites}: AppProps) { - + } /> diff --git a/src/components/location-tab/location-tab.tsx b/src/components/location-tab/location-tab.tsx index f1d4663..0a7f482 100644 --- a/src/components/location-tab/location-tab.tsx +++ b/src/components/location-tab/location-tab.tsx @@ -11,7 +11,7 @@ function LocationTab({cities = CITIES}: LocationTabProps) { cities.map((city) => ( classNames('locations__item-link tabs__item', isActive && 'tabs__item--active')} > diff --git a/src/components/map/map.tsx b/src/components/map/map.tsx index 62e8d92..a24592e 100644 --- a/src/components/map/map.tsx +++ b/src/components/map/map.tsx @@ -5,11 +5,12 @@ import {CITIES, URL_MARKER_CURRENT, URL_MARKER_DEFAULT} from '../../const'; import 'leaflet/dist/leaflet.css'; import useMap from '../../hooks/use-map.tsx'; import {OfferShortInfo} from '../../types/offer.ts'; +import {useAppSelector} from '../../hooks/store.ts'; +import {offersSelectors} from '../../store/slices/offers.ts'; type MapProps = { city: typeof CITIES[number]; offers: OfferShortInfo[]; - activeOffer: OfferShortInfo | null; container: string; }; @@ -25,7 +26,8 @@ const currentCustomIcon = new Icon({ iconAnchor: [20, 40] }); -function Map({container, city, offers, activeOffer}: MapProps) { +function Map({container, city, offers}: MapProps) { + const activeOffer = useAppSelector(offersSelectors.activeOffer); const mapRef = useRef(null); const map = useMap(mapRef, city); diff --git a/src/const.ts b/src/const.ts index b960f0c..5528a4f 100644 --- a/src/const.ts +++ b/src/const.ts @@ -29,7 +29,7 @@ const CITY_NAMES = [ 'Dusseldorf', ] as const; -const DEFAULT_CITY_SLUG = CITIES[0].slug; +const DEFAULT_CITY_SLUG = CITIES[0].name; const OFFER_TYPES = [ 'hotel', diff --git a/src/hooks/store.ts b/src/hooks/store.ts index 1859cbc..4145bc8 100644 --- a/src/hooks/store.ts +++ b/src/hooks/store.ts @@ -1,7 +1,15 @@ import {TypedUseSelectorHook, useDispatch, useSelector} from 'react-redux'; import {AppDispatch, RootState} from '../types/store.ts'; +import {ActionCreatorsMapObject, bindActionCreators} from '@reduxjs/toolkit'; +import {useMemo} from 'react'; const useAppDispatch = useDispatch; const useAppSelector: TypedUseSelectorHook = useSelector; -export {useAppDispatch, useAppSelector}; +const useActionCreators = (actions: Actions) => { + const dispatch = useAppDispatch(); + + return useMemo(() => bindActionCreators(actions, dispatch), []); +}; + +export {useAppDispatch, useAppSelector, useActionCreators}; diff --git a/src/index.tsx b/src/index.tsx index 2b8e91f..99bcb6f 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,14 +1,9 @@ import React from 'react'; import ReactDOM from 'react-dom/client'; import App from './app.tsx'; -import {OFFERS_COUNT} from './const.ts'; -import {getFavoriteOffers, getOffersShortInfo} from './mocks/offers.ts'; import {Provider} from 'react-redux'; import {store} from './store'; -const offers = getOffersShortInfo(); -const offersFavorites = getFavoriteOffers(); - const root = ReactDOM.createRoot( document.getElementById('root') as HTMLElement ); @@ -16,7 +11,7 @@ const root = ReactDOM.createRoot( root.render( - + ); diff --git a/src/mocks/offers.ts b/src/mocks/offers.ts index 5b3f774..be66227 100644 --- a/src/mocks/offers.ts +++ b/src/mocks/offers.ts @@ -2,11 +2,402 @@ import {OfferShortInfo, OfferFullInfo} from '../types/offer.ts'; const OFFERS_SHORT_INFO: OfferShortInfo[] = [ { - 'id': '655253b6-082c-4276-a0e2-15464a0c9d30', - 'title': 'Amazing and Extremely Central Flat', - 'type': 'apartment', - 'price': 307, + 'id': 'c994b8d8-5425-4ac1-afc5-83a58021cb75', + 'title': 'Beautiful & luxurious apartment at great location', + 'type': 'house', + 'price': 324, + 'previewImage': 'https://15.design.htmlacademy.pro/static/hotel/19.jpg', + 'city': { + 'name': 'Paris', + 'location': { + 'latitude': 48.85661, + 'longitude': 2.351499, + 'zoom': 13 + } + }, + 'location': { + 'latitude': 48.837610000000005, + 'longitude': 2.364499, + 'zoom': 16 + }, + 'isFavorite': false, + 'isPremium': false, + 'rating': 2.1 + }, + { + 'id': 'f7fa7b71-004e-4f33-959b-cb9944eccd12', + 'title': 'Loft Studio in the Central Area', + 'type': 'room', + 'price': 275, + 'previewImage': 'https://15.design.htmlacademy.pro/static/hotel/6.jpg', + 'city': { + 'name': 'Paris', + 'location': { + 'latitude': 48.85661, + 'longitude': 2.351499, + 'zoom': 13 + } + }, + 'location': { + 'latitude': 48.843610000000005, + 'longitude': 2.338499, + 'zoom': 16 + }, + 'isFavorite': false, + 'isPremium': true, + 'rating': 2.2 + }, + { + 'id': 'a54a5970-356c-410b-bbc0-cdaae9eb866e', + 'title': 'The Pondhouse - A Magical Place', + 'type': 'hotel', + 'price': 329, + 'previewImage': 'https://15.design.htmlacademy.pro/static/hotel/11.jpg', + 'city': { + 'name': 'Cologne', + 'location': { + 'latitude': 50.938361, + 'longitude': 6.959974, + 'zoom': 13 + } + }, + 'location': { + 'latitude': 50.934361, + 'longitude': 6.943974, + 'zoom': 16 + }, + 'isFavorite': false, + 'isPremium': true, + 'rating': 2.3 + }, + { + 'id': '68f71f21-1a94-444a-8f3c-284cb6446b12', + 'title': 'Tile House', + 'type': 'house', + 'price': 802, + 'previewImage': 'https://15.design.htmlacademy.pro/static/hotel/5.jpg', + 'city': { + 'name': 'Cologne', + 'location': { + 'latitude': 50.938361, + 'longitude': 6.959974, + 'zoom': 13 + } + }, + 'location': { + 'latitude': 50.918461, + 'longitude': 6.969974, + 'zoom': 16 + }, + 'isFavorite': false, + 'isPremium': false, + 'rating': 1.8 + }, + { + 'id': '06c3555b-0752-43df-ad7b-7c973552b404', + 'title': 'Nice, cozy, warm big bed apartment', + 'type': 'room', + 'price': 240, + 'previewImage': 'https://15.design.htmlacademy.pro/static/hotel/20.jpg', + 'city': { + 'name': 'Cologne', + 'location': { + 'latitude': 50.938361, + 'longitude': 6.959974, + 'zoom': 13 + } + }, + 'location': { + 'latitude': 50.957361, + 'longitude': 6.9509739999999995, + 'zoom': 16 + }, + 'isFavorite': false, + 'isPremium': false, + 'rating': 4.2 + }, + { + 'id': '4f29bce0-e72d-4a5b-84b5-26b7b80febfd', + 'title': 'Beautiful & luxurious apartment at great location', + 'type': 'hotel', + 'price': 301, + 'previewImage': 'https://15.design.htmlacademy.pro/static/hotel/17.jpg', + 'city': { + 'name': 'Cologne', + 'location': { + 'latitude': 50.938361, + 'longitude': 6.959974, + 'zoom': 13 + } + }, + 'location': { + 'latitude': 50.951361, + 'longitude': 6.944974, + 'zoom': 16 + }, + 'isFavorite': false, + 'isPremium': false, + 'rating': 3.1 + }, + { + 'id': '0a48ad96-81df-43be-84f9-6608b30f0ea9', + 'title': 'Canal View Prinsengracht', + 'type': 'room', + 'price': 159, + 'previewImage': 'https://15.design.htmlacademy.pro/static/hotel/11.jpg', + 'city': { + 'name': 'Cologne', + 'location': { + 'latitude': 50.938361, + 'longitude': 6.959974, + 'zoom': 13 + } + }, + 'location': { + 'latitude': 50.959361, + 'longitude': 6.978974, + 'zoom': 16 + }, + 'isFavorite': false, + 'isPremium': false, + 'rating': 4.1 + }, + { + 'id': 'cc801bd7-68df-480f-9f5f-b6084bfb8f47', + 'title': 'Wood and stone place', + 'type': 'house', + 'price': 324, + 'previewImage': 'https://15.design.htmlacademy.pro/static/hotel/16.jpg', + 'city': { + 'name': 'Cologne', + 'location': { + 'latitude': 50.938361, + 'longitude': 6.959974, + 'zoom': 13 + } + }, + 'location': { + 'latitude': 50.932361, + 'longitude': 6.960974, + 'zoom': 16 + }, + 'isFavorite': false, + 'isPremium': true, + 'rating': 4.2 + }, + { + 'id': '203f83bf-7b3d-44d4-9955-b6a785394211', + 'title': 'Wood and stone place', + 'type': 'room', + 'price': 145, + 'previewImage': 'https://15.design.htmlacademy.pro/static/hotel/10.jpg', + 'city': { + 'name': 'Cologne', + 'location': { + 'latitude': 50.938361, + 'longitude': 6.959974, + 'zoom': 13 + } + }, + 'location': { + 'latitude': 50.954361, + 'longitude': 6.982974, + 'zoom': 16 + }, + 'isFavorite': false, + 'isPremium': false, + 'rating': 3.5 + }, + { + 'id': '736e1ca2-f771-4fe4-b48c-a46ccc86c9a8', + 'title': 'Waterfront with extraordinary view', + 'type': 'hotel', + 'price': 342, + 'previewImage': 'https://15.design.htmlacademy.pro/static/hotel/4.jpg', + 'city': { + 'name': 'Cologne', + 'location': { + 'latitude': 50.938361, + 'longitude': 6.959974, + 'zoom': 13 + } + }, + 'location': { + 'latitude': 50.945361, + 'longitude': 6.962974, + 'zoom': 16 + }, + 'isFavorite': false, + 'isPremium': true, + 'rating': 1.6 + }, + { + 'id': 'c7f257e8-d6a9-420c-99ff-6134d7a62b18', + 'title': 'Penthouse, 4-5 rooms + 5 balconies', + 'type': 'room', + 'price': 211, + 'previewImage': 'https://15.design.htmlacademy.pro/static/hotel/1.jpg', + 'city': { + 'name': 'Cologne', + 'location': { + 'latitude': 50.938361, + 'longitude': 6.959974, + 'zoom': 13 + } + }, + 'location': { + 'latitude': 50.917361, + 'longitude': 6.977974, + 'zoom': 16 + }, + 'isFavorite': false, + 'isPremium': true, + 'rating': 3.9 + }, + { + 'id': '11dafbb5-45aa-4d48-8263-112033adcd74', + 'title': 'The house among olive ', + 'type': 'house', + 'price': 942, + 'previewImage': 'https://15.design.htmlacademy.pro/static/hotel/19.jpg', + 'city': { + 'name': 'Cologne', + 'location': { + 'latitude': 50.938361, + 'longitude': 6.959974, + 'zoom': 13 + } + }, + 'location': { + 'latitude': 50.945361, + 'longitude': 6.935974, + 'zoom': 16 + }, + 'isFavorite': false, + 'isPremium': false, + 'rating': 3.3 + }, + { + 'id': '53518427-5b12-4563-acef-76d0eb3bcb12', + 'title': 'The house among olive ', + 'type': 'house', + 'price': 350, 'previewImage': 'https://15.design.htmlacademy.pro/static/hotel/8.jpg', + 'city': { + 'name': 'Brussels', + 'location': { + 'latitude': 50.846557, + 'longitude': 4.351697, + 'zoom': 13 + } + }, + 'location': { + 'latitude': 50.854557, + 'longitude': 4.364697, + 'zoom': 16 + }, + 'isFavorite': false, + 'isPremium': false, + 'rating': 4 + }, + { + 'id': 'd290e408-6797-42c7-8e5a-aca60f695ba4', + 'title': 'Wood and stone place', + 'type': 'hotel', + 'price': 427, + 'previewImage': 'https://15.design.htmlacademy.pro/static/hotel/13.jpg', + 'city': { + 'name': 'Brussels', + 'location': { + 'latitude': 50.846557, + 'longitude': 4.351697, + 'zoom': 13 + } + }, + 'location': { + 'latitude': 50.842557, + 'longitude': 4.3536969999999995, + 'zoom': 16 + }, + 'isFavorite': false, + 'isPremium': true, + 'rating': 1.8 + }, + { + 'id': '33b61a8e-cdbc-4e80-93cc-68a36d3f0148', + 'title': 'Penthouse, 4-5 rooms + 5 balconies', + 'type': 'hotel', + 'price': 142, + 'previewImage': 'https://15.design.htmlacademy.pro/static/hotel/2.jpg', + 'city': { + 'name': 'Brussels', + 'location': { + 'latitude': 50.846557, + 'longitude': 4.351697, + 'zoom': 13 + } + }, + 'location': { + 'latitude': 50.822556999999996, + 'longitude': 4.347697, + 'zoom': 16 + }, + 'isFavorite': false, + 'isPremium': true, + 'rating': 4.2 + }, + { + 'id': '66894dec-8af7-4630-adb4-19131d1cd67c', + 'title': 'The Joshua Tree House', + 'type': 'room', + 'price': 230, + 'previewImage': 'https://15.design.htmlacademy.pro/static/hotel/2.jpg', + 'city': { + 'name': 'Brussels', + 'location': { + 'latitude': 50.846557, + 'longitude': 4.351697, + 'zoom': 13 + } + }, + 'location': { + 'latitude': 50.867557, + 'longitude': 4.357697, + 'zoom': 16 + }, + 'isFavorite': false, + 'isPremium': false, + 'rating': 1.4 + }, + { + 'id': 'a2baa3f4-cb6a-4cb7-9717-121749833440', + 'title': 'The Joshua Tree House', + 'type': 'hotel', + 'price': 204, + 'previewImage': 'https://15.design.htmlacademy.pro/static/hotel/4.jpg', + 'city': { + 'name': 'Brussels', + 'location': { + 'latitude': 50.846557, + 'longitude': 4.351697, + 'zoom': 13 + } + }, + 'location': { + 'latitude': 50.842557, + 'longitude': 4.363696999999999, + 'zoom': 16 + }, + 'isFavorite': false, + 'isPremium': false, + 'rating': 4.8 + }, + { + 'id': 'd1882a99-4b05-46d5-864d-9472db885664', + 'title': 'Loft Studio in the Central Area', + 'type': 'hotel', + 'price': 197, + 'previewImage': 'https://15.design.htmlacademy.pro/static/hotel/12.jpg', 'city': { 'name': 'Amsterdam', 'location': { @@ -16,20 +407,181 @@ const OFFERS_SHORT_INFO: OfferShortInfo[] = [ } }, 'location': { - 'latitude': 52.3909553943508, - 'longitude': 4.85309666406198, + 'latitude': 52.36554, + 'longitude': 4.911976, 'zoom': 16 }, - 'isFavorite': true, + 'isFavorite': false, 'isPremium': true, + 'rating': 1.1 + }, + { + 'id': 'cd3a717b-d364-412e-b798-0c00f9d48f28', + 'title': 'Beautiful & luxurious apartment at great location', + 'type': 'house', + 'price': 811, + 'previewImage': 'https://15.design.htmlacademy.pro/static/hotel/7.jpg', + 'city': { + 'name': 'Amsterdam', + 'location': { + 'latitude': 52.37454, + 'longitude': 4.897976, + 'zoom': 13 + } + }, + 'location': { + 'latitude': 52.385540000000006, + 'longitude': 4.902976, + 'zoom': 16 + }, + 'isFavorite': false, + 'isPremium': false, 'rating': 2 }, { - 'id': 'c5bc3b98-c10a-45e7-b617-93d69ab16712', + 'id': 'c1d3e3a4-b459-4a0f-b29f-4bd21b8ea6d0', + 'title': 'The house among olive ', + 'type': 'apartment', + 'price': 432, + 'previewImage': 'https://15.design.htmlacademy.pro/static/hotel/19.jpg', + 'city': { + 'name': 'Amsterdam', + 'location': { + 'latitude': 52.37454, + 'longitude': 4.897976, + 'zoom': 13 + } + }, + 'location': { + 'latitude': 52.397540000000006, + 'longitude': 4.9099759999999995, + 'zoom': 16 + }, + 'isFavorite': false, + 'isPremium': true, + 'rating': 3.3 + }, + { + 'id': '09264438-c4b6-472a-ae19-8c33647eae6d', 'title': 'The Pondhouse - A Magical Place', + 'type': 'apartment', + 'price': 256, + 'previewImage': 'https://15.design.htmlacademy.pro/static/hotel/18.jpg', + 'city': { + 'name': 'Amsterdam', + 'location': { + 'latitude': 52.37454, + 'longitude': 4.897976, + 'zoom': 13 + } + }, + 'location': { + 'latitude': 52.37454, + 'longitude': 4.881976, + 'zoom': 16 + }, + 'isFavorite': false, + 'isPremium': false, + 'rating': 5 + }, + { + 'id': '4e1c8df9-ca15-4d48-8883-24b9d2d02500', + 'title': 'Canal View Prinsengracht', + 'type': 'apartment', + 'price': 145, + 'previewImage': 'https://15.design.htmlacademy.pro/static/hotel/5.jpg', + 'city': { + 'name': 'Amsterdam', + 'location': { + 'latitude': 52.37454, + 'longitude': 4.897976, + 'zoom': 13 + } + }, + 'location': { + 'latitude': 52.367540000000005, + 'longitude': 4.883976, + 'zoom': 16 + }, + 'isFavorite': false, + 'isPremium': false, + 'rating': 4.4 + }, + { + 'id': 'c37dc428-d079-4d65-8bc7-ce8ac2f6fa36', + 'title': 'Waterfront with extraordinary view', + 'type': 'apartment', + 'price': 147, + 'previewImage': 'https://15.design.htmlacademy.pro/static/hotel/11.jpg', + 'city': { + 'name': 'Amsterdam', + 'location': { + 'latitude': 52.37454, + 'longitude': 4.897976, + 'zoom': 13 + } + }, + 'location': { + 'latitude': 52.361540000000005, + 'longitude': 4.883976, + 'zoom': 16 + }, + 'isFavorite': false, + 'isPremium': false, + 'rating': 4.9 + }, + { + 'id': 'a674e9f8-08a3-47cd-b89d-c122644c7864', + 'title': 'Penthouse, 4-5 rooms + 5 balconies', + 'type': 'hotel', + 'price': 464, + 'previewImage': 'https://15.design.htmlacademy.pro/static/hotel/8.jpg', + 'city': { + 'name': 'Amsterdam', + 'location': { + 'latitude': 52.37454, + 'longitude': 4.897976, + 'zoom': 13 + } + }, + 'location': { + 'latitude': 52.389540000000004, + 'longitude': 4.883976, + 'zoom': 16 + }, + 'isFavorite': false, + 'isPremium': true, + 'rating': 1.5 + }, + { + 'id': '9ee3adfc-30bb-4d65-9502-afba880158ff', + 'title': 'The Joshua Tree House', + 'type': 'hotel', + 'price': 309, + 'previewImage': 'https://15.design.htmlacademy.pro/static/hotel/16.jpg', + 'city': { + 'name': 'Amsterdam', + 'location': { + 'latitude': 52.37454, + 'longitude': 4.897976, + 'zoom': 13 + } + }, + 'location': { + 'latitude': 52.35054, + 'longitude': 4.908976, + 'zoom': 16 + }, + 'isFavorite': false, + 'isPremium': true, + 'rating': 4.4 + }, + { + 'id': '17d51e8e-b755-4eb8-baff-b6334e1b4c92', + 'title': 'The house among olive ', 'type': 'house', - 'price': 528, - 'previewImage': 'https://15.design.htmlacademy.pro/static/hotel/4.jpg', + 'price': 492, + 'previewImage': 'https://15.design.htmlacademy.pro/static/hotel/6.jpg', 'city': { 'name': 'Amsterdam', 'location': { @@ -39,20 +591,43 @@ const OFFERS_SHORT_INFO: OfferShortInfo[] = [ } }, 'location': { - 'latitude': 52.3609553943508, - 'longitude': 4.85309666406198, + 'latitude': 52.37154, + 'longitude': 4.889976, 'zoom': 16 }, 'isFavorite': false, 'isPremium': true, - 'rating': 1.1 + 'rating': 5 }, { - 'id': '31abd117-5027-4adb-8b89-388d1d4a9650', - 'title': 'Perfectly located Castro', + 'id': 'f6df4685-fe57-42e6-b0f3-8d76583cfb21', + 'title': 'Penthouse, 4-5 rooms + 5 balconies', + 'type': 'room', + 'price': 246, + 'previewImage': 'https://15.design.htmlacademy.pro/static/hotel/14.jpg', + 'city': { + 'name': 'Amsterdam', + 'location': { + 'latitude': 52.37454, + 'longitude': 4.897976, + 'zoom': 13 + } + }, + 'location': { + 'latitude': 52.364540000000005, + 'longitude': 4.9019759999999994, + 'zoom': 16 + }, + 'isFavorite': false, + 'isPremium': true, + 'rating': 1.2 + }, + { + 'id': 'c0a98a4e-4c8a-4812-9ae6-cf92664b0fc5', + 'title': 'Waterfront with extraordinary view', 'type': 'house', - 'price': 923, - 'previewImage': 'https://15.design.htmlacademy.pro/static/hotel/7.jpg', + 'price': 371, + 'previewImage': 'https://15.design.htmlacademy.pro/static/hotel/12.jpg', 'city': { 'name': 'Amsterdam', 'location': { @@ -62,20 +637,43 @@ const OFFERS_SHORT_INFO: OfferShortInfo[] = [ } }, 'location': { - 'latitude': 52.3909553943508, - 'longitude': 4.929309666406198, + 'latitude': 52.36354, + 'longitude': 4.911976, 'zoom': 16 }, 'isFavorite': false, 'isPremium': true, - 'rating': 2.3 + 'rating': 3.6 }, { - 'id': '0afa3f92-633b-4049-9199-cdd249a9a2f6', - 'title': 'Amazing and Extremely Central Flat', + 'id': '719c74b3-69b4-4aef-8b97-5d71f1a2060e', + 'title': 'The house among olive ', + 'type': 'house', + 'price': 302, + 'previewImage': 'https://15.design.htmlacademy.pro/static/hotel/11.jpg', + 'city': { + 'name': 'Amsterdam', + 'location': { + 'latitude': 52.37454, + 'longitude': 4.897976, + 'zoom': 13 + } + }, + 'location': { + 'latitude': 52.36354, + 'longitude': 4.889976, + 'zoom': 16 + }, + 'isFavorite': false, + 'isPremium': false, + 'rating': 1.6 + }, + { + 'id': 'a8c83622-8c40-4ec1-826a-a3ec04b595b7', + 'title': 'The house among olive ', 'type': 'apartment', - 'price': 123, - 'previewImage': 'https://15.design.htmlacademy.pro/static/hotel/19.jpg', + 'price': 100, + 'previewImage': 'https://15.design.htmlacademy.pro/static/hotel/13.jpg', 'city': { 'name': 'Amsterdam', 'location': { @@ -85,14 +683,221 @@ const OFFERS_SHORT_INFO: OfferShortInfo[] = [ } }, 'location': { - 'latitude': 52.3809553943508, - 'longitude': 4.939309666406198, + 'latitude': 52.388540000000006, + 'longitude': 4.899976, 'zoom': 16 }, 'isFavorite': false, 'isPremium': false, 'rating': 2.8 }, + { + 'id': '8d5e6777-71cb-4e31-afd6-3b817e1e18ab', + 'title': 'Waterfront with extraordinary view', + 'type': 'room', + 'price': 265, + 'previewImage': 'https://15.design.htmlacademy.pro/static/hotel/14.jpg', + 'city': { + 'name': 'Amsterdam', + 'location': { + 'latitude': 52.37454, + 'longitude': 4.897976, + 'zoom': 13 + } + }, + 'location': { + 'latitude': 52.36954, + 'longitude': 4.914976, + 'zoom': 16 + }, + 'isFavorite': false, + 'isPremium': false, + 'rating': 1.7 + }, + { + 'id': '75babdaf-e661-4bde-8de6-2e85ec851448', + 'title': 'Waterfront with extraordinary view', + 'type': 'apartment', + 'price': 377, + 'previewImage': 'https://15.design.htmlacademy.pro/static/hotel/3.jpg', + 'city': { + 'name': 'Amsterdam', + 'location': { + 'latitude': 52.37454, + 'longitude': 4.897976, + 'zoom': 13 + } + }, + 'location': { + 'latitude': 52.37554, + 'longitude': 4.9019759999999994, + 'zoom': 16 + }, + 'isFavorite': false, + 'isPremium': false, + 'rating': 2.9 + }, + { + 'id': '7873f6c7-b8e7-4f1b-ae55-7a17f79d189a', + 'title': 'Tile House', + 'type': 'house', + 'price': 557, + 'previewImage': 'https://15.design.htmlacademy.pro/static/hotel/4.jpg', + 'city': { + 'name': 'Amsterdam', + 'location': { + 'latitude': 52.37454, + 'longitude': 4.897976, + 'zoom': 13 + } + }, + 'location': { + 'latitude': 52.385540000000006, + 'longitude': 4.886976, + 'zoom': 16 + }, + 'isFavorite': false, + 'isPremium': true, + 'rating': 4.1 + }, + { + 'id': '467f990d-51a6-4be6-b2a1-5d8f31494c9b', + 'title': 'Penthouse, 4-5 rooms + 5 balconies', + 'type': 'room', + 'price': 166, + 'previewImage': 'https://15.design.htmlacademy.pro/static/hotel/13.jpg', + 'city': { + 'name': 'Hamburg', + 'location': { + 'latitude': 53.550341, + 'longitude': 10.000654, + 'zoom': 13 + } + }, + 'location': { + 'latitude': 53.528341000000005, + 'longitude': 10.018654000000002, + 'zoom': 16 + }, + 'isFavorite': false, + 'isPremium': true, + 'rating': 2.4 + }, + { + 'id': '0555dddd-5c10-431e-b9b5-11590fab8faa', + 'title': 'Penthouse, 4-5 rooms + 5 balconies', + 'type': 'hotel', + 'price': 190, + 'previewImage': 'https://15.design.htmlacademy.pro/static/hotel/2.jpg', + 'city': { + 'name': 'Hamburg', + 'location': { + 'latitude': 53.550341, + 'longitude': 10.000654, + 'zoom': 13 + } + }, + 'location': { + 'latitude': 53.538341, + 'longitude': 9.976654000000002, + 'zoom': 16 + }, + 'isFavorite': false, + 'isPremium': true, + 'rating': 4 + }, + { + 'id': '2486d121-6167-4a1c-b32e-9cd02910b3ba', + 'title': 'Tile House', + 'type': 'room', + 'price': 226, + 'previewImage': 'https://15.design.htmlacademy.pro/static/hotel/3.jpg', + 'city': { + 'name': 'Hamburg', + 'location': { + 'latitude': 53.550341, + 'longitude': 10.000654, + 'zoom': 13 + } + }, + 'location': { + 'latitude': 53.563341, + 'longitude': 10.019654000000001, + 'zoom': 16 + }, + 'isFavorite': false, + 'isPremium': true, + 'rating': 2.4 + }, + { + 'id': '7b02e5bf-d45c-475e-816b-88fabdd69e3d', + 'title': 'Loft Studio in the Central Area', + 'type': 'apartment', + 'price': 213, + 'previewImage': 'https://15.design.htmlacademy.pro/static/hotel/5.jpg', + 'city': { + 'name': 'Hamburg', + 'location': { + 'latitude': 53.550341, + 'longitude': 10.000654, + 'zoom': 13 + } + }, + 'location': { + 'latitude': 53.529341, + 'longitude': 9.975654, + 'zoom': 16 + }, + 'isFavorite': false, + 'isPremium': true, + 'rating': 2.8 + }, + { + 'id': '61163930-cf75-4ad7-830f-d5f2aec35eb8', + 'title': 'Nice, cozy, warm big bed apartment', + 'type': 'hotel', + 'price': 260, + 'previewImage': 'https://15.design.htmlacademy.pro/static/hotel/12.jpg', + 'city': { + 'name': 'Hamburg', + 'location': { + 'latitude': 53.550341, + 'longitude': 10.000654, + 'zoom': 13 + } + }, + 'location': { + 'latitude': 53.565341, + 'longitude': 9.980654000000001, + 'zoom': 16 + }, + 'isFavorite': false, + 'isPremium': false, + 'rating': 4.8 + }, + { + 'id': '73119400-5a51-411e-a0cc-1323627ada91', + 'title': 'Nice, cozy, warm big bed apartment', + 'type': 'hotel', + 'price': 288, + 'previewImage': 'https://15.design.htmlacademy.pro/static/hotel/8.jpg', + 'city': { + 'name': 'Dusseldorf', + 'location': { + 'latitude': 51.225402, + 'longitude': 6.776314, + 'zoom': 13 + } + }, + 'location': { + 'latitude': 51.217402, + 'longitude': 6.7693140000000005, + 'zoom': 16 + }, + 'isFavorite': false, + 'isPremium': true, + 'rating': 1.9 + }, ]; const OFFERS_FULL_INFO: OfferFullInfo[] = [ diff --git a/src/pages/favorites/favorites.tsx b/src/pages/favorites/favorites.tsx index af7bfe6..8c429ef 100644 --- a/src/pages/favorites/favorites.tsx +++ b/src/pages/favorites/favorites.tsx @@ -1,20 +1,22 @@ import Header from '../../components/header/header.tsx'; import {useDocumentTitle} from '../../hooks/document-title.ts'; -import {OfferShortInfo} from '../../types/offer.ts'; import Footer from '../../components/footer/footer.tsx'; import {Link} from 'react-router-dom'; import {AppRoute} from '../../const.ts'; import OfferCard from '../../components/offer-card/offer-card.tsx'; import FavoritesEmpty from '../../components/favorites-empty/favorites-empty.tsx'; +import {useAppSelector} from '../../hooks/store.ts'; +import {offersSelectors} from '../../store/slices/offers.ts'; interface FavoritesProps { title?: string; - offersFavorites: OfferShortInfo[]; } -function Favorites({title = 'Favorites', offersFavorites}: FavoritesProps) { +function Favorites({title = 'Favorites'}: FavoritesProps) { useDocumentTitle(title); + const offersFavorites = useAppSelector(offersSelectors.offersFavorites); + if (!offersFavorites.length) { return ; } diff --git a/src/pages/main/main.tsx b/src/pages/main/main.tsx index 5f7fa40..7a9ec6b 100644 --- a/src/pages/main/main.tsx +++ b/src/pages/main/main.tsx @@ -1,29 +1,26 @@ import OfferCard from '../../components/offer-card/offer-card.tsx'; import Header from '../../components/header/header.tsx'; import LocationTab from '../../components/location-tab/location-tab.tsx'; -import {OfferShortInfo} from '../../types/offer.ts'; +import {CitySlug, OfferShortInfo} from '../../types/offer.ts'; import {useDocumentTitle} from '../../hooks/document-title.ts'; import {CITIES} from '../../const.ts'; -import {useEffect, useState} from 'react'; import Map from '../../components/map/map.tsx'; -import {useLocation} from 'react-router-dom'; +import {useActionCreators, useAppSelector} from '../../hooks/store.ts'; +import {offersActions, offersSelectors} from '../../store/slices/offers.ts'; export type MainProps = { - offers: OfferShortInfo[]; - offersCount: number; title?: string; + citySlug: CitySlug; } -function Main({offers, offersCount, title = 'Main'}: MainProps) { +function Main({title = 'Main', citySlug}: MainProps) { useDocumentTitle(title); - const [activeOffer, setActiveOffer] = useState(null); - const location = useLocation(); - const [citySlug, setCitySlug] = useState(location.pathname.split('/').pop()); - useEffect(() => { - setCitySlug(location.pathname.split('/').pop()); - }, [location]); + const {setActiveOffer} = useActionCreators(offersActions); - const currentCity = CITIES.find((city) => city.slug === citySlug); + const activeCity = CITIES.find((city) => city.slug === citySlug); + + const allOffers = useAppSelector(offersSelectors.offers); + const offersByCity = allOffers.filter((offer) => offer.city.name === activeCity?.name); return (
@@ -40,11 +37,8 @@ function Main({offers, offersCount, title = 'Main'}: MainProps) {
- - Now active offer id: {activeOffer?.id} -

Places

- {offersCount} places to stay in Amsterdam + {offersByCity.length} place{offersByCity.length > 1 && 's'} to stay in {activeCity?.name}
Sort by @@ -61,12 +55,17 @@ function Main({offers, offersCount, title = 'Main'}: MainProps) {
- {offers.map((offer: OfferShortInfo) => setActiveOffer(offer || null)} componentType={'cities'} key={offer.id} offer={offer}/>)} + {offersByCity.map((offer: OfferShortInfo) => + ( + + setActiveOffer(offer)} componentType={'cities'} key={offer.id} offer={offer} + /> + ))}
{ - currentCity && + activeCity && }
diff --git a/src/pages/offer/offer.tsx b/src/pages/offer/offer.tsx index da78cf2..496ef25 100644 --- a/src/pages/offer/offer.tsx +++ b/src/pages/offer/offer.tsx @@ -13,8 +13,8 @@ import FormReview from '../../components/form-review/form-review.tsx'; import {AuthorizationStatus, CITIES} from '../../const.ts'; import Map from '../../components/map/map.tsx'; import OfferCard from '../../components/offer-card/offer-card.tsx'; -import {useState} from 'react'; -import {OfferShortInfo} from '../../types/offer.ts'; +import {useActionCreators} from '../../hooks/store.ts'; +import {offersActions} from '../../store/slices/offers.ts'; interface OfferProps { title?: string; @@ -23,9 +23,9 @@ interface OfferProps { function Offer({title = 'Offer', userAuth}: OfferProps) { useDocumentTitle(title); - const {offerId} = useParams(); - const [activeNearOffer, setActiveNearOffer] = useState(null); + const {setActiveOffer} = useActionCreators(offersActions); + const {offerId} = useParams(); if (!offerId) { return ; @@ -128,7 +128,7 @@ function Offer({title = 'Offer', userAuth}: OfferProps) {
- {cityFullInfo && } + {cityFullInfo && }
@@ -140,7 +140,7 @@ function Offer({title = 'Offer', userAuth}: OfferProps) { key={nearOffer.id} offer={nearOffer} componentType="near-places" - hoverHandler={() => setActiveNearOffer(nearOffer)} + hoverHandler={() => setActiveOffer(nearOffer)} /> ) )} diff --git a/src/store/action.ts b/src/store/action.ts deleted file mode 100644 index 6d6aa77..0000000 --- a/src/store/action.ts +++ /dev/null @@ -1,15 +0,0 @@ -import {createAction} from '@reduxjs/toolkit'; -import {CITIES} from '../const.ts'; -import {getOffersShortInfo} from '../mocks/offers.ts'; - -export const setCityAction = createAction( - 'main/city', - (value: typeof CITIES[number]['slug']) => ({ - payload: value - })); - -export const getOffersShortInfoAction = createAction( - 'main/offers-short', - () => ({ - payload: getOffersShortInfo(), - })); diff --git a/src/store/index.ts b/src/store/index.ts index b78131c..073d625 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -1,6 +1,8 @@ import {configureStore} from '@reduxjs/toolkit'; -import {reducer} from './reducer.ts'; +import {offersSlice} from './slices/offers.ts'; export const store = configureStore({ - reducer: reducer + reducer: { + [offersSlice.name]: offersSlice.reducer + } }); diff --git a/src/store/reducer.ts b/src/store/reducer.ts deleted file mode 100644 index 6f815de..0000000 --- a/src/store/reducer.ts +++ /dev/null @@ -1,25 +0,0 @@ -import {CITIES, DEFAULT_CITY_SLUG} from '../const.ts'; -import {getOffersShortInfo} from '../mocks/offers.ts'; -import {createReducer} from '@reduxjs/toolkit'; -import {getOffersShortInfoAction, setCityAction} from './action.ts'; -import {OfferShortInfo} from '../types/offer.ts'; - -interface State { - city: typeof CITIES[number]['slug']; - offers: OfferShortInfo[]; -} - -const initialState: State = { - city: DEFAULT_CITY_SLUG, - offers: getOffersShortInfo(), -}; - -export const reducer = createReducer(initialState, (builder) => { - builder - .addCase(setCityAction, (state, action) => { - state.city = action.payload; - }) - .addCase(getOffersShortInfoAction, (state, action) => { - state.offers = action.payload; - }); -}); diff --git a/src/store/slices/offers.ts b/src/store/slices/offers.ts new file mode 100644 index 0000000..abf4159 --- /dev/null +++ b/src/store/slices/offers.ts @@ -0,0 +1,41 @@ +import {OfferShortInfo} from '../../types/offer.ts'; +import {getFavoriteOffers, getOffersShortInfo} from '../../mocks/offers.ts'; +import {createSlice, PayloadAction} from '@reduxjs/toolkit'; + +interface OffersState { + offers: OfferShortInfo[]; + offersFavorites: OfferShortInfo[]; + activeOffer: OfferShortInfo | null; +} + +const initialState: OffersState = { + offers: getOffersShortInfo(), + offersFavorites: getFavoriteOffers(), + activeOffer: null, +}; + +const offersSlice = createSlice({ + initialState, + name: 'offers', + reducers: { + getOffersShortInfoAction: (state, action: PayloadAction) => { + state.offers = action.payload; + }, + getFavoritesOffersAction: (state, action: PayloadAction) => { + state.offers = action.payload; + }, + setActiveOffer: (state, action: PayloadAction) => { + state.activeOffer = action.payload; + }, + }, + selectors: { + offers: (state) => state.offers, + activeOffer: (state) => state.activeOffer, + offersFavorites: (state) => state.offersFavorites, + } +}); + +const offersActions = offersSlice.actions; +const offersSelectors = offersSlice.selectors; + +export { offersSlice, offersActions, offersSelectors }; diff --git a/src/types/offer.ts b/src/types/offer.ts index f88f0dd..3b5f3fe 100644 --- a/src/types/offer.ts +++ b/src/types/offer.ts @@ -2,6 +2,7 @@ import {CITIES, OFFER_TYPES} from '../const'; type OfferType = typeof OFFER_TYPES[number]; type CityName = typeof CITIES[number]['name']; +type CitySlug = typeof CITIES[number]['slug']; interface OfferShortInfo { id: string; @@ -42,4 +43,4 @@ interface Host { 'avatarUrl': string; } -export type {OfferShortInfo, OfferFullInfo, City, Location, CityName, Host}; +export type {OfferShortInfo, OfferFullInfo, City, Location, CityName, Host, CitySlug};