diff --git a/frontend_reactjs/src/App.tsx b/frontend_reactjs/src/App.tsx index 434d9e4..944173b 100644 --- a/frontend_reactjs/src/App.tsx +++ b/frontend_reactjs/src/App.tsx @@ -15,7 +15,9 @@ import Documents from "./pages/Documents" import Reports from "./pages/Reports" import Boilers from "./pages/Boilers" 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 = [ @@ -35,6 +37,22 @@ export const pages = [ drawer: false, dashboard: false, }, + { + label: "", + path: "/auth/password-reset", + icon: , + component: , + drawer: false, + dashboard: false, + }, + { + label: "Настройки", + path: "/settings", + icon: , + component: , + drawer: false, + dashboard: true, + }, { label: "Главная", path: "/", diff --git a/frontend_reactjs/src/components/AccountMenu.tsx b/frontend_reactjs/src/components/AccountMenu.tsx index f3e1ef4..a8eb86c 100644 --- a/frontend_reactjs/src/components/AccountMenu.tsx +++ b/frontend_reactjs/src/components/AccountMenu.tsx @@ -130,7 +130,9 @@ export default function AccountMenu() { - + { + navigate('/settings') + }}> diff --git a/frontend_reactjs/src/components/FormFields.tsx b/frontend_reactjs/src/components/FormFields.tsx new file mode 100644 index 0000000..802f627 --- /dev/null +++ b/frontend_reactjs/src/components/FormFields.tsx @@ -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 = async (data) => { + try { + await submitHandler?.(data).then(() => { + mutateHandler?.() + }) + } catch (error) { + console.error(error) + } + } + + return ( +
+ + + {title} + + + {fields.map((field: CreateField) => { + return ( + { + if (field.watch) { + if (watch(field.watch) != val) { + return field.watchMessage || '' + } + } + } + })} + error={!!errors[field.key]} + /> + ) + })} + + + + + +
+ ) +} + +export default FormFields \ No newline at end of file diff --git a/frontend_reactjs/src/components/modals/ModalCreate.tsx b/frontend_reactjs/src/components/modals/ModalCreate.tsx deleted file mode 100644 index 31b2cf2..0000000 --- a/frontend_reactjs/src/components/modals/ModalCreate.tsx +++ /dev/null @@ -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 = async (data) => { - try { - await submitHandler?.(data).then(() => { - mutateHandler?.() - }) - } catch (error) { - console.error(error) - } - } - - return ( - setOpen(false)} - aria-labelledby="modal-modal-title" - aria-describedby="modal-modal-description" - > -
- - - {title} - - - {fields.map((field: CreateField) => { - return ( - - ) - })} - - - - - - - -
-
- ) -} - -export default ModalCreate \ No newline at end of file diff --git a/frontend_reactjs/src/interfaces/create.ts b/frontend_reactjs/src/interfaces/create.ts index 9fa2d04..382bdcd 100644 --- a/frontend_reactjs/src/interfaces/create.ts +++ b/frontend_reactjs/src/interfaces/create.ts @@ -1,3 +1,5 @@ +import { Validate } from "react-hook-form"; + export interface CreateFieldTypes { string: 'string'; number: 'number'; @@ -9,7 +11,12 @@ export interface CreateFieldTypes { custom: 'custom'; } +export interface InputTypes { + password: 'password'; +} + export type CreateFieldType = CreateFieldTypes[keyof CreateFieldTypes] +export type InputType = InputTypes[keyof InputTypes] export interface CreateField { key: string; @@ -17,4 +24,8 @@ export interface CreateField { type: CreateFieldType; required?: boolean; defaultValue?: any; + inputType?: InputType; + validate?: Validate; + watch?: string; + watchMessage?: string; } \ No newline at end of file diff --git a/frontend_reactjs/src/interfaces/user.ts b/frontend_reactjs/src/interfaces/user.ts index 8c96b86..3fcaeb3 100644 --- a/frontend_reactjs/src/interfaces/user.ts +++ b/frontend_reactjs/src/interfaces/user.ts @@ -1,9 +1,10 @@ -export interface IUserCreate { +export interface IUser { + id: number; + password: string; email: string; login: string; phone: string; name: string; surname: string; is_active: boolean; - password: string; } \ No newline at end of file diff --git a/frontend_reactjs/src/layouts/MainLayout.tsx b/frontend_reactjs/src/layouts/MainLayout.tsx index 96845e3..e0711cc 100644 --- a/frontend_reactjs/src/layouts/MainLayout.tsx +++ b/frontend_reactjs/src/layouts/MainLayout.tsx @@ -1,9 +1,31 @@ // Layout for fullscreen pages +import { Box, createTheme, ThemeProvider, useTheme } from "@mui/material"; import { Outlet } from "react-router-dom"; export default function MainLayout() { + const theme = useTheme() + const innerTheme = createTheme(theme) + return ( - + + 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', + }} + > + + + ) } \ No newline at end of file diff --git a/frontend_reactjs/src/pages/Roles.tsx b/frontend_reactjs/src/pages/Roles.tsx index a5cc2fc..0b2a583 100644 --- a/frontend_reactjs/src/pages/Roles.tsx +++ b/frontend_reactjs/src/pages/Roles.tsx @@ -1,10 +1,10 @@ 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 { useRoles } from '../hooks/swrHooks' import { CreateField } from '../interfaces/create' -import ModalCreate from '../components/modals/ModalCreate' import RoleService from '../services/RoleService' +import FormFields from '../components/FormFields' export default function Roles() { const { roles, isError, isLoading } = useRoles() @@ -37,13 +37,16 @@ export default function Roles() { Добавить роль - + onClose={() => setOpen(false)} + > + + () - const { roles } = useRoles() + const fetchCurrentUser = async () => { + if (token) { + await UserService.getCurrentUser(token).then(response => { + setCurrentUser(response.data) + }) + } + } - const columns: GridColDef[] = [ - { field: 'id', headerName: 'ID', type: "number", width: 70 }, - { field: 'email', headerName: 'Email', width: 130, editable: true }, - { field: 'login', headerName: 'Логин', width: 130, editable: true }, - { field: 'phone', headerName: 'Телефон', width: 90, editable: true }, - { 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 - }, - ]; + useEffect(() => { + if (token) { + fetchCurrentUser() + } + }, [token]) - if (isError) return
Произошла ошибка при получении данных.
- if (isLoading) return + const profileFields: CreateField[] = [ + { 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 ( - + + + + + + + - processRowUpdate={(updatedRow) => { - return updatedRow - }} - - onProcessRowUpdateError={() => { - }} - /> + } ) } \ No newline at end of file diff --git a/frontend_reactjs/src/pages/Users.tsx b/frontend_reactjs/src/pages/Users.tsx index 4fee583..5d8262b 100644 --- a/frontend_reactjs/src/pages/Users.tsx +++ b/frontend_reactjs/src/pages/Users.tsx @@ -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 { useRoles, useUsers } from "../hooks/swrHooks" import { IRole } from "../interfaces/role" import { useState } from "react" import { CreateField } from "../interfaces/create" -import ModalCreate from "../components/modals/ModalCreate" import UserService from "../services/UserService" +import FormFields from "../components/FormFields" export default function Users() { const { users, isError, isLoading } = useUsers() @@ -56,13 +56,16 @@ export default function Users() { Добавить пользователя - + onClose={() => setOpen(false)} + > + + ({ + defaultValues: { + email: '' + } + }) + + const onSubmit: SubmitHandler = 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 ( + + + + Восстановление пароля + + +
+ {!success && + + + Введите адрес электронной почты, на который будут отправлены новые данные для авторизации: + + + + + + + + + + + + } + {success && + + + + + + На указанный адрес было отправлено письмо с новыми данными для авторизации. + + + + + + + + } +
+
+
+ ) +} + +export default PasswordReset \ No newline at end of file diff --git a/frontend_reactjs/src/pages/auth/SignIn.tsx b/frontend_reactjs/src/pages/auth/SignIn.tsx index d8c74d2..1ea53ea 100644 --- a/frontend_reactjs/src/pages/auth/SignIn.tsx +++ b/frontend_reactjs/src/pages/auth/SignIn.tsx @@ -1,5 +1,5 @@ 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 { ApiResponse, LoginFormData } from '../../interfaces/auth'; import { login, setUserData } from '../../store/auth'; @@ -50,32 +50,48 @@ const SignIn = () => { Вход +
- - - - + + + + + + + + Восстановить пароль + + + + + + + + + + + diff --git a/frontend_reactjs/src/pages/auth/SignUp.tsx b/frontend_reactjs/src/pages/auth/SignUp.tsx index 3c5c7d9..8813dc5 100644 --- a/frontend_reactjs/src/pages/auth/SignUp.tsx +++ b/frontend_reactjs/src/pages/auth/SignUp.tsx @@ -1,10 +1,10 @@ import { useForm, SubmitHandler } from 'react-hook-form'; import { TextField, Button, Container, Typography, Box } from '@mui/material'; import UserService from '../../services/UserService'; -import { IUserCreate } from '../../interfaces/user'; +import { IUser } from '../../interfaces/user'; const SignUp = () => { - const { register, handleSubmit, formState: { errors } } = useForm({ + const { register, handleSubmit, formState: { errors } } = useForm({ defaultValues: { email: '', login: '', @@ -17,7 +17,7 @@ const SignUp = () => { }) - const onSubmit: SubmitHandler = async (data) => { + const onSubmit: SubmitHandler = async (data) => { try { await UserService.createUser(data) } catch (error) { diff --git a/frontend_reactjs/src/services/AuthService.ts b/frontend_reactjs/src/services/AuthService.ts index 49509ce..56d9c61 100644 --- a/frontend_reactjs/src/services/AuthService.ts +++ b/frontend_reactjs/src/services/AuthService.ts @@ -1,6 +1,7 @@ import { AxiosRequestConfig } from "axios"; import { BASE_URL } from "../constants"; import axiosInstance from "../http/axiosInstance"; +import { IUser } from "../interfaces/user"; const config: AxiosRequestConfig = { baseURL: BASE_URL.auth, @@ -21,4 +22,12 @@ export default class AuthService { static async getCurrentUser(token: string) { 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) + } } \ No newline at end of file diff --git a/frontend_reactjs/src/services/UserService.ts b/frontend_reactjs/src/services/UserService.ts index 83b62f8..e88c922 100644 --- a/frontend_reactjs/src/services/UserService.ts +++ b/frontend_reactjs/src/services/UserService.ts @@ -2,14 +2,14 @@ import { AxiosRequestConfig } from "axios"; import axiosInstance from "../http/axiosInstance"; import { UserCreds, UserData } from "../interfaces/auth"; import { BASE_URL } from "../constants"; -import { IUserCreate } from "../interfaces/user"; +import { IUser } from "../interfaces/user"; const config: AxiosRequestConfig = { baseURL: BASE_URL.auth } export default class UserService { - static async createUser(data: IUserCreate) { + static async createUser(data: IUser) { return await axiosInstance.post(`/auth/user`, data, config) }