makeRequest
This commit is contained in:
parent
f3296b81f0
commit
151e8a8366
@ -1,67 +1,58 @@
|
||||
import axios from 'axios'
|
||||
import axios from "axios";
|
||||
interface MakeRequest {
|
||||
method?: string
|
||||
url: string
|
||||
body?: unknown
|
||||
useToken?: boolean
|
||||
contentType?: boolean
|
||||
signal?: AbortSignal
|
||||
method?: string;
|
||||
url: string;
|
||||
body?: unknown;
|
||||
useToken?: boolean;
|
||||
contentType?: boolean;
|
||||
signal?: AbortSignal;
|
||||
}
|
||||
|
||||
export default (props: MakeRequest) => {
|
||||
return (
|
||||
new Promise(async (resolve, reject) => {
|
||||
await makeRequest(props)
|
||||
.then(r => resolve(r))
|
||||
.catch(r => reject(r))
|
||||
})
|
||||
)
|
||||
}
|
||||
return new Promise(async (resolve, reject) => {
|
||||
await makeRequest(props)
|
||||
.then((r) => resolve(r))
|
||||
.catch((r) => reject(r));
|
||||
});
|
||||
};
|
||||
|
||||
function makeRequest({
|
||||
method = "post",
|
||||
url,
|
||||
body,
|
||||
useToken = true,
|
||||
signal,
|
||||
contentType = false
|
||||
}: MakeRequest) {
|
||||
//В случае 401 рефреш должен попробовать вызваться 1 раз
|
||||
let counterRefresh = true
|
||||
let headers: any = {}
|
||||
if (useToken) headers["Authorization"] = localStorage.getItem('AT')
|
||||
if (contentType) headers["Content-Type"] = "application/json"
|
||||
return axios({
|
||||
url: url,
|
||||
method: method,
|
||||
headers: headers,
|
||||
data: body,
|
||||
signal,
|
||||
function makeRequest({ method = "post", url, body, useToken = true, signal, contentType = false }: MakeRequest) {
|
||||
//В случае 401 рефреш должен попробовать вызваться 1 раз
|
||||
let counterRefresh = true;
|
||||
let headers: any = {};
|
||||
if (useToken) headers["Authorization"] = localStorage.getItem("AT");
|
||||
if (contentType) headers["Content-Type"] = "application/json";
|
||||
return axios({
|
||||
url: url,
|
||||
method: method,
|
||||
headers: headers,
|
||||
data: body,
|
||||
signal,
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.data && response.data.accessToken) {
|
||||
localStorage.setItem("AT", response.data.accessToken);
|
||||
}
|
||||
return response;
|
||||
})
|
||||
.then(response => {
|
||||
if (response.data && response.data.accessToken) {
|
||||
localStorage.setItem('AT', response.data.accessToken)
|
||||
}
|
||||
return response
|
||||
})
|
||||
.catch(error => {
|
||||
if (error.response.status == 401 && counterRefresh) {
|
||||
refresh().then(response => {
|
||||
if (response.data && response.data.accessToken) localStorage.setItem('AT', response.data.accessToken)
|
||||
counterRefresh = false
|
||||
})
|
||||
} else {
|
||||
throw error
|
||||
}
|
||||
throw error
|
||||
})
|
||||
.catch((error) => {
|
||||
if (error.response.status == 401 && counterRefresh) {
|
||||
refresh().then((response) => {
|
||||
if (response.data && response.data.accessToken) localStorage.setItem("AT", response.data.accessToken);
|
||||
counterRefresh = false;
|
||||
});
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
|
||||
function refresh() {
|
||||
return axios("https://admin.pena.digital/auth/refresh", {
|
||||
headers: {
|
||||
"Authorization": localStorage.getItem('AT'),
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
})
|
||||
}
|
||||
return axios("https://admin.pena.digital/auth/refresh", {
|
||||
headers: {
|
||||
Authorization: localStorage.getItem("AT"),
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@ -1,119 +1,130 @@
|
||||
import * as React from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { Formik, Field, Form } from 'formik';
|
||||
import {useTheme} from "@mui/material/styles";
|
||||
import { Link } from "react-router-dom"
|
||||
import {Box, Typography} from "@mui/material";
|
||||
import { Formik, Field, Form } from "formik";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import { Link } from "react-router-dom";
|
||||
import { Box, Typography } from "@mui/material";
|
||||
import Logo from "@pages/Logo";
|
||||
import CleverButton from "@kitUI/cleverButton"
|
||||
import MakeRequest from "@kitUI/makeRequest";
|
||||
import CleverButton from "@kitUI/cleverButton";
|
||||
import EmailOutlinedIcon from "@mui/icons-material/EmailOutlined";
|
||||
import LockOutlinedIcon from "@mui/icons-material/LockOutlined";
|
||||
import OutlinedInput from "@kitUI/outlinedInput";
|
||||
import { authStore } from "@root/stores/auth";
|
||||
|
||||
export default () => {
|
||||
const theme = useTheme()
|
||||
const navigate = useNavigate();
|
||||
const [restore, setRestore] = React.useState(true)
|
||||
const [isReady, setIsReady] = React.useState(true)
|
||||
if (restore) {
|
||||
return (
|
||||
|
||||
<Formik
|
||||
initialValues={{
|
||||
mail: ""
|
||||
}}
|
||||
onSubmit={(values) => {
|
||||
setRestore(false)
|
||||
}}
|
||||
const theme = useTheme();
|
||||
const navigate = useNavigate();
|
||||
const [restore, setRestore] = React.useState(true);
|
||||
const [isReady, setIsReady] = React.useState(true);
|
||||
const { makeRequest } = authStore();
|
||||
if (restore) {
|
||||
return (
|
||||
<Formik
|
||||
initialValues={{
|
||||
mail: "",
|
||||
}}
|
||||
onSubmit={(values) => {
|
||||
setRestore(false);
|
||||
}}
|
||||
>
|
||||
<Form>
|
||||
<Box
|
||||
component="section"
|
||||
sx={{
|
||||
minHeight: "100vh",
|
||||
height: "100%",
|
||||
width: "100%",
|
||||
backgroundColor: theme.palette.content.main,
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
padding: "15px 0",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
component="article"
|
||||
sx={{
|
||||
width: "350px",
|
||||
backgroundColor: theme.palette.content.main,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
"> *": {
|
||||
marginTop: "15px",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Form>
|
||||
<Box component="section"
|
||||
sx={{
|
||||
minHeight: "100vh",
|
||||
height: "100%",
|
||||
width: "100%",
|
||||
backgroundColor: theme.palette.content.main,
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
padding: "15px 0"
|
||||
}}
|
||||
>
|
||||
<Box component="article"
|
||||
sx={{
|
||||
width: "350px",
|
||||
backgroundColor: theme.palette.content.main,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
"> *": {
|
||||
marginTop: "15px"
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Typography variant="h6" color={theme.palette.secondary.main}>Восстановление пароля</Typography>
|
||||
<Logo/>
|
||||
<Typography variant="h6" color={theme.palette.secondary.main}>
|
||||
Восстановление пароля
|
||||
</Typography>
|
||||
<Logo />
|
||||
|
||||
<Box sx={{display:"flex", alignItems:"center",marginTop:"15px","> *": {marginRight:"10px"}}}>
|
||||
<EmailOutlinedIcon htmlColor={theme.palette.golden.main}/>
|
||||
<Field as={OutlinedInput} autoComplete="none" variant="filled" name="mail" label="Эл. почта"/>
|
||||
</Box>
|
||||
<CleverButton type="submit" text="Отправить" isReady={isReady}/>
|
||||
<Link to="/signin" style={{textDecoration:"none"}}><Typography color={theme.palette.golden.main}>Я помню пароль</Typography></Link>
|
||||
</Box>
|
||||
</Box>
|
||||
</Form>
|
||||
</Formik>
|
||||
)
|
||||
} else {
|
||||
return(
|
||||
<Formik
|
||||
initialValues={{
|
||||
code: ""
|
||||
}}
|
||||
onSubmit={(values) => {
|
||||
}}
|
||||
<Box sx={{ display: "flex", alignItems: "center", marginTop: "15px", "> *": { marginRight: "10px" } }}>
|
||||
<EmailOutlinedIcon htmlColor={theme.palette.golden.main} />
|
||||
<Field as={OutlinedInput} autoComplete="none" variant="filled" name="mail" label="Эл. почта" />
|
||||
</Box>
|
||||
<CleverButton type="submit" text="Отправить" isReady={isReady} />
|
||||
<Link to="/signin" style={{ textDecoration: "none" }}>
|
||||
<Typography color={theme.palette.golden.main}>Я помню пароль</Typography>
|
||||
</Link>
|
||||
</Box>
|
||||
</Box>
|
||||
</Form>
|
||||
</Formik>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<Formik
|
||||
initialValues={{
|
||||
code: "",
|
||||
}}
|
||||
onSubmit={(values) => {}}
|
||||
>
|
||||
<Form>
|
||||
<Box
|
||||
component="section"
|
||||
sx={{
|
||||
minHeight: "100vh",
|
||||
height: "100%",
|
||||
width: "100%",
|
||||
backgroundColor: theme.palette.content.main,
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
padding: "15px 0",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
component="article"
|
||||
sx={{
|
||||
width: "350px",
|
||||
backgroundColor: theme.palette.content.main,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
"> *": {
|
||||
marginTop: "15px",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Form>
|
||||
<Box component="section"
|
||||
sx={{
|
||||
minHeight: "100vh",
|
||||
height: "100%",
|
||||
width: "100%",
|
||||
backgroundColor: theme.palette.content.main,
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
padding: "15px 0"
|
||||
}}
|
||||
>
|
||||
<Box component="article"
|
||||
sx={{
|
||||
width: "350px",
|
||||
backgroundColor: theme.palette.content.main,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
"> *": {
|
||||
marginTop: "15px"
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Typography variant="h6" color={theme.palette.secondary.main}>Восстановление пароля</Typography>
|
||||
<Logo/>
|
||||
<Typography variant="h6" color={theme.palette.secondary.main}>
|
||||
Восстановление пароля
|
||||
</Typography>
|
||||
<Logo />
|
||||
|
||||
<Box sx={{display:"flex", alignItems:"center",marginTop:"15px","> *": {marginRight:"10px"}}}>
|
||||
<LockOutlinedIcon htmlColor={theme.palette.golden.main}/>
|
||||
<Field as={OutlinedInput} name="code" variant="filled" label="Код из сообщения"/>
|
||||
</Box>
|
||||
<Box sx={{ display: "flex", alignItems: "center", marginTop: "15px", "> *": { marginRight: "10px" } }}>
|
||||
<LockOutlinedIcon htmlColor={theme.palette.golden.main} />
|
||||
<Field as={OutlinedInput} name="code" variant="filled" label="Код из сообщения" />
|
||||
</Box>
|
||||
|
||||
<CleverButton type="submit" text="Отправить" isReady={isReady}/>
|
||||
<Link to="/signin" style={{textDecoration:"none"}}><Typography color={theme.palette.golden.main}>Я помню пароль</Typography></Link>
|
||||
</Box>
|
||||
</Box>
|
||||
</Form>
|
||||
</Formik>
|
||||
)
|
||||
}
|
||||
}
|
||||
<CleverButton type="submit" text="Отправить" isReady={isReady} />
|
||||
<Link to="/signin" style={{ textDecoration: "none" }}>
|
||||
<Typography color={theme.palette.golden.main}>Я помню пароль</Typography>
|
||||
</Link>
|
||||
</Box>
|
||||
</Box>
|
||||
</Form>
|
||||
</Formik>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@ -1,137 +1,155 @@
|
||||
import * as React from "react"
|
||||
import * as React from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { enqueueSnackbar } from 'notistack';
|
||||
import {useTheme} from "@mui/material/styles"
|
||||
import { Formik, Field, Form } from 'formik'
|
||||
import { Link } from "react-router-dom"
|
||||
import { Box, Checkbox, TextField, Typography, FormControlLabel} from "@mui/material"
|
||||
import Logo from "@pages/Logo"
|
||||
import CleverButton from "@kitUI/cleverButton"
|
||||
import OutlinedInput from "@kitUI/outlinedInput"
|
||||
import makeRequest from "@kitUI/makeRequest";
|
||||
import { enqueueSnackbar } from "notistack";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import { Formik, Field, Form } from "formik";
|
||||
import { Link } from "react-router-dom";
|
||||
import { Box, Checkbox, Typography, FormControlLabel } from "@mui/material";
|
||||
import Logo from "@pages/Logo";
|
||||
import CleverButton from "@kitUI/cleverButton";
|
||||
import OutlinedInput from "@kitUI/outlinedInput";
|
||||
import EmailOutlinedIcon from "@mui/icons-material/EmailOutlined";
|
||||
import LockOutlinedIcon from "@mui/icons-material/LockOutlined"
|
||||
import LockOutlinedIcon from "@mui/icons-material/LockOutlined";
|
||||
import { authStore } from "@root/stores/auth";
|
||||
|
||||
interface Values {
|
||||
email: string;
|
||||
password: string;
|
||||
email: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
function validate(values: Values) {
|
||||
const errors = {} as any;
|
||||
if (!values.email) {
|
||||
errors.email = "Required";
|
||||
}
|
||||
if (!values.password) {
|
||||
errors.password = "Required";
|
||||
} else if (!/^[\S]{8,25}$/i.test(values.password)) {
|
||||
errors.password = "Invalid password";
|
||||
}
|
||||
return errors;
|
||||
const errors = {} as any;
|
||||
if (!values.email) {
|
||||
errors.email = "Required";
|
||||
}
|
||||
if (!values.password) {
|
||||
errors.password = "Required";
|
||||
} else if (!/^[\S]{8,25}$/i.test(values.password)) {
|
||||
errors.password = "Invalid password";
|
||||
}
|
||||
return errors;
|
||||
}
|
||||
export default () => {
|
||||
const theme = useTheme()
|
||||
const navigate = useNavigate();
|
||||
const [isReady, setIsReady] = React.useState(true)
|
||||
const theme = useTheme();
|
||||
const navigate = useNavigate();
|
||||
const [isReady, setIsReady] = React.useState(true);
|
||||
const { makeRequest } = authStore();
|
||||
|
||||
return(
|
||||
<Formik
|
||||
initialValues={{
|
||||
email: "",
|
||||
password: ""
|
||||
}}
|
||||
validate={validate}
|
||||
onSubmit={(values) => {
|
||||
makeRequest({
|
||||
url: "https://admin.pena.digital/auth/login",
|
||||
body: {
|
||||
"email": values.email,
|
||||
"password": values.password
|
||||
},
|
||||
useToken: false
|
||||
})
|
||||
.then((e) => {
|
||||
console.log(e)
|
||||
navigate("/users")
|
||||
})
|
||||
.catch((e) => {
|
||||
console.log(e)
|
||||
enqueueSnackbar(e.message ? e.message : `Unknown error`)
|
||||
})
|
||||
}}
|
||||
return (
|
||||
<Formik
|
||||
initialValues={{
|
||||
email: "",
|
||||
password: "",
|
||||
}}
|
||||
validate={validate}
|
||||
onSubmit={(values) => {
|
||||
makeRequest({
|
||||
url: "https://admin.pena.digital/auth/login",
|
||||
body: {
|
||||
email: values.email,
|
||||
password: values.password,
|
||||
},
|
||||
useToken: false,
|
||||
})
|
||||
.then((e) => {
|
||||
console.log(e);
|
||||
navigate("/users");
|
||||
})
|
||||
.catch((e) => {
|
||||
console.log(e);
|
||||
enqueueSnackbar(e.message ? e.message : `Unknown error`);
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Form>
|
||||
<Box
|
||||
component="section"
|
||||
sx={{
|
||||
minHeight: "100vh",
|
||||
height: "100%",
|
||||
width: "100%",
|
||||
backgroundColor: theme.palette.content.main,
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
padding: "15px 0",
|
||||
}}
|
||||
>
|
||||
<Form>
|
||||
<Box component="section"
|
||||
sx={{
|
||||
minHeight: "100vh",
|
||||
height: "100%",
|
||||
width: "100%",
|
||||
backgroundColor: theme.palette.content.main,
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
padding: "15px 0"
|
||||
}}
|
||||
>
|
||||
<Box component="article"
|
||||
sx={{
|
||||
width: "350px",
|
||||
backgroundColor: theme.palette.content.main,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
"> *": {
|
||||
marginTop: "15px"
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Logo/>
|
||||
<Box>
|
||||
<Typography variant="h5" color={theme.palette.secondary.main}>Добро пожаловать</Typography>
|
||||
<Typography variant="h6" color={theme.palette.secondary.main}>Мы рады что вы выбрали нас!</Typography>
|
||||
</Box>
|
||||
<Box sx={{display:"flex", alignItems:"center",marginTop:"15px","> *": {marginRight:"10px"}}}>
|
||||
<EmailOutlinedIcon htmlColor={theme.palette.golden.main}/>
|
||||
<Field as={OutlinedInput} name="email" variant="filled" label="Эл. почта"/>
|
||||
</Box>
|
||||
<Box sx={{display:"flex", alignItems:"center",marginTop:"15px","> *": {marginRight:"10px"}}}>
|
||||
<LockOutlinedIcon htmlColor={theme.palette.golden.main}/>
|
||||
<Field as={OutlinedInput} type="password" name="password" variant="filled" label="Пароль"/>
|
||||
</Box>
|
||||
<Box component="article"
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center"
|
||||
}}
|
||||
>
|
||||
<FormControlLabel
|
||||
sx={{color:"white"}}
|
||||
control={<Checkbox
|
||||
value="checkedA"
|
||||
inputProps={{ 'aria-label': 'Checkbox A' }}
|
||||
sx={{
|
||||
color: "white",
|
||||
transform: "scale(1.5)",
|
||||
"&.Mui-checked": {
|
||||
color: "white",
|
||||
},
|
||||
"&.MuiFormControlLabel-root": {
|
||||
color: "white",
|
||||
},
|
||||
}}
|
||||
/>} label="Запомнить этот компьютер" />
|
||||
</Box>
|
||||
<Link to="/restore" style={{textDecoration:"none"}}><Typography color={theme.palette.golden.main}>Забыли пароль?</Typography></Link>
|
||||
<CleverButton type="submit" text="Войти" isReady={isReady}/>
|
||||
<Box sx={{
|
||||
display: "flex"
|
||||
}}>
|
||||
<Typography color={theme.palette.secondary.main}>У вас нет аккаунта? </Typography>
|
||||
<Link to="/signup" style={{textDecoration:"none"}}><Typography color={theme.palette.golden.main}>Зарегестрируйтесь</Typography></Link>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</Form>
|
||||
</Formik>
|
||||
)
|
||||
}
|
||||
<Box
|
||||
component="article"
|
||||
sx={{
|
||||
width: "350px",
|
||||
backgroundColor: theme.palette.content.main,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
"> *": {
|
||||
marginTop: "15px",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Logo />
|
||||
<Box>
|
||||
<Typography variant="h5" color={theme.palette.secondary.main}>
|
||||
Добро пожаловать
|
||||
</Typography>
|
||||
<Typography variant="h6" color={theme.palette.secondary.main}>
|
||||
Мы рады что вы выбрали нас!
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box sx={{ display: "flex", alignItems: "center", marginTop: "15px", "> *": { marginRight: "10px" } }}>
|
||||
<EmailOutlinedIcon htmlColor={theme.palette.golden.main} />
|
||||
<Field as={OutlinedInput} name="email" variant="filled" label="Эл. почта" />
|
||||
</Box>
|
||||
<Box sx={{ display: "flex", alignItems: "center", marginTop: "15px", "> *": { marginRight: "10px" } }}>
|
||||
<LockOutlinedIcon htmlColor={theme.palette.golden.main} />
|
||||
<Field as={OutlinedInput} type="password" name="password" variant="filled" label="Пароль" />
|
||||
</Box>
|
||||
<Box
|
||||
component="article"
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<FormControlLabel
|
||||
sx={{ color: "white" }}
|
||||
control={
|
||||
<Checkbox
|
||||
value="checkedA"
|
||||
inputProps={{ "aria-label": "Checkbox A" }}
|
||||
sx={{
|
||||
color: "white",
|
||||
transform: "scale(1.5)",
|
||||
"&.Mui-checked": {
|
||||
color: "white",
|
||||
},
|
||||
"&.MuiFormControlLabel-root": {
|
||||
color: "white",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
}
|
||||
label="Запомнить этот компьютер"
|
||||
/>
|
||||
</Box>
|
||||
<Link to="/restore" style={{ textDecoration: "none" }}>
|
||||
<Typography color={theme.palette.golden.main}>Забыли пароль?</Typography>
|
||||
</Link>
|
||||
<CleverButton type="submit" text="Войти" isReady={isReady} />
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
}}
|
||||
>
|
||||
<Typography color={theme.palette.secondary.main}>У вас нет аккаунта? </Typography>
|
||||
<Link to="/signup" style={{ textDecoration: "none" }}>
|
||||
<Typography color={theme.palette.golden.main}>Зарегестрируйтесь</Typography>
|
||||
</Link>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</Form>
|
||||
</Formik>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,118 +1,138 @@
|
||||
import * as React from "react"
|
||||
import { enqueueSnackbar } from 'notistack';
|
||||
import * as React from "react";
|
||||
import { enqueueSnackbar } from "notistack";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useTheme } from "@mui/material/styles"
|
||||
import { Formik, Field, Form } from "formik"
|
||||
import { Link } from "react-router-dom"
|
||||
import {Box, Typography} from "@mui/material"
|
||||
import CleverButton from "@kitUI/cleverButton"
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import { Formik, Field, Form } from "formik";
|
||||
import { Link } from "react-router-dom";
|
||||
import { Box, Typography } from "@mui/material";
|
||||
import CleverButton from "@kitUI/cleverButton";
|
||||
import OutlinedInput from "@kitUI/outlinedInput";
|
||||
import makeRequest from "@kitUI/makeRequest";
|
||||
import Logo from "@pages/Logo/index"
|
||||
import EmailOutlinedIcon from "@mui/icons-material/EmailOutlined"
|
||||
import LockOutlinedIcon from "@mui/icons-material/LockOutlined"
|
||||
|
||||
import Logo from "@pages/Logo/index";
|
||||
import EmailOutlinedIcon from "@mui/icons-material/EmailOutlined";
|
||||
import LockOutlinedIcon from "@mui/icons-material/LockOutlined";
|
||||
import { authStore } from "@root/stores/auth";
|
||||
interface Values {
|
||||
email: string;
|
||||
password: string;
|
||||
repeatPassword: string;
|
||||
email: string;
|
||||
password: string;
|
||||
repeatPassword: string;
|
||||
}
|
||||
function validate(values: Values) {
|
||||
const errors = {} as any;
|
||||
if (!values.email) {
|
||||
errors.login = "Required";
|
||||
}
|
||||
if (!values.password) {
|
||||
errors.password = "Required";
|
||||
} else if (!/^[\S]{8,25}$/i.test(values.password)) {
|
||||
errors.password = "Invalid password";
|
||||
}
|
||||
if (values.password !== values.repeatPassword) {
|
||||
errors.repeatPassword = "Passwords do not match";
|
||||
}
|
||||
return errors;
|
||||
const errors = {} as any;
|
||||
if (!values.email) {
|
||||
errors.login = "Required";
|
||||
}
|
||||
if (!values.password) {
|
||||
errors.password = "Required";
|
||||
} else if (!/^[\S]{8,25}$/i.test(values.password)) {
|
||||
errors.password = "Invalid password";
|
||||
}
|
||||
if (values.password !== values.repeatPassword) {
|
||||
errors.repeatPassword = "Passwords do not match";
|
||||
}
|
||||
return errors;
|
||||
}
|
||||
export default () => {
|
||||
const navigate = useNavigate();
|
||||
const theme = useTheme()
|
||||
const [isReady, setIsReady] = React.useState(true)
|
||||
const navigate = useNavigate();
|
||||
const theme = useTheme();
|
||||
const [isReady, setIsReady] = React.useState(true);
|
||||
const { makeRequest } = authStore();
|
||||
|
||||
return(
|
||||
<Formik
|
||||
initialValues={{
|
||||
email: "",
|
||||
password: "",
|
||||
repeatPassword: ""
|
||||
}}
|
||||
validate={validate}
|
||||
onSubmit={(values) => {
|
||||
makeRequest({
|
||||
url: "https://admin.pena.digital/auth/register",
|
||||
body: {
|
||||
"login": values.email,
|
||||
"email": values.email,
|
||||
"password": values.repeatPassword,
|
||||
"phoneNumber": "--"
|
||||
},
|
||||
useToken: false
|
||||
})
|
||||
.then((e) => {
|
||||
navigate("/users")
|
||||
})
|
||||
.catch((e) => {
|
||||
console.log(e)
|
||||
enqueueSnackbar(e.response && e.response.data && e.response.data.message ? e.response.data.message : `Unknown error`)
|
||||
})
|
||||
}}
|
||||
return (
|
||||
<Formik
|
||||
initialValues={{
|
||||
email: "",
|
||||
password: "",
|
||||
repeatPassword: "",
|
||||
}}
|
||||
validate={validate}
|
||||
onSubmit={(values) => {
|
||||
makeRequest({
|
||||
url: "https://admin.pena.digital/auth/register",
|
||||
body: {
|
||||
login: values.email,
|
||||
email: values.email,
|
||||
password: values.repeatPassword,
|
||||
phoneNumber: "--",
|
||||
},
|
||||
useToken: false,
|
||||
})
|
||||
.then((e) => {
|
||||
navigate("/users");
|
||||
})
|
||||
.catch((e) => {
|
||||
console.log(e);
|
||||
enqueueSnackbar(
|
||||
e.response && e.response.data && e.response.data.message ? e.response.data.message : `Unknown error`
|
||||
);
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Form>
|
||||
<Box
|
||||
component="section"
|
||||
sx={{
|
||||
minHeight: "100vh",
|
||||
height: "100%",
|
||||
width: "100%",
|
||||
backgroundColor: theme.palette.content.main,
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
padding: "15px 0",
|
||||
}}
|
||||
>
|
||||
<Form>
|
||||
<Box component="section"
|
||||
sx={{
|
||||
minHeight: "100vh",
|
||||
height: "100%",
|
||||
width: "100%",
|
||||
backgroundColor: theme.palette.content.main,
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
padding: "15px 0"
|
||||
}}
|
||||
>
|
||||
<Box component="article"
|
||||
sx={{
|
||||
width: "350px",
|
||||
backgroundColor: theme.palette.content.main,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
"> *": {
|
||||
marginTop: "15px"
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Typography variant="h6" color={theme.palette.secondary.main}>Новый аккаунт</Typography>
|
||||
<Logo/>
|
||||
<Box
|
||||
component="article"
|
||||
sx={{
|
||||
width: "350px",
|
||||
backgroundColor: theme.palette.content.main,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
"> *": {
|
||||
marginTop: "15px",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Typography variant="h6" color={theme.palette.secondary.main}>
|
||||
Новый аккаунт
|
||||
</Typography>
|
||||
<Logo />
|
||||
|
||||
<Box>
|
||||
<Typography variant="h5" color={theme.palette.secondary.main}>Добро пожаловать</Typography>
|
||||
<Typography variant="h6" color={theme.palette.secondary.main}>Мы рады что вы выбрали нас!</Typography>
|
||||
</Box>
|
||||
<Box sx={{display:"flex", alignItems:"center",marginTop:"15px","> *": {marginRight:"10px"}}}>
|
||||
<EmailOutlinedIcon htmlColor={theme.palette.golden.main}/>
|
||||
<Field as={OutlinedInput} name="email" variant="filled" label="Эл. почта"/>
|
||||
</Box>
|
||||
<Box sx={{display:"flex", alignItems:"center",marginTop:"15px","> *": {marginRight:"10px"}}}>
|
||||
<LockOutlinedIcon htmlColor={theme.palette.golden.main}/>
|
||||
<Field as={OutlinedInput} type="password" name="password" variant="filled" label="Пароль"/>
|
||||
</Box>
|
||||
<Box sx={{display:"flex", alignItems:"center",marginTop:"15px","> *": {marginRight:"10px"}}}>
|
||||
<LockOutlinedIcon htmlColor={theme.palette.golden.main}/>
|
||||
<Field as={OutlinedInput} type="password" name="repeatPassword" variant="filled" label="Повторите пароль"/>
|
||||
</Box>
|
||||
<CleverButton type="submit" text="Отправить" isReady={isReady}/>
|
||||
<Link to="/signin" style={{textDecoration: "none"}} ><Typography color={theme.palette.golden.main}>У меня уже есть аккаунт</Typography></Link>
|
||||
</Box>
|
||||
</Box>
|
||||
</Form>
|
||||
</Formik>
|
||||
)
|
||||
}
|
||||
<Box>
|
||||
<Typography variant="h5" color={theme.palette.secondary.main}>
|
||||
Добро пожаловать
|
||||
</Typography>
|
||||
<Typography variant="h6" color={theme.palette.secondary.main}>
|
||||
Мы рады что вы выбрали нас!
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box sx={{ display: "flex", alignItems: "center", marginTop: "15px", "> *": { marginRight: "10px" } }}>
|
||||
<EmailOutlinedIcon htmlColor={theme.palette.golden.main} />
|
||||
<Field as={OutlinedInput} name="email" variant="filled" label="Эл. почта" />
|
||||
</Box>
|
||||
<Box sx={{ display: "flex", alignItems: "center", marginTop: "15px", "> *": { marginRight: "10px" } }}>
|
||||
<LockOutlinedIcon htmlColor={theme.palette.golden.main} />
|
||||
<Field as={OutlinedInput} type="password" name="password" variant="filled" label="Пароль" />
|
||||
</Box>
|
||||
<Box sx={{ display: "flex", alignItems: "center", marginTop: "15px", "> *": { marginRight: "10px" } }}>
|
||||
<LockOutlinedIcon htmlColor={theme.palette.golden.main} />
|
||||
<Field
|
||||
as={OutlinedInput}
|
||||
type="password"
|
||||
name="repeatPassword"
|
||||
variant="filled"
|
||||
label="Повторите пароль"
|
||||
/>
|
||||
</Box>
|
||||
<CleverButton type="submit" text="Отправить" isReady={isReady} />
|
||||
<Link to="/signin" style={{ textDecoration: "none" }}>
|
||||
<Typography color={theme.palette.golden.main}>У меня уже есть аккаунт</Typography>
|
||||
</Link>
|
||||
</Box>
|
||||
</Box>
|
||||
</Form>
|
||||
</Formik>
|
||||
);
|
||||
};
|
||||
|
||||
@ -8,88 +8,91 @@ import { GetTicketsRequest, Ticket } from "@root/model/ticket";
|
||||
import { clearTickets, updateTickets } from "@root/stores/tickets";
|
||||
import { enqueueSnackbar } from "notistack";
|
||||
import { clearMessages } from "@root/stores/messages";
|
||||
|
||||
import { authStore } from "@root/stores/auth";
|
||||
|
||||
const TICKETS_PER_PAGE = 20;
|
||||
|
||||
export default function Support() {
|
||||
const theme = useTheme();
|
||||
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
||||
const [currentPage, setCurrentPage] = useState<number>(0);
|
||||
const fetchingStateRef = useRef<"idle" | "fetching" | "all fetched">("idle");
|
||||
const theme = useTheme();
|
||||
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
||||
const [currentPage, setCurrentPage] = useState<number>(0);
|
||||
const { token } = authStore();
|
||||
const fetchingStateRef = useRef<"idle" | "fetching" | "all fetched">("idle");
|
||||
|
||||
useEffect(function fetchTickets() {
|
||||
const getTicketsBody: GetTicketsRequest = {
|
||||
amt: TICKETS_PER_PAGE,
|
||||
page: currentPage,
|
||||
status: "open",
|
||||
};
|
||||
const controller = new AbortController();
|
||||
useEffect(
|
||||
function fetchTickets() {
|
||||
const getTicketsBody: GetTicketsRequest = {
|
||||
amt: TICKETS_PER_PAGE,
|
||||
page: currentPage,
|
||||
status: "open",
|
||||
};
|
||||
const controller = new AbortController();
|
||||
|
||||
fetchingStateRef.current = "fetching";
|
||||
getTickets({
|
||||
body: getTicketsBody,
|
||||
signal: controller.signal,
|
||||
}).then(result => {
|
||||
console.log("GetTicketsResponse", result);
|
||||
if (result.data) {
|
||||
updateTickets(result.data);
|
||||
fetchingStateRef.current = "idle";
|
||||
} else fetchingStateRef.current = "all fetched";
|
||||
}).catch(error => {
|
||||
console.log("Error fetching tickets", error);
|
||||
enqueueSnackbar(error.message);
|
||||
fetchingStateRef.current = "fetching";
|
||||
getTickets({
|
||||
body: getTicketsBody,
|
||||
signal: controller.signal,
|
||||
})
|
||||
.then((result) => {
|
||||
console.log("GetTicketsResponse", result);
|
||||
if (result.data) {
|
||||
updateTickets(result.data);
|
||||
fetchingStateRef.current = "idle";
|
||||
} else fetchingStateRef.current = "all fetched";
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log("Error fetching tickets", error);
|
||||
enqueueSnackbar(error.message);
|
||||
});
|
||||
|
||||
return () => controller.abort();
|
||||
}, [currentPage]);
|
||||
return () => controller.abort();
|
||||
},
|
||||
[currentPage]
|
||||
);
|
||||
|
||||
useEffect(function subscribeToTickets() {
|
||||
const token = localStorage.getItem("AT");
|
||||
if (!token) return;
|
||||
useEffect(function subscribeToTickets() {
|
||||
if (!token) return;
|
||||
|
||||
const unsubscribe = subscribeToAllTickets({
|
||||
accessToken: token,
|
||||
onMessage(event) {
|
||||
try {
|
||||
const newTicket = JSON.parse(event.data) as Ticket;
|
||||
console.log("SSE: parsed newTicket:", newTicket);
|
||||
updateTickets([newTicket]);
|
||||
} catch (error) {
|
||||
console.log("SSE: couldn't parse:", event.data);
|
||||
console.log("Error parsing ticket SSE", error);
|
||||
}
|
||||
},
|
||||
onError(event) {
|
||||
console.log("SSE Error:", event);
|
||||
}
|
||||
});
|
||||
const unsubscribe = subscribeToAllTickets({
|
||||
accessToken: token,
|
||||
onMessage(event) {
|
||||
try {
|
||||
const newTicket = JSON.parse(event.data) as Ticket;
|
||||
console.log("SSE: parsed newTicket:", newTicket);
|
||||
updateTickets([newTicket]);
|
||||
} catch (error) {
|
||||
console.log("SSE: couldn't parse:", event.data);
|
||||
console.log("Error parsing ticket SSE", error);
|
||||
}
|
||||
},
|
||||
onError(event) {
|
||||
console.log("SSE Error:", event);
|
||||
},
|
||||
});
|
||||
|
||||
return () => {
|
||||
unsubscribe();
|
||||
clearMessages();
|
||||
clearTickets();
|
||||
};
|
||||
}, []);
|
||||
return () => {
|
||||
unsubscribe();
|
||||
clearMessages();
|
||||
clearTickets();
|
||||
};
|
||||
}, []);
|
||||
|
||||
const incrementCurrentPage = () => setCurrentPage(prev => prev + 1);
|
||||
const incrementCurrentPage = () => setCurrentPage((prev) => prev + 1);
|
||||
|
||||
const ticketList = <TicketList fetchingStateRef={fetchingStateRef} incrementCurrentPage={incrementCurrentPage} />;
|
||||
const ticketList = <TicketList fetchingStateRef={fetchingStateRef} incrementCurrentPage={incrementCurrentPage} />;
|
||||
|
||||
return (
|
||||
<Box sx={{
|
||||
display: "flex",
|
||||
width: "100%",
|
||||
flexDirection: upMd ? "row" : "column",
|
||||
gap: "12px",
|
||||
}}>
|
||||
{!upMd &&
|
||||
<Collapse headerText="Тикеты">
|
||||
{ticketList}
|
||||
</Collapse>
|
||||
}
|
||||
<Chat />
|
||||
{upMd && ticketList}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
width: "100%",
|
||||
flexDirection: upMd ? "row" : "column",
|
||||
gap: "12px",
|
||||
}}
|
||||
>
|
||||
{!upMd && <Collapse headerText="Тикеты">{ticketList}</Collapse>}
|
||||
<Chat />
|
||||
{upMd && ticketList}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,36 +1,42 @@
|
||||
import * as React from "react";
|
||||
import {Box, IconButton, Typography} from "@mui/material";
|
||||
import { Box, IconButton, Typography } from "@mui/material";
|
||||
import theme from "../../../theme";
|
||||
import ExitToAppOutlinedIcon from '@mui/icons-material/ExitToAppOutlined';
|
||||
import ExitToAppOutlinedIcon from "@mui/icons-material/ExitToAppOutlined";
|
||||
import Logo from "../../Logo";
|
||||
import makeRequest from "@kitUI/makeRequest";
|
||||
import { authStore } from "@root/stores/auth";
|
||||
|
||||
|
||||
const Header: React.FC = () => {
|
||||
const Header: React.FC = () => {
|
||||
const { makeRequest } = authStore();
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Box sx={{
|
||||
backgroundColor: theme.palette.menu.main,
|
||||
width: "100%",
|
||||
minHeight: "85px",
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
flexWrap: 'wrap',
|
||||
paddingLeft: '5px'
|
||||
}}>
|
||||
<Box sx={{
|
||||
width: "150px",
|
||||
<Box
|
||||
sx={{
|
||||
backgroundColor: theme.palette.menu.main,
|
||||
width: "100%",
|
||||
minHeight: "85px",
|
||||
display: "flex",
|
||||
justifyContent: "right",
|
||||
alignItems: "center"
|
||||
}}>
|
||||
justifyContent: "space-between",
|
||||
flexWrap: "wrap",
|
||||
paddingLeft: "5px",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
width: "150px",
|
||||
display: "flex",
|
||||
justifyContent: "right",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<Logo />
|
||||
</Box>
|
||||
|
||||
<Box sx={{
|
||||
display: "flex"
|
||||
}}>
|
||||
<Typography
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
variant="h6"
|
||||
sx={{
|
||||
maxWidth: "230px",
|
||||
@ -38,38 +44,39 @@ const Header: React.FC = () => {
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
fontWeight: "normal"
|
||||
fontWeight: "normal",
|
||||
}}
|
||||
>
|
||||
Добро пожаловать, Администратор сервиса
|
||||
Добро пожаловать, Администратор сервиса
|
||||
</Typography>
|
||||
|
||||
<IconButton
|
||||
onClick={()=>{
|
||||
makeRequest({
|
||||
url: "https://admin.pena.digital/auth/logout",
|
||||
contentType: true
|
||||
})
|
||||
.then(()=>localStorage.setItem('AT', ""))
|
||||
onClick={() => {
|
||||
makeRequest({
|
||||
url: "https://admin.pena.digital/auth/logout",
|
||||
contentType: true,
|
||||
}).then(() => localStorage.setItem("AT", ""));
|
||||
}}
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
cursor: "pointer",
|
||||
padding: "0 31px",
|
||||
}}
|
||||
>
|
||||
<ExitToAppOutlinedIcon
|
||||
sx={{
|
||||
color: theme.palette.golden.main,
|
||||
transform: "scale(1.3)",
|
||||
}}
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
cursor: "pointer",
|
||||
padding: '0 31px'
|
||||
}}>
|
||||
<ExitToAppOutlinedIcon sx={{
|
||||
color: theme.palette.golden.main,
|
||||
transform: "scale(1.3)"
|
||||
}} />
|
||||
/>
|
||||
</IconButton>
|
||||
</Box>
|
||||
</Box>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
export default Header;
|
||||
export default Header;
|
||||
|
||||
@ -1,21 +1,99 @@
|
||||
import axios, { AxiosResponse } from "axios";
|
||||
import { create } from "zustand";
|
||||
import { devtools } from "zustand/middleware";
|
||||
|
||||
type Token = string
|
||||
type Token = string;
|
||||
|
||||
interface AuthStore {
|
||||
token: Token
|
||||
setToken: (data: Token) => void;
|
||||
token: Token;
|
||||
setToken: (data: Token) => void;
|
||||
makeRequest: <TRequest = unknown, TResponse = unknown>(props: FirstRequest<TRequest>) => Promise<TResponse>;
|
||||
clearToken: () => void;
|
||||
}
|
||||
|
||||
interface FirstRequest<T> {
|
||||
method?: string;
|
||||
url: string;
|
||||
body?: T;
|
||||
useToken?: boolean;
|
||||
contentType?: boolean;
|
||||
signal?: AbortSignal;
|
||||
}
|
||||
|
||||
export const authStore = create<AuthStore>()(
|
||||
devtools(
|
||||
(set, get) => ({
|
||||
token: "",
|
||||
setToken: newToken => set({ token: newToken })
|
||||
}),
|
||||
{
|
||||
name: "token",
|
||||
}
|
||||
)
|
||||
);
|
||||
devtools(
|
||||
(set, get) => ({
|
||||
token: "",
|
||||
setToken: (newToken) => set({ token: newToken }),
|
||||
makeRequest: <TRequest, TResponse>(props: FirstRequest<TRequest>): Promise<TResponse> => {
|
||||
const newProps = { ...props, HC: (newToken: Token) => set({ token: newToken }), token: get().token };
|
||||
|
||||
return makeRequest<TRequest, TResponse>(newProps);
|
||||
},
|
||||
clearToken: () => set({ token: "" }),
|
||||
}),
|
||||
{
|
||||
name: "token",
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
interface MakeRequest<T> extends FirstRequest<T> {
|
||||
HC: (newToken: Token) => void;
|
||||
token: Token;
|
||||
}
|
||||
|
||||
async function makeRequest<TRequest, TResponse>({
|
||||
method = "post",
|
||||
url,
|
||||
body,
|
||||
useToken = true,
|
||||
contentType = false,
|
||||
HC,
|
||||
token,
|
||||
signal,
|
||||
}: MakeRequest<TRequest>) {
|
||||
//В случае 401 рефреш должен попробовать вызваться 1 раз
|
||||
let counterRefresh = true;
|
||||
let headers: any = {};
|
||||
if (useToken) headers["Authorization"] = token;
|
||||
if (contentType) headers["Content-Type"] = "application/json";
|
||||
|
||||
try {
|
||||
const response = await axios<TRequest, AxiosResponse<TResponse & { accessToken?: string }>>({
|
||||
url,
|
||||
method,
|
||||
headers,
|
||||
data: body,
|
||||
signal,
|
||||
});
|
||||
|
||||
if (response.data?.accessToken) {
|
||||
HC(response.data.accessToken);
|
||||
}
|
||||
|
||||
return response.data;
|
||||
} catch (error: any) {
|
||||
if (error?.response?.status === 401 && counterRefresh) {
|
||||
const refreshResponse = await refresh();
|
||||
if (refreshResponse.data?.accessToken) HC(refreshResponse.data.accessToken);
|
||||
counterRefresh = false;
|
||||
|
||||
headers["Authorization"] = refreshResponse.data.accessToken;
|
||||
const response = await axios<TRequest, AxiosResponse<TResponse>>({ url, method, headers, data: body, signal });
|
||||
|
||||
return response.data;
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
function refresh() {
|
||||
return axios<never, AxiosResponse<{ accessToken: string }>>("https://admin.pena.digital/auth/refresh", {
|
||||
headers: {
|
||||
Authorization: localStorage.getItem("AT"),
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user