Skip to content

Commit

Permalink
Add authorization and ChatPage
Browse files Browse the repository at this point in the history
  • Loading branch information
AxeRicin committed Dec 4, 2023
1 parent fbd0150 commit 9067f08
Show file tree
Hide file tree
Showing 14 changed files with 680 additions and 9 deletions.
375 changes: 374 additions & 1 deletion frontend/package-lock.json

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"private": true,
"proxy": "http://localhost:5001",
"dependencies": {
"@reduxjs/toolkit": "^1.9.7",
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
Expand All @@ -12,9 +13,13 @@
"classnames": "^2.3.2",
"formik": "^2.4.5",
"react": "^18.2.0",
"react-bootstrap": "^2.9.1",
"react-bootstrap-icons": "^1.10.3",
"react-dom": "^18.2.0",
"react-redux": "^8.1.3",
"react-router-dom": "^6.20.0",
"react-scripts": "5.0.1",
"socket.io-client": "^4.7.2",
"web-vitals": "^2.1.4",
"yup": "^1.3.2"
},
Expand Down
22 changes: 14 additions & 8 deletions frontend/src/Components/App.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,27 @@
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import { Provider } from 'react-redux';
import getRoutes from '../routes.js';
import Layout from './Layout.js';
import NotfoundPage from '../page/NotfoundPage.js';
import LoginPage from '../page/LoginPage.js';
import AuthProvider from '../hoc/AuthProvider.js';
import ChatPage from '../page/ChatPage.js';
import store from '../store/store.js';

const App = () => (
<div className="d-flex flex-column h-100">
<AuthProvider>
<BrowserRouter>
<Routes>
<Route path={getRoutes.main()} element={<Layout />}>
<Route path="*" element={<NotfoundPage />} />
<Route path={getRoutes.login()} element={<LoginPage />} />
</Route>
</Routes>
</BrowserRouter>
<Provider store={store}>
<BrowserRouter>
<Routes>
<Route path={getRoutes.main()} element={<Layout />}>
<Route index element={<ChatPage />} />
<Route path="*" element={<NotfoundPage />} />
<Route path={getRoutes.login()} element={<LoginPage />} />
</Route>
</Routes>
</BrowserRouter>
</Provider>
</AuthProvider>
</div>
);
Expand Down
62 changes: 62 additions & 0 deletions frontend/src/Components/ChannelBtn.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import cn from 'classnames';

import { useSelector, useDispatch } from 'react-redux';
import {
Button, ButtonGroup, Dropdown,
} from 'react-bootstrap';
import { setCurrentChannel } from '../slices/channelSlice';

const ChannelBtn = ({ channel }) => {
const { currentChannelID } = useSelector((state) => state.channelsInfo);
const isCurrentChannel = (id) => currentChannelID === id;
const dispatch = useDispatch();

const hendlerClickForChannelBtn = (id) => () => dispatch(setCurrentChannel(id));

const classChannelName = {
'w-100': true,
'rounded-0': true,
'text-start': true,
'text-truncate': channel.removable,
'btn-secondary': isCurrentChannel(channel.id),
};
const classExpandedBtn = {
'flex-grow-0': true,
'dropdown-toggle': true,
'dropdown-toggle-split': true,
'btn-secondary': isCurrentChannel(channel.id),
};
if (channel.removable) {
return (
<li className="nav-item w-100">
<ButtonGroup className="d-flex dropdown">
<Button onClick={hendlerClickForChannelBtn(channel.id)} variant="" className={cn(classChannelName)}>
#
{' '}
{channel.name}
</Button>
<Dropdown>
<Dropdown.Toggle variant="" className={classExpandedBtn}>
<span className="visually-hidden">Управление каналом</span>
</Dropdown.Toggle>
<Dropdown.Menu>
<Dropdown.Item>Удалить</Dropdown.Item>
<Dropdown.Item>Переименовать</Dropdown.Item>
</Dropdown.Menu>
</Dropdown>
</ButtonGroup>
</li>
);
}

return (
<li className="nav-item w-100">
<Button onClick={hendlerClickForChannelBtn(channel.id)} variant="" className={classChannelName}>
<span className="me-1">#</span>
{channel.name}
</Button>
</li>
);
};

export default ChannelBtn;
30 changes: 30 additions & 0 deletions frontend/src/Components/Channels.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { useSelector } from 'react-redux';
import { PlusSquare } from 'react-bootstrap-icons';
import ChannelBtn from './ChannelBtn';

const Channels = () => {
const channelsInfo = useSelector((state) => state.channelsInfo);
const { channels } = channelsInfo;

return (
<div className="col-4 col-md-2 border-end px-0 bg-light flex-column h-100 d-flex">
<div className="d-flex mt-1 justify-content-between mb-2 ps-4 pe-2 p-4">
<b>Каналы</b>
<button className="p-0 text-primary btn btn-group-vertical" type="button">
<PlusSquare width="20" height="20" />
<span className="visually-hidden">+</span>
</button>
</div>
<ul id="channels-box" className="nav flex-column nav-pills nav-fill px-2 mb-3 overflow-auto h-100 d-block">
{channels.map((channel) => (
<ChannelBtn
key={channel.id}
channel={channel}
/>
))}
</ul>
</div>
);
};

export default Channels;
47 changes: 47 additions & 0 deletions frontend/src/Components/ChatChannel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { useSelector } from 'react-redux';
import NewMessageForm from './NewMessageForm';

