Commit b0374cba authored by Ermolaev Timur's avatar Ermolaev Timur

Merge branch 'task-10-feature/login-registration-front' of…

Merge branch 'task-10-feature/login-registration-front' of ssh://git.attractor-school.com:30022/apollo64/crm-team-one into task-10-feature/login-registration-front
parents e900be17 77853c0b
...@@ -2,10 +2,10 @@ import {Routes, Route, Outlet, Navigate, BrowserRouter} from "react-router-dom"; ...@@ -2,10 +2,10 @@ import {Routes, Route, Outlet, Navigate, BrowserRouter} from "react-router-dom";
import {Container} from "@mui/material"; import {Container} from "@mui/material";
import {useSelector} from "react-redux"; 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'
const ProtectedRoute = ({isAllowed, roles, redirectUrl, children}) => { const ProtectedRoute = ({isAllowed, roles, redirectUrl, children}) => {
const user = useSelector(state => state.users?.user); const user = useSelector(state => state.users?.user);
console.log(user)
if (!isAllowed && !roles?.includes(user?.role)) { if (!isAllowed && !roles?.includes(user?.role)) {
return <Navigate to={redirectUrl} /> return <Navigate to={redirectUrl} />
} }
...@@ -14,6 +14,7 @@ const ProtectedRoute = ({isAllowed, roles, redirectUrl, children}) => { ...@@ -14,6 +14,7 @@ const ProtectedRoute = ({isAllowed, roles, redirectUrl, children}) => {
const App = () => { const App = () => {
const user = useSelector(state => state.users?.user); const user = useSelector(state => state.users?.user);
console.log(user)
return ( return (
<BrowserRouter> <BrowserRouter>
<Routes> <Routes>
...@@ -31,7 +32,7 @@ const App = () => { ...@@ -31,7 +32,7 @@ const App = () => {
<Route path={"/"} element={ <Route path={"/"} element={
<ProtectedRoute <ProtectedRoute
isAllowed={user} isAllowed={user}
redirectUrl={"/log-in"} redirectUrl={"/sign-in"}
> >
<h1>week page</h1> <h1>week page</h1>
</ProtectedRoute> </ProtectedRoute>
...@@ -40,7 +41,7 @@ const App = () => { ...@@ -40,7 +41,7 @@ const App = () => {
<Route path={"/week"} element={ <Route path={"/week"} element={
<ProtectedRoute <ProtectedRoute
isAllowed={user} isAllowed={user}
redirectUrl={"/log-in"} redirectUrl={"/sign-in"}
> >
<h1>week page</h1> <h1>week page</h1>
</ProtectedRoute> </ProtectedRoute>
...@@ -49,7 +50,7 @@ const App = () => { ...@@ -49,7 +50,7 @@ const App = () => {
<Route path={"/month"} element={ <Route path={"/month"} element={
<ProtectedRoute <ProtectedRoute
isAllowed={user} isAllowed={user}
redirectUrl={"/log-in"} redirectUrl={"/sign-in"}
> >
<h1>month page</h1> <h1>month page</h1>
</ProtectedRoute> </ProtectedRoute>
...@@ -58,7 +59,7 @@ const App = () => { ...@@ -58,7 +59,7 @@ const App = () => {
<Route path={"/my-tasks"} element={ <Route path={"/my-tasks"} element={
<ProtectedRoute <ProtectedRoute
isAllowed={user} isAllowed={user}
redirectUrl={"/log-in"} redirectUrl={"/sign-in"}
> >
<MyTasks/> <MyTasks/>
</ProtectedRoute> </ProtectedRoute>
...@@ -67,7 +68,7 @@ const App = () => { ...@@ -67,7 +68,7 @@ const App = () => {
<Route path={"/profile/:id"} element={ <Route path={"/profile/:id"} element={
<ProtectedRoute <ProtectedRoute
isAllowed={user} isAllowed={user}
redirectUrl={"/log-in"} redirectUrl={"/sign-in"}
> >
<h1>profile page</h1> <h1>profile page</h1>
</ProtectedRoute> </ProtectedRoute>
...@@ -87,11 +88,11 @@ const App = () => { ...@@ -87,11 +88,11 @@ const App = () => {
roles={["superuser"]} roles={["superuser"]}
redirectUrl={"/"} redirectUrl={"/"}
> >
<h1>sign-up page</h1> <Register/>
</ProtectedRoute> </ProtectedRoute>
}/> }/>
<Route path={"/log-in"} element={<h1>log-in page</h1>}/> <Route path={"/sign-in"} element={<Login/>}/>
<Route path='*' element={<h1>404</h1>}/> <Route path='*' element={<h1>404</h1>}/>
</Route> </Route>
</Routes> </Routes>
...@@ -100,3 +101,4 @@ const App = () => { ...@@ -100,3 +101,4 @@ const App = () => {
}; };
export default 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 = () => { ...@@ -6,7 +6,7 @@ const AnonymousMenu = () => {
<Button <Button
component={NavLink} component={NavLink}
color="inherit" color="inherit"
to="/log-in" to="/sign-in"
> >
Вход Вход
</Button> </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);
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"}>
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())
console.log(response)
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) {
console.dir(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 = { const initialState = {
user: { user: {
name: 'Ivan', name: 'Ivan',
surname: 'Petrov', surname: 'Petrov',
email: 'test@gmail.com', email: 'test@gmail.com',
role: 'superuser' role: 'superuser'
} },
registerError: null,
loginError: null,
loading: false
}; };
const usersReducer = (state = initialState, action) => { const usersReduser = (state = initialState, action) => {
switch(action.type) { 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: default:
return state; return state;
} }
}; };
export default usersReducer; export default usersReduser;
\ No newline at end of file
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