Last active
January 21, 2023 20:41
-
-
Save evgeniyworkbel/e4da762fdba10ad1db2d8406e33753d2 to your computer and use it in GitHub Desktop.
Курс "JS: React Hooks"
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
В этом испытании вам предстоит реализовать авторизацию в настоящем 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