const ChatChannel = () => {
const { channels, currentChannelID } = useSelector((state) => state.channelsInfo);
const { messages } = useSelector((state) => state.messagesInfo);

const currentChannel = channels.find((channel) => channel.id === currentChannelID);

const currentMessages = messages.filter((message) => message.channelId === currentChannelID);

return (
<div className="col p-0 h-100">
<div className="d-flex flex-column h-100">
<div className="bg-light mb-4 p-3 shadow-sm small">
<p className="m-0">
<b>
#
{' '}
{currentChannel.name}
</b>
</p>
<span className="text-muted">
{currentMessages.length}
{' '}
сообщения
</span>
</div>
<div id="messages-box" className="chat-messages overflow-auto px-5 ">
{currentMessages.map((message) => (
<div key={message.id} className="text-break mb-2">
<b>{message.username}</b>
:
{' '}
{message.body}
</div>
))}
</div>
<div className="mt-auto px-5 py-3">
<NewMessageForm />
</div>
</div>
</div>
);
};

export default ChatChannel;
9 changes: 9 additions & 0 deletions frontend/src/Components/Loader.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const Loader = () => (
<div className="h-100 d-flex justify-content-center align-items-center">
<div className="spinner-border text-primary" role="status">
<span className="visually-hidden">Загрузка</span>
</div>
</div>
);

export default Loader;
35 changes: 35 additions & 0 deletions frontend/src/Components/NewMessageForm.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { useFormik } from 'formik';
import { ArrowRightSquare } from 'react-bootstrap-icons';
import io from 'socket.io-client';

console.log(io);
const NewMessageForm = () => {
const formik = useFormik({
initialValues: {
body: '',
},
onSubmit: (values) => console.log('Сообщение отправлено: ', values),
});

return (
<form className="py-1 border rounded-2" onSubmit={formik.handleSubmit}>
<div className="input-group has-validation">
<input
className="border-0 p-0 ps-2 form-control"
name="body"
aria-label="Новое сообщение"
placeholder="Введите сообщение..."
type="text"
value={formik.values.body}
onChange={formik.handleChange}
/>
<button className="btn btn-group-vertical" type="submit" disabled="">
<ArrowRightSquare width="20" height="20" />
<span className="visually-hidden">Отправить</span>
</button>
</div>
</form>
);
};

export default NewMessageForm;
1 change: 1 addition & 0 deletions frontend/src/assets/add.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
42 changes: 42 additions & 0 deletions frontend/src/page/ChatPage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import axios from 'axios';
import { useEffect, useContext, useState } from 'react';
import { useDispatch } from 'react-redux';
import getRoutes from '../routes.js';
import { AuthContext } from '../hoc/AuthProvider';
import { addChennels } from '../slices/channelSlice';
import { addMessages } from '../slices/messagesSlice.js';
import Loader from '../Components/Loader.js';
import Channels from '../Components/Channels.js';
import ChatChannel from '../Components/ChatChannel.js';

const ChatPage = () => {
const { userToken } = useContext(AuthContext);
const dispatch = useDispatch();
const [isLoaded, setIsLoaded] = useState(false);

useEffect(() => {
const fetchData = async () => {
const response = await axios.get(getRoutes.data(), {
headers: {
Authorization: `Bearer ${userToken}`,
},
});
const { data } = response;
dispatch(addChennels(data));
dispatch(addMessages(data));
setIsLoaded(true);
};
fetchData();
});
if (!isLoaded) return (<Loader />);
return (
<div className="container h-100 my-4 overflow-hidden rounded shadow">
<div className="row h-100 bg-white flex-md-row">
<Channels />
<ChatChannel />
</div>
</div>
);
};

export default ChatPage;
1 change: 1 addition & 0 deletions frontend/src/routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ export default {
main: () => '/',
login: () => '/login',
signup: () => '/api/v1/login',
data: () => '/api/v1/data',
};
26 changes: 26 additions & 0 deletions frontend/src/slices/channelSlice.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/* eslint-disable no-param-reassign */
import { createSlice } from '@reduxjs/toolkit';

const initialState = {
channels: [],
currentChannelID: null,
};

export const channelSlice = createSlice({
name: 'channels',
initialState,
reducers: {
addChennels: (state, action) => {
const { channels, currentChannelId } = action.payload;
state.currentChannelID = currentChannelId;
state.channels = channels;
},
setCurrentChannel: (state, { payload: id }) => {
state.currentChannelID = id;
},
},
});

export const { addChennels, setCurrentChannel } = channelSlice.actions;

export default channelSlice.reducer;
22 changes: 22 additions & 0 deletions frontend/src/slices/messagesSlice.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/* eslint-disable no-param-reassign */

import { createSlice } from '@reduxjs/toolkit';

const initialState = {
messages: [],
};

export const messagesSlice = createSlice({
name: 'messages',
initialState,
reducers: {
addMessages: (state, action) => {
const { messages } = action.payload;
state.messages = messages;
},
},
});

export const { addMessages } = messagesSlice.actions;

export default messagesSlice.reducer;
12 changes: 12 additions & 0 deletions frontend/src/store/store.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { configureStore } from '@reduxjs/toolkit';
import channelReducer from '../slices/channelSlice';
import messagesReduser from '../slices/messagesSlice';

const store = configureStore({
reducer: {
channelsInfo: channelReducer,
messagesInfo: messagesReduser,
},
});

export default store;

0 comments on commit 9067f08

Please sign in to comment.