update

parent fffd782f
module.exports = {
root: true,
env: { browser: true, es2020: true },
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react-hooks/recommended',
'prettier',
],
ignorePatterns: ['dist', '.eslintrc.cjs'],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaFeatures: {
jsx: true,
},
ecmaVersion: 'latest',
sourceType: 'module',
// project: "./tsconfig.json",
},
plugins: ['react-refresh', 'react', '@typescript-eslint'],
rules: {
'react-refresh/only-export-components': ['warn', { allowConstantExport: true }],
'react/react-in-jsx-scope': 0,
},
};
......@@ -14,8 +14,11 @@ export const SignIn = () => {
const onFinish: FormProps<FieldTypeSignIn>['onFinish'] = (values) => {
dispatch(signInUser(values))
}
const onFinishFailed: FormProps<FieldTypeSignIn>['onFinishFailed'] = (values) => {
dispatch(signInUser(values.values))
}
return (
<Form
name="basic"
......@@ -24,6 +27,7 @@ export const SignIn = () => {
style={{ maxWidth: 600 }}
initialValues={{ remember: true }}
onFinish={onFinish}
onFinishFailed={onFinishFailed}
autoComplete="off"
>
<Form.Item<FieldTypeSignIn>
......
import { IUserState } from '@/features/usersSlice';
import { AppBar, Toolbar, Typography, styled } from '@mui/material';
import { IUserState, logout } from '@/features/usersSlice';
import { useAppDispatch } from '@/store/hook';
import { AppBar, Box, Button, Toolbar, Typography, styled } from '@mui/material';
import { Link } from 'react-router-dom';
const StyledLink = styled(Link)(() => ({
color: 'inherit',
textDecoration: 'none',
['&:hover']: { color: 'inherit' },
['&:hover']: { color: 'inherit' }
}));
const StyledBox = styled(Link)(() => ({
width: '200px',
display: 'flex',
justifyContent: 'space-between',
textDecoration: 'none',
color: 'white'
}));
export function AppToolbar({user}: {user: IUserState | null}) {
const dispatch = useAppDispatch()
return (
<>
<AppBar position="fixed">
......@@ -16,9 +27,17 @@ export function AppToolbar({user}: {user: IUserState | null}) {
<Typography variant="h6" component={StyledLink} to={'/'}>
Computer parts shop
</Typography>
<Typography variant="h6" component={StyledLink} to={'/'}>
{user?.username}
</Typography>
{
user &&
<Box component={StyledBox}>
<Typography variant="h6" component={StyledLink} to={'/'}>
{user?.username}
</Typography>
<Button variant="contained" onClick={() => dispatch(logout(user?.id))}>
Logout
</Button>
</Box>
}
</Toolbar>
</AppBar>
</>
......
import { Register } from "@/components/Register";
import { SignIn } from "@/components/SignIn";
import { IUserError } from "@/features/usersSlice";
import { useAppSelector } from "@/store/hook";
import { Row, Tabs, TabsProps } from "antd";
import { notification, Row, Tabs, TabsProps } from "antd";
import { useEffect } from "react";
import { useNavigate } from "react-router-dom";
......@@ -20,12 +21,24 @@ const items: TabsProps['items'] = [
export const Auth = () => {
const navigate = useNavigate()
const {user} = useAppSelector(state => state.users)
const {user, error} = useAppSelector(state => state.users)
useEffect(() => {
if(user) navigate('/')
}, [navigate, user])
useEffect(() => {
if(error) {
(error as IUserError[]).map(item => (
notification.error({
message: item.type,
description: item.messages[0],
duration: 2
})
))
}
}, [error])
return (
<Row
align={'middle'}
......
......@@ -2,6 +2,11 @@ import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
import { axiosApiClient } from "../helpers/axiosApiClient";
import { FieldTypeRegister } from "@/components/Register";
import { FieldTypeSignIn } from "@/components/SignIn";
import { isAxiosError, AxiosError } from "axios";
export interface IUserError {
type: string
messages: string[]
}
export interface IUserState {
id: string;
......@@ -12,7 +17,7 @@ export interface IUserState {
interface State {
user: IUserState | null;
error: Error | null;
error: IUserError[] | null | Error;
loading: boolean;
}
......@@ -24,26 +29,36 @@ const initialState: State = {
export const registerUser = createAsyncThunk(
'users/register',
async (body: FieldTypeRegister) => {
async (body: FieldTypeRegister, {rejectWithValue}) => {
try {
const {data} = await axiosApiClient.post<IUserState>('/user/registration', body)
localStorage.setItem('token', data.token)
return data
} catch (e) {
throw new Error((e as Error).message)
if(isAxiosError(e)) {
const error: AxiosError<any> = e
return rejectWithValue(error.response?.data)
} else {
throw new Error((e as Error).message)
}
}
}
)
export const signInUser = createAsyncThunk(
'users/signInUser',
async (body: FieldTypeSignIn) => {
async (body: FieldTypeSignIn, {rejectWithValue}) => {
try {
const {data} = await axiosApiClient.post<IUserState>('/user/signIn', body)
localStorage.setItem('token', data.token)
return data
} catch (e) {
throw new Error((e as Error).message)
if(isAxiosError(e)) {
const error: AxiosError<any> = e
return rejectWithValue(error.response?.data)
} else {
throw new Error((e as Error).message)
}
}
}
)
......@@ -57,8 +72,18 @@ export const validateToken = createAsyncThunk(
Authorization: localStorage.getItem('token')
}
})
console.log(data);
return data
} catch (e) {
throw new Error((e as Error).message)
}
}
)
export const logout = createAsyncThunk(
'users/logout',
async (userId?: string) => {
try {
const {data} = await axiosApiClient.get(`/user/logout?id=${userId}`)
return data
} catch (e) {
throw new Error((e as Error).message)
......@@ -81,7 +106,7 @@ const usersSlice = createSlice(
})
.addCase(registerUser.rejected, (state, action) => {
state.loading = false;
state.error = action.error as Error;
state.error = action.payload as IUserError[];
})
.addCase(registerUser.pending, (state) => {
state.loading = true;
......@@ -94,7 +119,7 @@ const usersSlice = createSlice(
})
.addCase(signInUser.rejected, (state, action) => {
state.loading = false;
state.error = action.error as Error;
state.error = action.payload as IUserError[];
})
.addCase(signInUser.pending, (state) => {
state.loading = true;
......@@ -112,6 +137,21 @@ const usersSlice = createSlice(
.addCase(validateToken.pending, (state) => {
state.loading = true;
})
// LOGOUT
.addCase(logout.fulfilled, (state) => {
state.user = null
localStorage.clear()
state.loading = false;
})
.addCase(logout.rejected, (state, action) => {
state.loading = false;
state.error = action.error as Error;
})
.addCase(logout.pending, (state) => {
state.user = null
state.loading = true;
})
},
}
)
......
......@@ -42,7 +42,17 @@ export class UserController {
validateToken: RequestHandler = async (req, res): Promise<void> => {
const token = req.headers.authorization
const user = await this.service.validateToken(token || '')
const user = await this.service.validateToken(token)
res.send(user)
}
logout: RequestHandler = async (req, res): Promise<void> => {
try {
const {id} = req.query
await this.service.logout(parseInt(id as string))
res.status(200).send(id)
} catch (e) {
res.status(500).send(e);
}
}
}
......@@ -38,11 +38,22 @@ export class UserRepo {
return userWithoutPass
}
async validateToken(token: string): Promise<IUser | null> {
const user = await this.repo.findOne({where: {token: token}})
if(!user) return null
const userWithoutPass = _.omit(user, 'password')
return userWithoutPass
async validateToken(token?: string): Promise<IUser | null> {
if(token) {
const user = await this.repo.findOne({where: {token: token}})
if(!user) return null
const userWithoutPass = _.omit(user, 'password')
return userWithoutPass
} else {
return null
}
}
async logout(userId: number) {
const user = await this.repo.findOne({where: {id: userId}})
if(!user) throw new Error('User not have')
user.token = ''
await this.repo.save(user)
}
}
......
......@@ -16,5 +16,6 @@ export class UserRoute implements IRoute {
this.router.post('/registration', this.controller.registration);
this.router.post('/signIn', this.controller.signIn);
this.router.get('/validateToken', this.controller.validateToken);
this.router.get('/logout', this.controller.logout);
}
}
......@@ -16,7 +16,11 @@ export class UserService {
return await userRepo.registration(registrationUserDto)
}
async validateToken(token: string) {
async validateToken(token?: string) {
return await userRepo.validateToken(token)
}
async logout(userId: number) {
return await userRepo.logout(userId)
}
}
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