From cb33d30f21b31405e8d1027a41aa3f5dfb131881 Mon Sep 17 00:00:00 2001 From: ktvtk Date: Mon, 2 Dec 2024 17:35:58 +0500 Subject: [PATCH] hw 8.2 --- src/const.ts | 5 ++ .../main-empty-screen/main-empty-screen.tsx | 66 +++++++++++++++++++ src/pages/main-screen/main-screen.tsx | 14 +++- src/pages/offer-screen/offer-screen.tsx | 16 +++-- src/servises/api.ts | 4 -- src/store/api-actions.ts | 27 ++++++-- src/store/app-data/app-data.ts | 10 ++- src/store/app-data/selectors.ts | 4 +- .../detail-offer-data/detail-offer-data.ts | 2 +- src/store/detail-offer-data/selectors.ts | 2 +- src/types/state.ts | 5 +- 11 files changed, 131 insertions(+), 24 deletions(-) create mode 100644 src/pages/main-empty-screen/main-empty-screen.tsx diff --git a/src/const.ts b/src/const.ts index 6a410b7..1770d1b 100644 --- a/src/const.ts +++ b/src/const.ts @@ -126,4 +126,9 @@ export enum FavoriteStatus { Add } +export enum LoadingStatus { + Loading = 'loading' , + Succeed = 'succeed', + Failed = 'failed' +} diff --git a/src/pages/main-empty-screen/main-empty-screen.tsx b/src/pages/main-empty-screen/main-empty-screen.tsx new file mode 100644 index 0000000..a116d5b --- /dev/null +++ b/src/pages/main-empty-screen/main-empty-screen.tsx @@ -0,0 +1,66 @@ +import {JSX} from 'react'; +import MemoizedHeader from '../../components/header/header.tsx'; +import {Helmet} from 'react-helmet-async'; + +export function MainEmptyScreen() : JSX.Element { + return ( +
+ + 6 cities + + +
+

Cities

+
+
+ +
+
+
+
+
+
+ No places to stay available +

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

+
+
+
+
+
+
+
+ ); +} diff --git a/src/pages/main-screen/main-screen.tsx b/src/pages/main-screen/main-screen.tsx index b2ebdc3..81ee2ac 100644 --- a/src/pages/main-screen/main-screen.tsx +++ b/src/pages/main-screen/main-screen.tsx @@ -7,8 +7,11 @@ import {useAppSelector} from '../../hooks'; import {Sorting} from '../../components/sorting/sorting.tsx'; import {SortOption} from '../../types/sort-option.ts'; import MemoizedHeader from '../../components/header/header.tsx'; -import {getActiveCity} from '../../store/app-data/selectors.ts'; +import {getActiveCity, getLoadingStatus} from '../../store/app-data/selectors.ts'; import {getOffers} from '../../store/offers-data/selectors.ts'; +import {LoadingStatus} from '../../const.ts'; +import {MainEmptyScreen} from '../main-empty-screen/main-empty-screen.tsx'; +import {Loading} from '../../components/loading/loading.tsx'; export function MainScreen(): JSX.Element { @@ -37,6 +40,15 @@ export function MainScreen(): JSX.Element { const handleSortChange = (option: SortOption) => { setSortingOption(option); }; + const loadingStatus = useAppSelector(getLoadingStatus); + + if (loadingStatus !== LoadingStatus.Loading && offers.length === 0){ + return ; + } + + if (loadingStatus === LoadingStatus.Loading) { + return ; + } return (
diff --git a/src/pages/offer-screen/offer-screen.tsx b/src/pages/offer-screen/offer-screen.tsx index 2fcab2a..e27df31 100644 --- a/src/pages/offer-screen/offer-screen.tsx +++ b/src/pages/offer-screen/offer-screen.tsx @@ -1,7 +1,7 @@ import {JSX, useEffect, useMemo} from 'react'; import {Helmet} from 'react-helmet-async'; import {Navigate, useParams} from 'react-router-dom'; -import {AuthorizationStatus} from '../../const.ts'; +import {AuthorizationStatus, LoadingStatus} from '../../const.ts'; import {Map} from '../../components/map/map.tsx'; import MemoizedReviewList from '../../components/review-list/review-list.tsx'; import {OffersList} from '../../components/offers-list/offers-list.tsx'; @@ -13,6 +13,7 @@ import {setDetailOffer} from '../../store/detail-offer-data/detail-offer-data.ts import {getDetailOffer, getNearOffers, getReviews} from '../../store/detail-offer-data/selectors.ts'; import {getAuthoriztionStatus} from '../../store/user-data/selectors.ts'; import MemoizedReviewForm from '../../components/review-form/review-form.tsx'; +import {getLoadingStatus} from '../../store/app-data/selectors.ts'; export function OfferScreen() : JSX.Element { const {id} = useParams(); @@ -29,15 +30,20 @@ export function OfferScreen() : JSX.Element { const memoizedNearOffers = useMemo(() => nearOffers.slice(0, 3), [nearOffers]); const reviews = useAppSelector(getReviews); const isAuth = useAppSelector(getAuthoriztionStatus) === AuthorizationStatus.Authorized; + const loadingStatus = useAppSelector(getLoadingStatus); - if (offer === null){ - return (); + if (loadingStatus === LoadingStatus.Loading){ + return ; } - if (offer === undefined) { + if (loadingStatus === LoadingStatus.Failed && !offer) { return ; } + if (!offer) { + return <> ; + } + return (
@@ -137,7 +143,7 @@ export function OfferScreen() : JSX.Element {
diff --git a/src/servises/api.ts b/src/servises/api.ts index ae718db..01f31e5 100644 --- a/src/servises/api.ts +++ b/src/servises/api.ts @@ -3,7 +3,6 @@ import {getToken} from './token.ts'; import {store} from '../store'; import {AuthorizationStatus} from '../const.ts'; import {setAuthorizationStatus} from '../store/user-data/user-data.ts'; -import {setDetailOffer} from '../store/detail-offer-data/detail-offer-data.ts'; const baseURL = 'https://14.design.htmlacademy.pro/six-cities'; const requestTimeout = 5000; @@ -39,9 +38,6 @@ export const createAPI = () : AxiosInstance => { setAuthorizationStatus(AuthorizationStatus.Unauthorized), ); } - if (error.response && error.response.status === 404) { - store.dispatch(setDetailOffer(undefined)); - } throw error; }, ); diff --git a/src/store/api-actions.ts b/src/store/api-actions.ts index 746da9b..220fde4 100644 --- a/src/store/api-actions.ts +++ b/src/store/api-actions.ts @@ -2,16 +2,17 @@ import {createAsyncThunk} from '@reduxjs/toolkit'; import {AppDispatch, State} from '../types/state.ts'; import {AxiosInstance} from 'axios'; import {Offer} from '../types/offer.ts'; -import {ApiRoute, AuthorizationStatus} from '../const.ts'; +import {ApiRoute, AuthorizationStatus, LoadingStatus} from '../const.ts'; import {DetailOffer} from '../types/detail-offer.ts'; import {Review, ReviewInfo} from '../types/review.ts'; -import {saveToken, dropToken, getToken} from '../servises/token.ts'; +import {dropToken, getToken, saveToken} from '../servises/token.ts'; import {AuthInfo, LoginInfo} from '../types/user.ts'; import {store} from './index.ts'; import {FavoriteInfo} from '../types/favorite-info.ts'; import {setFavoritesCount, setFavoritesOffers, setOffers, updateOffers} from './offers-data/offers-data.ts'; import {setDetailOffer, setNearOffers, setReviews} from './detail-offer-data/detail-offer-data.ts'; import {saveUserEmail, setAuthorizationStatus} from './user-data/user-data.ts'; +import {setLoadingStatus} from './app-data/app-data.ts'; export const fetchOffers = createAsyncThunk( 'data/fetchOffers', async (_arg, {dispatch, extra: api}) => { - const {data} = await api.get(ApiRoute.offers); - dispatch(setOffers(data)); + dispatch(setLoadingStatus(LoadingStatus.Loading)); + try { + const { data } = await api.get(ApiRoute.offers); + dispatch(setOffers(data)); // обновляем данные + dispatch(setLoadingStatus(LoadingStatus.Succeed)); + } catch (error) { + dispatch(setLoadingStatus(LoadingStatus.Failed)); + throw error; + } } ); @@ -59,8 +67,15 @@ export const fetchDetailOffer = createAsyncThunk( 'data/fetchDetailOffer', async (offerId, {dispatch, extra: api}) => { - const {data} = await api.get(`${ApiRoute.offers}/${offerId}`); - dispatch(setDetailOffer(data)); + dispatch(setLoadingStatus(LoadingStatus.Loading)); + try { + const {data} = await api.get(`${ApiRoute.offers}/${offerId}`); + dispatch(setDetailOffer(data)); + dispatch(setLoadingStatus(LoadingStatus.Succeed)); + } catch (error) { + dispatch(setLoadingStatus(LoadingStatus.Failed)); + throw error; + } } ); diff --git a/src/store/app-data/app-data.ts b/src/store/app-data/app-data.ts index 944e1f4..84e9f88 100644 --- a/src/store/app-data/app-data.ts +++ b/src/store/app-data/app-data.ts @@ -1,10 +1,11 @@ import {AppData} from '../../types/state.ts'; -import {Namespace, Paris} from '../../const.ts'; +import {LoadingStatus, Namespace, Paris} from '../../const.ts'; import {createSlice, PayloadAction} from '@reduxjs/toolkit'; import {City} from '../../types/city.ts'; const initialState: AppData = { - city: Paris + city: Paris, + loadingStatus: null }; export const appData = createSlice({ @@ -13,8 +14,11 @@ export const appData = createSlice({ reducers: { changeActiveCity: (state, action: PayloadAction) => { state.city = action.payload; + }, + setLoadingStatus: (state, action: PayloadAction) => { + state.loadingStatus = action.payload; } } }); -export const {changeActiveCity} = appData.actions; +export const {changeActiveCity, setLoadingStatus} = appData.actions; diff --git a/src/store/app-data/selectors.ts b/src/store/app-data/selectors.ts index 4abfb58..555b384 100644 --- a/src/store/app-data/selectors.ts +++ b/src/store/app-data/selectors.ts @@ -1,5 +1,7 @@ import {State} from '../../types/state.ts'; -import {Namespace} from '../../const.ts'; +import {LoadingStatus, Namespace} from '../../const.ts'; import {City} from '../../types/city.ts'; export const getActiveCity = (state: State): City => state[Namespace.App].city; + +export const getLoadingStatus = (state: State): LoadingStatus | null => state[Namespace.App].loadingStatus; diff --git a/src/store/detail-offer-data/detail-offer-data.ts b/src/store/detail-offer-data/detail-offer-data.ts index 820f293..9c2609b 100644 --- a/src/store/detail-offer-data/detail-offer-data.ts +++ b/src/store/detail-offer-data/detail-offer-data.ts @@ -15,7 +15,7 @@ export const detailOfferData = createSlice({ name: Namespace.DetailOffer, initialState, reducers:{ - setDetailOffer: (state, action: PayloadAction) => { + setDetailOffer: (state, action: PayloadAction) => { state.detailOffer = action.payload; }, setNearOffers: (state, action: PayloadAction) => { diff --git a/src/store/detail-offer-data/selectors.ts b/src/store/detail-offer-data/selectors.ts index fe2c0e2..1f1cd4e 100644 --- a/src/store/detail-offer-data/selectors.ts +++ b/src/store/detail-offer-data/selectors.ts @@ -4,6 +4,6 @@ import {Namespace} from '../../const.ts'; import {Offer} from '../../types/offer.ts'; import {Review} from '../../types/review.ts'; -export const getDetailOffer = (state: State): DetailOffer | null | undefined=> state[Namespace.DetailOffer].detailOffer; +export const getDetailOffer = (state: State): DetailOffer | null => state[Namespace.DetailOffer].detailOffer; export const getNearOffers = (state: State): Offer[] => state[Namespace.DetailOffer].nearOffers; export const getReviews = (state: State): Review[] => state[Namespace.DetailOffer].reviews; diff --git a/src/types/state.ts b/src/types/state.ts index e3673f0..e9d6825 100644 --- a/src/types/state.ts +++ b/src/types/state.ts @@ -3,7 +3,7 @@ import {City} from './city.ts'; import {DetailOffer} from './detail-offer.ts'; import {Offer} from './offer.ts'; import {Review} from './review.ts'; -import {AuthorizationStatus} from '../const.ts'; +import {AuthorizationStatus, LoadingStatus} from '../const.ts'; export type State = ReturnType; @@ -11,10 +11,11 @@ export type AppDispatch = typeof store.dispatch; export type AppData = { city: City; + loadingStatus: LoadingStatus | null; }; export type DetailOfferData = { - detailOffer: DetailOffer | null | undefined; + detailOffer: DetailOffer | null; nearOffers: Offer[]; reviews: Review[]; };