forked from VinokurovVE/tests
Refactored forms
This commit is contained in:
@ -15,7 +15,9 @@ import Documents from "./pages/Documents"
|
|||||||
import Reports from "./pages/Reports"
|
import Reports from "./pages/Reports"
|
||||||
import Boilers from "./pages/Boilers"
|
import Boilers from "./pages/Boilers"
|
||||||
import Servers from "./pages/Servers"
|
import Servers from "./pages/Servers"
|
||||||
import { Api, Assignment, Cloud, Factory, Home, Login, People, Shield, Storage } from "@mui/icons-material"
|
import { Api, Assignment, Cloud, Factory, Home, Login, Password, People, Settings as SettingsIcon, Shield, Storage } from "@mui/icons-material"
|
||||||
|
import Settings from "./pages/Settings"
|
||||||
|
import PasswordReset from "./pages/auth/PasswordReset"
|
||||||
|
|
||||||
// Определение страниц с путями и компонентом для рендера
|
// Определение страниц с путями и компонентом для рендера
|
||||||
export const pages = [
|
export const pages = [
|
||||||
@ -35,6 +37,22 @@ export const pages = [
|
|||||||
drawer: false,
|
drawer: false,
|
||||||
dashboard: false,
|
dashboard: false,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: "",
|
||||||
|
path: "/auth/password-reset",
|
||||||
|
icon: <Password />,
|
||||||
|
component: <PasswordReset />,
|
||||||
|
drawer: false,
|
||||||
|
dashboard: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Настройки",
|
||||||
|
path: "/settings",
|
||||||
|
icon: <SettingsIcon />,
|
||||||
|
component: <Settings />,
|
||||||
|
drawer: false,
|
||||||
|
dashboard: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: "Главная",
|
label: "Главная",
|
||||||
path: "/",
|
path: "/",
|
||||||
|
@ -130,7 +130,9 @@ export default function AccountMenu() {
|
|||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
|
||||||
|
|
||||||
<MenuItem onClick={handleClose}>
|
<MenuItem onClick={() => {
|
||||||
|
navigate('/settings')
|
||||||
|
}}>
|
||||||
<ListItemIcon>
|
<ListItemIcon>
|
||||||
<Settings fontSize="small" />
|
<Settings fontSize="small" />
|
||||||
</ListItemIcon>
|
</ListItemIcon>
|
||||||
|
91
frontend_reactjs/src/components/FormFields.tsx
Normal file
91
frontend_reactjs/src/components/FormFields.tsx
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
import { SubmitHandler, useForm } from 'react-hook-form'
|
||||||
|
import { CreateField } from '../interfaces/create'
|
||||||
|
import { Box, Button, CircularProgress, Stack, TextField, Typography } from '@mui/material';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
title?: string;
|
||||||
|
submitHandler?: any;
|
||||||
|
fields: CreateField[];
|
||||||
|
submitButtonText?: string;
|
||||||
|
mutateHandler?: any;
|
||||||
|
defaultValues?: {};
|
||||||
|
watchValues?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
function FormFields({
|
||||||
|
title = '',
|
||||||
|
submitHandler,
|
||||||
|
fields,
|
||||||
|
submitButtonText = 'Сохранить',
|
||||||
|
mutateHandler,
|
||||||
|
defaultValues
|
||||||
|
}: Props) {
|
||||||
|
const getDefaultValues = (fields: CreateField[]) => {
|
||||||
|
let result: { [key: string]: string | boolean } = {}
|
||||||
|
fields.forEach((field: CreateField) => {
|
||||||
|
result[field.key] = field.defaultValue || defaultValues?.[field.key as keyof {}]
|
||||||
|
})
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
const { register, handleSubmit, watch, formState: { errors, isSubmitting, dirtyFields } } = useForm({
|
||||||
|
defaultValues: getDefaultValues(fields)
|
||||||
|
})
|
||||||
|
|
||||||
|
const onSubmit: SubmitHandler<any> = async (data) => {
|
||||||
|
try {
|
||||||
|
await submitHandler?.(data).then(() => {
|
||||||
|
mutateHandler?.()
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form onSubmit={handleSubmit(onSubmit)}>
|
||||||
|
<Stack spacing={2} width='100%'>
|
||||||
|
<Typography variant="h6" component="h6" gutterBottom>
|
||||||
|
{title}
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
{fields.map((field: CreateField) => {
|
||||||
|
return (
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
margin='normal'
|
||||||
|
key={field.key}
|
||||||
|
type={field.inputType ? field.inputType : 'text'}
|
||||||
|
label={field.headerName || field.key.charAt(0).toUpperCase() + field.key.slice(1)}
|
||||||
|
required={field.required || false}
|
||||||
|
{...register(field.key, {
|
||||||
|
required: `${field.key} обязателен`,
|
||||||
|
validate: (val: string | boolean) => {
|
||||||
|
if (field.watch) {
|
||||||
|
if (watch(field.watch) != val) {
|
||||||
|
return field.watchMessage || ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
error={!!errors[field.key]}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
|
||||||
|
<Box sx={{
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
gap: "8px"
|
||||||
|
}}>
|
||||||
|
<Button disabled={isSubmitting} type="submit" variant="contained" color="primary">
|
||||||
|
{isSubmitting ? <CircularProgress size={16} /> : submitButtonText}
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Stack>
|
||||||
|
</form>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default FormFields
|
@ -1,108 +0,0 @@
|
|||||||
import { Box, Button, Modal, TextField, Typography } from '@mui/material';
|
|
||||||
import { SubmitHandler, useForm } from 'react-hook-form';
|
|
||||||
import { CreateField } from '../../interfaces/create';
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
title?: string;
|
|
||||||
open: boolean;
|
|
||||||
setOpen: (state: boolean) => void;
|
|
||||||
submitHandler?: any;
|
|
||||||
fields: CreateField[];
|
|
||||||
submitButtonText?: string;
|
|
||||||
mutateHandler?: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
const style = {
|
|
||||||
position: 'absolute',
|
|
||||||
top: '50%',
|
|
||||||
left: '50%',
|
|
||||||
transform: 'translate(-50%, -50%)',
|
|
||||||
width: 400,
|
|
||||||
bgcolor: 'background.paper',
|
|
||||||
boxShadow: 24,
|
|
||||||
borderRadius: 2,
|
|
||||||
p: 4,
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
gap: "8px"
|
|
||||||
}
|
|
||||||
|
|
||||||
function ModalCreate({
|
|
||||||
title = '',
|
|
||||||
open,
|
|
||||||
setOpen,
|
|
||||||
submitHandler,
|
|
||||||
fields,
|
|
||||||
submitButtonText = 'Сохранить',
|
|
||||||
mutateHandler
|
|
||||||
}: Props) {
|
|
||||||
const getDefaultValues = (fields: CreateField[]) => {
|
|
||||||
let result: { [key: string]: string | boolean } = {}
|
|
||||||
fields.forEach((field: CreateField) => {
|
|
||||||
result[field.key] = field.defaultValue
|
|
||||||
})
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
const { register, handleSubmit, formState: { errors } } = useForm({
|
|
||||||
defaultValues: getDefaultValues(fields)
|
|
||||||
})
|
|
||||||
|
|
||||||
const onSubmit: SubmitHandler<any> = async (data) => {
|
|
||||||
try {
|
|
||||||
await submitHandler?.(data).then(() => {
|
|
||||||
mutateHandler?.()
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Modal
|
|
||||||
open={open}
|
|
||||||
onClose={() => setOpen(false)}
|
|
||||||
aria-labelledby="modal-modal-title"
|
|
||||||
aria-describedby="modal-modal-description"
|
|
||||||
>
|
|
||||||
<form onSubmit={handleSubmit(onSubmit)}>
|
|
||||||
<Box sx={style}>
|
|
||||||
<Typography variant="h6" component="h6" gutterBottom>
|
|
||||||
{title}
|
|
||||||
</Typography>
|
|
||||||
|
|
||||||
{fields.map((field: CreateField) => {
|
|
||||||
return (
|
|
||||||
<TextField
|
|
||||||
key={field.key}
|
|
||||||
fullWidth
|
|
||||||
margin='normal'
|
|
||||||
label={field.headerName || field.key.charAt(0).toUpperCase() + field.key.slice(1)}
|
|
||||||
|
|
||||||
required={field.required || false}
|
|
||||||
{...register(field.key, { required: `${field.key} обязателен` })}
|
|
||||||
error={!!errors[field.key]}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
|
|
||||||
<Box sx={{
|
|
||||||
display: "flex",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
gap: "8px"
|
|
||||||
}}>
|
|
||||||
<Button type="submit" variant="contained" color="primary">
|
|
||||||
{submitButtonText}
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Button type="button" variant="outlined" color="primary" onClick={() => setOpen(false)}>
|
|
||||||
Отмена
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
</form>
|
|
||||||
</Modal>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ModalCreate
|
|
@ -1,3 +1,5 @@
|
|||||||
|
import { Validate } from "react-hook-form";
|
||||||
|
|
||||||
export interface CreateFieldTypes {
|
export interface CreateFieldTypes {
|
||||||
string: 'string';
|
string: 'string';
|
||||||
number: 'number';
|
number: 'number';
|
||||||
@ -9,7 +11,12 @@ export interface CreateFieldTypes {
|
|||||||
custom: 'custom';
|
custom: 'custom';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface InputTypes {
|
||||||
|
password: 'password';
|
||||||
|
}
|
||||||
|
|
||||||
export type CreateFieldType = CreateFieldTypes[keyof CreateFieldTypes]
|
export type CreateFieldType = CreateFieldTypes[keyof CreateFieldTypes]
|
||||||
|
export type InputType = InputTypes[keyof InputTypes]
|
||||||
|
|
||||||
export interface CreateField {
|
export interface CreateField {
|
||||||
key: string;
|
key: string;
|
||||||
@ -17,4 +24,8 @@ export interface CreateField {
|
|||||||
type: CreateFieldType;
|
type: CreateFieldType;
|
||||||
required?: boolean;
|
required?: boolean;
|
||||||
defaultValue?: any;
|
defaultValue?: any;
|
||||||
|
inputType?: InputType;
|
||||||
|
validate?: Validate<string, boolean>;
|
||||||
|
watch?: string;
|
||||||
|
watchMessage?: string;
|
||||||
}
|
}
|
@ -1,9 +1,10 @@
|
|||||||
export interface IUserCreate {
|
export interface IUser {
|
||||||
|
id: number;
|
||||||
|
password: string;
|
||||||
email: string;
|
email: string;
|
||||||
login: string;
|
login: string;
|
||||||
phone: string;
|
phone: string;
|
||||||
name: string;
|
name: string;
|
||||||
surname: string;
|
surname: string;
|
||||||
is_active: boolean;
|
is_active: boolean;
|
||||||
password: string;
|
|
||||||
}
|
}
|
@ -1,9 +1,31 @@
|
|||||||
// Layout for fullscreen pages
|
// Layout for fullscreen pages
|
||||||
|
|
||||||
|
import { Box, createTheme, ThemeProvider, useTheme } from "@mui/material";
|
||||||
import { Outlet } from "react-router-dom";
|
import { Outlet } from "react-router-dom";
|
||||||
|
|
||||||
export default function MainLayout() {
|
export default function MainLayout() {
|
||||||
|
const theme = useTheme()
|
||||||
|
const innerTheme = createTheme(theme)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Outlet />
|
<ThemeProvider theme={innerTheme}>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
color: (theme) => theme.palette.mode === 'light'
|
||||||
|
? theme.palette.grey[900]
|
||||||
|
: theme.palette.grey[100],
|
||||||
|
backgroundColor: (theme) =>
|
||||||
|
theme.palette.mode === 'light'
|
||||||
|
? theme.palette.grey[100]
|
||||||
|
: theme.palette.grey[900],
|
||||||
|
flexGrow: 1,
|
||||||
|
maxHeight: "100vh",
|
||||||
|
height: '100%',
|
||||||
|
overflow: 'auto',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Outlet />
|
||||||
|
</Box>
|
||||||
|
</ThemeProvider>
|
||||||
)
|
)
|
||||||
}
|
}
|
@ -1,10 +1,10 @@
|
|||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { Box, Button, CircularProgress } from '@mui/material'
|
import { Box, Button, CircularProgress, Modal } from '@mui/material'
|
||||||
import { DataGrid, GridColDef } from '@mui/x-data-grid'
|
import { DataGrid, GridColDef } from '@mui/x-data-grid'
|
||||||
import { useRoles } from '../hooks/swrHooks'
|
import { useRoles } from '../hooks/swrHooks'
|
||||||
import { CreateField } from '../interfaces/create'
|
import { CreateField } from '../interfaces/create'
|
||||||
import ModalCreate from '../components/modals/ModalCreate'
|
|
||||||
import RoleService from '../services/RoleService'
|
import RoleService from '../services/RoleService'
|
||||||
|
import FormFields from '../components/FormFields'
|
||||||
|
|
||||||
export default function Roles() {
|
export default function Roles() {
|
||||||
const { roles, isError, isLoading } = useRoles()
|
const { roles, isError, isLoading } = useRoles()
|
||||||
@ -37,13 +37,16 @@ export default function Roles() {
|
|||||||
Добавить роль
|
Добавить роль
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<ModalCreate
|
<Modal
|
||||||
open={open}
|
open={open}
|
||||||
setOpen={setOpen}
|
onClose={() => setOpen(false)}
|
||||||
fields={createFields}
|
>
|
||||||
submitHandler={RoleService.createRole}
|
<FormFields
|
||||||
title="Создание роли"
|
fields={createFields}
|
||||||
/>
|
submitHandler={RoleService.createRole}
|
||||||
|
title="Создание роли"
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
<DataGrid
|
<DataGrid
|
||||||
autoHeight
|
autoHeight
|
||||||
|
@ -1,38 +1,41 @@
|
|||||||
import { Box, CircularProgress } from "@mui/material"
|
import { Box, Stack, Typography } from "@mui/material"
|
||||||
import { DataGrid, GridColDef } from "@mui/x-data-grid"
|
import UserService from "../services/UserService"
|
||||||
import { useRoles, useUsers } from "../hooks/swrHooks"
|
import { useAuthStore } from "../store/auth"
|
||||||
import { IRole } from "../interfaces/role"
|
import { useEffect, useState } from "react"
|
||||||
|
import { CreateField } from "../interfaces/create"
|
||||||
|
import { IUser } from "../interfaces/user"
|
||||||
|
import FormFields from "../components/FormFields"
|
||||||
|
|
||||||
export default function Settings() {
|
export default function Settings() {
|
||||||
const { users, isError, isLoading } = useUsers()
|
const { token } = useAuthStore()
|
||||||
|
const [currentUser, setCurrentUser] = useState<IUser>()
|
||||||
|
|
||||||
const { roles } = useRoles()
|
const fetchCurrentUser = async () => {
|
||||||
|
if (token) {
|
||||||
|
await UserService.getCurrentUser(token).then(response => {
|
||||||
|
setCurrentUser(response.data)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const columns: GridColDef[] = [
|
useEffect(() => {
|
||||||
{ field: 'id', headerName: 'ID', type: "number", width: 70 },
|
if (token) {
|
||||||
{ field: 'email', headerName: 'Email', width: 130, editable: true },
|
fetchCurrentUser()
|
||||||
{ field: 'login', headerName: 'Логин', width: 130, editable: true },
|
}
|
||||||
{ field: 'phone', headerName: 'Телефон', width: 90, editable: true },
|
}, [token])
|
||||||
{ field: 'name', headerName: 'Имя', width: 90, editable: true },
|
|
||||||
{ field: 'surname', headerName: 'Фамилия', width: 90, editable: true },
|
|
||||||
{ field: 'is_active', headerName: 'Активен', type: "boolean", width: 90, editable: true },
|
|
||||||
{
|
|
||||||
field: 'role_id',
|
|
||||||
headerName: 'Роль',
|
|
||||||
valueGetter: (value) => {
|
|
||||||
if (roles) {
|
|
||||||
const roleName = roles.find((role: IRole) => role.id === value).name
|
|
||||||
return roleName
|
|
||||||
} else {
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
},
|
|
||||||
width: 90
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
if (isError) return <div>Произошла ошибка при получении данных.</div>
|
const profileFields: CreateField[] = [
|
||||||
if (isLoading) return <CircularProgress />
|
{ key: 'email', headerName: 'E-mail', type: 'string', required: true },
|
||||||
|
{ key: 'login', headerName: 'Логин', type: 'string', required: true },
|
||||||
|
{ key: 'phone', headerName: 'Телефон', type: 'string', required: false },
|
||||||
|
{ key: 'name', headerName: 'Имя', type: 'string', required: true },
|
||||||
|
{ key: 'surname', headerName: 'Фамилия', type: 'string', required: true },
|
||||||
|
]
|
||||||
|
|
||||||
|
const passwordFields: CreateField[] = [
|
||||||
|
{ key: 'password', headerName: 'Новый пароль', type: 'string', required: true, inputType: 'password' },
|
||||||
|
{ key: 'password_confirm', headerName: 'Подтверждение пароля', type: 'string', required: true, inputType: 'password', watch: 'password', watchMessage: 'Пароли не совпадают' },
|
||||||
|
]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{
|
<Box sx={{
|
||||||
@ -42,27 +45,28 @@ export default function Settings() {
|
|||||||
gap: "16px",
|
gap: "16px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DataGrid
|
{currentUser &&
|
||||||
autoHeight
|
<Stack spacing={2} width='100%'>
|
||||||
style={{ width: "100%" }}
|
<Stack width='100%'>
|
||||||
rows={users}
|
<FormFields
|
||||||
columns={columns}
|
fields={profileFields}
|
||||||
initialState={{
|
defaultValues={currentUser}
|
||||||
pagination: {
|
//submitHandler={RoleService.createRole}
|
||||||
paginationModel: { page: 0, pageSize: 10 },
|
title="Пользователь"
|
||||||
},
|
/>
|
||||||
}}
|
</Stack>
|
||||||
pageSizeOptions={[10, 20, 50, 100]}
|
<Stack width='100%'>
|
||||||
checkboxSelection
|
<FormFields
|
||||||
disableRowSelectionOnClick
|
fields={passwordFields}
|
||||||
|
defaultValues={currentUser}
|
||||||
|
watchValues={['password, password_confirm']}
|
||||||
|
//submitHandler={RoleService.createRole}
|
||||||
|
title="Смена пароля"
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
processRowUpdate={(updatedRow) => {
|
}
|
||||||
return updatedRow
|
|
||||||
}}
|
|
||||||
|
|
||||||
onProcessRowUpdateError={() => {
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
</Box>
|
||||||
)
|
)
|
||||||
}
|
}
|
@ -1,11 +1,11 @@
|
|||||||
import { Box, Button, CircularProgress } from "@mui/material"
|
import { Box, Button, CircularProgress, Modal } from "@mui/material"
|
||||||
import { DataGrid, GridColDef } from "@mui/x-data-grid"
|
import { DataGrid, GridColDef } from "@mui/x-data-grid"
|
||||||
import { useRoles, useUsers } from "../hooks/swrHooks"
|
import { useRoles, useUsers } from "../hooks/swrHooks"
|
||||||
import { IRole } from "../interfaces/role"
|
import { IRole } from "../interfaces/role"
|
||||||
import { useState } from "react"
|
import { useState } from "react"
|
||||||
import { CreateField } from "../interfaces/create"
|
import { CreateField } from "../interfaces/create"
|
||||||
import ModalCreate from "../components/modals/ModalCreate"
|
|
||||||
import UserService from "../services/UserService"
|
import UserService from "../services/UserService"
|
||||||
|
import FormFields from "../components/FormFields"
|
||||||
|
|
||||||
export default function Users() {
|
export default function Users() {
|
||||||
const { users, isError, isLoading } = useUsers()
|
const { users, isError, isLoading } = useUsers()
|
||||||
@ -56,13 +56,16 @@ export default function Users() {
|
|||||||
Добавить пользователя
|
Добавить пользователя
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<ModalCreate
|
<Modal
|
||||||
open={open}
|
open={open}
|
||||||
setOpen={setOpen}
|
onClose={() => setOpen(false)}
|
||||||
fields={createFields}
|
>
|
||||||
submitHandler={UserService.createUser}
|
<FormFields
|
||||||
title="Создание пользователя"
|
fields={createFields}
|
||||||
/>
|
submitHandler={UserService.createUser}
|
||||||
|
title="Создание пользователя"
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
<DataGrid
|
<DataGrid
|
||||||
autoHeight
|
autoHeight
|
||||||
|
92
frontend_reactjs/src/pages/auth/PasswordReset.tsx
Normal file
92
frontend_reactjs/src/pages/auth/PasswordReset.tsx
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
import { Box, Button, CircularProgress, Container, Fade, Grow, Stack, TextField, Typography } from '@mui/material'
|
||||||
|
import { useState } from 'react'
|
||||||
|
import { SubmitHandler, useForm } from 'react-hook-form';
|
||||||
|
import AuthService from '../../services/AuthService';
|
||||||
|
import { CheckCircle } from '@mui/icons-material';
|
||||||
|
|
||||||
|
interface PasswordResetProps {
|
||||||
|
email: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function PasswordReset() {
|
||||||
|
const [success, setSuccess] = useState(false)
|
||||||
|
|
||||||
|
const { register, handleSubmit, watch, setError, formState: { errors, isSubmitting } } = useForm<PasswordResetProps>({
|
||||||
|
defaultValues: {
|
||||||
|
email: ''
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const onSubmit: SubmitHandler<PasswordResetProps> = async (data) => {
|
||||||
|
await AuthService.resetPassword(data.email).then(response => {
|
||||||
|
if (response.status === 200) {
|
||||||
|
//setError('email', { message: response.data.msg })
|
||||||
|
setSuccess(true)
|
||||||
|
} else if (response.status === 422) {
|
||||||
|
setError('email', { message: response.statusText })
|
||||||
|
}
|
||||||
|
}).catch((error: Error) => {
|
||||||
|
setError('email', { message: error.message })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container maxWidth="sm">
|
||||||
|
<Box my={4}>
|
||||||
|
<Typography variant="h4" component="h1" gutterBottom>
|
||||||
|
Восстановление пароля
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<form onSubmit={handleSubmit(onSubmit)}>
|
||||||
|
{!success && <Fade in={!success}>
|
||||||
|
<Stack spacing={2}>
|
||||||
|
<Typography>
|
||||||
|
Введите адрес электронной почты, на который будут отправлены новые данные для авторизации:
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
margin="normal"
|
||||||
|
label="E-mail"
|
||||||
|
required
|
||||||
|
{...register('email', { required: 'Введите E-mail' })}
|
||||||
|
error={!!errors.email}
|
||||||
|
helperText={errors.email?.message}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Box sx={{ display: 'flex', gap: '16px' }}>
|
||||||
|
<Button fullWidth type="submit" disabled={isSubmitting || watch('email').length == 0} variant="contained" color="primary">
|
||||||
|
{isSubmitting ? <CircularProgress size={16} /> : 'Восстановить пароль'}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button fullWidth href="/auth/signin" type="button" variant="text" color="primary">
|
||||||
|
Назад
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
</Stack>
|
||||||
|
</Fade>}
|
||||||
|
{success &&
|
||||||
|
<Grow in={success}>
|
||||||
|
<Stack spacing={2}>
|
||||||
|
<Stack direction='row' alignItems='center' spacing={2}>
|
||||||
|
<CheckCircle color='success' />
|
||||||
|
<Typography>
|
||||||
|
На указанный адрес было отправлено письмо с новыми данными для авторизации.
|
||||||
|
</Typography>
|
||||||
|
</Stack>
|
||||||
|
<Box sx={{ display: 'flex', gap: '16px' }}>
|
||||||
|
<Button fullWidth href="/auth/signin" type="button" variant="contained" color="primary">
|
||||||
|
Войти
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Stack>
|
||||||
|
</Grow>
|
||||||
|
}
|
||||||
|
</form>
|
||||||
|
</Box>
|
||||||
|
</Container>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PasswordReset
|
@ -1,5 +1,5 @@
|
|||||||
import { useForm, SubmitHandler } from 'react-hook-form';
|
import { useForm, SubmitHandler } from 'react-hook-form';
|
||||||
import { TextField, Button, Container, Typography, Box } from '@mui/material';
|
import { TextField, Button, Container, Typography, Box, Stack, Link } from '@mui/material';
|
||||||
import { AxiosResponse } from 'axios';
|
import { AxiosResponse } from 'axios';
|
||||||
import { ApiResponse, LoginFormData } from '../../interfaces/auth';
|
import { ApiResponse, LoginFormData } from '../../interfaces/auth';
|
||||||
import { login, setUserData } from '../../store/auth';
|
import { login, setUserData } from '../../store/auth';
|
||||||
@ -50,32 +50,48 @@ const SignIn = () => {
|
|||||||
<Typography variant="h4" component="h1" gutterBottom>
|
<Typography variant="h4" component="h1" gutterBottom>
|
||||||
Вход
|
Вход
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<form onSubmit={handleSubmit(onSubmit)}>
|
<form onSubmit={handleSubmit(onSubmit)}>
|
||||||
<TextField
|
<Stack spacing={2}>
|
||||||
fullWidth
|
<TextField
|
||||||
margin="normal"
|
fullWidth
|
||||||
label="Логин"
|
margin="normal"
|
||||||
required
|
label="Логин"
|
||||||
{...register('username', { required: 'Введите логин' })}
|
required
|
||||||
error={!!errors.username}
|
{...register('username', { required: 'Введите логин' })}
|
||||||
helperText={errors.username?.message}
|
error={!!errors.username}
|
||||||
/>
|
helperText={errors.username?.message}
|
||||||
<TextField
|
/>
|
||||||
fullWidth
|
|
||||||
margin="normal"
|
<TextField
|
||||||
type="password"
|
fullWidth
|
||||||
label="Пароль"
|
margin="normal"
|
||||||
required
|
type="password"
|
||||||
{...register('password', { required: 'Введите пароль' })}
|
label="Пароль"
|
||||||
error={!!errors.password}
|
required
|
||||||
helperText={errors.password?.message}
|
{...register('password', { required: 'Введите пароль' })}
|
||||||
/>
|
error={!!errors.password}
|
||||||
<Button type="submit" variant="contained" color="primary">
|
helperText={errors.password?.message}
|
||||||
Вход
|
/>
|
||||||
</Button>
|
|
||||||
<Button href="/auth/signup" type="button" variant="text" color="primary">
|
<Box sx={{ display: 'flex', gap: '16px', justifyContent: 'flex-end' }}>
|
||||||
Регистрация
|
<Link href="/auth/password-reset" color="primary">
|
||||||
</Button>
|
Восстановить пароль
|
||||||
|
</Link>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box sx={{ display: 'flex', gap: '16px' }}>
|
||||||
|
<Button fullWidth type="submit" variant="contained" color="primary">
|
||||||
|
Вход
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button fullWidth href="/auth/signup" type="button" variant="text" color="primary">
|
||||||
|
Регистрация
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
</Box>
|
</Box>
|
||||||
</Container>
|
</Container>
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { useForm, SubmitHandler } from 'react-hook-form';
|
import { useForm, SubmitHandler } from 'react-hook-form';
|
||||||
import { TextField, Button, Container, Typography, Box } from '@mui/material';
|
import { TextField, Button, Container, Typography, Box } from '@mui/material';
|
||||||
import UserService from '../../services/UserService';
|
import UserService from '../../services/UserService';
|
||||||
import { IUserCreate } from '../../interfaces/user';
|
import { IUser } from '../../interfaces/user';
|
||||||
|
|
||||||
const SignUp = () => {
|
const SignUp = () => {
|
||||||
const { register, handleSubmit, formState: { errors } } = useForm<IUserCreate>({
|
const { register, handleSubmit, formState: { errors } } = useForm<IUser>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
email: '',
|
email: '',
|
||||||
login: '',
|
login: '',
|
||||||
@ -17,7 +17,7 @@ const SignUp = () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
const onSubmit: SubmitHandler<IUserCreate> = async (data) => {
|
const onSubmit: SubmitHandler<IUser> = async (data) => {
|
||||||
try {
|
try {
|
||||||
await UserService.createUser(data)
|
await UserService.createUser(data)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { AxiosRequestConfig } from "axios";
|
import { AxiosRequestConfig } from "axios";
|
||||||
import { BASE_URL } from "../constants";
|
import { BASE_URL } from "../constants";
|
||||||
import axiosInstance from "../http/axiosInstance";
|
import axiosInstance from "../http/axiosInstance";
|
||||||
|
import { IUser } from "../interfaces/user";
|
||||||
|
|
||||||
const config: AxiosRequestConfig = {
|
const config: AxiosRequestConfig = {
|
||||||
baseURL: BASE_URL.auth,
|
baseURL: BASE_URL.auth,
|
||||||
@ -21,4 +22,12 @@ export default class AuthService {
|
|||||||
static async getCurrentUser(token: string) {
|
static async getCurrentUser(token: string) {
|
||||||
return await axiosInstance.get(`/auth/get_current_user/${token}`, config)
|
return await axiosInstance.get(`/auth/get_current_user/${token}`, config)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async resetPassword(email: string) {
|
||||||
|
return await axiosInstance.put(`/auth/user/reset_password?email=${email}`, null, config)
|
||||||
|
}
|
||||||
|
|
||||||
|
static async updatePassword(data: IUser) {
|
||||||
|
return await axiosInstance.put(`/auth/user/password_change`, data, config)
|
||||||
|
}
|
||||||
}
|
}
|
@ -2,14 +2,14 @@ import { AxiosRequestConfig } from "axios";
|
|||||||
import axiosInstance from "../http/axiosInstance";
|
import axiosInstance from "../http/axiosInstance";
|
||||||
import { UserCreds, UserData } from "../interfaces/auth";
|
import { UserCreds, UserData } from "../interfaces/auth";
|
||||||
import { BASE_URL } from "../constants";
|
import { BASE_URL } from "../constants";
|
||||||
import { IUserCreate } from "../interfaces/user";
|
import { IUser } from "../interfaces/user";
|
||||||
|
|
||||||
const config: AxiosRequestConfig = {
|
const config: AxiosRequestConfig = {
|
||||||
baseURL: BASE_URL.auth
|
baseURL: BASE_URL.auth
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class UserService {
|
export default class UserService {
|
||||||
static async createUser(data: IUserCreate) {
|
static async createUser(data: IUser) {
|
||||||
return await axiosInstance.post(`/auth/user`, data, config)
|
return await axiosInstance.post(`/auth/user`, data, config)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user