Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save evgeniyworkbel/e4da762fdba10ad1db2d8406e33753d2 to your computer and use it in GitHub Desktop.
Save evgeniyworkbel/e4da762fdba10ad1db2d8406e33753d2 to your computer and use it in GitHub Desktop.
Курс "JS: React Hooks"
LoginPage.jsx
// @ts-check
import axios from 'axios';
import React, { useEffect, useRef, useState } from 'react';
import { useFormik } from 'formik';
import { Button, Form } from 'react-bootstrap';
import { useLocation, useNavigate } from 'react-router-dom';
import useAuth from '../hooks/index.jsx';
import routes from '../routes.js';
const LoginPage = () => {
// BEGIN
const auth = useAuth();
const [authFailed, setAuthFailed] = useState(false);
const inputRef = useRef();
const location = useLocation();
const navigate = useNavigate();
useEffect(() => {
inputRef.current.focus();
}, []);
const formik = useFormik({
initialValues: {
username: '',
password: '',
},
onSubmit: async (values) => {
setAuthFailed(false);
try {
const res = await axios.post(routes.loginPath(), values);
localStorage.setItem('userId', JSON.stringify(res.data));
auth.logIn();
const { from } = location.state || { from: { pathname: '/' } };
navigate(from);
} catch (err) {
formik.setSubmitting(false);
if (err.isAxiosError && err.response.status === 401) {
setAuthFailed(true);
inputRef.current.select();
return;
}
throw err;
}
},
});
return (
<div className="container-fluid">
<div className="row justify-content-center pt-5">
<div className="col-sm-4">
<Form onSubmit={formik.handleSubmit} className="p-3">
<fieldset disabled={formik.isSubmitting}>
<Form.Group>
<Form.Label htmlFor="username">Username</Form.Label>
<Form.Control
onChange={formik.handleChange}
value={formik.values.username}
placeholder="username"
name="username"
id="username"
autoComplete="username"
isInvalid={authFailed}
required
ref={inputRef}
/>
</Form.Group>
<Form.Group>
<Form.Label htmlFor="password">Password</Form.Label>
<Form.Control
type="password"
onChange={formik.handleChange}
value={formik.values.password}
placeholder="password"
name="password"
id="password"
autoComplete="current-password"
isInvalid={authFailed}
required
/>
<Form.Control.Feedback type="invalid">the username or password is incorrect</Form.Control.Feedback>
</Form.Group>
<Button type="submit" variant="outline-primary">Submit</Button>
</fieldset>
</Form>
</div>
</div>
</div>
);
// END
};
export default LoginPage;
PrivatePage.jsx
// @ts-check
import axios from 'axios';
import React, { useEffect, useState } from 'react';
import routes from '../routes.js';
const getAuthHeader = () => {
const userId = JSON.parse(localStorage.getItem('userId'));
if (userId && userId.token) {
return { Authorization: `Bearer ${userId.token}` };
}
return {};
};
const PrivatePage = () => {
// BEGIN
const [content, setContent] = useState('');
useEffect(() => {
const fetchContent = async () => {
const { data } = await axios.get(routes.usersPath(), { headers: getAuthHeader() });
setContent(data);
};
fetchContent();
}, []);
return content && <p>{content}</p>;
// END
};
export default PrivatePage;
LoginPage.jsx
// @ts-check
import axios from 'axios';
import React, { useEffect, useRef, useState } from 'react';
import { useFormik } from 'formik';
import { Button, Form } from 'react-bootstrap';
import { useLocation, useNavigate } from 'react-router-dom';
import useAuth from '../hooks/index.jsx';
import routes from '../routes.js';
const LoginPage = () => {
// BEGIN
const auth = useAuth();
const [authFailed, setAuthFailed] = useState(false);
const inputRef = useRef();
const location = useLocation();
const navigate = useNavigate();
useEffect(() => {
inputRef.current.focus();
}, []);
const formik = useFormik({
initialValues: {
username: '',
password: '',
},
onSubmit: async (values) => {
setAuthFailed(false);
try {
const res = await axios.post(routes.loginPath(), values);
localStorage.setItem('userId', JSON.stringify(res.data));
auth.logIn();
const { from } = location.state || { from: { pathname: '/' } };
navigate(from);
} catch (err) {
formik.setSubmitting(false);
if (err.isAxiosError && err.response.status === 401) {
setAuthFailed(true);
inputRef.current.select();
return;
}
throw err;
}
},
});
return (
<div className="container-fluid">
<div className="row justify-content-center pt-5">
<div className="col-sm-4">
<Form onSubmit={formik.handleSubmit} className="p-3">
<fieldset disabled={formik.isSubmitting}>
<Form.Group>
<Form.Label htmlFor="username">Username</Form.Label>
<Form.Control
onChange={formik.handleChange}
value={formik.values.username}
placeholder="username"
name="username"
id="username"
autoComplete="username"
isInvalid={authFailed}
required
ref={inputRef}
/>
</Form.Group>
<Form.Group>
<Form.Label htmlFor="password">Password</Form.Label>
<Form.Control
type="password"
onChange={formik.handleChange}
value={formik.values.password}
placeholder="password"
name="password"
id="password"
autoComplete="current-password"
isInvalid={authFailed}
required
/>
<Form.Control.Feedback type="invalid">the username or password is incorrect</Form.Control.Feedback>
</Form.Group>
<Button type="submit" variant="outline-primary">Submit</Button>
</fieldset>
</Form>
</div>
</div>
</div>
);
// END
};
export default LoginPage;
PrivatePage.jsx
// @ts-check
import axios from 'axios';
import React, { useEffect, useState } from 'react';
import routes from '../routes.js';
const getAuthHeader = () => {
const userId = JSON.parse(localStorage.getItem('userId'));
if (userId && userId.token) {
return { Authorization: `Bearer ${userId.token}` };
}
return {};
};
const PrivatePage = () => {
// BEGIN
const [content, setContent] = useState('');
useEffect(() => {
const fetchContent = async () => {
const { data } = await axios.get(routes.usersPath(), { headers: getAuthHeader() });
setContent(data);
};
fetchContent();
}, []);
return content && <p>{content}</p>;
// END
};
export default PrivatePage;
В этом испытании вам предстоит реализовать авторизацию в настоящем SPA (single-page application). Идея состоит в том, что при получении валидной пары логин-пароль сервер возвращает токен, который сохраняется в local storage и отправляется на сервер с каждым клиентским запросом. Приложение состоит из главной, публичной и приватной страниц, а также страницы с формой входа. Часть кода уже написана, внимательно изучите файлы приложения. Выполнение испытания потребует изучения новых хуков и библиотек. Рекомендуем проходить это испытание после выполнения предыдущего.
components/LoginPage.jsx
Реализуйте компонент с формой авторизации пользователя. Форма содержит поля username и password. В случае ошибки аутентификации в форме показывается сообщение the username or password is incorrect. При успешной проверке полученный с сервера токен необходимо сохранить и сделать редирект на ту страницу, с которой пользователь попал в форму логина. Если пользователь зашёл по прямой ссылке, его следует перенаправить на главную страницу.
Пример формы:
<form>
<div class="form-group">
<label class="form-label" for="username">Username</label>
<input placeholder="username" name="username" autocomplete="username" required id="username" class="form-control">
</div>
<div class="form-group">
<label class="form-label" for="password">Password</label>
<input placeholder="password" name="password" autocomplete="current-password" required id="password" class="form-control" type="password">
<div class="invalid-feedback">the username or password is incorrect</div>
</div>
<button type="submit" class="btn btn-outline-primary">Submit</button>
</form>
components/PrivatePage.jsx
Реализуйте компонент, который запрашивает данные с сервера и выводит их. Для получения данных необходимо к запросу добавить заголовок Authorization, содержащий токен. После проверки сервер вернёт строку, которую нужно вывести на странице. Роутинг настроен так, что на страницу /private можно попасть только после успешной авторизации.
Подсказки
Для проверки авторизации можно использовать слово user в качестве логина и пароля
Документация на встроенные хуки: useState, useEffect, useRef, useContext
Документация на сторонние хуки: useImmer, useFormik
Документация на библиотеки: React Bootstrap, react-router-dom https://reactrouter.com/docs/en/v6
Для сохранения данных авторизации на стороне клиента, можно использовать window.localStorage
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment