50 Commits 8822f65971 ... a0c4f58fcb

Auteur SHA1 Message Date
  serg1557733 a0c4f58fcb fix counter + private messeges online and offline il y a 1 an
  serg1557733 2384242f40 Merge branch 'new-branch' il y a 1 an
  serg1557733 472afbdeab fix private messeges online and offline il y a 1 an
  serg1557733 06b6ca5785 fix private messages sending and geting, need to fix getting own messages merged with changes il y a 1 an
  serg1557733 92474d3ab1 fix private messages sending and geting, need to fix getting own messages il y a 1 an
  serg1557733 518b4e5d97 fix private messages sending and geting, need to fix getting own messages il y a 1 an
  serg1557733 f85e5ce150 fix user writing message in main chat il y a 1 an
  serg1557733 dc7791daee fix message reducer il y a 1 an
  serg1557733 cd27c3a02c fix socket connected for sending user data il y a 1 an
  serg1557733 ed397caa92 fix socket connected for sending user data il y a 1 an
  serg1557733 396a2ba88f add new branch changes il y a 1 an
  serg1557733 af455d9bf8 first il y a 1 an
  serg1557733 970dfd378b first il y a 1 an
  serg1557733 7e2f218817 clean reducer and change socket connektion il y a 1 an
  serg1557733 9b9fcb1af3 clean and fix reducer and socket connection il y a 1 an
  serg1557733 0af40e8d69 add friends functions to front il y a 1 an
  serg1557733 606ad72de2 add friends functions to front il y a 1 an
  serg1557733 f8bb0c8106 add friends functions and emits on server il y a 1 an
  serg1557733 a567ed0d96 add friends functions and emits on server il y a 1 an
  serg1557733 53af6fc604 add friends to db il y a 1 an
  serg1557733 797bec0054 add new button addToFriend il y a 1 an
  serg1557733 00f345a907 fix some bugs need to check camera sendings function il y a 1 an
  serg1557733 8b70cbc3a6 add new private message indicatorplus merge from newbranch il y a 1 an
  serg1557733 2c098125ad fix mobile style and add funktion for hide users info il y a 1 an
  serg1557733 07683ad661 add new private message indicator il y a 1 an
  serg1557733 9ecaead1cf test commit il y a 1 an
  serg1557733 4c28157dc0 add private messages counter labe il y a 1 an
  serg1557733 52f374141a add normal time format with relative date js il y a 1 an
  serg1557733 711a0f6511 fix some bugs v1 il y a 1 an
  serg1557733 0fdf6c590b fix private messages need to fix sort and messages for myself il y a 1 an
  serg1557733 de487c8969 fix private chat header il y a 1 an
  serg1557733 3134362900 add dataReducer and private chat header v1 and fix some bugs with socket il y a 1 an
  serg1557733 93cb1909f1 fix main chat and some dispatch events il y a 1 an
  serg1557733 ea1211ac06 add find users component and function il y a 1 an
  serg1557733 a8ab9f1ac1 add find users component and function fix il y a 1 an
  serg1557733 4413debfb5 add find users component and function il y a 1 an
  serg1557733 b672ac3d1e private chats find all user start task il y a 1 an
  serg1557733 8309927b54 private chats il y a 1 an
  serg1557733 25267ecf2f need to fix reciving private messages il y a 1 an
  serg1557733 298038298b fix mongoose DB private messages find method and send to front il y a 1 an
  serg1557733 b2e4e8053a need to fix private messages new message il y a 1 an
  serg1557733 789fe4bb6a need to fix private messages array il y a 1 an
  serg1557733 62305d639a need to fix private messages array il y a 1 an
  serg1557733 0d484e2918 add db private messages il y a 1 an
  serg1557733 3bb49788d0 test private messages ok il y a 1 an
  serg1557733 208dc3fd6e start private messagin function il y a 1 an
  serg1557733 67e40fc2bf refactoring code and new components il y a 1 an
  serg1557733 0bdb1aef56 refactoring code and new components il y a 1 an
  serg1557733 b5c9fc0284 new styles and start privat message reducer il y a 1 an
  serg1557733 312849f3b0 fix onlineUsers and styles il y a 1 an
31 fichiers modifiés avec 1072 ajouts et 266 suppressions
  1. 164 14
      backend/app.js
  2. 13 0
      backend/avatars/PrivateMessage.js
  3. 13 0
      backend/db/models/PrivateMessage.js
  4. 2 1
      backend/db/models/User.js
  5. 20 2
      frontend/src/App.jsx
  6. 63 23
      frontend/src/components/chatPage/ChatPage.jsx
  7. 1 1
      frontend/src/components/chatPage/SwitchButton.jsx
  8. 17 0
      frontend/src/components/chatPage/YoutubeMessage.jsx
  9. 10 7
      frontend/src/components/chatPage/chatPage.scss
  10. 33 0
      frontend/src/components/chatPage/generalChat/AddToFriends.jsx
  11. 88 0
      frontend/src/components/chatPage/generalChat/AdminUserInfiButton.jsx
  12. 29 0
      frontend/src/components/chatPage/generalChat/FindUserBox.jsx
  13. 22 0
      frontend/src/components/chatPage/generalChat/MainChatButtton.jsx
  14. 97 0
      frontend/src/components/chatPage/generalChat/UserInfoButton.jsx
  15. 48 0
      frontend/src/components/chatPage/generalChat/userInfo.scss
  16. 3 5
      frontend/src/components/chatPage/messageForm/MessegaForm.jsx
  17. 12 0
      frontend/src/components/chatPage/messageForm/userInfo.scss
  18. 30 0
      frontend/src/components/chatPage/privateChat/PrivatChatHeader.jsx
  19. 152 0
      frontend/src/components/chatPage/privateChat/PrivateChat.jsx
  20. 16 0
      frontend/src/components/chatPage/privateChat/userInfo.scss
  21. 64 104
      frontend/src/components/chatPage/userInfo/UserInfo.jsx
  22. 29 1
      frontend/src/components/chatPage/userInfo/userInfo.scss
  23. 21 8
      frontend/src/components/chatPage/utils/dateFormat.js
  24. 2 0
      frontend/src/index.js
  25. 29 0
      frontend/src/reducers/dataReducers.js
  26. 8 25
      frontend/src/reducers/messageReducer.js
  27. 15 69
      frontend/src/reducers/socketReducer.js
  28. 12 5
      frontend/src/reducers/userDataReducer.js
  29. 2 1
      frontend/src/store.js
  30. 16 0
      frontend/src/utils/messagesSocketEvents.js
  31. 41 0
      frontend/src/utils/socketsEvents.js

+ 164 - 14
backend/app.js

@@ -6,6 +6,7 @@ const mongoose = require('mongoose');
 const socket = require("socket.io");
 const User = require('./db/models/User');
 const Message = require('./db/models/Message');
+const PrivateMessage = require('./db/models/PrivateMessage')
 const jwt = require('jsonwebtoken');
 const bcrypt = require('bcrypt');
 require('dotenv').config(); // add dotnv for config
