Skip to content

Commit

Permalink
hw 7.11
Browse files Browse the repository at this point in the history
  • Loading branch information
ktvtk committed Nov 23, 2024
1 parent 1e40d2a commit 6688fe3
Show file tree
Hide file tree
Showing 10 changed files with 173 additions and 48 deletions.
10 changes: 10 additions & 0 deletions src/components/app/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,16 @@ export function App(): React.JSX.Element {
path={AppRoute.Offer}
element={<OfferScreen />}
/>
<Route
path={AppRoute.Favorites}
element={
<PrivateRoute
authorizationStatus={AuthorizationStatus.Auth}
>
<FavoriteScreen />
</PrivateRoute>
}
/>
<Route
path='*'
element={<NotFoundScreen />}
Expand Down
17 changes: 17 additions & 0 deletions src/components/loading/loading.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/* Ключевые кадры для анимации вращения */
@keyframes react-spinners-rotate {
100% {
transform: rotate(360deg);
}
}

/* Ключевые кадры для анимации отскока */
@keyframes react-spinners-bounce {
0%, 100% {
transform: scale(0);
}
50% {
transform: scale(1.0);
}
}

37 changes: 37 additions & 0 deletions src/components/loading/loading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import './loading.css';
import React, {JSX} from 'react';

export function Loading() : JSX.Element {
// Жестко заданные параметры
const color : string = '#3069a6';
const size: number = 80;
const speedMultiplier : number = 1;

const wrapperStyle : React.CSSProperties = {
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
position: 'relative',
width: '100vw',
height: '100vh',
animation: `react-spinners-rotate ${2 / speedMultiplier}s 0s infinite linear`,
};

const dotStyle = (i: number) : React.CSSProperties => ({
position: 'relative',
top: i % 2 ? '0' : 'auto',
bottom: i % 2 ? 'auto' : '0',
height: `${size / 2}px`,
width: `${size / 2}px`,
backgroundColor: color,
borderRadius: '100%',
animation: `react-spinners-bounce ${2 / speedMultiplier}s ${i === 2 ? '1s' : '0s'} infinite linear`,
});

return (
<span style={wrapperStyle}>
<span style={dotStyle(1)} />
<span style={dotStyle(2)} />
</span>
);
}
2 changes: 1 addition & 1 deletion src/const.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,5 +111,5 @@ export const apiRoute = {
favorite: '/favorite',
login: '/login',
logout: '/logout',
reviews: '/comments/:offerId'
reviews: '/comments'
};
12 changes: 5 additions & 7 deletions src/pages/favorites-screen/favorite-screen.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
import {JSX} from 'react';
import {Helmet} from 'react-helmet-async';
import {Offer} from '../../types/offer.ts';
import {Link} from 'react-router-dom';
import {AppRoute} from '../../const.ts';
import {useAppSelector} from '../../hooks';

type FavoriteScreenProps = {
offers: Offer[];
}

export function FavoriteScreen({offers} : FavoriteScreenProps) : JSX.Element {
export function FavoriteScreen() : JSX.Element {
const offers = useAppSelector((state) => state.offers);
const favorites = offers.filter((offer) => offer.isFavorite);
const cities = Array.from(new Set(favorites.map((offer) => offer.city.name))).sort();

Expand All @@ -32,7 +30,7 @@ export function FavoriteScreen({offers} : FavoriteScreenProps) : JSX.Element {
<div className="header__avatar-wrapper user__avatar-wrapper">
</div>
<span className="header__user-name user__name">[email protected]</span>
<span className="header__favorite-count">3</span>
<span className="header__favorite-count">{favorites.length}</span>
</a>
</li>
<li className="header__nav-item">
Expand Down Expand Up @@ -72,7 +70,7 @@ export function FavoriteScreen({offers} : FavoriteScreenProps) : JSX.Element {
</div>}
<div className="favorites__image-wrapper place-card__image-wrapper">
<Link to={AppRoute.Offer.replace(':id', favorite.id)}>
<img className="place-card__image" src={`/img/${favorite.imageSrc}`} width="150" height="110" alt="Place image"/>
<img className="place-card__image" src={favorite.previewImage} width="150" height="110" alt="Place image"/>
</Link>
</div>
<div className="favorites__card-info place-card__info">
Expand Down
4 changes: 2 additions & 2 deletions src/pages/not-found-screen/not-found-screen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import './not-found-screen.css';

export function NotFoundScreen() : JSX.Element {
return (
<body className="page-400">
<div className="page-400">
<Helmet>
<title>6 sities: Page Not Found</title>
</Helmet>
Expand All @@ -15,6 +15,6 @@ export function NotFoundScreen() : JSX.Element {
<h2 className="text-not-found">Page Not Found</h2>
<Link to={AppRoute.Main} className="back-button">Go back to Home</Link>
</div>
</body>
</div>
);
}
71 changes: 36 additions & 35 deletions src/pages/offer-screen/offer-screen.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,35 @@
import {JSX} from 'react';
import {JSX, useEffect} from 'react';
import {Helmet} from 'react-helmet-async';
import {DetailOffer} from '../../types/detail-offer.ts';
import {Link, Navigate, useParams} from 'react-router-dom';
import {AppRoute} from '../../const.ts';
import {Review} from '../../types/review.ts';
import ReviewForm from '../../components/review-form/review-form.tsx';
import {Map} from '../../components/map/map.tsx';
import {Offer} from '../../types/offer.ts';
import {ReviewList} from '../../components/review-list/review-list.tsx';
import {OffersList} from '../../components/offers-list/offers-list.tsx';
import {useAppSelector} from '../../hooks';

type OfferScreenProps = {
offers: DetailOffer[];
reviews: Review[];
nearOffers: Offer[];
}
import {store} from '../../store';
import {setDetailOffer} from '../../store/action.ts';
import {fetchDetailOffer, fetchNearOffers, fetchReviews} from '../../store/api-actions.ts';
import {Loading} from '../../components/loading/loading.tsx';

export function OfferScreen() : JSX.Element {
const detailOffers = useAppSelector(((state) => state.offers));
const {id} = useParams();
const offer = detailOffers.find((item) => item.id === id);
useEffect(() => {
store.dispatch(setDetailOffer(null));
store.dispatch(fetchDetailOffer(id!));
store.dispatch(fetchNearOffers(id!));
store.dispatch(fetchReviews(id!));
}, [id]);

const offer = useAppSelector((state) => state.detailOffer);
const nearOffers = useAppSelector((state) => state.nearOffers).slice(
0,
3,
);
const reviews = useAppSelector((state) => state.reviews);
if (offer === null){
return (<Loading />);
}

if (offer) {
return (
Expand Down Expand Up @@ -61,24 +70,11 @@ export function OfferScreen() : JSX.Element {
<section className="offer">
<div className="offer__gallery-container container">
<div className="offer__gallery">
<div className="offer__image-wrapper">
<img className="offer__image" src="img/room.jpg" alt="Photo studio"/>
</div>
<div className="offer__image-wrapper">
<img className="offer__image" src="img/apartment-01.jpg" alt="Photo studio"/>
</div>
<div className="offer__image-wrapper">
<img className="offer__image" src="img/apartment-02.jpg" alt="Photo studio"/>
</div>
<div className="offer__image-wrapper">
<img className="offer__image" src="img/apartment-03.jpg" alt="Photo studio"/>
</div>
<div className="offer__image-wrapper">
<img className="offer__image" src="img/studio-01.jpg" alt="Photo studio"/>
</div>
<div className="offer__image-wrapper">
<img className="offer__image" src="img/apartment-01.jpg" alt="Photo studio"/>
</div>
{offer.images.map((image, index) => (
<div key={image} className="offer__image-wrapper">
<img className="offer__image" src={image} alt={`Photo ${index + 1}`}/>
</div>
))}
</div>
</div>
<div className="offer__container container">
Expand All @@ -90,7 +86,10 @@ export function OfferScreen() : JSX.Element {
<h1 className="offer__name">
{offer.title}
</h1>
<button className={`offer__bookmark-button ${offer.isFavorite && 'offer__bookmark-button--active'} button`} type="button">
<button
className={`offer__bookmark-button ${offer.isFavorite && 'offer__bookmark-button--active'} button`}
type="button"
>
<svg className="offer__bookmark-icon" width="31" height="33">
<use xlinkHref="#icon-bookmark"></use>
</svg>
Expand All @@ -112,7 +111,7 @@ export function OfferScreen() : JSX.Element {
{offer.bedrooms} Bedrooms
</li>
<li className="offer__feature offer__feature--adults">
Max {offer.maxAdults} adults
Max {offer.maxAdults} adults
</li>
</ul>
<div className="offer__price">
Expand All @@ -133,7 +132,9 @@ export function OfferScreen() : JSX.Element {
<h2 className="offer__host-title">Meet the host</h2>
<div className="offer__host-user user">
<div className="offer__avatar-wrapper offer__avatar-wrapper--pro user__avatar-wrapper">
<img className="offer__avatar user__avatar" src={offer.host.avatarUrl} width="74" height="74" alt="Host avatar"/>
<img className="offer__avatar user__avatar" src={offer.host.avatarUrl} width="74" height="74"
alt="Host avatar"
/>
</div>
<span className="offer__user-name">
{offer.host.name}
Expand All @@ -157,15 +158,15 @@ export function OfferScreen() : JSX.Element {
</div>
<Map
city={offer.city}
offers={nearThreeOffers}
offers={nearOffers}
selectedOffer={undefined}
/>
</section>
<div className="container">
<section className="near-places places">
<h2 className="near-places__title">Other places in the neighbourhood</h2>
<div className="near-places__list places__list">
<OffersList offers={nearThreeOffers} onChange={() => {}} />
<OffersList offers={nearOffers} onChange={() => {}} />
</div>
</section>
</div>
Expand Down
7 changes: 7 additions & 0 deletions src/store/action.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
import {createAction} from '@reduxjs/toolkit';
import {City} from '../types/city.ts';
import {Offer} from '../types/offer.ts';
import {DetailOffer} from '../types/detail-offer.ts';
import {Review} from '../types/review.ts';

export const changeActiveCity = createAction<City>('offers/changeActiveCity');

export const setOffers = createAction<Offer[]>('offers/setOffers');

export const setDetailOffer = createAction<DetailOffer | null>('offers/setDetailOffer');

export const setNearOffers = createAction<Offer[]>('offers/setNearOffers');

export const setReviews = createAction<Review[]>('offers/setReviews');
40 changes: 39 additions & 1 deletion src/store/api-actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ import {AppDispatch, State} from '../types/state.ts';
import {AxiosInstance} from 'axios';
import {Offer} from '../types/offer.ts';
import {apiRoute} from '../const.ts';
import {setOffers} from './action.ts';
import {setOffers, setDetailOffer, setNearOffers, setReviews} from './action.ts';
import {DetailOffer} from '../types/detail-offer.ts';
import {Review} from '../types/review.ts';

export const fetchOffers = createAsyncThunk<void, undefined, {
dispatch: AppDispatch;
Expand All @@ -16,3 +18,39 @@ export const fetchOffers = createAsyncThunk<void, undefined, {
dispatch(setOffers(data));
}
);

export const fetchDetailOffer = createAsyncThunk<void, Offer['id'], {
dispatch: AppDispatch;
state: State;
extra: AxiosInstance;
}>(
'data/fetchDetailOffer',
async (offerId, {dispatch, extra: api}) => {
const {data} = await api.get<DetailOffer>(`${apiRoute.offers}/${offerId}`);
dispatch(setDetailOffer(data));
}
);

export const fetchNearOffers = createAsyncThunk<void, Offer['id'], {
dispatch: AppDispatch;
state: State;
extra: AxiosInstance;
}>(
'data/fetchNearOffers',
async (offerId, {dispatch, extra: api}) => {
const {data} = await api.get<Offer[]>(`${apiRoute.offers}/${offerId}/nearby`);
dispatch(setNearOffers(data));
}
);

export const fetchReviews = createAsyncThunk<void, Offer['id'], {
dispatch: AppDispatch;
state: State;
extra: AxiosInstance;
}>(
'data/fetchReviews',
async (offerId, {dispatch, extra: api}) => {
const {data} = await api.get<Review[]>(`${apiRoute.reviews}/${offerId}`);
dispatch(setReviews(data));
}
);
21 changes: 19 additions & 2 deletions src/store/reducer.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,25 @@
import {Paris} from '../const.ts';
import {createReducer} from '@reduxjs/toolkit';
import {changeActiveCity, setOffers} from './action.ts';
import {changeActiveCity, setDetailOffer, setNearOffers, setOffers, setReviews} from './action.ts';
import {City} from '../types/city.ts';
import {Offer} from '../types/offer.ts';
import {DetailOffer} from '../types/detail-offer.ts';
import {Review} from '../types/review.ts';

type InitialState = {
activeCity: City;
offers: Offer[];
detailOffer: DetailOffer | null;
nearOffers: Offer[];
reviews: Review[];
}

const initialState : InitialState = {
activeCity: Paris,
offers: []
offers: [],
detailOffer: null,
nearOffers: [],
reviews: [],
};

export const reducer = createReducer(initialState, (builder) => {
Expand All @@ -21,5 +29,14 @@ export const reducer = createReducer(initialState, (builder) => {
})
.addCase(setOffers, (state, action) => {
state.offers = action.payload;
})
.addCase(setDetailOffer, (state, action) => {
state.detailOffer = action.payload;
})
.addCase(setNearOffers, (state, action) => {
state.nearOffers = action.payload;
})
.addCase(setReviews, (state, action) => {
state.reviews = action.payload;
});
});

0 comments on commit 6688fe3

Please sign in to comment.