Compare commits
3 Commits
395b6c2d89
...
6a0c4c9dac
Author | SHA1 | Date | |
---|---|---|---|
6a0c4c9dac | |||
d041df0bbd | |||
619fd952a5 |
55
front/src/components/AuthForm.tsx
Normal file
55
front/src/components/AuthForm.tsx
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
import { FormEventHandler } from "react"
|
||||||
|
import { Button, Form } from "react-bootstrap"
|
||||||
|
|
||||||
|
type AuthFormProps = {
|
||||||
|
register: boolean
|
||||||
|
handleAuth: FormEventHandler<HTMLFormElement>,
|
||||||
|
loading: boolean,
|
||||||
|
error: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const AuthForm = ({ handleAuth, register, loading, error }: AuthFormProps) => {
|
||||||
|
const buttonText = loading ? "Загрузка..." : (error || (register ? "Зарегистрироваться" : "Войти"))
|
||||||
|
return (
|
||||||
|
<Form onSubmit={handleAuth}>
|
||||||
|
<Form.Group className="mb-3" controlId="email">
|
||||||
|
<Form.Label>Почта</Form.Label>
|
||||||
|
<Form.Control type="email" required />
|
||||||
|
</Form.Group>
|
||||||
|
|
||||||
|
{register && <>
|
||||||
|
<Form.Group className="mb-3" controlId="name">
|
||||||
|
<Form.Label>Имя</Form.Label>
|
||||||
|
<Form.Control type="text" required />
|
||||||
|
</Form.Group>
|
||||||
|
|
||||||
|
<Form.Group className="mb-3" controlId="surname">
|
||||||
|
<Form.Label>Фамилия</Form.Label>
|
||||||
|
<Form.Control type="text" required />
|
||||||
|
</Form.Group>
|
||||||
|
</>}
|
||||||
|
|
||||||
|
<Form.Group className="mb-3" controlId="password">
|
||||||
|
<Form.Label>Пароль</Form.Label>
|
||||||
|
<Form.Control type="password" required />
|
||||||
|
</Form.Group>
|
||||||
|
|
||||||
|
{register &&
|
||||||
|
<Form.Group className="mb-3" controlId="privacyPolicyConsent">
|
||||||
|
<Form.Check label="<a>условиями обработки персональных данных</a>">
|
||||||
|
<Form.Check.Input type="checkbox" required />
|
||||||
|
<Form.Check.Label>
|
||||||
|
Я согласен с <a href={`${document.location.origin}/privacy_policy.pdf`} target="_blank" rel="noopener noreferrer">условиями обработки персональных данных</a>
|
||||||
|
</Form.Check.Label>
|
||||||
|
</Form.Check>
|
||||||
|
</Form.Group>
|
||||||
|
}
|
||||||
|
|
||||||
|
<Button variant="success" type="submit">
|
||||||
|
{buttonText}
|
||||||
|
</Button>
|
||||||
|
</Form>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AuthForm
|
@ -6,6 +6,7 @@ import LocationMarker from './LocationMarker'
|
|||||||
import TrashboxMarkers from './TrashboxMarkers'
|
import TrashboxMarkers from './TrashboxMarkers'
|
||||||
import WithToken from './WithToken'
|
import WithToken from './WithToken'
|
||||||
import ClickHandler from './ClickHandler'
|
import ClickHandler from './ClickHandler'
|
||||||
|
import AuthForm from "./AuthForm"
|
||||||
|
|
||||||
export {
|
export {
|
||||||
AnnouncementDetails,
|
AnnouncementDetails,
|
||||||
@ -16,4 +17,5 @@ export {
|
|||||||
TrashboxMarkers,
|
TrashboxMarkers,
|
||||||
WithToken,
|
WithToken,
|
||||||
ClickHandler,
|
ClickHandler,
|
||||||
|
AuthForm,
|
||||||
}
|
}
|
||||||
|
@ -2,13 +2,14 @@ import { useEffect, useRef, useState } from "react"
|
|||||||
|
|
||||||
import { API_URL } from "../../config"
|
import { API_URL } from "../../config"
|
||||||
import { isLiteralUnion } from "../../utils/types"
|
import { isLiteralUnion } from "../../utils/types"
|
||||||
|
import { handleHTTPErrors } from "../../utils"
|
||||||
|
|
||||||
const addErrors = ["Не удалось опубликовать объявление", 'Неверный ответ от сервера', 'Неизвестная ошибка'] as const
|
const addErrors = ["Не удалось опубликовать объявление", 'Неверный ответ от сервера', 'Неизвестная ошибка'] as const
|
||||||
type AddError = typeof addErrors[number]
|
type AddError = typeof addErrors[number]
|
||||||
|
|
||||||
const isAddError = (obj: unknown): obj is AddError => isLiteralUnion(obj, addErrors)
|
const isAddError = (obj: unknown): obj is AddError => isLiteralUnion(obj, addErrors)
|
||||||
|
|
||||||
const buttonStates = ["Опубликовать", "Загрузка", "Опубликовано", "Отменено"] as const
|
const buttonStates = ["Опубликовать", "Загрузка...", "Опубликовано", "Отменено"] as const
|
||||||
type ButtonState = typeof buttonStates[number] | AddError
|
type ButtonState = typeof buttonStates[number] | AddError
|
||||||
|
|
||||||
type AddResponse = {
|
type AddResponse = {
|
||||||
@ -26,14 +27,14 @@ const useAddAnnouncement = () => {
|
|||||||
const abortControllerRef = useRef<AbortController>()
|
const abortControllerRef = useRef<AbortController>()
|
||||||
|
|
||||||
const doAdd = async (formData: FormData) => {
|
const doAdd = async (formData: FormData) => {
|
||||||
if (status === "Загрузка") {
|
if (status === "Загрузка...") {
|
||||||
abortControllerRef.current?.abort()
|
abortControllerRef.current?.abort()
|
||||||
setStatus("Отменено")
|
setStatus("Отменено")
|
||||||
timerIdRef.current = setTimeout(() => setStatus("Опубликовать"), 3000)
|
timerIdRef.current = setTimeout(() => setStatus("Опубликовать"), 3000)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
setStatus("Загрузка")
|
setStatus("Загрузка...")
|
||||||
|
|
||||||
const abortController = new AbortController()
|
const abortController = new AbortController()
|
||||||
abortControllerRef.current = abortController
|
abortControllerRef.current = abortController
|
||||||
@ -45,6 +46,8 @@ const useAddAnnouncement = () => {
|
|||||||
signal: abortController.signal
|
signal: abortController.signal
|
||||||
})
|
})
|
||||||
|
|
||||||
|
handleHTTPErrors(res)
|
||||||
|
|
||||||
const data: unknown = await res.json()
|
const data: unknown = await res.json()
|
||||||
|
|
||||||
if (!isAddResponse(data)) throw new Error('Неверный ответ от сервера')
|
if (!isAddResponse(data)) throw new Error('Неверный ответ от сервера')
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { useState } from "react"
|
import { useState } from "react"
|
||||||
import { API_URL } from "../../config"
|
import { API_URL } from "../../config"
|
||||||
import { isConst, isObject } from "../../utils/types"
|
import { isConst, isObject } from "../../utils/types"
|
||||||
|
import { handleHTTPErrors } from "../../utils"
|
||||||
|
|
||||||
interface AuthData {
|
interface AuthData {
|
||||||
email: string,
|
email: string,
|
||||||
@ -57,6 +58,9 @@ function useAuth() {
|
|||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
handleHTTPErrors(res)
|
||||||
|
|
||||||
const signupData: unknown = await res.json()
|
const signupData: unknown = await res.json()
|
||||||
|
|
||||||
if (!isSignUpResponse(signupData)) {
|
if (!isSignUpResponse(signupData)) {
|
||||||
|
@ -4,6 +4,7 @@ import { useNavigate } from "react-router-dom"
|
|||||||
import { getToken } from "../../utils/auth"
|
import { getToken } from "../../utils/auth"
|
||||||
import { API_URL } from "../../config"
|
import { API_URL } from "../../config"
|
||||||
import { isObject } from "../../utils/types"
|
import { isObject } from "../../utils/types"
|
||||||
|
import { handleHTTPErrors } from "../../utils"
|
||||||
|
|
||||||
type BookResponse = {
|
type BookResponse = {
|
||||||
Success: boolean
|
Success: boolean
|
||||||
@ -13,7 +14,7 @@ const isBookResponse = (obj: unknown): obj is BookResponse => isObject(obj, {
|
|||||||
"Success": "boolean"
|
"Success": "boolean"
|
||||||
})
|
})
|
||||||
|
|
||||||
type BookStatus = "" | "Загрузка" | "Забронировано" | "Ошибка бронирования"
|
type BookStatus = "" | "Загрузка..." | "Забронировано" | "Ошибка бронирования"
|
||||||
|
|
||||||
function useBook(id: number) {
|
function useBook(id: number) {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
@ -24,7 +25,7 @@ function useBook(id: number) {
|
|||||||
const token = getToken()
|
const token = getToken()
|
||||||
|
|
||||||
if (token) {
|
if (token) {
|
||||||
setStatus("Загрузка")
|
setStatus("Загрузка...")
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
@ -39,6 +40,8 @@ function useBook(id: number) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
handleHTTPErrors(res)
|
||||||
|
|
||||||
const data: unknown = await res.json()
|
const data: unknown = await res.json()
|
||||||
|
|
||||||
if (!isBookResponse(data)) {
|
if (!isBookResponse(data)) {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { useEffect, useRef, useState } from "react"
|
import { useEffect, useRef, useState } from "react"
|
||||||
import { isAborted } from '../../utils'
|
|
||||||
|
import { handleHTTPErrors, isAborted } from '../../utils'
|
||||||
|
|
||||||
const useFetch = <T>(url: string, params: RequestInit | undefined, initialData: T, dataGuard: (obj: unknown) => obj is T) => {
|
const useFetch = <T>(url: string, params: RequestInit | undefined, initialData: T, dataGuard: (obj: unknown) => obj is T) => {
|
||||||
const [data, setData] = useState(initialData)
|
const [data, setData] = useState(initialData)
|
||||||
@ -18,17 +19,7 @@ const useFetch = <T>(url: string, params: RequestInit | undefined, initialData:
|
|||||||
|
|
||||||
fetch(url, { ...params, signal: abortControllerRef.current.signal })
|
fetch(url, { ...params, signal: abortControllerRef.current.signal })
|
||||||
.then(res => {
|
.then(res => {
|
||||||
if (!res.ok) {
|
handleHTTPErrors(res)
|
||||||
switch (res.status) {
|
|
||||||
case 401:
|
|
||||||
throw new Error("Ошибка авторизации")
|
|
||||||
case 404:
|
|
||||||
throw new Error("Объект не найден")
|
|
||||||
default: {
|
|
||||||
throw new Error("Ошибка ответа от сервера")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.json()
|
return res.json()
|
||||||
})
|
})
|
||||||
|
@ -9,6 +9,7 @@ import { useAddAnnouncement, useTrashboxes } from "../hooks/api"
|
|||||||
import { categoryNames } from "../assets/category"
|
import { categoryNames } from "../assets/category"
|
||||||
import { stations, lines, lineNames } from "../assets/metro"
|
import { stations, lines, lineNames } from "../assets/metro"
|
||||||
import { isObject } from "../utils/types"
|
import { isObject } from "../utils/types"
|
||||||
|
import { handleHTTPErrors } from "../utils"
|
||||||
|
|
||||||
function AddPage() {
|
function AddPage() {
|
||||||
const [addressPosition, setAddressPosition] = useState(latLng(59.972, 30.3227))
|
const [addressPosition, setAddressPosition] = useState(latLng(59.972, 30.3227))
|
||||||
@ -22,6 +23,8 @@ function AddPage() {
|
|||||||
try {
|
try {
|
||||||
const res = await fetch(location.protocol + "//nominatim.openstreetmap.org/search?format=json&q=" + address)
|
const res = await fetch(location.protocol + "//nominatim.openstreetmap.org/search?format=json&q=" + address)
|
||||||
|
|
||||||
|
handleHTTPErrors(res)
|
||||||
|
|
||||||
const fetchData: unknown = await res.json()
|
const fetchData: unknown = await res.json()
|
||||||
|
|
||||||
console.log("f", fetchData)
|
console.log("f", fetchData)
|
||||||
@ -37,6 +40,8 @@ function AddPage() {
|
|||||||
try {
|
try {
|
||||||
const res = await fetch(`${location.protocol}//nominatim.openstreetmap.org/reverse?format=json&accept-language=ru&lat=${addressPosition.lat}&lon=${addressPosition.lng}`)
|
const res = await fetch(`${location.protocol}//nominatim.openstreetmap.org/reverse?format=json&accept-language=ru&lat=${addressPosition.lat}&lon=${addressPosition.lng}`)
|
||||||
|
|
||||||
|
handleHTTPErrors(res)
|
||||||
|
|
||||||
const fetchData: unknown = await res.json()
|
const fetchData: unknown = await res.json()
|
||||||
|
|
||||||
if (!isObject<{ display_name: string }>(fetchData, { "display_name": "string" })) {
|
if (!isObject<{ display_name: string }>(fetchData, { "display_name": "string" })) {
|
||||||
@ -159,7 +164,7 @@ function AddPage() {
|
|||||||
{trashboxes.loading
|
{trashboxes.loading
|
||||||
? (
|
? (
|
||||||
<div style={{ height: 400 }}>
|
<div style={{ height: 400 }}>
|
||||||
<p>Загрузка</p>
|
<p>Загрузка...</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
trashboxes.error ? (
|
trashboxes.error ? (
|
||||||
|
@ -1,14 +1,15 @@
|
|||||||
import { FormEventHandler } from 'react'
|
import { FormEventHandler } from 'react'
|
||||||
import { Form, Button, Card, Tabs, Tab } from "react-bootstrap"
|
import { Card, Tabs, Tab } from "react-bootstrap"
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
import { useAuth } from "../hooks/api";
|
import { useAuth } from "../hooks/api";
|
||||||
import { setToken } from "../utils/auth";
|
import { setToken } from "../utils/auth";
|
||||||
|
import { AuthForm } from '../components';
|
||||||
|
|
||||||
function LoginPage() {
|
function LoginPage() {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
|
|
||||||
const { doAuth } = useAuth() // TODO: Add loading and error handling
|
const { doAuth, loading, error } = useAuth()
|
||||||
|
|
||||||
const handleAuth = (newAccount: boolean): FormEventHandler<HTMLFormElement> => async (event) => {
|
const handleAuth = (newAccount: boolean): FormEventHandler<HTMLFormElement> => async (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
@ -36,57 +37,10 @@ function LoginPage() {
|
|||||||
<Card.Body>
|
<Card.Body>
|
||||||
<Tabs defaultActiveKey="register" fill justify className="mb-3">
|
<Tabs defaultActiveKey="register" fill justify className="mb-3">
|
||||||
<Tab eventKey="register" title="Регистрация">
|
<Tab eventKey="register" title="Регистрация">
|
||||||
<Form onSubmit={handleAuth(true)}>
|
<AuthForm handleAuth={handleAuth(true)} register={true} loading={loading} error={error} />
|
||||||
<Form.Group className="mb-3" controlId="email">
|
|
||||||
<Form.Label>Почта</Form.Label>
|
|
||||||
<Form.Control type="email" required />
|
|
||||||
</Form.Group>
|
|
||||||
|
|
||||||
<Form.Group className="mb-3" controlId="name">
|
|
||||||
<Form.Label>Имя</Form.Label>
|
|
||||||
<Form.Control type="text" required />
|
|
||||||
</Form.Group>
|
|
||||||
|
|
||||||
<Form.Group className="mb-3" controlId="surname">
|
|
||||||
<Form.Label>Фамилия</Form.Label>
|
|
||||||
<Form.Control type="text" required />
|
|
||||||
</Form.Group>
|
|
||||||
|
|
||||||
<Form.Group className="mb-3" controlId="password">
|
|
||||||
<Form.Label>Пароль</Form.Label>
|
|
||||||
<Form.Control type="password" required />
|
|
||||||
</Form.Group>
|
|
||||||
|
|
||||||
<Form.Group className="mb-3" controlId="privacyPolicyConsent">
|
|
||||||
<Form.Check label="<a>условиями обработки персональных данных</a>">
|
|
||||||
<Form.Check.Input type="checkbox" required />
|
|
||||||
<Form.Check.Label>
|
|
||||||
Я согласен с <a href={`${document.location.origin}/privacy_policy.pdf`} target="_blank" rel="noopener noreferrer">условиями обработки персональных данных</a>
|
|
||||||
</Form.Check.Label>
|
|
||||||
</Form.Check>
|
|
||||||
</Form.Group>
|
|
||||||
|
|
||||||
<Button variant="success" type="submit">
|
|
||||||
Зарегистрироваться
|
|
||||||
</Button>
|
|
||||||
</Form>
|
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab eventKey="login" title="Вход">
|
<Tab eventKey="login" title="Вход">
|
||||||
<Form onSubmit={handleAuth(false)}>
|
<AuthForm handleAuth={handleAuth(false)} register={false} loading={loading} error={error} />
|
||||||
<Form.Group className="mb-3" controlId="email">
|
|
||||||
<Form.Label>Почта</Form.Label>
|
|
||||||
<Form.Control type="email" required />
|
|
||||||
</Form.Group>
|
|
||||||
|
|
||||||
<Form.Group className="mb-3" controlId="password">
|
|
||||||
<Form.Label>Пароль</Form.Label>
|
|
||||||
<Form.Control type="password" required />
|
|
||||||
</Form.Group>
|
|
||||||
|
|
||||||
<Button variant="success" type="submit">
|
|
||||||
Войти
|
|
||||||
</Button>
|
|
||||||
</Form>
|
|
||||||
</Tab>
|
</Tab>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</Card.Body>
|
</Card.Body>
|
||||||
|
@ -1,3 +1,17 @@
|
|||||||
const isAborted = (err: Error) => err.name === 'AbortError'
|
const isAborted = (err: Error) => err.name === 'AbortError'
|
||||||
|
|
||||||
export { isAborted }
|
const handleHTTPErrors = (res: Response) => {
|
||||||
|
if (!res.ok) {
|
||||||
|
switch (res.status) {
|
||||||
|
case 401:
|
||||||
|
throw new Error("Ошибка авторизации")
|
||||||
|
case 404:
|
||||||
|
throw new Error("Объект не найден")
|
||||||
|
default: {
|
||||||
|
throw new Error("Ошибка ответа от сервера")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { isAborted, handleHTTPErrors }
|
||||||
|
Loading…
x
Reference in New Issue
Block a user