написал страницу авторизации

This commit is contained in:
2025-10-25 16:37:08 +03:00
parent bc920da1be
commit 446d236bc1
6 changed files with 169 additions and 12 deletions
+1 -1
View File
@@ -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;
+1 -1
View File
@@ -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 />} />
+6 -2
View File
@@ -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;
+9 -2
View File
@@ -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 />
);
};
+146 -5
View File
@@ -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;
+6 -1
View File
@@ -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>
)