@@ -201,14 +202,13 @@ io.use( async (socket, next) => {
     const token = socket.handshake.auth.token; 
     const sockets = await io.fetchSockets();
     if(!token) {
-        console.log('socket disconnect')
+        console.log('token error - socket disconnect')
         socket.disconnect();
         return;
     }
     
     const usersOnline = [];
     sockets.map(sock => usersOnline.push(sock.user))
-
    
     try {
         const user = jwt.verify(token, TOKEN_KEY);
@@ -239,19 +239,53 @@ io.on("connection", async (socket) => {
     const userName = socket.user.userName;
     const sockets = await io.fetchSockets();
     const dbUser = await getOneUser(userName);
-    const exist = sockets.find(current => current.user.userName == socket.user.userName)
-    const usersOnline = sockets.map(sock => sock.user)
+    const allUsers = await getAllDbUsers(socket) // send allUsers from DB to socket user
+    //need to use this ID to socket privat messges
+ 
+    socket.emit('connected', dbUser); //socket.user
+    const usersInSocket = [];
+        for (let [id, socket] of io.of("/").sockets) {
+            
+            const dbUser = await getOneUser(socket.user.userName)
+            usersInSocket.push({...dbUser._doc,socketId: id });
+            
+        }
+// const onUser = []
+//     const usersOnlineID = usersOnline.map(users => Object.values(users)[0])
+//     const userSet = new Set(usersOnlineID)
+//     for (let id of userSet) {
+//         const userFromDb = await User.findById(id)
+//         onUser.push(userFromDb)
+//     }
 
 
-    const usersOnlineID = usersOnline.map(users => Object.values(users)[0])
-    const userSet = new Set(usersOnlineID)
+//tasks 
+// find private chats and send to users
 
-    console.log(userSet)
+// if(socket.user){
+//      const siPrivate = await PrivateMessage.find({toUser: socket.user.id})
+//      console.log(!!siPrivate)
+// }
 
+    io.emit('usersOnline', usersInSocket); // send array online users  
 
-    io.emit('usersOnline', usersOnline); // send array online users  
+    dbUser.populate({path:'friends'}).then(res => socket.emit('friends',res.friends)) 
+
+    //send private chats for user
+
+    const privateChats = await PrivateMessage.find( {$or:[ {toUser: dbUser._id}, {fromUser: dbUser._id }],foreignField: '_id'}).populate( ['fromUser','toUser'])//need to optimal way found
+
+    const myChats = []
+// privateChats.map((item, i) => {
+//     console.log(item)
+    
+// })
+///
+// console.log(myChats)
+ //console.log(privateChats)
+
+socket.emit('my chats', privateChats)
 
-    socket.emit('connected', dbUser); //socket.user
   
     if(socket.user.isAdmin){
          getAllDbUsers(socket); 
@@ -307,7 +341,7 @@ io.on("connection", async (socket) => {
             const filteredUsersOnline = usersOnline.filter(user => exist.user.id !== user.id)
         
            
-           socket.emit('usersOnline', filteredUsersOnline);
+        //   io.emit('usersOnline', filteredUsersOnline);
 
             // const sockets = await io.fetchSockets();
             // io.emit('usersOnline', sockets.map(sock => sock.user));
@@ -315,6 +349,7 @@ io.on("connection", async (socket) => {
 
         });
             console.log(`user :${socket.user.userName} , connected to socket`); 
+
         socket.on("muteUser",async (data) => {
             if(!socket.user.isAdmin){
                 return;
@@ -333,13 +368,123 @@ io.on("connection", async (socket) => {
                 // }
            });
 
-        socket.on('privat', (data) => {
-            console.log(data)
-        
-        })
 
+        socket.on('privat chat', async (data) => {
+            //find user to in Db
+            const privateMessagesToUser = await PrivateMessage.find({toUser: {$in:[data.user._id, data.toUser._id]}, fromUser: {$in:[data.user._id, data.toUser._id]}}).sort({ 'createDate': 1 })
+            //find user from in db
+            //compare users and if messages is - send 
+            socket.emit('send privat messages', privateMessagesToUser)
+            
+          })
 
+        socket.on("private message", async ({ fromUser, from, message, toUser , to}) => {
+//create message and save to DB      
+            const privateMessage = new PrivateMessage({
+                text:  message,
+                createDate: Date.now(),
+                fromUser,
+                toUser
+            });
+            await privateMessage.save()
+          //emit event 
+          
+        const privateMessageSentUser = await User.find({_id: fromUser }) // send from user what messaged
+       // const privateMessagesToUser = await PrivateMessage.find({toUser: {$in:[fromUser._id, toUser._id]}, fromUser: {$in:[fromUser._id,toUser._id]}}).sort({ 'createDate': 1 })
+        socket.to(toUser?.socketId).emit('private', {...privateMessage._doc, sender: privateMessageSentUser });
+        //socket.to(fromUser?.socketId).emit('private', {...privateMessage._doc, sender: privateMessageSentUser });
+
+
+// fix time start and messages after private 
+
+//********EXEMPLE DOCUMENTATION
+
+// Persistent messages
+// On the server-side (server/index.js), we now persist the message in our new store:
+
+// io.on("connection", (socket) => {
+//   // ...
+//   socket.on("private message", ({ content, to }) => {
+//     const message = {
+//       content,
+//       from: socket.userID,
+//       to,
+//     };
+//     socket.to(to).to(socket.userID).emit("private message", message);
+//     messageStore.saveMessage(message);
+//   });
+//   // ...
+// });
+
+// And we fetch the list of messages upon connection:
+
+// io.on("connection", (socket) => {
+//   // ...
+//   const users = [];
+//   const messagesPerUser = new Map();
+//   messageStore.findMessagesForUser(socket.userID).forEach((message) => {
+//     const { from, to } = message;
+//     const otherUser = socket.userID === from ? to : from;
+//     if (messagesPerUser.has(otherUser)) {
+//       messagesPerUser.get(otherUser).push(message);
+//     } else {
+//       messagesPerUser.set(otherUser, [message]);
+//     }
+//   });
+//   sessionStore.findAllSessions().forEach((session) => {
+//     users.push({
+//       userID: session.userID,
+//       username: session.username,
+//       connected: session.connected,
+//       messages: messagesPerUser.get(session.userID) || [],
+//     });
+//   });
+//   socket.emit("users", users);
+//   // ...
+// });
+
+
+//*********** PRIVAT EXEMPLE */      
+
+
+
+            //add send messages to myself   socket.emit('send privat messages', privateMessagesToUser)     
+
+            // //send new messages array to user
+
+            // const privateMessagesToUser = await PrivateMessage.find({toUser: {$in:[fromUser._id, toUser._id]}, fromUser: {$in:[fromUser._id, toUser._id]}}).sort({ 'createDate': 1 })
+
+            //socket.emit('private', {privateMessageSentUser, fromUser})
+
+          });
+
+//add and remove friends functions
+
+          socket.on('addToFriends', async (data) => {
+            const isFriend  =  await User.find({userName:userName,friends: {'_id':data.user._id}})
+            if(!!isFriend.length){
+                    await User.findOneAndUpdate({userName: userName},{$set: {'friends':  []}}, {new: true })
+                } 
+            await dbUser.friends.push({'_id':data.user._id})
+            await dbUser.save()
+            await User.findOne({userName}).populate({path:'friends'}).then(res => socket.emit('friends',res.friends) )
+            
+        }) 
+
+        //need to fix - removed all users from frend only clicked not removed
+          
+        socket.on('removeFromFriends', async(user) => {
+            const res = await User.updateOne({ userName}, {
+                $pullAll: {
+                    friends: [{_id: user.user._id}],
+                },
+            });
+         await User.findOne({userName}).populate({path:'friends'}).then(res => socket.emit('friends',res.friends)) 
+
+    })
     
+//admin functions 
+
         socket.on("banUser",async (data) => {
             if(!socket.user.isAdmin){
                 return;
@@ -359,11 +504,16 @@ io.on("connection", async (socket) => {
             // }
            });
 
+
+
+
            socket.on('userWriting', async () => {
                 let isTyping = true;
                 io.emit('writing', {userName, isTyping})
            })
 
+// edit and remove messages
+
            socket.on('editmessage', async (data) => {
             console.log(data.messageNewText)
             const user = jwt.verify(data.token, TOKEN_KEY)

+ 13 - 0
backend/avatars/PrivateMessage.js

@@ -0,0 +1,13 @@
+const {model, Schema} = require('mongoose');
+
+const PrivateMessage = new Schema({
+    text: {type: String, required: true},
+    createDate: {type: Date, required: true},
+    fromUser:{ type: Schema.Types.ObjectId, ref: 'User' },
+    toUser:{ type: Schema.Types.ObjectId, ref: 'User' },
+    file: {type: String} , // not in use
+    fileType: {type: String} //not in use
+
+})
+
+module.exports = model('PrivateMessage', PrivateMessage)

+ 13 - 0
backend/db/models/PrivateMessage.js

@@ -0,0 +1,13 @@
+const {model, Schema} = require('mongoose');
+
+const PrivateMessage = new Schema({
+    text: {type: String, required: true},
+    createDate: {type: Date, required: true},
+    fromUser:{ type: Schema.Types.ObjectId, ref: 'User' },
+    toUser:{ type: Schema.Types.ObjectId, ref: 'User' },
+    file: {type: String} , // not in use
+    fileType: {type: String} //not in use
+
+})
+
+module.exports = model('PrivateMessage', PrivateMessage)

+ 2 - 1
backend/db/models/User.js

@@ -7,7 +7,8 @@ const User = new Schema({
     isBanned: {type: Boolean, default: false},
     isMutted: {type: Boolean, default: false},
     avatar: {type: String, unique: true, required: false},
-    messages: [{type: Schema.Types.ObjectId, ref: 'Message' }]
+    messages: [{type: Schema.Types.ObjectId, ref: 'Message' }],
+    friends: [{ type: Schema.Types.ObjectId, ref: 'User' }]
 })
 
 module.exports = model('User', User)

+ 20 - 2
frontend/src/App.jsx

@@ -1,11 +1,29 @@
 import { LoginForm } from './components/loginForm/LoginForm';
 import { ChatPage } from './components/chatPage/ChatPage';
 import { useSelector } from 'react-redux';
+import {io} from 'socket.io-client';
+import { useDispatch } from 'react-redux';
+import { getSocket } from './reducers/socketReducer';
+import { useEffect } from 'react';
 
 export default function App() {
-
     const token = useSelector(state => localStorage.getItem('token') || state.userDataReducer.token);
+    const SOCKET_URL = process.env.REACT_APP_SERVER_URL;
+
+    const dispatch = useDispatch();
+
+   useEffect(() => {
+    if(token){
+        const socket = io.connect(    
+                SOCKET_URL, 
+                {auth: {token}})
+                if(socket){
+                  dispatch(getSocket(socket))  
+                }        
+    }
+ },[token])
+
     
-    return token ? <ChatPage /> : <LoginForm/>
+   return token ? <ChatPage /> : <LoginForm/>
 };
 

+ 63 - 23
frontend/src/components/chatPage/ChatPage.jsx

@@ -4,7 +4,7 @@ import TextareaAutosize from '@mui/material/TextareaAutosize';
 import { MessageForm } from './messageForm/MessegaForm';
 import { UserInfo } from './userInfo/UserInfo';
 import { store } from '../../store';
-import { removeToken} from '../../reducers/userDataReducer'
+import { removeToken, isPrivatChat, privateMessage} from '../../reducers/userDataReducer'
 import { useDispatch, useSelector } from 'react-redux';
 import {getSocket} from'../../reducers/socketReducer';
 import { sendMessage, storeMessage, fileMessage } from '../../reducers/messageReducer';
@@ -17,30 +17,37 @@ import './chatPage.scss';
 import WebcamCapture from './service/webCam/WebcamCapture';
 import useSound from 'use-sound';
 import getNotif from '../../assets/sendSound.mp3'
+import { PrivateChat } from './privateChat/PrivateChat';
+import { PrivatChatHeader } from './privateChat/PrivatChatHeader';
+import { socketEvents } from '../../utils/socketsEvents';
+import { addNewPrivateMessage } from '../../reducers/socketReducer';
 
 export const ChatPage = () => {
 
-    const SOCKET_EVENTS = ['allmessages', 'usersOnline', 'allDbUsers']
-
     const dispatch = useDispatch();
-
     const token = useSelector(state => localStorage.getItem('token') || state.userDataReducer.token);
     const user = useSelector(state => state.getUserSocketReducer.socketUserData)
     const socket = useSelector(state => state.getUserSocketReducer.socket)
     const editOldMessage = useSelector(state => state.messageReducer.editMessage)
     let showUserInfoBox = useSelector(state => state.messageReducer.showUserInfoBox)// || localStorage.getItem('showBox');
+    const toUser = useSelector(state => state.userDataReducer.toUser)
+    const chatId = useSelector(state => state.userDataReducer.toUser.socketId)
+    const isPrivatChat = useSelector(state => state.userDataReducer.isPrivatChat)
+    const newPrivateMessages = useSelector(state => state.getUserSocketReducer.newPrivateMessages)
 
     const [message, setMessage] = useState({message: ''});
     const [isUserTyping, setUserTyping] = useState([]);
     const [isCamActiv, setisCamActiv] = useState(false);
     const [showSpinner, setshowSpinner] = useState(false);
     const [loadingPercentage, setLoadPercentage] = useState(0)
-    
+
+    const usersOnline = useSelector(state => state.getUserSocketReducer.usersOnline);
+
     const isTabletorMobile = (window.screen.width < 730);
+    const isNewMessage = newPrivateMessages.length > 0
 
     const [play] = useSound(getNotif, {volume: 0.005});
 
-
     const axiosConfig =   {
         headers: {
             "Content-type": "multipart/form-data"
@@ -67,6 +74,39 @@ export const ChatPage = () => {
             setisCamActiv(!isCamActiv) // test camera
     }
 
+    const sendPrivateMessage = () => {
+        
+        const toUserSocket = usersOnline.find(user => user._id == toUser._id)|| toUser
+        const fromUserSocket = usersOnline.find(userInSocket => userInSocket._id == user._id)
+
+        ///***need to fix  sending own messages to me
+
+
+        // store.dispatch(addNewPrivateMessage({
+        //     fromUser: fromUserSocket,
+        //     message: message.message,
+        //     to: chatId,
+        //     toUser:toUserSocket
+        //   }))
+
+
+        socket.emit("private message", {
+            fromUser: fromUserSocket,
+            message: message.message,
+            to: chatId,
+            toUser:toUserSocket
+                })
+
+        if(toUserSocket){
+            socket.emit('privat chat', {
+                    user: fromUserSocket,
+                    to: chatId,
+                    toUser: toUserSocket
+                })
+        }
+       
+        
+    }
 
     useEffect(() => {
         if(socket) {
@@ -75,18 +115,18 @@ export const ChatPage = () => {
                     setTimeout(() => setUserTyping([]), 500 )
                 })  
         }
-   }, [socket])
+   }, [socket, token])
 
 
+ 
     useEffect(() => {
-   
-        if(token){
-            SOCKET_EVENTS.map(event => dispatch(getSocket(event)))   
+        if(token && socket){
+            socketEvents(socket)
         }
-    }, [token, editOldMessage, showUserInfoBox])
+    }, [token, socket, user])
+
+
 
-  
- 
     return (
         
         <div className='rootContainer'>
@@ -97,7 +137,7 @@ export const ChatPage = () => {
             <Box className={isTabletorMobile?'usersBoxMobile':'usersBox'}
                   sx = {showUserInfoBox ? {
                         transform: "translateX(100%)",
-                        display: "none"
+                        display: "block"
                         }: { }}>      
                     <UserInfo/> 
                     { isTabletorMobile ? <SwitchButton/> : null}
@@ -138,9 +178,9 @@ export const ChatPage = () => {
                  </div> 
                  :
                  ""}
-                    <MessageForm/>
-
-                    {isUserTyping.isTyping && (isUserTyping.userName !== user.userName)? <span> User {isUserTyping.userName} typing..</span> : ""}
+                    {isPrivatChat? <PrivateChat/>   : <MessageForm/>}
+                   
+                    {isUserTyping.isTyping && !isPrivatChat &&(isUserTyping.userName !== user.userName)? <span> User {isUserTyping.userName} typing..</span> : ""}
 
                     <Box 
 
@@ -148,9 +188,9 @@ export const ChatPage = () => {
                         onSubmit = {e  => {
                                         e.preventDefault()
                                         if (message.message.length){
-                                            dispatch(sendMessage({user, socket}))
-                                            dispatch(getSocket('allmessages'))
-                                            dispatch(editMessage({editMessage: ''}))
+                                            isPrivatChat? sendPrivateMessage() : dispatch(sendMessage({user, socket}));
+                                           // isPrivatChat && dispatch(getSocket('allmessages'))
+                                            isPrivatChat &&dispatch(editMessage({editMessage: ''}))
                                             setMessage({message: ''})
                                             play()
                                         }
@@ -224,15 +264,15 @@ export const ChatPage = () => {
                             onKeyPress={(e) => {
                                 if (e.key === "Enter")   {
                                     e.preventDefault();
-                                    dispatch(sendMessage({user, socket}))
-                                    dispatch(getSocket('allmessages'))
+                                    isPrivatChat? sendPrivateMessage() : dispatch(sendMessage({user, socket}));
+                                    //dispatch(getSocket('allmessages'))
                                     dispatch(editMessage({editMessage: ''}))
                                     setMessage({message: ''})
                                 }
                             }}
                             onChange={e => { 
                                 dispatch(storeMessage({message: e.target.value}))
-                                socket.emit('userWriting');
+                               !isPrivatChat &&socket.emit('userWriting');
                                 setMessage({message: e.target.value})}
                             } 
                             // onFocus={ e => {

+ 1 - 1
frontend/src/components/chatPage/SwitchButton.jsx

@@ -14,7 +14,7 @@ export const SwitchButton = () => {
     return (
         <div>  
 
-            <label>Infobar</label>
+            <label>Hide users</label>
 
             <Switch {...label} size="small"  onChange={() => handleChange()} />
         </div>

+ 17 - 0
frontend/src/components/chatPage/YoutubeMessage.jsx

@@ -0,0 +1,17 @@
+export const YoutubeMessage = (item) => {
+    
+    const regYoutube = /http(?:s?):\/\/(?:www\.)?youtu(?:be\.com\/watch\?v=|\.be\/)([\w\-\_]*)(&(amp;)?‌​[\w\?‌​=]*)?/; //for youtube video
+
+    return (
+        <iframe 
+            width="280" 
+            height="160" 
+            style={{'maxWidth': "90%"}}
+            src={`https://www.youtube.com/embed/`+ (item.text.match(regYoutube)[1])}
+            title="YouTube video player" 
+            allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" 
+            allowFullScreen> 
+            
+        </iframe>
+    )
+}

+ 10 - 7
frontend/src/components/chatPage/chatPage.scss

@@ -114,7 +114,7 @@
     position: relative;
     padding: 20px;
     border-radius: 10px;
-    background-color:rgb(243, 243, 243);
+    background-color:rgb(229, 232, 233);
     display: flex;
     flex-grow :2;
     flex-direction: column;
@@ -130,15 +130,16 @@
     overflow: scroll;
     border-radius: 10px;
     border: 2px solid grey; 
-    background-color:rgb(243, 243, 243);
+    background-color:rgb(30, 45, 52);
     .online {
-        border-radius: 5px;
+        position: relative;
+        border-radius: 3px;
         padding: 5px;
-        border: 2px solid grey; 
+        border:2px solid rgb(255, 255, 255); 
         margin-bottom: 5px;
         cursor: pointer;
-        background-color:rgb(247, 233, 233);
         font-weight: 700;
+        color:aliceblue;
 
     }
 }
@@ -152,15 +153,17 @@
     font-size: 11px;
     border-radius: 10px;
     border: 2px solid grey; 
-    background-color:rgb(243, 243, 243);
+    background-color:rgb(7, 42, 60);
+    color:rgb(239, 239, 233);
     transition: all 3s ease-in-out;    
     .online {
+        position: relative;
         border-radius: 5px;
         text-overflow: ellipsis;
         padding: 5px;
         border: 2px solid grey;
         margin-bottom: 5px;
-        background-color:rgb(247, 233, 233);
+        background-color:rgb(7, 42, 60);;
         font-weight: 500;
 
     }

+ 33 - 0
frontend/src/components/chatPage/generalChat/AddToFriends.jsx

@@ -0,0 +1,33 @@
+import { useState } from "react";
+import { useSelector } from 'react-redux';
+
+
+export const AddToFriends = (user) => {
+
+    const socket = useSelector(state => state.getUserSocketReducer.socket)
+
+    const isTabletorMobile = (window.screen.width < 730);
+
+    const usersFriends = useSelector(state => state.getUserSocketReducer.socketUserData).friends
+
+    let isMyFriend = false;
+    if(usersFriends) {
+        isMyFriend = usersFriends.find(friend => friend._id === user.user._id)
+    }
+
+    const [isFriend, setIsFreind] = useState(false)
+   
+    return (
+        <div onClick={() => {
+                            setIsFreind(!isFriend)
+                            isFriend ? socket.emit('removeFromFriends', user) : socket.emit('addToFriends', user)       
+            }} >
+            <div className= {isTabletorMobile ?'mobileButton addToFriendsButton': "addToFriendsButton" } 
+               style ={{backgroundColor:(isFriend || isMyFriend? 'red': '#1976d3' )}}
+            >
+                
+            </div>
+        </div>
+      
+    )
+}

+ 88 - 0
frontend/src/components/chatPage/generalChat/AdminUserInfiButton.jsx

@@ -0,0 +1,88 @@
+import {Button} from '@mui/material';
+import { store } from "../../../store";
+import { privateMessage } from "../../../reducers/userDataReducer";
+import { useSelector } from "react-redux";
+import { banUser } from '../service/banUser';
+import { muteUser } from '../service/muteUser';
+import './userInfo.scss';
+
+
+export const AdminUserInfiButton = ({item, i}) => {
+
+
+    const user = useSelector(state => state.getUserSocketReducer.socketUserData)
+    const usersOnline = useSelector(state => state.getUserSocketReducer.usersOnline);
+    const socket = useSelector(state => state.getUserSocketReducer.socket)
+    const isTabletorMobile = (window.screen.width < 730);
+    const chatId = useSelector(state => state.userDataReducer.chatId)
+    const isPrivatChat = useSelector(state => state.userDataReducer.isPrivatChat)
+    const arrUsersOnline = usersOnline.map( i => i?.userName)
+    const userNamesOnlineSet =  new Set(arrUsersOnline)
+
+
+    return(
+        <div 
+            key={item._id}
+            className={isPrivatChat&&(chatId === item._id)? 'online active' :'online' }                       
+            onClick={() => {
+                console.log(item._id, user._id)
+                store.dispatch(privateMessage({chatId: item._id}))
+                socket.emit('privat chat', {
+                    user,
+                    to: item._id,
+                    })
+            }}>
+                <div>
+                    {item.userName}
+                </div>
+
+                <div>
+                    {(user.userName === item.userName )?   
+                        "admin"
+                    :   
+                    <>      
+                        <Button
+                            variant="contained"
+                            onClick={()=>{
+                                muteUser(item.userName, item?.isMutted, socket)
+                            }}
+                            sx={(isTabletorMobile) 
+                                ? 
+                                {height: '15px',
+                                    maxWidth:'20px'}: 
+                                {
+                                margin:'3px',
+                                height: '25px'}}>
+
+                                {item.isMutted
+                                ? 
+                                'unmute'
+                                : 'mute'}
+                        </Button>
+
+                        <Button
+                            variant="contained"
+                            onClick={()=>{ 
+                                banUser(item.userName, item.isBanned, socket)
+                            }}
+                            sx={(isTabletorMobile) 
+                                ? 
+                                {height: '15px',
+                                margin:'2px'} : 
+                                {
+                                margin:'3px',
+                                height: '25px'}}
+                        >
+                                {item?.isBanned ? 'unban' : 'ban'}
+                        </Button> 
+                    </>}
+            
+                </div>
+                    {
+                    userNamesOnlineSet.has(item.userName)? 
+                    <span key={i} style={{color: 'green'}}>online</span>
+                    : ''
+                    }
+        </div>
+    )
+}

+ 29 - 0
frontend/src/components/chatPage/generalChat/FindUserBox.jsx

@@ -0,0 +1,29 @@
+import { useSelector } from 'react-redux';
+import { useState } from 'react';
+import './userInfo.scss';
+import {UserInfoButton} from './UserInfoButton';
+
+
+export const FindUserBox = () => {
+
+    const allUsers = useSelector(state => state.getUserSocketReducer.allUsers)
+    const [findUser, setfindUser] = useState('');
+    const [showUsers, setShowUsers] = useState(false)
+    const res = allUsers.filter(user =>  user.userName.toLowerCase().includes(findUser.toLowerCase()))
+    return (
+        <>
+            <div className='online'>  
+                <div>Find users for chat</div>
+                <input style={{width:'80%'}}
+                        value = {findUser} 
+                        onChange={(e) => {
+                            setfindUser(e.target.value)
+                            setShowUsers(true)
+                            }} >
+                    
+                </input>
+            </div>
+            {showUsers && findUser.length > 0  && res.map((item, i) => <UserInfoButton item = {item} i = {i}  key={i}/> )}
+        </>
+    )
+}

+ 22 - 0
frontend/src/components/chatPage/generalChat/MainChatButtton.jsx

@@ -0,0 +1,22 @@
+import { useSelector } from 'react-redux';
+import { generalMessage } from '../../../reducers/userDataReducer';
+import './userInfo.scss';
+import { store } from '../../../store';
+
+export const MainChatButtton = () => {
+
+    const isPrivatChat = useSelector(state => state.userDataReducer.isPrivatChat)
+
+    return (
+        <div 
+            className={!isPrivatChat? 'online active' :'online' }                       
+            onClick={() => {
+              //  store.dispatch(getSocket('allmessages'))
+                store.dispatch(generalMessage())
+            }}
+        >  
+            <div>Main Chat</div>
+            <span style={{color: 'green'}}> for all </span>
+        </div>
+    )
+}

+ 97 - 0
frontend/src/components/chatPage/generalChat/UserInfoButton.jsx

@@ -0,0 +1,97 @@
+import { store } from "../../../store";
+import { privateMessage } from "../../../reducers/userDataReducer";
+import { useSelector } from "react-redux";
+import { StyledAvatar } from "../messageForm/StyledAvatar";
+import { Avatar } from "@mui/material";
+import { useState, useEffect } from "react";
+import {selectedUser} from "../../../reducers/dataReducers";
+
+import {isNewPrivateMessages} from "../../../reducers/dataReducers";
+
+import { AddToFriends } from "./AddToFriends";
+
+export const UserInfoButton = ({item, i}) => {
+
+
+    const SERVER_URL = process.env.REACT_APP_SERVER_URL
+
+    const user = useSelector(state => state.getUserSocketReducer.socketUserData)
+    const socket = useSelector(state => state.getUserSocketReducer.socket)
+    const isPrivatChat = useSelector(state => state.userDataReducer.isPrivatChat)
+    const chatId = useSelector(state => state.userDataReducer.toUser.socketId)
+    const storeUserAvatar = useSelector(state => state.userDataReducer.avatar)
+    const usersOnline = useSelector(state => state.getUserSocketReducer.usersOnline)
+
+    const userNamesOnlineSet =  new Set(usersOnline.map( i => i.userName))
+
+    let userAvatarUrl = storeUserAvatar || user.avatar;
+
+    
+    const newPrivateMessagesArray = useSelector(state => state.getUserSocketReducer.newPrivateMessagesArray)
+    const isNewPrivate = useSelector(state => state.dataReducer.isNewPrivateMessages)
+
+
+const [counter, setCounter] = useState(newPrivateMessagesArray.length)
+
+console.log(newPrivateMessagesArray.length)
+
+useEffect(() => {
+    store.dispatch(isNewPrivateMessages(true))
+    console.log('true')
+ } , [newPrivateMessagesArray]);
+
+
+    const newPrivateMessages = useSelector(state => state.getUserSocketReducer.newPrivateMessages)
+
+    const [isPrivate, setIsPrivate] = useState(false)
+
+    useEffect(() => {
+        if(newPrivateMessages.text && newPrivateMessages?.sender[0].userName === item.userName){
+         setIsPrivate(!!newPrivateMessages.text)
+         }
+         setCounter(counter => counter + 1)
+
+    },[newPrivateMessages])
+   
+    return (
+        <div 
+        className={(item.socketId&&isPrivatChat&&(chatId === item.socketId))? 'online active' :'online' }                       
+        onClick={(e) => {
+            store.dispatch(selectedUser(item))
+            store.dispatch(privateMessage({toUser: item}))
+            store.dispatch(isNewPrivateMessages(false))
+            setCounter(0) 
+            setIsPrivate(false) 
+            socket.emit('privat chat', {
+                user,
+                to: item.socketId,
+                toUser: item
+              })
+            }
+        }
+        >  
+         {isPrivate && item.userName && <span style={{color:'red'}} > new </span>} 
+            <div style={{color: item.color}}>
+            <StyledAvatar  key={i}  sx={{ marginRight:2}} 
+                           anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}  
+                           variant = {userNamesOnlineSet.has(item.userName)? 'dot' : ''}
+
+>
+    
+                <Avatar 
+                    src= {SERVER_URL + '/'+ item?.avatar}
+                    sx={{ alignSelf: 'flex-end'}}
+                    
+                    > 
+                    {item?.userName.slice(0, 1)}
+                </Avatar>   
+
+            </StyledAvatar>
+                <span> {item.userName}  </span>
+               {isNewPrivate && newPrivateMessagesArray.length > 0 && <span style={{color:'red',position: 'fixed' }} >  {counter}  </span>} 
+            </div>
+            <AddToFriends user = {item}/>
+
+    </div>
+    )
+}

+ 48 - 0
frontend/src/components/chatPage/generalChat/userInfo.scss

@@ -0,0 +1,48 @@
+.userAvatar {
+    background-color: grey;
+    width: 100px;
+    height: 100px;
+    font-size: 14;
+    margin: 20px auto;
+}
+
+.active {
+    background-color:rgb(30, 8, 62);
+}
+
+.addToFriendsButton {
+        width: 15px; 
+        height: 15px;
+        top: 8px;
+        background-color: #1976d3;
+        transform: rotate(45deg);
+        position: absolute; 
+        left: 8px;
+        cursor:pointer;
+      
+}  
+.mobileButton {   
+            width: 15px; 
+            height: 15px;
+            top: 0px;
+            left: -5px; 
+        }
+.addToFriendsButton::before,
+    .addToFriendsButton::after {
+        content : ''; 
+        display: block;
+        width: 15px;
+        height: 15px;
+        background-color:inherit;
+        border-radius: 50%; 
+        position: absolute;
+    }
+    .addToFriendsButton::before {
+        top: -8px;
+        left: 0;
+    }
+    .addToFriendsButton::after {
+        top: 0;
+        left: -8px;
+    }  
+

+ 3 - 5
frontend/src/components/chatPage/messageForm/MessegaForm.jsx

@@ -10,7 +10,6 @@ import { MessageEditorMenu } from '../MessageEditorMenu.jsx';
 import imgBtn from '../../../assets/img/gg.png';
 import useSound from 'use-sound';
 import notifSound from '../../../assets/get.mp3'
-import { useMemo } from 'react';
 
 
 export const MessageForm = () => {
@@ -26,7 +25,7 @@ export const MessageForm = () => {
     const userNamesOnlineSet =  new Set(usersOnline.map( i => i.userName))
     const storeMessageId = useSelector(state => state.messageReducer.messageId)
     const newMessages = useSelector(state => state.getUserSocketReducer.newMessages)
-
+    
     let endMessages = useRef(null);
     const [isEditing, setIsEditing] = useState(false)   
     const [isEditiedMessage, setIsEditiedMessage] = useState(false) //need to type in the bottom of message after message was edited
@@ -36,14 +35,13 @@ export const MessageForm = () => {
 
     const [play] = useSound(notifSound);
 
+
     useEffect(() => {
         if (!isEditing) {
             scrollToBottom((endMessages)) 
         } 
       }, [startMessages, messages, newMessages]);
            
-    console.log(usersOnline)
-
     useEffect(()=> {
         if(newMessages.length > 0 && newMessages[newMessages.length-1].userName !== user.userName){
            // play()                 
@@ -163,7 +161,7 @@ export const MessageForm = () => {
                         {isEditiedMessage && <i>Edited</i>}
                         <div className={ 
                                 (item.userName === user.userName)? 'myDate' :'date'}>
-                                {dateFormat(item).time}
+                                {dateFormat(item)}
                         </div>
                     </div>
                 )}

+ 12 - 0
frontend/src/components/chatPage/messageForm/userInfo.scss

@@ -0,0 +1,12 @@
+.userAvatar {
+    background-color: grey;
+    width: 100px;
+    height: 100px;
+    font-size: 14;
+    margin: 20px auto;
+}
+
+.active {
+    background-color:rgb(30, 8, 62);
+}
+

+ 30 - 0
frontend/src/components/chatPage/privateChat/PrivatChatHeader.jsx

@@ -0,0 +1,30 @@
+import { useSelector } from "react-redux"
+import './userInfo.scss';
+import { StyledAvatar } from "../messageForm/StyledAvatar";
+import { Avatar } from "@mui/material";
+
+export const PrivatChatHeader = () => {
+
+    const SERVER_URL = process.env.REACT_APP_SERVER_URL
+
+//add dispatch and saving name for user to store
+    const selectedUser = useSelector(state => state.dataReducer.selectedUser)
+
+    return (
+        <div className="active" >
+            <StyledAvatar    
+                sx={{ marginRight:2}} 
+                anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}  
+            >
+                <Avatar 
+                    src= {SERVER_URL + '/'+ selectedUser?.avatar}
+                    sx={{ alignSelf: 'flex-end'}} 
+                > 
+                    {selectedUser.userName.slice(0, 1)}
+                </Avatar>   
+            </StyledAvatar>
+              Private Chat with {selectedUser.userName}  
+           
+        </div>
+    )
+}

+ 152 - 0
frontend/src/components/chatPage/privateChat/PrivateChat.jsx

@@ -0,0 +1,152 @@
+import { Avatar, Box, Button} from '@mui/material';
+import { dateFormat } from '../utils/dateFormat';
+import { useSelector } from 'react-redux';   
+import { useRef, useEffect, useState} from 'react';
+import { scrollToBottom } from '../utils/scrollToBottom';
+import { useDispatch } from 'react-redux';
+import { editMessage } from '../../../reducers/messageReducer';
+import { MessageEditorMenu } from '../MessageEditorMenu.jsx';
+import imgBtn from '../../../assets/img/gg.png';
+import useSound from 'use-sound';
+import { PrivatChatHeader } from './PrivatChatHeader';
+import { privateMessage } from '../../../reducers/userDataReducer';
+import notifSound from '../../../assets/get.mp3'
+import {isNewPrivateMessages} from "../../../reducers/dataReducers";
+import { UserInfoButton } from '../generalChat/UserInfoButton';
+import { YoutubeMessage } from '../YoutubeMessage';
+
+//need to fix update wenn message sendet and icon for new private messages
+
+export const PrivateChat = () => {
+
+    const dispatch = useDispatch();
+    const socket = useSelector(state => state.getUserSocketReducer.socket)
+
+    const SERVER_URL =process.env.REACT_APP_SERVER_URL
+
+    const user = useSelector(state => state.getUserSocketReducer.socketUserData)
+    const storeMessageId = useSelector(state => state.messageReducer.messageId)
+    const selectedUser = useSelector(state => state.dataReducer.selectedUser)
+    const newPrivateMessages = useSelector(state => state.getUserSocketReducer.newPrivateMessages)
+
+    const [startMessages, setStartMessages] = useState([])   
+    let endMessages = useRef(null);
+    socket.on('send privat messages', (messages)=> {
+        setStartMessages(messages)
+    });
+  
+// bug need to fix****************
+
+
+
+    const [isEditing, setIsEditing] = useState(false)   
+    const [isEditiedMessage, setIsEditiedMessage] = useState(false) //need to type in the bottom of message after message was edited
+
+    const [play] = useSound(notifSound);
+    const regYoutube = /http(?:s?):\/\/(?:www\.)?youtu(?:be\.com\/watch\?v=|\.be\/)([\w\-\_]*)(&(amp;)?‌​[\w\?‌​=]*)?/; //for youtube video
+
+
+
+
+    
+      useEffect(() => {
+        if(startMessages.length > 0){
+           setStartMessages([...startMessages, newPrivateMessages]) 
+        }
+        }, [newPrivateMessages]);
+
+
+        useEffect(() => {
+            if (!isEditing) {
+                scrollToBottom((endMessages)) 
+            }
+    
+          }, [startMessages]);
+           
+    return (  
+
+        <>
+       
+            <PrivatChatHeader/>
+                <Box className='messageBox'>  
+                
+                    {
+                    startMessages.map((item, i) =>
+                        <div key={i + 1} className={ 
+                            (item.fromUser === user._id)? 'message myMessage' :'message'}
+                            onClick = {(e) => {
+                                if(e.target.closest("div").className.includes('myMessage') && (item.userName === user.userName) && (item.text === e.target.textContent)){
+                                    e.currentTarget.className += ' editMessage'  
+                                    dispatch(editMessage({socket, editMessage: e.target.textContent, messageId: item._id}))  
+                                    setIsEditing(true)
+                                    }
+                            }}
+                            > 
+                            {storeMessageId === item._id ? <MessageEditorMenu />: ""} 
+                 
+                            <div 
+                                key={i}
+                            
+                                className={ 
+                                    (item.fromUser === user._id)? 'message myMessage' :'message'}>
+                            
+                            { 
+                            item.text.match(regYoutube) ? <YoutubeMessage item = {item} />: 
+                                (item.file && item.fileType && item.fileType.split('/')[0] !== 'image') ? 
+
+                                <div style={{'display': 'flex', 'alignItems': 'center'}} >
+
+                                    <a href={SERVER_URL + '/' +item.file} download> 
+                                        <Button
+                                            variant="contained" 
+                                            component="label"
+                                            sx = {{
+                                                minWidth: 'auto',
+                                                minHeight: '25px',
+                                                backgroundImage:'url(' + imgBtn + ')' ,
+                                                backgroundPosition: 'center', 
+                                                backgroundRepeat: "no-repeat", 
+                                                backgroundSize: '15px 20px',
+                                                backgroundColor: '#d3d3d3'
+
+                                            }}  
+                                        >
+                                        </Button>  
+                                    </a>
+                                    <p style={{'marginLeft': '15px'}}  >{item.text}</p>  
+                                </div>
+                            : 
+                                <p>{item.text}</p>
+                            
+                            }
+
+                            { 
+                                (item.file && item.fileType && item.fileType.split('/')[0] == 'image' ) //need to fix for other type files
+                                ? 
+                                    <img width={'auto'} style={{'maxWidth': "90%"}} src={ SERVER_URL + '/' + item.file} alt={'error load image'}/>
+                                :
+                                ''
+                            }
+
+                            </div>
+
+                            <div className={ 
+                                (item.userName === user.userName)? 'myDate' :'date'}>
+                                {dateFormat(item)}
+                            </div>
+                            {isEditiedMessage && <i>Edited</i>}
+                            {/* <div className={ 
+                                    (item.fromUser === user._id)? 'myDate' :'date'}>
+                                    {dateFormat(item).time}
+                            </div> */}
+                        </div>
+                    )}
+
+                    <div ref={endMessages}></div>
+
+            </Box>
+        </>      
+    )
+
+    
+} 

+ 16 - 0
frontend/src/components/chatPage/privateChat/userInfo.scss

@@ -0,0 +1,16 @@
+.hidden {
+    display: none;
+    transform: translateX(-100%);
+}
+
+.active {
+    display: block;
+    transition: all 1 linear;
+    background-color:rgb(30, 45, 52);
+    border-radius: 5px;
+    padding: 5px;
+    font-weight: 700;
+    text-align: center;
+    color:azure;
+}
+

+ 64 - 104
frontend/src/components/chatPage/userInfo/UserInfo.jsx

@@ -1,28 +1,19 @@
-import {Button,Avatar} from '@mui/material';
+import {Avatar} from '@mui/material';
+import { StyledAvatar } from '../messageForm/StyledAvatar';
 import { useSelector } from 'react-redux';
-import { banUser } from '../service/banUser';
-import { muteUser } from '../service/muteUser';
 import './userInfo.scss';
 import { useDispatch } from 'react-redux';
 import { getUserAvatar } from '../../../reducers/userDataReducer';
-import { useState, useEffect } from 'react';
-import { store } from '../../../store';
-import { getSocket } from '../../../reducers/socketReducer';
+import { useState } from 'react';
+import { UserInfoButton } from '../generalChat/UserInfoButton';
+import { AdminUserInfiButton } from '../generalChat/AdminUserInfiButton';
+import { MainChatButtton } from '../generalChat/MainChatButtton';
+import { FindUserBox } from '../generalChat/FindUserBox';
+import './userInfo.scss';
 
 
 export const UserInfo = () => {
 
-    const PC_AVATAR_STYLE =    {
-        bgcolor: 'grey',
-        width: '100px',
-        height: '100px',
-        fontSize: 14,
-        margin: '20px auto',
-        cursor: 'pointer'
-        };
-
-
-    const MOBILE_AVATAR_STYLE =  { margin: '5px auto'};
  
     const [displayType, setDisplayType] = useState('none');
 
@@ -42,114 +33,83 @@ export const UserInfo = () => {
     const socket = useSelector(state => state.getUserSocketReducer.socket)
     const isTabletorMobile = (window.screen.width < 730);
     const storeUserAvatar = useSelector(state => state.userDataReducer.avatar)
+    const chatId = useSelector(state => state.userDataReducer.chatId)
+    const showUserInfoBox = useSelector(state => state.userDataReducer.showUserInfoBox)
+    const newPrivateMessages = useSelector(state => state.getUserSocketReducer.newPrivateMessages)
+    const newMessage = useSelector(state => state.getUserSocketReducer.newMessages)
 
-
-
+    const friends = useSelector(state => state.getUserSocketReducer.friends)
+    const friendsIds = friends.map(friend => friend._id)
     let userAvatarUrl = storeUserAvatar || user.avatar;
 
-    
-    const userNamesOnlineSet =  new Set(usersOnline.map( i => i.userName))
-
     const inputHandler = (e) => {
         const file = e.target.files[0]
         dispatch(getUserAvatar(file))
         setDisplayType('none')
     }
 
+console.log(user)
+
+    // if(socket){
+    //     socket.on('my chats', (data)=> console.log('my chats', data))
+    // }
+        
+
     return (
             <>  
-                <h4> Hello, {user.userName} </h4>
+                <h4 style={{color:'white'}}> Hello, {user.userName} </h4>
                
                 <Avatar
-                    sx={isTabletorMobile ? MOBILE_AVATAR_STYLE : PC_AVATAR_STYLE} //add deleting function after update avatar
+                    className={isTabletorMobile ? 'mobileAvatar' : 'pcUsersAvatar'} //add deleting function after update avatar
                     onClick={() => loadAvatarHandler()}
                     src={userAvatarUrl ? SERVER_URL +'/'+ userAvatarUrl : ""}
                     >
                 </Avatar>  
                 
+               <div
+                    className={isTabletorMobile ? 'mobileUsersInfoBox' : 'pcUsersInfoBox'} 
+                    style={showUserInfoBox && isTabletorMobile ? {'transform':'translate(-100%)'}:{ 'transform':'translate(0)'}}   
+                >
                 <input
-                        type="file"
-                        accept="image/png, image/jpeg"
-                        name='file'
-                        style = {{
+                    type="file"
+                    accept="image/png, image/jpeg"
+                    name='file'
+                    style = {{
                             display: displayType
                         }}
-                        onChange = {e => inputHandler(e)}
-                       />
-                    {user.isAdmin && !isTabletorMobile ? 
-                        allUsers.map((item, key) =>
-                            <div 
-                                key={item._id}
-                                className='online'
-                                onClick={() => console.log(item.id)}
-                                >
-                                <div>
-                                    {item.userName}
-                                </div>
-
-                                <div>
-                                    {(user.userName === item.userName )?   
-                                        "admin"
-                                    :   
-                                    <>      
-                                        <Button
-                                            variant="contained"
-                                            onClick={()=>{
-                                                muteUser(item.userName, item?.isMutted, socket)
-                                            }}
-                                            sx={(isTabletorMobile) 
-                                                ? 
-                                                {height: '15px',
-                                                 maxWidth:'20px'}: 
-                                                {
-                                                margin:'3px',
-                                                height: '25px'}}>
-
-                                                {item.isMutted
-                                                ? 
-                                                'unmute'
-                                                : 'mute'}
-                                        </Button>
-
-                                        <Button
-                                            variant="contained"
-                                            onClick={()=>{ 
-                                                banUser(item.userName, item.isBanned, socket)
-                                            }}
-                                            sx={(isTabletorMobile) 
-                                                ? 
-                                                {height: '15px',
-                                                margin:'2px'} : 
-                                                {
-                                                margin:'3px',
-                                                height: '25px'}}
-                                       >
-                                                {item?.isBanned ? 'unban' : 'ban'}
-                                        </Button> 
-                                    </>}
-                            
-                                </div>
-                                    {
-                                    userNamesOnlineSet.has(item.userName)? 
-                                    <span key={key} style={{color: 'green'}}>online</span>
-                                    : ''
-                                    }
-                            </div>) 
-                    :
-                     !isTabletorMobile && usersOnline.map((item, i) =>
-                        <div 
-                            key={i}
-                            className='online'                        
-                            onClick={() => console.log(item.id)}
->  
-                                <div style={{color: item.color}}>
-                                    {item.userName}
-                                </div>
-                                <span style={{color: 'green'}}>
-                                    online
-                                </span>
-                        </div>)
-                }
+                    onChange = {e => inputHandler(e)}/>
+
+
+                 
+                {friends.map((item, i) =>(user.userName !== item.userName) && <UserInfoButton item = {item} i = {i}  key={i} 
+                /> )}
+                    
+                   
+                <MainChatButtton/>     
+
+                <FindUserBox/>     
+                
+                    { user.isAdmin && !isTabletorMobile ? 
+                            allUsers.map((item, i) =>
+                            (user.userName !== item?.userName) 
+                                && <AdminUserInfiButton item={item} i={i} key={i}/>) 
+                        :
+
+                            usersOnline.map((item, i) =>
+                                    (user.userName !== item.userName && !friendsIds.includes(item._id) ) && <UserInfoButton item = {item} i = {i}  key={i} />                   
+                            )
+                    }
+
+                    {
+                    newPrivateMessages.length > 0 
+                        && newPrivateMessages.map((item, i) => 
+                       // <UserInfoButton item = {item} i = {i}  key={i} />
+                       console.log(item)
+                        )
+
+                    }
+
+                </div>
             </>
         )
 }

+ 29 - 1
frontend/src/components/chatPage/userInfo/userInfo.scss

@@ -4,4 +4,32 @@
     height: 100px;
     font-size: 14;
     margin: 20px auto;
-}
+}
+
+.active {
+    background-color:rgb(30, 8, 62);
+}
+
+.mobileUsersInfoBox {
+    position: absolute;
+    top: 100px;
+    left: 10px;
+    z-index: 10;
+    transition: all 1s;
+    transform: translate(-100%);
+    transition: all 1s;
+    max-width: 100px;
+
+
+}
+.pcUsersAvatar {
+    background-color: grey;
+    width: 80px !important;
+    height: 80px !important; //need to change in classlist styles
+    margin: 20px auto;
+    cursor: pointer;
+}
+
+.mobileAvatar {
+     margin: 5px auto
+};

+ 21 - 8
frontend/src/components/chatPage/utils/dateFormat.js

@@ -1,9 +1,22 @@
 export const dateFormat = (item) => {
-//need to change on  Moment js
-    const res = item.createDate.split('T');
-    const date = {
-        year : res[0],
-        time : res[1].slice(-13, -5)
-    }
-    return date;
-}  
+
+    const lang = navigator.language;
+    const rtf = new Intl.RelativeTimeFormat( lang, { numeric: 'auto' });
+
+    const dateSecDelta = Math.round((new Date(item.createDate).getTime() - Date.now())/1000) ;
+
+    const secondTimeMarkers = [60, 3600, 86400, 86400 * 7, 86400 * 30, 86400 * 365, Infinity];
+
+    const literalTimeMarkers = ['second','minute', 'hour', 'day', 'week', 'month' ,'year'];
+
+    const indexTime = secondTimeMarkers.findIndex(time => time > Math.abs(dateSecDelta))
+
+    const divisor = indexTime ? secondTimeMarkers[indexTime - 1] : 1 //for seconds if index 0 then seconds 
+
+    const time = rtf.format(Math.floor(dateSecDelta/divisor),literalTimeMarkers[indexTime])
+
+    return time;
+}  
+
+
+   

+ 2 - 0
frontend/src/index.js

@@ -6,6 +6,8 @@ import {Provider} from 'react-redux';
 import {store} from './store'
 import * as serviceWorkerRegistration from './serviceWorkerRegistration';
 
+
+
 ReactDOM.render(
     <Provider store={store}> 
         <App/>

+ 29 - 0
frontend/src/reducers/dataReducers.js

@@ -0,0 +1,29 @@
+import {createSlice } from '@reduxjs/toolkit';
+
+const initialState = {
+    selectedUser: {},
+    isNewPrivateMessages: true
+}
+
+export const dataReducersSlice = createSlice({
+    name: 'dataReducer',
+    initialState,
+    reducers: {
+        selectedUser: (state, action) => {
+                state.selectedUser = action.payload  
+      },
+        isNewPrivateMessages: (state, action) => {
+                state.isNewPrivateMessages = action.payload  
+      },
+    },
+  })
+  
+const {actions, reducer} = dataReducersSlice;
+const dataReducer = reducer;
+
+export default dataReducer;
+
+export const {
+        selectedUser,
+        isNewPrivateMessages
+        } = actions;

+ 8 - 25
frontend/src/reducers/messageReducer.js

@@ -1,7 +1,9 @@
 import { createSlice} from '@reduxjs/toolkit';
 import axios from 'axios';
 import { createAsyncThunk } from '@reduxjs/toolkit';
-
+import { sendMessageToSocket } from '../utils/messagesSocketEvents';
+import { deleteMessageHandler } from '../utils/messagesSocketEvents';
+import { editMessageHandler } from '../utils/messagesSocketEvents';
 
 const initialState = {
     message:'',
@@ -13,24 +15,7 @@ const initialState = {
     ref: null
 }
 
-const POST_FILES_URL = process.env.REACT_APP_SERVER_URL+`/files`;
-
-export const sendMessageToSocket = (state, data) => {
-             if (!!state.message && state.message.length < 200) {    //remove to other file
-                data.socket.emit('message', {...data.user, message: state.message}); 
-            } 
-    };
-
-export const deleteMessageHandler = (state, data) => {
-    data.socket.emit('deleteMessage', {messageId: data.messageId, token: data.socket.auth.token});  
-};
-
-    
-export const editMessageHandler = (state, data) => {
-    if(data.socket){
-         data.socket.emit('editmessage', {messageNewText: data.editMessage.message, messageId: data.messageId, token: data.socket.auth.token}); //add backend functional later find by id and edit 
-    }
-};
+const POST_FILES_URL = process.env.REACT_APP_SERVER_URL + `/files`;
 
 export const fileMessage = createAsyncThunk(
     'messageReducer/fileMessageStatus',
@@ -39,8 +24,8 @@ export const fileMessage = createAsyncThunk(
         const token = localStorage.getItem('token')
         try {
             const formData = new FormData();
-            if(files.length) {
-                 for (let i = 0; i < files.length; i++) {
+            if(files?.length) {
+                 for (let i = 0; i < files?.length; i++) {
                 formData.append('files', files[i])
                 }
             } else {
@@ -71,7 +56,6 @@ const messageReducerSlice = createSlice({
             },
         deleteMessage: (state, action) => {
             deleteMessageHandler(state, action.payload)
-           
         },
         sendMessage: (state, action) => sendMessageToSocket(state, action.payload),
         clearMessage: (state) => {state.message = ''}
@@ -80,9 +64,7 @@ const messageReducerSlice = createSlice({
     extraReducers: (bilder) => {
     bilder
     .addCase(fileMessage.fulfilled, (state, action) => {
-        console.log('send', action)
-        state.files = action.payload.data?.files
-            
+        state.files = action.payload.data?.files           
     })  
     .addCase(fileMessage.pending, (state, action) => {
         console.log('pending', fileMessage.pending())
@@ -100,6 +82,7 @@ export default messageReducer;
 export const {
     storeMessage, 
     sendMessage,
+    sendPrivateMessage,
     clearMessage,
     editMessage,
     deleteMessage

+ 15 - 69
frontend/src/reducers/socketReducer.js

@@ -1,8 +1,4 @@
 import {createSlice } from '@reduxjs/toolkit';
-import {io} from 'socket.io-client';
-import { store } from '../store';
-import { removeToken } from './userDataReducer';
-
 
 const initialState = {
     socketStatus: 'idle',
@@ -13,70 +9,12 @@ const initialState = {
     allUsers: [],
     writing: false,
     usersWriting: [],
-    newMessages : []
+    newMessages : [],
+    newPrivateMessages: {},
+    newPrivateMessagesArray: [],
+    friends: []
 }
 
-const SOCKET_URL = process.env.REACT_APP_SERVER_URL;
-
-
-const connectToSocket = (event) => {
-        try {
-            const token = localStorage.getItem('token');
-            if(token){
-                const socket = io.connect(    //need to add other function for connecting
-                    SOCKET_URL, 
-                    {auth: {token}})
-                    socket.on('connected', data => {
-                                store.dispatch(getUser(data));
-                               // socketEventsDispatch(socket)
-                            })
-                            .on(event, (data) => {
-                                   switch (event){
-                                    case 'allmessages':
-                                        store.dispatch(getAllMessages(data));
-                                        break;
-                                    case 'allDbUsers':
-                                        store.dispatch(getAllUsers(data));
-                                        break;
-                                    default: 
-                                        break;
-                                    }
-                                })
-                                
-                            .on('newmessage', (data) => {
-                                store.dispatch(addNewMessage(data))
-                                })
-                            .on('ban', (data) => {
-                                store.dispatch(removeToken()); 
-                                localStorage.removeItem('token');
-                                })
-                            .on('usersOnline', (data) => {
-                                    store.dispatch(getUsersOnline(data))
-                                })
-                            .on('disconnect', (data) => {
-                                if( data === 'io server disconnect') {
-                                    socket.disconnect();
-                                    store.dispatch(removeToken()); 
-                                }
-                            })
-                            .on('error', e => {console.log('On connected', e)}); 
-                            
-                return socket;       
-            }   
-        } catch (error) {
-            console.log('error connecting to socket ', error)
-        } 
-    };
-
-// const socketEventsDispatch = (socket) => {
-//     socket
-//         .on('writing', (data) => {
-//                 console.log(data)
-//                 store.dispatch(writing(data));   
-//      })
-// }
-
-
 export const getUserSocketSlice = createSlice({
     name: 'userSocket',
     initialState,
@@ -85,7 +23,7 @@ export const getUserSocketSlice = createSlice({
             state.socket = null
             state.socketStatus = 'disconnected'},
         getSocket: (state, action) => {
-            state.socket = connectToSocket(action.payload);
+            state.socket = action.payload
             state.socketStatus = 'connected';
         },
         getUser: (state, action) => {state.socketUserData = action.payload},
@@ -93,9 +31,15 @@ export const getUserSocketSlice = createSlice({
         getUsersOnline: (state, action) => {state.usersOnline = action.payload},
         getAllUsers: (state, action) => {state.allUsers = action.payload},
         addNewMessage: (state, action) => {state.newMessages.push(action.payload)}, 
+        addNewPrivateMessage: (state, action) => {
+            state.newPrivateMessages = action.payload
+            state.newPrivateMessagesArray.push(action.payload)
+        }, 
+        // addNewPrivateMessage: (state, action) => {state.newPrivateMessages = action.payload}, 
+        // friendsFromSocket:(state, action) => {state.friends = action.payload}
+        
         }
-    }
-);
+    });
 
 
 const {actions, reducer} = getUserSocketSlice;
@@ -109,6 +53,8 @@ export const {
     getAllMessages,
     getUsersOnline,
     addNewMessage,
+    addNewPrivateMessage,
     getAllUsers,
+    friendsFromSocket,
     writing
     } = actions;

+ 12 - 5
frontend/src/reducers/userDataReducer.js

@@ -13,10 +13,10 @@ const initialState = {
     socket: null, 
     responseMessage: '',
     showUserInfoBox: false,
+    isPrivatChat: false,
+    toUser: {},
     avatar: ''
 }
-const SERVER_URL =  process.env.REACT_APP_SERVER_URL
-
 const POST_URL =  process.env.REACT_APP_SERVER_URL + '/login';
 const GET_AVATAR_URL = process.env.REACT_APP_SERVER_URL +  '/avatar';
 
@@ -24,7 +24,6 @@ export const getUserData = createAsyncThunk(
     'userData/getUser',
      ( t , thunkAPI) => {
       const userData = thunkAPI.getState().userDataReducer;
-      console.log(POST_URL)
         if(userData.userName){
             if(isValidPayload({...userData}) && isValidUserName({...userData}))
                 try {
@@ -59,6 +58,13 @@ const getUserDataSlice = createSlice({
     name: 'userData',
     initialState,
     reducers: {
+        privateMessage: (state, action)=> {
+            state.isPrivatChat = true;
+            state.toUser = action.payload.toUser
+        },
+        generalMessage: (state, action)=> {
+            state.isPrivatChat = false;
+        },
         setUserName: (state, action) => {state.userName = action.payload.userName},
         setUserPassword: (state, action) => {state.password = action.payload.password},
             
@@ -68,7 +74,6 @@ const getUserDataSlice = createSlice({
         deleteResponseMessage: state => {state.responseMessage = ''},
         showUserInfoBox: state => {
             state.showUserInfoBox = !state.showUserInfoBox  //replace later to other reducer file
-            console.log('reducer showUserInfoBox', state.showUserInfoBox)
           //localStorage.setItem('showBox', !state.showUserInfoBox)
         },
     },
@@ -111,5 +116,7 @@ export const {
     setUserPassword,
     removeToken,
     deleteResponseMessage,
-    showUserInfoBox
+    showUserInfoBox,
+    privateMessage,
+    generalMessage
 } = actions;

+ 2 - 1
frontend/src/store.js

@@ -2,12 +2,13 @@
 import userDataReducer from './reducers/userDataReducer';
 import getUserSocketReducer from './reducers/socketReducer';
 import messageReducer from './reducers/messageReducer';
+import dataReducer from './reducers/dataReducers';
 import { configureStore } from '@reduxjs/toolkit';
 
 
 
 export  const store = configureStore({
-    reducer: {userDataReducer, getUserSocketReducer, messageReducer},
+    reducer: {userDataReducer, getUserSocketReducer, messageReducer, dataReducer},
     middleware: getDefaultMiddleware => getDefaultMiddleware({
         serializableCheck: false
       }),

+ 16 - 0
frontend/src/utils/messagesSocketEvents.js

@@ -0,0 +1,16 @@
+export const sendMessageToSocket = (state, data) => {
+    if (!!state.message && state.message.length < 200) {    //remove to other file
+       data.socket.emit('message', {...data.user, message: state.message}); 
+   } 
+};
+
+export const deleteMessageHandler = (state, data) => {
+data.socket.emit('deleteMessage', {messageId: data.messageId, token: data.socket.auth.token});  
+};
+
+
+export const editMessageHandler = (state, data) => {
+if(data.socket){
+data.socket.emit('editmessage', {messageNewText: data.editMessage.message, messageId: data.messageId, token: data.socket.auth.token}); //add backend functional later find by id and edit 
+}
+};

+ 41 - 0
frontend/src/utils/socketsEvents.js

@@ -0,0 +1,41 @@
+import { store } from "../store";
+import { getAllMessages, getAllUsers, addNewMessage, getUser,addNewPrivateMessage,getUsersOnline,friendsFromSocket } from "../reducers/socketReducer";
+import { removeToken } from "../reducers/userDataReducer";
+
+export const socketEvents = (socket) => {
+console.log('socket event...')
+socket.on('connected',  data => {
+                        store.dispatch(getUser(data));
+                        console.log('getuserdata', data)
+        })
+        .on('allmessages', (data) => {
+            store.dispatch(getAllMessages(data));
+                })
+        .on('allDbUsers', (data) => {
+            store.dispatch(getAllUsers(data));
+                    })    
+        .on('newmessage', (data) => {
+            store.dispatch(addNewMessage(data))
+            })
+        .on('private', (data) => {
+            store.dispatch(addNewPrivateMessage(data))
+                })
+        .on('ban', (data) => {
+            store.dispatch(removeToken()); 
+            localStorage.removeItem('token');
+            })
+        .on('usersOnline', (data) => {
+                store.dispatch(getUsersOnline(data))
+            })
+        .on('friends', data => {
+                store.dispatch(friendsFromSocket(data))
+            })
+        .on('disconnect', (data) => {
+            if( data === 'io server disconnect') {
+                // socket.disconnect();
+                store.dispatch(removeToken()); 
+            }
+        })
+        .on('error', e => {console.log('On connected', e)}); 
+}
+