сделал создание пользователя
This commit is contained in:
parent
1ed36acfe2
commit
12c448aef9
@ -10,10 +10,15 @@ class RolesRepository:
|
|||||||
def __init__(self, db: AsyncSession) -> None:
|
def __init__(self, db: AsyncSession) -> None:
|
||||||
self.db = db
|
self.db = db
|
||||||
|
|
||||||
|
async def get_all(self) -> list[Role]:
|
||||||
|
query = select(Role)
|
||||||
|
result = await self.db.execute(query)
|
||||||
|
return result.scalars().all()
|
||||||
|
|
||||||
async def get_by_id(self, role_id: int) -> Optional[Role]:
|
async def get_by_id(self, role_id: int) -> Optional[Role]:
|
||||||
query = (
|
query = (
|
||||||
select(Role)
|
select(Role)
|
||||||
.filter_by(role_id=role_id)
|
.filter_by(id=role_id)
|
||||||
)
|
)
|
||||||
result = await self.db.execute(query)
|
result = await self.db.execute(query)
|
||||||
return result.scalars().first()
|
return result.scalars().first()
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
from typing import Optional
|
from typing import Optional, List
|
||||||
|
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
@ -10,6 +10,11 @@ class UsersRepository:
|
|||||||
def __init__(self, db: AsyncSession) -> None:
|
def __init__(self, db: AsyncSession) -> None:
|
||||||
self.db = db
|
self.db = db
|
||||||
|
|
||||||
|
async def get_all(self) -> List[User]:
|
||||||
|
query = select(User)
|
||||||
|
result = await self.db.execute(query)
|
||||||
|
return result.scalars().all()
|
||||||
|
|
||||||
async def get_by_id(self, user_id: int) -> Optional[User]:
|
async def get_by_id(self, user_id: int) -> Optional[User]:
|
||||||
query = (
|
query = (
|
||||||
select(User)
|
select(User)
|
||||||
|
|||||||
26
api/app/controllers/roles_router.py
Normal file
26
api/app/controllers/roles_router.py
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
from typing import List, Optional
|
||||||
|
|
||||||
|
from fastapi import APIRouter, Depends, Response
|
||||||
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
|
from app.database.session import get_db
|
||||||
|
from app.domain.entities.roles import RoleRead
|
||||||
|
from app.domain.models import User
|
||||||
|
from app.infrastructure.dependencies import require_admin
|
||||||
|
from app.infrastructure.roles_service import RolesService
|
||||||
|
|
||||||
|
roles_router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
|
@roles_router.get(
|
||||||
|
'/',
|
||||||
|
response_model=List[RoleRead],
|
||||||
|
summary='Returns all roles',
|
||||||
|
description='Returns all roles',
|
||||||
|
)
|
||||||
|
async def get_all_roles(
|
||||||
|
db: AsyncSession = Depends(get_db),
|
||||||
|
user: User = Depends(require_admin),
|
||||||
|
):
|
||||||
|
roles_service = RolesService(db)
|
||||||
|
return await roles_service.get_all()
|
||||||
@ -4,14 +4,29 @@ from fastapi import APIRouter, Depends, Response
|
|||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from app.database.session import get_db
|
from app.database.session import get_db
|
||||||
from app.domain.entities.users import UserRead, UserUpdate, PasswordChangeRequest
|
from app.domain.entities.users import UserRead, UserUpdate, PasswordChangeRequest, UserCreate
|
||||||
from app.domain.models import User
|
from app.domain.models import User
|
||||||
from app.infrastructure.dependencies import require_auth_user
|
from app.infrastructure.dependencies import require_auth_user, require_admin
|
||||||
|
from app.infrastructure.register_service import RegisterService
|
||||||
from app.infrastructure.users_service import UsersService
|
from app.infrastructure.users_service import UsersService
|
||||||
|
|
||||||
users_router = APIRouter()
|
users_router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
|
@users_router.get(
|
||||||
|
'/',
|
||||||
|
response_model=List[UserRead],
|
||||||
|
summary='Return all users',
|
||||||
|
description='Return all users',
|
||||||
|
)
|
||||||
|
async def get_all_users(
|
||||||
|
db: AsyncSession = Depends(get_db),
|
||||||
|
user: User = Depends(require_admin)
|
||||||
|
):
|
||||||
|
users_service = UsersService(db)
|
||||||
|
return await users_service.get_all()
|
||||||
|
|
||||||
|
|
||||||
@users_router.get(
|
@users_router.get(
|
||||||
'/me/',
|
'/me/',
|
||||||
response_model=Optional[UserRead],
|
response_model=Optional[UserRead],
|
||||||
@ -25,6 +40,7 @@ async def get_authenticated_user_data(
|
|||||||
users_service = UsersService(db)
|
users_service = UsersService(db)
|
||||||
return await users_service.get_by_id(user.id)
|
return await users_service.get_by_id(user.id)
|
||||||
|
|
||||||
|
|
||||||
@users_router.put(
|
@users_router.put(
|
||||||
'/{user_id}/',
|
'/{user_id}/',
|
||||||
response_model=Optional[UserRead],
|
response_model=Optional[UserRead],
|
||||||
@ -40,6 +56,7 @@ async def update_user(
|
|||||||
users_service = UsersService(db)
|
users_service = UsersService(db)
|
||||||
return await users_service.update(user_id, user_data, user)
|
return await users_service.update(user_id, user_data, user)
|
||||||
|
|
||||||
|
|
||||||
@users_router.post(
|
@users_router.post(
|
||||||
'/change-password/{user_id}/',
|
'/change-password/{user_id}/',
|
||||||
response_model=Optional[UserRead],
|
response_model=Optional[UserRead],
|
||||||
@ -54,3 +71,17 @@ async def change_password(
|
|||||||
):
|
):
|
||||||
users_service = UsersService(db)
|
users_service = UsersService(db)
|
||||||
return await users_service.change_password(user_id, password_data, user)
|
return await users_service.change_password(user_id, password_data, user)
|
||||||
|
|
||||||
|
@users_router.post(
|
||||||
|
'/create/',
|
||||||
|
response_model=Optional[UserRead],
|
||||||
|
summary='Creates a new user',
|
||||||
|
description='Creates a new user',
|
||||||
|
)
|
||||||
|
async def create_user(
|
||||||
|
user: UserCreate,
|
||||||
|
db: AsyncSession = Depends(get_db),
|
||||||
|
current_user: User = Depends(require_admin)
|
||||||
|
):
|
||||||
|
register_service = RegisterService(db)
|
||||||
|
return await register_service.create_user(user)
|
||||||
|
|||||||
@ -18,7 +18,15 @@ class UserRegister(BaseModel):
|
|||||||
repeat_password: str = Field(min_length=8)
|
repeat_password: str = Field(min_length=8)
|
||||||
|
|
||||||
|
|
||||||
class UserCreate(UserRegister):
|
class UserCreate(BaseModel):
|
||||||
|
first_name: str = Field(max_length=250)
|
||||||
|
last_name: str = Field(max_length=250)
|
||||||
|
patronymic: Optional[str] = Field(default=None, max_length=250)
|
||||||
|
login: str = Field(max_length=250)
|
||||||
|
email: Optional[EmailStr] = None
|
||||||
|
birthdate: date
|
||||||
|
password: str = Field(min_length=8)
|
||||||
|
repeat_password: str = Field(min_length=8)
|
||||||
role_id: int = Field()
|
role_id: int = Field()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
20
api/app/infrastructure/roles_service.py
Normal file
20
api/app/infrastructure/roles_service.py
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
from typing import List
|
||||||
|
|
||||||
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
|
from app.application.roles_repository import RolesRepository
|
||||||
|
from app.domain.entities.roles import RoleRead
|
||||||
|
|
||||||
|
|
||||||
|
class RolesService:
|
||||||
|
def __init__(self, db: AsyncSession):
|
||||||
|
self.roles_repository = RolesRepository(db)
|
||||||
|
|
||||||
|
async def get_all(self) -> List[RoleRead]:
|
||||||
|
roles = await self.roles_repository.get_all()
|
||||||
|
response = []
|
||||||
|
|
||||||
|
for role in roles:
|
||||||
|
response.append(RoleRead.model_validate(role))
|
||||||
|
|
||||||
|
return response
|
||||||
@ -1,4 +1,4 @@
|
|||||||
from typing import Optional
|
from typing import Optional, List
|
||||||
|
|
||||||
from fastapi import HTTPException, status
|
from fastapi import HTTPException, status
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
@ -15,6 +15,15 @@ class UsersService:
|
|||||||
self.users_repository = UsersRepository(db)
|
self.users_repository = UsersRepository(db)
|
||||||
self.settings = Settings()
|
self.settings = Settings()
|
||||||
|
|
||||||
|
async def get_all(self) -> List[UserRead]:
|
||||||
|
users = await self.users_repository.get_all()
|
||||||
|
response = []
|
||||||
|
|
||||||
|
for user in users:
|
||||||
|
response.append(UserRead.model_validate(user))
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
async def get_by_id(self, user_id: int) -> UserRead:
|
async def get_by_id(self, user_id: int) -> UserRead:
|
||||||
user = await self.users_repository.get_by_id(user_id)
|
user = await self.users_repository.get_by_id(user_id)
|
||||||
if not user:
|
if not user:
|
||||||
|
|||||||
@ -3,6 +3,7 @@ from starlette.middleware.cors import CORSMiddleware
|
|||||||
|
|
||||||
from app.controllers.auth_router import auth_router
|
from app.controllers.auth_router import auth_router
|
||||||
from app.controllers.register_router import register_router
|
from app.controllers.register_router import register_router
|
||||||
|
from app.controllers.roles_router import roles_router
|
||||||
from app.controllers.users_router import users_router
|
from app.controllers.users_router import users_router
|
||||||
from app.settings import Settings
|
from app.settings import Settings
|
||||||
|
|
||||||
@ -21,6 +22,7 @@ def start_app():
|
|||||||
|
|
||||||
api_app.include_router(auth_router, prefix=f'{settings.prefix}/auth', tags=['auth'])
|
api_app.include_router(auth_router, prefix=f'{settings.prefix}/auth', tags=['auth'])
|
||||||
api_app.include_router(register_router, prefix=f'{settings.prefix}/register', tags=['register'])
|
api_app.include_router(register_router, prefix=f'{settings.prefix}/register', tags=['register'])
|
||||||
|
api_app.include_router(roles_router, prefix=f'{settings.prefix}/roles', tags=['roles'])
|
||||||
api_app.include_router(users_router, prefix=f'{settings.prefix}/users', tags=['users'])
|
api_app.include_router(users_router, prefix=f'{settings.prefix}/users', tags=['users'])
|
||||||
|
|
||||||
return api_app
|
return api_app
|
||||||
|
|||||||
20
web/src/Api/rolesApi.js
Normal file
20
web/src/Api/rolesApi.js
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import {baseQueryWithAuth} from "./baseQuery.js";
|
||||||
|
import {createApi} from "@reduxjs/toolkit/query/react";
|
||||||
|
|
||||||
|
|
||||||
|
export const rolesApi = createApi({
|
||||||
|
reducerPath: "rolesApi",
|
||||||
|
baseQuery: baseQueryWithAuth,
|
||||||
|
tagTypes: ["role"],
|
||||||
|
endpoints: (builder) => ({
|
||||||
|
getAllRoles: builder.query({
|
||||||
|
query: () => ({
|
||||||
|
url: "/roles/",
|
||||||
|
method: "GET",
|
||||||
|
}),
|
||||||
|
providesTags: ["role"],
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const {useGetAllRolesQuery} = rolesApi;
|
||||||
@ -7,6 +7,10 @@ export const usersApi = createApi({
|
|||||||
baseQuery: baseQueryWithAuth,
|
baseQuery: baseQueryWithAuth,
|
||||||
tagTypes: ["user"],
|
tagTypes: ["user"],
|
||||||
endpoints: (builder) => ({
|
endpoints: (builder) => ({
|
||||||
|
getAllUsers: builder.query({
|
||||||
|
query: () => "/users/",
|
||||||
|
providesTags: ["user"],
|
||||||
|
}),
|
||||||
getAuthenticatedUserData: builder.query({
|
getAuthenticatedUserData: builder.query({
|
||||||
query: () => "/users/me/",
|
query: () => "/users/me/",
|
||||||
}),
|
}),
|
||||||
@ -26,11 +30,21 @@ export const usersApi = createApi({
|
|||||||
}),
|
}),
|
||||||
invalidatesTags: ["user"],
|
invalidatesTags: ["user"],
|
||||||
}),
|
}),
|
||||||
|
createUser: builder.mutation({
|
||||||
|
query: (data) => ({
|
||||||
|
url: "/users/create/",
|
||||||
|
method: "POST",
|
||||||
|
body: data,
|
||||||
|
}),
|
||||||
|
invalidatesTags: ["user"],
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const {
|
export const {
|
||||||
|
useGetAllUsersQuery,
|
||||||
useGetAuthenticatedUserDataQuery,
|
useGetAuthenticatedUserDataQuery,
|
||||||
useUpdateUserMutation,
|
useUpdateUserMutation,
|
||||||
useUpdateUserPasswordMutation,
|
useUpdateUserPasswordMutation,
|
||||||
|
useCreateUserMutation,
|
||||||
} = usersApi;
|
} = usersApi;
|
||||||
@ -2,6 +2,7 @@ import {Navigate, Outlet} from "react-router-dom";
|
|||||||
import {useGetAuthenticatedUserDataQuery} from "../Api/usersApi.js";
|
import {useGetAuthenticatedUserDataQuery} from "../Api/usersApi.js";
|
||||||
import LoadingIndicator from "../Components/Widgets/LoadingIndicator/LoadingIndicator.jsx";
|
import LoadingIndicator from "../Components/Widgets/LoadingIndicator/LoadingIndicator.jsx";
|
||||||
import {Result} from "antd";
|
import {Result} from "antd";
|
||||||
|
import CONFIG from "../Core/сonfig.js";
|
||||||
|
|
||||||
const AdminRoute = () => {
|
const AdminRoute = () => {
|
||||||
const {
|
const {
|
||||||
@ -24,7 +25,7 @@ const AdminRoute = () => {
|
|||||||
return <Navigate to="/login"/>;
|
return <Navigate to="/login"/>;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!user.role || user.role.title !== "root") {
|
if (!user.role || user.role.title !== CONFIG.ROOT_ROLE_NAME) {
|
||||||
return <Navigate to="/"/>;
|
return <Navigate to="/"/>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import LoginPage from "../Components/Pages/LoginPage/LoginPage.jsx";
|
|||||||
import CoursesPage from "../Components/Pages/Courses/CoursesPage.jsx";
|
import CoursesPage from "../Components/Pages/Courses/CoursesPage.jsx";
|
||||||
import MainLayout from "../Components/Layouts/MainLayout.jsx";
|
import MainLayout from "../Components/Layouts/MainLayout.jsx";
|
||||||
import ProfilePage from "../Components/Pages/ProfilePage/ProfilePage.jsx";
|
import ProfilePage from "../Components/Pages/ProfilePage/ProfilePage.jsx";
|
||||||
|
import AdminPage from "../Components/Pages/AdminPage/AdminPage.jsx";
|
||||||
|
|
||||||
|
|
||||||
const AppRouter = () => (
|
const AppRouter = () => (
|
||||||
@ -20,9 +21,9 @@ const AppRouter = () => (
|
|||||||
</Route>
|
</Route>
|
||||||
|
|
||||||
<Route element={<AdminRoute/>}>
|
<Route element={<AdminRoute/>}>
|
||||||
{/*<Route element={<MainLayout />}>*/}
|
<Route element={<MainLayout />}>
|
||||||
{/* <Route path="/admin" element={<AdminPage />} />*/}
|
<Route path="/admin" element={<AdminPage />} />
|
||||||
{/*</Route>*/}
|
</Route>
|
||||||
</Route>
|
</Route>
|
||||||
|
|
||||||
<Route path={"*"} element={<Navigate to={"/"}/>}/>
|
<Route path={"*"} element={<Navigate to={"/"}/>}/>
|
||||||
|
|||||||
@ -3,7 +3,8 @@ import {Layout, Menu} from "antd";
|
|||||||
import CoursesPage from "../Pages/Courses/CoursesPage.jsx";
|
import CoursesPage from "../Pages/Courses/CoursesPage.jsx";
|
||||||
import LoadingIndicator from "../Widgets/LoadingIndicator/LoadingIndicator.jsx";
|
import LoadingIndicator from "../Widgets/LoadingIndicator/LoadingIndicator.jsx";
|
||||||
import {Outlet} from "react-router-dom";
|
import {Outlet} from "react-router-dom";
|
||||||
import {BookOutlined, LogoutOutlined, UserOutlined} from "@ant-design/icons";
|
import {BookOutlined, ControlOutlined, LogoutOutlined, UserOutlined} from "@ant-design/icons";
|
||||||
|
import CONFIG from "../../Core/сonfig.js";
|
||||||
|
|
||||||
const {Content, Footer, Sider} = Layout;
|
const {Content, Footer, Sider} = Layout;
|
||||||
|
|
||||||
@ -22,11 +23,20 @@ const MainLayout = () => {
|
|||||||
|
|
||||||
const menuItems = [
|
const menuItems = [
|
||||||
getItem("Мои курсы", "/courses", <BookOutlined/>),
|
getItem("Мои курсы", "/courses", <BookOutlined/>),
|
||||||
getItem("Профиль", "/profile", <UserOutlined/>),
|
|
||||||
getItem("Выйти", "logout", <LogoutOutlined/>),
|
|
||||||
{type: "divider"}
|
{type: "divider"}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
if (user.role.title === CONFIG.ROOT_ROLE_NAME) {
|
||||||
|
menuItems.push(
|
||||||
|
getItem("Панель администратора", "/admin", <ControlOutlined/>)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
menuItems.push(
|
||||||
|
getItem("Профиль", "/profile", <UserOutlined/>),
|
||||||
|
getItem("Выйти", "logout", <LogoutOutlined/>),
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout style={{minHeight: "100vh", margin: "-0.4vw"}}>
|
<Layout style={{minHeight: "100vh", margin: "-0.4vw"}}>
|
||||||
<Sider
|
<Sider
|
||||||
|
|||||||
104
web/src/Components/Pages/AdminPage/AdminPage.jsx
Normal file
104
web/src/Components/Pages/AdminPage/AdminPage.jsx
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
import {Button, Col, Flex, FloatButton, Input, Result, Row, Table, Tooltip, Typography} from "antd";
|
||||||
|
import {ControlOutlined, PlusOutlined} from "@ant-design/icons";
|
||||||
|
import useAdminPage from "./useAdminPage.js";
|
||||||
|
import LoadingIndicator from "../../Widgets/LoadingIndicator/LoadingIndicator.jsx";
|
||||||
|
import CreateUserModalForm from "./CreateUserModalForm/CreateUserModalForm.jsx";
|
||||||
|
|
||||||
|
const {Title} = Typography;
|
||||||
|
|
||||||
|
const AdminPage = () => {
|
||||||
|
const {
|
||||||
|
handleSelectUserToEdit,
|
||||||
|
rolesData,
|
||||||
|
filteredUsers,
|
||||||
|
handleSearch,
|
||||||
|
isLoading,
|
||||||
|
isError,
|
||||||
|
openCreateModal,
|
||||||
|
} = useAdminPage();
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: "ID",
|
||||||
|
dataIndex: "id",
|
||||||
|
key: "id",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Фамилия",
|
||||||
|
dataIndex: "last_name",
|
||||||
|
key: "lastName",
|
||||||
|
sorter: (a, b) => a.last_name.localeCompare(b.last_name),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Имя",
|
||||||
|
dataIndex: "first_name",
|
||||||
|
key: "firstName",
|
||||||
|
sorter: (a, b) => a.first_name.localeCompare(b.first_name),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Отчество",
|
||||||
|
dataIndex: "patronymic",
|
||||||
|
key: "patronymic",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Роль",
|
||||||
|
dataIndex: ["role", "title"],
|
||||||
|
key: "role",
|
||||||
|
filters: rolesData.map(role => ({text: role.title, value: role.title})),
|
||||||
|
onFilter: (value, record) => record.role.title === value,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Статус",
|
||||||
|
dataIndex: "status",
|
||||||
|
key: "status",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Действия",
|
||||||
|
key: "actions",
|
||||||
|
render: (_, record) => (
|
||||||
|
<Button type="link" onClick={() => handleSelectUserToEdit(record)}>
|
||||||
|
Редактировать
|
||||||
|
</Button>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
if (isError) {
|
||||||
|
return <Result status="500" title="500" subTitle="Произошла ошибка при загрузке данных"/>;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return <LoadingIndicator/>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Row>
|
||||||
|
<Col style={{width: '100%'}}>
|
||||||
|
<Title level={1}>
|
||||||
|
<ControlOutlined/> Панель администратора
|
||||||
|
</Title>
|
||||||
|
<Flex vertical>
|
||||||
|
<Input
|
||||||
|
placeholder="Введите фамилию, имя или отчество"
|
||||||
|
style={{marginBottom: 12, width: "100%"}}
|
||||||
|
allowClear
|
||||||
|
onChange={handleSearch}
|
||||||
|
/>
|
||||||
|
<Table
|
||||||
|
columns={columns}
|
||||||
|
dataSource={filteredUsers}
|
||||||
|
rowKey="id"
|
||||||
|
pagination={{pageSize: 10}}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
<Tooltip title="Добавить пользователя">
|
||||||
|
<FloatButton onClick={openCreateModal} icon={<PlusOutlined/>} type={"primary"}/>
|
||||||
|
</Tooltip>
|
||||||
|
</Col>
|
||||||
|
|
||||||
|
<CreateUserModalForm/>
|
||||||
|
</Row>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AdminPage;
|
||||||
@ -0,0 +1,108 @@
|
|||||||
|
import {Button, DatePicker, Form, Input, Modal, Select, Tooltip, Typography} from "antd";
|
||||||
|
import useCreateUserModalForm from "./useCreateUserModalForm.js";
|
||||||
|
import {CalendarOutlined, InfoCircleOutlined} from "@ant-design/icons";
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
|
|
||||||
|
const CreateUserModalForm = () => {
|
||||||
|
const {
|
||||||
|
modalVisible,
|
||||||
|
handleCancel,
|
||||||
|
handleFinish,
|
||||||
|
form,
|
||||||
|
roles,
|
||||||
|
isLoading,
|
||||||
|
isError,
|
||||||
|
} = useCreateUserModalForm();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
title="Создать пользователя"
|
||||||
|
open={modalVisible}
|
||||||
|
onCancel={handleCancel}
|
||||||
|
footer={null}
|
||||||
|
>
|
||||||
|
<Form
|
||||||
|
form={form}
|
||||||
|
onFinish={handleFinish}
|
||||||
|
layout="vertical"
|
||||||
|
>
|
||||||
|
<Form.Item label="Фамилия" name="last_name" rules={[{required: true, message: "Введите фамилию"}]}>
|
||||||
|
<Input/>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label="Имя" name="first_name" rules={[{required: true, message: "Введите имя"}]}>
|
||||||
|
<Input/>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label="Отчество" name="patronymic">
|
||||||
|
<Input/>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label="Логин" name="login" rules={[{required: true, message: "Введите логин"}]}>
|
||||||
|
<Input/>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
name="email"
|
||||||
|
label="Email"
|
||||||
|
rules={[{required: true, message: "Введите email", type: "email"}]}>
|
||||||
|
<Input/>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
name="birthdate"
|
||||||
|
label="Дата рождения"
|
||||||
|
rules={[{required: true, message: "Введите дату рождения"}]}
|
||||||
|
>
|
||||||
|
<DatePicker
|
||||||
|
suffixIcon={<CalendarOutlined/>}
|
||||||
|
format="DD.MM.YYYY"
|
||||||
|
style={{width: "100%"}}
|
||||||
|
size="large"
|
||||||
|
maxDate={dayjs()}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
name="role_id"
|
||||||
|
label="Роль"
|
||||||
|
rules={[{required: true, message: "Выберите роль"}]}
|
||||||
|
>
|
||||||
|
<Select>
|
||||||
|
{roles.map((role) => (
|
||||||
|
<Select.Option key={role.id} value={role.id}>
|
||||||
|
{role.title}
|
||||||
|
</Select.Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
<Tooltip
|
||||||
|
title="Пароль должен содержать не менее 8 символов, включая хотя бы одну букву и одну цифру и один специальный символ"
|
||||||
|
>
|
||||||
|
<Typography.Title level={3} style={{width: 30}}>
|
||||||
|
<InfoCircleOutlined/>
|
||||||
|
</Typography.Title>
|
||||||
|
</Tooltip>
|
||||||
|
<Form.Item label="Пароль" name="password" rules={[{required: true, message: "Введите пароль"}]}>
|
||||||
|
<Input.Password/>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label="Подтвердите пароль" name="repeat_password"
|
||||||
|
rules={[{required: true, message: "Подтвердите пароль"}, ({getFieldValue}) => ({
|
||||||
|
validator(_, value) {
|
||||||
|
if (!value || getFieldValue("password") === value) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
return Promise.reject(new Error("Пароли не совпадают"));
|
||||||
|
},
|
||||||
|
}),]}>
|
||||||
|
<Input.Password/>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item>
|
||||||
|
<Button type="primary" htmlType="submit" loading={isLoading}>
|
||||||
|
Создать
|
||||||
|
</Button>
|
||||||
|
<Button onClick={handleCancel} style={{marginLeft: 8}}>
|
||||||
|
Отмена
|
||||||
|
</Button>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CreateUserModalForm;
|
||||||
@ -0,0 +1,79 @@
|
|||||||
|
import {useDispatch, useSelector} from "react-redux";
|
||||||
|
import {Form, notification} from "antd";
|
||||||
|
import {setOpenModalCreateUser, setSelectedUserToUpdate} from "../../../../Redux/Slices/usersSlice.js";
|
||||||
|
import {useGetAllRolesQuery} from "../../../../Api/rolesApi.js";
|
||||||
|
import {useCreateUserMutation} from "../../../../Api/usersApi.js";
|
||||||
|
|
||||||
|
|
||||||
|
const useCreateUserModalForm = () => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
|
||||||
|
const {
|
||||||
|
openModalCreateUser
|
||||||
|
} = useSelector(state => state.users);
|
||||||
|
|
||||||
|
const modalVisible = openModalCreateUser;
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
dispatch(setOpenModalCreateUser(false));
|
||||||
|
};
|
||||||
|
|
||||||
|
const [
|
||||||
|
registerUser,
|
||||||
|
{
|
||||||
|
isLoading: isLoadingRegister,
|
||||||
|
isError: isErrorRegister,
|
||||||
|
}
|
||||||
|
] = useCreateUserMutation();
|
||||||
|
|
||||||
|
const handleFinish = async () => {
|
||||||
|
const values = form.getFieldsValue();
|
||||||
|
const payload = {
|
||||||
|
...values,
|
||||||
|
birthdate: values.birthdate
|
||||||
|
? values.birthdate.format("YYYY-MM-DD")
|
||||||
|
: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log(payload);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await registerUser(payload).unwrap();
|
||||||
|
notification.success({
|
||||||
|
title: "Пользователь зарегистрирован",
|
||||||
|
description: "Пользователь успешно зарегистрирован",
|
||||||
|
placement: "topRight",
|
||||||
|
});
|
||||||
|
form.resetFields();
|
||||||
|
handleCancel();
|
||||||
|
} catch (error) {
|
||||||
|
notification.error({
|
||||||
|
title: "Ошибка регистрации пользователя",
|
||||||
|
description: error?.data?.detail || "Не удалось зарегистрировать пользователя",
|
||||||
|
placement: "topRight",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: roles = [],
|
||||||
|
isLoading: isLoadingRoles,
|
||||||
|
isError: isErrorRoles,
|
||||||
|
} = useGetAllRolesQuery(undefined, {
|
||||||
|
pollingInterval: 60000,
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
return {
|
||||||
|
modalVisible,
|
||||||
|
handleCancel,
|
||||||
|
handleFinish,
|
||||||
|
form,
|
||||||
|
roles,
|
||||||
|
isLoading: isLoadingRoles,
|
||||||
|
isError: isErrorRoles,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useCreateUserModalForm;
|
||||||
65
web/src/Components/Pages/AdminPage/useAdminPage.js
Normal file
65
web/src/Components/Pages/AdminPage/useAdminPage.js
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
import {useGetAllUsersQuery} from "../../../Api/usersApi.js";
|
||||||
|
import {useGetAllRolesQuery} from "../../../Api/rolesApi.js";
|
||||||
|
import {useMemo, useState} from "react";
|
||||||
|
import {useDispatch} from "react-redux";
|
||||||
|
import {setOpenModalCreateUser, setSelectedUserToUpdate} from "../../../Redux/Slices/usersSlice.js";
|
||||||
|
|
||||||
|
|
||||||
|
const useAdminPage = () => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
const [
|
||||||
|
searchString,
|
||||||
|
setSearchString,
|
||||||
|
] = useState("");
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: usersData = [],
|
||||||
|
isLoading: usersIsLoading,
|
||||||
|
isError: usersIsError,
|
||||||
|
} = useGetAllUsersQuery(undefined, {
|
||||||
|
pollingInterval: 20000,
|
||||||
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: rolesData = [],
|
||||||
|
isLoading: rolesIsLoading,
|
||||||
|
isError: rolesIsError,
|
||||||
|
} = useGetAllRolesQuery(undefined, {
|
||||||
|
pollingInterval: 20000,
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleSearch = (e) => {
|
||||||
|
setSearchString(e.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const filteredUsers = useMemo(() => {
|
||||||
|
return usersData.filter((user) => {
|
||||||
|
return Object.entries(user).some(([key, value]) => {
|
||||||
|
if (typeof value === "string") {
|
||||||
|
return value.toString().toLowerCase().includes(searchString.toLowerCase());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}, [usersData, searchString]);
|
||||||
|
|
||||||
|
const handleSelectUserToEdit = (user) => {
|
||||||
|
dispatch(setSelectedUserToUpdate(user));
|
||||||
|
};
|
||||||
|
|
||||||
|
const openCreateModal = () => {
|
||||||
|
dispatch(setOpenModalCreateUser(true));
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
handleSelectUserToEdit,
|
||||||
|
rolesData,
|
||||||
|
filteredUsers,
|
||||||
|
handleSearch,
|
||||||
|
isLoading: usersIsLoading | rolesIsLoading,
|
||||||
|
isError: usersIsError | rolesIsError,
|
||||||
|
openCreateModal,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useAdminPage;
|
||||||
@ -1,10 +1,31 @@
|
|||||||
import useProfilePage from "./useProfilePage.js";
|
import useProfilePage from "./useProfilePage.js";
|
||||||
import {Button, Col, DatePicker, Divider, Form, Input, Result, Row, Typography} from "antd";
|
import {
|
||||||
import {UserOutlined} from "@ant-design/icons";
|
Button,
|
||||||
|
Card,
|
||||||
|
Col,
|
||||||
|
DatePicker,
|
||||||
|
Form,
|
||||||
|
Input,
|
||||||
|
Result,
|
||||||
|
Row,
|
||||||
|
Typography,
|
||||||
|
Avatar,
|
||||||
|
Space,
|
||||||
|
Divider,
|
||||||
|
Upload,
|
||||||
|
} from "antd";
|
||||||
|
import {
|
||||||
|
UserOutlined,
|
||||||
|
MailOutlined,
|
||||||
|
LockOutlined,
|
||||||
|
CalendarOutlined,
|
||||||
|
SaveOutlined,
|
||||||
|
UploadOutlined,
|
||||||
|
} from "@ant-design/icons";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import LoadingIndicator from "../../Widgets/LoadingIndicator/LoadingIndicator.jsx";
|
import LoadingIndicator from "../../Widgets/LoadingIndicator/LoadingIndicator.jsx";
|
||||||
|
|
||||||
const {Title} = Typography;
|
const {Title, Text} = Typography;
|
||||||
|
|
||||||
const ProfilePage = () => {
|
const ProfilePage = () => {
|
||||||
const {
|
const {
|
||||||
@ -16,15 +37,15 @@ const ProfilePage = () => {
|
|||||||
isUpdateUserLoading,
|
isUpdateUserLoading,
|
||||||
isUpdateUserError,
|
isUpdateUserError,
|
||||||
onFinishProfileForm,
|
onFinishProfileForm,
|
||||||
onFinishPasswordForm
|
onFinishPasswordForm,
|
||||||
} = useProfilePage();
|
} = useProfilePage();
|
||||||
|
|
||||||
if (isError) {
|
if (isError) {
|
||||||
return (
|
return (
|
||||||
<Result
|
<Result
|
||||||
status="error"
|
status="error"
|
||||||
title="Ошибка"
|
title="Ошибка загрузки профиля"
|
||||||
subTitle="Произошла ошибка при загрузке данных профиля"
|
subTitle="Не удалось загрузить данные. Попробуйте обновить страницу."
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -34,83 +55,157 @@ const ProfilePage = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Col>
|
<Row justify="center" style={{padding: "24px 16px", minHeight: "100vh", background: "#f5f5f5"}}>
|
||||||
<Row>
|
<Col xs={24} sm={22} md={20} lg={16} xl={12}>
|
||||||
<Title>
|
<Card
|
||||||
<UserOutlined/> Управление профилем
|
title={
|
||||||
|
<Space>
|
||||||
|
<Avatar size={40} icon={<UserOutlined/>} style={{backgroundColor: "#1890ff"}}/>
|
||||||
|
<div>
|
||||||
|
<Title level={4} style={{margin: -3}}>
|
||||||
|
{userData?.first_name} {userData?.last_name}
|
||||||
</Title>
|
</Title>
|
||||||
<Divider/>
|
<Text type="secondary">Роль: {userData?.role?.title || "Пользователь"}</Text>
|
||||||
|
</div>
|
||||||
</Row>
|
</Space>
|
||||||
<Form name={"profile"} form={userForm} onFinish={onFinishProfileForm}>
|
}
|
||||||
|
style={{borderRadius: 12, boxShadow: "0 4px 12px rgba(0,0,0,0.05)", marginBottom: 24}}
|
||||||
|
>
|
||||||
|
<Form form={userForm} layout="vertical" onFinish={onFinishProfileForm}>
|
||||||
|
<Row gutter={16}>
|
||||||
|
<Col xs={24} md={12}>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="first_name"
|
name="first_name"
|
||||||
|
label="Имя"
|
||||||
rules={[{required: true, message: "Введите имя"}]}
|
rules={[{required: true, message: "Введите имя"}]}
|
||||||
>
|
>
|
||||||
<Input placeholder={"Имя"}/>
|
<Input prefix={<UserOutlined/>} placeholder="Иван" size="large"/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
|
||||||
|
<Col xs={24} md={12}>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="last_name"
|
name="last_name"
|
||||||
|
label="Фамилия"
|
||||||
rules={[{required: true, message: "Введите фамилию"}]}
|
rules={[{required: true, message: "Введите фамилию"}]}
|
||||||
>
|
>
|
||||||
<Input placeholder={"Фамилия"}/>
|
<Input prefix={<UserOutlined/>} placeholder="Иванов" size="large"/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
</Col>
|
||||||
name="patronymic"
|
|
||||||
>
|
<Col xs={24} md={12}>
|
||||||
<Input placeholder={"Отчество"}/>
|
<Form.Item name="patronymic" label="Отчество">
|
||||||
|
<Input prefix={<UserOutlined/>} placeholder="Иванович" size="large"/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
|
||||||
|
<Col xs={24} md={12}>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="email"
|
name="email"
|
||||||
rules={[{required: true, message: "Введите email"}]}
|
label="Email"
|
||||||
|
rules={[{required: true, type: "email", message: "Введите корректный email"}]}
|
||||||
>
|
>
|
||||||
<Input placeholder={"Email"}/>
|
<Input prefix={<MailOutlined/>} placeholder="ivan@example.com" size="large"/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
|
||||||
|
<Col xs={24} md={12}>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="birthdate"
|
name="birthdate"
|
||||||
rules={[{required: true, message: "Введите дату рождения"}]}
|
label="Дата рождения"
|
||||||
|
rules={[{required: true, message: "Выберите дату рождения"}]}
|
||||||
>
|
>
|
||||||
<DatePicker
|
<DatePicker
|
||||||
maxDate={dayjs(new Date())}
|
suffixIcon={<CalendarOutlined/>}
|
||||||
placeholder={"Дата рождения"}
|
format="DD.MM.YYYY"
|
||||||
format={"DD.MM.YYYY"}
|
placeholder="15.03.1995"
|
||||||
|
style={{width: "100%"}}
|
||||||
|
size="large"
|
||||||
|
maxDate={dayjs()}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
</Col>
|
||||||
name="login"
|
|
||||||
rules={[{required: true, message: "Введите логин"}]}
|
<Col xs={24} md={12}>
|
||||||
>
|
<Form.Item name="login" label="Логин">
|
||||||
<Input placeholder={"Логин"} disabled/>
|
<Input disabled prefix={<UserOutlined/>} size="large"/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item>
|
</Col>
|
||||||
<Button loading={isUpdateUserLoading} type="primary" htmlType="submit" block>
|
</Row>
|
||||||
Сохранить
|
|
||||||
|
<Form.Item style={{marginBottom: 0}}>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
htmlType="submit"
|
||||||
|
loading={isUpdateUserLoading}
|
||||||
|
icon={<SaveOutlined/>}
|
||||||
|
size="large"
|
||||||
|
block
|
||||||
|
>
|
||||||
|
Сохранить изменения
|
||||||
</Button>
|
</Button>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Form>
|
</Form>
|
||||||
<Divider/>
|
</Card>
|
||||||
|
|
||||||
<Title level={3}>Смена пароля</Title>
|
{/* Карточка смены пароля */}
|
||||||
<Form name={"password"} form={passwordForm} onFinish={onFinishPasswordForm}>
|
<Card
|
||||||
|
title={<Title level={4}><LockOutlined/> Смена пароля</Title>}
|
||||||
|
bordered={false}
|
||||||
|
style={{borderRadius: 12, boxShadow: "0 4px 12px rgba(0,0,0,0.05)"}}
|
||||||
|
>
|
||||||
|
<Form form={passwordForm} layout="vertical" onFinish={onFinishPasswordForm}>
|
||||||
|
<Row gutter={16}>
|
||||||
|
<Col xs={24} md={12}>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="password"
|
name="password"
|
||||||
rules={[{required: true, message: "Введите пароль"}]}
|
label="Новый пароль"
|
||||||
|
rules={[
|
||||||
|
{required: true, message: "Введите новый пароль"},
|
||||||
|
{min: 8, message: "Пароль должен быть не менее 8 символов"},
|
||||||
|
]}
|
||||||
>
|
>
|
||||||
<Input.Password placeholder={"Пароль"}/>
|
<Input.Password prefix={<LockOutlined/>} placeholder="••••••••" size="large"/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
|
||||||
|
<Col xs={24} md={12}>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="repeat_password"
|
name="repeat_password"
|
||||||
rules={[{required: true, message: "Введите пароль"}]}
|
label="Повторите пароль"
|
||||||
|
dependencies={["password"]}
|
||||||
|
rules={[
|
||||||
|
{required: true, message: "Повторите пароль"},
|
||||||
|
({getFieldValue}) => ({
|
||||||
|
validator(_, value) {
|
||||||
|
if (!value || getFieldValue("password") === value) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
return Promise.reject(new Error("Пароли не совпадают"));
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
]}
|
||||||
>
|
>
|
||||||
<Input.Password placeholder={"Повторите пароль"}/>
|
<Input.Password prefix={<LockOutlined/>} placeholder="••••••••" size="large"/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item>
|
</Col>
|
||||||
<Button loading={isLoading} type="primary" htmlType="submit" block>
|
</Row>
|
||||||
Сохранить
|
|
||||||
|
<Form.Item style={{marginBottom: 0}}>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
htmlType="submit"
|
||||||
|
icon={<SaveOutlined/>}
|
||||||
|
size="large"
|
||||||
|
block
|
||||||
|
>
|
||||||
|
Изменить пароль
|
||||||
</Button>
|
</Button>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Form>
|
</Form>
|
||||||
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
|
</Row>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
const CONFIG = {
|
const CONFIG = {
|
||||||
BASE_URL: import.meta.env.VITE_BASE_URL,
|
BASE_URL: import.meta.env.VITE_BASE_URL,
|
||||||
|
ROOT_ROLE_NAME: import.meta.env.VITE_ROOT_ROLE_NAME
|
||||||
};
|
};
|
||||||
|
|
||||||
export default CONFIG;
|
export default CONFIG;
|
||||||
23
web/src/Redux/Slices/usersSlice.js
Normal file
23
web/src/Redux/Slices/usersSlice.js
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import {createSlice} from "@reduxjs/toolkit";
|
||||||
|
|
||||||
|
const initialState = {
|
||||||
|
selectedUserToUpdate: null,
|
||||||
|
openModalCreateUser: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const usersSlice = createSlice({
|
||||||
|
name: "users",
|
||||||
|
initialState,
|
||||||
|
reducers: {
|
||||||
|
setSelectedUserToUpdate(state, action) {
|
||||||
|
state.selectedUserToUpdate = action.payload;
|
||||||
|
},
|
||||||
|
setOpenModalCreateUser(state, action) {
|
||||||
|
state.openModalCreateUser = action.payload;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const {setSelectedUserToUpdate, setOpenModalCreateUser} = usersSlice.actions;
|
||||||
|
|
||||||
|
export default usersSlice.reducer;
|
||||||
@ -1,19 +1,25 @@
|
|||||||
import {configureStore} from "@reduxjs/toolkit";
|
import {configureStore} from "@reduxjs/toolkit";
|
||||||
import authReducer from "./Slices/authSlice.js";
|
import authReducer from "./Slices/authSlice.js";
|
||||||
|
import usersReducer from "./Slices/usersSlice.js";
|
||||||
import {authApi} from "../Api/authApi.js";
|
import {authApi} from "../Api/authApi.js";
|
||||||
import {usersApi} from "../Api/usersApi.js";
|
import {usersApi} from "../Api/usersApi.js";
|
||||||
|
import {rolesApi} from "../Api/rolesApi.js";
|
||||||
|
|
||||||
export const store = configureStore({
|
export const store = configureStore({
|
||||||
reducer: {
|
reducer: {
|
||||||
auth: authReducer,
|
auth: authReducer,
|
||||||
[authApi.reducerPath]: authApi.reducer,
|
[authApi.reducerPath]: authApi.reducer,
|
||||||
|
|
||||||
[usersApi.reducerPath]: usersApi.reducer
|
users: usersReducer,
|
||||||
|
[usersApi.reducerPath]: usersApi.reducer,
|
||||||
|
|
||||||
|
[rolesApi.reducerPath]: rolesApi.reducer,
|
||||||
},
|
},
|
||||||
middleware: (getDefaultMiddleware) => (
|
middleware: (getDefaultMiddleware) => (
|
||||||
getDefaultMiddleware().concat(
|
getDefaultMiddleware().concat(
|
||||||
authApi.middleware,
|
authApi.middleware,
|
||||||
usersApi.middleware,
|
usersApi.middleware,
|
||||||
|
rolesApi.middleware,
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user