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

Обратная сторона реальности #4

Merged
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
11 changes: 7 additions & 4 deletions src/components/app/app.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,30 @@
import MainScreen from '../../pages/main-screen/main-screen';
import {BrowserRouter, Route, Routes} from 'react-router-dom';
import { BrowserRouter, Route, Routes } from 'react-router-dom';
import LoginScreen from '../../pages/login-screen/login-screen.tsx';
import FavoritesScreen from '../../pages/favorites-screen/favorites-screen.tsx';
import NotFoundScreen from '../../pages/not-found-screen/not-found-screen.tsx';
import OfferScreen from '../../pages/offer-screen/offer-screen.tsx';
import PrivateRoute from '../private-route/private-route.tsx';
import { AppRoute, AuthorizationStatus } from '../../const.ts';
import { Offer } from '../../types/offer';

type AppScreenProps = {
placesCount: number;
offers: Offer[];
}

function App({placesCount}: AppScreenProps): JSX.Element {
function App({placesCount, offers}: AppScreenProps): JSX.Element {
const favourites: Offer[] = offers.filter((o) => o.isFavorite);
return (
<BrowserRouter>
<Routes>
<Route path="*" element={<NotFoundScreen/>} />
<Route path={AppRoute.Main} element={<MainScreen placesCount={placesCount}/>} />
<Route path={AppRoute.Main} element={<MainScreen placesCount={placesCount} offers={offers}/>} />
<Route
path={AppRoute.Favorites}
element={
<PrivateRoute authorizationStatus={AuthorizationStatus.NoAuth} >
<FavoritesScreen/>
<FavoritesScreen offers={favourites}/>
</PrivateRoute>
}
/>
Expand Down
26 changes: 19 additions & 7 deletions src/components/city-card/city-card.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,30 @@
function CityCard(): JSX.Element {
import { Offer } from '../../types/offer';
import { Link } from 'react-router-dom';

type OfferProps = {
offer: Offer;
}

function CityCard({offer}: OfferProps): JSX.Element {
return (
<article className="cities__card place-card">
{offer.isPremium ? (
<div className="place-card__mark">
<span>Premium</span>
</div>
) : null}
<div className="cities__image-wrapper place-card__image-wrapper">
<a href="#">
<img className="place-card__image" src="img/apartment-02.jpg" width="260" height="200" alt="Place image"/>
<img className="place-card__image" src={offer.image[0]} width="260" height="200" alt="Place image"/>
</a>
</div>
<div className="place-card__info">
<div className="place-card__price-wrapper">
<div className="place-card__price">
<b className="place-card__price-value">&euro;132</b>
<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">
<button className={offer.isFavorite ? 'place-card__bookmark-button place-card__bookmark-button--active button' : 'place-card__bookmark-button button'} type="button">
<svg className="place-card__bookmark-icon" width="18" height="19">
<use xlinkHref="#icon-bookmark"></use>
</svg>
Expand All @@ -21,14 +33,14 @@ function CityCard(): JSX.Element {
</div>
<div className="place-card__rating rating">
<div className="place-card__stars rating__stars">
<span style={{width: '80%'}}></span>
<span style={{width: `${(offer.rating / 5) * 100}%`}}></span>
<span className="visually-hidden">Rating</span>
</div>
</div>
<h2 className="place-card__name">
<a href="#">Canal View Prinsengracht</a>
<Link to={`/offer/${offer.id}`}>{offer.title}</Link>
</h2>
<p className="place-card__type">Apartment</p>
<p className="place-card__type">{offer.type}</p>
</div>
</article>
);
Expand Down
65 changes: 65 additions & 0 deletions src/components/comment-form/comment-form.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { useState } from 'react';

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

return (
<form className="reviews__form form" action="#" 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}/>
<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}/>
<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}/>
<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}/>
<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}/>
<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>
</svg>
</label>
</div>
<textarea className="reviews__textarea form__textarea" id="review" name="review" value={formData.review} onChange={handleFieldChange} placeholder="Tell how was your stay, what you like and what can be improved"></textarea>
<div className="reviews__button-wrapper">
<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>
</div>
</form>
);
}
export default CommentForm;
17 changes: 17 additions & 0 deletions src/components/offer-list/offer-list.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Offer } from '../../types/offer';
import CityCard from '../city-card/city-card';

type OfferListProps = {
offers: Offer[];
};

function OfferList({offers}: OfferListProps): JSX.Element {
return (
<div className="cities__places-list places__list tabs__content">
{offers.map((offer) => (
<CityCard key={offer.id} offer={offer}/>
))}
</div>
);
}
export default OfferList;
3 changes: 2 additions & 1 deletion src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './components/app/app';
import { Settings } from './const';
import { offers } from './mocks/offers';

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

root.render(
<React.StrictMode>
<App placesCount={Settings.placesCount} />
<App placesCount={Settings.placesCount} offers={offers}/>
</React.StrictMode>
);
49 changes: 49 additions & 0 deletions src/mocks/offers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { Offer } from '../types/offer';
import { reviews } from './reviews';

export const offers: Offer[] = [
{
id: '1',
image: ['img/apartment-01.jpg'],
isPremium: true,
price: 120,
title: 'Beautiful & luxurious studio at great location',
type: 'Apartment',
isFavorite: false,
rating: 4,
reviews: [reviews[0]]
},
{
id: '2',
image: ['img/room.jpg'],
isPremium: false,
price: 80,
title: 'Wood and stone place',
type: 'Room',
isFavorite: true,
rating: 4,
reviews: [reviews[1]]
},
{
id: '3',
image: ['img/apartment-02.jpg'],
isPremium: false,
price: 132,
title: 'Canal View Prinsengracht',
type: 'Apartment',
isFavorite: false,
rating: 4,
reviews: [reviews[2]]
},
{
id: '4',
image: ['img/apartment-03.jpg'],
isPremium: true,
price: 180,
title: 'Nice, cozy, warm big bed apartment',
type: 'Apartment',
isFavorite: true,
rating: 5,
reviews: [reviews[3]]
},
];
36 changes: 36 additions & 0 deletions src/mocks/reviews.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Review } from '../types/review';

export const reviews: Review[] = [
{
id: '1',
avatar: 'img/avatar-angelina.jpg',
author: 'Angelina',
rating: 5,
date: 'March 2022',
comment: 'Beautiful apartments with stunning views, perfect for those who appreciate comfort and beauty.'
},
{
id: '2',
avatar: 'img/avatar-max.jpg',
author: 'Max',
rating: 3,
date: 'December 2023',
comment: 'The room is cozy but lacks sufficient heating, making it uncomfortable during colder months.'
},
{
id: '3',
avatar: 'img/avatar-angelina.jpg',
author: 'Angelina',
rating: 5,
date: 'May 2024',
comment: 'Well equipped apartment in a great location with all possible amenities.'
},
{
id: '4',
avatar: 'img/avatar-max.jpg',
author: 'Max',
rating: 5,
date: 'December 2024',
comment: 'Luxurious apartments with breathtaking views, ideal for those seeking both comfort and elegance in a prime location.'
}
];
Loading
Loading