Merge branch 'task-10-feature/login-registration-front' into 'development'

Task 10 feature/login registration front

See merge request !7
parents e900be17 d56280a9
import {Routes, Route, Outlet, Navigate, BrowserRouter} from "react-router-dom";
import {Container} from "@mui/material";
import {useSelector} from "react-redux";
import AppToolbar from "./components/UI/AppToolBar/AppToolBar";
import AppToolbar from './components/UI/AppToolBar/AppToolbar'
import MyTasks from './containers/MyTasks/MyTasks'
import Login from './containers/Login/Login'
import Register from './containers/Register/Register'
const ProtectedRoute = ({isAllowed, roles, redirectUrl, children}) => {
const user = useSelector(state => state.users?.user);
......@@ -31,7 +33,7 @@ const App = () => {
<Route path={"/"} element={
<ProtectedRoute
isAllowed={user}
redirectUrl={"/log-in"}
redirectUrl={"/sign-in"}
>
<h1>week page</h1>
</ProtectedRoute>
......@@ -40,7 +42,7 @@ const App = () => {
<Route path={"/week"} element={
<ProtectedRoute
isAllowed={user}
redirectUrl={"/log-in"}
redirectUrl={"/sign-in"}
>
<h1>week page</h1>
</ProtectedRoute>
......@@ -49,7 +51,7 @@ const App = () => {
<Route path={"/month"} element={
<ProtectedRoute
isAllowed={user}
redirectUrl={"/log-in"}
redirectUrl={"/sign-in"}
>
<h1>month page</h1>
</ProtectedRoute>
......@@ -58,7 +60,7 @@ const App = () => {
<Route path={"/my-tasks"} element={
<ProtectedRoute
isAllowed={user}
redirectUrl={"/log-in"}
redirectUrl={"/sign-in"}
>
<MyTasks/>
</ProtectedRoute>
......@@ -67,7 +69,7 @@ const App = () => {
<Route path={"/profile/:id"} element={
<ProtectedRoute
isAllowed={user}
redirectUrl={"/log-in"}
redirectUrl={"/sign-in"}
>
<h1>profile page</h1>
</ProtectedRoute>
......@@ -87,11 +89,11 @@ const App = () => {
roles={["superuser"]}
redirectUrl={"/"}
>
<h1>sign-up page</h1>
<Register/>
</ProtectedRoute>
}/>
<Route path={"/log-in"} element={<h1>log-in page</h1>}/>
<Route path={"/sign-in"} element={<Login/>}/>
<Route path='*' element={<h1>404</h1>}/>
</Route>
</Routes>
......@@ -100,3 +102,4 @@ const App = () => {
};
export default App;
import axios from "axios";
import {apiUrl} from "./constants";
const instance = axios.create({
baseURL: apiUrl
});
export default instance;
......@@ -6,7 +6,7 @@ const AnonymousMenu = () => {
<Button
component={NavLink}
color="inherit"
to="/log-in"
to="/sign-in"
>
Вход
</Button>
......
import { Button, Menu, MenuItem } from "@mui/material";
import { useState } from "react";
import { useDispatch } from "react-redux";
import { NavLink, useNavigate } from "react-router-dom";
import { logoutUser } from "../../../store/actions/usersActions";
import HasAccess from "../../UI/HasAccess/HasAccess";
const UserMenu = ({ user }) => {
const dispatch = useDispatch();
const navigate = useNavigate()
const [anchorEl, setAnchorEl] = useState(null);
const open = Boolean(anchorEl);
const handleClick = (event) => {
setAnchorEl(event.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
};
const logout = () => {
dispatch(logoutUser(navigate));
handleClose()
}
return <>
<Button
color="inherit"
onClick={handleClick}
>
Hello, {user?.displayName}
</Button>
{/* <HasAccess roles={["admin"]}>
<Button
component={NavLink}
color="inherit"
to="/admin"
>
Admin panel
</Button>
</HasAccess> */}
<Menu
anchorEl={anchorEl}
open={open}
onClose={handleClose}
>
<MenuItem onClick={handleClose}>Profile</MenuItem>
<MenuItem onClick={handleClose}>My account</MenuItem>
<MenuItem onClick={logout}>Logout</MenuItem>
</Menu>
</>
};
export default UserMenu;
\ No newline at end of file
import {AppBar, Box, Toolbar, Typography} from "@mui/material";
import {NavLink} from "react-router-dom";
import {useSelector} from "react-redux";
import HasAccess from "../HasAccess/HasAccess";
import AnonymousMenu from "../../Menus/AnonymousMenu/AnonymousMenu";
import WorkerMenu from "../../Menus/WorkerMenu/WorkerMenu";
import AdminMenu from "../../Menus/AdminMenu/AdminMenu";
const AppToolbar = () => {
const user = useSelector(state => state.users.user);
return <Box sx={{ flexGrow: 1, mb: "40px" }}>
<AppBar position="static">
<Toolbar>
<Typography
variant="h4"
component="div"
sx={{ flexGrow: 1 }}>
<NavLink to='/' style={{textDecoration: 'none', color: 'inherit'}}>Task Manager</NavLink>
</Typography>
<HasAccess allowed={!user}>
<AnonymousMenu/>
</HasAccess>
<HasAccess roles={['user']}>
<WorkerMenu/>
</HasAccess>
<HasAccess roles={['superuser']}>
<AdminMenu/>
</HasAccess>
</Toolbar>
</AppBar>
</Box>
};
export default AppToolbar;
import { Grid, TextField, MenuItem } from "@mui/material";
import PropTypes from "prop-types";
const FormElement = ({ name, label, state, error, onChange, select, options, type = "'text" }) => {
let inputChildren = null
if (select) {
inputChildren = options.map(option => {
return <MenuItem key={option._id} value={option._id}>
{option.name}
</MenuItem>
})
}
let input = <TextField
select={select}
id={name}
name={name}
label={label}
type={type}
variant="outlined"
value={state?.[name]}
onChange={onChange}
error={!!error}
helperText={error}
fullWidth
>
{inputChildren}
</TextField>
return <Grid item xs={12}>
{input}
</Grid>
}
FormElement.propTypes = {
name: PropTypes.string.isRequired,
label: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired,
state: PropTypes.object,
error: PropTypes.string,
type: PropTypes.string
}
export default FormElement;
\ No newline at end of file
import { CircularProgress } from "@mui/material";
const Loader = ({loading}) => {
return loading &&
<CircularProgress color="primary" size={50} style={{margin: "0 auto"}} />
};
export default Loader;
\ No newline at end of file
import { Button, Grid} from "@mui/material";
// import { NavLink } from "react-router-dom";
import FormElement from "../UI/Form/FormElement/FormElement";
const UserForm = ({ state, onChange, onSubmit, getFieldError, buttonText, resetPassword }) => {
return <form onSubmit={onSubmit}>
<Grid container spacing={2}>
<FormElement
onChange={onChange}
name="email"
label="Email"
state={state}
error={getFieldError?.("email")}
/>
<FormElement
onChange={onChange}
name="password"
label="Password"
type="password"
state={state}
error={getFieldError?.("password")}
/>
</Grid>
<Button
sx={{ mt: "15px" }}
type="submit"
fullWidth
variant="contained"
color="primary"
>
{buttonText}
</Button>
{/* <Button
component={NavLink}
to={"/forgottenpassword"}
sx={{ mt: "15px" }}
type="submit"
fullWidth
variant="contained"
color="primary"
>
{resetPassword}
</Button> */}
</form>
}
export default UserForm;
\ No newline at end of file
import { Button, Grid } from "@mui/material";
import FormElement from "../UI/Form/FormElement/FormElement";
const UserRegistrationForm = ({ state, onChange, onSubmit, getFieldError, buttonText, fileChangeHandler }) => {
return <form onSubmit={onSubmit}>
<Grid container spacing={2}>
<FormElement
onChange={onChange}
name="name"
label="Name"
state={state}
error={getFieldError?.("name")}
/>
<FormElement
onChange={onChange}
name="surname"
label="Surname"
state={state}
error={getFieldError?.("surname")}
/>
<FormElement
onChange={onChange}
name="email"
label="Email"
state={state}
error={getFieldError?.("email")}
/>
<FormElement
onChange={onChange}
name="number"
label="Number"
state={state}
error={getFieldError?.("number")}
/>
<FormElement
onChange={onChange}
name="password"
label="Password"
type="password"
state={state}
error={getFieldError?.("password")}
/>
{/* <FormElement
onChange={onChange}
name="displayName"
label="DisplayName"
state={state}
/> */}
{/* <FormElement
onChange={fileChangeHandler}
name="avatar"
label="Avatar"
type="file"
state={state}
/> */}
</Grid>
<Button
sx={{ mt: "15px" }}
type="submit"
fullWidth
variant="contained"
color="primary"
>
{buttonText}
</Button>
</form>
}
export default UserRegistrationForm;
\ No newline at end of file
export const apiUrl = "http://localhost:8000";
export const uploadsUrl = `${apiUrl}/uploads`;
// import { useState } from "react";
// import { useDispatch, useSelector } from "react-redux";
// import { useNavigate } from "react-router-dom";
// import Loader from "../../components/UI/Loader/Loader";
// import UserForm from "../../components/UserForm/UserForm";
// import { loginUser } from "../../store/actions/usersActions";
// import PersonIcon from '@mui/icons-material/Person';
// import styled from "@emotion/styled";
// import { Alert, Avatar, Container, Typography } from "@mui/material";
// const StyledContainer = styled(Container)`
// padding-top: 30px;
// padding-bottom: 30px;
// box-shadow: 0 18px 30px 0 rgba(0, 0, 0, 0.6);
// border-radius: 6px;
// `;
// const StyledTitle = styled(Typography)`
// text-align: center;
// font-size: 30px;
// margin-bottom: 30px;
// `;
// const ForgottenPassword = () => {
// const [state, setState] = useState({
// email: '',
// redirectUrl: 'http://localhost:3000/passwordreset'
// });
// const dispatch = useDispatch();
// const { loginError, loading } = useSelector(state => state.users);
// console.log(loginError)
// const navigate = useNavigate("/")
// const inputChangeHandler = (e) => {
// const { name, value } = e.target;
// setState((prevState) => {
// return {
// ...prevState,
// [name]: value
// }
// });
// };
// const submitHandler = async (e) => {
// e.preventDefault();
// await dispatch(loginUser(state, navigate));
// };
// return <>
// <StyledContainer component={"section"} maxWidth={"xs"}>
// {!!loginError && <Alert color="error">{loginError}</Alert>}
// <Avatar sx={{ m: "0 auto 30px" }}>
// <PersonIcon />
// </Avatar>
// <StyledTitle variant={"h1"}>
// Password Reset
// </StyledTitle>
// <UserForm
// onSubmit={submitHandler}
// state={state}
// onChange={inputChangeHandler}
// buttonText={"Sign In"}
// resetPassword={"Forgot your password?"}
// />
// </StyledContainer>
// <Loader loading={loading} />
// </>
// };
// export default ForgottenPassword;
\ No newline at end of file
import { useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { useNavigate } from "react-router-dom";
import Loader from "../../components/UI/Loader/Loader";
import UserForm from "../../components/UserForm/UserForm";
import { loginUser } from "../../store/actions/usersActions";
import PersonIcon from '@mui/icons-material/Person';
import styled from "@emotion/styled";
import { Alert, Avatar, Container, Typography } from "@mui/material";
const StyledContainer = styled(Container)`
padding-top: 30px;
padding-bottom: 30px;
box-shadow: 0 18px 30px 0 rgba(0, 0, 0, 0.6);
border-radius: 6px;
`;
const StyledTitle = styled(Typography)`
text-align: center;
font-size: 30px;
margin-bottom: 30px;
`;
const Login = () => {
const [state, setState] = useState({
email: '',
password: ''
});
const dispatch = useDispatch();
const { loginError, loading } = useSelector(state => state.users);
const navigate = useNavigate("/")
const inputChangeHandler = (e) => {
const { name, value } = e.target;
setState((prevState) => {
return {
...prevState,
[name]: value
}
});
};
const submitHandler = async (e) => {
e.preventDefault();
await dispatch(loginUser(state, navigate));
};
return <>
<StyledContainer component={"section"} maxWidth={"xs"}>
{!!loginError && <Alert color="error">{loginError}</Alert>}
<Avatar sx={{ m: "0 auto 30px" }}>
<PersonIcon />
</Avatar>
<StyledTitle variant={"h1"}>
Sign In
</StyledTitle>
<UserForm
onSubmit={submitHandler}
state={state}
onChange={inputChangeHandler}
buttonText={"Sign In"}
// resetPassword={"Forgot your password?"}
/>
</StyledContainer>
<Loader loading={loading} />
</>
};
export default Login;
\ No newline at end of file
import { useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { useNavigate } from "react-router-dom";
import Loader from "../../components/UI/Loader/Loader";
import UserForm from "../../components/UserForm/UserRegistrationForm";
import { loginUser, registerUser } from "../../store/actions/usersActions";
import styled from "@emotion/styled";
import { Avatar, Container, Typography } from "@mui/material";
import LockIcon from "@mui/icons-material/Lock";
const StyledContainer = styled(Container)`
padding-top: 30px;
padding-bottom: 30px;
box-shadow: 0 18px 30px 0 rgba(0, 0, 0, 0.6);
border-radius: 6px;
`;
const StyledTitle = styled(Typography)`
text-align: center;
font-size: 30px;
margin-bottom: 30px;
`;
const Register = () => {
const [state, setState] = useState({
name: '',
surname: "",
email: "",
number: "",
password: '',
// avatar: "",
});
const dispatch = useDispatch();
const { registerError, loading } = useSelector(state => state.users);
const navigate = useNavigate()
const fileChangeHandler = (e) => {
const name = e.target.name;
const file = e.target.files[0];
setState(prevState => {
return {
...prevState,
[name]: file
}
})
}
const inputChangeHandler = (e) => {
const { name, value } = e.target;
setState((prevState) => {
return {
...prevState,
[name]: value
}
});
};
const submitHandler = async (e) => {
e.preventDefault();
const formData = new FormData();
Object.keys(state).forEach(key => {
formData.append(key, state[key]);
})
await dispatch(registerUser(formData, navigate));
await dispatch(loginUser(state, navigate))
};
const getFieldError = (fieldname) => {
return registerError?.errors?.[fieldname]?.message
}
return <>
<StyledContainer component={"section"} maxWidth={"xs"}>
<Avatar sx={{ m: "0 auto 30px" }}>
<LockIcon />
</Avatar>
<StyledTitle variant={"h1"}>
Sign Up
</StyledTitle>
<UserForm
onSubmit={submitHandler}
state={state}
onChange={inputChangeHandler}
buttonText={"Sign Up"}
getFieldError={getFieldError}
fileChangeHandler={fileChangeHandler}
/>
</StyledContainer>
<Loader loading={loading} />
</>
};
export default Register;
\ No newline at end of file
export const REGISTER_USER_REQUEST = "REGISTER_USER_REQUEST";
export const REGISTER_USER_SUCCESS = "REGISTER_USER_SUCCESS";
export const REGISTER_USER_FAILURE = "REGISTER_USER_FAILURE";
export const LOGIN_USER_SUCCESS = "LOGIN_USER_SUCCESS";
export const LOGIN_USER_FAILURE = "LOGIN_USER_FAILURE";
export const LOGOUT_USER_SUCCESS = "LOGOUT_USER_SUCCESS";
export const LOGOUT_USER_FAILURE = "LOGOUT_USER_FAILURE";
\ No newline at end of file
export const showNotification = (message, variant="success") => {
return {type: "SHOW_NOTIFICATION", message, variant}
}
export const hideNotification = () => {
return {type: "HIDE_NOTIFICATION"};
};
\ No newline at end of file
import axios from "../../axiosPlanner";
import { LOGIN_USER_FAILURE, LOGIN_USER_SUCCESS, LOGOUT_USER_FAILURE, LOGOUT_USER_SUCCESS, REGISTER_USER_FAILURE, REGISTER_USER_REQUEST, REGISTER_USER_SUCCESS } from "../actionTypes/actionTypes"
import { showNotification } from "./commonActions";
const registerUserRequest = () => {
return {type: REGISTER_USER_REQUEST}
};
const registerUserSuccess = () => {
return {type: REGISTER_USER_SUCCESS}
};
const registerUserFailure = (error) => {
return {type: REGISTER_USER_FAILURE, error}
};
export const registerUser = (userData, navigate) => {
return async (dispatch) => {
dispatch(registerUserRequest());
try {
const response = await axios.post("/users", userData);
dispatch(registerUserSuccess())
navigate("/")
} catch (error) {
if (error.response?.data) {
dispatch(registerUserFailure(error.response.data));
} else {
dispatch(registerUserFailure({global: "Потеряно соедиение"}));
}
}
}
}
const loginUserSuccess = (user) => {
return {type: LOGIN_USER_SUCCESS, user}
}
const loginUserFailure = (error) => {
return {type: LOGIN_USER_FAILURE, error}
}
const logoutUserSuccess = () => {
return {type: LOGOUT_USER_SUCCESS}
}
const logoutUserFailure = (error) => {
return {type: LOGOUT_USER_FAILURE, error}
}
export const loginUser = (userData, navigate) => {
return async (dispatch) => {
try {
const response = await axios.post("users/sessions", userData);
dispatch(loginUserSuccess(response.data));
navigate("/")
} catch (e) {
dispatch(loginUserFailure(e?.response?.data?.err))
}
}
}
export const logoutUser = (navigate) => {
return async (dispatch, getState) => {
try {
await axios.delete("/users/sessions", {
headers: {
'Authorization': getState().users.user?.token
}
});
dispatch(logoutUserSuccess());
navigate("/");
dispatch(showNotification("Вы успешно вышли"));
} catch (e) {
dispatch(logoutUserFailure(e?.response?.data));
dispatch(showNotification("Не удалось выйти", "error"));
}
}
}
\ No newline at end of file
import { REGISTER_USER_REQUEST, REGISTER_USER_SUCCESS, REGISTER_USER_FAILURE, LOGIN_USER_SUCCESS, LOGIN_USER_FAILURE, LOGOUT_USER_SUCCESS } from "../actionTypes/actionTypes";
const initialState = {
user: {
name: 'Ivan',
surname: 'Petrov',
email: 'test@gmail.com',
role: 'superuser'
}
role: 'user'
},
registerError: null,
loginError: null,
loading: false
};
const usersReducer = (state = initialState, action) => {
const usersReduser = (state = initialState, action) => {
switch(action.type) {
case REGISTER_USER_REQUEST:
return {...state, loading: true};
case REGISTER_USER_SUCCESS:
return {...state, loading: false};
case REGISTER_USER_FAILURE:
return {...state, loading: false, registerError: action.error};
case LOGIN_USER_SUCCESS:
return {...state, user: action.user};
case LOGIN_USER_FAILURE:
return {...state, loginError: action.error};
case LOGOUT_USER_SUCCESS:
return {...state, user: null};
default:
return state;
}
};
export default usersReducer;
\ No newline at end of file
export default usersReduser;
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment