Skip to content

Commit

Permalink
Merge pull request #13 from Mayanzev/module8-task2
Browse files Browse the repository at this point in the history
  • Loading branch information
keksobot authored May 23, 2024
2 parents c67d48b + b209b35 commit 1efe9af
Show file tree
Hide file tree
Showing 23 changed files with 345 additions and 185 deletions.
7 changes: 4 additions & 3 deletions src/components/app/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import FavoritesScreen from '../../pages/favorites-screen/favorites-screen.tsx';
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 { AppRoute, AuthorizationStatus } from '../../const.ts';
import { useAppSelector } from '../../hooks/index.ts';
import LoadingScreen from '../../pages/loading-screen/loading-screen.tsx';
import HistoryRouter from '../history-route/history-route.tsx';
Expand All @@ -16,15 +16,16 @@ import { getIsOffersDataLoading } from '../../store/offers-data/selectors.ts';


function App(): JSX.Element {

const isOffersDataLoading = useAppSelector(getIsOffersDataLoading);
const authorizationStatus = useAppSelector(getAuthorizationStatus);
const isFavoriteOffersDataLoading = useAppSelector(getIsOffersDataLoading);

if (isOffersDataLoading || !authorizationStatus) {
if (isOffersDataLoading || authorizationStatus === AuthorizationStatus.Unknown || isFavoriteOffersDataLoading) {
return (
<LoadingScreen />
);
}

return (
<HistoryRouter history={browserHistory}>
<Routes>
Expand Down
60 changes: 60 additions & 0 deletions src/components/change-favorite-button/change-favorite-button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { useEffect, useState } from 'react';
import { useAppDispatch, useAppSelector } from '../../hooks';
import { postFavoriteAction } from '../../store/api-actions';
import { getFavoriteOffersId } from '../../store/favorite-process/selectors';
import { changeFavoritesId } from '../../store/favorite-process/favorite-process';
import { AppRoute, AuthorizationStatus } from '../../const';
import { redirectToRoute } from '../../store/action';
import { getAuthorizationStatus } from '../../store/user-process/selectors';

type ChangeFavoriteButtonProps = {
offerId: string;
typeButton: string;
width: string;
height: string;
};

function ChangeFavoriteButton({ offerId, typeButton, width, height }: ChangeFavoriteButtonProps): JSX.Element {
const dispatch = useAppDispatch();
const favoritesOffersId = useAppSelector(getFavoriteOffersId);
const status = useAppSelector(getAuthorizationStatus);
const [isFavorite, setIsFavorite] = useState(favoritesOffersId.includes(offerId));
const [isSubmitting, setIsSubmitting] = useState(false);

useEffect(() => {
if (!isSubmitting) {
setIsFavorite(favoritesOffersId.includes(offerId));
}
}, [favoritesOffersId, offerId, isSubmitting, dispatch]);

const handleFavorite = () => {
if (status === AuthorizationStatus.NoAuth) {
dispatch(redirectToRoute(AppRoute.Login));
} else {
setIsSubmitting(true);
const updatedFavorites = isFavorite ? favoritesOffersId.filter((id) => id !== offerId) : [...favoritesOffersId, offerId];
dispatch(changeFavoritesId(updatedFavorites));
setIsFavorite(!isFavorite);
dispatch(postFavoriteAction({ id: offerId, status: isFavorite ? 0 : 1 }))
.then(() => setIsSubmitting(false))
.catch(() => {
setIsSubmitting(false);
});
}
};
return (
<button
className={isFavorite ? `${typeButton}__bookmark-button ${typeButton}__bookmark-button--active button` : `${typeButton}__bookmark-button button`}
type="button"
onClick={handleFavorite}
disabled={isSubmitting}
>
<svg className={`${typeButton}__bookmark-icon`} width={width} height={height}>
<use xlinkHref="#icon-bookmark"></use>
</svg>
<span className="visually-hidden">To bookmarks</span>
</button>
);
}

export default ChangeFavoriteButton;
21 changes: 12 additions & 9 deletions src/components/comment-form/comment-form.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Fragment, useState } from 'react';
import { Fragment, useEffect, useState } from 'react';
import { useAppDispatch, useAppSelector } from '../../hooks';
import { postReviewAction } from '../../store/api-actions';
import { getChosenOffer } from '../../store/offer-data/selectors';
import { getChosenOffer, getIsCommentPosting, getIsCommentRejected } from '../../store/offer-data/selectors';

function CommentForm(): JSX.Element {
const [formData, setFormData] = useState({
Expand All @@ -20,14 +20,17 @@ function CommentForm(): JSX.Element {

const MINIMUM_COMMENT_CHARACTERS = 50;
const MAXIMUM_COMMENT_CHARACTERS = 300;
const isCommentPosting = useAppSelector(getIsCommentPosting);

const isSubmitInvalid = (
formData.review.length < MINIMUM_COMMENT_CHARACTERS ||
formData.review.length > MAXIMUM_COMMENT_CHARACTERS ||
formData.rating === null
formData.rating === null ||
isCommentPosting
);

const dispatch = useAppDispatch();
const isCommentRejected = useAppSelector(getIsCommentRejected);

const resetForm = () => {
setFormData({
Expand All @@ -36,14 +39,14 @@ function CommentForm(): JSX.Element {
});
};

useEffect(() => {
if (!isCommentRejected) {
resetForm();
}
}, [isCommentRejected]);

const submitHandle = () => {
dispatch(postReviewAction({ id: id ? id : '', comment: formData.review, rating: Number(formData.rating) }));
setFormData((prevState) => ({
...prevState,
rating: null,
review: ''
}));
resetForm();
};

const ratingTitles: { [key: number]: string } = {
Expand Down
10 changes: 6 additions & 4 deletions src/components/login-navigation/login-navigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,20 @@ 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 { getFavoriteOffersId } 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(getFavoriteOffersId);

return (
<nav className="header__nav">
{authorizationStatus === AuthorizationStatus.Auth ? (
Expand All @@ -23,7 +25,7 @@ function LoginNavigation(): JSX.Element {
<div className="header__avatar-wrapper user__avatar-wrapper">
</div>
<span className="header__user-name user__name">{userData?.email}</span>
<span className="header__favorite-count">{favoriteOffers.length}</span>
<span className="header__favorite-count">{favoriteNumber.length}</span>
</Link>
</li>
<li className="header__nav-item">
Expand Down
30 changes: 17 additions & 13 deletions src/components/offer-card/offer-card.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
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';
import ChangeFavoriteButton from '../change-favorite-button/change-favorite-button';

type OfferProps = {
offer: Offer;
Expand All @@ -13,32 +14,35 @@ type OfferProps = {
function OfferCard({ offer, cardType }: OfferProps): JSX.Element {
const dispatch = useAppDispatch();
return (
<article className={cardType}
onMouseEnter={() => dispatch(changeHighlightedMarker(offer.location))}
onMouseLeave={() => dispatch(changeHighlightedMarker(undefined))}
<article
className={cardType}
{...(cardType === listToCard.get(typeOfCardList.standart) && {
onMouseEnter: () => dispatch(changeHighlightedMarker(offer.location)),
onMouseLeave: () => dispatch(changeHighlightedMarker(undefined))
})}
>
{offer.isPremium ? (
<div className="place-card__mark">
<span>Premium</span>
</div>
) : null}
<div className="cities__image-wrapper place-card__image-wrapper">
<div className={`${cardType === listToCard.get(typeOfCardList.favourites) ? 'favorites' : 'cities'}__image-wrapper place-card__image-wrapper`}>
<a href="#">
<img className="place-card__image" src={offer.previewImage} width="260" height="200" alt="Place image" />
<img className="place-card__image" src={offer.previewImage} width={cardType === listToCard.get(typeOfCardList.favourites) ? '150' : '260'} height={cardType === listToCard.get(typeOfCardList.favourites) ? '110' : '200'} alt="Place image" />
</a>
</div>
<div className="place-card__info">
<div className={(cardType === listToCard.get(typeOfCardList.favourites)) ? 'favorites__card-info place-card__info' : 'place-card__info'}>
<div className="place-card__price-wrapper">
<div className="place-card__price">
<b className="place-card__price-value">&euro;{offer.price}</b>
<span className="place-card__price-text">&#47;&nbsp;night</span>
</div>
<button className={offer.isFavorite ? 'place-card__bookmark-button place-card__bookmark-button--active button' : 'place-card__bookmark-button button'} type="button">
<svg className="place-card__bookmark-icon" width="18" height="19">
<use xlinkHref="#icon-bookmark"></use>
</svg>
<span className="visually-hidden">To bookmarks</span>
</button>
<ChangeFavoriteButton
offerId={offer.id}
typeButton='place-card'
width='18'
height='19'
/>
</div>
<div className="place-card__rating rating">
<div className="place-card__stars rating__stars">
Expand Down
6 changes: 4 additions & 2 deletions src/const.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ export enum APIRoute {
Login = '/login',
Logout = '/logout',
Comments = '/comments',
Nearby = '/nearby'
Nearby = '/nearby',
Favorite = '/favorite'
}

export const cities = {
Expand All @@ -40,5 +41,6 @@ export enum NameSpace {
Offer = 'OFFER',
Offers = 'OFFERS',
User = 'USER',
Common = 'COMMON'
Common = 'COMMON',
Favorite = 'FAVORITE'
}
3 changes: 2 additions & 1 deletion src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import ReactDOM from 'react-dom/client';
import App from './components/app/app';
import { Provider } from 'react-redux';
import { store } from './store';
import { checkAuthAction, fetchOffersAction } from './store/api-actions';
import { checkAuthAction, fetchFavoriteAction, fetchOffersAction } from './store/api-actions';
import ErrorMessage from './components/error-message/error-message';

const root = ReactDOM.createRoot(
Expand All @@ -12,6 +12,7 @@ const root = ReactDOM.createRoot(

store.dispatch(checkAuthAction());
store.dispatch(fetchOffersAction());
store.dispatch(fetchFavoriteAction());

root.render(
<React.StrictMode>
Expand Down
20 changes: 20 additions & 0 deletions src/pages/empty-offers/empty-offers.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
type EmptyOffersProps = {
city: string;
}

function EmptyOffers({city}: EmptyOffersProps): JSX.Element {
return (
<div className="cities">
<div className="cities__places-container cities__places-container--empty container">
<section className="cities__no-places">
<div className="cities__status-wrapper tabs__content">
<b className="cities__status">No places to stay available</b>
<p className="cities__status-description">We could not find any property available at the moment in {city}</p>
</div>
</section>
<div className="cities__right-section" />
</div>
</div>
);
}
export default EmptyOffers;
72 changes: 51 additions & 21 deletions src/pages/favorites-screen/favorites-screen.tsx
Original file line number Diff line number Diff line change
@@ -1,36 +1,66 @@
import { Link } from 'react-router-dom';
import { typeOfCardList } from '../../utils';
import OfferList from '../../components/offer-list/offer-list';
import { useAppSelector } from '../../hooks';
import { useAppDispatch, 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';
import { changeCity } from '../../store/common-data/common-data';
import { redirectToRoute } from '../../store/action';


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));
const favoriteOffersCitiesSet = new Set(favoriteOffers.map((of) => of.city.name));
const favoriteOffersCities = Array.from(favoriteOffersCitiesSet);
const favorites = useAppSelector(getFavoriteOffersId);
const dispatch = useAppDispatch();

const handleCityClick = (city: string) => {
dispatch(changeCity(city));
dispatch(redirectToRoute(AppRoute.Main));
};

return (
<div className="page">
<Header />
<main className="page__main page__main--favorites">
<div className="page__favorites-container container">
<section className="favorites">
<h1 className="favorites__title">Saved listing</h1>
<ul className="favorites__list">
<li className="favorites__locations-items">
<div className="favorites__locations locations locations--current">
<div className="locations__item">
<a className="locations__item-link">
<span>Amsterdam</span>
</a>
</div>
</div>
<OfferList offers={favoriteOffers} listType={typeOfCardList.favourites} />
</li>
</ul>
</section>
</div>
</main>
{favorites.length !== 0 ? (
<main className="page__main page__main--favorites">
<div className="page__favorites-container container">
<section className="favorites">
<h1 className="favorites__title">Saved listing</h1>
<ul className="favorites__list">
{favoriteOffersCities.map((city) => (
<li className="favorites__locations-items" key={city}>
<div className="favorites__locations locations locations--current">
<div className="locations__item">
<a className="locations__item-link" onClick={() => handleCityClick(city)}>
<span>{city}</span>
</a>
</div>
</div>
<OfferList offers={favoriteOffers.filter((o) => o.city.name === city)} listType={typeOfCardList.favourites} />
</li>
))}
</ul>
</section>
</div>
</main>
) : (
<main className="page__main page__main--favorites page__main--favorites-empty">
<div className="page__favorites-container container">
<section className="favorites favorites--empty">
<h1 className="visually-hidden">Favorites (empty)</h1>
<div className="favorites__status-wrapper">
<b className="favorites__status">Nothing yet saved.</b>
<p className="favorites__status-description">Save properties to narrow down search or plan your future trips.</p>
</div>
</section>
</div>
</main>
)}
<footer className="footer container">
<Link to={AppRoute.Main} className="header__logo-link">
<img className="footer__logo" src="img/logo.svg" alt="6 cities logo" width="64" height="33" />
Expand Down
Loading

0 comments on commit 1efe9af

Please sign in to comment.