Skip to content

Commit

Permalink
done task
Browse files Browse the repository at this point in the history
  • Loading branch information
mgmman committed Nov 21, 2024
1 parent 99b916f commit 22abdf5
Show file tree
Hide file tree
Showing 11 changed files with 296 additions and 93 deletions.
144 changes: 82 additions & 62 deletions src/Pages/login-page/login-page.tsx
Original file line number Diff line number Diff line change
@@ -1,71 +1,91 @@
import { Helmet } from 'react-helmet-async';
import { FormEvent, useState } from 'react';
import { Layout } from '../../components/layout.tsx';
import { LoginInfo } from '../../dataTypes/user.ts';
import { store } from '../../store/store.ts';
import { login } from '../../store/actions.ts';

export function LoginPage(): React.JSX.Element {
const [loginInfo, setLoginInfo] = useState<LoginInfo>({
email: '',
password: '',
});
const handleSubmit = (e: FormEvent) => {
e.preventDefault();
store.dispatch(login(loginInfo));
};

const validateEmail = (email: string) => {
const re = /^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}$/;
return re.test(String(email).toLowerCase());
};
const isValid = () =>
loginInfo.email &&
validateEmail(loginInfo.email) &&
loginInfo.password &&
loginInfo.password.length > 3 &&
loginInfo.password.match(/[a-zA-z]/g) &&
loginInfo.password.match(/[0-9]/g);
return (
<div className="page page--gray page--login">
<header className="header">
<div className="container">
<div className="header__wrapper">
<div className="header__left">
<a className="header__logo-link" href="main.html">
<img
className="header__logo"
src="img/logo.svg"
alt="6 cities logo"
width="81"
height="41"
/>
</a>
</div>
</div>
</div>
</header>

<main className="page__main page__main--login">
<Helmet>
<title>6 cities - login</title>
</Helmet>
<div className="page__login-container container">
<section className="login">
<h1 className="login__title">Sign in</h1>
<form className="login__form form" action="#" method="post">
<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
/>
<Layout dontShowUserInfo>
<main className="page__main page__main--login">
<Helmet>
<title>6 cities - login</title>
</Helmet>
<div className="page__login-container container">
<section className="login">
<h1 className="login__title">Sign in</h1>
<form className="login__form form">
<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"
onChange={(event) =>
setLoginInfo({ ...loginInfo, email: event.target.value })
}

Check failure on line 49 in src/Pages/login-page/login-page.tsx

View workflow job for this annotation

GitHub Actions / Check

Unexpected newline before '}'
required
/>
</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"
onChange={(event) =>
setLoginInfo({
...loginInfo,
password: event.target.value,
})
}

Check failure on line 65 in src/Pages/login-page/login-page.tsx

View workflow job for this annotation

GitHub Actions / Check

Unexpected newline before '}'
required
/>
</div>
<button
className="login__submit form__submit button"
type="submit"
onClick={handleSubmit}
disabled={!isValid()}
>
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>
</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
/>
</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">
<a className="locations__item-link" href="#">
<span>Amsterdam</span>
</a>
</div>
</section>
</div>
</main>
</section>
</div>
</main>
</Layout>
</div>
);
}
37 changes: 36 additions & 1 deletion src/api/api.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,48 @@
import axios, {AxiosInstance} from 'axios';
import axios, {
AxiosError,
AxiosInstance,
InternalAxiosRequestConfig,
} from 'axios';
import { getToken } from '../utils/token-utils.ts';
import { store } from '../store/store.ts';
import { AuthorizationStatus } from '../dataTypes/enums/authorization-status.ts';
import { setAuthorizationStatus } from '../store/actions.ts';

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

type DetailMessageType = {
errorType: string;
message: string;
};

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

api.interceptors.request.use((config: InternalAxiosRequestConfig) => {
const token = getToken();

if (token && config.headers) {
config.headers['x-token'] = token;
}

return config;
});

api.interceptors.response.use(
(response) => response,
(error: AxiosError<DetailMessageType>) => {
if (error.response && error.response.status === 401) {
store.dispatch(
setAuthorizationStatus(AuthorizationStatus.Unauthorized),
);
}
throw error;
},
);

