написал страницу авторизации
This commit is contained in:
@@ -2,5 +2,5 @@ import { useDispatch, useSelector } from 'react-redux';
|
||||
import type {TypedUseSelectorHook} from 'react-redux';
|
||||
import type { RootState, AppDispatch } from './store';
|
||||
|
||||
export const useAppDispatch: () => AppDispatch = useDispatch;
|
||||
export const useAppDispatch = () => useDispatch<AppDispatch>();
|
||||
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
|
||||
@@ -8,7 +8,7 @@ import { Route } from "react-router";
|
||||
function AppRoutes() {
|
||||
return (
|
||||
<Routes>
|
||||
{/*<Route path="/login" element={<LoginPage />} />*/}
|
||||
<Route path="/login" element={<LoginPage />} />
|
||||
<Route path="/" element={<Layout />}>
|
||||
<Route index element={<DashboardPage />} />
|
||||
<Route path="history" element={<HistoryPage />} />
|
||||
|
||||
@@ -22,7 +22,10 @@ const authSlice = createSlice({
|
||||
state.token = null;
|
||||
state.error = null;
|
||||
localStorage.removeItem("token");
|
||||
}
|
||||
},
|
||||
clearError: (state) => {
|
||||
state.error = null;
|
||||
},
|
||||
},
|
||||
extraReducers: builder => {
|
||||
builder
|
||||
@@ -34,6 +37,7 @@ const authSlice = createSlice({
|
||||
state.loading = false;
|
||||
state.token = action.payload.token;
|
||||
state.error = null;
|
||||
localStorage.setItem('token', action.payload.token);
|
||||
})
|
||||
.addCase(loginThunk.rejected, (state, action) => {
|
||||
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;
|
||||
@@ -1,16 +1,23 @@
|
||||
import Header from "../../widgets/Header/Header";
|
||||
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 { token } = useAppSelector((state: RootState) => state.auth)
|
||||
|
||||
return(
|
||||
<div className="dashboard">
|
||||
token ? <div className="dashboard">
|
||||
<div className="content-wrapper">
|
||||
<Header />
|
||||
<main className="content">
|
||||
<Outlet />
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</div> : <Navigate to="/login" replace />
|
||||
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,9 +1,150 @@
|
||||
const LoginPage = () =>{
|
||||
return(
|
||||
<>
|
||||
import React, { useEffect, useState } from 'react';
|
||||
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;
|
||||
@@ -1,12 +1,17 @@
|
||||
import { NavLink } from "react-router";
|
||||
import { useAppDispatch, useAppSelector } from '../../app/hooks';
|
||||
import { logout } from "../../features/auth/authSlice";
|
||||
|
||||
const Header = () =>{
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
return(
|
||||
<nav className="header">
|
||||
<div className="account-menu">
|
||||
<NavLink to="/" end >Главная</NavLink>
|
||||
<NavLink to="history" >История</NavLink>
|
||||
<NavLink to="login" >Выход</NavLink>
|
||||
<NavLink to="login" onClick={() => dispatch(logout())} >Выход</NavLink>
|
||||
</div>
|
||||
</nav>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user