написал страницу авторизации
This commit is contained in:
@@ -2,5 +2,5 @@ import { useDispatch, useSelector } from 'react-redux';
|
|||||||
import type {TypedUseSelectorHook} from 'react-redux';
|
import type {TypedUseSelectorHook} from 'react-redux';
|
||||||
import type { RootState, AppDispatch } from './store';
|
import type { RootState, AppDispatch } from './store';
|
||||||
|
|
||||||
export const useAppDispatch: () => AppDispatch = useDispatch;
|
export const useAppDispatch = () => useDispatch<AppDispatch>();
|
||||||
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
|
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
|
||||||
@@ -8,7 +8,7 @@ import { Route } from "react-router";
|
|||||||
function AppRoutes() {
|
function AppRoutes() {
|
||||||
return (
|
return (
|
||||||
<Routes>
|
<Routes>
|
||||||
{/*<Route path="/login" element={<LoginPage />} />*/}
|
<Route path="/login" element={<LoginPage />} />
|
||||||
<Route path="/" element={<Layout />}>
|
<Route path="/" element={<Layout />}>
|
||||||
<Route index element={<DashboardPage />} />
|
<Route index element={<DashboardPage />} />
|
||||||
<Route path="history" element={<HistoryPage />} />
|
<Route path="history" element={<HistoryPage />} />
|
||||||
|
|||||||
@@ -22,7 +22,10 @@ const authSlice = createSlice({
|
|||||||
state.token = null;
|
state.token = null;
|
||||||
state.error = null;
|
state.error = null;
|
||||||
localStorage.removeItem("token");
|
localStorage.removeItem("token");
|
||||||
}
|
},
|
||||||
|
clearError: (state) => {
|
||||||
|
state.error = null;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
extraReducers: builder => {
|
extraReducers: builder => {
|
||||||
builder
|
builder
|
||||||
@@ -34,6 +37,7 @@ const authSlice = createSlice({
|
|||||||
state.loading = false;
|
state.loading = false;
|
||||||
state.token = action.payload.token;
|
state.token = action.payload.token;
|
||||||
state.error = null;
|
state.error = null;
|
||||||
|
localStorage.setItem('token', action.payload.token);
|
||||||
})
|
})
|
||||||
.addCase(loginThunk.rejected, (state, action) => {
|
.addCase(loginThunk.rejected, (state, action) => {
|
||||||
state.loading = false;
|
state.loading = false;
|
||||||
@@ -42,5 +46,5 @@ const authSlice = createSlice({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export const { logout } = authSlice.actions;
|
export const { logout, clearError } = authSlice.actions;
|
||||||
export default authSlice.reducer;
|
export default authSlice.reducer;
|
||||||
@@ -1,16 +1,23 @@
|
|||||||
import Header from "../../widgets/Header/Header";
|
import Header from "../../widgets/Header/Header";
|
||||||
import { Outlet } from "react-router";
|
import { Outlet } from "react-router";
|
||||||
|
import { useAppDispatch, useAppSelector } from '../../app/hooks';
|
||||||
|
import type { RootState } from "../../app/store";
|
||||||
|
import { Navigate } from "react-router";
|
||||||
|
|
||||||
const Layout = () =>{
|
const Layout = () =>{
|
||||||
|
|
||||||
|
const { token } = useAppSelector((state: RootState) => state.auth)
|
||||||
|
|
||||||
return(
|
return(
|
||||||
<div className="dashboard">
|
token ? <div className="dashboard">
|
||||||
<div className="content-wrapper">
|
<div className="content-wrapper">
|
||||||
<Header />
|
<Header />
|
||||||
<main className="content">
|
<main className="content">
|
||||||
<Outlet />
|
<Outlet />
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div> : <Navigate to="/login" replace />
|
||||||
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,150 @@
|
|||||||
const LoginPage = () =>{
|
import React, { useEffect, useState } from 'react';
|
||||||
return(
|
import { useNavigate } from 'react-router-dom';
|
||||||
<>
|
import { Form, Input, Button, Checkbox, Alert, Card, Spin, Image } from 'antd';
|
||||||
|
import { UserOutlined, LockOutlined } from '@ant-design/icons';
|
||||||
|
import { useAppDispatch, useAppSelector } from '../../app/hooks';
|
||||||
|
import { loginThunk } from "../../features/auth/authThunks";
|
||||||
|
import { clearError } from '../../features/auth/authSlice';
|
||||||
|
|
||||||
</>
|
interface LoginForm {
|
||||||
)
|
email: string;
|
||||||
|
password: string;
|
||||||
|
rememberMe: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const LoginPage: React.FC = () => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const { loading, error, token } = useAppSelector((state) => state.auth);
|
||||||
|
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (token) {
|
||||||
|
navigate('/');
|
||||||
|
}
|
||||||
|
}, [token, navigate]);
|
||||||
|
|
||||||
|
const handleSubmit = async (values: LoginForm) => {
|
||||||
|
try {
|
||||||
|
const result = await dispatch(loginThunk(values)).unwrap();
|
||||||
|
|
||||||
|
if (result.token) {
|
||||||
|
navigate('/data-collection');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Login error:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleForgotPassword = () => {
|
||||||
|
console.log('Forgot password clicked');
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="login-container">
|
||||||
|
<div className="login-logo">
|
||||||
|
<Image
|
||||||
|
src="/logo.png"
|
||||||
|
alt="Company Logo"
|
||||||
|
preview={false}
|
||||||
|
className="login-logo-image"
|
||||||
|
fallback="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjgwIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxyZWN0IHdpZHRoPSIyMDAiIGhlaWdodD0iODAiIGZpbGw9IiNmZmYiLz48dGV4dCB4PSIxMDAiIHk9IjQwIiBmb250LWZhbWlseT0iQXJpYWwiIGZvbnQtc2l6ZT0iMTQiIGZpbGw9IiMzMzMiIHRleHQtYW5jaG9yPSJtaWRkbGUiPkxPR08gQ09NUEFOWTwvdGV4dD48L3N2Zz4="
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Card
|
||||||
|
className="login-form-card"
|
||||||
|
|
||||||
|
>
|
||||||
|
<h2 className="login-title">
|
||||||
|
Вход в систему
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
{error && (
|
||||||
|
<Alert
|
||||||
|
message={error}
|
||||||
|
type="error"
|
||||||
|
showIcon
|
||||||
|
closable
|
||||||
|
onClose={() => dispatch(clearError())}
|
||||||
|
className="login-error-alert"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Form
|
||||||
|
form={form}
|
||||||
|
name="login"
|
||||||
|
onFinish={handleSubmit}
|
||||||
|
autoComplete="off"
|
||||||
|
layout="vertical"
|
||||||
|
>
|
||||||
|
<Form.Item
|
||||||
|
name="email"
|
||||||
|
label="Email"
|
||||||
|
rules={[
|
||||||
|
{ required: true, message: 'Пожалуйста, введите email' },
|
||||||
|
{ type: 'email', message: 'Введите корректный email адрес' },
|
||||||
|
]}
|
||||||
|
className="login-form-item"
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
prefix={<UserOutlined />}
|
||||||
|
placeholder="Введите email"
|
||||||
|
size="large"
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
name="password"
|
||||||
|
label="Пароль"
|
||||||
|
rules={[
|
||||||
|
{ required: true, message: 'Пожалуйста, введите пароль' },
|
||||||
|
{ min: 8, message: 'Пароль должен содержать минимум 8 символов' },
|
||||||
|
]}
|
||||||
|
className="login-form-item"
|
||||||
|
>
|
||||||
|
<Input.Password
|
||||||
|
prefix={<LockOutlined />}
|
||||||
|
placeholder="Введите пароль"
|
||||||
|
size="large"
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
name="rememberMe"
|
||||||
|
valuePropName="checked"
|
||||||
|
className="login-form-item"
|
||||||
|
>
|
||||||
|
<Checkbox>Запомнить меня</Checkbox>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item className="login-form-item">
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
htmlType="submit"
|
||||||
|
size="large"
|
||||||
|
block
|
||||||
|
disabled={loading}
|
||||||
|
className="login-submit-button"
|
||||||
|
>
|
||||||
|
{loading ? <Spin size="small" /> : 'Войти'}
|
||||||
|
</Button>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
|
||||||
|
<div className="login-forgot-password">
|
||||||
|
<Button
|
||||||
|
type="link"
|
||||||
|
onClick={handleForgotPassword}
|
||||||
|
className="login-forgot-password-button"
|
||||||
|
>
|
||||||
|
Забыли пароль?
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default LoginPage;
|
export default LoginPage;
|
||||||
@@ -1,12 +1,17 @@
|
|||||||
import { NavLink } from "react-router";
|
import { NavLink } from "react-router";
|
||||||
|
import { useAppDispatch, useAppSelector } from '../../app/hooks';
|
||||||
|
import { logout } from "../../features/auth/authSlice";
|
||||||
|
|
||||||
const Header = () =>{
|
const Header = () =>{
|
||||||
|
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
return(
|
return(
|
||||||
<nav className="header">
|
<nav className="header">
|
||||||
<div className="account-menu">
|
<div className="account-menu">
|
||||||
<NavLink to="/" end >Главная</NavLink>
|
<NavLink to="/" end >Главная</NavLink>
|
||||||
<NavLink to="history" >История</NavLink>
|
<NavLink to="history" >История</NavLink>
|
||||||
<NavLink to="login" >Выход</NavLink>
|
<NavLink to="login" onClick={() => dispatch(logout())} >Выход</NavLink>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user