Commit fb74dd9f authored by Нелли Ибрагимова's avatar Нелли Ибрагимова

Merge branch 'development' of…

Merge branch 'development' of ssh://git.attractor-school.com:30022/apollo64/crm-team-one into task-18-feature-modification-my_tasks_page
parents bb6dbc9d 55faffab
This diff is collapsed.
......@@ -26,9 +26,13 @@
"@types/cors": "^2.8.12",
"@types/express": "^4.17.14",
"bcrypt": "^5.1.0",
"class-validator": "^0.13.2",
"cors": "^2.8.5",
"express": "^4.18.2",
"mongoose": "^6.7.0",
"nanoid": "^3.3.4"
"nanoid": "^3.3.4",
"pg": "^8.8.0",
"reflect-metadata": "^0.1.13",
"typeorm": "^0.3.10"
}
}
import { DataSource } from "typeorm";
import {User} from './models/User';
import {Task} from './models/Task';
import { Project } from "./models/Project";
export const myDataSource = new DataSource({
type: "postgres",
host: "localhost",
port: 5432,
username: "pluser",
password: "pluser",
database: "planner",
entities: [User,Task,Project],
logging: true,
synchronize: true, // in build switch to false
migrationsRun: false
})
\ No newline at end of file
import {
Column,
Entity,
PrimaryGeneratedColumn,
CreateDateColumn,
BaseEntity,
ManyToOne,
ManyToMany,
OneToMany
} from 'typeorm';
import {User} from './User';
import {Task} from './Task';
// type IncomingData={
// title: string | null;
// color: string
// admin:User
// workers?: User[]
// tasks?: Task[]
// dateDue?:Date
// department?:boolean
// }
interface IProject{
id: string;
title: string;
color: string;
admin:User;
workers:User[];
tasks:Task[]|null;
createdAt: Date;
dateDue: Date| null;
department:boolean;
}
@Entity({ name: 'Project' })
export class Project extends BaseEntity implements IProject{
// data: IncomingData;
// constructor(data:IncomingData){
// super();
// this.data = data
// }
@PrimaryGeneratedColumn('uuid')
id!: string
// @Column({ name: 'title', type: 'varchar', length:100,nullable: false, default: this.data.title })
// title!: string
@Column({ name: 'title', type: 'varchar', length:100,nullable: false})
title!: string
@CreateDateColumn({ name: 'createdAt', type: Date, default: new Date() })
createdAt!: Date;
@Column({ name: 'color', type: 'varchar', length:100,nullable: true })
color!: string
@Column({ name: 'dateDue', type: Date, default: null })
dateDue!: Date| null;
@Column({ name: 'department', type: Boolean,nullable:true ,default: false})
department!: boolean | false;
@ManyToOne(() => User, (user: { projects: Project[]; }) => user.projects)
admin!: User;
@ManyToMany(() => User, (user: { projects: Project[]; }) => user.projects)
workers!: User[];
@OneToMany(() => Task, (task: { project: Project; })=>task.project)
tasks!:Task[]|null;
}
import {
Column,
Entity,
PrimaryGeneratedColumn,
CreateDateColumn,
BaseEntity,
ManyToOne,
ManyToMany
} from 'typeorm';
import {User} from './User';
import {Project} from './Project';
type taskFinishType = "open" | "done" |"failed";
interface ITask{
id: string;
title: string;
description: string;
createdAt: Date;
// dateTimeStart:Date| null;
// dateTimeDue:Date| null;
assignedTo: User[];
accomplish: taskFinishType;
author: User;
project:Project|null;
}
@Entity({ name: 'Task' })
export class Task extends BaseEntity implements ITask{
@PrimaryGeneratedColumn('uuid')
id!: string
@Column({ name: 'title', type: 'varchar', length:50,nullable: false })
title!: string
@Column({ name: 'description', type: 'varchar', length:50,nullable: true })
description!: string
@CreateDateColumn({ name: 'created_at', type: Date, default: new Date() })
createdAt!: Date;
// @CreateDateColumn({ name: 'dateTimeStart', type: Date,nullable: true })
// dateTimeStart!: Date | null;
// @CreateDateColumn({ name: 'dateTimeDue', type: Date,nullable: true })
// dateTimeDue!: Date | null;
@Column({
type: "enum",
enum: ["opened", "done" , "failed"],
default: "opened"
})
accomplish!: taskFinishType
@ManyToOne(() => User, (user: { tasks: Task[]; }) => user.tasks)
author!: User;
@ManyToMany(() => User, (user: { tasks: Task[]; }) => user.tasks)
assignedTo!: User[];
@ManyToOne(()=>Project,(project:{tasks: Task[]}) => project.tasks)
project!: Project | null;
}
import { Schema, model } from 'mongoose';
import {
Column,
Entity,
PrimaryGeneratedColumn,
CreateDateColumn,
BeforeInsert,
BaseEntity,
ManyToMany,
OneToMany
} from 'typeorm';
import {IsEmail
} from "class-validator";
import bcrypt from 'bcrypt';
import {nanoid} from 'nanoid';
import {Task} from './Task';
import {Project} from './Project';
const SALT_WORK_FACTOR= 10;
// 1. Create an interface representing a document in MongoDB.
type userRoleType = "worker" | "director";
interface IUser {
id:string;
name: string;
surname: string;
email: string;
displayName: string;
password:string;
role: string;
token: string;
role: userRoleType;
createdAt: Date;
createdTasks:Task[]| null;
}
// 2. Create a Schema corresponding to the document interface.
const UserSchema = new Schema<IUser>({
name: { type: String, required: true },
surname: { type: String, required: true },
email: { type: String, required: true },
displayName: { type: String, required: true },
password: { type: String, required: true },
role:{type:String,default: 'user',enum:['user','admin']}
});
// How does next's type defined?
UserSchema.pre('save', async function(next:any):Promise<void>{
if(!this.isModified('password')) return next();
console.log('next', next)
const salt = await bcrypt.genSalt(SALT_WORK_FACTOR);
const hash = await bcrypt.hash(this.password, salt);
this.password = hash;
next();
})
UserSchema.set('toJSON',{
transform:(doc:any, ret:any, options:any)=>{
delete ret.password;
return ret;
@Entity({ name: 'User' })
export class User extends BaseEntity implements IUser {
@PrimaryGeneratedColumn('uuid')
id!: string
@Column({ name: 'name', type: 'varchar', length:20,nullable: false })
name!: string
@Column({ name: 'surname', type: 'varchar', length:30,nullable: false })
surname!: string
@Column({ name: 'displayName', type: 'varchar', length:30,nullable: false })
displayName!: string
@Column({ name: 'email', type: 'varchar',length:20, unique: true, nullable: false })
@IsEmail()
email!: string
@Column({ name: 'phone', type: 'varchar',length:10, unique: true, nullable: true})
phone?: string
@Column({ name: 'token', type: 'varchar',length:100, unique: true, nullable: false })
token!: string
@CreateDateColumn({ name: 'created_at', type: Date, default: new Date() })
createdAt!: Date;
@Column({
type: "enum",
enum: ["worker", "director"],
default: "worker"
})
role!: userRoleType
@Column({ type: 'varchar', nullable: false, select:false })
password!: string
@OneToMany(() => Task, (task: { user: User }) => task.user)
createdTasks!: Task[];
@ManyToMany(() => Task, (task: { users: User[] }) =>task.users)
tasksToMake!: Task[];
@OneToMany(() => Project, (project: { user: User }) => project.user)
adminInProjects!: Project[];
@ManyToMany(() => Project, (project: { users: User[] }) =>project.users)
workerInProjects!: Project[];
@BeforeInsert()
protected async beforeInserthashPassword():Promise<void> {
const salt = await bcrypt.genSalt(SALT_WORK_FACTOR);
this.password = await bcrypt.hash(this.password, salt);
}
})
UserSchema.methods.checkPassword =function(password:string):Promise<boolean>{
return bcrypt.compare(password,this.password);
}
public generateToken():void{
this.token = nanoid()
return
}
public async checkPassword(
candidatePassword: string,
):Promise<boolean> {
console.log("Checking password", candidatePassword,'this.password', this.password)
return await bcrypt.compare(candidatePassword, this.password);
}
UserSchema.methods.generateToken =function(){
this.token=nanoid();
}
// 3. Create a Model.
const User = model<IUser>('User', UserSchema);
export default User;
import express,{Router, Request, Response} from 'express';
import {Project} from '../models/Project';
import {myDataSource} from '../app-data-source';
import { User } from '../models/User';
const router:Router = express.Router();
const dataSource = myDataSource;
router.get('/',async (req:Request, res:Response): Promise<Response>=> {
const projects:Project[] = await dataSource.manager.find(Project)
return res.send({projects})
})
router.get("/:project_id",async (req:Request, res:Response): Promise<Response> => {
const project : Project|null= await dataSource.manager.findOneBy(Project, {
id: req.params.project_id
})
if (!project) return res.status(404).send({Message:'no info in the base'})
return res.send({project})
})
router.post('/', async (req:Request, res:Response): Promise<Response> => {
if (!req.body) return res.status(400).send({Message:'problem in incoming req.body'})
const {title, dateDue,color,department, userId,workers,tasks}= req.body;
const user = await dataSource
.createQueryBuilder()
.select("user")
.from(User, "user")
.where("user.id = :id", { id: userId })
.getOne()
if(!user) return res.status(404).send({Message:'user not found'})
const project : Project= new Project()
project.title = title;
project.color = color;
project.dateDue = dateDue || null;
project.department = department;
project.workers = workers|| null;
project.tasks = tasks || null;
project.admin = user;
await project.save()
return res.send({project})
})
// router.get('/:userId', async (req : Request, res : Response): Promise<Response>=>{
// const userId:string = req.params.userId
// const user = await dataSource
// .createQueryBuilder()
// .select("user")
// .from(User, "user")
// .where("user.id = :id", { id: userId })
// .getOne()
// console.log('user ', user)
// const userProjects = await dataSource
// .createQueryBuilder()
// .select('project')
// .from(Project, "project")
// .where("Project_adminId = :id", { id: userId })
// return res.send({userProjects})
// })
export default router;
import express,{Router, Request, Response} from 'express';
// import {User} from '../models/User';
// import {Project} from '../models/Project';
import {Task} from '../models/Task';
import {myDataSource} from '../app-data-source';
// import { nanoid } from 'nanoid';
const router:Router = express.Router();
const dataSource = myDataSource;
router.get('/', async(req:Request, res:Response):Promise<Response> => {
const tasks = await dataSource.manager.find(Task)
return res.send({tasks})
})
export default router;
import express,{Router, Request, Response} from 'express';
import User from '../models/User';
import {User} from '../models/User';
import {myDataSource} from '../app-data-source';
import { nanoid } from 'nanoid';
const router:Router = express.Router();
const dataSource = myDataSource;
router.get('/', async (req : Request, res : Response):Promise<object> => {
const users = await dataSource.manager.find(User)
return res.send({users})
})
router.post('/', async (req : Request, res : Response):Promise<object> => {
router.post('/', async (req : Request, res : Response) => {
try{
const {name,surname,password,email} = req.body;
const displayName = surname+' '+name[0]+'.'
const user = new User({
name:name,
surname:surname,
email:email,
password:password,
displayName:displayName
})
const user = new User();
user.name = name;
user.surname = surname;
user.password = password;
user.displayName= displayName;
user.email = email;
user.generateToken()
await user.save();
return res.send({user})
}catch(e ){
if (e instanceof Error){
return res.status(404).send({'message':e.message});
}
return res.status(500).send({'message':'Broke server'});
}
const userToFront:User|null = await dataSource.manager.findOneBy(User, {
email: user.email
})
return res.send({userToFront})
})
router.post('/sessions/', async (req : Request, res : Response):Promise<object> => {
const {email, password} = req.body;
const user = await dataSource
.createQueryBuilder()
.select("user")
.from(User, "user")
.where("user.email = :email", { email: email })
.getOne()
if(!user) return res.status(404).send({Message:'user not found'})
const isMatch:boolean = await user.checkPassword(password);
if (!isMatch) return res.status(400).send({
error: "Wrong Password"
})
const userToFront:User|null = await dataSource.manager.findOneBy(User, {
email: req.body.email
})
return res.send({
message: "message: 'Correct user & password",
user: userToFront
})
})
router.get('/', async (req : Request, res : Response):Promise<Response<any, Record<string, any>>> => {
const users = await User.find()
return res.send({users})
router.delete('/sessions', async(req: Request, res: Response):Promise<void | object> => {
const token = req.get('Authorization');
const successMsg = {message:'success'};
if(!token) return res.send(successMsg)
const user = await dataSource.manager.findOneBy(User, {
token: token
})
if(!user) return res.send({successMsg});
console.log('token: ' + token)
user.token = nanoid();
await user.save();
})
export default router;
import express, { Express } from 'express';
import cors from 'cors';
import mongoose from 'mongoose';
import users from './routers/users';
import tasks from './routers/tasks';
import projects from './routers/projects';
import {myDataSource} from './app-data-source';
console.log('Hello world!');
myDataSource
.initialize()
.then(() => {
console.log("Data Source has been initialized!")
})
.catch((err) => {
console.error("Error during Data Source initialization:", err)
})
const app:Express = express();
app.use(express.static('public'));
......@@ -11,18 +21,17 @@ app.use(cors())
app.use(express.json());
const PORT = 8000;
app.use('/users',users)
app.use('/tasks',tasks)
app.use('/projects',projects)
const run = async() => {
mongoose.connect(`mongodb://localhost:27017/task-planner`);
app.listen(PORT, () => {
console.log(`Server started at http://localhost:${PORT}/`);
})
process.on("exit", () => {
mongoose.disconnect();
})
}
console.log('Hello world!');
run().catch(console.error);
\ No newline at end of file
{
"compilerOptions": {
"experimentalDecorators": true,
/* Visit https://aka.ms/tsconfig to read more about this file */
/* Projects */
......
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
......@@ -5,6 +5,7 @@ import App from './App';
import { configureStore } from '@reduxjs/toolkit';
import { Provider } from 'react-redux';
import usersReducer from './store/reducers/usersReducer';
import axios from 'axios';
const localStorageMiddleware = ({getState}) => (next) => (action) => {
const result = next(action);
......@@ -19,6 +20,13 @@ const loadFromLocalStorage = () => {
return undefined;
};
axios.interceptors.request.use(config=>{
try{
config.headers['Authorization']=store.getState().users.user.token;
} catch(e){
}
return config;
})
const store = configureStore({
reducer: {
......@@ -33,4 +41,4 @@ root.render(
<Provider store={store}>
<App />
</Provider>
);
\ 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',
token:'3fc61d09-1941-48b0-b765-17d22a5caab9'
}
user: {
name: 'Ivan',
surname: 'Petrov',
email: 'test@gmail.com',
role: 'user'
},
registerError: null,
loginError: null,
loading: false
};
const usersReducer = (state = initialState, action) => {
switch(action.type) {
default:
return state;
}
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