Skip to content

Commit

Permalink
done task
Browse files Browse the repository at this point in the history
  • Loading branch information
mgmman committed Nov 20, 2024
1 parent aa4e18c commit 99b916f
Show file tree
Hide file tree
Showing 15 changed files with 246 additions and 395 deletions.
1 change: 1 addition & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
<title>6 cities</title>
<link rel="stylesheet" href="css/main.css">
<link rel="stylesheet" href="src/pages/not-found-page/not-found-page.css">
<link rel="stylesheet" href="src/components/spinner/spinner.css">
</head>

<body>
Expand Down
11 changes: 3 additions & 8 deletions src/Pages/favorites-page/favorites-page.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
import { Helmet } from 'react-helmet-async';
import { Layout } from '../../components/layout.tsx';
import { OfferGroup } from '../../components/offer/offer-group.tsx';
import { Offer } from '../../dataTypes/offer.ts';
import {useAppSelector} from '../../store/store.ts';

interface FavoritesPageProps {
offers: Offer[];
}

export function FavoritesPage({
offers,
}: FavoritesPageProps): React.JSX.Element {
export function FavoritesPage(): React.JSX.Element {
const offers = useAppSelector((state) => state.offers).filter((offer) => offer.isFavorite);
return (
<div className="page">
<Layout showFooter>
Expand Down
173 changes: 93 additions & 80 deletions src/Pages/offer-page/offer-page.tsx
Original file line number Diff line number Diff line change
@@ -1,108 +1,121 @@
import { Helmet } from 'react-helmet-async';
import { Layout } from '../../components/layout.tsx';
import { OffersList } from '../../components/offer/offers-list.tsx';
import { offerMocks } from '../../mocks/offers.ts';
import { Reviews } from '../../components/reviews/reviews.tsx';
import { reviewMocks } from '../../mocks/reviews.ts';
import { Map } from '../../components/map/map.tsx';
import React from 'react';
import React, { useEffect } from 'react';
import { useParams } from 'react-router-dom';
import { OfferInsideItems } from '../../components/offer/offer-inside-items.tsx';
import { detailedOfferMocks } from '../../mocks/detailed-offer.ts';
import { OfferHost } from '../../components/offer/offer-host.tsx';
import { capitalize, pluralizeAndCombine } from '../../utils/string-utils.ts';
import { Rating } from '../../components/rating.tsx';
import { OfferGallery } from '../../components/offer/offer-gallery.tsx';
import { BookmarkButton } from '../../components/bookmark-button.tsx';
import { store, useAppSelector } from '../../store/store.ts';
import {
fetchNearbyOffers,
fetchOffer,
setCurrentOffer,
} from '../../store/actions.ts';
import { Spinner } from '../../components/spinner/Spinner.tsx';

export function OfferPage(): React.JSX.Element {
const offerId = useParams().id;
const offers = offerMocks.filter((offer) => offer.id !== offerId).slice(0, 3);
const currentOffer = detailedOfferMocks.find(
(offer) => offer.id === offerId,
)!;
useEffect(() => {
store.dispatch(setCurrentOffer(null));
store.dispatch(fetchOffer(offerId!));
store.dispatch(fetchNearbyOffers(offerId!));
}, [offerId]);
const nearbyOffers = useAppSelector((state) => state.nearbyOffers).slice(
0,
3,
);
const currentOffer = useAppSelector((state) => state.currentOffer);
return (
<div className="page">
<Layout>
<main className="page__main page__main--offer">
<Helmet>6 cities - offer</Helmet>
<section className="offer">
<OfferGallery imageSources={currentOffer.images} />
<div className="offer__container container">
<div className="offer__wrapper">
{currentOffer.isPremium && (
<div className="offer__mark">
<span>Premium</span>
{!currentOffer ? (
<Spinner caption={'Loading...'} />
) : (
<>
<section className="offer">
<OfferGallery imageSources={currentOffer.images} />
<div className="offer__container container">
<div className="offer__wrapper">
{currentOffer.isPremium && (
<div className="offer__mark">
<span>Premium</span>
</div>
)}
<div className="offer__name-wrapper">
<h1 className="offer__name">{currentOffer.title}</h1>
<BookmarkButton
size="big"
isFavorite={currentOffer.isFavorite}
usagePlace="offer"
/>
</div>
<Rating
rating={currentOffer.rating}
usePlace="offer"
isInOffer
/>
<ul className="offer__features">
<li className="offer__feature offer__feature--entire">
{capitalize(currentOffer.type)}
</li>
<li className="offer__feature offer__feature--bedrooms">
{pluralizeAndCombine('bedroom', currentOffer.bedrooms)}
</li>
<li className="offer__feature offer__feature--adults">
Max{' '}
{pluralizeAndCombine('adult', currentOffer.maxAdults)}
</li>
</ul>
<div className="offer__price">
<b className="offer__price-value">
&euro;{currentOffer.price}
</b>
<span className="offer__price-text">&nbsp;night</span>
</div>
<OfferInsideItems items={currentOffer.goods} />
<div className="offer__host">
<OfferHost host={currentOffer.host} />
<div className="offer__description">
<p className="offer__text">
{currentOffer.description}
</p>
</div>
</div>
<Reviews reviews={[]} />
</div>
)}
<div className="offer__name-wrapper">
<h1 className="offer__name">{currentOffer.title}</h1>
<BookmarkButton size='big' isFavorite={currentOffer.isFavorite} usagePlace='offer' />
</div>
<Rating
rating={currentOffer.rating}
usePlace="offer"
isInOffer
<Map
city={currentOffer.city}
points={[...nearbyOffers, currentOffer].map((x) => ({
location: x.location,
id: x.id,
}))}
selectedPoint={{
location: currentOffer.location,
id: currentOffer.id,
}}
/>
<ul className="offer__features">
<li className="offer__feature offer__feature--entire">
{capitalize(currentOffer.type)}
</li>
<li className="offer__feature offer__feature--bedrooms">
{pluralizeAndCombine('bedroom', currentOffer.bedrooms)}
</li>
<li className="offer__feature offer__feature--adults">
Max {pluralizeAndCombine('adult', currentOffer.maxAdults)}
</li>
</ul>
<div className="offer__price">
<b className="offer__price-value">
&euro;{currentOffer.price}
</b>
<span className="offer__price-text">&nbsp;night</span>
</div>
<OfferInsideItems items={currentOffer.goods} />
<div className="offer__host">
<OfferHost host={currentOffer.host} />
<div className="offer__description">
<p className="offer__text">
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.
</p>
<p className="offer__text">
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.
</p>
</section>
<div className="container">
<section className="near-places places">
<h2 className="near-places__title">
Other places in the neighbourhood
</h2>
<div className="near-places__list places__list">
{nearbyOffers && <OffersList offers={nearbyOffers} />}
</div>
</div>
<Reviews reviews={reviewMocks} />
</div>
</div>
<Map
city={offers[0].city}
points={[...offers, currentOffer].map((x) => ({
location: x.location,
id: x.id,
}))}
selectedPoint={{
location: currentOffer.location,
id: currentOffer.id,
}}
/>
</section>
<div className="container">
<section className="near-places places">
<h2 className="near-places__title">
Other places in the neighbourhood
</h2>
<div className="near-places__list places__list">
<OffersList offers={offers} />
</section>
</div>
</section>
</div>
</>
)}
</main>
</Layout>
</div>
Expand Down
13 changes: 13 additions & 0 deletions src/api/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import axios, {AxiosInstance} from 'axios';

const BACKEND_URL = 'https://14.design.htmlacademy.pro/six-cities';
const REQUEST_TIMEOUT = 5000;

export const createAPI = (): AxiosInstance => {
const api = axios.create({
baseURL: BACKEND_URL,
timeout: REQUEST_TIMEOUT,
});

return api;
};
9 changes: 2 additions & 7 deletions src/components/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,10 @@ import { NotFoundPage } from '../pages/not-found-page/not-found-page.tsx';
import { AuthorizationWrapper } from './authorization-wrapper.tsx';
import { AppRoutes } from '../dataTypes/enums/app-routes.ts';
import { HelmetProvider } from 'react-helmet-async';
import { Offer } from '../dataTypes/offer.ts';
import { Provider } from 'react-redux';
import { store } from '../store/store.ts';

interface AppProps {
offers: Offer[];
}

export function App({ offers }: AppProps): React.JSX.Element {
export function App(): React.JSX.Element {
return (
<Provider store={store}>
<HelmetProvider>
Expand All @@ -27,7 +22,7 @@ export function App({ offers }: AppProps): React.JSX.Element {
path={AppRoutes.Favorites}
element={
<AuthorizationWrapper isAuthorized={false}>
<FavoritesPage offers={offers} />
<FavoritesPage />
</AuthorizationWrapper>
}
/>
Expand Down
10 changes: 7 additions & 3 deletions src/components/offer/offer-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export function OfferCard({
onMouseLeave={handleMouseLeave}
className={cn(
'place-card',
{ 'cities__card': isOnMainPage },
{ cities__card: isOnMainPage },

Check failure on line 43 in src/components/offer/offer-card.tsx

View workflow job for this annotation

GitHub Actions / Check

Identifier 'cities__card' is not in camel case
{ 'near-places__card': !isOnMainPage },
)}
>
Expand All @@ -59,7 +59,7 @@ export function OfferCard({
<Link to={`${AppRoutes.Offer}/${id}`}>
<img
className="place-card__image"
src={`../../markup/img/${image}`}
src={image}
width="260"
height="200"
alt="Place image"
Expand All @@ -72,7 +72,11 @@ export function OfferCard({
<b className="place-card__price-value">&euro;{price}</b>
<span className="place-card__price-text">&#47;&nbsp;night</span>
</div>
<BookmarkButton size='small' isFavorite={isFavorite} usagePlace='place-card' />
<BookmarkButton
size="small"
isFavorite={isFavorite}
usagePlace="place-card"
/>
</div>
<Rating rating={rating} usePlace="place-card" />
<h2 className="place-card__name">
Expand Down
12 changes: 12 additions & 0 deletions src/components/spinner/Spinner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
interface SpinnerProps {
caption?: string;
}

export function Spinner({ caption }: SpinnerProps) {
return (
<>
<div className="spinner"></div>
{caption && <span>{caption}</span>}
</>
);
}
14 changes: 14 additions & 0 deletions src/components/spinner/spinner.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
.spinner {
border: 4px solid rgba(0, 0, 0, 0.1);
border-left-color: #09f;
border-radius: 50%;
width: 50px;
height: 50px;
animation: spin 1s linear infinite;
}

@keyframes spin {
to {
transform: rotate(360deg);
}
}
3 changes: 3 additions & 0 deletions src/dataTypes/enums/api-routes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export enum ApiRoutes {
Offers = '/offers',
}
7 changes: 5 additions & 2 deletions src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import { App } from './components/app.tsx';
import { offerMocks } from './mocks/offers.ts';
import { store } from './store/store.ts';
import { fetchOffers } from './store/actions.ts';

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

store.dispatch(fetchOffers());

root.render(
<React.StrictMode>
<App offers={offerMocks} />
<App />
</React.StrictMode>,
);
Loading

0 comments on commit 99b916f

Please sign in to comment.