update

parent a2e84f82
This diff is collapsed.
......@@ -10,12 +10,13 @@
"preview": "vite preview"
},
"dependencies": {
"@emotion/react": "^11.11.4",
"@emotion/styled": "^11.11.5",
"@emotion/react": "^11.13.0",
"@emotion/styled": "^11.13.0",
"@fontsource/roboto": "^5.0.12",
"@mui/icons-material": "^5.15.14",
"@mui/material": "^5.15.14",
"@mui/material": "^5.16.7",
"@reduxjs/toolkit": "^2.1.0",
"antd": "^5.20.0",
"axios": "^1.6.7",
"path": "^0.12.7",
"react": "^18.2.0",
......
import { Container, CssBaseline } from '@mui/material';
import { Container } from '@mui/material';
import { Route, Routes } from 'react-router-dom';
import { AppToolbar } from './components/UI/AppToolbar';
import { Products } from './containers/Products';
import { NewProductForm } from './containers/NewProductForm';
import { Auth } from './containers/Auth';
import { ProtectedRoute } from './components/ProtectedRoute';
import { useAppDispatch, useAppSelector } from './store/hook';
import { useEffect } from 'react';
import { validateToken } from './features/usersSlice';
function App() {
const dispatch = useAppDispatch()
const {user} = useAppSelector(state => state.users)
useEffect(() => {
dispatch(validateToken())
}, [dispatch])
return (
<>
<a href=""></a>
<CssBaseline />
<header>
<AppToolbar />
<AppToolbar user={user}/>
</header>
<main>
<Container maxWidth="xl" sx={{ mt: 10 }}>
<Routes>
<Route path="/" element={<Products />} />
<Route path="/products/new" element={<NewProductForm />} />
<Route path="/" element={
<ProtectedRoute user={user}>
<Products />
</ProtectedRoute>
}/>
<Route path="/products/new" element={
<ProtectedRoute user={user}>
<NewProductForm />
</ProtectedRoute>
}/>
<Route path='/auth' element={<Auth/>} />
</Routes>
</Container>
</main>
......
import { IUserState } from "@/features/usersSlice"
import { ReactNode } from "react"
import { Navigate } from "react-router-dom"
type TProps = {
user: IUserState | null
children: ReactNode
}
export const ProtectedRoute = ({user, children}: TProps) => {
if(!user) return <Navigate to={'/auth'}/>
return children
}
\ No newline at end of file
import { registerUser } from '@/features/usersSlice';
import { useAppDispatch } from '@/store/hook';
import {Button, Form, FormProps, Input} from 'antd'
export type FieldTypeRegister = {
username: string;
displayName: string;
password: string;
}
export const Register = () => {
const dispatch = useAppDispatch()
const onFinish: FormProps<FieldTypeRegister>['onFinish'] = (values) => {
dispatch(registerUser(values))
}
return (
<Form
name="basic"
labelCol={{ span: 8 }}
wrapperCol={{ span: 16 }}
style={{ maxWidth: 600 }}
initialValues={{ remember: true }}
onFinish={onFinish}
autoComplete="off"
>
<Form.Item<FieldTypeRegister>
label="Username"
name="username"
rules={[{ required: true, message: 'Please input your username!' }]}
>
<Input />
</Form.Item>
<Form.Item<FieldTypeRegister>
label="Nickname"
name="displayName"
>
<Input />
</Form.Item>
<Form.Item<FieldTypeRegister>
label="Password"
name="password"
rules={[{ required: true, message: 'Please input your password!' }]}
>
<Input.Password />
</Form.Item>
<Form.Item wrapperCol={{ offset: 8, span: 16 }}>
<Button type="primary" htmlType="submit">
Submit
</Button>
</Form.Item>
</Form>
)
}
\ No newline at end of file
import { signInUser } from '@/features/usersSlice';
import { useAppDispatch } from '@/store/hook';
import {Button, Form, FormProps, Input} from 'antd'
export type FieldTypeSignIn = {
username: string;
password: string;
}
export const SignIn = () => {
const dispatch = useAppDispatch()
const onFinish: FormProps<FieldTypeSignIn>['onFinish'] = (values) => {
dispatch(signInUser(values))
}
return (
<Form
name="basic"
labelCol={{ span: 8 }}
wrapperCol={{ span: 16 }}
style={{ maxWidth: 600 }}
initialValues={{ remember: true }}
onFinish={onFinish}
autoComplete="off"
>
<Form.Item<FieldTypeSignIn>
label="Username"
name="username"
rules={[{ required: true, message: 'Please input your username!' }]}
>
<Input />
</Form.Item>
<Form.Item<FieldTypeSignIn>
label="Password"
name="password"
rules={[{ required: true, message: 'Please input your password!' }]}
>
<Input.Password />
</Form.Item>
<Form.Item wrapperCol={{ offset: 8, span: 16 }}>
<Button type="primary" htmlType="submit">
Submit
</Button>
</Form.Item>
</Form>
)
}
\ No newline at end of file
import { IUserState } from '@/features/usersSlice';
import { AppBar, Toolbar, Typography, styled } from '@mui/material';
import { Link } from 'react-router-dom';
......@@ -7,14 +8,17 @@ const StyledLink = styled(Link)(() => ({
['&:hover']: { color: 'inherit' },
}));
export function AppToolbar() {
export function AppToolbar({user}: {user: IUserState | null}) {
return (
<>
<AppBar position="fixed">
<Toolbar>
<Toolbar style={{display: 'flex', justifyContent: 'space-between'}}>
<Typography variant="h6" component={StyledLink} to={'/'}>
Computer parts shop
</Typography>
<Typography variant="h6" component={StyledLink} to={'/'}>
{user?.username}
</Typography>
</Toolbar>
</AppBar>
</>
......
import { Register } from "@/components/Register";
import { SignIn } from "@/components/SignIn";
import { useAppSelector } from "@/store/hook";
import { Row, Tabs, TabsProps } from "antd";
import { useEffect } from "react";
import { useNavigate } from "react-router-dom";
const items: TabsProps['items'] = [
{
key: '1',
label: 'Sign In',
children: <SignIn/>,
},
{
key: '2',
label: 'Registration',
children: <Register/>,
},
]
export const Auth = () => {
const navigate = useNavigate()
const {user} = useAppSelector(state => state.users)
useEffect(() => {
if(user) navigate('/')
}, [navigate, user])
return (
<Row
align={'middle'}
justify={'center'}
style={{height: '90vh'}}
>
<Tabs
defaultActiveKey="1"
items={items}
/>
</Row>
)
}
\ No newline at end of file
import { Link } from 'react-router-dom';
import { Typography, Grid, Button } from '@mui/material';
export function Products() {
return (
<Grid container direction="column" spacing={2}>
<Grid item container direction="row" justifyContent="space-between" alignItems="center">
<Grid item>
<Typography variant="h4">Products</Typography>
</Grid>
<Grid item>
<Button color="primary" component={Link} to={'/products/new'}>
Add product
</Button>
</Grid>
</Grid>
</Grid>
);
}
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
import { axiosApiClient } from "../helpers/axiosApiClient";
import { FieldTypeRegister } from "@/components/Register";
import { FieldTypeSignIn } from "@/components/SignIn";
export interface IUserState {
id: string;
username: string;
displayName: string;
token: string;
}
interface State {
user: IUserState | null;
error: Error | null;
loading: boolean;
}
const initialState: State = {
user: null,
error: null,
loading: false
}
export const registerUser = createAsyncThunk(
'users/register',
async (body: FieldTypeRegister) => {
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)
}
}
)
export const signInUser = createAsyncThunk(
'users/signInUser',
async (body: FieldTypeSignIn) => {
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)
}
}
)
export const validateToken = createAsyncThunk(
'users/validateToken',
async () => {
try {
const {data} = await axiosApiClient.get<IUserState>('/user/validateToken', {
headers: {
Authorization: localStorage.getItem('token')
}
})
console.log(data);
return data
} catch (e) {
throw new Error((e as Error).message)
}
}
)
const usersSlice = createSlice(
{
name: 'users',
initialState,
reducers: {},
extraReducers(builder) {
builder
// REGISTRATION
.addCase(registerUser.fulfilled, (state, action) => {
state.user = action.payload;
state.loading = false;
})
.addCase(registerUser.rejected, (state, action) => {
state.loading = false;
state.error = action.error as Error;
})
.addCase(registerUser.pending, (state) => {
state.loading = true;
})
// SIGN IN
.addCase(signInUser.fulfilled, (state, action) => {
state.user = action.payload;
state.loading = false;
})
.addCase(signInUser.rejected, (state, action) => {
state.loading = false;
state.error = action.error as Error;
})
.addCase(signInUser.pending, (state) => {
state.loading = true;
})
// VALIDATE TOKEN
.addCase(validateToken.fulfilled, (state, action) => {
state.user = action.payload;
state.loading = false;
})
.addCase(validateToken.rejected, (state, action) => {
state.loading = false;
state.error = action.error as Error;
})
.addCase(validateToken.pending, (state) => {
state.loading = true;
})
},
}
)
export default usersSlice.reducer;
import { configureStore } from '@reduxjs/toolkit';
import productsReducer from '../features/productsSlice.ts';
import usersReducer from '@/features/usersSlice.ts';
const store = configureStore({
reducer: {
products: productsReducer,
users: usersReducer
}
})
......
......@@ -27,4 +27,10 @@ export class UserController {
const user = await this.service.registration(registrationInDto)
res.send(user)
}
validateToken: RequestHandler = async (req, res): Promise<void> => {
const token = req.headers.authorization
const user = await this.service.validateToken(token || '')
res.send(user)
}
}
......@@ -37,6 +37,13 @@ export class UserRepo {
const userWithoutPass = _.omit(user, 'password')
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
}
}
export const userRepo = new UserRepo()
\ No newline at end of file
......@@ -15,5 +15,6 @@ export class UserRoute implements IRoute {
private init() {
this.router.post('/registration', this.controller.registration);
this.router.post('/signIn', this.controller.signIn);
this.router.get('/validateToken', this.controller.validateToken);
}
}
......@@ -10,4 +10,8 @@ export class UserService {
async registration(registrationUserDto: RegistrationUserDto) {
return await userRepo.registration(registrationUserDto)
}
async validateToken(token: string) {
return await userRepo.validateToken(token)
}
}
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