Skip to content

Commit

Permalink
Merge pull request #14 from Nawwar14/module9-task1
Browse files Browse the repository at this point in the history
  • Loading branch information
keksobot authored Dec 21, 2024
2 parents 1f1fd81 + 10ce00e commit 378c100
Show file tree
Hide file tree
Showing 41 changed files with 2,692 additions and 1,257 deletions.
3,204 changes: 2,002 additions & 1,202 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@
"history": "5.3.0",
"http-status-codes": "2.3.0",
"leaflet": "1.7.1",
"next": "^13.4.2",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-helmet-async": "1.3.0",
"react-redux": "8.1.3",
"react-router-dom": "6.16.0"
"react-router-dom": "6.16.0",
"react-toastify": "^11.0.2"
},
"devDependencies": {
"@jedmao/redux-mock-store": "3.0.5",
Expand Down
15 changes: 9 additions & 6 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ import { HelmetProvider } from 'react-helmet-async';
import MainPage from './components/MainPage/MainPage';
import Favorite from './components/Favorites/Favorite';
import LoginPage from './components/Login/LoginPage';
import NotFoundPage from './components/NotFoundPage/NotFoundPage';

import Offer from './components/Offer/Offer';
import { useAppSelector,useAppDispatch } from './hooks';
import { fetchOfferObjectAction } from './api-actions.ts';
import PrivateRoute from './components/routes/private-route/index.tsx';
import { getAuthStatus } from './store/userselector.ts';
//import LoadingScreen from './components/loading-screen/loading-screen';

export const App: React.FC = () => {
Expand All @@ -16,20 +18,21 @@ export const App: React.FC = () => {
//const isLoading = useAppSelector(getLoadingOfferPage);
const currentCity = useAppSelector((state) => state.currentCity);
const offers = useAppSelector((state) => state.offerPage);
const cities = useAppSelector((state) => state.Cities);

useEffect(() => {
dispatch(fetchOfferObjectAction());
}, [dispatch]);
// eslint-disable-next-line no-unused-expressions

const offerdetails = useAppSelector((state) => state.offerIdDetails);
const authorizationStatus = useAppSelector(getAuthStatus);
return (
<HelmetProvider>
<BrowserRouter>
<Routes>
<Route path="/" element={<MainPage offers={offers.offer} currentCity={currentCity.currentCity} cities={cities.cities}/>} />
<Route path="/" element={<MainPage offers={offers.offer} currentCity={currentCity.currentCity}/>} />
<Route path="/login" element={<LoginPage />} />
<Route path="/offer/:id" element={(offers.offer?.filter((o) => o.id === offerdetails.offer.id).length) > 0 ? <Offer offerdetails={offerdetails.offer} offers={offers.offer} currentCity={currentCity.currentCity}/> : <NotFoundPage/>} />
<Route key="/offer/:id" path="/offer/:id"
element={<PrivateRoute key="/offer/:id" authState={authorizationStatus}>{<Offer />}</PrivateRoute>}
/>
<Route path="/favorites" element={<Favorite offers={offers.offer} />} />
</Routes>
</BrowserRouter>
Expand Down
3 changes: 2 additions & 1 deletion src/action.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { createAction } from '@reduxjs/toolkit';
import { OfferObject, OfferIdDetails } from './types/types';
import { OfferObject, OfferIdDetails, AppRoute } from './types/types';
export const changeCity = createAction<string>('ChangeCity');

export const AddOffer = createAction<OfferObject[]>('AddOffer');
Expand All @@ -9,3 +9,4 @@ export const loadOffers = createAction<OfferObject[]>('data/fetchOffers');
export const loadOfferDetails = createAction<OfferIdDetails>('data/loadOffer');

export const setOffer = createAction<OfferIdDetails>('offer/set');
export const redirectToRoute = createAction<AppRoute>('user/redirectToRoute');
2 changes: 1 addition & 1 deletion src/api-actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ export const postComment = createAsyncThunk<
});
export const setIsOfferFavorite = createAsyncThunk<
void,
{ offerId: string; isFavorite: boolean },
{ offerId: string | undefined; isFavorite: boolean },
{
dispatch: AppDispatch;
state: State;
Expand Down
1 change: 1 addition & 0 deletions src/components/CityList/CityList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export const ListCities: React.FC<CitiesListProps> = ({
{cities.map((city) => (
<li
className="locations__item"
data-testid='location_item'
key={city.title}
onClick={() => {
onSelect(city.title);
Expand Down
6 changes: 3 additions & 3 deletions src/components/Login/LoginPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,18 +71,18 @@ function LoginPage(): JSX.Element {
<form className="login__form form" onSubmit={handleSubmit} action="#" method="post">
<div className="login__input-wrapper form__input-wrapper">
<label className="visually-hidden">E-mail</label>
<input className="login__input form__input" onChange={handleEmailChange} value={email} type="email" name="email" placeholder="Email" required />
<input data-testid = 'email_input' className="login__input form__input" onChange={handleEmailChange} value={email} type="email" name="email" placeholder="Email" required />
</div>
<div className="login__input-wrapper form__input-wrapper">
<label className="visually-hidden">Password</label>
<input className="login__input form__input" onChange={handlePasswordChange} value={password} type="password" pattern="(?=.*\d)(?=.*[a-zA-Z]).{2,}" title="Contains one letter and one digit" name="password" placeholder="Password" required />
<input data-testid = 'password_input' className="login__input form__input" onChange={handlePasswordChange} value={password} type="password" pattern="(?=.*\d)(?=.*[a-zA-Z]).{2,}" title="Contains one letter and one digit" name="password" placeholder="Password" required />
</div>
<button className="login__submit form__submit button" type="submit">Sign in</button>
</form>
</section>
<section className="locations locations--login locations--current">
<div className="locations__item">
<Link className="locations__item-link" to={AppRoute.Main}>
<Link data-testid='location_item-link' className="locations__item-link" to={AppRoute.Main}>
<span>Amsterdam</span>
</Link>
</div>
Expand Down
6 changes: 2 additions & 4 deletions src/components/MainPage/MainPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,10 @@ import { logout } from '../../api-actions.ts';
//import { getToken } from '../../token.ts';
type MainPageProps = {
currentCity: City;
cities: City[];
offers: OfferObject[];
};
export const MainPage : FC<MainPageProps> = ({
currentCity,
cities,
offers,
}:MainPageProps) => {
const navigate = useNavigate();
Expand All @@ -30,7 +28,7 @@ export const MainPage : FC<MainPageProps> = ({
const authStatusMemo = useMemo(() => authStatus,[authStatus]);
const userEmailMemo = useMemo(() => userEmail, [userEmail]);
const offerListMemo = useMemo(() => offers, [offers]);

const cities = useAppSelector((state) => state.Cities);
// const isLoading = useAppSelector(getLoadingOfferPage);
// const offers = useAppSelector(getOffer);
//useEffect(() => {
Expand Down Expand Up @@ -113,7 +111,7 @@ export const MainPage : FC<MainPageProps> = ({
<h1 className="visually-hidden">Cities</h1>
<div className="tabs">
<section className="locations container">
<ListCities currentCity={currentCity.title} cities={cities} onSelect={handleUserSelectCity}/>
<ListCities currentCity={currentCity.title} cities={cities.cities} onSelect={handleUserSelectCity}/>
</section>
</div>
<div className="cities">
Expand Down
2 changes: 1 addition & 1 deletion src/components/NotFoundPage/NotFoundPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ function NotFoundPage():JSX.Element{
</li>
</ul>
</section>
<h2 style={{ textAlign : 'center'}}>Error 404. Page not found. <Link to = "/"> Back to main page</Link></h2>
<h2 style={{ textAlign : 'center'}}>404 Not Found. Page not found. <Link to = "/"> Back to main page</Link></h2>
<div className="cities__right-section">
<section className="cities__map map"></section>
</div>
Expand Down
60 changes: 26 additions & 34 deletions src/components/Offer/Offer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,42 +6,34 @@ import { useAppDispatch, useAppSelector } from '../../hooks/index';
//import { offers } from '../../mock/offers';

import Map from '../Map/Map';
import { AppRoute, UserReview, OfferObject, City,OfferIdDetails, CardCssNameList } from '../../types/types';
import { AppRoute, UserReview, CardCssNameList } from '../../types/types';
import { AuthorizationStatus } from '../../const.ts';
import OfferList from './OfferList.tsx';
import { getAuthStatus,getUserEmail} from '../../store/userselector.ts';
import { fetchComments, fetchOffer, fetchOfferNeibourhood, logout, setIsOfferFavorite } from '../../api-actions.ts';
import classNames from 'classnames';
import { useEffect, useState } from 'react';
type OfferProps = {
offerdetails:OfferIdDetails;
offers: OfferObject[] | null;
currentCity: City;
};

export const Offer: React.FC<OfferProps> = ({
// eslint-disable-next-line react/prop-types
offerdetails,
offers,
currentCity,
}) => {
export const Offer: React.FC = () => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
const { id: idOffer } = useParams();
const dispatch = useAppDispatch();
const currentCity = useAppSelector((state) => state.currentCity);
useEffect(()=>{
dispatch(fetchOffer(idOffer ?? ''));
dispatch(fetchOfferNeibourhood(idOffer ?? ''));
dispatch(fetchComments(idOffer ?? ''));
},[idOffer,dispatch]);
const nearbyOffers = useAppSelector((store) => store.offerIdDetails.nearbyOffers);
const offerdetails = useAppSelector((store) => store.offerIdDetails.offer);
const offers = useAppSelector((state) => state.offerPage);
const comments:UserReview[] = useAppSelector((store) => store.offerIdDetails.comments);
const userEmail = useAppSelector(getUserEmail);
const authStatus = useAppSelector(getAuthStatus);
const [ isFavorite, setisFavorite ] = useState(false);
const navigate = useNavigate();
useEffect(() => {
setisFavorite(offerdetails.isFavorite);
}, [offerdetails.isFavorite]);
setisFavorite(offerdetails?.isFavorite ?? false);
}, [offerdetails?.isFavorite]);

const onFavoriteClick = () => {
if(authStatus === AuthorizationStatus.NoAuth || authStatus === AuthorizationStatus.Unknown) {
Expand All @@ -51,7 +43,7 @@ export const Offer: React.FC<OfferProps> = ({
}
dispatch(
setIsOfferFavorite({
offerId: offerdetails.id,
offerId: offerdetails?.id,
isFavorite: !isFavorite
}),
);
Expand Down Expand Up @@ -117,7 +109,7 @@ export const Offer: React.FC<OfferProps> = ({
<section className="offer">
<div className="offer__gallery-container container">
<div className="offer__gallery">
{offerdetails.images.map((image) => (
{offerdetails?.images.map((image) => (
<div key={image} className="offer__image-wrapper">
<img className="offer__image" src={image} alt="Фото студии" />
</div>
Expand All @@ -126,14 +118,14 @@ export const Offer: React.FC<OfferProps> = ({
</div>
<div className="offer__container container">
<div className="offer__wrapper">
{offerdetails.isPremium ? (
{offerdetails?.isPremium ? (
<div className="offer__mark">
<span>Premium</span>
</div>
) : null}
<div className="offer__name-wrapper">
<h1 className="offer__name">
{offerdetails.title}
{offerdetails?.title}
</h1>
{authStatus && (
<button
Expand All @@ -153,30 +145,30 @@ export const Offer: React.FC<OfferProps> = ({
</div>
<div className="offer__rating rating">
<div className="offer__stars rating__stars">
<span style={{width: `${(offerdetails.rating / 5) * 100}%`}}></span>
<span style={{width: `${(offerdetails?.rating ?? 0 / 5) * 100}%`}}></span>
<span className="visually-hidden">Rating</span>
</div>
<span className="offer__rating-value rating__value">{offerdetails.rating}</span>
<span className="offer__rating-value rating__value">{offerdetails?.rating}</span>
</div>
<ul className="offer__features">
<li className="offer__feature offer__feature--entire">
{offerdetails.type}
{offerdetails?.type}
</li>
<li className="offer__feature offer__feature--bedrooms">
{offerdetails.bedrooms} Bedrooms
{offerdetails?.bedrooms} Bedrooms
</li>
<li className="offer__feature offer__feature--adults">
Max {offerdetails.maxAdults} Adults
Max {offerdetails?.maxAdults} Adults
</li>
</ul>
<div className="offer__price">
<b className="offer__price-value">&euro;{offerdetails.price}</b>
<b className="offer__price-value">&euro;{offerdetails?.price}</b>
<span className="offer__price-text">&nbsp;night</span>
</div>
<div className="offer__inside">
<h2 className="offer__inside-title">What&apos;s inside</h2>
<ul className="offer__inside-list">
{offerdetails.goods.map((ins) => (
{offerdetails?.goods.map((ins) => (
<li key={ins} className="offer__inside-item">
{ins}
</li>
Expand All @@ -187,18 +179,18 @@ export const Offer: React.FC<OfferProps> = ({
<h2 className="offer__host-title">Meet the host</h2>
<div className="offer__host-user user">
<div className="offer__avatar-wrapper offer__avatar-wrapper--pro user__avatar-wrapper">
<img className="offer__avatar user__avatar" src={offerdetails.host.avatarUrl} width="74" height="74" alt="Host avatar"/>
<img className="offer__avatar user__avatar" src={offerdetails?.host.avatarUrl} width="74" height="74" alt="Host avatar"/>
</div>
<span className="offer__user-name">
{offerdetails.host.name}
{offerdetails?.host.name}
</span>
<span className="offer__user-status">
{offerdetails.host.isPro}
{offerdetails?.host.isPro}
</span>
</div>
<div className="offer__description">
<p className="offer__text">
{offerdetails.description}
{offerdetails?.description}
</p>
</div>
</div>
Expand All @@ -207,10 +199,10 @@ export const Offer: React.FC<OfferProps> = ({
</div>
<section className="offer__map map">
<Map
offers={offers === null ? undefined : [...offers]}
selectedPoint={offers?.[1]}
currentCity={currentCity}
activeOffer={offers === null ? null : offers[1].id}
offers={offers === null ? undefined : [...offers.offer]}
selectedPoint={offers.offer?.[1]}
currentCity={currentCity.currentCity}
activeOffer={offers.offer === null ? null : offers.offer[1].id}
/>
</section>
</section>
Expand Down
27 changes: 27 additions & 0 deletions src/components/routes/history-route/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import {useState, useLayoutEffect} from 'react';
import {Router} from 'react-router-dom';
import { HistoryRouterProps } from './types';

export default function HistoryRouter({
basename,
children,
history,
}: HistoryRouterProps) {
const [state, setState] = useState({
action: history.action,
location: history.location,
});

useLayoutEffect(() => history.listen(setState), [history]);

return (
<Router
basename={basename}
location={state.location}
navigationType={state.action}
navigator={history}
>
{children}
</Router>
);
}
7 changes: 7 additions & 0 deletions src/components/routes/history-route/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { BrowserHistory } from 'history';

export interface HistoryRouterProps {
history: BrowserHistory;
basename?: string;
children?: React.ReactNode;
}
13 changes: 13 additions & 0 deletions src/components/routes/private-route/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import {Navigate} from 'react-router-dom';
import { PropsWithChildren } from 'react';
import { routesEnum } from '../../../shared/config';
import { AuthorizationStatus } from '../../../const';

interface IPrivateRoute extends PropsWithChildren{
authState: AuthorizationStatus;
}
function PrivateRoute({children, authState}: IPrivateRoute) {
return authState === AuthorizationStatus.Auth ? children : <Navigate to={routesEnum.LOGIN} />;
}

export default PrivateRoute;
Loading

0 comments on commit 378c100

Please sign in to comment.