return api;
};
22 changes: 18 additions & 4 deletions src/components/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ import { LoginPage } from '../pages/login-page/login-page.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 { AuthorizationWrapper } from './authorization-wrapper.tsx';
import {
AuthorizationWrapperForAuthorizedOnly,
AuthorizationWrapperForUnauthorizedOnly,
} from './authorization-wrapper.tsx';
import { AppRoutes } from '../dataTypes/enums/app-routes.ts';
import { HelmetProvider } from 'react-helmet-async';
import { Provider } from 'react-redux';
Expand All @@ -17,13 +20,24 @@ export function App(): React.JSX.Element {
<BrowserRouter>
<Routes>
<Route path={AppRoutes.MainPage} element={<MainPage />} />
<Route path={AppRoutes.Login} element={<LoginPage />} />
<Route
path={AppRoutes.Login}
element={
<AuthorizationWrapperForUnauthorizedOnly
fallbackUrl={AppRoutes.MainPage}
>
<LoginPage />
</AuthorizationWrapperForUnauthorizedOnly>
}
/>
<Route
path={AppRoutes.Favorites}
element={
<AuthorizationWrapper isAuthorized={false}>
<AuthorizationWrapperForAuthorizedOnly
fallbackUrl={AppRoutes.Login}
>
<FavoritesPage />
</AuthorizationWrapper>
</AuthorizationWrapperForAuthorizedOnly>
}
/>
<Route path={`${AppRoutes.Offer}/:id`} element={<OfferPage />} />
Expand Down
24 changes: 20 additions & 4 deletions src/components/authorization-wrapper.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,29 @@
import { Navigate } from 'react-router-dom';
import { useAppSelector } from '../store/store.ts';
import { AuthorizationStatus } from '../dataTypes/enums/authorization-status.ts';
import { AppRoutes } from '../dataTypes/enums/app-routes.ts';

interface AuthorizationWrapperProps {
isAuthorized: boolean;
children: React.JSX.Element;
fallbackUrl: AppRoutes;
}

export function AuthorizationWrapper({
isAuthorized,
export function AuthorizationWrapperForAuthorizedOnly({
children,
fallbackUrl,
}: AuthorizationWrapperProps): React.JSX.Element {
return isAuthorized ? children : <Navigate to="/login" />;
const isAuthorized =
useAppSelector((state) => state.authorizationStatus) ===
AuthorizationStatus.Authorized;
return isAuthorized ? children : <Navigate to={fallbackUrl} />;
}

export function AuthorizationWrapperForUnauthorizedOnly({
children,
fallbackUrl,
}: AuthorizationWrapperProps): React.JSX.Element {
const isUnauthorized =
useAppSelector((state) => state.authorizationStatus) ===
AuthorizationStatus.Unauthorized;
return isUnauthorized ? children : <Navigate to={fallbackUrl} />;
}
70 changes: 49 additions & 21 deletions src/components/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,26 @@
import { AppRoutes } from '../dataTypes/enums/app-routes.ts';
import { Link } from 'react-router-dom';
import { store, useAppSelector } from '../store/store.ts';
import { AuthorizationStatus } from '../dataTypes/enums/authorization-status.ts';
import { logout } from '../store/actions.ts';

interface LayoutProps {
children: React.JSX.Element;
showFooter?: boolean;
dontShowUserInfo?: boolean;
}

export function Layout({
children,
showFooter,
dontShowUserInfo,
}: LayoutProps): React.JSX.Element {
const isAuthorized =
useAppSelector((state) => state.authorizationStatus) ===
AuthorizationStatus.Authorized;
const handleLogout = () => {
store.dispatch(logout());
};
return (
<>
<header className="header">
Expand All @@ -26,27 +37,44 @@ export function Layout({
/>
</Link>
</div>
<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={AppRoutes.Favorites}
>
<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>
</ul>
</nav>
{dontShowUserInfo ||
(isAuthorized ? (
<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={AppRoutes.Favorites}
>
<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" onClick={handleLogout}>
<span className="header__signout">Sign out</span>
</a>
</li>
</ul>
</nav>
) : (
<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={AppRoutes.Login}
>
<div className="header__avatar-wrapper user__avatar-wrapper"></div>
<span className="header__login">Sign in</span>
</Link>
</li>
</ul>
</nav>
))}
</div>
</div>
</header>
Expand Down
2 changes: 2 additions & 0 deletions src/dataTypes/enums/api-routes.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export enum ApiRoutes {
Offers = '/offers',
Login = '/login',
Logout = '/logout',
}
13 changes: 13 additions & 0 deletions src/dataTypes/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,16 @@
avatarUrl: string;
isPro: boolean;
};

export type LoginInfo = {
email: string;
password: string;
};

export type AuthInfo = {
name: string;
avatarUrl: string;
isPro: boolean;
email: string;
token: string;
};
Loading

0 comments on commit 22abdf5

Please sign in to comment.