diff --git a/src/App.tsx b/src/App.tsx index 87c602a..38ebfbe 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -3,45 +3,22 @@ import MainPage from './components/MainPage/MainPage'; import Favorite from './components/Favorites/Favorite'; import LoginPage from './components/Login/LoginPage'; import Offer from './components/Offer/Offer'; +import { useAppSelector } from './hooks'; +import { REVIEWERS } from './mock/reviewers'; -type Offer = { - id: number; - title: string; - price: number; - rating: number; - type: string; - isPremium: boolean; - isFavorite: boolean; - NumberOfPlaces: number; - previewImage: string; - city: { - name: string; - location: { - latitude: number; - longitude: number; - zoom: number; - }; - }; - location: { - latitude: number; - longitude: number; - zoom: number; - }; -}; - -type AppProps = { - offers: Offer[]; -}; -export default function App({ offers }: AppProps) { +export const App: React.FC = () => { + const currentCity = useAppSelector((state) => state.currentCity); + const offers = useAppSelector((state) => state.offers); + const cities = useAppSelector((state) => state.cities); return ( - } /> + } /> } /> - } /> + } /> } /> ); -} +}; diff --git a/src/action.ts b/src/action.ts new file mode 100644 index 0000000..a3db947 --- /dev/null +++ b/src/action.ts @@ -0,0 +1,5 @@ +import { createAction } from '@reduxjs/toolkit'; +import { OfferObject } from './types/types'; +export const changeCity = createAction('ChangeCity'); + +export const AddOffer = createAction('AddOffer'); diff --git a/src/components/CityList/CityList.tsx b/src/components/CityList/CityList.tsx new file mode 100644 index 0000000..5cd2d2e --- /dev/null +++ b/src/components/CityList/CityList.tsx @@ -0,0 +1,38 @@ +import React from 'react'; +import { City } from '../../types/types'; + +const CITY_ACTIVE = 'tabs__item--active'; + +type CitiesListProps = { + currentCity: string; + cities: City[]; + onSelect: (cityName: string) => void; +} + +export const ListCities: React.FC = ({ + currentCity, + cities, + onSelect, +}) => ( +
    + {cities.map((city) => ( +
  • { + onSelect(city.title); + }} + > +
    + {city.title} +
    +
  • + ) + )} +
