+
+ {labels.title}
+
- {error &&
{error}
}
+ {error && (
+
+ {labels.errorPrefix}
+ {error}
+
+ )}
-
-
+
-
+
-
@@ -77,4 +52,4 @@ const LoginPage = () => {
);
};
-export default LoginPage;
+export default LoginPage;
\ No newline at end of file
diff --git a/web-app/src/Components/Pages/LoginPage/useLoginPage.js b/web-app/src/Components/Pages/LoginPage/useLoginPage.js
new file mode 100644
index 0000000..b6d3917
--- /dev/null
+++ b/web-app/src/Components/Pages/LoginPage/useLoginPage.js
@@ -0,0 +1,36 @@
+import { useDispatch } from "react-redux";
+import { notification } from "antd";
+import {setError, setUser} from "../../../Redux/Slices/authSlice.js";
+import {useLoginMutation} from "../../../Api/authApi.js";
+
+const useLoginPage = () => {
+ const dispatch = useDispatch();
+ const [loginUser, { isLoading }] = useLoginMutation();
+
+ const onFinish = async (loginData) => {
+ try {
+ const response = await loginUser(loginData).unwrap();
+ const token = response.access_token || response.token;
+ if (!token) {
+ throw new Error("Токен не получен от сервера");
+ }
+ localStorage.setItem("access_token", token);
+ dispatch(setUser({ token }));
+ } catch (error) {
+ const errorMessage = error?.data?.message || "Не удалось войти";
+ dispatch(setError(errorMessage));
+ notification.error({
+ message: "Ошибка при входе",
+ description: errorMessage,
+ placement: "topRight",
+ });
+ }
+ };
+
+ return {
+ onFinish,
+ isLoading,
+ };
+};
+
+export default useLoginPage;
\ No newline at end of file
diff --git a/web-app/src/Components/Pages/LoginPage/useLoginPageUI.js b/web-app/src/Components/Pages/LoginPage/useLoginPageUI.js
new file mode 100644
index 0000000..cf40404
--- /dev/null
+++ b/web-app/src/Components/Pages/LoginPage/useLoginPageUI.js
@@ -0,0 +1,58 @@
+import { useEffect } from "react";
+import { useNavigate } from "react-router-dom";
+import { useSelector } from "react-redux";
+import { Grid } from "antd";
+
+const { useBreakpoint } = Grid;
+
+const useLoginPageUI = () => {
+ const navigate = useNavigate();
+ const { user } = useSelector((state) => state.auth);
+ const screens = useBreakpoint();
+
+ const containerStyle = {
+ minHeight: "100vh",
+ };
+
+ const formContainerStyle = {
+ padding: screens.xs ? 10 : 20,
+ border: "1px solid #ddd",
+ borderRadius: 8,
+ };
+
+ const titleStyle = {
+ textAlign: "center",
+ };
+
+ const errorStyle = {
+ color: "red",
+ marginBottom: 15,
+ };
+
+ const labels = {
+ title: "Авторизация",
+ loginPlaceholder: "Логин",
+ passwordPlaceholder: "Пароль",
+ submitButton: "Войти",
+ loginRequired: "Пожалуйста, введите логин",
+ passwordRequired: "Пожалуйста, введите пароль",
+ errorPrefix: "Ошибка при входе: ",
+ };
+
+ useEffect(() => {
+ if (user) {
+ navigate("/");
+ }
+ document.title = labels.title;
+ }, [user, navigate]);
+
+ return {
+ containerStyle,
+ formContainerStyle,
+ titleStyle,
+ errorStyle,
+ labels,
+ };
+};
+
+export default useLoginPageUI;
\ No newline at end of file
diff --git a/web-app/src/Hooks/AuthContext.jsx b/web-app/src/Hooks/AuthContext.jsx
deleted file mode 100644
index 4b8352f..0000000
--- a/web-app/src/Hooks/AuthContext.jsx
+++ /dev/null
@@ -1,67 +0,0 @@
-import {createContext, useState, useContext, useEffect} from "react";
-import PropTypes from "prop-types";
-import loginUser from "../old_api/auth/loginRequest.js";
-import {Spin} from "antd";
-import {useNavigate} from "react-router-dom";
-import createApi from "../Core/axiosConfig.js";
-
-const AuthContext = createContext(undefined);
-
-const AuthProvider = ({children}) => {
- const [user, setUser] = useState(null);
- const [isLoading, setIsLoading] = useState(true);
- const navigate = useNavigate();
-
- useEffect(() => {
- const token = localStorage.getItem("access_token");
- if (token) {
- setUser({token});
- }
- setIsLoading(false);
- }, []);
-
-
- const login = async (loginData) => {
- try {
- const token = await loginUser(loginData, api);
- localStorage.setItem("access_token", token);
- setUser({token});
- } catch (error) {
- console.error("Login failed", error);
- throw error;
- }
- };
-
- const logoutAndRedirect = () => {
- localStorage.removeItem("access_token");
- setUser(null);
- navigate("/login");
- };
-
- const logout = () => {
- localStorage.removeItem("access_token");
- setUser(null);
- };
-
- if (isLoading) {
- return
;
- }
-
- const api = createApi(logoutAndRedirect);
-
- return (
-
- {children}
-
- );
-};
-
-AuthProvider.propTypes = {
- children: PropTypes.node.isRequired,
-};
-
-const useAuth = () => {
- return useContext(AuthContext);
-};
-
-export {useAuth, AuthProvider};
\ No newline at end of file
diff --git a/web-app/src/Hooks/useAuthUtils.js b/web-app/src/Hooks/useAuthUtils.js
new file mode 100644
index 0000000..20c9d85
--- /dev/null
+++ b/web-app/src/Hooks/useAuthUtils.js
@@ -0,0 +1,18 @@
+import { useDispatch } from "react-redux";
+import { useNavigate } from "react-router-dom";
+import { logout } from "../Redux/Slices/authSlice.js";
+
+const useAuthUtils = () => {
+ const dispatch = useDispatch();
+ const navigate = useNavigate();
+
+ const logoutAndRedirect = () => {
+ localStorage.removeItem("access_token");
+ dispatch(logout());
+ navigate("/login");
+ };
+
+ return { logoutAndRedirect };
+};
+
+export default useAuthUtils;
\ No newline at end of file
diff --git a/web-app/src/Redux/Slices/authSlice.js b/web-app/src/Redux/Slices/authSlice.js
new file mode 100644
index 0000000..f327c5d
--- /dev/null
+++ b/web-app/src/Redux/Slices/authSlice.js
@@ -0,0 +1,49 @@
+import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
+
+export const checkAuth = createAsyncThunk("auth/checkAuth", async () => {
+ const token = localStorage.getItem("access_token");
+ if (token) {
+ return { token };
+ }
+ return null;
+});
+
+const initialState = {
+ user: null,
+ isLoading: true,
+ error: null,
+};
+
+const authSlice = createSlice({
+ name: "auth",
+ initialState,
+ reducers: {
+ setUser(state, action) {
+ state.user = action.payload;
+ state.isLoading = false;
+ state.error = null;
+ },
+ setError(state, action) {
+ state.error = action.payload;
+ state.isLoading = false;
+ },
+ logout(state) {
+ state.user = null;
+ state.error = null;
+ state.isLoading = false;
+ },
+ },
+ extraReducers: (builder) => {
+ builder
+ .addCase(checkAuth.fulfilled, (state, action) => {
+ state.user = action.payload;
+ state.isLoading = false;
+ })
+ .addCase(checkAuth.rejected, (state) => {
+ state.isLoading = false;
+ });
+ },
+});
+
+export const { setUser, setError, logout } = authSlice.actions;
+export default authSlice.reducer;
\ No newline at end of file
diff --git a/web-app/src/Redux/store.js b/web-app/src/Redux/store.js
index ed47a43..9c0d86d 100644
--- a/web-app/src/Redux/store.js
+++ b/web-app/src/Redux/store.js
@@ -15,6 +15,8 @@ import {scheduledAppointmentsApi} from "../Api/scheduledAppointmentsApi.js";
import {appointmentTypesApi} from "../Api/appointmentTypesApi.js";
import {usersApi} from "../Api/usersApi.js";
import usersReducer from "./Slices/usersSlice.js";
+import {authApi} from "../Api/authApi.js";
+import authReducer from "./Slices/authSlice.js";
export const store = configureStore({
reducer: {
@@ -42,7 +44,10 @@ export const store = configureStore({
[appointmentTypesApi.reducerPath]: appointmentTypesApi.reducer,
[usersApi.reducerPath]: usersApi.reducer,
- usersUI: usersReducer
+ usersUI: usersReducer,
+
+ auth: authReducer,
+ [authApi.reducerPath]: authApi.reducer,
},
middleware: (getDefaultMiddleware) => (
getDefaultMiddleware().concat(
@@ -56,6 +61,7 @@ export const store = configureStore({
scheduledAppointmentsApi.middleware,
appointmentTypesApi.middleware,
usersApi.middleware,
+ authApi.middleware,
)
),
});
diff --git a/web-app/src/old_api/auth/loginRequest.js b/web-app/src/old_api/auth/loginRequest.js
deleted file mode 100644
index d0b6249..0000000
--- a/web-app/src/old_api/auth/loginRequest.js
+++ /dev/null
@@ -1,14 +0,0 @@
-import CONFIG from "../../Core/сonfig.js";
-
-const loginUser = async (loginData, api) => {
- const response = await api.post(`${CONFIG.BASE_URL}/login/`, loginData, {
- withCredentials: true,
- headers: {
- 'Content-Type': 'application/json'
- }
- });
-
- return response.data.access_token;
-};
-
-export default loginUser;
\ No newline at end of file