Skip to content

Commit

Permalink
Merge pull request #15 from fed1v/master
Browse files Browse the repository at this point in the history
  • Loading branch information
keksobot authored Jan 4, 2025
2 parents ae0869b + 6a098e3 commit 170fdc2
Show file tree
Hide file tree
Showing 33 changed files with 309 additions and 160 deletions.
12 changes: 5 additions & 7 deletions src/api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,25 @@ import axios, {AxiosError, AxiosInstance, AxiosResponse, InternalAxiosRequestCon
import {getToken} from './token.ts';
import {StatusCodes} from 'http-status-codes';
import {processErrorHandle} from './process-error-handle.ts';
import {ApiConfig} from '../consts.ts';

type DetailMessageType = {
type: string;
message: string;
}

const StatusCodeMapping: Record<number, boolean> = {
const statusCodeMapping: Record<number, boolean> = {
[StatusCodes.BAD_REQUEST]: true,
[StatusCodes.UNAUTHORIZED]: true,
[StatusCodes.NOT_FOUND]: true
};

const shouldDisplayError = (response: AxiosResponse) => !!StatusCodeMapping[response.status];

const BACKEND_URL = 'https://14.design.htmlacademy.pro/six-cities';
const REQUEST_TIMEOUT = 5000;
const shouldDisplayError = (response: AxiosResponse) => !!statusCodeMapping[response.status];

export const createAPI = (): AxiosInstance => {
const api = axios.create({
baseURL: BACKEND_URL,
timeout: REQUEST_TIMEOUT,
baseURL: ApiConfig.BackendUrl,
timeout: ApiConfig.RequestTimeout,
});

api.interceptors.request.use(
Expand Down
3 changes: 2 additions & 1 deletion src/components/app/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import {useAppSelector} from '../../hooks';
import {Spinner} from '../spinner/spinner.tsx';
import HistoryRouter from '../history-route/history-route.tsx';
import browserHistory from '../../browser-history.ts';
import {getAuthorizationStatus, getIsOffersDataLoading} from '../../store/selectors.ts';
import {getAuthorizationStatus} from '../../store/selectors/user-selectors.ts';
import {getIsOffersDataLoading} from '../../store/selectors/offers-selectors.ts';

type AppScreenProps = {
cityNames: string[];
Expand Down
6 changes: 4 additions & 2 deletions src/components/cities-list/city-item.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import {memo} from 'react';
import {Link} from 'react-router-dom';
import {AppRoute} from '../../consts.ts';

type CityItemProps = {
name: string;
Expand All @@ -13,9 +15,9 @@ export function CityItem({name, onCityClick}: CityItemProps) {
onCityClick(name);
}}
>
<a className="locations__item-link tabs__item" href="#">
<Link className="locations__item-link tabs__item" to={AppRoute.Main}>
<span>{name}</span>
</a>
</Link>
</li>
);
}
Expand Down
58 changes: 47 additions & 11 deletions src/components/comment-form/comment-form.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {ChangeEvent, FormEvent, useState} from 'react';
import {ChangeEvent, FormEvent, Fragment, useState} from 'react';
import {useAppDispatch} from '../../hooks';
import {sendCommentAction} from '../../store/api-actions.ts';

Expand All @@ -7,37 +7,58 @@ type CommentFormProps = {
}

export function CommentForm({offerId}: CommentFormProps) {
const [formData, setFormData] = useState({rating: 0, review: ''});
const [formData, setFormData] = useState({rating: '', review: ''});
const [isFormSubmitting, setIsFormSubmitting] = useState(false);
const [errorMessage, setErrorMessage] = useState('');

const dispatch = useAppDispatch();

function handleOnChangeForm(event: ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) {

function handleInputChange(event: ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) {
const {name, value} = event.currentTarget;
setFormData({...formData, [name]: value});
}

function handleOnSubmit(event: FormEvent) {
function handleFormSubmit(event: FormEvent) {
event.preventDefault();

if (!formData.rating || !formData.review) {
return;
}

setIsFormSubmitting(true);

const comment = {
offerId: offerId,
comment: formData.review,
rating: formData.rating,
};

dispatch(sendCommentAction(comment));
dispatch(sendCommentAction(comment))
.unwrap()
.then(() => {
setErrorMessage('');
setFormData({review: '', rating: ''});
})
.catch(() => {
setErrorMessage('Error. Please try again');
})
.finally(() => {
setIsFormSubmitting(false);
});
}

const rating = Number(formData.rating);

const isSubmitButtonActive = rating > 0 && formData.review.length >= 50 && formData.review.length <= 300
&& !isFormSubmitting;

return (
<form
className="reviews__form form"
action="#"
method="post"
onSubmit={handleOnSubmit}
onSubmit={handleFormSubmit}
>
<label className="reviews__label form__label" htmlFor="review">
Your review
Expand All @@ -47,14 +68,18 @@ export function CommentForm({offerId}: CommentFormProps) {
>
{
[5, 4, 3, 2, 1].map((stars) => (
<>
<Fragment
key={`${stars}-stars`}
>
<input
className="form__rating-input visually-hidden"
name="rating"
defaultValue={stars}
value={stars}
id={`${stars}-stars`}
type="radio"
onChange={handleOnChangeForm}
checked={rating === stars}
onChange={handleInputChange}
disabled={isFormSubmitting}
/>
<label
htmlFor={`${stars}-stars`}
Expand All @@ -65,7 +90,7 @@ export function CommentForm({offerId}: CommentFormProps) {
<use xlinkHref="#icon-star"/>
</svg>
</label>
</>
</Fragment>
))
}
</div>
Expand All @@ -75,8 +100,9 @@ export function CommentForm({offerId}: CommentFormProps) {
id="review"
name="review"
placeholder="Tell how was your stay, what you like and what can be improved"
onChange={handleOnChangeForm}
onChange={handleInputChange}
value={formData.review}
disabled={isFormSubmitting}
/>
<div className="reviews__button-wrapper">
<p className="reviews__help">
Expand All @@ -87,11 +113,21 @@ export function CommentForm({offerId}: CommentFormProps) {
</p>
<button
className="reviews__submit form__submit button"
disabled={!isSubmitButtonActive}
type="submit"
>
Submit
</button>
</div>

{
errorMessage &&
<p
className="error"
style={{color: 'red'}}
>{errorMessage}
</p>
}
</form>
);
}
2 changes: 1 addition & 1 deletion src/components/favorite-button/favorite-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {AppRoute, AuthorizationStatus, FavoriteType} from '../../consts.ts';
import {toggleFavoriteStatusAction} from '../../store/api-actions.ts';
import {redirectToRoute} from '../../store/action.ts';
import {useAppDispatch, useAppSelector} from '../../hooks';
import {getAuthorizationStatus} from '../../store/selectors.ts';
import {getAuthorizationStatus} from '../../store/selectors/user-selectors.ts';

type FavoriteButtonProps = {
id: string;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import {Offer} from '../../types/offer.ts';
import {MemoizedFavoritePlaceCard} from './favorite-place-card.tsx';
import {memo} from 'react';
import {Link} from 'react-router-dom';
import {AppRoute} from '../../consts.ts';

type FavoritesListProps = {
offers: Offer[];
Expand All @@ -18,9 +20,9 @@ export function FavoritePlaceCardList({offers}: FavoritesListProps) {
<li key={city} className="favorites__locations-items">
<div className="favorites__locations locations locations--current">
<div className="locations__item">
<a className="locations__item-link" href="#">
<Link className="locations__item-link" to={AppRoute.Main}>
<span>{city}</span>
</a>
</Link>
</div>
</div>

Expand Down
22 changes: 22 additions & 0 deletions src/components/favorites-content/favorites-content.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import {MemoizedFavoritePlaceCardList} from '../favorite-place-card/favorite-place-card-list.tsx';
import {Offers} from '../../types/offer.ts';

type FavoritesContentProps = {
favoriteOffers: Offers;
}

export function FavoritesContent({favoriteOffers}: FavoritesContentProps) {
return (
<main className="page__main page__main--favorites">
<div className="page__favorites-container container">
<section className="favorites">
<h1 className="favorites__title">Saved listing</h1>

<MemoizedFavoritePlaceCardList
offers={favoriteOffers}
/>
</section>
</div>
</main>
);
}
17 changes: 17 additions & 0 deletions src/components/favorites-content/favorites-empty.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
export function FavoritesEmpty() {
return (
<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>
);
}
3 changes: 2 additions & 1 deletion src/components/header/header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ import {Link} from 'react-router-dom';
import {AppRoute, AuthorizationStatus} from '../../consts.ts';
import {useAppDispatch, useAppSelector} from '../../hooks';
import {logoutAction} from '../../store/api-actions.ts';
import {getAuthorizationStatus, getFavoriteOffers, getUserData} from '../../store/selectors.ts';
import {memo, useCallback} from 'react';
import {getAuthorizationStatus, getUserData} from '../../store/selectors/user-selectors.ts';
import {getFavoriteOffers} from '../../store/selectors/offers-selectors.ts';

export function Header() {

Expand Down
12 changes: 6 additions & 6 deletions src/components/main-content/main-content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,16 @@ import {Offer, Offers} from '../../types/offer.ts';
import {Nullable} from 'vitest';

type MainContentProps = {
handleSortingOptionChange: (newSorting: Sorting) => void;
handleActiveOfferChange: (id: Nullable<string>) => void;
onSortingOptionChange: (newSorting: Sorting) => void;
onActiveOfferChange: (id: Nullable<string>) => void;
offers: Offers;
cityName: string;
activeOffer: Nullable<Offer>;
};

export function MainContent({
handleSortingOptionChange,
handleActiveOfferChange,
onSortingOptionChange,
onActiveOfferChange,
offers,
cityName,
activeOffer
Expand All @@ -30,11 +30,11 @@ export function MainContent({

<MemoizedSortOptions
options={Object.values(Sorting)}
onSortingOptionChange={handleSortingOptionChange}
onSortingOptionChange={onSortingOptionChange}
/>
<MemoizedCityPlaceCardList
offers={offers}
onActiveItemChange={handleActiveOfferChange}
onActiveItemChange={onActiveOfferChange}
/>
</section>

Expand Down
21 changes: 4 additions & 17 deletions src/components/map/map.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {memo, useEffect, useRef} from 'react';
import {useMap} from '../../hooks/use-map.ts';
import {Nullable} from 'vitest';
import {City, Location} from '../../types/city.ts';
import {CustomIcon} from '../../consts.ts';

type MapProps = {
city: City;
Expand All @@ -17,18 +18,6 @@ export function Map({city, activeCityLocation, offers, className}: MapProps) {
const mapRef = useRef<HTMLDivElement>(null);
const map = useMap(mapRef, city);

const defaultCustomIcon = leaflet.icon({
iconUrl: '/img/pin.svg',
iconSize: [40, 40],
iconAnchor: [20, 40],
});

const currentCustomIcon = leaflet.icon({
iconUrl: '/img/pin-active.svg',
iconSize: [40, 40],
iconAnchor: [20, 40],
});

useEffect(() => {
if (map) {
offers.forEach((offer) => {
Expand All @@ -38,14 +27,12 @@ export function Map({city, activeCityLocation, offers, className}: MapProps) {
lng: offer.location.longitude
}, {
icon: (offer.city.location === activeCityLocation)
? currentCustomIcon
: defaultCustomIcon
? CustomIcon.Current
: CustomIcon.Default
}).addTo(map);

});

}
}, [map, offers, city, activeCityLocation, currentCustomIcon, defaultCustomIcon]);
}, [map, offers, city, activeCityLocation]);

return (
<div
Expand Down
6 changes: 4 additions & 2 deletions src/components/place-card/place-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ export function PlaceCard({
classNamePrefix = 'near-places';
}

const capitalizedType = type[0].toUpperCase() + type.substring(1);

return (
<article
className={`${classNamePrefix}__card place-card`}
Expand Down Expand Up @@ -82,14 +84,14 @@ export function PlaceCard({

<div className="place-card__rating rating">
<div className="place-card__stars rating__stars">
<span style={{width: `${20 * rating}%`}}></span>
<span style={{width: `${20 * Math.round(rating)}%`}}></span>
<span className="visually-hidden">Rating</span>
</div>
</div>
<h2 className="place-card__name">
{title}
</h2>
<p className="place-card__type">{type}</p>
<p className="place-card__type">{capitalizedType}</p>
</div>
</Link>

Expand Down
Loading

0 comments on commit 170fdc2

Please sign in to comment.