From 9f066aa48502f52d791356bc439125d4401398bc Mon Sep 17 00:00:00 2001 From: mayonnaise <90057279+Mayanzev@users.noreply.github.com> Date: Tue, 21 May 2024 22:34:17 +0300 Subject: [PATCH 01/10] emty-offers page and fix --- src/components/comment-form/comment-form.tsx | 5 -- src/components/offer-card/offer-card.tsx | 12 ++- src/pages/empty-offers/empty-offers.tsx | 20 +++++ .../main-empty-screen/main-empty-screen.tsx | 86 ------------------- src/pages/main-screen/main-screen.tsx | 40 +++++---- src/pages/offer-screen/offer-screen.tsx | 2 + src/store/offers-data/offers-data.ts | 8 +- src/store/offers-data/selectors.ts | 1 + src/types/state.ts | 1 + 9 files changed, 62 insertions(+), 113 deletions(-) create mode 100644 src/pages/empty-offers/empty-offers.tsx delete mode 100644 src/pages/main-empty-screen/main-empty-screen.tsx diff --git a/src/components/comment-form/comment-form.tsx b/src/components/comment-form/comment-form.tsx index def1d3c..2519783 100644 --- a/src/components/comment-form/comment-form.tsx +++ b/src/components/comment-form/comment-form.tsx @@ -38,11 +38,6 @@ function CommentForm(): JSX.Element { const submitHandle = () => { dispatch(postReviewAction({ id: id ? id : '', comment: formData.review, rating: Number(formData.rating) })); - setFormData((prevState) => ({ - ...prevState, - rating: null, - review: '' - })); resetForm(); }; diff --git a/src/components/offer-card/offer-card.tsx b/src/components/offer-card/offer-card.tsx index 27ba950..22740fe 100644 --- a/src/components/offer-card/offer-card.tsx +++ b/src/components/offer-card/offer-card.tsx @@ -1,7 +1,7 @@ import { useAppDispatch } from '../../hooks'; import { Offer } from '../../types/offer'; import { Link } from 'react-router-dom'; -import { ratingPercentage } from '../../utils'; +import { listToCard, ratingPercentage, typeOfCardList } from '../../utils'; import { fetchNearbyAction, fetchOfferAction, fetchReviewsAction } from '../../store/api-actions'; import { changeHighlightedMarker } from '../../store/common-data/common-data'; @@ -12,10 +12,14 @@ type OfferProps = { function OfferCard({ offer, cardType }: OfferProps): JSX.Element { const dispatch = useAppDispatch(); + return ( -
dispatch(changeHighlightedMarker(offer.location))} - onMouseLeave={() => dispatch(changeHighlightedMarker(undefined))} +
dispatch(changeHighlightedMarker(offer.location)), + onMouseLeave: () => dispatch(changeHighlightedMarker(undefined)) + })} > {offer.isPremium ? (
diff --git a/src/pages/empty-offers/empty-offers.tsx b/src/pages/empty-offers/empty-offers.tsx new file mode 100644 index 0000000..c5131ab --- /dev/null +++ b/src/pages/empty-offers/empty-offers.tsx @@ -0,0 +1,20 @@ +type EmptyOffersProps = { + city: string; +} + +function EmptyOffers({city}: EmptyOffersProps): JSX.Element { + return ( +
+
+
+
+ No places to stay available +

We could not find any property available at the moment in {city}

+
+
+
+
+
+ ); +} +export default EmptyOffers; diff --git a/src/pages/main-empty-screen/main-empty-screen.tsx b/src/pages/main-empty-screen/main-empty-screen.tsx deleted file mode 100644 index 4d46b46..0000000 --- a/src/pages/main-empty-screen/main-empty-screen.tsx +++ /dev/null @@ -1,86 +0,0 @@ -function MainEmptyScreen(): JSX.Element { - return ( -
-
- -
- -
-

Cities

- -
-
-
-
- No places to stay available -

We could not find any property available at the moment in Dusseldorf

-
-
-
-
-
-
-
- ); -} -export default MainEmptyScreen; diff --git a/src/pages/main-screen/main-screen.tsx b/src/pages/main-screen/main-screen.tsx index f405972..444b8dc 100644 --- a/src/pages/main-screen/main-screen.tsx +++ b/src/pages/main-screen/main-screen.tsx @@ -5,40 +5,46 @@ import { useAppSelector } from '../../hooks'; import CityList from '../../components/city-list/city-list'; import { Header } from '../../components/header/header'; import { CardsSortingOptions } from '../../components/cards-sorting-options/cards-sorting-options'; -import { getOffers } from '../../store/offers-data/selectors'; +import { getErrorStatus, getOffers } from '../../store/offers-data/selectors'; import { getCity } from '../../store/common-data/selectors'; +import EmptyOffers from '../empty-offers/empty-offers'; function MainScreen(): JSX.Element { 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; + const hasError = useAppSelector(getErrorStatus); + return (
-
-
+
+

Cities

- +
-
-
-
-

Places

- {chosenOffers.length} places to stay in {city} - - -
-
-
- + {hasError ? ( + + ) : ( +
+
+
+

Places

+ {chosenOffers.length} places to stay in {city} + +
+
+
+ +
+
-
+ )}
); diff --git a/src/pages/offer-screen/offer-screen.tsx b/src/pages/offer-screen/offer-screen.tsx index 1775317..a2f5427 100644 --- a/src/pages/offer-screen/offer-screen.tsx +++ b/src/pages/offer-screen/offer-screen.tsx @@ -10,6 +10,7 @@ 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'; +import { changeHighlightedMarker } from '../../store/common-data/common-data'; const MAXIMUM_NEARBY_PREVIEW = 3; @@ -30,6 +31,7 @@ function OfferScreen(): JSX.Element { dispatch(fetchOfferAction(id)); dispatch(fetchReviewsAction(id)); dispatch(fetchNearbyAction(id)); + dispatch(changeHighlightedMarker(undefined)); }, [dispatch, id]); const isChosenOfferDataLoading = useAppSelector(getIsChosenOfferDataLoading); diff --git a/src/store/offers-data/offers-data.ts b/src/store/offers-data/offers-data.ts index ab0aa87..4b918a8 100644 --- a/src/store/offers-data/offers-data.ts +++ b/src/store/offers-data/offers-data.ts @@ -6,6 +6,7 @@ import { fetchOffersAction, logoutAction } from '../api-actions'; const initialState: OffersData = { offers: [], isOffersDataLoading: false, + hasError: false, }; export const offersData = createSlice({ @@ -20,12 +21,17 @@ export const offersData = createSlice({ }) .addCase(fetchOffersAction.pending, (state) => { state.isOffersDataLoading = true; + state.hasError = false; }) .addCase(logoutAction.fulfilled, (state) => { state.isOffersDataLoading = false; }) .addCase(logoutAction.pending, (state) => { state.isOffersDataLoading = true; + }) + .addCase(fetchOffersAction.rejected, (state) => { + state.isOffersDataLoading = false; + state.hasError = true; }); - }, + } }); diff --git a/src/store/offers-data/selectors.ts b/src/store/offers-data/selectors.ts index abfff46..140a2b4 100644 --- a/src/store/offers-data/selectors.ts +++ b/src/store/offers-data/selectors.ts @@ -4,3 +4,4 @@ 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; +export const getErrorStatus = (state: State): boolean => state[NameSpace.Offers].hasError; diff --git a/src/types/state.ts b/src/types/state.ts index 4a04de1..7dc341b 100644 --- a/src/types/state.ts +++ b/src/types/state.ts @@ -15,6 +15,7 @@ export type OfferData = { export type OffersData = { offers: Offer[]; isOffersDataLoading: boolean; + hasError: boolean; }; export type UserProcess = { From d43ccea20b0feeebea7942367c32e387f9d50ad1 Mon Sep 17 00:00:00 2001 From: mayonnaise <90057279+Mayanzev@users.noreply.github.com> Date: Wed, 22 May 2024 11:59:06 +0300 Subject: [PATCH 02/10] start second part module 8 --- src/components/app/app.tsx | 14 ++++++- .../change-favorite-button.tsx | 38 +++++++++++++++++++ .../login-navigation/login-navigation.tsx | 7 ++-- src/components/offer-card/offer-card.tsx | 9 ++--- src/const.ts | 6 ++- src/store/api-actions.ts | 25 ++++++++++++ .../favorite-process/favorite-process.ts | 37 ++++++++++++++++++ src/store/favorite-process/selectors.ts | 4 ++ src/store/offers-data/offers-data.ts | 2 +- src/store/root-reducer.ts | 2 + src/types/favorite-data.ts | 4 ++ src/types/state.ts | 6 +++ src/utils.ts | 2 +- 13 files changed, 140 insertions(+), 16 deletions(-) create mode 100644 src/components/change-favorite-button/change-favorite-button.tsx create mode 100644 src/store/favorite-process/favorite-process.ts create mode 100644 src/store/favorite-process/selectors.ts create mode 100644 src/types/favorite-data.ts diff --git a/src/components/app/app.tsx b/src/components/app/app.tsx index 03137f9..b356db8 100644 --- a/src/components/app/app.tsx +++ b/src/components/app/app.tsx @@ -6,25 +6,35 @@ import NotFoundScreen from '../../pages/not-found-screen/not-found-screen.tsx'; import OfferScreen from '../../pages/offer-screen/offer-screen.tsx'; import PrivateRoute from '../private-route/private-route.tsx'; import { AppRoute } from '../../const.ts'; -import { useAppSelector } from '../../hooks/index.ts'; +import { useAppDispatch, useAppSelector } from '../../hooks/index.ts'; import LoadingScreen from '../../pages/loading-screen/loading-screen.tsx'; import HistoryRouter from '../history-route/history-route.tsx'; import browserHistory from '../../browser-history.ts'; import MainRouteRedirection from '../main-route-redirection/main-route-redirection.tsx'; import { getAuthorizationStatus } from '../../store/user-process/selectors.ts'; -import { getIsOffersDataLoading } from '../../store/offers-data/selectors.ts'; +import { getIsOffersDataLoading, getOffers } from '../../store/offers-data/selectors.ts'; +import { changeFavoritesNumber } from '../../store/favorite-process/favorite-process.ts'; +import { useEffect } from 'react'; function App(): JSX.Element { + const dispatch = useAppDispatch(); const isOffersDataLoading = useAppSelector(getIsOffersDataLoading); const authorizationStatus = useAppSelector(getAuthorizationStatus); + const offers = useAppSelector(getOffers); + + useEffect(() => { + const favorite = offers.filter((o) => o.isFavorite); + dispatch(changeFavoritesNumber(favorite.length)); + }, [offers, dispatch]); if (isOffersDataLoading || !authorizationStatus) { return ( ); } + return ( diff --git a/src/components/change-favorite-button/change-favorite-button.tsx b/src/components/change-favorite-button/change-favorite-button.tsx new file mode 100644 index 0000000..c9ff16c --- /dev/null +++ b/src/components/change-favorite-button/change-favorite-button.tsx @@ -0,0 +1,38 @@ +import { useState } from "react"; +import { useAppDispatch, useAppSelector } from "../../hooks"; +import { fetchFavoriteAction, postFavoriteAction } from "../../store/api-actions"; +import { getFavoritesNumber } from "../../store/favorite-process/selectors"; +import { changeFavoritesNumber } from "../../store/favorite-process/favorite-process"; +import { Offer } from "../../types/offer"; + +type ChangeFavoriteButtonProps = { + offer: Offer; +}; + +function ChangeFavoriteButton({ offer }: ChangeFavoriteButtonProps): JSX.Element { + const dispatch = useAppDispatch(); + + const [isFavorite, setIsFavorite] = useState(offer.isFavorite); + const favoriteNumber = useAppSelector(getFavoritesNumber) + + const handleFavorite = () => { + dispatch(postFavoriteAction({ id: offer.id, status: isFavorite ? 0 : 1 })), + dispatch(fetchFavoriteAction()), + setIsFavorite(!isFavorite) + isFavorite ? favoriteNumber > 0 && dispatch(changeFavoritesNumber(favoriteNumber - 1)) : dispatch(changeFavoritesNumber(favoriteNumber + 1)) + } + + return ( + + ); +} +export default ChangeFavoriteButton; \ No newline at end of file diff --git a/src/components/login-navigation/login-navigation.tsx b/src/components/login-navigation/login-navigation.tsx index 141a331..6a39990 100644 --- a/src/components/login-navigation/login-navigation.tsx +++ b/src/components/login-navigation/login-navigation.tsx @@ -2,18 +2,17 @@ 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'; +import { getFavoritesNumber } from '../../store/favorite-process/selectors'; function LoginNavigation(): JSX.Element { const dispatch = useAppDispatch(); - const offers = useAppSelector(getOffers); - const favoriteOffers = offers.filter((offer) => offer.isFavorite); const userData = useAppSelector(getUserData); const authorizationStatus = useAppSelector(getAuthorizationStatus); const handleSignOut = () => { dispatch(logoutAction()); }; + const favoriteNumber = useAppSelector(getFavoritesNumber); return (
- +
diff --git a/src/const.ts b/src/const.ts index c4af238..de16c68 100644 --- a/src/const.ts +++ b/src/const.ts @@ -22,7 +22,8 @@ export enum APIRoute { Login = '/login', Logout = '/logout', Comments = '/comments', - Nearby = '/nearby' + Nearby = '/nearby', + Favorite = '/favorite' } export const cities = { @@ -40,5 +41,6 @@ export enum NameSpace { Offer = 'OFFER', Offers = 'OFFERS', User = 'USER', - Common = 'COMMON' + Common = 'COMMON', + Favorite = 'FAVORITE' } diff --git a/src/store/api-actions.ts b/src/store/api-actions.ts index 5c7cd53..d6f5f21 100644 --- a/src/store/api-actions.ts +++ b/src/store/api-actions.ts @@ -9,6 +9,9 @@ import { UserData } from '../types/user-data'; import { dropToken, saveToken } from '../services/token'; import { Review } from '../types/review'; import { setError } from './common-data/common-data'; +import { FavoriteData } from '../types/favorite-data'; +import { updateOffer } from '../utils'; +import { updateOffers } from './offers-data/offers-data'; export const clearErrorAction = createAsyncThunk( @@ -124,3 +127,25 @@ export const postReviewAction = createAsyncThunk( + 'FETCH_FAVORITE_ACTION', + async (_arg, {extra: api}) => { + const { data } = await api.get(APIRoute.Favorite); + return data; + }, + ); + + export const postFavoriteAction = createAsyncThunk( + 'CHANGE_FAVORITE_ACTION', + async ({id, status}, {extra: api}) => { + await api.post(`${APIRoute.Favorite}/${id}/${status}`); + }, + ); \ No newline at end of file diff --git a/src/store/favorite-process/favorite-process.ts b/src/store/favorite-process/favorite-process.ts new file mode 100644 index 0000000..2ed1b57 --- /dev/null +++ b/src/store/favorite-process/favorite-process.ts @@ -0,0 +1,37 @@ +import { PayloadAction, createSlice } from '@reduxjs/toolkit'; +import { NameSpace } from '../../const'; +import { FavoriteProcess } from '../../types/state'; +import { fetchFavoriteAction } from '../api-actions'; + + +const initialState: FavoriteProcess = { + favoriteOffers: [], + isFavoriteOffersDataLoading: false, + favoritesNumber: 0 +}; + +export const favoriteProcess = createSlice({ + name: NameSpace.Favorite, + initialState, + reducers: { + changeFavoritesNumber: (state, action: PayloadAction) => { + state.favoritesNumber = action.payload; + }, + }, + extraReducers(builder) { + builder + .addCase(fetchFavoriteAction.fulfilled, (state, action) => { + state.favoriteOffers = action.payload; + state.isFavoriteOffersDataLoading = false; + }) + .addCase(fetchFavoriteAction.pending, (state) => { + state.isFavoriteOffersDataLoading = true; + }) + .addCase(fetchFavoriteAction.rejected, (state) => { + state.isFavoriteOffersDataLoading = false; + }) + } +} +); + +export const { changeFavoritesNumber } = favoriteProcess.actions; diff --git a/src/store/favorite-process/selectors.ts b/src/store/favorite-process/selectors.ts new file mode 100644 index 0000000..c34c405 --- /dev/null +++ b/src/store/favorite-process/selectors.ts @@ -0,0 +1,4 @@ +import { NameSpace } from '../../const'; +import { State } from '../../types/state'; + +export const getFavoritesNumber = (state: State): number => state[NameSpace.Favorite].favoritesNumber; \ No newline at end of file diff --git a/src/store/offers-data/offers-data.ts b/src/store/offers-data/offers-data.ts index 4b918a8..9960903 100644 --- a/src/store/offers-data/offers-data.ts +++ b/src/store/offers-data/offers-data.ts @@ -34,4 +34,4 @@ export const offersData = createSlice({ state.hasError = true; }); } -}); +}); \ No newline at end of file diff --git a/src/store/root-reducer.ts b/src/store/root-reducer.ts index c01dafa..8e0bd4d 100644 --- a/src/store/root-reducer.ts +++ b/src/store/root-reducer.ts @@ -4,10 +4,12 @@ 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'; +import { favoriteProcess } from './favorite-process/favorite-process'; export const rootReducer = combineReducers({ [NameSpace.Offers]: offersData.reducer, [NameSpace.Offer]: offerData.reducer, [NameSpace.User]: userProcess.reducer, [NameSpace.Common]: commmonData.reducer, + [NameSpace.Favorite]: favoriteProcess.reducer, }); diff --git a/src/types/favorite-data.ts b/src/types/favorite-data.ts new file mode 100644 index 0000000..c2d4a2e --- /dev/null +++ b/src/types/favorite-data.ts @@ -0,0 +1,4 @@ +export type FavoriteData = { + id: string; + status: number; + }; diff --git a/src/types/state.ts b/src/types/state.ts index 7dc341b..fe5aad6 100644 --- a/src/types/state.ts +++ b/src/types/state.ts @@ -30,6 +30,12 @@ export type CommonData = { error: string | null; } +export type FavoriteProcess = { + favoriteOffers: Offer[]; + isFavoriteOffersDataLoading: boolean; + favoritesNumber: number; +} + export type State = ReturnType; export type AppDispatch = typeof store.dispatch; diff --git a/src/utils.ts b/src/utils.ts index 367ebf4..481cb97 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -49,4 +49,4 @@ export const extractYearMonth = (initDate: string): string => { ]; const monthName = months[Number(monthNumber) - 1]; return [monthName, year].join(' '); -}; +}; \ No newline at end of file From d25e05b96f69580be966a28e4b5d4f714732483a Mon Sep 17 00:00:00 2001 From: mayonnaise <90057279+Mayanzev@users.noreply.github.com> Date: Wed, 22 May 2024 13:15:29 +0300 Subject: [PATCH 03/10] fix --- src/components/app/app.tsx | 17 +++-------- .../change-favorite-button.tsx | 29 ++++++++++++------- .../login-navigation/login-navigation.tsx | 1 + src/store/api-actions.ts | 6 ++-- src/store/offers-data/offers-data.ts | 12 ++++++-- src/utils.ts | 2 +- 6 files changed, 37 insertions(+), 30 deletions(-) diff --git a/src/components/app/app.tsx b/src/components/app/app.tsx index b356db8..405a2a0 100644 --- a/src/components/app/app.tsx +++ b/src/components/app/app.tsx @@ -6,29 +6,20 @@ import NotFoundScreen from '../../pages/not-found-screen/not-found-screen.tsx'; import OfferScreen from '../../pages/offer-screen/offer-screen.tsx'; import PrivateRoute from '../private-route/private-route.tsx'; import { AppRoute } from '../../const.ts'; -import { useAppDispatch, useAppSelector } from '../../hooks/index.ts'; +import { useAppSelector } from '../../hooks/index.ts'; import LoadingScreen from '../../pages/loading-screen/loading-screen.tsx'; import HistoryRouter from '../history-route/history-route.tsx'; import browserHistory from '../../browser-history.ts'; import MainRouteRedirection from '../main-route-redirection/main-route-redirection.tsx'; import { getAuthorizationStatus } from '../../store/user-process/selectors.ts'; -import { getIsOffersDataLoading, getOffers } from '../../store/offers-data/selectors.ts'; -import { changeFavoritesNumber } from '../../store/favorite-process/favorite-process.ts'; -import { useEffect } from 'react'; +import { getIsOffersDataLoading } from '../../store/offers-data/selectors.ts'; -function App(): JSX.Element { - const dispatch = useAppDispatch(); +function App(): JSX.Element { const isOffersDataLoading = useAppSelector(getIsOffersDataLoading); const authorizationStatus = useAppSelector(getAuthorizationStatus); - const offers = useAppSelector(getOffers); - - useEffect(() => { - const favorite = offers.filter((o) => o.isFavorite); - dispatch(changeFavoritesNumber(favorite.length)); - }, [offers, dispatch]); - + if (isOffersDataLoading || !authorizationStatus) { return ( diff --git a/src/components/change-favorite-button/change-favorite-button.tsx b/src/components/change-favorite-button/change-favorite-button.tsx index c9ff16c..b1d584b 100644 --- a/src/components/change-favorite-button/change-favorite-button.tsx +++ b/src/components/change-favorite-button/change-favorite-button.tsx @@ -1,9 +1,10 @@ -import { useState } from "react"; +import { useEffect, useState } from "react"; import { useAppDispatch, useAppSelector } from "../../hooks"; -import { fetchFavoriteAction, postFavoriteAction } from "../../store/api-actions"; +import { postFavoriteAction } from "../../store/api-actions"; import { getFavoritesNumber } from "../../store/favorite-process/selectors"; import { changeFavoritesNumber } from "../../store/favorite-process/favorite-process"; import { Offer } from "../../types/offer"; +import { getOffers } from "../../store/offers-data/selectors"; type ChangeFavoriteButtonProps = { offer: Offer; @@ -11,16 +12,23 @@ type ChangeFavoriteButtonProps = { function ChangeFavoriteButton({ offer }: ChangeFavoriteButtonProps): JSX.Element { const dispatch = useAppDispatch(); - + const offers = useAppSelector(getOffers); + const favoriteNumber = useAppSelector(getFavoritesNumber); const [isFavorite, setIsFavorite] = useState(offer.isFavorite); - const favoriteNumber = useAppSelector(getFavoritesNumber) + + useEffect(() => { + const currentFavoriteNumber = offers.filter((o) => o.isFavorite).length; + if (currentFavoriteNumber !== favoriteNumber) { + dispatch(changeFavoritesNumber(currentFavoriteNumber)); + } + }, [offers, favoriteNumber, dispatch]); const handleFavorite = () => { - dispatch(postFavoriteAction({ id: offer.id, status: isFavorite ? 0 : 1 })), - dispatch(fetchFavoriteAction()), - setIsFavorite(!isFavorite) - isFavorite ? favoriteNumber > 0 && dispatch(changeFavoritesNumber(favoriteNumber - 1)) : dispatch(changeFavoritesNumber(favoriteNumber + 1)) - } + setIsFavorite(!isFavorite); + const newFavoriteNumber = isFavorite ? favoriteNumber - 1 : favoriteNumber + 1; + dispatch(changeFavoritesNumber(newFavoriteNumber)); + dispatch(postFavoriteAction({ id: offer.id, status: isFavorite ? 0 : 1 })); + }; return (
) : null} -
+
- Place image + Place image
-
+
€{offer.price} diff --git a/src/pages/favorites-screen/favorites-screen.tsx b/src/pages/favorites-screen/favorites-screen.tsx index 3607211..baabbca 100644 --- a/src/pages/favorites-screen/favorites-screen.tsx +++ b/src/pages/favorites-screen/favorites-screen.tsx @@ -5,10 +5,12 @@ import { useAppSelector } from '../../hooks'; import { AppRoute } from '../../const'; import { Header } from '../../components/header/header'; import { getOffers } from '../../store/offers-data/selectors'; +import { getFavoriteOffersId } from '../../store/favorite-process/selectors'; function FavoritesScreen(): JSX.Element { - const favoriteOffers = useAppSelector(getOffers).filter((offer) => offer.isFavorite); + const favoritesOffersId = useAppSelector(getFavoriteOffersId) + const favoriteOffers = useAppSelector(getOffers).filter((offer) => favoritesOffersId.includes(offer.id)); return (
From 9ed716e01d8ed89b52b953a3ec6d1c314802afb8 Mon Sep 17 00:00:00 2001 From: mayonnaise <90057279+Mayanzev@users.noreply.github.com> Date: Thu, 23 May 2024 00:11:12 +0300 Subject: [PATCH 06/10] fix --- src/components/login-navigation/login-navigation.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/login-navigation/login-navigation.tsx b/src/components/login-navigation/login-navigation.tsx index 4eb04f1..89cd62d 100644 --- a/src/components/login-navigation/login-navigation.tsx +++ b/src/components/login-navigation/login-navigation.tsx @@ -4,6 +4,7 @@ import { useAppDispatch, useAppSelector } from '../../hooks'; import { AppRoute, AuthorizationStatus } from '../../const'; import { getAuthorizationStatus, getUserData } from '../../store/user-process/selectors'; import { getFavoriteOffersId } from '../../store/favorite-process/selectors'; +import { changeFavoritesId } from '../../store/favorite-process/favorite-process'; function LoginNavigation(): JSX.Element { const dispatch = useAppDispatch(); @@ -11,6 +12,7 @@ function LoginNavigation(): JSX.Element { const authorizationStatus = useAppSelector(getAuthorizationStatus); const handleSignOut = () => { dispatch(logoutAction()); + dispatch(changeFavoritesId([])) }; const favoriteNumber = useAppSelector(getFavoriteOffersId); From fe73209a58d5a804ed10b43c346e29b11691f62f Mon Sep 17 00:00:00 2001 From: mayonnaise <90057279+Mayanzev@users.noreply.github.com> Date: Thu, 23 May 2024 17:37:31 +0300 Subject: [PATCH 07/10] fix --- src/components/app/app.tsx | 9 ----- .../change-favorite-button.tsx | 33 ++++++++------- src/components/comment-form/comment-form.tsx | 16 ++++++-- .../login-navigation/login-navigation.tsx | 4 +- .../favorites-screen/favorites-screen.tsx | 35 +++++++++++----- src/pages/main-screen/main-screen.tsx | 2 +- src/pages/offer-screen/offer-screen.tsx | 40 ++++++++++++++++--- src/store/api-actions.ts | 33 ++++++++------- .../favorite-process/favorite-process.ts | 5 +-- src/store/offer-data/offer-data.ts | 16 +++++++- src/store/offer-data/selectors.ts | 2 + src/store/offers-data/offers-data.ts | 10 ++--- src/types/state.ts | 2 + src/utils.ts | 3 +- 14 files changed, 135 insertions(+), 75 deletions(-) diff --git a/src/components/app/app.tsx b/src/components/app/app.tsx index 2873ca9..80fe345 100644 --- a/src/components/app/app.tsx +++ b/src/components/app/app.tsx @@ -13,9 +13,6 @@ import browserHistory from '../../browser-history.ts'; import MainRouteRedirection from '../main-route-redirection/main-route-redirection.tsx'; import { getAuthorizationStatus } from '../../store/user-process/selectors.ts'; import { getIsOffersDataLoading } from '../../store/offers-data/selectors.ts'; -import { useEffect } from 'react'; -import { store } from '../../store/index.ts'; -import { fetchFavoriteAction } from '../../store/api-actions.ts'; function App(): JSX.Element { @@ -23,12 +20,6 @@ function App(): JSX.Element { const authorizationStatus = useAppSelector(getAuthorizationStatus); const isFavoriteOffersDataLoading = useAppSelector(getIsOffersDataLoading); - useEffect(() => { - if (authorizationStatus === AuthorizationStatus.Auth) { - store.dispatch(fetchFavoriteAction); - } - }, [authorizationStatus]); - if (isOffersDataLoading || authorizationStatus === AuthorizationStatus.Unknown || isFavoriteOffersDataLoading) { return ( diff --git a/src/components/change-favorite-button/change-favorite-button.tsx b/src/components/change-favorite-button/change-favorite-button.tsx index e09cb58..c39a039 100644 --- a/src/components/change-favorite-button/change-favorite-button.tsx +++ b/src/components/change-favorite-button/change-favorite-button.tsx @@ -1,8 +1,8 @@ import { useEffect, useState } from 'react'; import { useAppDispatch, useAppSelector } from '../../hooks'; import { postFavoriteAction } from '../../store/api-actions'; -import { getFavoriteOffersId, getFavoritesNumber } from '../../store/favorite-process/selectors'; -import { changeFavoritesId, changeFavoritesNumber } from '../../store/favorite-process/favorite-process'; +import { getFavoriteOffersId } from '../../store/favorite-process/selectors'; +import { changeFavoritesId } from '../../store/favorite-process/favorite-process'; import { Offer } from '../../types/offer'; import { AppRoute, AuthorizationStatus } from '../../const'; import { redirectToRoute } from '../../store/action'; @@ -14,37 +14,40 @@ type ChangeFavoriteButtonProps = { function ChangeFavoriteButton({ offer }: ChangeFavoriteButtonProps): JSX.Element { const dispatch = useAppDispatch(); - const favoriteNumber = useAppSelector(getFavoritesNumber); const favoritesOffersId = useAppSelector(getFavoriteOffersId); - const [isFavorite, setIsFavorite] = useState(favoritesOffersId.includes(offer.id)); const status = useAppSelector(getAuthorizationStatus); + const [isFavorite, setIsFavorite] = useState(favoritesOffersId.includes(offer.id)); + const [isSubmitting, setIsSubmitting] = useState(false); useEffect(() => { - setIsFavorite(favoritesOffersId.includes(offer.id)); - }, [favoritesOffersId, offer.id]); - - useEffect(() => { - const currentFavoriteNumber = favoritesOffersId.length; - if (currentFavoriteNumber !== favoriteNumber) { - dispatch(changeFavoritesNumber(currentFavoriteNumber)); + if (!isSubmitting) { + setIsFavorite(favoritesOffersId.includes(offer.id)); } - }, [favoritesOffersId, favoriteNumber, dispatch]); + }, [favoritesOffersId, offer.id, isSubmitting, dispatch]); const handleFavorite = () => { if (status === AuthorizationStatus.NoAuth) { dispatch(redirectToRoute(AppRoute.Login)); } else { - dispatch(changeFavoritesId(isFavorite ? favoritesOffersId.filter((id) => id !== offer.id) : favoritesOffersId.concat(offer.id))); + setIsSubmitting(true); + const updatedFavorites = isFavorite + ? favoritesOffersId.filter((id) => id !== offer.id) + : [...favoritesOffersId, offer.id]; + dispatch(changeFavoritesId(updatedFavorites)); setIsFavorite(!isFavorite); - dispatch(postFavoriteAction({ id: offer.id, status: isFavorite ? 0 : 1 })); + dispatch(postFavoriteAction({ id: offer.id, status: isFavorite ? 0 : 1 })) + .then(() => setIsSubmitting(false)) + .catch(() => { + setIsSubmitting(false); + }); } }; - return (
diff --git a/src/pages/main-screen/main-screen.tsx b/src/pages/main-screen/main-screen.tsx index 444b8dc..62a5016 100644 --- a/src/pages/main-screen/main-screen.tsx +++ b/src/pages/main-screen/main-screen.tsx @@ -12,9 +12,9 @@ import EmptyOffers from '../empty-offers/empty-offers'; function MainScreen(): JSX.Element { const city = useAppSelector(getCity); const offers = useAppSelector(getOffers); + const hasError = useAppSelector(getErrorStatus); const chosenOffers = offers.filter((offer) => offer.city.name === city); const points = chosenOffers.map((offer) => offer.location); - const hasError = useAppSelector(getErrorStatus); return (
diff --git a/src/pages/offer-screen/offer-screen.tsx b/src/pages/offer-screen/offer-screen.tsx index a2f5427..6252ba2 100644 --- a/src/pages/offer-screen/offer-screen.tsx +++ b/src/pages/offer-screen/offer-screen.tsx @@ -3,14 +3,19 @@ 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 { fetchNearbyAction, fetchOfferAction, fetchReviewsAction } from '../../store/api-actions'; +import { fetchNearbyAction, fetchOfferAction, fetchReviewsAction, postFavoriteAction } from '../../store/api-actions'; import LoadingScreen from '../loading-screen/loading-screen'; import { useParams } from 'react-router-dom'; -import { useEffect } from 'react'; +import { useEffect, useState } 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'; import { changeHighlightedMarker } from '../../store/common-data/common-data'; +import { AppRoute, AuthorizationStatus } from '../../const'; +import { redirectToRoute } from '../../store/action'; +import { changeFavoritesId } from '../../store/favorite-process/favorite-process'; +import { getFavoriteOffersId } from '../../store/favorite-process/selectors'; +import { getAuthorizationStatus } from '../../store/user-process/selectors'; const MAXIMUM_NEARBY_PREVIEW = 3; @@ -20,13 +25,17 @@ function OfferScreen(): JSX.Element { const reviews = useAppSelector(getReviews); const nearbyOffers = useAppSelector(getNearbyOffers); const city = useAppSelector(getOffers)[0].city; + const favoritesOffersId = useAppSelector(getFavoriteOffersId); + const status = useAppSelector(getAuthorizationStatus); + const [isSubmitting, setIsSubmitting] = useState(false); + const id = String(useParams().id); + const [isFavorite, setIsFavorite] = useState(favoritesOffersId.includes(id)); const displayedNearby = (nearbyOffers).slice( 0, MAXIMUM_NEARBY_PREVIEW ); - const id = String(useParams().id); useEffect(() => { dispatch(fetchOfferAction(id)); dispatch(fetchReviewsAction(id)); @@ -41,6 +50,25 @@ function OfferScreen(): JSX.Element { ); } + const bedrooms = offer?.bedrooms; + const maxAdults = offer?.maxAdults; + + const handleFavorite = () => { + if (status === AuthorizationStatus.NoAuth) { + dispatch(redirectToRoute(AppRoute.Login)); + } else { + setIsSubmitting(true); + const updatedFavorites = isFavorite ? favoritesOffersId.filter((favoriteId) => favoriteId !== id) : [...favoritesOffersId, id]; + dispatch(changeFavoritesId(updatedFavorites)); + setIsFavorite(!isFavorite); + dispatch(postFavoriteAction({ id: id, status: isFavorite ? 0 : 1 })) + .then(() => setIsSubmitting(false)) + .catch(() => { + setIsSubmitting(false); + }); + } + }; + return (
@@ -66,7 +94,7 @@ function OfferScreen(): JSX.Element {

{offer?.title}

-
- +
diff --git a/src/pages/offer-screen/offer-screen.tsx b/src/pages/offer-screen/offer-screen.tsx index 6252ba2..049e13e 100644 --- a/src/pages/offer-screen/offer-screen.tsx +++ b/src/pages/offer-screen/offer-screen.tsx @@ -3,19 +3,15 @@ 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 { fetchNearbyAction, fetchOfferAction, fetchReviewsAction, postFavoriteAction } from '../../store/api-actions'; +import { fetchNearbyAction, fetchOfferAction, fetchReviewsAction } from '../../store/api-actions'; import LoadingScreen from '../loading-screen/loading-screen'; import { useParams } from 'react-router-dom'; -import { useEffect, useState } from 'react'; +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'; import { changeHighlightedMarker } from '../../store/common-data/common-data'; -import { AppRoute, AuthorizationStatus } from '../../const'; -import { redirectToRoute } from '../../store/action'; -import { changeFavoritesId } from '../../store/favorite-process/favorite-process'; -import { getFavoriteOffersId } from '../../store/favorite-process/selectors'; -import { getAuthorizationStatus } from '../../store/user-process/selectors'; +import ChangeFavoriteButton from '../../components/change-favorite-button/change-favorite-button'; const MAXIMUM_NEARBY_PREVIEW = 3; @@ -25,11 +21,7 @@ function OfferScreen(): JSX.Element { const reviews = useAppSelector(getReviews); const nearbyOffers = useAppSelector(getNearbyOffers); const city = useAppSelector(getOffers)[0].city; - const favoritesOffersId = useAppSelector(getFavoriteOffersId); - const status = useAppSelector(getAuthorizationStatus); - const [isSubmitting, setIsSubmitting] = useState(false); const id = String(useParams().id); - const [isFavorite, setIsFavorite] = useState(favoritesOffersId.includes(id)); const displayedNearby = (nearbyOffers).slice( 0, @@ -53,22 +45,6 @@ function OfferScreen(): JSX.Element { const bedrooms = offer?.bedrooms; const maxAdults = offer?.maxAdults; - const handleFavorite = () => { - if (status === AuthorizationStatus.NoAuth) { - dispatch(redirectToRoute(AppRoute.Login)); - } else { - setIsSubmitting(true); - const updatedFavorites = isFavorite ? favoritesOffersId.filter((favoriteId) => favoriteId !== id) : [...favoritesOffersId, id]; - dispatch(changeFavoritesId(updatedFavorites)); - setIsFavorite(!isFavorite); - dispatch(postFavoriteAction({ id: id, status: isFavorite ? 0 : 1 })) - .then(() => setIsSubmitting(false)) - .catch(() => { - setIsSubmitting(false); - }); - } - }; - return (
@@ -94,12 +70,12 @@ function OfferScreen(): JSX.Element {

{offer?.title}

- +
From b209b351409fb3bdd74ed042dbc45f47b56e7970 Mon Sep 17 00:00:00 2001 From: mayonnaise <90057279+Mayanzev@users.noreply.github.com> Date: Thu, 23 May 2024 20:24:47 +0300 Subject: [PATCH 10/10] fix lint --- .../change-favorite-button.tsx | 4 +--- src/components/comment-form/comment-form.tsx | 2 +- src/components/offer-card/offer-card.tsx | 2 +- .../favorites-screen/favorites-screen.tsx | 24 +++++++++---------- 4 files changed, 15 insertions(+), 17 deletions(-) diff --git a/src/components/change-favorite-button/change-favorite-button.tsx b/src/components/change-favorite-button/change-favorite-button.tsx index af07de7..07f9578 100644 --- a/src/components/change-favorite-button/change-favorite-button.tsx +++ b/src/components/change-favorite-button/change-favorite-button.tsx @@ -32,9 +32,7 @@ function ChangeFavoriteButton({ offerId, typeButton, width, height }: ChangeFavo dispatch(redirectToRoute(AppRoute.Login)); } else { setIsSubmitting(true); - const updatedFavorites = isFavorite - ? favoritesOffersId.filter((id) => id !== offerId) - : [...favoritesOffersId, offerId]; + const updatedFavorites = isFavorite ? favoritesOffersId.filter((id) => id !== offerId) : [...favoritesOffersId, offerId]; dispatch(changeFavoritesId(updatedFavorites)); setIsFavorite(!isFavorite); dispatch(postFavoriteAction({ id: offerId, status: isFavorite ? 0 : 1 })) diff --git a/src/components/comment-form/comment-form.tsx b/src/components/comment-form/comment-form.tsx index fc147ea..6b7903c 100644 --- a/src/components/comment-form/comment-form.tsx +++ b/src/components/comment-form/comment-form.tsx @@ -20,7 +20,7 @@ function CommentForm(): JSX.Element { const MINIMUM_COMMENT_CHARACTERS = 50; const MAXIMUM_COMMENT_CHARACTERS = 300; - const isCommentPosting = useAppSelector(getIsCommentPosting) + const isCommentPosting = useAppSelector(getIsCommentPosting); const isSubmitInvalid = ( formData.review.length < MINIMUM_COMMENT_CHARACTERS || diff --git a/src/components/offer-card/offer-card.tsx b/src/components/offer-card/offer-card.tsx index baeb24d..c8ce50b 100644 --- a/src/components/offer-card/offer-card.tsx +++ b/src/components/offer-card/offer-card.tsx @@ -42,7 +42,7 @@ function OfferCard({ offer, cardType }: OfferProps): JSX.Element { typeButton='place-card' width='18' height='19' - /> + />
diff --git a/src/pages/favorites-screen/favorites-screen.tsx b/src/pages/favorites-screen/favorites-screen.tsx index bdeed5a..8a7e1bd 100644 --- a/src/pages/favorites-screen/favorites-screen.tsx +++ b/src/pages/favorites-screen/favorites-screen.tsx @@ -21,7 +21,7 @@ function FavoritesScreen(): JSX.Element { const handleCityClick = (city: string) => { dispatch(changeCity(city)); dispatch(redirectToRoute(AppRoute.Main)); - } + }; return (
@@ -49,17 +49,17 @@ function FavoritesScreen(): JSX.Element {
) : ( -
-
-
-

Favorites (empty)

-
- Nothing yet saved. -

Save properties to narrow down search or plan your future trips.

-
-
-
-
+
+
+
+

Favorites (empty)

+
+ Nothing yet saved. +

Save properties to narrow down search or plan your future trips.

+
+
+
+
)}