From 4362f5d6259b8282ef8c4a44b75c10078151ed09 Mon Sep 17 00:00:00 2001 From: Takiera Date: Wed, 18 Sep 2024 17:14:43 +0300 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D1=8B=20=D0=BC=D0=BE=D0=BA=D0=B8=20=D0=B8=20state=20?= =?UTF-8?q?=D0=B4=D0=BB=D1=8F=20=D1=84=D0=BE=D1=80=D0=BC=D1=8B=20=D0=BE?= =?UTF-8?q?=D1=82=D0=BF=D1=80=D0=B0=D0=B2=D0=BA=D0=B8=20=D0=BA=D0=BE=D0=BC?= =?UTF-8?q?=D0=BC=D0=B5=D0=BD=D1=82=D0=B0=D1=80=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 6 +- src/components/app/app.tsx | 11 +- src/components/card/card.tsx | 47 ++- src/components/comment-form/comment-form.tsx | 50 +++ src/components/comment-form/stars.tsx | 40 ++ src/components/offers-list/offers-list.tsx | 30 ++ src/index.tsx | 9 +- src/mocks/offers.ts | 206 ++++++++++ src/pages/favorites-page.tsx | 109 +----- src/pages/main-page.tsx | 17 +- src/pages/offer-page.tsx | 378 +++---------------- 11 files changed, 436 insertions(+), 467 deletions(-) create mode 100644 src/components/comment-form/comment-form.tsx create mode 100644 src/components/comment-form/stars.tsx create mode 100644 src/components/offers-list/offers-list.tsx create mode 100644 src/mocks/offers.ts diff --git a/package-lock.json b/package-lock.json index 9e0465f..67fa68c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2300,9 +2300,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001546", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001546.tgz", - "integrity": "sha512-zvtSJwuQFpewSyRrI3AsftF6rM0X80mZkChIt1spBGEvRglCrjTniXvinc8JKRoqTwXAgvqTImaN9igfSMtUBw==", + "version": "1.0.30001660", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001660.tgz", + "integrity": "sha512-GacvNTTuATm26qC74pt+ad1fW15mlQ/zuTzzY1ZoIzECTP8HURDfF43kNxPgf7H1jmelCBQTTbBNxdSXOA7Bqg==", "dev": true, "funding": [ { diff --git a/src/components/app/app.tsx b/src/components/app/app.tsx index 07d413a..72e364f 100644 --- a/src/components/app/app.tsx +++ b/src/components/app/app.tsx @@ -6,29 +6,30 @@ import NotFoundPage from '../../pages/not-found-page'; import PrivateRoute from '../private-route/private-route'; import { Route, BrowserRouter, Routes } from 'react-router-dom'; import { AppRoute, AuthorizationStatus } from '../../consts'; +import { Offer } from '../../mocks/offers'; type AppProps = { - offersCount: number; + offers: Offer[]; }; -export default function App({ offersCount }: AppProps): JSX.Element { +export default function App({ offers }: AppProps): JSX.Element { return ( } + element={} /> - + } /> } /> - } /> + } /> } /> diff --git a/src/components/card/card.tsx b/src/components/card/card.tsx index a40e8f7..f715de4 100644 --- a/src/components/card/card.tsx +++ b/src/components/card/card.tsx @@ -1,27 +1,44 @@ -export default function Card() { +import { Link } from 'react-router-dom'; +import { Offer } from '../../mocks/offers'; + +type CardProps = { + offer: Offer; + offerClassName: string; +} + +const FAVORITES_CLASS_NAME = 'favorites'; + +export default function Card(props: CardProps) { + const {offer, offerClassName} = props; + const imgWidth = offerClassName === FAVORITES_CLASS_NAME ? 150 : 260; + const imgHeight = offerClassName === FAVORITES_CLASS_NAME ? 110 : 200; + const {isPremium, images, price, rating, title, type, id, isFavorite} = offer; + return ( -
-
- Premium -
-
+ ); diff --git a/src/components/comment-form/comment-form.tsx b/src/components/comment-form/comment-form.tsx new file mode 100644 index 0000000..f4f8feb --- /dev/null +++ b/src/components/comment-form/comment-form.tsx @@ -0,0 +1,50 @@ +import { useState } from 'react'; +import Stars from './stars'; +import { StarsValue } from './stars'; + +type CustomFormState = { + rating: StarsValue; + comment: string; +} + +export default function CommentForm() { + const [formState, setFormState] = useState({rating: '0', comment: ''}); + + return ( +
+ + setFormState({...formState, rating})}/> + +
+

+ To submit review please make sure to set{' '} + rating and describe + your stay with at least{' '} + 50 characters. +

+ +
+ + ); +} diff --git a/src/components/comment-form/stars.tsx b/src/components/comment-form/stars.tsx new file mode 100644 index 0000000..398f081 --- /dev/null +++ b/src/components/comment-form/stars.tsx @@ -0,0 +1,40 @@ +export type StarsValue = '0' | '1' | '2' | '3' | '4' | '5'; + +type StarsProps = { + value: StarsValue; + onChange: (value: StarsValue) => void; +} + +export default function Stars({value, onChange}: StarsProps) { + return ( +
+ {Array.from({ length: 5 }, (_, idx) => { + const key = String(5 - idx) as StarsValue; + + return ( + <> + onChange(key)} + /> + + + ); + })} +
+ ); +} diff --git a/src/components/offers-list/offers-list.tsx b/src/components/offers-list/offers-list.tsx new file mode 100644 index 0000000..b1f928b --- /dev/null +++ b/src/components/offers-list/offers-list.tsx @@ -0,0 +1,30 @@ +import { MouseEventHandler, useState } from 'react'; +import { Offer } from '../../mocks/offers'; +import Card from '../card/card'; + +type OffersListProps = { + className: string; + offers: Offer[]; + offerClassName: string; +} + +export default function OffersList({ className, offers, offerClassName }: OffersListProps): JSX.Element { + const setActiveCard = useState('')[1]; + const onMouseMoveHandler: MouseEventHandler = (evt) => { + let id = ''; + if(evt.target instanceof HTMLElement) { + const element = evt.target.closest('[data-id]'); + + if(element instanceof HTMLElement) { + id = element.dataset.id || ''; + } + } + + setActiveCard(id); + }; + + return ( +
+ {offers.map((offer) => )} +
); +} diff --git a/src/index.tsx b/src/index.tsx index f850174..bfc67cb 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,17 +1,18 @@ import React from 'react'; import ReactDOM from 'react-dom/client'; import App from './components/app/app'; +import {offers} from './mocks/offers'; const root = ReactDOM.createRoot( document.getElementById('root') as HTMLElement ); -const Setting = { - OffercCount: 5, -} as const; +// const Setting = { +// OffercCount: 5, +// } as const; root.render( - + ); diff --git a/src/mocks/offers.ts b/src/mocks/offers.ts new file mode 100644 index 0000000..0196d97 --- /dev/null +++ b/src/mocks/offers.ts @@ -0,0 +1,206 @@ +export type Offer = { + id: string; + title: string; + type: string; + price: number; + city: { + name: string; + location: { + latitude: number; + longitude: number; + zoom: number; + };}; + location: { + latitude: number; + longitude: number; + zoom: number; + }; + isFavorite: boolean; + isPremium: boolean; + rating: number; + description: string; + bedrooms: number; + goods: string[]; + host: { + name: string; + avatarUrl: string; + isPro: boolean; + }; + images: string[]; + maxAdults: number; + } + +export const offers: Offer[] = [ + { + id: '6af6f711-c28d-4121-82cd-e0b462a27f00', + title: 'Beautiful & luxurious apartment at great location', + type: 'Apartment', + price: 125, + city: { + 'name': 'Amsterdam', + 'location': { + 'latitude': 52.35514938496378, + 'longitude': 4.673877537499948, + 'zoom': 8 + }}, + location: { + 'latitude': 52.35514938496378, + 'longitude': 4.673877537499948, + 'zoom': 8 + }, + isFavorite: true, + isPremium: true, + rating: 80, + description: '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.', + bedrooms: 3, + goods: [ + 'Wi-Fi', + 'Washing machine', + 'Towels', + ], + host: { + 'name': 'Oliver Conner', + 'avatarUrl': 'https://url-to-image/image.png', + 'isPro': false + }, + images: [ + 'img/apartment-01.jpg', + 'img/room.jpg', + 'img/apartment-02.jpg', + 'img/apartment-03.jpg', + 'img/studio-01.jpg', + 'img/apartment-01.jpg' + ], + maxAdults: 4, + }, + { + id: '6af6f711-c28d-4121-e0b462a27f00', + title: 'Wood and stone place', + type: 'Apartment', + price: 80, + city: { + 'name': 'Amsterdam', + 'location': { + 'latitude': 52.35514938496378, + 'longitude': 4.673877537499948, + 'zoom': 8 + }}, + location: { + 'latitude': 52.35514938496378, + 'longitude': 4.673877537499948, + 'zoom': 8 + }, + isFavorite: false, + isPremium: false, + rating: 70, + description: '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.', + bedrooms: 2, + goods: [ + 'Wi-Fi', + 'Heating', + 'Coffee machine', + 'Baby seat', + ], + host: { + 'name': 'Oliver Conner', + 'avatarUrl': 'https://url-to-image/image.png', + 'isPro': false + }, + images: [ + 'img/room.jpg', + 'img/apartment-01.jpg', + 'img/apartment-02.jpg', + 'img/apartment-03.jpg', + 'img/studio-01.jpg', + 'img/apartment-01.jpg' + ], + maxAdults: 3, + }, + { + id: '6af6f711-c28d-4121-82cd-e0b4600', + title: 'Canal View Prinsengracht', + type: 'Room', + price: 132, + city: { + 'name': 'Amsterdam', + 'location': { + 'latitude': 52.35514938496378, + 'longitude': 4.673877537499948, + 'zoom': 8 + }}, + location: { + 'latitude': 52.35514938496378, + 'longitude': 4.673877537499948, + 'zoom': 8 + }, + isFavorite: true, + isPremium: false, + rating: 90, + description: '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.', + bedrooms: 4, + goods: [ + 'Wi-Fi', + 'Fridge', + 'Cabel TV' + ], + host: { + 'name': 'Oliver Conner', + 'avatarUrl': 'https://url-to-image/image.png', + 'isPro': false + }, + images: [ + 'img/apartment-02.jpg', + 'img/room.jpg', + 'img/apartment-01.jpg', + 'img/apartment-03.jpg', + 'img/studio-01.jpg', + 'img/apartment-01.jpg' + ], + maxAdults: 5, + }, + { + id: '6af6f711-121-82cd-e0b462a27f00', + title: 'Nice, cozy, warm big bed apartment', + type: 'Room', + price: 180, + city: { + 'name': 'Amsterdam', + 'location': { + 'latitude': 52.35514938496378, + 'longitude': 4.673877537499948, + 'zoom': 8 + }}, + location: { + 'latitude': 52.35514938496378, + 'longitude': 4.673877537499948, + 'zoom': 8 + }, + isFavorite: false, + isPremium: true, + rating: 60, + description: '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.', + bedrooms: 1, + goods: [ + 'Wi-Fi', + 'Coffee machine', + 'Baby seat', + 'Kitchen', + 'Dishwasher', + ], + host: { + 'name': 'Oliver Conner', + 'avatarUrl': 'https://url-to-image/image.png', + 'isPro': false + }, + images: [ + 'img/apartment-03.jpg', + 'img/room.jpg', + 'img/apartment-01.jpg', + 'img/apartment-02.jpg', + 'img/studio-01.jpg', + 'img/apartment-01.jpg' + ], + maxAdults: 2, + }, +]; + diff --git a/src/pages/favorites-page.tsx b/src/pages/favorites-page.tsx index 4637153..d5f6f7e 100644 --- a/src/pages/favorites-page.tsx +++ b/src/pages/favorites-page.tsx @@ -1,6 +1,14 @@ import Header from '../components/header/header'; +import { Offer } from '../mocks/offers'; +import OffersList from '../components/offers-list/offers-list'; + +type FavoritesPageProps = { + offers: Offer[]; +} + +export default function FavoritesPage({offers}: FavoritesPageProps): JSX.Element { + const favoriteOffers = offers.filter((offer) => offer.isFavorite); -export default function FavoritesPage(): JSX.Element { return (
@@ -18,104 +26,7 @@ export default function FavoritesPage(): JSX.Element {
-
- - -
-
- - Place image - -
-
-
-
- €80 - - / night - -
- -
-
-
- - Rating -
-
-

- Wood and stone place -

-

Room

-
-
-
+
  • diff --git a/src/pages/main-page.tsx b/src/pages/main-page.tsx index 0457d70..4c6430a 100644 --- a/src/pages/main-page.tsx +++ b/src/pages/main-page.tsx @@ -1,17 +1,12 @@ -import { ReactNode } from 'react'; -import Card from '../components/card/card'; import Header from '../components/header/header'; +import OffersList from '../components/offers-list/offers-list'; +import { Offer } from '../mocks/offers'; type MainPageProps = { - offersCount: number; + offers: Offer[]; }; -export default function MainPage({ offersCount }: MainPageProps): JSX.Element { - const elements: ReactNode[] = []; - - for (let i = 0; i < offersCount; i++) { - elements.push(); - } +export default function MainPage({ offers }: MainPageProps): JSX.Element { return (
    @@ -86,9 +81,7 @@ export default function MainPage({ offersCount }: MainPageProps): JSX.Element {
  • -
    - {elements} -
    +
    diff --git a/src/pages/offer-page.tsx b/src/pages/offer-page.tsx index c1b37ba..dc6379e 100644 --- a/src/pages/offer-page.tsx +++ b/src/pages/offer-page.tsx @@ -1,66 +1,54 @@ import Header from '../components/header/header'; +import {Offer} from '../mocks/offers'; +import OffersList from '../components/offers-list/offers-list'; +import { useParams } from 'react-router-dom'; +import CommentForm from '../components/comment-form/comment-form'; +import NotFoundPage from './not-found-page'; + +type OfferPageProps = { + offers: Offer[]; +} + +export default function OfferPage(props: OfferPageProps): JSX.Element { + const {offers} = props; + const { id } = useParams<{ id: string }>(); + const offer = offers.find((ofer) => ofer.id === id); + + + if (!offer) { + return (); + } + + const {images, isPremium, title, rating, type, bedrooms, maxAdults, price, goods, description} = offer; -export default function OfferPage(): JSX.Element { return (
    -
    -
    +
    -
    - Photo studio -
    -
    - Photo studio -
    -
    - Photo studio -
    -
    - Photo studio -
    -
    - Photo studio -
    -
    - Photo studio -
    + { images.map((image) => ( +
    + Photo studio +
    + ))}
    -
    - Premium -
    + {isPremium && ( +
    + Premium +
    + )}

    - Beautiful & luxurious studio at great location + {title}

    - + Rating
    4.8
    • - Apartment + {type}
    • - 3 Bedrooms + {bedrooms} Bedrooms
    • - Max 4 adults + Max {maxAdults} adults
    - €120 + €{price}  night

    What's inside

      -
    • Wi-Fi
    • -
    • Washing machine
    • -
    • Towels
    • -
    • Heating
    • -
    • Coffee machine
    • -
    • Baby seat
    • -
    • Kitchen
    • -
    • Dishwasher
    • -
    • Cabel TV
    • -
    • Fridge
    • + {goods.map((good) => ( +
    • {good}
    • + ))}
    @@ -123,9 +104,7 @@ export default function OfferPage(): JSX.Element {

    - 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. + {description}

    An independent House, strategically located between Rembrand @@ -155,7 +134,7 @@ export default function OfferPage(): JSX.Element {

    - + Rating
    @@ -170,122 +149,7 @@ export default function OfferPage(): JSX.Element {
    -
    - -
    - - - - - - - - - - - - - - -
    - -
    -

    - To submit review please make sure to set{' '} - rating and describe - your stay with at least{' '} - 50 characters. -

    - -
    -
    +
    @@ -296,151 +160,7 @@ export default function OfferPage(): JSX.Element {

    Other places in the neighbourhood

    -
    -
    -
    - - Place image - -
    -
    -
    -
    - €80 - - / night - -
    - -
    -
    -
    - - Rating -
    -
    -

    - Wood and stone place -

    -

    Room

    -
    -
    - - - - -
    +