Skip to content

Commit

Permalink
Merge pull request #11 from GooDeene/module7-task2
Browse files Browse the repository at this point in the history
  • Loading branch information
keksobot authored May 22, 2024
2 parents f9ee4e9 + 5b281e3 commit 725441a
Show file tree
Hide file tree
Showing 26 changed files with 382 additions and 98 deletions.
23 changes: 22 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@
"react-helmet-async": "1.3.0",
"react-loader-spinner": "^6.1.6",
"react-redux": "8.1.3",
"react-router-dom": "6.16.0"
"react-router-dom": "6.16.0",
"react-toastify": "^10.0.5"
},
"devDependencies": {
"@jedmao/redux-mock-store": "3.0.5",
Expand Down
19 changes: 6 additions & 13 deletions src/components/app/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,16 @@ import PrivateRoute from '../private-route/private-route.tsx';
import FavoritesPage from '../../pages/favorites-page/favorites-page.tsx';
import OfferPage from '../../pages/offer-page/offer-page.tsx';
import NotFoundPage from '../../pages/not-found-page/not-found-page.tsx';
import {OffersShort} from '../../types/offers/offer-short.ts';
import {OffersDetailed} from '../../types/offers/offer-detailed.ts';
import {useAppSelector} from '../../hooks/redux.ts';
import LoadingSpinner from '../loading-spinner/loading-spinner.tsx';

type AppProps = {
offersDetailed: OffersDetailed;
allFavorites: OffersShort;
}

function App({offersDetailed, allFavorites}: AppProps): JSX.Element {
function App(): JSX.Element {
const isOffersLoading = useAppSelector((state) => state.isOffersLoading);

const authStatus = useAppSelector((state) => state.authorizationStatus);

return (
<>
{isOffersLoading && <LoadingSpinner/>}
{(isOffersLoading || authStatus === AuthStatus.Unknown) && <LoadingSpinner/>}
<BrowserRouter>
<Routes>
<Route
Expand All @@ -38,15 +31,15 @@ function App({offersDetailed, allFavorites}: AppProps): JSX.Element {
<Route
path={AppRoute.Favourites}
element={
<PrivateRoute authorizationStatus={AuthStatus.Auth}>
<FavoritesPage allFavorites={allFavorites}/>
<PrivateRoute>
<FavoritesPage />
</PrivateRoute>
}
/>
<Route path={AppRoute.Offer}>
<Route index element={<NotFoundPage/>}/>
<Route path={':id'}
element={<OfferPage offersDetailed={offersDetailed}/>}
element={<OfferPage />}
/>
</Route>
<Route
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,19 @@ import DropdownOffersFilterOption from './dropdown-offers-filter-option.tsx';
import {useEffect, useState} from 'react';
import {setOffers} from '../../store/acions.ts';
import {SortComparers} from '../../const.ts';
import {fetchOffers} from '../../store/api-actions.ts';

import {fetchOffersAction} from '../../store/api-actions.ts';

function DropdownOffersFilter() {
const dispatch = useAppDispatch();
const offers = useAppSelector((store) => store.offers);
const currentSortingOrder = useAppSelector((store) => store.sortingOrder);
const offers = useAppSelector((state) => state.offers);
const currentSortingOrder = useAppSelector((state) => state.sortingOrder);
const [dropdownIsOpen, setDropdownIsOpen] = useState<boolean>(false);

const handleSortingOnClick = () => setDropdownIsOpen((value) => !value);

useEffect(() => {
if (currentSortingOrder === SortingOrder.popular && offers.length !== 0) {
dispatch(fetchOffers());
dispatch(fetchOffersAction());
} else if (offers.length !== 0) {
dispatch(setOffers(offers.toSorted(SortComparers[currentSortingOrder])));
}
Expand Down
47 changes: 34 additions & 13 deletions src/components/header/header-navigation.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,44 @@
import {Link} from 'react-router-dom';
import {AppRoute} from '../../const.ts';
import {useAuthorization} from '../../hooks/use-authorization.ts';
import {useAppDispatch, useAppSelector} from '../../hooks/redux.ts';
import {logoutAction} from '../../store/api-actions.ts';

function HeaderNavigation(): JSX.Element {
const isAuthorized = useAuthorization();
const dispatch = useAppDispatch();
const userData = useAppSelector((state) => state.userData);

const handleLinkClick = () => {
dispatch(logoutAction());
};

return (
<nav className="header__nav">
<ul className="header__nav-list">
<li className="header__nav-item user">
<Link className="header__nav-link header__nav-link--profile" to={AppRoute.Favourites}>
<div className="header__avatar-wrapper user__avatar-wrapper">
</div>
<span className="header__user-name user__name">{'[email protected]'}</span>
<span className="header__favorite-count">{3}</span>
</Link>
</li>
<li className="header__nav-item">
<a className="header__nav-link" href="#">
<span className="header__signout">Sign out</span>
</a>
</li>
{!isAuthorized &&
<li className="header__nav-item">
<Link className="header__nav-link" to={AppRoute.Login}>
<div className="header__avatar-wrapper user__avatar-wrapper"/>
<span className="header__signout">Sign in</span>
</Link>
</li>}
{isAuthorized &&
<>
<li className="header__nav-item user">
<Link className="header__nav-link header__nav-link--profile" to={AppRoute.Favourites}>
<div className="header__avatar-wrapper user__avatar-wrapper">
</div>
<span className="header__user-name user__name">{userData?.email}</span>
<span className="header__favorite-count">{3}</span>
</Link>
</li>
<li className="header__nav-item">
<Link className="header__nav-link" to={''} onClick={handleLinkClick}>
<span className="header__signout">Sign out</span>
</Link>
</li>
</>}
</ul>
</nav>
);
Expand Down
2 changes: 1 addition & 1 deletion src/components/offer/offer-card-list/offer-card-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ function OfferCardList({cityName, offers, variant, offersCount}: OfferCardListPr
<section className={sectionClassName}>
{isHeaderActive && <h2 className={headerClassName}>{headerContent}</h2>}
{isFoundActive && <b className="places__found">{offersCount} places to stay in {cityName}</b>}
{isFormActive && <DropdownOffersFilter/>}
{isFormActive && <DropdownOffersFilter />}
<OfferCardListContainer offers={offers} className={containerClassName} onCardHover={handleHoverCard}/>
</section>
);
Expand Down
4 changes: 2 additions & 2 deletions src/components/offer/offer-host.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {User} from '../../types/user.ts';
import {UserData} from '../../types/user-data.ts';

type OfferHostProps = {
host: User;
host: UserData;
description: string;
}

Expand Down
20 changes: 7 additions & 13 deletions src/components/private-route/private-route.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,14 @@
import {Navigate} from 'react-router-dom';
import {AppRoute, AuthStatus} from '../../const';
import {PropsWithChildren} from 'react';
import {PropsWithChildren, ReactNode} from 'react';
import {useAppSelector} from '../../hooks/redux.ts';

type PrivateRouteProps = {
authorizationStatus: AuthStatus;
children: JSX.Element;
}

function PrivateRoute(props: PropsWithChildren<PrivateRouteProps>): JSX.Element {
const {authorizationStatus, children} = props;
function PrivateRoute({children}: PropsWithChildren): ReactNode {
const authorizationStatus = useAppSelector((state) => state.authorizationStatus);

return (
authorizationStatus === AuthStatus.Auth
? children
: <Navigate to={AppRoute.Login} />
);
return authorizationStatus === AuthStatus.Auth
? children
: <Navigate to={AppRoute.Login} />;
}

export default PrivateRoute;
2 changes: 2 additions & 0 deletions src/const.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ export enum AuthStatus {

export enum APIRoute {
Offers = '/offers',
Login = '/login',
Logout = '/logout',
}

export const SortComparers : Record<SortingOrder, (a: OfferShort, b: OfferShort) => number> = {
Expand Down
6 changes: 6 additions & 0 deletions src/hooks/use-authorization.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import {useAppSelector} from './redux.ts';
import {AuthStatus} from '../const.ts';

export function useAuthorization() {
return useAppSelector((state) => state.authorizationStatus) === AuthStatus.Auth;
}
13 changes: 7 additions & 6 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,22 @@ import ReactDOM from 'react-dom/client';
import App from './components/app/app.tsx';
import {Provider} from 'react-redux';
import {store} from './store';
import {fetchOffers} from './store/api-actions.ts';
import {checkAuthStatusAction, fetchOffersAction} from './store/api-actions.ts';
import {ToastContainer} from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';

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

store.dispatch(fetchOffers());
store.dispatch(checkAuthStatusAction());
store.dispatch(fetchOffersAction());

root.render(
<React.StrictMode>
<Provider store={store}>
<App
offersDetailed={new Map()}
allFavorites={[]}
/>
<ToastContainer />
<App />
</Provider>
</React.StrictMode>
);
9 changes: 2 additions & 7 deletions src/pages/favorites-page/favorites-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,16 @@ import Header from '../../components/header/header.tsx';
import {Link} from 'react-router-dom';
import {AppRoute} from '../../const.ts';
import FavoritesList from '../../components/favorites-list/favorites-list.tsx';
import {OffersShort} from '../../types/offers/offer-short.ts';

type FavoritesPageProps = {
allFavorites: OffersShort;
}

function FavoritesPage({ allFavorites } : FavoritesPageProps) : JSX.Element {
function FavoritesPage() : JSX.Element {
return (
<div className="page">
<Header />
<main className="page__main page__main--favorites">
<div className="page__favorites-container container">
<section className="favorites">
<h1 className="favorites__title">Saved listing</h1>
<FavoritesList allFavorites={allFavorites} />
<FavoritesList allFavorites={[]} />
</section>
</div>
</main>
Expand Down
75 changes: 67 additions & 8 deletions src/pages/login-page/login-page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,44 @@
import Header from '../../components/header/header.tsx';
import {AppRoute, CityObject} from '../../const.ts';
import {Link, useNavigate} from 'react-router-dom';
import {FormEvent, useRef} from 'react';
import {useAppDispatch} from '../../hooks/redux.ts';
import {loginAction} from '../../store/api-actions.ts';
import {setActiveCity} from '../../store/acions.ts';
import {useAuthorization} from '../../hooks/use-authorization.ts';

function LoginPage(): JSX.Element {
const loginRef = useRef<HTMLInputElement | null>(null);
const passwordRef = useRef<HTMLInputElement | null>(null);

const dispatch = useAppDispatch();
const navigate = useNavigate();

const cities = Object.values(CityObject);
const chosenCity = cities[Math.floor(Math.random() * cities.length)];

const isAuthorized = useAuthorization();

if (isAuthorized) {
navigate(AppRoute.Main);
}

const handleLinkOnClick = () => {
dispatch(setActiveCity(chosenCity));
};

const handleSubmit = (evt: FormEvent<HTMLFormElement>) => {
evt.preventDefault();

if (loginRef.current !== null && passwordRef.current !== null) {
dispatch(loginAction({
login: loginRef.current.value,
password: passwordRef.current.value
}));
}
};


function LoginPage() : JSX.Element {
return (
<div className="page page--gray page--login">
<Header disableNav/>
Expand All @@ -9,23 +47,44 @@ function LoginPage() : JSX.Element {
<div className="page__login-container container">
<section className="login">
<h1 className="login__title">Sign in</h1>
<form className="login__form form" action="#" method="post">
<form className="login__form form" action="" onSubmit={handleSubmit}>
<div className="login__input-wrapper form__input-wrapper">
<label className="visually-hidden">E-mail</label>
<input className="login__input form__input" type="email" name="email" placeholder="Email" required />
<input
className="login__input form__input"
type="email"
name="email"
placeholder="Email"
ref={loginRef}
/>
</div>
<div className="login__input-wrapper form__input-wrapper">
<label className="visually-hidden">Password</label>
<input className="login__input form__input" type="password" name="password" placeholder="Password" required />
<input
className="login__input form__input"
type="password"
name="password"
placeholder="Password"
ref={passwordRef}
required
/>
</div>
<button className="login__submit form__submit button" type="submit">Sign in</button>
<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">
<a className="locations__item-link" href="#">
<span>Amsterdam</span>
</a>
<Link
className="locations__item-link"
to={AppRoute.Main}
onClick={handleLinkOnClick}
>
<span>{chosenCity.name}</span>
</Link>
</div>
</section>
</div>
Expand Down
Loading

0 comments on commit 725441a

Please sign in to comment.