+); diff --git a/src/components/Favorites/Favorite.tsx b/src/components/Favorites/Favorite.tsx index c8d4033..365752a 100644 --- a/src/components/Favorites/Favorite.tsx +++ b/src/components/Favorites/Favorite.tsx @@ -1,30 +1,9 @@ import OfferCard from '../Offer/OfferCard'; -type Offer = { - id: number; - title: string; - price: number; - rating: number; - type: string; - isPremium: boolean; - isFavorite: boolean; - NumberOfPlaces: number; - previewImage: string; - city: { - name: string; - location: { - latitude: number; - longitude: number; - zoom: number; - }; - }; - location: { - latitude: number; - longitude: number; - zoom: number; - }; -}; + +import { OfferObject, CardCssNameList } from '../../types/types'; + type FavoriteProps = { - offers: Offer[]; + offers: OfferObject[]; }; const Favorite = ({ offers }: FavoriteProps) => (
@@ -34,7 +13,7 @@ const Favorite = ({ offers }: FavoriteProps) => (

Saved listings

{offers.map((offer) => ( - + ))}
diff --git a/src/components/MainPage/MainPage.tsx b/src/components/MainPage/MainPage.tsx index 90845e8..33cbf7c 100644 --- a/src/components/MainPage/MainPage.tsx +++ b/src/components/MainPage/MainPage.tsx @@ -1,41 +1,39 @@ import {FC} from 'react'; +import { useNavigate } from 'react-router-dom'; import OfferList from '../Offer/OfferList'; +import { useAppDispatch } from '../../hooks'; +import { OfferObject,AppRoute, City, CardCssNameList } from '../../types/types'; +import { changeCity } from '../../action'; +import { ListCities } from '../../components/CityList/CityList'; import Map from '../Map/Map'; -type Offer = { - id: number; - title: string; - price: number; - rating: number; - type: string; - isPremium: boolean; - isFavorite: boolean; - NumberOfPlaces: number; - previewImage: string; - city: { - name: string; - location: { - latitude: number; - longitude: number; - zoom: number; - }; - }; - location: { - latitude: number; - longitude: number; - zoom: number; - }; -}; type MainPageProps = { - offers: Offer[]; + offers: OfferObject[]; + currentCity: City; + cities: City[]; }; -export const MainPage : FC = ({ offers }) => - ( +export const MainPage : FC = ({ + offers, + currentCity, + cities, +}:MainPageProps) => { + const navigate = useNavigate(); + const dispatch = useAppDispatch(); + + const handleUserSelectCity = (cityName: string) => { + dispatch(changeCity(cityName)); + // dispatch(fillOffers()); + }; + + return (
@@ -64,45 +62,14 @@ export const MainPage : FC = ({ offers }) =>

Cities

Places

- {offers.length} places to stay in Amsterdam + {offers.filter((a) =>a.city.name === currentCity.title).length} places to stay in {currentCity.title}
Sort by @@ -119,12 +86,12 @@ export const MainPage : FC = ({ offers }) =>
- + a.city.name === currentCity.title)} cardcssname={CardCssNameList.citiesList}/>
- + a.city.name === currentCity.title)} selectedPoint={offers[3]} currentCity={currentCity} />
@@ -132,4 +99,5 @@ export const MainPage : FC = ({ offers }) =>
); +}; export default MainPage; diff --git a/src/components/Map/Map.tsx b/src/components/Map/Map.tsx index 3a6693a..0438578 100644 --- a/src/components/Map/Map.tsx +++ b/src/components/Map/Map.tsx @@ -1,31 +1,8 @@ import {useRef, useEffect} from 'react'; import {Icon, Marker, layerGroup} from 'leaflet'; import useMap from '../../hooks/use-map'; +import { OfferObject, City } from '../../types/types'; import 'leaflet/dist/leaflet.css'; -type Offer = { - id: number; - title: string; - price: number; - rating: number; - type: string; - isPremium: boolean; - isFavorite: boolean; - NumberOfPlaces: number; - previewImage: string; - city: { - name: string; - location: { - latitude: number; - longitude: number; - zoom: number; - }; - }; - location: { - latitude: number; - longitude: number; - zoom: number; - }; -}; const defaultCustomIcon = new Icon({ iconUrl: 'https://assets.htmlacademy.ru/content/intensive/javascript-1/demo/interactive-map/pin.svg', iconSize: [40, 40], @@ -38,15 +15,16 @@ const currentCustomIcon = new Icon({ iconAnchor: [20, 40] }); type MainPageProps = { - offers: Offer[]; - selectedPoint :Offer; + offers: OfferObject[]; + currentCity: City; + selectedPoint: OfferObject; }; function Map(props: MainPageProps): JSX.Element { - const {offers, selectedPoint} = props; + const {offers, currentCity,selectedPoint} = props; const mapRef = useRef(null); - const map = useMap(mapRef, 'Амстердам'); + const map = useMap(mapRef, currentCity.title); useEffect(() => { if (map) { diff --git a/src/components/Offer/Offer.tsx b/src/components/Offer/Offer.tsx index 957e487..7bcf49a 100644 --- a/src/components/Offer/Offer.tsx +++ b/src/components/Offer/Offer.tsx @@ -1,209 +1,219 @@ +/* eslint-disable react/prop-types */ +import { Link } from 'react-router-dom'; import SendCommentForm from '../SendCommentForm/SendCommentForm'; import { ReviewList } from '../Reviews/ReviewList'; -import { offers } from '../../mock/offers'; +//import { offers } from '../../mock/offers'; import { OtherPlacesNearby } from '../OtherPlacesNearby/OtherPlacesNearby'; import Map from '../Map/Map'; -export default function Offer () { - return ( -
-
-
-
-
- - 6 cities logo - -
- +import { + AppRoute, + UserReview, + OfferObject, + City, +} from '../../types/types'; +type OfferProps = { + reviews: UserReview[]; + offers: OfferObject[]; + currentCity: City; +}; + +export const Offer: React.FC = ({ + // eslint-disable-next-line react/prop-types + reviews, + offers, + currentCity, +}) => ( +
+
+
+
+
-
-
-
-
-
- Photo studio -
-
- Photo studio -
-
- Photo studio -
-
- Photo studio -
-
- Photo studio -
-
- Photo studio -
+
+
+
+
+
+ Photo studio +
+
+ Photo studio +
+
+ Photo studio +
+
+ Photo studio +
+
+ Photo studio +
+
+ Photo studio
-
-
-
- Premium -
-
-

- Beautiful & luxurious studio at great location -

- -
-
-
- - Rating -
- 4.8 +
+
+
+
+ Premium +
+
+

+ Beautiful & luxurious studio at great location +

+ +
+
+
+ + Rating
-
    -
  • - Apartment -
  • -
  • - 3 Bedrooms -
  • -
  • - Max 4 adults -
  • + 4.8 +
+
    +
  • + Apartment +
  • +
  • + 3 Bedrooms +
  • +
  • + Max 4 adults +
  • +
+
+ €120 +  night +
+
+

What's inside

+
    +
  • Wi-Fi
  • +
  • Washing machine
  • +
  • Towels
  • +
  • Heating
  • +
  • Coffee machine
  • +
  • Baby seat
  • +
  • Kitchen
  • +
  • Dishwasher
  • +
  • Cabel TV
  • +
  • Fridge
-
- €120 -  night -
-
-

What's inside

-
    -
  • Wi-Fi
  • -
  • Washing machine
  • -
  • Towels
  • -
  • Heating
  • -
  • Coffee machine
  • -
  • Baby seat
  • -
  • Kitchen
  • -
  • Dishwasher
  • -
  • Cabel TV
  • -
  • Fridge
  • -
-
-
-

Meet the host

-
-
- Host avatar -
- Angelina - Pro -
-
-

- A quiet cozy and picturesque that hides behind a a river by - the unique lightness of Amsterdam. The building is green and - from 18th century. -

-

- An independent House, strategically located between Rembrand - Square and National Opera, but where the bustle of the city - comes to rest in this alley flowery and colorful. -

+
+
+

Meet the host

+
+
+ Host avatar
+ Angelina + Pro +
+
+

+ A quiet cozy and picturesque that hides behind a a river by + the unique lightness of Amsterdam. The building is green and + from 18th century. +

+

+ An independent House, strategically located between Rembrand + Square and National Opera, but where the bustle of the city + comes to rest in this alley flowery and colorful. +

-
+
-
- -
-
-
-
-
-

Reviews

- {} -
-
-
- ); -} +
+ +
+
+
+ +
+
+

Reviews

+ {} +
+
+
+); +export default Offer; diff --git a/src/components/Offer/OfferCard.tsx b/src/components/Offer/OfferCard.tsx index f644358..a0653df 100644 --- a/src/components/Offer/OfferCard.tsx +++ b/src/components/Offer/OfferCard.tsx @@ -1,65 +1,51 @@ +import React from 'react'; import { Link } from 'react-router-dom'; - -type Offer = { - id: number; - title: string; - price: number; - rating: number; - type: string; - isPremium: boolean; - isFavorite: boolean; - NumberOfPlaces: number; - previewImage: string; - city: { - name: string; - location: { - latitude: number; - longitude: number; - zoom: number; - }; - }; - location: { - latitude: number; - longitude: number; - zoom: number; - }; -}; +import { OfferObject, AppRoute } from '../../types/types'; +import { useState } from 'react'; type OfferCardProps = { - offer: Offer; + offer: OfferObject; + cardcssname: string; }; -const OfferCard = ({ offer }: OfferCardProps) => ( -
- {offer.isPremium && ( -
- Premium -
- )} -
- - {offer.title} - -
-
-
-
- €{offer.price} - / night +export const OfferCard: React.FC = ({ + offer, + cardcssname +}) => { + const [ isActiveCard, setActiveCard ] = useState(false); + return ( +
+ setActiveCard(!isActiveCard)} + > + {offer.isPremium && ( +
+ Premium
+ )} +
+ + {offer.title} +
-
-
- - Rating +
+
+
+ €{offer.price} + / night +
+
+
+ + Rating +
+
+

+ {offer.title} +

+

{offer.type}

-

- {offer.title} -

-

{offer.type}

-
-
-); - +
+ ); +}; export default OfferCard; diff --git a/src/components/Offer/OfferList.tsx b/src/components/Offer/OfferList.tsx index 32825f3..1ac6f8d 100644 --- a/src/components/Offer/OfferList.tsx +++ b/src/components/Offer/OfferList.tsx @@ -1,36 +1,14 @@ import { useState } from 'react'; import OfferCard from './OfferCard'; +import { OfferObject } from '../../types/types'; -type Offer = { - id: number; - title: string; - price: number; - rating: number; - type: string; - isPremium: boolean; - isFavorite: boolean; - NumberOfPlaces: number; - previewImage: string; - city: { - name: string; - location: { - latitude: number; - longitude: number; - zoom: number; - }; - }; - location: { - latitude: number; - longitude: number; - zoom: number; - }; -}; type OfferListProps = { - offers: Offer[]; + offers: OfferObject[]; + cardcssname: string; }; -const OfferList = ({ offers }: OfferListProps) => { +const OfferList = ({ offers, cardcssname}: OfferListProps) => { const [activeOfferId, setActiveOfferId] = useState(null); return ( @@ -41,7 +19,7 @@ const OfferList = ({ offers }: OfferListProps) => { onMouseEnter={() => setActiveOfferId(offer.id)} onMouseLeave={() => setActiveOfferId(null)} > - +
))}
{activeOfferId &&

Active Offer ID: {activeOfferId}

}
diff --git a/src/components/OtherPlacesNearby/OtherPlacesNearby.tsx b/src/components/OtherPlacesNearby/OtherPlacesNearby.tsx index 2f64499..c6e9db5 100644 --- a/src/components/OtherPlacesNearby/OtherPlacesNearby.tsx +++ b/src/components/OtherPlacesNearby/OtherPlacesNearby.tsx @@ -1,10 +1,10 @@ import OfferList from '../Offer/OfferList'; import { offers } from '../../mock/offers'; - +import {CardCssNameList } from '../../types/types'; export const OtherPlacesNearby = () => (

Other places in the neighbourhood

- +
); diff --git a/src/components/Reviews/Review.tsx b/src/components/Reviews/Review.tsx index 515ec11..16d639e 100644 --- a/src/components/Reviews/Review.tsx +++ b/src/components/Reviews/Review.tsx @@ -1,16 +1,7 @@ /* eslint-disable react/prop-types */ import { Rating } from '../Rating/Rating'; -type ReviewObject = { - id: string; - date: Date; - user: { - name: string; - avatarUrl: string; - isPro: boolean; - }; - comment: string; - rating: number; -}; +import { UserReview} from '../../types/types'; + export const dateToYearMonthDay = (date: Date) => new Intl.DateTimeFormat('en-CA', { year: 'numeric', @@ -22,7 +13,7 @@ export const dateToMonthWordYear = (date: Date) => new Intl.DateTimeFormat('en-CA', { year: 'numeric', month: 'long' }).format( date, ); -export const Review: React.FC = ({ +export const Review: React.FC = ({ comment, date, rating, @@ -33,7 +24,7 @@ export const Review: React.FC = ({
Reviews avatar useDispatch(); +export const useAppSelector: TypedUseSelectorHook = useSelector; diff --git a/src/index.tsx b/src/index.tsx index 36201f0..a850ac6 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,13 +1,17 @@ import React from 'react'; import ReactDOM from 'react-dom/client'; -import App from './App'; -import { offers } from './mock/offers'; +import {App} from './App'; + +import { Provider } from 'react-redux'; +import { store } from './store'; const root = ReactDOM.createRoot( document.getElementById('root') as HTMLElement ); root.render( - + + + ); diff --git a/src/mock/cities.ts b/src/mock/cities.ts new file mode 100644 index 0000000..2ec39fc --- /dev/null +++ b/src/mock/cities.ts @@ -0,0 +1,34 @@ +import { City } from '../types/types'; + +export const CITYLIST: City[] = [ + { + title: 'Paris', + lng: 2.3522, + lat: 48.8566, + }, + { + title: 'Amsterdam', + lng: 4.9041, + lat: 52.3676, + }, + { + title: 'Cologne', + lng: 6.9603, + lat: 50.9375, + }, + { + title: 'Brussels', + lng: 4.3517, + lat: 50.8503, + }, + { + title: 'Hamburg', + lng: 9.9937, + lat: 53.5511, + }, + { + title: 'Dusseldorf', + lng: 6.7735, + lat: 51.2277, + }, +]; diff --git a/src/mock/offers.ts b/src/mock/offers.ts index b253cd2..76728e1 100644 --- a/src/mock/offers.ts +++ b/src/mock/offers.ts @@ -38,7 +38,7 @@ export const offers = [ zoom: 0, }, city: { - name: 'Amsterdam', + name: 'Paris', location: { latitude: 52.35514938496378, longitude: 4.673877537499948, diff --git a/src/mock/reviewers.ts b/src/mock/reviewers.ts new file mode 100644 index 0000000..6ed57a4 --- /dev/null +++ b/src/mock/reviewers.ts @@ -0,0 +1,22 @@ +export const REVIEWERS = [ + { + id: 1, + user: { + name: 'Max', + avatar: 'img/avatar-01.jpg', + }, + rating: 5, + comment: 'All super', + date: new Date(), + }, + { + id: 2, + user: { + name: 'Igor', + avatar: 'img/avatar-02.jpg', + }, + rating: 1, + comment: 'All bad', + date: new Date(), + }, +]; diff --git a/src/reducer.ts b/src/reducer.ts new file mode 100644 index 0000000..71c99c5 --- /dev/null +++ b/src/reducer.ts @@ -0,0 +1,28 @@ +import { createReducer } from '@reduxjs/toolkit'; +import { City, OfferObject } from './types/types'; +import { changeCity, AddOffer } from './action'; +import { CITYLIST } from './mock/cities'; +import { offers } from './mock/offers'; +type InitialState = { + currentCity: City; + cities: City[]; + offers: OfferObject[]; +}; + +const initialState: InitialState = { + currentCity: CITYLIST[0], + cities: CITYLIST, + offers: offers, +}; + +export const reducer = createReducer(initialState, (builder) => { + builder + .addCase(changeCity, (state, action) => { + state.currentCity = state.cities.find( + (city) => city.title === action.payload + )!; + }) + .addCase(AddOffer, (state, action) => { + state.offers = action.payload; + }); +}); diff --git a/src/store/index.ts b/src/store/index.ts new file mode 100644 index 0000000..320da04 --- /dev/null +++ b/src/store/index.ts @@ -0,0 +1,4 @@ +import { configureStore } from '@reduxjs/toolkit'; +import { reducer } from '../reducer'; + +export const store = configureStore({ reducer }); diff --git a/src/types/types.ts b/src/types/types.ts index b661606..d24d2c2 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -1,8 +1,8 @@ +import { store } from '../store'; export type City = { title: string; lat: number; lng: number; - zoom: number; }; export type Point = { @@ -10,5 +10,55 @@ export type Point = { lat: number; lng: number; }; +export type OfferObject = { + id: number; + title: string; + price: number; + rating: number; + type: string; + isPremium: boolean; + isFavorite: boolean; + NumberOfPlaces: number; + previewImage: string; + city: { + name: string; + location: { + latitude: number; + longitude: number; + zoom: number; + }; + }; + location: { + latitude: number; + longitude: number; + zoom: number; + }; +}; +export enum AppRoute { + Login = '/login', + Favorites = '/favorites', + Main = '/', + Offer = '/offer/:id', +} + +export const enum CardCssNameList { + citiesList = 'cities__card', + neardPlace = 'near-places__card', + favoritePlace = 'favorites__card', +} + +export type UserReview = { + id: number; + user: { + name: string; + avatar: string; + }; + rating: number; + comment: string; + date: Date; +}; export type Points = Point[]; +export type State = ReturnType; + +export type AppDispatch = typeof store.dispatch;