feat: Обновление профиля и смена пароля пользователя

Добавлены модальные формы для редактирования профиля и смены пароля.
Обновлены API запросы для работы с данными пользователя.
This commit is contained in:
Андрей Дувакин 2025-06-03 19:13:04 +05:00
parent 78c654422f
commit 827cfb413a
21 changed files with 337 additions and 322 deletions

View File

@ -7,7 +7,7 @@ export const appointmentsApi = createApi({
tagTypes: ['Appointment'],
endpoints: (builder) => ({
getAppointments: builder.query({
query: () => '/appointments/',
query: (doctor_id) => `/appointments/doctor/${doctor_id}/`,
providesTags: ['Appointment'],
refetchOnMountOrArgChange: 5,
}),

View File

@ -7,7 +7,7 @@ export const scheduledAppointmentsApi = createApi({
tagTypes: ['ScheduledAppointment'],
endpoints: (builder) => ({
getScheduledAppointments: builder.query({
query: () => `/scheduled_appointments/`,
query: (doctor_id) => `/scheduled_appointments/doctor/${doctor_id}/`,
providesTags: ['ScheduledAppointment'],
}),
createScheduledAppointment: builder.mutation({

View File

@ -19,7 +19,7 @@ export const usersApi = createApi({
}),
changePassword: builder.mutation({
query: (data) => ({
url: "/users/change-password",
url: "/users/change-password/",
method: "POST",
body: data,
}),

View File

@ -1,10 +1,15 @@
import { Navigate, Outlet } from "react-router-dom";
import { useSelector } from "react-redux";
import LoadingIndicator from "../Components/Widgets/LoadingIndicator/LoadingIndicator.jsx";
const PrivateRoute = () => {
const {user} = useSelector((state) => state.auth);
const { user, userData, isLoading } = useSelector((state) => state.auth);
if (!user) {
if (isLoading) {
return <LoadingIndicator />;
}
if (!user || !userData) {
return <Navigate to="/login" />;
}

View File

@ -9,7 +9,12 @@ import { Grid } from "antd";
const { useBreakpoint } = Grid;
const useAppointmentFormModalUI = (createAppointment, patients, cancelAppointment, useGetByPatientIdQuery) => {
const dispatch = useDispatch();
const dispatch = useDispatch()
const {
userData
} = useSelector((state) => state.auth);
const { modalVisible, scheduledData } = useSelector((state) => state.appointmentsUI);
const [form] = Form.useForm();
const screens = useBreakpoint();
@ -24,7 +29,7 @@ const useAppointmentFormModalUI = (createAppointment, patients, cancelAppointmen
const [searchPreviousAppointments, setSearchPreviousAppointments] = useState("");
const editor = useRef(null);
const { data: appointments = [] } = useGetAppointmentsQuery(undefined, {
const { data: appointments = [] } = useGetAppointmentsQuery((userData.id), {
pollingInterval: 20000,
});

View File

@ -0,0 +1,96 @@
import {Button, Form, Input, Modal, Select, Space, Typography} from "antd";
import useUpdateUserModalFormUI from "./useUpdateUserModalFormUI.js";
import useUpdateUserModalForm from "./useUpdateUserModalForm.js";
const UpdateUserModalForm = () => {
const editProfileModalData = useUpdateUserModalForm();
const editProfileModalUI = useUpdateUserModalFormUI(editProfileModalData.updateUser, editProfileModalData.updatePassword);
return (
<Modal
title="Редактировать профиль"
open={editProfileModalUI.isModalOpen}
onCancel={editProfileModalUI.handleCancel}
footer={null}
>
<Form
form={editProfileModalUI.userForm}
layout="vertical"
onFinish={editProfileModalUI.handleUserSubmit}
>
<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>
{editProfileModalUI.isCurrentUser && (
<Form.Item
name="role_id"
label="Роль"
rules={[{required: true, message: "Выберите роль"}]}
>
<Select>
{editProfileModalData.roles.map((role) => (
<Select.Option key={role.id} value={role.id}>
{role.title}
</Select.Option>
))}
</Select>
</Form.Item>
)}
<Form.Item label="Логин" name="login" rules={[{required: true, message: "Введите логин"}]}>
<Input disabled/>
</Form.Item>
<Form.Item>
<Space>
<Button type="primary" htmlType="submit" loading={editProfileModalData.isUserUpdating}>
Сохранить
</Button>
<Button onClick={editProfileModalUI.handleCancel}>Отмена</Button>
</Space>
</Form.Item>
</Form>
<Form
form={editProfileModalUI.passwordForm}
layout="vertical"
onFinish={editProfileModalUI.handlePasswordSubmit}
>
<Typography.Title level={4}>Смена пароля</Typography.Title>
<Form.Item label="Новый пароль" name="new_password"
rules={[{required: true, message: "Введите пароль"}]}>
<Input.Password/>
</Form.Item>
<Form.Item label="Подтвердите пароль" name="confirm_password"
rules={[
{required: true, message: "Подтвердите пароль"},
({getFieldValue}) => ({
validator(_, value) {
if (value && value !== getFieldValue("new_password")) {
return Promise.reject("Пароли не совпадают");
}
return Promise.resolve();
},
}),
]}>
<Input.Password/>
</Form.Item>
<Form.Item>
<Space>
<Button type="primary" htmlType="submit" loading={editProfileModalData.isPasswordUpdating}>
Изменить пароль
</Button>
<Button onClick={editProfileModalUI.resetForms}>Сбросить</Button>
</Space>
</Form.Item>
</Form>
</Modal>
)
};
export default UpdateUserModalForm;

View File

@ -0,0 +1,28 @@
import {useChangePasswordMutation, useUpdateUserMutation} from "../../../Api/usersApi.js";
import {useGetRolesQuery} from "../../../Api/rolesApi.js";
const useUpdateUserModalForm = () => {
const {
data: roles = [],
isLoading: isLoadingRoles,
isError: isErrorRoles,
} = useGetRolesQuery(undefined, {
pollingInterval: 60000,
});
const [updateUser, {isLoading: isUserUpdating}] = useUpdateUserMutation();
const [updatePassword, {isLoading: isPasswordUpdating}] = useChangePasswordMutation();
return {
roles,
isLoadingRoles,
isErrorRoles,
updateUser,
updatePassword,
isUserUpdating,
isPasswordUpdating,
};
};
export default useUpdateUserModalForm;

View File

@ -0,0 +1,89 @@
import {useDispatch, useSelector} from "react-redux";
import {setSelectedUser} from "../../../Redux/Slices/usersSlice.js";
import {Form, notification} from "antd";
import {useEffect} from "react";
const useUpdateUserModalFormUI = (updateUser, updatePassword) => {
const dispatch = useDispatch();
const [userForm] = Form.useForm();
const [passwordForm] = Form.useForm();
const {
selectedUser,
isCurrentUser,
} = useSelector((state) => state.usersUI);
useEffect(() => {
if (selectedUser) {
userForm.setFieldsValue(selectedUser);
}
}, [selectedUser, userForm]);
const isModalOpen = selectedUser !== null;
const handleCancel = () => {
dispatch(setSelectedUser(null));
};
const handleUserSubmit = async () => {
const values = userForm.getFieldsValue();
try {
await updateUser({userId: selectedUser.id, ...values}).unwrap();
notification.success({
message: "Успех",
description: "Профиль успешно обновлен.",
placement: "topRight",
});
userForm.resetFields();
dispatch(setSelectedUser(null));
} catch (error) {
notification.error({
message: "Ошибка",
description: error?.data?.detail || "Не удалось обновить профиль.",
placement: "topRight",
});
}
};
const handlePasswordSubmit = async () => {
const values = passwordForm.getFieldsValue();
try {
await updatePassword({user_id: selectedUser.id, ...values}).unwrap();
notification.success({
message: "Успех",
description: "Пароль успешно обновлен.",
placement: "topRight",
});
passwordForm.resetFields();
dispatch(setSelectedUser(null));
} catch (error) {
notification.error({
message: "Ошибка",
description: error?.data?.detail || "Не удалось обновить пароль.",
placement: "topRight",
});
}
};
const resetForms = () => {
passwordForm.resetFields();
};
return {
isModalOpen,
userForm,
passwordForm,
isCurrentUser,
handleCancel,
handleUserSubmit,
handlePasswordSubmit,
resetForms,
};
};
export default useUpdateUserModalFormUI;

View File

@ -1,14 +1,7 @@
import {useGetAuthenticatedUserDataQuery} from "../../Api/usersApi.js";
import { useSelector } from "react-redux";
const useMainLayout = () => {
const {
data: user,
isLoading: isUserLoading,
isError: isUserError,
} = useGetAuthenticatedUserDataQuery(undefined, {
pollingInterval: 20000,
});
const { userData: user, isLoading: isUserLoading, error: isUserError } = useSelector((state) => state.auth);
return {
user,

View File

@ -1,18 +1,15 @@
import {useState} from "react";
import {Table, Typography, Button, Modal, Form, Input, Select, Alert, Result} from "antd";
import {Table, Button, Result, Typography} from "antd";
import {ControlOutlined} from "@ant-design/icons";
import LoadingIndicator from "../../Widgets/LoadingIndicator/LoadingIndicator.jsx";
import useAdminPage from "./useAdminPage.js";
import useAdminPageUI from "./useAdminPageUI.js";
import CreateUserModalForm from "./Components/CreateUserModalForm/CreateUserModalForm.jsx";
import UpdateUserModalForm from "../../Dummies/UpdateUserModalForm/UpdateUserModalForm.jsx";
const {Title} = Typography;
const {Option} = Select;
const AdminPage = () => {
const adminPageData = useAdminPage();
const adminPageUI = useAdminPageUI();
const [errorMessage, setErrorMessage] = useState(null);
const columns = [
{
@ -55,35 +52,6 @@ const AdminPage = () => {
},
];
const handleEditSubmit = async (values) => {
try {
await adminPageData.updateUser({
id: adminPageUI.selectedUser.id,
fullName: values.fullName,
password: values.password || undefined, // Отправляем пароль только если он указан
role: {title: values.role},
});
adminPageUI.closeEditModal();
setErrorMessage(null);
} catch (error) {
setErrorMessage("Ошибка при обновлении пользователя");
}
};
const handleCreateSubmit = async (values) => {
try {
await adminPageData.createUser({
fullName: values.fullName,
email: values.email,
password: values.password,
role: {title: values.role},
});
adminPageUI.closeCreateModal();
setErrorMessage(null);
} catch (error) {
setErrorMessage("Ошибка при создании пользователя");
}
};
if (adminPageData.isError) {
return <Result status="500" title="500" subTitle="Произошла ошибка при загрузке данных"/>
@ -95,9 +63,9 @@ const AdminPage = () => {
<LoadingIndicator/>
) : (
<>
<Title level={1}>
<Typography.Title level={1}>
<ControlOutlined/> Панель администратора
</Title>
</Typography.Title>
<Button
type="primary"
onClick={adminPageUI.openCreateModal}
@ -114,6 +82,7 @@ const AdminPage = () => {
/>
<CreateUserModalForm/>
<UpdateUserModalForm/>
</>
)}
</div>

View File

@ -18,7 +18,6 @@ const useCreateUserModalFormUI = (registerUser) => {
const values = form.getFieldsValue();
try {
console.log(values);
await registerUser(values).unwrap();
notification.success({
message: "Пользователь зарегистрирован",

View File

@ -2,6 +2,7 @@ import {useState} from "react";
import {Grid, Form} from "antd";
import {useDispatch} from "react-redux";
import {openModal} from "../../../Redux/Slices/adminSlice.js";
import {setIsCurrentUser, setSelectedUser} from "../../../Redux/Slices/usersSlice.js";
const {useBreakpoint} = Grid;
@ -9,49 +10,23 @@ const useAdminPageUI = () => {
const dispatch = useDispatch();
const screens = useBreakpoint();
const [editModalVisible, setEditModalVisible] = useState(false);
const [createModalVisible, setCreateModalVisible] = useState(false);
const [selectedUser, setSelectedUser] = useState(null);
const [editForm] = Form.useForm();
const [createForm] = Form.useForm();
const containerStyle = {padding: screens.xs ? 16 : 24};
const openEditModal = (user) => {
setSelectedUser(user);
editForm.setFieldsValue({
fullName: user.fullName,
role: user.role?.title || "",
});
setEditModalVisible(true);
};
const closeEditModal = () => {
setEditModalVisible(false);
setSelectedUser(null);
editForm.resetFields();
dispatch(setSelectedUser(user));
dispatch(setIsCurrentUser(true));
};
const openCreateModal = () => {
dispatch(openModal());
};
const closeCreateModal = () => {
setCreateModalVisible(false);
createForm.resetFields();
};
return {
containerStyle,
editModalVisible,
createModalVisible,
selectedUser,
editForm,
createForm,
openEditModal,
closeEditModal,
openCreateModal,
closeCreateModal,
};
};

View File

@ -3,13 +3,18 @@ import { useGetScheduledAppointmentsQuery } from "../../../Api/scheduledAppointm
import {useGetPatientsQuery} from "../../../Api/patientsApi.js";
import {notification} from "antd";
import {useEffect} from "react";
import {useSelector} from "react-redux";
const useAppointments = () => {
const {
userData
} = useSelector(state => state.auth);
const {
data: appointments = [],
isLoading: isLoadingAppointments,
isError: isErrorAppointments,
} = useGetAppointmentsQuery(undefined, {
} = useGetAppointmentsQuery((userData.id), {
pollingInterval: 20000,
});
@ -17,7 +22,7 @@ const useAppointments = () => {
data: scheduledAppointments = [],
isLoading: isLoadingScheduledAppointments,
isError: isErrorScheduledAppointments,
} = useGetScheduledAppointmentsQuery(undefined, {
} = useGetScheduledAppointmentsQuery((userData.id), {
pollingInterval: 20000,
});

View File

@ -3,7 +3,7 @@ import {useGetScheduledAppointmentsQuery} from "../../../Api/scheduledAppointmen
import {useGetPatientsQuery} from "../../../Api/patientsApi.js";
import {notification} from "antd";
import {useEffect} from "react";
import {useDispatch} from "react-redux";
import {useDispatch, useSelector} from "react-redux";
import {
setSelectedAppointment,
setSelectedScheduledAppointment,
@ -22,11 +22,15 @@ dayjs.extend(isBetween); // Extend dayjs with isBetween
const useHomePage = () => {
const dispatch = useDispatch();
const {
userData
} = useSelector((state) => state.auth);
const {
data: appointments = [],
isLoading: isLoadingAppointments,
isError: isErrorAppointments,
} = useGetAppointmentsQuery(undefined, {
} = useGetAppointmentsQuery((userData.id), {
pollingInterval: 20000,
});
@ -34,7 +38,7 @@ const useHomePage = () => {
data: scheduledAppointments = [],
isLoading: isLoadingScheduledAppointments,
isError: isErrorScheduledAppointments,
} = useGetScheduledAppointmentsQuery(undefined, {
} = useGetScheduledAppointmentsQuery((userData.id), {
pollingInterval: 20000,
});

View File

@ -2,6 +2,7 @@ import { useDispatch } from "react-redux";
import { notification } from "antd";
import { setError, setUser } from "../../../Redux/Slices/authSlice.js";
import { useLoginMutation } from "../../../Api/authApi.js";
import { checkAuth } from "../../../Redux/Slices/authSlice.js";
const useLoginPage = () => {
const dispatch = useDispatch();
@ -16,6 +17,8 @@ const useLoginPage = () => {
}
localStorage.setItem("access_token", token);
dispatch(setUser({ token }));
await dispatch(checkAuth()).unwrap();
} catch (error) {
const errorMessage = error?.data?.detail || "Не удалось войти";
dispatch(setError(errorMessage));

View File

@ -1,4 +1,4 @@
import { useEffect } from "react";
import {useEffect, useRef} from "react";
import {useNavigate} from "react-router-dom";
import {useSelector} from "react-redux";
import {Grid} from "antd";
@ -7,8 +7,9 @@ const { useBreakpoint } = Grid;
const useLoginPageUI = () => {
const navigate = useNavigate();
const { user } = useSelector((state) => state.auth);
const {user, userData, isLoading} = useSelector((state) => state.auth);
const screens = useBreakpoint();
const hasRedirected = useRef(false);
const containerStyle = {
minHeight: "100vh",
@ -40,11 +41,12 @@ const useLoginPageUI = () => {
};
useEffect(() => {
if (user) {
if (user && userData && !isLoading && !hasRedirected.current) {
hasRedirected.current = true;
navigate("/");
}
document.title = labels.title;
}, [user, navigate]);
}, [user, userData, isLoading, navigate]);
return {
containerStyle,

View File

@ -1,32 +1,23 @@
import {Button, Card, Col, Form, Input, Modal, Row, Space, Typography, Result} from "antd";
import {Button, Card, Col, Row, Typography, Result} from "antd";
import {EditOutlined, UserOutlined} from "@ant-design/icons";
import LoadingIndicator from "../../Widgets/LoadingIndicator/LoadingIndicator.jsx";
import useProfilePage from "./useProfilePage.js";
import useProfilePageUI from "./useProfilePageUI.js";
import {useSelector} from "react-redux";
import UpdateUserModalForm from "../../Dummies/UpdateUserModalForm/UpdateUserModalForm.jsx";
const ProfilePage = () => {
const {
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);
handleEditUser,
} = useProfilePageUI(userData);
if (isError) {
return (
@ -70,81 +61,14 @@ const ProfilePage = () => {
<Button
type="primary"
icon={<EditOutlined/>}
onClick={handleEditProfile}
onClick={handleEditUser}
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>
<UpdateUserModalForm/>
</>
)}
</div>

View File

@ -1,15 +1,8 @@
import {
useChangePasswordMutation,
useGetAuthenticatedUserDataQuery,
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 dispatch = useDispatch();
const {
data: userData = {},
isLoading: isLoadingUserData,
@ -18,75 +11,10 @@ const useProfilePage = () => {
pollingInterval: 20000,
});
const [updateUserProfile, {isLoading: isUpdatingProfile}] = useUpdateUserMutation();
const [changePassword, {isLoading: isChangingPassword}] = useChangePasswordMutation();
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,
login: userData.login,
};
await updateUserProfile({userId: userData.id, ...profileData}).unwrap();
notification.success({
message: "Успех",
description: "Профиль успешно обновлен.",
placement: "topRight",
});
dispatch(closeEditProfileModal());
} catch (error) {
notification.error({
message: "Ошибка",
description: error?.data?.detail || "Не удалось обновить профиль.",
placement: "topRight",
});
}
};
const handleSubmitPassword = async (values) => {
try {
const passwordData = {
current_password: values.current_password,
new_password: values.new_password,
confirm_password: values.confirm_password,
user_id: userData.id,
};
await changePassword(passwordData).unwrap();
notification.success({
message: "Успех",
description: "Пароль успешно изменен.",
placement: "topRight",
});
dispatch(closeEditProfileModal());
} catch (error) {
notification.error({
message: "Ошибка",
description: error?.data?.detail || "Не удалось изменить пароль.",
placement: "topRight",
});
}
};
return {
userData,
isLoading: isLoadingUserData,
isError: isErrorUserData,
isUpdating: isUpdatingProfile || isChangingPassword,
handleEditProfile,
handleCancelEdit,
handleSubmitProfile,
handleSubmitPassword,
};
};

View File

@ -1,67 +1,28 @@
import {Form, Grid} from "antd";
import {Grid} from "antd";
import {useDispatch} from "react-redux";
import {setSelectedUser} from "../../../Redux/Slices/usersSlice.js";
const { useBreakpoint } = Grid;
const useProfilePageUI = () => {
const useProfilePageUI = (userData) => {
const dispatch = useDispatch();
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 [profileForm] = Form.useForm();
const [passwordForm] = Form.useForm();
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("Пароли не совпадают"));
},
}),
],
const handleEditUser = () => {
dispatch(setSelectedUser(userData))
};
return {
containerStyle,
cardStyle,
buttonStyle,
formStyle,
profileFormRules,
passwordFormRules,
profileForm,
passwordForm,
isMobile: screens.xs,
handleEditUser,
};
};

View File

@ -1,15 +1,27 @@
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
import { usersApi } from "../../Api/usersApi.js";
export const checkAuth = createAsyncThunk("auth/checkAuth", async () => {
export const checkAuth = createAsyncThunk("auth/checkAuth", async (_, { dispatch, rejectWithValue }) => {
try {
const token = localStorage.getItem("access_token");
if (token) {
return { token };
if (!token) {
return rejectWithValue("No token found");
}
const userData = await dispatch(
usersApi.endpoints.getAuthenticatedUserData.initiate(undefined, { forceRefetch: true })
).unwrap();
return { token, userData };
} catch (error) {
localStorage.removeItem("access_token");
return rejectWithValue(error?.data?.detail || "Failed to authenticate");
}
return null;
});
const initialState = {
user: null,
userData: null,
isLoading: true,
error: null,
};
@ -29,21 +41,34 @@ const authSlice = createSlice({
},
logout(state) {
state.user = null;
state.userData = null;
state.error = null;
state.isLoading = false;
localStorage.removeItem("access_token");
},
setUserData(state, action) {
state.userData = action.payload;
},
},
extraReducers: (builder) => {
builder
.addCase(checkAuth.pending, (state) => {
state.isLoading = true;
state.error = null;
})
.addCase(checkAuth.fulfilled, (state, action) => {
state.user = action.payload;
state.user = { token: action.payload.token };
state.userData = action.payload.userData;
state.isLoading = false;
})
.addCase(checkAuth.rejected, (state) => {
.addCase(checkAuth.rejected, (state, action) => {
state.user = null;
state.userData = null;
state.isLoading = false;
state.error = action.payload;
});
},
});
export const { setUser, setError, logout } = authSlice.actions;
export const { setUser, setError, logout, setUserData } = authSlice.actions;
export default authSlice.reducer;

View File

@ -1,22 +1,26 @@
import {createSlice} from "@reduxjs/toolkit";
const initialState = {
editProfileModalVisible: false,
selectedUser: null,
isCurrentUser: false
};
const usersSlice = createSlice({
name: "usersUI",
initialState,
reducers: {
openEditProfileModal(state) {
state.editProfileModalVisible = true;
setSelectedUser(state, action) {
state.selectedUser = action.payload;
},
closeEditProfileModal(state) {
state.editProfileModalVisible = false;
setIsCurrentUser(state, action) {
state.isCurrentUser = action.payload;
},
},
});
export const { openEditProfileModal, closeEditProfileModal } = usersSlice.actions;
export const {
setSelectedUser,
setIsCurrentUser,
} = usersSlice.actions;
export default usersSlice.reducer;