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,
},
};
...@@ -15,6 +15,9 @@ export const SignIn = () => { ...@@ -15,6 +15,9 @@ export const SignIn = () => {
dispatch(signInUser(values)) dispatch(signInUser(values))
} }
const onFinishFailed: FormProps<FieldTypeSignIn>['onFinishFailed'] = (values) => {
dispatch(signInUser(values.values))
}
return ( return (
<Form <Form
...@@ -24,6 +27,7 @@ export const SignIn = () => { ...@@ -24,6 +27,7 @@ export const SignIn = () => {
style={{ maxWidth: 600 }} style={{ maxWidth: 600 }}
initialValues={{ remember: true }} initialValues={{ remember: true }}
onFinish={onFinish} onFinish={onFinish}
onFinishFailed={onFinishFailed}
autoComplete="off" autoComplete="off"
> >
<Form.Item<FieldTypeSignIn> <Form.Item<FieldTypeSignIn>
......
import { IUserState } from '@/features/usersSlice'; import { IUserState, logout } from '@/features/usersSlice';
import { AppBar, Toolbar, Typography, styled } from '@mui/material'; import { useAppDispatch } from '@/store/hook';
import { AppBar, Box, Button, Toolbar, Typography, styled } from '@mui/material';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
const StyledLink = styled(Link)(() => ({ const StyledLink = styled(Link)(() => ({
color: 'inherit', color: 'inherit',
textDecoration: 'none', 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}) { export function AppToolbar({user}: {user: IUserState | null}) {
const dispatch = useAppDispatch()
return ( return (
<> <>
<AppBar position="fixed"> <AppBar position="fixed">
...@@ -16,9 +27,17 @@ export function AppToolbar({user}: {user: IUserState | null}) { ...@@ -16,9 +27,17 @@ export function AppToolbar({user}: {user: IUserState | null}) {
<Typography variant="h6" component={StyledLink} to={'/'}> <Typography variant="h6" component={StyledLink} to={'/'}>
Computer parts shop Computer parts shop
</Typography> </Typography>
{
user &&
<Box component={StyledBox}>
<Typography variant="h6" component={StyledLink} to={'/'}> <Typography variant="h6" component={StyledLink} to={'/'}>
{user?.username} {user?.username}
</Typography> </Typography>
<Button variant="contained" onClick={() => dispatch(logout(user?.id))}>
Logout
</Button>
</Box>
}
</Toolbar> </Toolbar>
</AppBar> </AppBar>
</> </>
......
import { Register } from "@/components/Register"; import { Register } from "@/components/Register";
import { SignIn } from "@/components/SignIn"; import { SignIn } from "@/components/SignIn";
import { IUserError } from "@/features/usersSlice";
import { useAppSelector } from "@/store/hook"; import { useAppSelector } from "@/store/hook";
import { Row, Tabs, TabsProps } from "antd"; import { notification, Row, Tabs, TabsProps } from "antd";
import { useEffect } from "react"; import { useEffect } from "react";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
...@@ -20,12 +21,24 @@ const items: TabsProps['items'] = [ ...@@ -20,12 +21,24 @@ const items: TabsProps['items'] = [
export const Auth = () => { export const Auth = () => {
const navigate = useNavigate() const navigate = useNavigate()
const {user} = useAppSelector(state => state.users) const {user, error} = useAppSelector(state => state.users)
useEffect(() => { useEffect(() => {
if(user) navigate('/') if(user) navigate('/')
}, [navigate, user]) }, [navigate, user])
useEffect(() => {
if(error) {
(error as IUserError[]).map(item => (
notification.error({
message: item.type,
description: item.messages[0],
duration: 2
})
))
}
}, [error])
return ( return (
<Row <Row
align={'middle'} align={'middle'}
......
...@@ -2,6 +2,11 @@ import { createAsyncThunk, createSlice } from "@reduxjs/toolkit"; ...@@ -2,6 +2,11 @@ import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
import { axiosApiClient } from "../helpers/axiosApiClient"; import { axiosApiClient } from "../helpers/axiosApiClient";
import { FieldTypeRegister } from "@/components/Register"; import { FieldTypeRegister } from "@/components/Register";
import { FieldTypeSignIn } from "@/components/SignIn"; import { FieldTypeSignIn } from "@/components/SignIn";
import { isAxiosError, AxiosError } from "axios";
export interface IUserError {
type: string
messages: string[]
}
export interface IUserState { export interface IUserState {
id: string; id: string;
...@@ -12,7 +17,7 @@ export interface IUserState { ...@@ -12,7 +17,7 @@ export interface IUserState {
interface State { interface State {
user: IUserState | null; user: IUserState | null;
error: Error | null; error: IUserError[] | null | Error;
loading: boolean; loading: boolean;
} }
...@@ -24,28 +29,38 @@ const initialState: State = { ...@@ -24,28 +29,38 @@ const initialState: State = {
export const registerUser = createAsyncThunk( export const registerUser = createAsyncThunk(
'users/register', 'users/register',
async (body: FieldTypeRegister) => { async (body: FieldTypeRegister, {rejectWithValue}) => {
try { try {
const {data} = await axiosApiClient.post<IUserState>('/user/registration', body) const {data} = await axiosApiClient.post<IUserState>('/user/registration', body)
localStorage.setItem('token', data.token) localStorage.setItem('token', data.token)
return data return data
} catch (e) { } catch (e) {
if(isAxiosError(e)) {
const error: AxiosError<any> = e
return rejectWithValue(error.response?.data)
} else {
throw new Error((e as Error).message) throw new Error((e as Error).message)
} }
} }
}
) )
export const signInUser = createAsyncThunk( export const signInUser = createAsyncThunk(
'users/signInUser', 'users/signInUser',
async (body: FieldTypeSignIn) => { async (body: FieldTypeSignIn, {rejectWithValue}) => {
try { try {
const {data} = await axiosApiClient.post<IUserState>('/user/signIn', body) const {data} = await axiosApiClient.post<IUserState>('/user/signIn', body)
localStorage.setItem('token', data.token) localStorage.setItem('token', data.token)
return data return data
} catch (e) { } catch (e) {
if(isAxiosError(e)) {
const error: AxiosError<any> = e
return rejectWithValue(error.response?.data)
} else {
throw new Error((e as Error).message) throw new Error((e as Error).message)
} }
} }
}
) )
export const validateToken = createAsyncThunk( export const validateToken = createAsyncThunk(
...@@ -57,8 +72,18 @@ export const validateToken = createAsyncThunk( ...@@ -57,8 +72,18 @@ export const validateToken = createAsyncThunk(
Authorization: localStorage.getItem('token') 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 return data
} catch (e) { } catch (e) {
throw new Error((e as Error).message) throw new Error((e as Error).message)
...@@ -81,7 +106,7 @@ const usersSlice = createSlice( ...@@ -81,7 +106,7 @@ const usersSlice = createSlice(
}) })
.addCase(registerUser.rejected, (state, action) => { .addCase(registerUser.rejected, (state, action) => {
state.loading = false; state.loading = false;
state.error = action.error as Error; state.error = action.payload as IUserError[];
}) })
.addCase(registerUser.pending, (state) => { .addCase(registerUser.pending, (state) => {
state.loading = true; state.loading = true;
...@@ -94,7 +119,7 @@ const usersSlice = createSlice( ...@@ -94,7 +119,7 @@ const usersSlice = createSlice(
}) })
.addCase(signInUser.rejected, (state, action) => { .addCase(signInUser.rejected, (state, action) => {
state.loading = false; state.loading = false;
state.error = action.error as Error; state.error = action.payload as IUserError[];
}) })
.addCase(signInUser.pending, (state) => { .addCase(signInUser.pending, (state) => {
state.loading = true; state.loading = true;
...@@ -112,6 +137,21 @@ const usersSlice = createSlice( ...@@ -112,6 +137,21 @@ const usersSlice = createSlice(
.addCase(validateToken.pending, (state) => { .addCase(validateToken.pending, (state) => {
state.loading = true; 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 { ...@@ -42,7 +42,17 @@ export class UserController {
validateToken: RequestHandler = async (req, res): Promise<void> => { validateToken: RequestHandler = async (req, res): Promise<void> => {
const token = req.headers.authorization const token = req.headers.authorization
const user = await this.service.validateToken(token || '') const user = await this.service.validateToken(token)
res.send(user) 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 { ...@@ -38,11 +38,22 @@ export class UserRepo {
return userWithoutPass return userWithoutPass
} }
async validateToken(token: string): Promise<IUser | null> { async validateToken(token?: string): Promise<IUser | null> {
if(token) {
const user = await this.repo.findOne({where: {token: token}}) const user = await this.repo.findOne({where: {token: token}})
if(!user) return null if(!user) return null
const userWithoutPass = _.omit(user, 'password') const userWithoutPass = _.omit(user, 'password')
return userWithoutPass 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 { ...@@ -16,5 +16,6 @@ export class UserRoute implements IRoute {
this.router.post('/registration', this.controller.registration); this.router.post('/registration', this.controller.registration);
this.router.post('/signIn', this.controller.signIn); this.router.post('/signIn', this.controller.signIn);
this.router.get('/validateToken', this.controller.validateToken); this.router.get('/validateToken', this.controller.validateToken);
this.router.get('/logout', this.controller.logout);
} }
} }
...@@ -16,7 +16,11 @@ export class UserService { ...@@ -16,7 +16,11 @@ export class UserService {
return await userRepo.registration(registrationUserDto) return await userRepo.registration(registrationUserDto)
} }
async validateToken(token: string) { async validateToken(token?: string) {
return await userRepo.validateToken(token) 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