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

Добро пожаловать, или посторонним вход воспрещён (часть 1) #11

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
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,24 +4,23 @@
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])));
}
}, [currentSortingOrder]);

Check warning on line 23 in src/components/dropdown-offers-filter/dropdown-offers-filter.tsx

View workflow job for this annotation

GitHub Actions / Check

React Hook useEffect has missing dependencies: 'dispatch' and 'offers'. Either include them or remove the dependency array

return (
<form className="places__sorting" action="#" method="get">
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
Loading