add

parent ddeeae31
......@@ -22,11 +22,13 @@
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-redux": "^9.1.0",
"react-router-dom": "^6.22.3"
"react-router-dom": "^6.22.3",
"redux-persist": "^6.0.0"
},
"devDependencies": {
"@types/react": "^18.2.43",
"@types/react-dom": "^18.2.17",
"@types/redux-persist": "^4.3.1",
"@typescript-eslint/eslint-plugin": "^6.20.0",
"@typescript-eslint/parser": "^6.14.0",
"@vitejs/plugin-react": "^4.2.1",
......
......@@ -47,6 +47,9 @@ importers:
react-router-dom:
specifier: ^6.22.3
version: 6.30.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
redux-persist:
specifier: ^6.0.0
version: 6.0.0(react@18.3.1)(redux@5.0.1)
devDependencies:
'@types/react':
specifier: ^18.2.43
......@@ -54,6 +57,9 @@ importers:
'@types/react-dom':
specifier: ^18.2.17
version: 18.3.6(@types/react@18.3.20)
'@types/redux-persist':
specifier: ^4.3.1
version: 4.3.1(react@18.3.1)(redux@5.0.1)
'@typescript-eslint/eslint-plugin':
specifier: ^6.20.0
version: 6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.2.2))(eslint@8.57.1)(typescript@5.2.2)
......@@ -778,6 +784,10 @@ packages:
'@types/react@18.3.20':
resolution: {integrity: sha512-IPaCZN7PShZK/3t6Q87pfTkRm6oLTd4vztyoj+cbHUF1g3FfVb2tFIL79uCRKEfv16AhqDMBywP2VW3KIZUvcg==}
'@types/redux-persist@4.3.1':
resolution: {integrity: sha512-YkMnMUk+4//wPtiSTMfsxST/F9Gh9sPWX0LVxHuOidGjojHtMdpep2cYvQgfiDMnj34orXyZI+QJCQMZDlafKA==}
deprecated: This is a stub types definition for redux-persist (https://github.com/rt2zz/redux-persist). redux-persist provides its own type definitions, so you don't need @types/redux-persist installed!
'@types/semver@7.7.0':
resolution: {integrity: sha512-k107IF4+Xr7UHjwDc7Cfd6PRQfbdkiRabXGRjo07b4WyPahFBZCZ1sE+BNxYIJPPg73UkfOsVOLwqVc/6ETrIA==}
......@@ -1989,6 +1999,15 @@ packages:
resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==}
engines: {node: '>=0.10.0'}
redux-persist@6.0.0:
resolution: {integrity: sha512-71LLMbUq2r02ng2We9S215LtPu3fY0KgaGE0k8WRgl6RkqxtGfl7HUozz1Dftwsb0D/5mZ8dwAaPbtnzfvbEwQ==}
peerDependencies:
react: '>=16'
redux: '>4.0.0'
peerDependenciesMeta:
react:
optional: true
redux-thunk@3.1.0:
resolution: {integrity: sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==}
peerDependencies:
......@@ -2969,6 +2988,13 @@ snapshots:
'@types/prop-types': 15.7.14
csstype: 3.1.3
'@types/redux-persist@4.3.1(react@18.3.1)(redux@5.0.1)':
dependencies:
redux-persist: 6.0.0(react@18.3.1)(redux@5.0.1)
transitivePeerDependencies:
- react
- redux
'@types/semver@7.7.0': {}
'@types/use-sync-external-store@0.0.6': {}
......@@ -4509,6 +4535,12 @@ snapshots:
dependencies:
loose-envify: 1.4.0
redux-persist@6.0.0(react@18.3.1)(redux@5.0.1):
dependencies:
redux: 5.0.1
optionalDependencies:
react: 18.3.1
redux-thunk@3.1.0(redux@5.0.1):
dependencies:
redux: 5.0.1
......
import { LogoutOutlined } from '@ant-design/icons';
import { AppBar, Toolbar, Typography, styled } from '@mui/material';
import { Avatar, Button, Dropdown, MenuProps, Row } from 'antd';
import { shallowEqual } from 'react-redux';
import { Link } from 'react-router-dom';
import { logout } from '../../features/usersSlice';
import { useAppDispatch, useAppSelector } from '../../store/hooks';
const StyledLink = styled(Link)(() => ({
color: 'inherit',
......@@ -8,13 +13,49 @@ const StyledLink = styled(Link)(() => ({
}));
export const AppToolBar = () => {
const dispatch = useAppDispatch();
const { user } = useAppSelector((state) => state.user, shallowEqual);
const items: MenuProps['items'] = [
{
label: (
<Button danger icon={<LogoutOutlined />} onClick={() => dispatch(logout())}>
Logout
</Button>
),
key: '0',
},
];
return (
<>
<AppBar position="fixed">
<Toolbar>
<Toolbar style={{ width: '100%' }}>
<Row justify={'space-between'} align={'middle'} style={{ width: '100%' }}>
{user ? (
<Typography variant="h6" component={StyledLink} to={'/'}>
Computer parts shop
</Typography>
) : (
<Typography variant="h6">Auth</Typography>
)}
{user && (
<Row align={'middle'}>
<Typography variant="h6" style={{ marginRight: '15px' }}>
Welcome {user?.username}
</Typography>
<Dropdown menu={{ items }} trigger={['click']}>
<Avatar size={40}>
<Typography style={{ textTransform: 'capitalize', fontSize: '20px' }}>
{user?.username[0]}
</Typography>
</Avatar>
</Dropdown>
</Row>
)}
</Row>
</Toolbar>
</AppBar>
</>
......
import { IUser } from '@/containers/Auth';
import { axiosApiClient } from '../helpers/axiosApiClient';
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
import { AxiosError, isAxiosError } from 'axios';
import { axiosApiClient } from '../helpers/axiosApiClient';
interface State {
user: IUser | null;
......@@ -57,6 +57,18 @@ export const login = createAsyncThunk(
}
);
export const logout = createAsyncThunk('auth/logout', async () => {
await axiosApiClient
.get('/auth/logout', {
headers: {
Authorization: `Bearer ${localStorage.getItem('token')}`,
},
})
.then(() => {
localStorage.clear();
});
});
const usersSlice = createSlice({
name: 'users',
initialState,
......@@ -101,6 +113,18 @@ const usersSlice = createSlice({
})
.addCase(refreshToken.pending, (state) => {
state.loading = true;
})
// LOGOUT TOKEN
.addCase(logout.fulfilled, (state) => {
state.user = null;
state.loading = false;
})
.addCase(logout.rejected, (state) => {
state.loading = false;
})
.addCase(logout.pending, (state) => {
state.loading = true;
});
},
});
......
......@@ -2,16 +2,19 @@ import React from 'react';
import ReactDOM from 'react-dom/client';
import { Provider } from 'react-redux';
import { BrowserRouter } from 'react-router-dom';
import { PersistGate } from 'redux-persist/integration/react';
import App from './App.tsx';
import './index.css';
import store from './store/index.tsx';
import store, { persistor } from './store/index.tsx';
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<BrowserRouter>
<App />
</BrowserRouter>
</PersistGate>
</Provider>
</React.StrictMode>
);
import { configureStore } from '@reduxjs/toolkit';
import {
FLUSH,
PAUSE,
PERSIST,
persistReducer,
persistStore,
PURGE,
REGISTER,
REHYDRATE,
} from 'redux-persist';
import storage from 'redux-persist/lib/storage';
import productsReducer from '../features/productsSlice';
import userReducer from '../features/usersSlice';
const persistConfig = {
key: 'root',
version: 1,
storage,
whitelist: ['user'],
};
const persistProducts = persistReducer(persistConfig, productsReducer);
const persistUser = persistReducer(persistConfig, userReducer);
const store = configureStore({
reducer: {
products: productsReducer,
user: userReducer,
products: persistProducts,
user: persistUser,
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: {
ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],
},
}),
});
export const persistor = persistStore(store);
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
export default store;
import { Module } from '@nestjs/common';
import { ProductModule } from './product/product.module';
import { CategoryModule } from './category/category.module';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Product } from './product/entities/product.entity';
import { AuthModule } from './auth/auth.module';
import { CategoryModule } from './category/category.module';
import { Category } from './category/entities/category.entity';
import { Product } from './product/entities/product.entity';
import { ProductModule } from './product/product.module';
import { SeedModule } from './seed/seed.module';
import { User } from './user/entities/user.entity';
import { UserModule } from './user/user.module';
import { AuthModule } from './auth/auth.module';
import { SeedModule } from './seed/seed.module';
@Module({
imports: [
TypeOrmModule.forRoot({
......@@ -16,7 +16,7 @@ import { SeedModule } from './seed/seed.module';
port: 5432,
username: 'postgres',
password: 'root',
database: 'test_server',
database: 'postgres',
schema: 'public',
entities: [Product, Category, User],
synchronize: true,
......
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
import {
Controller,
Post,
Body,
Controller,
Get,
UseGuards,
Post,
Request,
UseGuards,
} from '@nestjs/common';
import { JwtAuthGuard } from 'src/common/guards/jwt-auth.guarf';
import { AuthService } from './auth.service';
import { RegisterDto } from './dto/register.dto';
import { JwtAuthGuard } from 'src/common/guards/jwt-auth.guarf';
@Controller('auth')
export class AuthController {
......@@ -26,6 +26,13 @@ export class AuthController {
return this.authService.login(body.username, body.password);
}
@UseGuards(JwtAuthGuard)
@Get('logout')
logout(@Request() req) {
const username = req?.user?.username;
return this.authService.logout(username as string);
}
@UseGuards(JwtAuthGuard)
@Get('refreshToken')
refreshToken(@Request() req) {
......
......@@ -3,10 +3,10 @@ import {
NotFoundException,
UnauthorizedException,
} from '@nestjs/common';
import { RegisterDto } from './dto/register.dto';
import { UserService } from 'src/user/user.service';
import { JwtService } from '@nestjs/jwt';
import * as bcrypt from 'bcrypt';
import { UserService } from 'src/user/user.service';
import { RegisterDto } from './dto/register.dto';
const SALT_WORK_FACTOR = 10;
......@@ -39,6 +39,10 @@ export class AuthService {
return await this.userService.findOneByUserName(username);
}
async logout(username: string) {
return await this.userService.logout(username);
}
async login(username: string, password: string) {
const user = await this.userService.findOneByUserName(username);
if (!user) throw new NotFoundException(' Такого юзера нет ');
......
import { Injectable, NotFoundException } from '@nestjs/common';
import { CreateUserDto } from './dto/create-user.dto';
import { InjectRepository } from '@nestjs/typeorm';
import { User } from './entities/user.entity';
import { Repository } from 'typeorm';
import { CreateUserDto } from './dto/create-user.dto';
import { User } from './entities/user.entity';
@Injectable()
export class UserService {
......@@ -32,6 +32,11 @@ export class UserService {
return await this.userRepo.findOneBy({ username });
}
async logout(username: string): Promise<string> {
await this.userRepo.update({ username }, { refreshToken: '' });
return 'logout';
}
async remove(id: number): Promise<number> {
const user = await this.userRepo.findOneBy({ id });
......
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