feat: Профиль пользователя
Добавлена функциональность профиля пользователя: * Получение данных пользователя * Редактирование профиля * Изменение пароля
This commit is contained in:
parent
d5fb35e266
commit
fda45296a4
@ -19,7 +19,19 @@ export const usersApi = createApi({
|
|||||||
body: data,
|
body: data,
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
|
updateUser: builder.mutation({
|
||||||
|
query: ({userId, ...data}) => ({
|
||||||
|
url: `/users/${userId}/`,
|
||||||
|
method: "PUT",
|
||||||
|
body: data,
|
||||||
|
}),
|
||||||
|
invalidatesTags: ['User']
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const {useGetAuthenticatedUserDataQuery} = usersApi;
|
export const {
|
||||||
|
useGetAuthenticatedUserDataQuery,
|
||||||
|
useChangePasswordMutation,
|
||||||
|
useUpdateUserMutation,
|
||||||
|
} = usersApi;
|
||||||
@ -1,22 +1,38 @@
|
|||||||
import {BrowserRouter as Router} from "react-router-dom";
|
import {BrowserRouter as Router} from "react-router-dom";
|
||||||
import AppRouter from "./AppRouter.jsx";
|
import AppRouter from "./AppRouter.jsx";
|
||||||
import "/src/Styles/app.css";
|
import "/src/Styles/app.css";
|
||||||
import {Provider} from "react-redux";
|
import {useDispatch, useSelector} from "react-redux";
|
||||||
import store from "../Redux/store.js";
|
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import locale from 'antd/locale/ru_RU';
|
import locale from 'antd/locale/ru_RU';
|
||||||
import {ConfigProvider} from "antd";
|
import {ConfigProvider} from "antd";
|
||||||
|
import {useEffect} from "react";
|
||||||
|
import {checkAuth} from "../Redux/Slices/authSlice.js";
|
||||||
|
import LoadingIndicator from "../Components/Widgets/LoadingIndicator/LoadingIndicator.jsx";
|
||||||
|
import ErrorBoundary from "./ErrorBoundary.jsx";
|
||||||
|
|
||||||
dayjs.locale('ru');
|
dayjs.locale('ru');
|
||||||
|
|
||||||
const App = () => (
|
const App = () => {
|
||||||
<Provider store={store}>
|
const dispatch = useDispatch();
|
||||||
|
const {isLoading} = useSelector((state) => state.auth);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
dispatch(checkAuth());
|
||||||
|
}, [dispatch]);
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return <LoadingIndicator/>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
<Router>
|
<Router>
|
||||||
<ConfigProvider locale={locale}>
|
<ConfigProvider locale={locale}>
|
||||||
<AppRouter/>
|
<ErrorBoundary>
|
||||||
|
<AppRouter/>
|
||||||
|
</ErrorBoundary>
|
||||||
</ConfigProvider>
|
</ConfigProvider>
|
||||||
</Router>
|
</Router>
|
||||||
</Provider>
|
);
|
||||||
);
|
};
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
|
|||||||
@ -1,14 +1,14 @@
|
|||||||
import { Navigate, Outlet } from "react-router-dom";
|
import {Navigate, Outlet} from "react-router-dom";
|
||||||
import { useSelector } from "react-redux";
|
import {useSelector} from "react-redux";
|
||||||
|
|
||||||
const PrivateRoute = () => {
|
const PrivateRoute = () => {
|
||||||
const { user } = useSelector((state) => state.auth);
|
const {user} = useSelector((state) => state.auth);
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return <Navigate to="/login" />;
|
return <Navigate to="/login"/>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <Outlet />;
|
return <Outlet/>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default PrivateRoute;
|
export default PrivateRoute;
|
||||||
@ -1,9 +1,13 @@
|
|||||||
import {StrictMode} from 'react'
|
import {StrictMode} from 'react'
|
||||||
import {createRoot} from 'react-dom/client'
|
import {createRoot} from 'react-dom/client'
|
||||||
import App from './App.jsx'
|
import App from './App.jsx'
|
||||||
|
import store from "../Redux/store.js";
|
||||||
|
import {Provider} from "react-redux";
|
||||||
|
|
||||||
createRoot(document.getElementById('root')).render(
|
createRoot(document.getElementById('root')).render(
|
||||||
<StrictMode>
|
<StrictMode>
|
||||||
<App/>
|
<Provider store={store}>
|
||||||
|
<App/>
|
||||||
|
</Provider>
|
||||||
</StrictMode>
|
</StrictMode>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { Grid } from "antd";
|
import { Grid } from "antd";
|
||||||
import { useMemo } from "react";
|
import {useEffect, useMemo} from "react";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import isBetween from "dayjs/plugin/isBetween";
|
import isBetween from "dayjs/plugin/isBetween";
|
||||||
import {useSelector} from "react-redux"; // Import isBetween plugin
|
import {useSelector} from "react-redux"; // Import isBetween plugin
|
||||||
@ -18,6 +18,10 @@ const useHomePageUI = (appointments, scheduledAppointments, patients) => {
|
|||||||
const buttonStyle = { width: screens.xs ? "100%" : "auto" };
|
const buttonStyle = { width: screens.xs ? "100%" : "auto" };
|
||||||
const chartContainerStyle = { padding: 16, background: "#fff", borderRadius: 4 };
|
const chartContainerStyle = { padding: 16, background: "#fff", borderRadius: 4 };
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
document.title = "Главная страница";
|
||||||
|
}, []);
|
||||||
|
|
||||||
const todayEvents = useMemo(() => {
|
const todayEvents = useMemo(() => {
|
||||||
return [...appointments, ...scheduledAppointments].filter((event) =>
|
return [...appointments, ...scheduledAppointments].filter((event) =>
|
||||||
dayjs(event.appointment_datetime || event.scheduled_datetime).isSame(dayjs(), "day")
|
dayjs(event.appointment_datetime || event.scheduled_datetime).isSame(dayjs(), "day")
|
||||||
|
|||||||
@ -1,16 +1,32 @@
|
|||||||
import { Button, Card, Col, Form, Input, Modal, Row, Space, Typography, Result } from "antd";
|
import {Button, Card, Col, Form, Input, Modal, Row, Space, Typography, Result} from "antd";
|
||||||
import { EditOutlined } from "@ant-design/icons";
|
import {EditOutlined} from "@ant-design/icons";
|
||||||
import LoadingIndicator from "../../Widgets/LoadingIndicator/LoadingIndicator.jsx";
|
import LoadingIndicator from "../../Widgets/LoadingIndicator/LoadingIndicator.jsx";
|
||||||
import useProfilePage from "./useProfilePage.js";
|
import useProfilePage from "./useProfilePage.js";
|
||||||
import useProfilePageUI from "./useProfilePageUI.js";
|
import useProfilePageUI from "./useProfilePageUI.js";
|
||||||
import { useSelector } from "react-redux";
|
import {useSelector} from "react-redux";
|
||||||
|
|
||||||
const ProfilePage = () => {
|
const ProfilePage = () => {
|
||||||
const { userData, isLoading, isError, isUpdating, handleEditProfile, handleCancelEdit, handleSubmitProfile, handleSubmitPassword } = useProfilePage();
|
const {
|
||||||
const { containerStyle, cardStyle, buttonStyle, formStyle, profileFormRules, passwordFormRules, isMobile } = useProfilePageUI();
|
userData,
|
||||||
|
isLoading,
|
||||||
|
isError,
|
||||||
|
isUpdating,
|
||||||
|
handleEditProfile,
|
||||||
|
handleCancelEdit,
|
||||||
|
handleSubmitProfile,
|
||||||
|
handleSubmitPassword
|
||||||
|
} = useProfilePage();
|
||||||
|
const {
|
||||||
|
containerStyle,
|
||||||
|
cardStyle,
|
||||||
|
buttonStyle,
|
||||||
|
formStyle,
|
||||||
|
profileFormRules,
|
||||||
|
passwordFormRules,
|
||||||
|
profileForm,
|
||||||
|
passwordForm
|
||||||
|
} = useProfilePageUI();
|
||||||
const editProfileModalVisible = useSelector((state) => state.usersUI.editProfileModalVisible);
|
const editProfileModalVisible = useSelector((state) => state.usersUI.editProfileModalVisible);
|
||||||
const [profileForm] = Form.useForm();
|
|
||||||
const [passwordForm] = Form.useForm();
|
|
||||||
|
|
||||||
if (isError) {
|
if (isError) {
|
||||||
return (
|
return (
|
||||||
@ -25,7 +41,7 @@ const ProfilePage = () => {
|
|||||||
return (
|
return (
|
||||||
<div style={containerStyle}>
|
<div style={containerStyle}>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<LoadingIndicator />
|
<LoadingIndicator/>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<Typography.Title level={1}>Профиль</Typography.Title>
|
<Typography.Title level={1}>Профиль</Typography.Title>
|
||||||
@ -50,9 +66,9 @@ const ProfilePage = () => {
|
|||||||
</Row>
|
</Row>
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
icon={<EditOutlined />}
|
icon={<EditOutlined/>}
|
||||||
onClick={handleEditProfile}
|
onClick={handleEditProfile}
|
||||||
style={{ ...buttonStyle, marginTop: 16 }}
|
style={{...buttonStyle, marginTop: 16}}
|
||||||
>
|
>
|
||||||
Редактировать
|
Редактировать
|
||||||
</Button>
|
</Button>
|
||||||
@ -77,16 +93,16 @@ const ProfilePage = () => {
|
|||||||
style={formStyle}
|
style={formStyle}
|
||||||
>
|
>
|
||||||
<Form.Item label="Фамилия" name="last_name" rules={profileFormRules.last_name}>
|
<Form.Item label="Фамилия" name="last_name" rules={profileFormRules.last_name}>
|
||||||
<Input />
|
<Input/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label="Имя" name="first_name" rules={profileFormRules.first_name}>
|
<Form.Item label="Имя" name="first_name" rules={profileFormRules.first_name}>
|
||||||
<Input />
|
<Input/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label="Отчество" name="patronymic" rules={profileFormRules.patronymic}>
|
<Form.Item label="Отчество" name="patronymic" rules={profileFormRules.patronymic}>
|
||||||
<Input />
|
<Input/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label="Логин" name="login" rules={profileFormRules.login}>
|
<Form.Item label="Логин" name="login" rules={profileFormRules.login}>
|
||||||
<Input disabled />
|
<Input disabled/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item>
|
<Form.Item>
|
||||||
<Space>
|
<Space>
|
||||||
@ -105,14 +121,16 @@ const ProfilePage = () => {
|
|||||||
style={formStyle}
|
style={formStyle}
|
||||||
>
|
>
|
||||||
<Typography.Title level={4}>Смена пароля</Typography.Title>
|
<Typography.Title level={4}>Смена пароля</Typography.Title>
|
||||||
<Form.Item label="Текущий пароль" name="current_password" rules={passwordFormRules.current_password}>
|
<Form.Item label="Текущий пароль" name="current_password"
|
||||||
<Input.Password />
|
rules={passwordFormRules.current_password}>
|
||||||
|
<Input.Password/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label="Новый пароль" name="new_password" rules={passwordFormRules.new_password}>
|
<Form.Item label="Новый пароль" name="new_password" rules={passwordFormRules.new_password}>
|
||||||
<Input.Password />
|
<Input.Password/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label="Подтвердите пароль" name="confirm_password" rules={passwordFormRules.confirm_password}>
|
<Form.Item label="Подтвердите пароль" name="confirm_password"
|
||||||
<Input.Password />
|
rules={passwordFormRules.confirm_password}>
|
||||||
|
<Input.Password/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item>
|
<Form.Item>
|
||||||
<Space>
|
<Space>
|
||||||
|
|||||||
@ -1,7 +1,11 @@
|
|||||||
import { useGetAuthenticatedUserDataQuery } from "../../../Api/usersApi.js";
|
import {
|
||||||
import { useDispatch } from "react-redux";
|
useChangePasswordMutation,
|
||||||
import { openEditProfileModal, closeEditProfileModal } from "../../../Redux/Slices/usersSlice.js";
|
useGetAuthenticatedUserDataQuery,
|
||||||
import { notification } from "antd";
|
useUpdateUserMutation
|
||||||
|
} from "../../../Api/usersApi.js";
|
||||||
|
import {useDispatch} from "react-redux";
|
||||||
|
import {openEditProfileModal, closeEditProfileModal} from "../../../Redux/Slices/usersSlice.js";
|
||||||
|
import {notification} from "antd";
|
||||||
|
|
||||||
const useProfilePage = () => {
|
const useProfilePage = () => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
@ -14,14 +18,9 @@ const useProfilePage = () => {
|
|||||||
pollingInterval: 20000,
|
pollingInterval: 20000,
|
||||||
});
|
});
|
||||||
|
|
||||||
// const [updateUserProfile, { isLoading: isUpdatingProfile }] = useUpdateUserProfileMutation();
|
const [updateUserProfile, {isLoading: isUpdatingProfile}] = useUpdateUserMutation();
|
||||||
const updateUserProfile = () => {};
|
|
||||||
const isUpdatingProfile = false;
|
|
||||||
const isErrorUpdatingProfile = false;
|
|
||||||
|
|
||||||
// const [changePassword, { isLoading: isChangingPassword }] = useChangePasswordMutation();
|
const [changePassword, {isLoading: isChangingPassword}] = useChangePasswordMutation();
|
||||||
const changePassword = () => {};
|
|
||||||
const isChangingPassword = false;
|
|
||||||
|
|
||||||
const handleEditProfile = () => {
|
const handleEditProfile = () => {
|
||||||
dispatch(openEditProfileModal());
|
dispatch(openEditProfileModal());
|
||||||
@ -37,8 +36,9 @@ const useProfilePage = () => {
|
|||||||
first_name: values.first_name,
|
first_name: values.first_name,
|
||||||
last_name: values.last_name,
|
last_name: values.last_name,
|
||||||
patronymic: values.patronymic || null,
|
patronymic: values.patronymic || null,
|
||||||
|
login: userData.login,
|
||||||
};
|
};
|
||||||
await updateUserProfile(profileData).unwrap();
|
await updateUserProfile({userId: userData.id, ...profileData}).unwrap();
|
||||||
notification.success({
|
notification.success({
|
||||||
message: "Успех",
|
message: "Успех",
|
||||||
description: "Профиль успешно обновлен.",
|
description: "Профиль успешно обновлен.",
|
||||||
@ -60,6 +60,7 @@ const useProfilePage = () => {
|
|||||||
current_password: values.current_password,
|
current_password: values.current_password,
|
||||||
new_password: values.new_password,
|
new_password: values.new_password,
|
||||||
confirm_password: values.confirm_password,
|
confirm_password: values.confirm_password,
|
||||||
|
user_id: userData.id,
|
||||||
};
|
};
|
||||||
await changePassword(passwordData).unwrap();
|
await changePassword(passwordData).unwrap();
|
||||||
notification.success({
|
notification.success({
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { Grid } from "antd";
|
import {Form, Grid} from "antd";
|
||||||
|
|
||||||
const { useBreakpoint } = Grid;
|
const { useBreakpoint } = Grid;
|
||||||
|
|
||||||
@ -10,6 +10,9 @@ const useProfilePageUI = () => {
|
|||||||
const buttonStyle = { width: screens.xs ? "100%" : "auto" };
|
const buttonStyle = { width: screens.xs ? "100%" : "auto" };
|
||||||
const formStyle = { maxWidth: 600 };
|
const formStyle = { maxWidth: 600 };
|
||||||
|
|
||||||
|
const [profileForm] = Form.useForm();
|
||||||
|
const [passwordForm] = Form.useForm();
|
||||||
|
|
||||||
const profileFormRules = {
|
const profileFormRules = {
|
||||||
first_name: [
|
first_name: [
|
||||||
{ required: true, message: "Пожалуйста, введите имя" },
|
{ required: true, message: "Пожалуйста, введите имя" },
|
||||||
@ -56,6 +59,8 @@ const useProfilePageUI = () => {
|
|||||||
formStyle,
|
formStyle,
|
||||||
profileFormRules,
|
profileFormRules,
|
||||||
passwordFormRules,
|
passwordFormRules,
|
||||||
|
profileForm,
|
||||||
|
passwordForm,
|
||||||
isMobile: screens.xs,
|
isMobile: screens.xs,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user