начал делать профиль
This commit is contained in:
parent
6ef548514f
commit
f7678962fc
@ -44,3 +44,8 @@ class UsersRepository:
|
|||||||
await self.db.commit()
|
await self.db.commit()
|
||||||
await self.db.refresh(user)
|
await self.db.refresh(user)
|
||||||
return user
|
return user
|
||||||
|
|
||||||
|
async def update(self, user: User) -> User:
|
||||||
|
await self.db.merge(user)
|
||||||
|
await self.db.commit()
|
||||||
|
return user
|
||||||
|
|||||||
41
api/app/controllers/users_router.py
Normal file
41
api/app/controllers/users_router.py
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from fastapi import APIRouter, Depends
|
||||||
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
|
from app.database.session import get_db
|
||||||
|
from app.domain.entities.change_password import ChangePasswordEntity
|
||||||
|
from app.domain.entities.user import UserEntity
|
||||||
|
from app.infrastructure.dependencies import get_current_user
|
||||||
|
from app.infrastructure.users_service import UsersService
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
|
@router.get(
|
||||||
|
'/my-data/',
|
||||||
|
response_model=Optional[UserEntity],
|
||||||
|
summary='Returns current authenticated user data',
|
||||||
|
description='Returns current authenticated user data',
|
||||||
|
)
|
||||||
|
async def get_authenticated_user_data(
|
||||||
|
db: AsyncSession = Depends(get_db),
|
||||||
|
user=Depends(get_current_user),
|
||||||
|
):
|
||||||
|
users_service = UsersService(db)
|
||||||
|
return await users_service.get_by_id(user.id)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get(
|
||||||
|
'/change-password/',
|
||||||
|
response_model=Optional[UserEntity],
|
||||||
|
summary='Change password for user',
|
||||||
|
description='Changes password for user',
|
||||||
|
)
|
||||||
|
async def get_authenticated_user_data(
|
||||||
|
data: ChangePasswordEntity,
|
||||||
|
db: AsyncSession = Depends(get_db),
|
||||||
|
user=Depends(get_current_user),
|
||||||
|
):
|
||||||
|
users_service = UsersService(db)
|
||||||
|
return await users_service.get_by_id(data.user_id, data.new_password, user.id)
|
||||||
6
api/app/domain/entities/change_password.py
Normal file
6
api/app/domain/entities/change_password.py
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
|
class ChangePasswordEntity(BaseModel):
|
||||||
|
user_id: int
|
||||||
|
new_password: str
|
||||||
@ -16,6 +16,43 @@ class UsersService:
|
|||||||
self.users_repository = UsersRepository(db)
|
self.users_repository = UsersRepository(db)
|
||||||
self.roles_repository = RolesRepository(db)
|
self.roles_repository = RolesRepository(db)
|
||||||
|
|
||||||
|
async def get_by_id(self, user_id: int) -> Optional[UserEntity]:
|
||||||
|
user = await self.users_repository.get_by_id(user_id)
|
||||||
|
if not user:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
|
detail='User was not found',
|
||||||
|
)
|
||||||
|
|
||||||
|
return self.model_to_entity(user)
|
||||||
|
|
||||||
|
async def change_password(self, user_id: int, new_password: str, current_user_id: int) -> Optional[UserEntity]:
|
||||||
|
user = await self.users_repository.get_by_id(user_id)
|
||||||
|
if not user:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
|
detail='User was not found',
|
||||||
|
)
|
||||||
|
|
||||||
|
current_user = await self.users_repository.get_by_id(current_user_id)
|
||||||
|
if not user:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
|
detail='User was not found',
|
||||||
|
)
|
||||||
|
|
||||||
|
if user.id != current_user.id and current_user.role.title != 'Администратор':
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_403_FORBIDDEN,
|
||||||
|
detail='Permission denied',
|
||||||
|
)
|
||||||
|
|
||||||
|
user.set_password(new_password)
|
||||||
|
|
||||||
|
user = await self.users_repository.update(user)
|
||||||
|
|
||||||
|
return self.model_to_entity(user)
|
||||||
|
|
||||||
async def register_user(self, register_entity: RegisterEntity) -> Optional[UserEntity]:
|
async def register_user(self, register_entity: RegisterEntity) -> Optional[UserEntity]:
|
||||||
role = await self.roles_repository.get_by_id(register_entity.role_id)
|
role = await self.roles_repository.get_by_id(register_entity.role_id)
|
||||||
if not role:
|
if not role:
|
||||||
|
|||||||
@ -12,6 +12,7 @@ from app.controllers.register_routes import router as register_router
|
|||||||
from app.controllers.scheduled_appointments_router import router as scheduled_appointments_router
|
from app.controllers.scheduled_appointments_router import router as scheduled_appointments_router
|
||||||
from app.controllers.set_content_router import router as set_content_router
|
from app.controllers.set_content_router import router as set_content_router
|
||||||
from app.controllers.sets_router import router as sets_router
|
from app.controllers.sets_router import router as sets_router
|
||||||
|
from app.controllers.users_router import router as users_router
|
||||||
from app.settings import settings
|
from app.settings import settings
|
||||||
|
|
||||||
|
|
||||||
@ -37,6 +38,7 @@ def start_app():
|
|||||||
api_app.include_router(scheduled_appointments_router, prefix=f'{settings.APP_PREFIX}/scheduled_appointments', tags=['scheduled_appointments'])
|
api_app.include_router(scheduled_appointments_router, prefix=f'{settings.APP_PREFIX}/scheduled_appointments', tags=['scheduled_appointments'])
|
||||||
api_app.include_router(set_content_router, prefix=f'{settings.APP_PREFIX}/set_content', tags=['set_content'])
|
api_app.include_router(set_content_router, prefix=f'{settings.APP_PREFIX}/set_content', tags=['set_content'])
|
||||||
api_app.include_router(sets_router, prefix=f'{settings.APP_PREFIX}/sets', tags=['sets'])
|
api_app.include_router(sets_router, prefix=f'{settings.APP_PREFIX}/sets', tags=['sets'])
|
||||||
|
api_app.include_router(users_router, prefix=f'{settings.APP_PREFIX}/users', tags=['users'])
|
||||||
|
|
||||||
return api_app
|
return api_app
|
||||||
|
|
||||||
|
|||||||
32
web-app/src/Api/usersApi.js
Normal file
32
web-app/src/Api/usersApi.js
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import {createApi, fetchBaseQuery} from "@reduxjs/toolkit/query/react";
|
||||||
|
import CONFIG from "../Core/сonfig.js";
|
||||||
|
|
||||||
|
|
||||||
|
export const usersApi = createApi({
|
||||||
|
reducerPath: 'usersApi',
|
||||||
|
baseQuery: fetchBaseQuery({
|
||||||
|
baseUrl: CONFIG.BASE_URL,
|
||||||
|
prepareHeaders: (headers) => {
|
||||||
|
const token = localStorage.getItem('access_token');
|
||||||
|
if (token) headers.set('Authorization', `Bearer ${token}`);
|
||||||
|
return headers;
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
tagTypes: ['User'],
|
||||||
|
endpoints: (builder) => ({
|
||||||
|
getAuthenticatedUserData: builder.query({
|
||||||
|
query: () => '/users/my-data/',
|
||||||
|
providesTags: ['User'],
|
||||||
|
refetchOnMountOrArgChange: 5,
|
||||||
|
}),
|
||||||
|
changePassword: builder.mutation({
|
||||||
|
query: (data) => ({
|
||||||
|
url: "/users/change-password",
|
||||||
|
method: "POST",
|
||||||
|
body: data,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const {useGetAuthenticatedUserDataQuery} = usersApi;
|
||||||
@ -7,6 +7,7 @@ import HomePage from "../Components/Pages/HomePage/HomePage.jsx";
|
|||||||
import LensesSetsPage from "../Components/Pages/LensesSetsPage/LensesSetsPage.jsx";
|
import LensesSetsPage from "../Components/Pages/LensesSetsPage/LensesSetsPage.jsx";
|
||||||
import IssuesPage from "../Components/Pages/IssuesPage/IssuesPage.jsx";
|
import IssuesPage from "../Components/Pages/IssuesPage/IssuesPage.jsx";
|
||||||
import AppointmentsPage from "../Components/Pages/AppointmentsPage/AppointmentsPage.jsx";
|
import AppointmentsPage from "../Components/Pages/AppointmentsPage/AppointmentsPage.jsx";
|
||||||
|
import ProfilePage from "../Components/Pages/ProfilePage/ProfilePage.jsx";
|
||||||
|
|
||||||
|
|
||||||
const AppRouter = () => (
|
const AppRouter = () => (
|
||||||
@ -19,6 +20,7 @@ const AppRouter = () => (
|
|||||||
<Route path={"/lenses"} element={<LensesSetsPage/>}/>
|
<Route path={"/lenses"} element={<LensesSetsPage/>}/>
|
||||||
<Route path={"/issues"} element={<IssuesPage/>}/>
|
<Route path={"/issues"} element={<IssuesPage/>}/>
|
||||||
<Route path={"/appointments"} element={<AppointmentsPage/>}/>
|
<Route path={"/appointments"} element={<AppointmentsPage/>}/>
|
||||||
|
<Route path={"/profile"} element={<ProfilePage/>}/>
|
||||||
<Route path={"/"} element={<HomePage/>}/>
|
<Route path={"/"} element={<HomePage/>}/>
|
||||||
</Route>
|
</Route>
|
||||||
</Route>
|
</Route>
|
||||||
|
|||||||
133
web-app/src/Components/Pages/ProfilePage/ProfilePage.jsx
Normal file
133
web-app/src/Components/Pages/ProfilePage/ProfilePage.jsx
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
import { Button, Card, Col, Form, Input, Modal, Row, Space, Typography, Result } from "antd";
|
||||||
|
import { EditOutlined } from "@ant-design/icons";
|
||||||
|
import LoadingIndicator from "../../Widgets/LoadingIndicator.jsx";
|
||||||
|
import useProfilePage from "./useProfilePage.js";
|
||||||
|
import useProfilePageUI from "./useProfilePageUI.js";
|
||||||
|
import { useSelector } from "react-redux";
|
||||||
|
|
||||||
|
const ProfilePage = () => {
|
||||||
|
const { userData, isLoading, isError, isUpdating, handleEditProfile, handleCancelEdit, handleSubmitProfile, handleSubmitPassword } = useProfilePage();
|
||||||
|
const { containerStyle, cardStyle, buttonStyle, formStyle, profileFormRules, passwordFormRules, isMobile } = useProfilePageUI();
|
||||||
|
const editProfileModalVisible = useSelector((state) => state.usersUI.editProfileModalVisible);
|
||||||
|
const [profileForm] = Form.useForm();
|
||||||
|
const [passwordForm] = Form.useForm();
|
||||||
|
|
||||||
|
if (isError) {
|
||||||
|
return (
|
||||||
|
<Result
|
||||||
|
status="error"
|
||||||
|
title="Ошибка"
|
||||||
|
subTitle="Произошла ошибка при загрузке данных профиля"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={containerStyle}>
|
||||||
|
{isLoading ? (
|
||||||
|
<LoadingIndicator />
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Typography.Title level={1}>Профиль</Typography.Title>
|
||||||
|
<Card style={cardStyle}>
|
||||||
|
<Row gutter={[16, 16]}>
|
||||||
|
<Col span={24}>
|
||||||
|
<Typography.Text strong>Фамилия: </Typography.Text>
|
||||||
|
<Typography.Text>{userData.last_name || "-"}</Typography.Text>
|
||||||
|
</Col>
|
||||||
|
<Col span={24}>
|
||||||
|
<Typography.Text strong>Имя: </Typography.Text>
|
||||||
|
<Typography.Text>{userData.first_name || "-"}</Typography.Text>
|
||||||
|
</Col>
|
||||||
|
<Col span={24}>
|
||||||
|
<Typography.Text strong>Отчество: </Typography.Text>
|
||||||
|
<Typography.Text>{userData.patronymic || "-"}</Typography.Text>
|
||||||
|
</Col>
|
||||||
|
<Col span={24}>
|
||||||
|
<Typography.Text strong>Логин: </Typography.Text>
|
||||||
|
<Typography.Text>{userData.login || "-"}</Typography.Text>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
icon={<EditOutlined />}
|
||||||
|
onClick={handleEditProfile}
|
||||||
|
style={{ ...buttonStyle, marginTop: 16 }}
|
||||||
|
>
|
||||||
|
Редактировать
|
||||||
|
</Button>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Modal
|
||||||
|
title="Редактировать профиль"
|
||||||
|
open={editProfileModalVisible}
|
||||||
|
onCancel={handleCancelEdit}
|
||||||
|
footer={null}
|
||||||
|
>
|
||||||
|
<Form
|
||||||
|
form={profileForm}
|
||||||
|
layout="vertical"
|
||||||
|
onFinish={handleSubmitProfile}
|
||||||
|
initialValues={{
|
||||||
|
first_name: userData.first_name,
|
||||||
|
last_name: userData.last_name,
|
||||||
|
patronymic: userData.patronymic,
|
||||||
|
login: userData.login,
|
||||||
|
}}
|
||||||
|
style={formStyle}
|
||||||
|
>
|
||||||
|
<Form.Item label="Фамилия" name="last_name" rules={profileFormRules.last_name}>
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label="Имя" name="first_name" rules={profileFormRules.first_name}>
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label="Отчество" name="patronymic" rules={profileFormRules.patronymic}>
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label="Логин" name="login" rules={profileFormRules.login}>
|
||||||
|
<Input disabled />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item>
|
||||||
|
<Space>
|
||||||
|
<Button type="primary" htmlType="submit" loading={isUpdating}>
|
||||||
|
Сохранить
|
||||||
|
</Button>
|
||||||
|
<Button onClick={handleCancelEdit}>Отмена</Button>
|
||||||
|
</Space>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
|
||||||
|
<Form
|
||||||
|
form={passwordForm}
|
||||||
|
layout="vertical"
|
||||||
|
onFinish={handleSubmitPassword}
|
||||||
|
style={formStyle}
|
||||||
|
>
|
||||||
|
<Typography.Title level={4}>Смена пароля</Typography.Title>
|
||||||
|
<Form.Item label="Текущий пароль" name="current_password" rules={passwordFormRules.current_password}>
|
||||||
|
<Input.Password />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label="Новый пароль" name="new_password" rules={passwordFormRules.new_password}>
|
||||||
|
<Input.Password />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label="Подтвердите пароль" name="confirm_password" rules={passwordFormRules.confirm_password}>
|
||||||
|
<Input.Password />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item>
|
||||||
|
<Space>
|
||||||
|
<Button type="primary" htmlType="submit" loading={isUpdating}>
|
||||||
|
Изменить пароль
|
||||||
|
</Button>
|
||||||
|
<Button onClick={() => passwordForm.resetFields()}>Сбросить</Button>
|
||||||
|
</Space>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ProfilePage;
|
||||||
92
web-app/src/Components/Pages/ProfilePage/useProfilePage.js
Normal file
92
web-app/src/Components/Pages/ProfilePage/useProfilePage.js
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
import { useGetAuthenticatedUserDataQuery } from "../../../Api/usersApi.js";
|
||||||
|
import { useDispatch } from "react-redux";
|
||||||
|
import { openEditProfileModal, closeEditProfileModal } from "../../../Redux/Slices/usersSlice.js";
|
||||||
|
import { notification } from "antd";
|
||||||
|
|
||||||
|
const useProfilePage = () => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: userData = {},
|
||||||
|
isLoading: isLoadingUserData,
|
||||||
|
isError: isErrorUserData,
|
||||||
|
} = useGetAuthenticatedUserDataQuery(undefined, {
|
||||||
|
pollingInterval: 20000,
|
||||||
|
});
|
||||||
|
|
||||||
|
// const [updateUserProfile, { isLoading: isUpdatingProfile }] = useUpdateUserProfileMutation();
|
||||||
|
const updateUserProfile = () => {};
|
||||||
|
const isUpdatingProfile = false;
|
||||||
|
const isErrorUpdatingProfile = false;
|
||||||
|
|
||||||
|
// const [changePassword, { isLoading: isChangingPassword }] = useChangePasswordMutation();
|
||||||
|
const changePassword = () => {};
|
||||||
|
const isChangingPassword = false;
|
||||||
|
|
||||||
|
const handleEditProfile = () => {
|
||||||
|
dispatch(openEditProfileModal());
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancelEdit = () => {
|
||||||
|
dispatch(closeEditProfileModal());
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmitProfile = async (values) => {
|
||||||
|
try {
|
||||||
|
const profileData = {
|
||||||
|
first_name: values.first_name,
|
||||||
|
last_name: values.last_name,
|
||||||
|
patronymic: values.patronymic || null,
|
||||||
|
};
|
||||||
|
await updateUserProfile(profileData).unwrap();
|
||||||
|
notification.success({
|
||||||
|
message: "Успех",
|
||||||
|
description: "Профиль успешно обновлен.",
|
||||||
|
placement: "topRight",
|
||||||
|
});
|
||||||
|
dispatch(closeEditProfileModal());
|
||||||
|
} catch (error) {
|
||||||
|
notification.error({
|
||||||
|
message: "Ошибка",
|
||||||
|
description: error?.data?.message || "Не удалось обновить профиль.",
|
||||||
|
placement: "topRight",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmitPassword = async (values) => {
|
||||||
|
try {
|
||||||
|
const passwordData = {
|
||||||
|
current_password: values.current_password,
|
||||||
|
new_password: values.new_password,
|
||||||
|
confirm_password: values.confirm_password,
|
||||||
|
};
|
||||||
|
await changePassword(passwordData).unwrap();
|
||||||
|
notification.success({
|
||||||
|
message: "Успех",
|
||||||
|
description: "Пароль успешно изменен.",
|
||||||
|
placement: "topRight",
|
||||||
|
});
|
||||||
|
dispatch(closeEditProfileModal());
|
||||||
|
} catch (error) {
|
||||||
|
notification.error({
|
||||||
|
message: "Ошибка",
|
||||||
|
description: error?.data?.message || "Не удалось изменить пароль.",
|
||||||
|
placement: "topRight",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
userData,
|
||||||
|
isLoading: isLoadingUserData,
|
||||||
|
isError: isErrorUserData,
|
||||||
|
isUpdating: isUpdatingProfile || isChangingPassword,
|
||||||
|
handleEditProfile,
|
||||||
|
handleCancelEdit,
|
||||||
|
handleSubmitProfile,
|
||||||
|
handleSubmitPassword,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useProfilePage;
|
||||||
63
web-app/src/Components/Pages/ProfilePage/useProfilePageUI.js
Normal file
63
web-app/src/Components/Pages/ProfilePage/useProfilePageUI.js
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
import { Grid } from "antd";
|
||||||
|
|
||||||
|
const { useBreakpoint } = Grid;
|
||||||
|
|
||||||
|
const useProfilePageUI = () => {
|
||||||
|
const screens = useBreakpoint();
|
||||||
|
|
||||||
|
const containerStyle = { padding: screens.xs ? 16 : 24 };
|
||||||
|
const cardStyle = { marginBottom: 24 };
|
||||||
|
const buttonStyle = { width: screens.xs ? "100%" : "auto" };
|
||||||
|
const formStyle = { maxWidth: 600 };
|
||||||
|
|
||||||
|
const profileFormRules = {
|
||||||
|
first_name: [
|
||||||
|
{ required: true, message: "Пожалуйста, введите имя" },
|
||||||
|
{ max: 50, message: "Имя не может превышать 50 символов" },
|
||||||
|
],
|
||||||
|
last_name: [
|
||||||
|
{ required: true, message: "Пожалуйста, введите фамилию" },
|
||||||
|
{ max: 50, message: "Фамилия не может превышать 50 символов" },
|
||||||
|
],
|
||||||
|
patronymic: [
|
||||||
|
{ max: 50, message: "Отчество не может превышать 50 символов" },
|
||||||
|
],
|
||||||
|
login: [
|
||||||
|
{ required: true, message: "Логин обязателен" },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const passwordFormRules = {
|
||||||
|
current_password: [
|
||||||
|
{ required: true, message: "Пожалуйста, введите текущий пароль" },
|
||||||
|
{ min: 8, message: "Пароль должен содержать минимум 8 символов" },
|
||||||
|
],
|
||||||
|
new_password: [
|
||||||
|
{ required: true, message: "Пожалуйста, введите новый пароль" },
|
||||||
|
{ min: 8, message: "Пароль должен содержать минимум 8 символов" },
|
||||||
|
],
|
||||||
|
confirm_password: [
|
||||||
|
{ required: true, message: "Пожалуйста, подтвердите новый пароль" },
|
||||||
|
({ getFieldValue }) => ({
|
||||||
|
validator(_, value) {
|
||||||
|
if (!value || getFieldValue("new_password") === value) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
return Promise.reject(new Error("Пароли не совпадают"));
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
containerStyle,
|
||||||
|
cardStyle,
|
||||||
|
buttonStyle,
|
||||||
|
formStyle,
|
||||||
|
profileFormRules,
|
||||||
|
passwordFormRules,
|
||||||
|
isMobile: screens.xs,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useProfilePageUI;
|
||||||
22
web-app/src/Redux/Slices/usersSlice.js
Normal file
22
web-app/src/Redux/Slices/usersSlice.js
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { createSlice } from "@reduxjs/toolkit";
|
||||||
|
|
||||||
|
const initialState = {
|
||||||
|
editProfileModalVisible: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const usersSlice = createSlice({
|
||||||
|
name: "usersUI",
|
||||||
|
initialState,
|
||||||
|
reducers: {
|
||||||
|
openEditProfileModal(state) {
|
||||||
|
state.editProfileModalVisible = true;
|
||||||
|
},
|
||||||
|
closeEditProfileModal(state) {
|
||||||
|
state.editProfileModalVisible = false;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const { openEditProfileModal, closeEditProfileModal } = usersSlice.actions;
|
||||||
|
|
||||||
|
export default usersSlice.reducer;
|
||||||
@ -13,6 +13,8 @@ import {appointmentsApi} from "../Api/appointmentsApi.js";
|
|||||||
import appointmentsReducer from "./Slices/appointmentsSlice.js";
|
import appointmentsReducer from "./Slices/appointmentsSlice.js";
|
||||||
import {scheduledAppointmentsApi} from "../Api/scheduledAppointmentsApi.js";
|
import {scheduledAppointmentsApi} from "../Api/scheduledAppointmentsApi.js";
|
||||||
import {appointmentTypesApi} from "../Api/appointmentTypesApi.js";
|
import {appointmentTypesApi} from "../Api/appointmentTypesApi.js";
|
||||||
|
import {usersApi} from "../Api/usersApi.js";
|
||||||
|
import usersReducer from "./Slices/usersSlice.js";
|
||||||
|
|
||||||
export const store = configureStore({
|
export const store = configureStore({
|
||||||
reducer: {
|
reducer: {
|
||||||
@ -38,6 +40,9 @@ export const store = configureStore({
|
|||||||
[scheduledAppointmentsApi.reducerPath]: scheduledAppointmentsApi.reducer,
|
[scheduledAppointmentsApi.reducerPath]: scheduledAppointmentsApi.reducer,
|
||||||
|
|
||||||
[appointmentTypesApi.reducerPath]: appointmentTypesApi.reducer,
|
[appointmentTypesApi.reducerPath]: appointmentTypesApi.reducer,
|
||||||
|
|
||||||
|
[usersApi.reducerPath]: usersApi.reducer,
|
||||||
|
usersUI: usersReducer
|
||||||
},
|
},
|
||||||
middleware: (getDefaultMiddleware) => (
|
middleware: (getDefaultMiddleware) => (
|
||||||
getDefaultMiddleware().concat(
|
getDefaultMiddleware().concat(
|
||||||
@ -50,6 +55,7 @@ export const store = configureStore({
|
|||||||
appointmentsApi.middleware,
|
appointmentsApi.middleware,
|
||||||
scheduledAppointmentsApi.middleware,
|
scheduledAppointmentsApi.middleware,
|
||||||
appointmentTypesApi.middleware,
|
appointmentTypesApi.middleware,
|
||||||
|
usersApi.middleware,
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user