@@ -19,5 +20,5 @@ function Header(): JSX.Element {
);
}
-export default Header;
+export const Header = memo(HeaderComponent);
diff --git a/src/components/login-navigation/login-navigation.tsx b/src/components/login-navigation/login-navigation.tsx
index a66e6ad..141a331 100644
--- a/src/components/login-navigation/login-navigation.tsx
+++ b/src/components/login-navigation/login-navigation.tsx
@@ -2,13 +2,15 @@ import { Link } from 'react-router-dom';
import { logoutAction } from '../../store/api-actions';
import { useAppDispatch, useAppSelector } from '../../hooks';
import { AppRoute, AuthorizationStatus } from '../../const';
+import { getOffers } from '../../store/offers-data/selectors';
+import { getAuthorizationStatus, getUserData } from '../../store/user-process/selectors';
-function HeaderNavigation(): JSX.Element {
+function LoginNavigation(): JSX.Element {
const dispatch = useAppDispatch();
- const offers = useAppSelector((state) => state.offers);
+ const offers = useAppSelector(getOffers);
const favoriteOffers = offers.filter((offer) => offer.isFavorite);
- const userData = useAppSelector((state) => state.userData);
- const authorizationStatus = useAppSelector((state) => state.authorizationStatus);
+ const userData = useAppSelector(getUserData);
+ const authorizationStatus = useAppSelector(getAuthorizationStatus);
const handleSignOut = () => {
dispatch(logoutAction());
};
@@ -45,4 +47,4 @@ function HeaderNavigation(): JSX.Element {
);
}
-export default HeaderNavigation;
+export default LoginNavigation;
diff --git a/src/components/main-route-redirection/main-route-redirection.tsx b/src/components/main-route-redirection/main-route-redirection.tsx
index 6945992..d89c87e 100644
--- a/src/components/main-route-redirection/main-route-redirection.tsx
+++ b/src/components/main-route-redirection/main-route-redirection.tsx
@@ -1,6 +1,7 @@
import { Navigate } from 'react-router-dom';
import { AppRoute, AuthorizationStatus } from '../../const';
import { useAppSelector } from '../../hooks';
+import { getAuthorizationStatus } from '../../store/user-process/selectors';
type MainRouteRedirectionProps = {
children: JSX.Element;
@@ -8,7 +9,7 @@ type MainRouteRedirectionProps = {
function MainRouteRedirection(props: MainRouteRedirectionProps): JSX.Element {
const {children} = props;
- const authorizationStatus = useAppSelector((state) => state.authorizationStatus);
+ const authorizationStatus = useAppSelector(getAuthorizationStatus);
return (
authorizationStatus === AuthorizationStatus.NoAuth ? children :
);
diff --git a/src/components/map/map.tsx b/src/components/map/map.tsx
index e58e0c0..f902476 100644
--- a/src/components/map/map.tsx
+++ b/src/components/map/map.tsx
@@ -5,6 +5,8 @@ import { City, Point } from '../../types/location';
import 'leaflet/dist/leaflet.css';
import { useAppSelector } from '../../hooks';
import { URL_MARKER_CURRENT, URL_MARKER_STANDART } from '../../const';
+import { getChosenOffer } from '../../store/offer-data/selectors';
+import { getHighlightedMarker } from '../../store/common-data/selectors';
type MapProps = {
points: Point[];
@@ -24,13 +26,13 @@ const standartIcon = new Icon({
});
function Map(props: MapProps): JSX.Element {
- const highlightedMarker = useAppSelector((state) => state.highlightedMarker);
+ const highlightedMarker = useAppSelector(getHighlightedMarker);
const { points, city } = props;
const mapRef = useRef(null);
const map = useMap(mapRef, city);
- const currentPoint = useAppSelector((state) => state.chosenOffer?.location);
+ const currentPoint = useAppSelector(getChosenOffer)?.location;
useEffect(() => {
if (map) {
diff --git a/src/components/offer-card/offer-card.tsx b/src/components/offer-card/offer-card.tsx
index 65ebaa9..27ba950 100644
--- a/src/components/offer-card/offer-card.tsx
+++ b/src/components/offer-card/offer-card.tsx
@@ -1,9 +1,9 @@
import { useAppDispatch } from '../../hooks';
-import { changeHighlightedMarker } from '../../store/action';
import { Offer } from '../../types/offer';
import { Link } from 'react-router-dom';
import { ratingPercentage } from '../../utils';
import { fetchNearbyAction, fetchOfferAction, fetchReviewsAction } from '../../store/api-actions';
+import { changeHighlightedMarker } from '../../store/common-data/common-data';
type OfferProps = {
offer: Offer;
diff --git a/src/components/offer-list/offer-list.tsx b/src/components/offer-list/offer-list.tsx
index 954629a..0282297 100644
--- a/src/components/offer-list/offer-list.tsx
+++ b/src/components/offer-list/offer-list.tsx
@@ -2,6 +2,7 @@ import { getSortedOffers, listToCard, typeOfCardList } from '../../utils';
import { useAppSelector } from '../../hooks';
import { Offer } from '../../types/offer';
import OfferCard from '../offer-card/offer-card';
+import { getSortType } from '../../store/common-data/selectors';
type OfferListProps = {
offers: Offer[];
@@ -9,7 +10,7 @@ type OfferListProps = {
};
function OfferList({offers, listType}: OfferListProps): JSX.Element {
- const chosenSortType = useAppSelector((state) => state.sortType);
+ const chosenSortType = useAppSelector(getSortType);
const type = listToCard.get(listType);
return (
diff --git a/src/components/private-route/private-route.tsx b/src/components/private-route/private-route.tsx
index cf3af98..b4a2938 100644
--- a/src/components/private-route/private-route.tsx
+++ b/src/components/private-route/private-route.tsx
@@ -1,6 +1,7 @@
import { Navigate } from 'react-router-dom';
import { AppRoute, AuthorizationStatus } from '../../const';
import { useAppSelector } from '../../hooks';
+import { getAuthorizationStatus } from '../../store/user-process/selectors';
type PrivateRouteProps = {
children: JSX.Element;
@@ -8,7 +9,7 @@ type PrivateRouteProps = {
function PrivateRoute(props: PrivateRouteProps): JSX.Element {
const {children} = props;
- const authorizationStatus = useAppSelector((state) => state.authorizationStatus);
+ const authorizationStatus = useAppSelector(getAuthorizationStatus);
return (
authorizationStatus === AuthorizationStatus.Auth ? children :
);
diff --git a/src/components/reviews-item/reviews-item.tsx b/src/components/reviews-item/reviews-item.tsx
index d656b55..225747c 100644
--- a/src/components/reviews-item/reviews-item.tsx
+++ b/src/components/reviews-item/reviews-item.tsx
@@ -1,4 +1,5 @@
import { Review } from '../../types/review';
+import { extractYearMonth } from '../../utils';
type ReviewProps = {
review: Review;
@@ -25,7 +26,7 @@ function ReviewItem({review}: ReviewProps): JSX.Element {
{review.comment}
-
+
);
diff --git a/src/components/reviews-list/reviews-list.tsx b/src/components/reviews-list/reviews-list.tsx
index c5859da..bfaa816 100644
--- a/src/components/reviews-list/reviews-list.tsx
+++ b/src/components/reviews-list/reviews-list.tsx
@@ -3,18 +3,23 @@ import ReviewItem from '../reviews-item/reviews-item';
import CommentForm from '../../components/comment-form/comment-form';
import { AuthorizationStatus } from '../../const';
import { useAppSelector } from '../../hooks';
+import { getAuthorizationStatus } from '../../store/user-process/selectors';
type ReviewsListProps = {
reviews: Review[];
};
+const MAXIMUM_REVIEWS_COUNT = 10;
+
function ReviewsList({ reviews }: ReviewsListProps): JSX.Element {
- const authorizationStatus = useAppSelector((state) => state.authorizationStatus);
+ const authorizationStatus = useAppSelector(getAuthorizationStatus);
+ const sortedReviews = reviews.slice().sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());
+ const limitedReviews = sortedReviews.slice(0, MAXIMUM_REVIEWS_COUNT);
return (
- Reviews · {reviews.length}
+ Reviews · {limitedReviews.length}
- {reviews.map((review) => (
+ {limitedReviews.map((review) => (
))}
diff --git a/src/const.ts b/src/const.ts
index db2c569..c4af238 100644
--- a/src/const.ts
+++ b/src/const.ts
@@ -35,3 +35,10 @@ export const cities = {
};
export const TIMEOUT_SHOW_ERROR = 2000;
+
+export enum NameSpace {
+ Offer = 'OFFER',
+ Offers = 'OFFERS',
+ User = 'USER',
+ Common = 'COMMON'
+}
diff --git a/src/pages/favorites-screen/favorites-screen.tsx b/src/pages/favorites-screen/favorites-screen.tsx
index ef1aa4e..3607211 100644
--- a/src/pages/favorites-screen/favorites-screen.tsx
+++ b/src/pages/favorites-screen/favorites-screen.tsx
@@ -2,12 +2,13 @@ import { Link } from 'react-router-dom';
import { typeOfCardList } from '../../utils';
import OfferList from '../../components/offer-list/offer-list';
import { useAppSelector } from '../../hooks';
-import Header from '../../components/header/header';
import { AppRoute } from '../../const';
+import { Header } from '../../components/header/header';
+import { getOffers } from '../../store/offers-data/selectors';
function FavoritesScreen(): JSX.Element {
- const favoriteOffers = useAppSelector((state) => state.offers).filter((offer) => offer.isFavorite);
+ const favoriteOffers = useAppSelector(getOffers).filter((offer) => offer.isFavorite);
return (
@@ -19,7 +20,7 @@ function FavoritesScreen(): JSX.Element {
diff --git a/src/pages/loading-screen/loading-screen.css b/src/pages/loading-screen/loading-screen.css
new file mode 100644
index 0000000..fb7b603
--- /dev/null
+++ b/src/pages/loading-screen/loading-screen.css
@@ -0,0 +1,24 @@
+.loading-screen {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ height: 100vh;
+ width: 100vw;
+ font-size: 1.5rem;
+ }
+
+ .spinner {
+ border: 4px solid rgba(0, 0, 0, 0.1);
+ border-left-color: #333;
+ border-radius: 50%;
+ width: 24px;
+ height: 24px;
+ animation: spin 1s linear infinite;
+ margin-left: 20px;
+ }
+
+ @keyframes spin {
+ to {
+ transform: rotate(360deg);
+ }
+ }
\ No newline at end of file
diff --git a/src/pages/loading-screen/loading-screen.tsx b/src/pages/loading-screen/loading-screen.tsx
index b886023..2949ab0 100644
--- a/src/pages/loading-screen/loading-screen.tsx
+++ b/src/pages/loading-screen/loading-screen.tsx
@@ -1,6 +1,11 @@
+import './loading-screen.css';
+
function LoadingScreen(): JSX.Element {
return (
-
Loading ...
+
);
}
diff --git a/src/pages/login-screen/login-screen.tsx b/src/pages/login-screen/login-screen.tsx
index 8b2ebcd..514cde9 100644
--- a/src/pages/login-screen/login-screen.tsx
+++ b/src/pages/login-screen/login-screen.tsx
@@ -4,6 +4,13 @@ import { loginAction } from '../../store/api-actions';
import { useAppDispatch } from '../../hooks';
import { Link } from 'react-router-dom';
import { redirectToRoute } from '../../store/action';
+import { changeCity } from '../../store/common-data/common-data';
+
+function getRandomCity() {
+ const cityKeys = Object.keys(cities) as (keyof typeof cities)[];
+ const randomIndex = Math.floor(Math.random() * cityKeys.length);
+ return cities[cityKeys[randomIndex]];
+}
function LoginScreen(): JSX.Element {
const emailRef = useRef
(null);
@@ -23,6 +30,8 @@ function LoginScreen(): JSX.Element {
}
};
+ const city = getRandomCity();
+
return (
@@ -57,9 +66,10 @@ function LoginScreen(): JSX.Element {
diff --git a/src/pages/main-screen/main-screen.tsx b/src/pages/main-screen/main-screen.tsx
index 6c5b57d..f405972 100644
--- a/src/pages/main-screen/main-screen.tsx
+++ b/src/pages/main-screen/main-screen.tsx
@@ -3,12 +3,14 @@ import Map from '../../components/map/map';
import { typeOfCardList } from '../../utils';
import { useAppSelector } from '../../hooks';
import CityList from '../../components/city-list/city-list';
-import CardsSortingOptions from '../../components/cards-sorting-options/cards-sorting-options';
-import Header from '../../components/header/header';
+import { Header } from '../../components/header/header';
+import { CardsSortingOptions } from '../../components/cards-sorting-options/cards-sorting-options';
+import { getOffers } from '../../store/offers-data/selectors';
+import { getCity } from '../../store/common-data/selectors';
function MainScreen(): JSX.Element {
- const city = useAppSelector((state) => state.city);
- const offers = useAppSelector((state) => state.offers);
+ const city = useAppSelector(getCity);
+ const offers = useAppSelector(getOffers);
const chosenOffers = offers.filter((offer) => offer.city.name === city);
const points = chosenOffers.map((offer) => offer.location);
const chosenCity = chosenOffers[0].city;
diff --git a/src/pages/offer-screen/offer-screen.tsx b/src/pages/offer-screen/offer-screen.tsx
index a1f8766..1775317 100644
--- a/src/pages/offer-screen/offer-screen.tsx
+++ b/src/pages/offer-screen/offer-screen.tsx
@@ -3,20 +3,22 @@ import Map from '../../components/map/map';
import OfferList from '../../components/offer-list/offer-list';
import { ratingPercentage, typeOfCardList } from '../../utils';
import { useAppDispatch, useAppSelector } from '../../hooks';
-import Header from '../../components/header/header';
import { fetchNearbyAction, fetchOfferAction, fetchReviewsAction } from '../../store/api-actions';
import LoadingScreen from '../loading-screen/loading-screen';
import { useParams } from 'react-router-dom';
import { useEffect } from 'react';
+import { Header } from '../../components/header/header';
+import { getChosenOffer, getIsChosenOfferDataLoading, getNearbyOffers, getReviews } from '../../store/offer-data/selectors';
+import { getOffers } from '../../store/offers-data/selectors';
const MAXIMUM_NEARBY_PREVIEW = 3;
function OfferScreen(): JSX.Element {
const dispatch = useAppDispatch();
- const offer = useAppSelector((state) => state.chosenOffer);
- const reviews = useAppSelector((state) => state.reviews);
- const nearbyOffers = useAppSelector((state) => state.nearbyOffers);
- const city = useAppSelector((state) => state.offers[0].city);
+ const offer = useAppSelector(getChosenOffer);
+ const reviews = useAppSelector(getReviews);
+ const nearbyOffers = useAppSelector(getNearbyOffers);
+ const city = useAppSelector(getOffers)[0].city;
const displayedNearby = (nearbyOffers).slice(
0,
@@ -30,8 +32,8 @@ function OfferScreen(): JSX.Element {
dispatch(fetchNearbyAction(id));
}, [dispatch, id]);
- const isSelectedOfferDataLoading = useAppSelector((state) => state.isChosenOfferDataLoading);
- if (isSelectedOfferDataLoading) {
+ const isChosenOfferDataLoading = useAppSelector(getIsChosenOfferDataLoading);
+ if (isChosenOfferDataLoading) {
return (
);
diff --git a/src/services/process-error-handle.ts b/src/services/process-error-handle.ts
index db11914..e2839a4 100644
--- a/src/services/process-error-handle.ts
+++ b/src/services/process-error-handle.ts
@@ -1,6 +1,6 @@
import {store} from '../store';
-import {setError} from '../store/action';
-import {clearErrorAction} from '../store/api-actions';
+import { clearErrorAction } from '../store/api-actions';
+import { setError } from '../store/common-data/common-data';
export const processErrorHandle = (message: string): void => {
store.dispatch(setError(message));
diff --git a/src/store/action.ts b/src/store/action.ts
index 3a00bcf..d50b4ec 100644
--- a/src/store/action.ts
+++ b/src/store/action.ts
@@ -1,58 +1,6 @@
import { createAction } from '@reduxjs/toolkit';
-import { Point } from '../types/location';
-import { ExtendedOffer, Offer } from '../types/offer';
-import { AppRoute, AuthorizationStatus } from '../const';
-import { UserData } from '../types/user-data';
-import { Review } from '../types/review';
-
-export const changeCity = createAction('CITY_CHANGE', (value: string) => ({
- payload: value
-}));
-
-export const changeSortOptions = createAction('CHANGE_SORT_OPTIONS', (value: string) => ({
- payload: value
-}));
-
-export const changeHighlightedMarker = createAction('CHANGE_HIGHLIGHTED_MARKER', (value: Point | undefined) => ({
- payload: value
-}));
-
-export const loadOffers = createAction('LOAD_OFFERS', (value: Offer[]) => ({
- payload: value
-}));
-
-export const changeChosenOffer = createAction('CHANGE_CHOSEN_OFFER', (value: ExtendedOffer) => ({
- payload: value
-}));
-
-export const setOffersDataLoadingStatus = createAction('SET_OFFERS_DATA_LOADING_STATUS', (value: boolean) => ({
- payload: value
-}));
-
-export const setError = createAction('SET_ERROR', (value: string | null) => ({
- payload: value
-}));
-
-export const requireAuthorization = createAction('REQUIRE_AUTHORIZATION', (value: AuthorizationStatus) => ({
- payload: value
-}));
-
-export const loadUserData = createAction('LOAD_USER_DATA', (value: UserData) => ({
- payload: value
-}));
+import { AppRoute} from '../const';
export const redirectToRoute = createAction('REDIRECT_TO_ROUTE', (value: AppRoute) => ({
payload: value
}));
-
-export const loadReviews = createAction('CHANGE_REVIEWS', (value: Review[]) => ({
- payload: value
-}));
-
-export const changeNearbyOffers = createAction('CHANGE_NEARBY_OFFERS', (value: Offer[]) => ({
- payload: value
-}));
-
-export const setChosenOfferDataLoadingStatus = createAction('SET_CHOSEN_OFFER_DATA_LOADING_STATUS', (value: boolean) => ({
- payload: value
-}));
diff --git a/src/store/api-actions.ts b/src/store/api-actions.ts
index a0accc6..5c7cd53 100644
--- a/src/store/api-actions.ts
+++ b/src/store/api-actions.ts
@@ -1,57 +1,50 @@
import { createAsyncThunk } from '@reduxjs/toolkit';
import { AppDispatch, State } from '../types/state';
import { AxiosInstance } from 'axios';
-import { changeChosenOffer, changeNearbyOffers, loadOffers, loadReviews, loadUserData, redirectToRoute, requireAuthorization, setChosenOfferDataLoadingStatus, setError, setOffersDataLoadingStatus } from './action';
+import { redirectToRoute } from './action';
import { ExtendedOffer, Offer } from '../types/offer';
-import { APIRoute, AppRoute, AuthorizationStatus, TIMEOUT_SHOW_ERROR } from '../const';
-import { store } from './';
+import { APIRoute, AppRoute, TIMEOUT_SHOW_ERROR } from '../const';
import { AuthData } from '../types/auth-data';
import { UserData } from '../types/user-data';
import { dropToken, saveToken } from '../services/token';
import { Review } from '../types/review';
+import { setError } from './common-data/common-data';
+
export const clearErrorAction = createAsyncThunk(
'CLEAR_ERROR_ACTION',
- () => {
- setTimeout(
- () => store.dispatch(setError(null)),
- TIMEOUT_SHOW_ERROR,
- );
- },
+ (_, { dispatch }) => {
+ setTimeout(() => {
+ dispatch(setError(null));
+ }, TIMEOUT_SHOW_ERROR);
+ }
);
-export const fetchOffersAction = createAsyncThunk(
'FETCH_OFFERS_ACTION',
- async (_arg, { dispatch, extra: api }) => {
- dispatch(setOffersDataLoadingStatus(true));
+ async (_arg, { extra: api }) => {
const { data } = await api.get(APIRoute.Offers);
- dispatch(loadOffers(data));
- dispatch(setOffersDataLoadingStatus(false));
+ return data;
},
);
-export const checkAuthAction = createAsyncThunk(
'CHECK_AUTH_ACTION',
- async (_arg, { dispatch, extra: api }) => {
- try {
- const { data } = await api.get(APIRoute.Login);
- dispatch(requireAuthorization(AuthorizationStatus.Auth));
- dispatch(loadUserData(data));
- } catch {
- dispatch(requireAuthorization(AuthorizationStatus.NoAuth));
- }
- },
+ async (_arg, { extra: api }) => {
+ const { data } = await api.get(APIRoute.Login);
+ return data;
+ }
);
-export const loginAction = createAsyncThunk {
const { data } = await api.post(APIRoute.Login, { email, password });
saveToken(data.token);
- dispatch(requireAuthorization(AuthorizationStatus.Auth));
- dispatch(loadUserData(data));
dispatch(redirectToRoute(AppRoute.Main));
+ return data;
},
);
@@ -72,72 +64,63 @@ export const logoutAction = createAsyncThunk(
'LOGOUT_ACTION',
- async (_arg, { dispatch, extra: api }) => {
- dispatch(setOffersDataLoadingStatus(true));
+ async (_arg, { extra: api }) => {
await api.delete(APIRoute.Logout);
dropToken();
- dispatch(requireAuthorization(AuthorizationStatus.NoAuth));
- dispatch(setOffersDataLoadingStatus(false));
},
);
-export const fetchOfferAction = createAsyncThunk(
'FETCH_OFFER_ACTION',
- async (id, { dispatch, extra: api }) => {
- dispatch(setChosenOfferDataLoadingStatus(true));
+ async (id, { extra: api }) => {
const { data } = await api.get(`${APIRoute.Offers}/${id}`);
- dispatch(changeChosenOffer(data));
- dispatch(setChosenOfferDataLoadingStatus(false));
+ return data;
},
);
-export const fetchReviewsAction = createAsyncThunk(
'FETCH_REVIEWS_ACTION',
- async (id, { dispatch, extra: api }) => {
- dispatch(setChosenOfferDataLoadingStatus(true));
+ async (id, { extra: api }) => {
const { data } = await api.get(`${APIRoute.Comments}/${id}`);
- dispatch(loadReviews(data));
- dispatch(setChosenOfferDataLoadingStatus(false));
+ return data;
},
);
-export const fetchNearbyAction = createAsyncThunk(
'FETCH_NEARBY_ACTION',
- async (id, { dispatch, extra: api }) => {
- dispatch(setChosenOfferDataLoadingStatus(true));
+ async (id, { extra: api }) => {
const { data } = await api.get(`${APIRoute.Offers}/${id}${APIRoute.Nearby}`);
- dispatch(changeNearbyOffers(data));
- dispatch(setChosenOfferDataLoadingStatus(false));
+ return data;
},
);
-export const postReviewAction = createAsyncThunk(
- 'POST_REVIEW_ACTION',
- async ({ id, comment, rating }, { dispatch, extra: api }) => {
- await api.post(`${APIRoute.Comments}/${id}`, { comment, rating });
- const { data: reviews } = await api.get(`${APIRoute.Comments}/${id}`);
- dispatch(loadReviews(reviews));
- },
-);
+ {
+ dispatch: AppDispatch;
+ state: State;
+ extra: AxiosInstance;
+ }>(
+ 'POST_REVIEW_ACTION',
+ async ({ id, comment, rating }, { extra: api }) => {
+ await api.post(`${APIRoute.Comments}/${id}`, { comment, rating });
+ const { data } = await api.get(`${APIRoute.Comments}/${id}`);
+ return data;
+ },
+ );
diff --git a/src/store/common-data/common-data.ts b/src/store/common-data/common-data.ts
new file mode 100644
index 0000000..8860226
--- /dev/null
+++ b/src/store/common-data/common-data.ts
@@ -0,0 +1,33 @@
+import { PayloadAction, createSlice } from '@reduxjs/toolkit';
+import { NameSpace, cities } from '../../const';
+import { filters } from '../../utils';
+import { CommonData } from '../../types/state';
+import { Point } from '../../types/location';
+
+const initialState: CommonData = {
+ sortType: filters.POPULAR,
+ city: cities.Paris,
+ highlightedMarker: undefined,
+ error: null,
+};
+
+export const commmonData = createSlice({
+ name: NameSpace.Common,
+ initialState,
+ reducers: {
+ changeSortType: (state, action: PayloadAction) => {
+ state.sortType = action.payload;
+ },
+ changeCity: (state, action: PayloadAction) => {
+ state.city = action.payload;
+ },
+ changeHighlightedMarker: (state, action: PayloadAction) => {
+ state.highlightedMarker = action.payload;
+ },
+ setError: (state, action: PayloadAction) => {
+ state.error = action.payload;
+ },
+ },
+});
+
+export const { changeSortType, changeCity, changeHighlightedMarker, setError } = commmonData.actions;
diff --git a/src/store/common-data/selectors.ts b/src/store/common-data/selectors.ts
new file mode 100644
index 0000000..51cc5d3
--- /dev/null
+++ b/src/store/common-data/selectors.ts
@@ -0,0 +1,8 @@
+import { NameSpace } from '../../const';
+import { Point } from '../../types/location';
+import { State } from '../../types/state';
+
+export const getSortType = (state: State): string => state[NameSpace.Common].sortType;
+export const getCity = (state: State): string => state[NameSpace.Common].city;
+export const getHighlightedMarker = (state: State): Point | undefined => state[NameSpace.Common].highlightedMarker;
+export const getError = (state: State): string | null => state[NameSpace.Common].error;
diff --git a/src/store/index.ts b/src/store/index.ts
index b6f6ecd..23cbdc4 100644
--- a/src/store/index.ts
+++ b/src/store/index.ts
@@ -1,12 +1,12 @@
import { configureStore } from '@reduxjs/toolkit';
-import { reducer } from './reducer';
import { createAPI } from '../services/api';
import { redirect } from './middlewares/redirect';
+import { rootReducer } from './root-reducer';
export const api = createAPI();
export const store = configureStore({
- reducer,
+ reducer: rootReducer,
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
thunk: {
diff --git a/src/store/middlewares/redirect.ts b/src/store/middlewares/redirect.ts
index 955a510..aef23f7 100644
--- a/src/store/middlewares/redirect.ts
+++ b/src/store/middlewares/redirect.ts
@@ -1,9 +1,9 @@
import {PayloadAction} from '@reduxjs/toolkit';
import browserHistory from '../../browser-history';
import {Middleware} from 'redux';
-import {reducer} from '../reducer';
+import { rootReducer } from '../root-reducer';
-type Reducer = ReturnType;
+type Reducer = ReturnType;
export const redirect: Middleware =
() =>
diff --git a/src/store/offer-data/offer-data.ts b/src/store/offer-data/offer-data.ts
new file mode 100644
index 0000000..0d1708f
--- /dev/null
+++ b/src/store/offer-data/offer-data.ts
@@ -0,0 +1,46 @@
+import { createSlice } from '@reduxjs/toolkit';
+import { NameSpace } from '../../const';
+import { fetchNearbyAction, fetchOfferAction, fetchReviewsAction, postReviewAction } from '../api-actions';
+import { OfferData } from '../../types/state';
+
+
+const initialState: OfferData = {
+ chosenOffer: undefined,
+ reviews: [],
+ nearbyOffers: [],
+ isChosenOfferDataLoading: false
+};
+
+export const offerData = createSlice({
+ name: NameSpace.Offer,
+ initialState,
+ reducers: {},
+ extraReducers(builder) {
+ builder
+ .addCase(fetchOfferAction.fulfilled, (state, action) => {
+ state.isChosenOfferDataLoading = false;
+ state.chosenOffer = action.payload;
+ })
+ .addCase(fetchOfferAction.pending, (state) => {
+ state.isChosenOfferDataLoading = true;
+ })
+ .addCase(fetchReviewsAction.fulfilled, (state, action) => {
+ state.isChosenOfferDataLoading = false;
+ state.reviews = action.payload;
+ })
+ .addCase(fetchReviewsAction.pending, (state) => {
+ state.isChosenOfferDataLoading = false;
+ })
+ .addCase(fetchNearbyAction.fulfilled, (state, action) => {
+ state.isChosenOfferDataLoading = false;
+ state.nearbyOffers = action.payload;
+ })
+ .addCase(fetchNearbyAction.pending, (state) => {
+ state.isChosenOfferDataLoading = true;
+ })
+ .addCase(postReviewAction.fulfilled, (state, action) => {
+ state.reviews = action.payload;
+ });
+ }
+}
+);
diff --git a/src/store/offer-data/selectors.ts b/src/store/offer-data/selectors.ts
new file mode 100644
index 0000000..086e931
--- /dev/null
+++ b/src/store/offer-data/selectors.ts
@@ -0,0 +1,9 @@
+import { NameSpace } from '../../const';
+import { ExtendedOffer, Offer } from '../../types/offer';
+import { Review } from '../../types/review';
+import { State } from '../../types/state';
+
+export const getChosenOffer = (state: State): ExtendedOffer | undefined => state[NameSpace.Offer].chosenOffer;
+export const getReviews = (state: State): Review[] => state[NameSpace.Offer].reviews;
+export const getNearbyOffers = (state: State): Offer[] => state[NameSpace.Offer].nearbyOffers;
+export const getIsChosenOfferDataLoading = (state: State): boolean => state[NameSpace.Offer].isChosenOfferDataLoading;
diff --git a/src/store/offers-data/offers-data.ts b/src/store/offers-data/offers-data.ts
new file mode 100644
index 0000000..ab0aa87
--- /dev/null
+++ b/src/store/offers-data/offers-data.ts
@@ -0,0 +1,31 @@
+import { createSlice } from '@reduxjs/toolkit';
+import { NameSpace } from '../../const';
+import { OffersData } from '../../types/state';
+import { fetchOffersAction, logoutAction } from '../api-actions';
+
+const initialState: OffersData = {
+ offers: [],
+ isOffersDataLoading: false,
+};
+
+export const offersData = createSlice({
+ name: NameSpace.Offers,
+ initialState,
+ reducers: {},
+ extraReducers: (builder) => {
+ builder
+ .addCase(fetchOffersAction.fulfilled, (state, action) => {
+ state.offers = action.payload;
+ state.isOffersDataLoading = false;
+ })
+ .addCase(fetchOffersAction.pending, (state) => {
+ state.isOffersDataLoading = true;
+ })
+ .addCase(logoutAction.fulfilled, (state) => {
+ state.isOffersDataLoading = false;
+ })
+ .addCase(logoutAction.pending, (state) => {
+ state.isOffersDataLoading = true;
+ });
+ },
+});
diff --git a/src/store/offers-data/selectors.ts b/src/store/offers-data/selectors.ts
new file mode 100644
index 0000000..abfff46
--- /dev/null
+++ b/src/store/offers-data/selectors.ts
@@ -0,0 +1,6 @@
+import { NameSpace } from '../../const';
+import { Offer } from '../../types/offer';
+import { State } from '../../types/state';
+
+export const getOffers = (state: State): Offer[] => state[NameSpace.Offers].offers;
+export const getIsOffersDataLoading = (state: State): boolean => state[NameSpace.Offers].isOffersDataLoading;
diff --git a/src/store/reducer.ts b/src/store/reducer.ts
deleted file mode 100644
index 2e7dfc2..0000000
--- a/src/store/reducer.ts
+++ /dev/null
@@ -1,81 +0,0 @@
-import { createReducer } from '@reduxjs/toolkit';
-import { ExtendedOffer, Offer } from '../types/offer';
-import { changeChosenOffer, changeCity, changeHighlightedMarker, changeNearbyOffers, changeSortOptions, loadOffers, loadReviews, loadUserData, requireAuthorization, setChosenOfferDataLoadingStatus, setError, setOffersDataLoadingStatus } from './action';
-import { filters } from '../utils';
-import { Point } from '../types/location';
-import { AuthorizationStatus, cities } from '../const';
-import { UserData } from '../types/user-data';
-import { Review } from '../types/review';
-
-
-type StateType = {
- city: string;
- offers: Offer[];
- sortType: string;
- highlightedMarker?: Point;
- chosenOffer?: ExtendedOffer;
- isOffersDataLoading: boolean;
- error: string | null;
- authorizationStatus: AuthorizationStatus;
- userData?: UserData;
- reviews: Review[];
- nearbyOffers: Offer[];
- isChosenOfferDataLoading: boolean;
- }
-
-const initialState: StateType = {
- city: cities.Paris,
- offers: [],
- sortType: filters.POPULAR,
- highlightedMarker: undefined,
- chosenOffer: undefined,
- isOffersDataLoading: false,
- error: null,
- authorizationStatus: AuthorizationStatus.Unknown,
- userData: undefined,
- reviews: [],
- nearbyOffers: [],
- isChosenOfferDataLoading: false
-};
-
-const reducer = createReducer(initialState, (builder) => {
- builder
- .addCase(changeCity, (state, action) => {
- state.city = action.payload;
- })
- .addCase(changeSortOptions, (state, action) => {
- state.sortType = action.payload;
- })
- .addCase(changeHighlightedMarker, (state, action) => {
- state.highlightedMarker = action.payload;
- })
- .addCase(loadOffers, (state, action) => {
- state.offers = action.payload;
- })
- .addCase(changeChosenOffer, (state, action) => {
- state.chosenOffer = action.payload;
- })
- .addCase(setOffersDataLoadingStatus, (state, action) => {
- state.isOffersDataLoading = action.payload;
- })
- .addCase(setError, (state, action) => {
- state.error = action.payload;
- })
- .addCase(requireAuthorization, (state, action) => {
- state.authorizationStatus = action.payload;
- })
- .addCase(loadUserData, (state, action) => {
- state.userData = action.payload;
- })
- .addCase(loadReviews, (state, action) => {
- state.reviews = action.payload;
- })
- .addCase(changeNearbyOffers, (state, action) => {
- state.nearbyOffers = action.payload;
- })
- .addCase(setChosenOfferDataLoadingStatus, (state, action) => {
- state.isChosenOfferDataLoading = action.payload;
- });
-});
-
-export {reducer};
diff --git a/src/store/root-reducer.ts b/src/store/root-reducer.ts
new file mode 100644
index 0000000..c01dafa
--- /dev/null
+++ b/src/store/root-reducer.ts
@@ -0,0 +1,13 @@
+import { combineReducers } from '@reduxjs/toolkit';
+import { NameSpace } from '../const';
+import { offersData } from './offers-data/offers-data';
+import { offerData } from './offer-data/offer-data';
+import { userProcess } from './user-process/user-process';
+import { commmonData } from './common-data/common-data';
+
+export const rootReducer = combineReducers({
+ [NameSpace.Offers]: offersData.reducer,
+ [NameSpace.Offer]: offerData.reducer,
+ [NameSpace.User]: userProcess.reducer,
+ [NameSpace.Common]: commmonData.reducer,
+});
diff --git a/src/store/user-process/selectors.ts b/src/store/user-process/selectors.ts
new file mode 100644
index 0000000..f35c5c3
--- /dev/null
+++ b/src/store/user-process/selectors.ts
@@ -0,0 +1,6 @@
+import { AuthorizationStatus, NameSpace } from '../../const';
+import { State } from '../../types/state';
+import { UserData } from '../../types/user-data';
+
+export const getAuthorizationStatus = (state: State): AuthorizationStatus => state[NameSpace.User].authorizationStatus;
+export const getUserData = (state: State): UserData | undefined => state[NameSpace.User].userData;
diff --git a/src/store/user-process/user-process.ts b/src/store/user-process/user-process.ts
new file mode 100644
index 0000000..13fa219
--- /dev/null
+++ b/src/store/user-process/user-process.ts
@@ -0,0 +1,35 @@
+import { createSlice } from '@reduxjs/toolkit';
+import { AuthorizationStatus, NameSpace } from '../../const';
+import { checkAuthAction, loginAction, logoutAction } from '../api-actions';
+import { UserProcess } from '../../types/state';
+
+const initialState: UserProcess = {
+ authorizationStatus: AuthorizationStatus.Unknown,
+ userData: undefined,
+};
+
+export const userProcess = createSlice({
+ name: NameSpace.User,
+ initialState,
+ reducers: {},
+ extraReducers(builder) {
+ builder
+ .addCase(checkAuthAction.fulfilled, (state, action) => {
+ state.authorizationStatus = AuthorizationStatus.Auth;
+ state.userData = action.payload;
+ })
+ .addCase(checkAuthAction.rejected, (state) => {
+ state.authorizationStatus = AuthorizationStatus.NoAuth;
+ })
+ .addCase(loginAction.fulfilled, (state, action) => {
+ state.authorizationStatus = AuthorizationStatus.Auth;
+ state.userData = action.payload;
+ })
+ .addCase(loginAction.rejected, (state) => {
+ state.authorizationStatus = AuthorizationStatus.NoAuth;
+ })
+ .addCase(logoutAction.fulfilled, (state) => {
+ state.authorizationStatus = AuthorizationStatus.NoAuth;
+ });
+ }
+});
diff --git a/src/types/state.ts b/src/types/state.ts
index c45a265..4a04de1 100644
--- a/src/types/state.ts
+++ b/src/types/state.ts
@@ -1,4 +1,33 @@
-import { store } from '../store/index.js';
+import { AuthorizationStatus } from '../const';
+import { store } from '../store/index';
+import { Point } from './location';
+import { ExtendedOffer, Offer } from './offer';
+import { Review } from './review';
+import { UserData } from './user-data';
+
+export type OfferData = {
+ chosenOffer?: ExtendedOffer;
+ reviews: Review[];
+ nearbyOffers: Offer[];
+ isChosenOfferDataLoading: boolean;
+ };
+
+export type OffersData = {
+ offers: Offer[];
+ isOffersDataLoading: boolean;
+ };
+
+export type UserProcess = {
+ authorizationStatus: AuthorizationStatus;
+ userData?: UserData;
+ };
+
+export type CommonData = {
+ sortType: string;
+ city: string;
+ highlightedMarker: Point | undefined;
+ error: string | null;
+}
export type State = ReturnType;
diff --git a/src/utils.ts b/src/utils.ts
index 4b7cf60..367ebf4 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -39,3 +39,14 @@ export const getSortedOffers = (
};
export const ratingPercentage = (rating: number) => `${(rating / 5) * 100}%`;
+
+export const extractYearMonth = (initDate: string): string => {
+ const date = new Date(initDate);
+ const year = date.getUTCFullYear().toString();
+ const monthNumber = (date.getUTCMonth() + 1).toString().padStart(2, '0');
+ const months = [
+ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'
+ ];
+ const monthName = months[Number(monthNumber) - 1];
+ return [monthName, year].join(' ');
+};