Skip to content

Commit

Permalink
Merge pull request #11 from Mayanzev/module7-task3
Browse files Browse the repository at this point in the history
  • Loading branch information
keksobot authored May 11, 2024
2 parents aebb601 + 1bc7fc3 commit 7289f55
Show file tree
Hide file tree
Showing 20 changed files with 314 additions and 213 deletions.
10 changes: 9 additions & 1 deletion src/components/app/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ 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';


function App(): JSX.Element {
Expand All @@ -35,7 +36,14 @@ function App(): JSX.Element {
</PrivateRoute>
}
/>
<Route path={AppRoute.Login} element={<LoginScreen />} />
<Route
path={AppRoute.Login}
element={
<MainRouteRedirection >
<LoginScreen />
</MainRouteRedirection>
}
/>
<Route path={AppRoute.Offer} element={<OfferScreen />} />
</Routes>
</HistoryRouter>
Expand Down
13 changes: 7 additions & 6 deletions src/components/city-list/city-list.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,25 @@
import { City } from '../../types/location';
import { useAppDispatch, useAppSelector } from '../../hooks';
import { changeCity } from '../../store/action';
import { CITIES } from '../../const';
import { cities } from '../../const';


function CityList(): JSX.Element {
const chosenCity = useAppSelector((state) => state.city);
const dispatch = useAppDispatch();
const handleCityChange = (city: City) => {
const handleCityChange = (city: string) => {
dispatch(changeCity(city));
};


return(
<ul className="locations__list tabs__list">
{CITIES.map((city) => (
<li className="locations__item" key={city.name}>
{Object.keys(cities).map((city) => (
<li className="locations__item" key={city}>
<a className={`locations__item-link tabs__item ${(city === chosenCity) ? 'tabs__item--active' : ''}`} onClick={() => {
handleCityChange(city);
}}
>
<span>{city.name}</span>
<span>{city}</span>
</a>
</li>
))}
Expand Down
40 changes: 30 additions & 10 deletions src/components/comment-form/comment-form.tsx
Original file line number Diff line number Diff line change
@@ -1,51 +1,71 @@
import { useState } from 'react';
import { useAppDispatch, useAppSelector } from '../../hooks';
import { postReviewAction } from '../../store/api-actions';

function CommentForm(): JSX.Element {
const [formData, setFormData] = useState({
rating: '1',
rating: null,
review: '',
});
const handleFieldChange = (evt: React.ChangeEvent<HTMLInputElement|HTMLTextAreaElement>): void => {
const {name, value} = evt.target;
const handleFieldChange = (evt: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>): void => {
const { name, value } = evt.target;
setFormData({
...formData,
[name]: value
});
};

const id = useAppSelector((state) => state.chosenOffer?.id);

const MINIMUM_COMMENT_CHARACTERS = 50;
const MAXIMUM_COMMENT_CHARACTERS = 300;

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

const dispatch = useAppDispatch();

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

return (
<form className="reviews__form form" action="#" method="post">
<form className="reviews__form form" method="post">
<label className="reviews__label form__label" htmlFor="review">Your review</label>
<div className="reviews__rating-form form__rating">
<input className="form__rating-input visually-hidden" name="rating" value="5" id="5-stars" type="radio" onChange={handleFieldChange}/>
<input className="form__rating-input visually-hidden" name="rating" value="5" id="5-stars" type="radio" onChange={handleFieldChange} />
<label htmlFor="5-stars" className="reviews__rating-label form__rating-label" title="perfect">
<svg className="form__star-image" width="37" height="33">
<use xlinkHref="#icon-star"></use>
</svg>
</label>

<input className="form__rating-input visually-hidden" name="rating" value="4" id="4-stars" type="radio" onChange={handleFieldChange}/>
<input className="form__rating-input visually-hidden" name="rating" value="4" id="4-stars" type="radio" onChange={handleFieldChange} />
<label htmlFor="4-stars" className="reviews__rating-label form__rating-label" title="good">
<svg className="form__star-image" width="37" height="33">
<use xlinkHref="#icon-star"></use>
</svg>
</label>

<input className="form__rating-input visually-hidden" name="rating" value="3" id="3-stars" type="radio" onChange={handleFieldChange}/>
<input className="form__rating-input visually-hidden" name="rating" value="3" id="3-stars" type="radio" onChange={handleFieldChange} />
<label htmlFor="3-stars" className="reviews__rating-label form__rating-label" title="not bad">
<svg className="form__star-image" width="37" height="33">
<use xlinkHref="#icon-star"></use>
</svg>
</label>

<input className="form__rating-input visually-hidden" name="rating" value="2" id="2-stars" type="radio" onChange={handleFieldChange}/>
<input className="form__rating-input visually-hidden" name="rating" value="2" id="2-stars" type="radio" onChange={handleFieldChange} />
<label htmlFor="2-stars" className="reviews__rating-label form__rating-label" title="badly">
<svg className="form__star-image" width="37" height="33">
<use xlinkHref="#icon-star"></use>
</svg>
</label>

<input className="form__rating-input visually-hidden" name="rating" value="1" id="1-star" type="radio" onChange={handleFieldChange}/>
<input className="form__rating-input visually-hidden" name="rating" value="1" id="1-star" type="radio" onChange={handleFieldChange} />
<label htmlFor="1-star" className="reviews__rating-label form__rating-label" title="terribly">
<svg className="form__star-image" width="37" height="33">
<use xlinkHref="#icon-star"></use>
Expand All @@ -57,7 +77,7 @@ function CommentForm(): JSX.Element {
<p className="reviews__help">
To submit review please make sure to set <span className="reviews__star">rating</span> and describe your stay with at least <b className="reviews__text-amount">50 characters</b>.
</p>
<button className="reviews__submit form__submit button" type="submit" disabled >Submit</button>
<button className="reviews__submit form__submit button" type="button" onClick={submitHandle} disabled={isSubmitInvalid} >Submit</button>
</div>
</form>
);
Expand Down
16 changes: 16 additions & 0 deletions src/components/main-route-redirection/main-route-redirection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Navigate } from 'react-router-dom';
import { AppRoute, AuthorizationStatus } from '../../const';
import { useAppSelector } from '../../hooks';

type MainRouteRedirectionProps = {
children: JSX.Element;
}

function MainRouteRedirection(props: MainRouteRedirectionProps): JSX.Element {
const {children} = props;
const authorizationStatus = useAppSelector((state) => state.authorizationStatus);
return (
authorizationStatus === AuthorizationStatus.NoAuth ? children : <Navigate to={AppRoute.Main}/>
);
}
export default MainRouteRedirection;
31 changes: 23 additions & 8 deletions src/components/map/map.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import React, { useEffect } from 'react';
import { useEffect, useRef } from 'react';
import { Icon, Marker, layerGroup } from 'leaflet';
import useMap from '../../hooks/use-map';
import { Point } from '../../types/location';
import { City, Point } from '../../types/location';
import 'leaflet/dist/leaflet.css';
import { useAppSelector } from '../../hooks';
import { URL_MARKER_CURRENT, URL_MARKER_STANDART } from '../../const';

type MapProps = {
points: Point[];
city: City;
}

const currentIcon = new Icon({
Expand All @@ -23,16 +24,28 @@ const standartIcon = new Icon({
});

function Map(props: MapProps): JSX.Element {
const city = useAppSelector((state) => state.city);
const highlightedMarker = useAppSelector((state) => state.highlightedMarker);

const {points} = props;
const mapRef = React.useRef(null);
const { points, city } = props;
const mapRef = useRef(null);
const map = useMap(mapRef, city);

const currentPoint = useAppSelector((state) => state.chosenOffer?.location);

useEffect(() => {
if(map) {
if (map) {
const markerLayer = layerGroup().addTo(map);

if (currentPoint) {
const marker = new Marker({
lat: currentPoint.latitude,
lng: currentPoint.longitude,
});
marker
.setIcon(currentIcon)
.addTo(markerLayer);
}

points.forEach((point) => {
const marker = new Marker({
lat: point.latitude,
Expand All @@ -48,14 +61,16 @@ function Map(props: MapProps): JSX.Element {
.setIcon(icon)
.addTo(markerLayer);
});

map.setView([city.location.latitude, city.location.longitude], city.location.zoom);

return () => {
map.removeLayer(markerLayer);
};
}
}, [map, points, highlightedMarker, city]);
}, [map, points, highlightedMarker, city, currentPoint]);

return <div style={{height: '100%'}} ref={mapRef}></div>;
return <div style={{ height: '100%' }} ref={mapRef}></div>;
}

export default Map;
17 changes: 13 additions & 4 deletions src/components/offer-card/offer-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@ import { changeHighlightedMarker } from '../../store/action';
import { Offer } from '../../types/offer';
import { Link } from 'react-router-dom';
import { ratingPercentage } from '../../utils';
import { fetchNearbyAction, fetchOfferAction, fetchReviewsAction } from '../../store/api-actions';

type OfferProps = {
offer: Offer;
cardType: string;
}

function OfferCard({offer, cardType}: OfferProps): JSX.Element {
function OfferCard({ offer, cardType }: OfferProps): JSX.Element {
const dispatch = useAppDispatch();
return (
<article className={cardType}
Expand All @@ -23,7 +24,7 @@ function OfferCard({offer, cardType}: OfferProps): JSX.Element {
) : null}
<div className="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="260" height="200" alt="Place image" />
</a>
</div>
<div className="place-card__info">
Expand All @@ -41,12 +42,20 @@ function OfferCard({offer, cardType}: OfferProps): JSX.Element {
</div>
<div className="place-card__rating rating">
<div className="place-card__stars rating__stars">
<span style={{width: ratingPercentage(offer.rating)}}></span>
<span style={{ width: ratingPercentage(offer.rating) }}></span>
<span className="visually-hidden">Rating</span>
</div>
</div>
<h2 className="place-card__name">
<Link to={`/offer/${offer.id}`}>{offer.title}</Link>
<Link to={`/offer/${offer.id}`}
onClick={() => {
dispatch(fetchOfferAction(offer.id));
dispatch(fetchReviewsAction(offer.id));
dispatch(fetchNearbyAction(offer.id));
}}
>
{offer.title}
</Link>
</h2>
<p className="place-card__type">{offer.type}</p>
</div>
Expand Down
2 changes: 1 addition & 1 deletion src/components/private-route/private-route.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {Navigate} from 'react-router-dom';
import { Navigate } from 'react-router-dom';
import { AppRoute, AuthorizationStatus } from '../../const';
import { useAppSelector } from '../../hooks';

Expand Down
4 changes: 2 additions & 2 deletions src/components/reviews-item/reviews-item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ function ReviewItem({review}: ReviewProps): JSX.Element {
<li className="reviews__item">
<div className="reviews__user user">
<div className="reviews__avatar-wrapper user__avatar-wrapper">
<img className="reviews__avatar user__avatar" src={review.avatar} width="54" height="54" alt="Reviews avatar"/>
<img className="reviews__avatar user__avatar" src={review.user.avatarUrl} width="54" height="54" alt="Reviews avatar"/>
</div>
<span className="reviews__user-name">
{review.author}
{review.user.name}
</span>
</div>
<div className="reviews__info">
Expand Down
5 changes: 4 additions & 1 deletion src/components/reviews-list/reviews-list.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import { Review } from '../../types/review';
import ReviewItem from '../reviews-item/reviews-item';
import CommentForm from '../../components/comment-form/comment-form';
import { AuthorizationStatus } from '../../const';
import { useAppSelector } from '../../hooks';

type ReviewsListProps = {
reviews: Review[];
};

function ReviewsList({ reviews }: ReviewsListProps): JSX.Element {
const authorizationStatus = useAppSelector((state) => state.authorizationStatus);
return (
<section className="offer__reviews reviews">
<h2 className="reviews__title">Reviews &middot; <span className="reviews__amount">{reviews.length}</span></h2>
Expand All @@ -15,7 +18,7 @@ function ReviewsList({ reviews }: ReviewsListProps): JSX.Element {
<ReviewItem key={review.id} review={review} />
))}
</ul>
<CommentForm />
{authorizationStatus === AuthorizationStatus.Auth && <CommentForm />}
</section>
);
}
Expand Down
64 changes: 11 additions & 53 deletions src/const.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { City } from './types/location';

export enum AppRoute {
Main = '/',
Login = '/login',
Expand All @@ -22,58 +20,18 @@ export const URL_MARKER_STANDART =
export enum APIRoute {
Offers = '/offers',
Login = '/login',
Logout = '/logout'
Logout = '/logout',
Comments = '/comments',
Nearby = '/nearby'
}

export const CITIES: City[] = [
{
name: 'Paris',
location: {
latitude: 48.864716,
longitude: 2.349014,
zoom: 13,
},
},
{
name: 'Brussels',
location: {
latitude: 50.85034,
longitude: 4.35171,
zoom: 13,
},
},
{
name: 'Cologne',
location: {
latitude: 50.935173,
longitude: 6.953101,
zoom: 13,
},
},
{
name: 'Amsterdam',
location: {
latitude: 52.3740300,
longitude: 4.8896900,
zoom: 13,
},
},
{
name: 'Hamburg',
location: {
latitude: 53.551086,
longitude: 9.993682,
zoom: 13,
},
},
{
name: 'Dusseldorf',
location: {
latitude: 51.233334,
longitude: 6.783333,
zoom: 13,
},
},
];
export const cities = {
Paris: 'Paris',
Cologne: 'Cologne',
Brussels: 'Brussels',
Amsterdam: 'Amsterdam',
Hamburg: 'Hamburg',
Dusseldorf: 'Dusseldorf',
};

export const TIMEOUT_SHOW_ERROR = 2000;
Loading

0 comments on commit 7289f55

Please sign in to comment.