Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Контроль и ограничения (часть 2) #8

Merged
merged 3 commits into from
Mar 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 5 additions & 10 deletions src/components/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,24 @@ import FavoritePage from '../pages/favorite-page';
import LoginPage from '../pages/login-page';
import { Route, Routes } from 'react-router-dom';
import { AppRoute, authorizationStatus } from '../const';
import NotFoundPage from '../pages/not-found-page';
import NotFoundPage from '../pages/not-found-page/not-found-page';
import ProtectedRoute from './protected-route';
import { Offer } from '../types/offer';

type TAppProps = {
offers: Offer[];
}

export default function App({ offers }: TAppProps) {
export default function App() {
return (
<Routes>
<Route path='/'>
<Route
index
element={<MainPage offers={offers} />}
element={<MainPage />}
/>
<Route
path={`/${AppRoute.Offer}/:offerId`}
element={<OfferPage offers={offers} />}
element={<OfferPage />}
/>
<Route
path={AppRoute.Favorites}
element={<ProtectedRoute hasAccess={authorizationStatus.AUTH}> <FavoritePage offers={offers} /></ProtectedRoute>}
element={<ProtectedRoute hasAccess={authorizationStatus.AUTH}> <FavoritePage /></ProtectedRoute>}
/>
<Route
path={AppRoute.Login}
Expand Down
38 changes: 24 additions & 14 deletions src/components/card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,40 +9,50 @@
width: string;
height: string;
};
handelPointCardMouseOver?: (currentOffer: OfferPreviews | null) => void;
}

export default function Card({ offer, optionCard }: TCardProps) {
export default function Card({ offer, optionCard, handelPointCardMouseOver }: TCardProps) {
const { width, height, classCard } = optionCard;

const onPointCardMouseOver = () => {
handelPointCardMouseOver(offer);

Check failure on line 19 in src/components/card.tsx

View workflow job for this annotation

GitHub Actions / build

Cannot invoke an object which is possibly 'undefined'.
};

const onPointCardMouseLeave = () => {
handelPointCardMouseOver(null);

Check failure on line 23 in src/components/card.tsx

View workflow job for this annotation

GitHub Actions / build

Cannot invoke an object which is possibly 'undefined'.
};

return (
<article className={`${classCard} place-card`} data-id={offer.id}>
<article className={`${classCard} place-card`} onMouseOver={onPointCardMouseOver} onMouseLeave={onPointCardMouseLeave}>
{offer.isPremium &&
<div className="place-card__mark">
<span>Premium</span>
</div>}

<div className="cities__image-wrapper place-card__image-wrapper">
<Link to={`/${AppRoute.Offer}/${offer.id}`}>
<img className="place-card__image" src={`${offer.previewImage}`} width={width} height={height} alt="Place image" data-id={offer.id} />
<img className="place-card__image" src={`${offer.previewImage}`} width={width} height={height} alt="Place image" />
</Link>
</div>
<div className="place-card__info" data-id={offer.id}>
<div className="place-card__price-wrapper" data-id={offer.id}>
<div className="place-card__price" data-id={offer.id}>
<b className="place-card__price-value" data-id={offer.id}>&euro;{offer.price}</b>
<span className="place-card__price-text" data-id={offer.id}>&#47;&nbsp;night</span>
<div className="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="place-card__bookmark-button button" type="button" data-id={offer.id}>
<svg className="place-card__bookmark-icon" width="18" height="19" data-id={offer.id}>
<button className="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>
</div>
<Rating ratingClass="place-card" rating={offer.rating} data-id={offer.id} />
<h2 className="place-card__name" data-id={offer.id}>
<Link to={`/${AppRoute.Offer}/${offer.id}`} state={offer} data-id={offer.id}>{offer.title}</Link>
<Rating ratingClass="place-card" rating={offer.rating} />
<h2 className="place-card__name" >
<Link to={`/${AppRoute.Offer}/${offer.id}`} state={offer} >{offer.title}</Link>
</h2>
<p className="place-card__type" data-id={offer.id}>{offer.type}</p>
<p className="place-card__type" >{offer.type}</p>
</div>
</article>
);
Expand Down
21 changes: 7 additions & 14 deletions src/components/list-cards.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,23 @@
import { MouseEvent, useState } from 'react';
import { optionCard } from '../const';
import { OptionCard } from '../const';
import { Offer } from '../types/offer';
import Card from './card';
import { OfferPreviews } from '../types/offer-preview';

type ListOfferNearbyProps = {
offers: Offer[];
onListItemHover: (currentCard: Offer) => void;
onListItemHover: (currentCard: OfferPreviews | null) => void;
extraClass: string;
}

export default function ListCards({ offers, onListItemHover, extraClass }: ListOfferNearbyProps) {
const [cardId, setCardId] = useState('');

const onPointingCardMouseOver = ({ target }: MouseEvent<HTMLDivElement>) => {
setCardId(target.dataset.id);
const handelPointingCardMouseOver = (offer: OfferPreviews | null) => {
onListItemHover(offer);
};

const currentCard = offers.find((offer) => offer.id === cardId);

if (currentCard !== undefined) {
onListItemHover(currentCard);
}

return (
<div className={extraClass} onMouseOver={onPointingCardMouseOver}>
{offers.map((offer) => <Card key={offer.id} optionCard={optionCard.CITIES_CARD} offer={offer} />)}
<div className={extraClass}>
{offers.map((offer) => <Card key={offer.id} optionCard={OptionCard.CITIES_CARD} offer={offer} handelPointCardMouseOver={handelPointingCardMouseOver} />)}
</div>
);
}
16 changes: 16 additions & 0 deletions src/components/list-location.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import Location from './location';
import { SyntheticEvent } from 'react';

type ListLocation = {
listLocations: string[];
handleCurrentCityClick: (evt: SyntheticEvent<HTMLSpanElement>) => void;
currentCity: string;
}

export default function ListLocation({ listLocations, handleCurrentCityClick, currentCity }: ListLocation) {
return (
<ul className="locations__list tabs__list">
{listLocations.map((location) => <Location key={location} city={location} isActive={currentCity === location} handleCurrentCityClick={handleCurrentCityClick} />)}
</ul>
);
}
18 changes: 18 additions & 0 deletions src/components/location.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { SyntheticEvent } from 'react';

type LocationProps = {
city: string;
isActive: boolean;
handleCurrentCityClick: (evt: SyntheticEvent<HTMLSpanElement>) => void;
}

export default function Location({ city, isActive, handleCurrentCityClick }: LocationProps) {
return (
<li className='locations__item'>
<a className={`locations__item-link tabs__item ${isActive ? 'tabs__item--active' : ''}`} href="#">
<span onClick={handleCurrentCityClick}>{city}</span>
</a>
</li>
);
}

19 changes: 14 additions & 5 deletions src/components/map.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ import { useEffect, useRef } from 'react';
import { URL_MARKER_DEFAULT, URL_MARKER_CURRENT } from '../const';
import { City } from '../types/city';
import useMap from '../hooks/useMap';
import { OfferPreviews } from '../types/offer-preview';
import { MapSize } from '../const';

type MapProps = {
city: City;
offers: Offer[];
selectedOffer: Offer | undefined;
selectedOffer: OfferPreviews | null;
};

const defaultCustomIcon = leaflet.icon({
Expand All @@ -32,24 +34,31 @@ export default function Map(props: MapProps): JSX.Element {

useEffect(() => {
if (map) {
const markerLayer = leaflet.layerGroup().addTo(map);
offers.forEach((offer) => {
leaflet.marker({
lat: offer.location.latitude,
lng: offer.location.longitude
}, {
icon: (selectedOffer !== undefined && offer.title === selectedOffer.title)
icon: (selectedOffer !== null && offer.title === selectedOffer.title)
? currentCustomIcon
: defaultCustomIcon,
})
.addTo(map);
.addTo(markerLayer);
});
return () => {
map.removeLayer(markerLayer);
mapRef.current = null;
};
}
}, [map, offers, selectedOffer]);


}, [map, offers, selectedOffer, city]);

return (
<div
ref={mapRef}
style={{ width: '100%', height: '100%' }}
style={{ width: MapSize.WIDTH, height: MapSize.HEIGHT }}
>

</div>
Expand Down
17 changes: 17 additions & 0 deletions src/components/places-option.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { SyntheticEvent } from 'react';

type PlacesOptionProps = {
place: string;
handelSortOfferClick: (sortType: string) => void;
}

export default function PlacesOption({ place, handelSortOfferClick }: PlacesOptionProps) {
const onSortOfferClick = (evt: SyntheticEvent<HTMLLIElement>) => {
if (evt.currentTarget.textContent !== null) {
handelSortOfferClick(evt.currentTarget.textContent);
}
};
return (
<li className="places__option" tabIndex={0} onClick={onSortOfferClick}>{place}</li>
);
}
15 changes: 15 additions & 0 deletions src/components/places-options.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import PlacesOption from './places-option';
import { placesOption } from '../const';

type PlacesOptionsProps = {
isOpen: boolean;
handelSortOfferClick: (sortType: string) => void;
}

export default function PlacesOptions({ isOpen, handelSortOfferClick }: PlacesOptionsProps) {
return (
<ul className={`places__options places__options--custom ${isOpen && 'places__options--opened'}`}>
{placesOption.map((place) => <PlacesOption key={place} place={place} handelSortOfferClick={handelSortOfferClick} />)}
</ul>
);
}
12 changes: 10 additions & 2 deletions src/const.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const AuthorizationStatus = {
NO_AUTH: false,
};

const optionCard = {
const OptionCard = {
CITIES_CARD: {
classCard: 'cities__card',
width: '260',
Expand All @@ -23,16 +23,24 @@ const optionCard = {
}
};

const MapSize = {
WIDTH: '100%',
HEIGHT: '100%',
};


const OptionListCard = {
FAVORITES_CARD: 'near-places__list places__list',
CITIES_CARD: 'cities__places-list places__list tabs__content',
};

const placesOption = ['Popular', 'Price: low to high', 'Price: high to low', 'Top rated first',];

const URL_MARKER_DEFAULT = '../markup/img/pin.svg';

const URL_MARKER_CURRENT = '../markup/img/pin-active.svg';


const CountStar: number = 5;

export { CountStar, AppRoute, AuthorizationStatus as authorizationStatus, optionCard, URL_MARKER_DEFAULT, URL_MARKER_CURRENT, OptionListCard };
export { CountStar, AppRoute, AuthorizationStatus as authorizationStatus, OptionCard, URL_MARKER_DEFAULT, URL_MARKER_CURRENT, OptionListCard, MapSize, placesOption };
9 changes: 9 additions & 0 deletions src/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
import type { State, AppDispatch } from '../types/state';

const useAppDispatch = () => useDispatch<AppDispatch>();

const useAppSelector: TypedUseSelectorHook<State> = useSelector;

export { useAppDispatch, useAppSelector };

3 changes: 1 addition & 2 deletions src/hooks/useMap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ function useMap(
): Map | null {
const [map, setMap] = useState<Map | null>(null);
const isRenderedRef = useRef<boolean>(false);

useEffect(() => {
if (mapRef.current !== null && !isRenderedRef.current) {
const instance = new Map(mapRef.current, {
Expand All @@ -28,9 +27,9 @@ function useMap(
);

instance.addLayer(layer);

setMap(instance);
}

isRenderedRef.current = true;
}, [mapRef, city]);

Expand Down
14 changes: 9 additions & 5 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,21 @@
import { BrowserRouter } from 'react-router-dom';
import { HelmetProvider } from 'react-helmet-async';
import { offers } from './mocks/offers';
import { Provider } from 'react-redux';
import { store } from './store';

const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);

root.render(
<React.StrictMode>
<HelmetProvider>
<BrowserRouter>
<App offers={offers} />
</BrowserRouter>
</HelmetProvider>
<Provider store={store}>
<HelmetProvider>
<BrowserRouter>
<App offers={offers} />

Check failure on line 19 in src/index.tsx

View workflow job for this annotation

GitHub Actions / build

Type '{ offers: Offer[]; }' is not assignable to type 'IntrinsicAttributes'.
</BrowserRouter>
</HelmetProvider>
</Provider>
</React.StrictMode>
);
1 change: 1 addition & 0 deletions src/mocks/locations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const locations = ['Paris', 'Cologne', 'Brussels', 'Amsterdam', 'Hamburg', 'Dusseldorf',];
13 changes: 6 additions & 7 deletions src/pages/favorite-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,15 @@ import Container from '../components/container';
import Card from '../components/card';
import FavoriteItems from '../components/favorite-items';
import { Helmet } from 'react-helmet-async';
import { Offer } from '../types/offer';
import { optionCard } from '../const';
import { OptionCard } from '../const';
import FavoritesEmpty from '../components/favorites-empty';
import { getFavoritesByLocation } from '../utils/utils';
import { useAppSelector } from '../hooks';

type TOfferPageProps = {
offers: Offer[];
}

export default function FavoritePage({ offers }: TOfferPageProps) {
export default function FavoritePage() {
const offers = useAppSelector((state) => state.offers);

const favorites = getFavoritesByLocation(offers);
return (
<Container mainClass='favorites'>
Expand All @@ -26,7 +25,7 @@ export default function FavoritePage({ offers }: TOfferPageProps) {
<ul className="favorites__list">
{Object.entries(favorites).map(([location, gropedFavorites]) => (
<FavoriteItems key={location} city={location}>
{gropedFavorites.map((favorite) => <Card key={favorite.id} optionCard={optionCard.FAVORITES_CARD} offer={favorite} />)}
{gropedFavorites.map((favorite) => <Card key={favorite.id} optionCard={OptionCard.FAVORITES_CARD} offer={favorite} />)}
</FavoriteItems>
)
)}
Expand Down
Loading
Loading