Kaynağa Gözat

fixed socket bugs, fixed popup, added counting

Ivar 3 yıl önce
ebeveyn
işleme
68a2205b78

+ 3 - 32
README.md

@@ -1,6 +1,7 @@
-# Getting Started with Create React App
+# Project description
+
+Chat app built on socket io and graphQl api. Redux, thunk, react, mui and some react libraries like dropzone, sortable hoc used.
 
-This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
 
 ## Available Scripts
 
@@ -38,33 +39,3 @@ If you aren't satisfied with the build tool and configuration choices, you can `
 Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own.
 
 You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it.
-
-## Learn More
-
-You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
-
-To learn React, check out the [React documentation](https://reactjs.org/).
-
-### Code Splitting
-
-This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting)
-
-### Analyzing the Bundle Size
-
-This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size)
-
-### Making a Progressive Web App
-
-This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)
-
-### Advanced Configuration
-
-This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)
-
-### Deployment
-
-This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)
-
-### `npm run build` fails to minify
-
-This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)

+ 1 - 1
public/index.html

@@ -25,7 +25,7 @@
       Learn how to configure a non-root public URL by running `npm run build`.
     -->
     <script src='https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.2.0/socket.io.js'></script>
-    <title>TopChat229</title>
+    <title>TopChat</title>
   </head>
   <body>
     <noscript>You need to enable JavaScript to run this app.</noscript>

+ 4 - 20
src/App.js

@@ -7,9 +7,6 @@ import {
   store,
   socket
 } from "./reducers"
-import {
-  actionFullChatList,
-} from "./actions"
 
 import {
   Login, 
@@ -25,7 +22,8 @@ import Grid from '@mui/material/Grid'
 
 
 const PageNoChat = () => (
-  <h1>Выбирай чат</h1>
+  <div style={{ height: "100vh", width: "100%", backgroundColor: "#eee" }}>
+  </div>
 )
 
 const AuthSwitch = ({ token }) => {
@@ -54,7 +52,7 @@ const AuthSwitch = ({ token }) => {
           <Grid item xs={12} sm={8}>
             <Switch> 
               <Route path="/main" component={PageNoChat} exact/>
-              <Route path="/main/:_id" component={MsgPage} />
+              <Route path="/main/:_id" component={MsgPage} exact/>
               <Route path="*" component={PageNoChat} /> 
             </Switch>
           </Grid>
@@ -75,7 +73,7 @@ const AuthSwitch = ({ token }) => {
 }
 const CAuthSwitch = connect(state => ({ token: state.auth.token || null }))(AuthSwitch)
 
-const history = createHistory()
+export const history = createHistory()
 
 function App() {
 
@@ -95,17 +93,3 @@ function App() {
   )
 }
 export default App
-
-
-
-
-// const RRoute = ({ action, component:Component, ...routeProps}) => {
-//   const WrapperComponent = (componentProps) => {
-//     action(componentProps.match)
-//     return <Component {...componentProps} />
-//   }
-//   return <Route {...routeProps} component={WrapperComponent} />
-// }
-// const CRRoute = connect(null, {action: match => ({type: 'ROUTE', match})})(RRoute)
-
-

+ 15 - 5
src/App.scss

@@ -1,7 +1,7 @@
 
 
 .avatarInModal {
-   // пиздец...
+
    .MuiAvatar-root {
       width: 80px;
       height: 80px;
@@ -40,8 +40,6 @@
          align-items: center;
 
          .chatName {
-            // flex: 1 1 70%;
-            // width: auto;
             max-width: 70%;
             flex-grow: 1;
 
@@ -63,8 +61,6 @@
          align-items: center;
 
          .chatText {
-            // flex: 0 1 70%;
-            // width: auto;
             max-width: 70%;
             flex-grow: 1;
 
@@ -77,6 +73,20 @@
 
          .chatMsgCount {
             flex-shrink: 0;
+            font-size: 14px;
+            font-weight: 600;
+            color: #fff;
+
+            .countWrapper {
+               border-radius: 50%;
+               display: flex;
+               align-items: center;
+               justify-content: center;
+               background-color: #1976d2dd;
+               padding: 1px 5px;
+               min-width: 22px;
+               height: max-content;
+            }
          }
       }
    }

+ 24 - 8
src/actions/chatsActions.js

@@ -1,3 +1,4 @@
+import { history } from '../App'
 import {gql} from '../helpers'
 import {   
    actionPromise, 
@@ -121,7 +122,7 @@ export const actionGetChatsByUser = (userId, skipCount=0, limitCount=50) => (
 
 
 export const actionFullChatList = (userId, currentCount, limitCount=50) => (
-   async (dispatch) => {
+   async (dispatch, getState) => {
       let payload = await dispatch(actionGetChatsByUser(userId, currentCount, limitCount))
       if (payload) {
          await dispatch(actionChatList(payload))
@@ -199,13 +200,28 @@ const actionUpdateUserChats = (userId, newChats) => (
 
 export const removeUserChat = (chatId) => (
    async (dispatch, getState) => {
-      let { promise } = getState()
-      let userId = promise.myProfile.payload._id
-      let oldChats = promise.myProfile.payload.chats
-      let newChats = oldChats.filter((chat) => chat._id !== chatId)
-      await dispatch(actionUpdateUserChats(userId, newChats))
-      let chat = await dispatch(actionGetChatById(chatId))
-      await dispatch(actionChatOne(chat))
+      const state = getState()
+      const myId = state.promise.myProfile.payload._id
+      const oldChats = state.promise.myProfile.payload.chats
+     
+      const newChats = oldChats.filter((chat) => chat._id !== chatId)
+      await dispatch(actionUpdateUserChats(myId, newChats))
+
+      const ownerId = state.chats[chatId]?.owner?._id
+      // тут событие ухода из чата не приходит по сокету, 
+      // поэтому нужно делать все то, что и в сокете
+
+      if (myId !== ownerId) {
+         dispatch(actionChatLeft({_id: chatId})) 
+         const [,route, histId] = history.location.pathname.split('/')
+         if (histId === chatId) {
+            history.push('/')
+         }
+      } else {
+         const chat = await dispatch(actionGetChatById(chatId))
+         await dispatch(actionChatOne(chat))
+      }
+
       await dispatch(actionAboutMe()) 
    }
 )

+ 10 - 1
src/actions/index.js

@@ -30,7 +30,11 @@ import {
 import {
    actionFindUsers
 } from './findActions'
-
+import {
+   actionOnMsg,
+   actionOnChat,
+   actionOnChatLeft,
+} from './socketActions'
 
 
 export {
@@ -64,6 +68,11 @@ export {
 export {
    actionFindUsers
 } 
+export {
+   actionOnMsg,
+   actionOnChat,
+   actionOnChatLeft,
+} 
 
 
 

+ 20 - 10
src/actions/msgActions.js

@@ -69,7 +69,9 @@ export const actionFullMsgsByChat = (chatId, currentCount, limitCount=50) => (
 
       let chat = getState().chats[chatId]
 
-      if (!chat || (chat.messages && chat.messages[0]?._id !== chat.firstMsgId) ) {
+      if (!chat || ( (chat.messages && chat.messages[0]?._id !== chat.firstMsgId) &&
+                        ( (chat.messages?.length ?? 0) < currentCount + limitCount) )
+                        ) {
          console.log(chat)
 
          let payload = await dispatch(actionGetMsgsByChat(chatId, currentCount, limitCount))
@@ -101,12 +103,19 @@ const actionFirstMsgByChat = (chatId) => (
 )
 
 
-export const actionGetAllLastMsg = (chats) => (
-   async (dispatch) => {
 
-      let msgReq = chats.map((chat) => Promise.all(
-                                       [dispatch(actionGetMsgsByChat(chat._id, 0, 1)),
-                                       dispatch(actionFirstMsgByChat(chat._id)) ]) )
+export const actionGetAllLastMsg = (chats) => (
+   async (dispatch, getState) => {
+      
+
+      let msgReq = chats.map((chat) => 
+          Promise.all(
+            [  dispatch(actionGetMsgsByChat(chat._id, 0, 1)),
+               
+               getState().chats[chat._id]?.firstMsgId ? 
+                  Promise.resolve([]) :
+                     dispatch(actionFirstMsgByChat(chat._id))
+                         ])  )
                                        
       for await (const [lastMsgs, firstMsgs] of msgReq) {
          lastMsgs.length && dispatch(actionMsgOne(lastMsgs[0]))
@@ -177,7 +186,7 @@ export const actionGetMsgById = (msgId) => (
 
 
 
-const actionUpdateMsg = (chatId, text, media, replyedMsg, msgId) => (
+const actionUpdateMsg = (chatId, text, media, msgId ) => (
    actionPromise('updateMsg', gql(`mutation updateMsg($msg: MessageInput) {
       MessageUpsert(message: $msg) {
          _id
@@ -216,11 +225,12 @@ const actionUpdateMsg = (chatId, text, media, replyedMsg, msgId) => (
             _id
          }
       }
-   }`, { msg: {_id: msgId, 
+   }`, { msg: {
+               _id: msgId, 
                text, 
                chat: {_id: chatId}, 
                media, 
-               replyTo: {_id: replyedMsg}  
+               // replyTo: {_id: undefined}
             } }
    ))
 )
@@ -251,7 +261,7 @@ export const actionSendMsg = (chatId, text, inputName, files, msgId) => (
          }
       }
 
-      let payload = await dispatch(actionUpdateMsg(chatId, text, media, msgId))
+      let payload = await dispatch(actionUpdateMsg(chatId, text, media, msgId ))
       // if (payload) {
       //    await dispatch(actionMsgOne(payload))
       //    let chatUpdated = await dispatch(actionGetChatById(chatId))

+ 97 - 0
src/actions/socketActions.js

@@ -0,0 +1,97 @@
+import { history } from '../App'
+import { 
+   actionAboutMe,
+   actionMsgOne, 
+   actionMsgList,
+   actionChatOne, 
+   actionChatLeft
+} from '../reducers'
+
+import {   
+   actionGetMsgsByChat,
+   actionGetMsgById,
+   actionGetChatById, 
+} from '.'
+
+import msgSound from '../assets/msgSound.ogg'
+import chatSound from '../assets/chatSound.ogg'
+
+function playAudio(audio) {
+   const newAudio = new Audio(audio)
+   newAudio.play()
+}
+
+// данные из сокета приходят не полные, 
+// поэтому потом приходится затягивать доп данные из графа
+
+export const actionOnMsg = (msg) => (
+   async (dispatch, getState) => {
+      const state = getState()
+      const myId = state.auth.payload?.sub?.id
+      const ownerId = msg.owner?._id
+      if (myId !== ownerId) {
+         playAudio(msgSound)
+      }
+   
+      const chatId = msg.chat?._id
+   
+      await dispatch(actionMsgOne(msg)) 
+   
+      const msgFull = await dispatch(actionGetMsgById(msg._id))
+      await dispatch(actionMsgOne(msgFull)) 
+   
+      const chatUpdated = await dispatch(actionGetChatById(chatId))
+      await dispatch(actionChatOne(chatUpdated))
+   }
+)
+
+
+export const actionOnChat = (chat) => (
+   async (dispatch, getState) => {
+      const state = getState()
+      const myId = state.auth.payload?.sub?.id
+      // приходится делать так, так как овнер не приходит по сокету
+      const ownerId = state.chats[chat._id]?.owner?._id
+   
+      if (myId !== ownerId) {
+         playAudio(chatSound)
+      }
+      dispatch(actionChatOne(chat)) 
+   
+      const chatFull = await dispatch(actionGetChatById(chat._id))
+      await dispatch(actionChatOne(chatFull))
+   
+      const chatMsgs = await dispatch(actionGetMsgsByChat(chat._id))
+      await dispatch(actionMsgList(chatMsgs))
+   
+      await dispatch(actionAboutMe()) 
+
+
+   }
+)
+
+
+export const actionOnChatLeft = (chat) => (
+   async (dispatch, getState) => {
+      const state = getState()
+      const myId = state.auth.payload?.sub?.id
+      const ownerId = state.chats[chat._id]?.owner?._id
+   
+      // если чат чужой, то удаляем его и апдейтим роутер
+      // если мой, то просто обновляем статус чата
+      if (myId !== ownerId) {
+         playAudio(chatSound)
+         dispatch(actionChatLeft(chat)) 
+         const [,route, histId] = history.location.pathname.split('/')
+         if (histId === chat._id) {
+            history.push('/')
+         }
+      } else {
+         dispatch(actionChatOne(chat)) 
+         const chatFull = await dispatch(actionGetChatById(chat._id))
+         await dispatch(actionChatOne(chatFull))
+      }
+   
+      await dispatch(actionAboutMe()) 
+   }
+)

+ 20 - 14
src/components/ChatList.jsx

@@ -8,20 +8,23 @@ import { FloatBtn, ChatAvatar, CChatModal } from "../components"
 import { connect }  from 'react-redux'
 
 
-const Chat = ({ chat, currChat }) => {
+const Chat = ({ chat, currChat, myId, lv }) => {
 
-  // const [newMsgCount, setNewMsgCount] = useState(chat.messages &&
-  //                    (chat.messages.filter(msg => msg.createdAt > chat.lastVizited)).length )
-  const [msgText, setMsgText] = useState(chat.messages && 
+  const [newMsgCount, setNewMsgCount] = useState( chat.messages &&
+                     (chat.messages.filter(msg => ( !currChat && (msg.owner._id !== myId) && (msg.createdAt > chat.lastVizited)) )).length )
+  
+  const [msgText, setMsgText] = useState( chat.messages && 
                     chat.messages[chat.messages.length - 1]?.text || '...')
 
 
   useEffect(() => {
-    // setNewMsgCount( chat.messages &&
-    //         (chat.messages.filter(msg => msg.createdAt > chat.lastVizited)).length )
+
+    setNewMsgCount( chat.messages &&
+            (chat.messages.filter(msg => ( !currChat && (msg.owner._id !== myId) && (msg.createdAt > chat.lastVizited)) )).length )
+
     setMsgText( chat.messages && 
             chat.messages[chat.messages.length - 1]?.text || '...' )
-  }, [chat])
+  }, [chat, currChat, lv])
 
   return (
     <Link 
@@ -56,7 +59,12 @@ const Chat = ({ chat, currChat }) => {
             </div> 
 
             <div className={"chatMsgCount"}>
-              {/* {newMsgCount} */}
+              { !!newMsgCount && 
+                <div className={"countWrapper"}>
+                  {newMsgCount || ''}
+                </div> 
+              }
+             
             </div> 
           </div> 
 
@@ -66,6 +74,8 @@ const Chat = ({ chat, currChat }) => {
     </Link>
   )
 }
+const CChat = connect( state => ({ myId: state.promise.myProfile?.payload?._id || null }))(Chat)
+
 
 
 const FloatBtnModal = ({ chat={}, OPEN }) => {
@@ -79,10 +89,6 @@ const FloatBtnModal = ({ chat={}, OPEN }) => {
 
 const ChatList = ({ chats=[], currChatId }) => {
 
-  // const [chatsInput, setChats] = useState(chats)
-  // useEffect(() => {
-  //   setChats(chats)
-  // }, [chats])
 
   return (
       <List        
@@ -93,7 +99,7 @@ const ChatList = ({ chats=[], currChatId }) => {
               <CChatModal key={'creation'} create={true} render={FloatBtnModal} />
 
               {chats.map(chat =>          
-                    <Chat key={chat._id} chat={chat} currChat={currChatId === chat._id} /> 
+                    <CChat key={chat._id} chat={chat} currChat={currChatId === chat._id} lv={chat.lastVizited} /> 
               )}  
           </div>  
       </List>
@@ -102,5 +108,5 @@ const ChatList = ({ chats=[], currChatId }) => {
       
   )
 }
-export const CChatList = connect( state => ({ chats: Object.values(state.chats).filter(el => !!el._id)}))(ChatList)
+export const CChatList = connect( state => ({ chats: Object.values(state.chats).filter(el => !!el._id) }))(ChatList)
 

+ 2 - 1
src/components/ChatModal.jsx

@@ -8,6 +8,7 @@ import TextField from '@mui/material/TextField';
 import CloseIcon from '@mui/icons-material/Close';
 import List from '@mui/material/List';
 
+import CancelIcon from '@mui/icons-material/Cancel';
 import {ReactComponent as KickLogo} from '../assets/kick.svg'
 
 
@@ -38,7 +39,7 @@ const styleModalParrent = {
 
 const DelBtn = ({ onEvent }) => (
    <IconButton edge="end" onClick={onEvent}>
-      <KickLogo />
+      <CancelIcon />
    </IconButton>
 )
 

+ 21 - 79
src/components/Msg.jsx

@@ -6,7 +6,7 @@ import Popover from '@mui/material/Popover';
 import List from '@mui/material/List';
 import ListItemButton from '@mui/material/ListItemButton';
 import { connect } from 'react-redux'
-import { dateFromStamp, stringColor, backURL, decorateLinks } from '../helpers'
+import { dateFromStamp, stringColor, backURL } from '../helpers'
 
 import { UserAvatar, CMyAvatar } from '.'
 
@@ -127,13 +127,14 @@ const textDownload = {
 }
 
 
+
 const Msg = ({ msg, myProfile, onEdit }) => {
 
    const myId = myProfile?._id || null
    const myLogin = myProfile?.login || null
    const myNick = myProfile?.nick || null
 
-   const { _id, text, owner, media, createdAt, nextOwner, nextTime } = msg
+   const { _id, text, owner, media, createdAt, nextMsg } = msg
    const { nick, login, avatar } = owner
 
    const allMedia = {}
@@ -153,30 +154,24 @@ const Msg = ({ msg, myProfile, onEdit }) => {
 
 
    const [anchorEl, setAnchorEl] = useState(null)
-   // const [top, setTop] = useState(0)
-   // const [left, setLeft] = useState(0)
+   const [top, setTop] = useState(0)
+   const [left, setLeft] = useState(0)
  
    const handleClick = (e) => {
-      // e.preventDefault()
-      // console.log(e)
-      // if (e.button === 2) {
-      //    console.log('dasdadasdas')
-      // }
-      // console.log(e.pageX - e.target.offsetLeft, e.pageY - e.target.offsetTop)
-      // console.log(e.target.offsetTop, e.target.offsetLeft)
-
-      // setTop(e.clientY)
-      // setLeft(e.clientX)
+      e.preventDefault()
+      setTop(e.clientY)
+      setLeft(e.clientX)
       setAnchorEl(e.currentTarget)
-
    }
 
    const handleClose = () => {
       setAnchorEl(null)
    }
  
-   const open = Boolean(anchorEl);
- 
+   const open = !!anchorEl
+
+   
+
 
    const nameBlockNew = {...nameBlock, color: stringColor.stringToColor(nick || login)}
    return (
@@ -185,14 +180,11 @@ const Msg = ({ msg, myProfile, onEdit }) => {
             open={open}
             anchorEl={anchorEl}
             onClose={handleClose}
-            anchorOrigin={{
-              vertical: 'bottom',
-              horizontal: 'left',
+            anchorReference={'anchorPosition'}
+            anchorPosition={{
+               top: top,
+               left: left
             }}
-            // anchorPosition={{
-            //    top: 300,
-            //    left: 300
-            // }}
          >
               
          <List sx={{ width: '100%', maxWidth: 300, bgcolor: 'background.paper' }}>
@@ -213,7 +205,6 @@ const Msg = ({ msg, myProfile, onEdit }) => {
                </ListItemButton>
             }
 
-
          </List>
 
 
@@ -222,16 +213,16 @@ const Msg = ({ msg, myProfile, onEdit }) => {
          <div onClick={handleClick}
 
             style={  (myId === owner._id) ? 
-                        ( (nextOwner === owner._id) ? 
+                        ( (nextMsg?.owner?._id === owner._id) ? 
                            {...myMsgBlock, marginBottom: "2px"} : {...myMsgBlock, marginBottom: "15px"}) : 
-                              ( (nextOwner === owner._id) ? 
+                              ( (nextMsg?.owner?._id  === owner._id) ? 
                                  {...msgBlock, marginBottom: "2px"} : {...msgBlock, marginBottom: "15px"})
                }
          >
 
             <div style={avBlock} >
-               { (nextOwner === owner._id && 
-                     nextTime - createdAt < 600000) || 
+               { (nextMsg?.owner?._id  === owner._id && 
+                     nextMsg?.createdAt - createdAt < 600000) || 
                            ( (myId === owner._id) ? 
                                  <CMyAvatar /> : 
                                        <UserAvatar profile={owner} /> ) }
@@ -308,7 +299,7 @@ const Msg = ({ msg, myProfile, onEdit }) => {
                      </div>
                   }
 
-                  <pre style={textBlock}>
+                  <pre style={textBlock} >
                      {text}
                   </pre>
                   
@@ -324,52 +315,3 @@ const Msg = ({ msg, myProfile, onEdit }) => {
 }
 export const CMsg = connect( state => ({ myProfile: state.promise.myProfile?.payload || {}}))(Msg)
 
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-//tg
-
-{/* <div class="media-inner dark interactive" style="">
-   <canvas class="thumbnail" width="1304" height="720" style="width: 480px; height: 265px;">
-   </canvas>
-   <img class="thumbnail opacity-transition slow" alt="" 
-      draggable="true" style="width: 480px; height: 265px;">
-   <video class="full-media" width="480" height="265" autoplay="" loop="" playsinline="" draggable="true">
-      <source src="blob:https://web.telegram.org/464cc156-4e7f-415e-bec5-5fdf5b6c2d98">
-   </video>
-   <div class="message-media-duration">5:27</div>
-</div> */}
-
-
-{/* <div class="File interactive">
-   <div class="file-icon-container">
-      <div class="file-icon orange">
-         <span class="file-ext" dir="auto">
-            zip
-         </span>
-            </div>
-               <i class="action-icon icon-download"></i>
-            </div>
-         <div class="file-info">
-            <div class="file-title" dir="auto">2.1 01-02.zip</div>
-            <div class="file-subtitle" dir="auto">
-               <span>0.6 KB</span>
-            </div>
-         </div>
-      </div> */}

+ 2 - 2
src/components/MsgList.jsx

@@ -25,12 +25,12 @@ const MsgList = ({chats={}, chatId, onEdit }) => {
 
             msgArr.map(msg => 
                               <CMsg               
-                                 key={msg.nextId} 
+                                 key={msg.nextMsg?._id || null} 
                                  msg={msg} 
                                  onEdit={onEdit}
                                /> ) :
                
-                     <div>сообщений нема</div>         
+                     <div>сообщений нет</div>         
          }  
 
       </div>

+ 54 - 56
src/components/SendingField.jsx

@@ -181,71 +181,69 @@ const MsgDropZone = ({ setText, setFiles, setMsgId, files, text, onEnter }) => {
 
   return (
    <>
-      <form action="/upload" method="post" encType="multipart/form-data" id='form' > 
-         <section style={containerWrapp}>
-
+      <section style={containerWrapp}>
+
+         <SortableList 
+            onSortEnd={onSortEnd}
+            axis={'xy'}
+            pressDelay={100}
+         >
+            {files.map((file, i) => (
+               <SortableItem 
+                  key={file.url} 
+                  index={i} 
+                  file={file}
+                  onDelete={() => onDelete(i)}
+                  axis={'xy'}
+               />
+            ))}
+         </SortableList>
+      
 
-            <SortableList 
-               onSortEnd={onSortEnd}
-               axis={'xy'}
-               pressDelay={100}
-            >
-               {files.map((file, i) => (
-                  <SortableItem 
-                     key={file.url} 
-                     index={i} 
-                     file={file}
-                     onDelete={() => onDelete(i)}
-                     axis={'xy'}
-                  />
-               ))}
-            </SortableList>
-        
-
-            <div {...getRootProps({className: 'dropzone'})} style={{ display: 'flex', alignItems: 'center'}}>
-                  <input {...getInputProps()} type="file" name="media" id='media'/>
-
-                  <AttachFileIcon fontSize="large" sx={{ cursor: 'pointer' }} />
-                  
-                  <TextareaAutosize
-                     ref={textArea}  
-                     value={text}                   
-                     minRows={4}
-                     maxRows={10}
-                     placeholder="Написать сообщение..."
-                     style={{ width: '100%' }}
-                     onClick={(e) => e.stopPropagation()}
-                     onChange={(e) => setText(e.target.value)}
-                     onKeyPress={(e) => {
-                        if (e.key === 'Enter' && !e.shiftKey) { 
-                           e.preventDefault()   
-                           ;(text.match(/^\s*$/) && files.length === 0) ||
-                                 onEnter()
-                           setText('')
-                           setFiles([])
-                           setMsgId()
-                        }
-                     }}
-                  />
-            </div>
+         <div {...getRootProps({className: 'dropzone'})} style={{ display: 'flex', alignItems: 'center'}}>
+               <input {...getInputProps()} type="file" name="media" id='media'/>
+
+               <AttachFileIcon fontSize="large" sx={{ cursor: 'pointer' }} />
+               
+               <TextareaAutosize
+                  ref={textArea}  
+                  value={text}                   
+                  minRows={4}
+                  maxRows={10}
+                  placeholder="Написать сообщение..."
+                  style={{ width: '100%' }}
+                  onClick={(e) => e.stopPropagation()}
+                  onChange={(e) => setText(e.target.value)}
+                  onKeyPress={(e) => {
+                     if (e.key === 'Enter' && !e.shiftKey) { 
+                        e.preventDefault()   
+                        ;(text.match(/^\s*$/) && files.length === 0) ||
+                              onEnter()
+                        setText('')
+                        setFiles([])
+                        setMsgId()
+                     }
+                  }}
+               />
+         </div>
 
-         </section>
-      </form>
+      </section>
    </>
   )
 }
 
 
-const SendingField = ({ chatId, onSend, msg, myId }) => {
+const SendingField = ({ chatId, onSend, msg }) => {
+
+   const [text, setText] = useState(msg?.text || '')
+   const [files, setFiles] = useState(msg?.media.map(mediaFile =>( {...mediaFile, url: backURL +  mediaFile.url} )) || [])
+   const [msgId, setMsgId] = useState(msg?._id || null)
 
-   const [text, setText] = useState( (myId === msg?.owner._id && msg?.text) || '')
-   const [files, setFiles] = useState( (myId === msg?.owner._id && msg?.media.map(mediaFile =>( {...mediaFile, url: backURL +  mediaFile.url} )) ) || [])
-   const [msgId, setMsgId] = useState( (myId === msg?.owner._id && msg?._id) || undefined  )
 
    useEffect(() => {
-      setText( (myId === msg?.owner._id && msg?.text) || '')
-      setFiles( (myId === msg?.owner._id && msg?.media.map(mediaFile =>( {...mediaFile, url: backURL +  mediaFile.url} )) ) || [])
-      setMsgId( (myId === msg?.owner._id && msg?._id) || undefined )
+      setText(msg?.text || '')
+      setFiles(msg?.media.map(mediaFile =>( {...mediaFile, url: backURL +  mediaFile.url} )) || [])
+      setMsgId(msg?._id || null)
    },[msg])
 
    return (
@@ -299,5 +297,5 @@ const SendingField = ({ chatId, onSend, msg, myId }) => {
       </Box>
    )
 }
-export const CSendingField= connect( state => ({ myId: state.promise.myProfile?.payload?._id || null}),
+export const CSendingField= connect( null,
                            {onSend: actionSendMsg})(SendingField)

+ 3 - 3
src/helpers/passReq.js

@@ -4,9 +4,9 @@ import { printStrReq } from '.'
 export const passReq = {
 
    minPass:'2', 
-   char:false, 
-   bigChar:false, 
-   number:false,
+   char:true, 
+   bigChar:true, 
+   number:true,
 
    checkPass(password) {
       if (  password.length >= this.minPass && 

+ 0 - 2
src/pages/ChatsPage.jsx

@@ -29,11 +29,9 @@ const ChatsPage = ({  match:{params:{_id}}, auth,
      if (userId) {
       await getChats(userId, chatBlock, chatsCount)
 
-      // CHECK: правильно ли тут написано?
       let res = await getAllCount(userId)
       setCount(res)
      } 
-    //  console.log(chatBlock, count)
 
      return function cleanUp() {
      }

+ 26 - 21
src/pages/MsgPage.jsx

@@ -48,36 +48,44 @@ const msgSend = {
 
  
 
-const MsgBlock = ({ chatId, scroll, setScroll, getMsgs, setLastVizited, msgsCount=20 }) => {
+const MsgBlock = ({ chatId, getMsgs, setLastVizited, msgsCount=20 }) => {
 
    const [msgToEdit, setmMsgToEdit] = useState()
 
    const [msgsBlock, setMsgsBlock] = useState(0)
 
-   // перезагрузка и обновление стейта происходит тут ??????????
-   // надо как то отлавливать первый не первый рендер ????????
 
    const divRef = useRef(null)
-   // useEffect(() => {
-   //    divRef.current.scrollTop = scroll[chatId]
-   //    console.log(scroll)
-   // },[scroll])
 
 
    useEffect(() => {
       getMsgs(chatId, msgsBlock, msgsCount)
    },[msgsBlock])
 
-   // useEffect(() => {
-   //    return () => {
-   //       setLastVizited({
-   //             _id: chatId, 
-   //             // lastVizited: (Math.ceil((new Date()).getTime()/1000).toString(16) + '0'.repeat(16) )
-   //             lastVizited: (new Date()).getTime()
-   //          }
-   //       )  
-   //    }
-   // },)
+
+   useEffect(() => {
+      // console.log(chatId, "МАУНТ СООБЩЕНИЙ")
+
+      const date = (new Date()).getTime()
+      setLastVizited({
+            _id: chatId, 
+            lastVizited: date,
+         }
+      ) 
+
+      return () => {
+         // console.log(chatId, "АНМАУНТ СООБЩЕНИЙ")
+
+         const date = (new Date()).getTime()
+         setLastVizited({
+               _id: chatId, 
+               lastVizited: date,
+            }
+         )  
+      }
+   }, [chatId])
+
+   // lastVizited: (Math.ceil((new Date()).getTime()/1000).toString(16) + '0'.repeat(16) )
 
    return (
       <>  
@@ -97,7 +105,6 @@ const MsgBlock = ({ chatId, scroll, setScroll, getMsgs, setLastVizited, msgsCoun
                   style={msgs}
                   ref={divRef}
                   onScroll={(e) => {
-                     // setScroll({...scroll, [chatId]: e.target.scrollTop})
 
                      if (e.target.scrollTop*(-1) >= e.target.scrollHeight - e.target.clientHeight) {
                         setMsgsBlock(msgsBlock => msgsBlock + msgsCount)
@@ -122,11 +129,9 @@ const CMsgBlock = connect( null, {getMsgs: actionFullMsgsByChat, setLastVizited:
 
 export const MsgPage = ({ match:{params:{_id}} }) => {
 
-   const [scroll, setScroll] = useState({})
-
    return (
       <>
-         <CMsgBlock chatId={_id} scroll={scroll} setScroll={setScroll} key={_id} />
+         <CMsgBlock chatId={_id} key={_id} />
       </>
    )
 }

+ 20 - 28
src/reducers/chatsReducer.js

@@ -21,7 +21,7 @@
     }
     
     function refreshMsgs(newMsgs, oldMsgs) {
-      const msgState = oldMsgs
+      const msgState = [...oldMsgs]
 
       for (const newMsg of newMsgs || []) {   
         const currIndex = msgState.findIndex(oldMsg => oldMsg._id === newMsg._id)
@@ -53,9 +53,7 @@
       for (let i = 0; i < msgState.length; i++) {
         const msg = msgState[i]
         
-        msg.nextId = msgState[i + 1]?._id || null
-        msg.nextOwner = msgState[i + 1]?.owner?._id || null
-        msg.nextTime = msgState[i + 1]?.createdAt || 0
+        msg.nextMsg = msgState[i + 1] || null
         informedState.push(msg)
       }
 
@@ -63,6 +61,7 @@
     }
 
 
+
     function sortChats(unsortedChats) {
       return Object.fromEntries(
         Object.entries(unsortedChats).sort((a, b) => {
@@ -78,8 +77,7 @@
     }
 
     const types = {
-      // BUG: иногда часть чатов пропадают из стора, причины не до конца понятны
-      // конкретный триггер пока не обнаружен, по идее в их id как то попадает undefined
+      
       CHATS() {
         if (payload) {
 
@@ -91,33 +89,30 @@
             const oldChat = oldChats[chat._id]
             // если его еще нет, то просто записываем
             if (!oldChat) {
-              oldChats[chat._id] = chat //-----------------------------------------
+              oldChats[chat._id] = {...chat} //-----------------------------------------
               // если есть, то идем по свойствам нового чата
             } else for (const key in chat) {
               // записываем значение свойства нового и старого чата 
               const oldValue = oldChat[key]
               const newValue = chat[key]
+
               // проверяем наличие значений
-              // если оба значения есть, переходим к проверке на массив (на объект вроде не надо)
-              if (oldValue && newValue) {
-                // если массив, то выясняем какой и вызываем соответствующие функции слияния
-                if (Array.isArray(newValue)) {
-                  if (key === 'messages') {
-                    oldChats[chat._id][key] = getInfoAboutNext(refreshMsgs(newValue, oldValue))
-                  }
-                  if (key === 'members') {
-                    oldChats[chat._id][key] = newValue
-                  }
-                  // если не массив, то перезаписываем новым значением
+              // если оба значения или только новое, переходим к проверке на массив сообщений
+              if (newValue) {
+
+                // если массив сообщений, то вызываем соответствующие функции слияния
+                if (key === 'messages') {
+                  
+                  oldChats[chat._id][key] = getInfoAboutNext(refreshMsgs(newValue, oldValue))
+
                 } else {
+
                   oldChats[chat._id][key] = newValue //-----------------------------------
                 }
-
-                // если есть только новое, записать новое
-              } else if (newValue) {
-                oldChats[chat._id][key] = newValue //-------------------------------------
-                // если есть только старое, записать старое (можно ничего не делать)
+                
+              // если есть только старое, записать старое (можно ничего не делать)
               } else {
+
                 oldChats[chat._id][key] = oldValue //-------------------------------------
               }
             }
@@ -126,7 +121,6 @@
           
           const newState = sortChats(oldChats)
 
-          // console.log(newState, 'dasdasdasdasd')
           return newState
         }
         return state
@@ -138,6 +132,7 @@
       },
 
       MSGS() {
+
         if (payload && payload.length > 0) {
         
           const chatId = payload[0]?.chat?._id
@@ -145,7 +140,6 @@
           const msgState = state[chatId]?.messages || []
           
           const newMsgState = getInfoAboutNext(refreshMsgs(payload, msgState))
-          // const newMsgState = refreshMsgs(payload, msgState)
 
           const newState = {
             ...state,
@@ -158,8 +152,7 @@
           return newState
         }
         return state
-      }
-
+      }      
     }
     if (type in types) {        
         return types[type]()
@@ -177,7 +170,6 @@
         {type: 'CHAT_LEFT', payload: chat}
   )
 
-  // добавить id чата ?
   export const actionMsgList = (msgs) => (
         {type: 'MSGS', payload: msgs}
   )

+ 4 - 1
src/reducers/localStoredReducer.js

@@ -3,11 +3,14 @@
 
 export const localStoredReducer = (reducer, localStorageName) => (
     (state, action) => {
+        // console.log(state, action)
+
         if (!state && localStorage[localStorageName]) {
             return JSON.parse(localStorage[localStorageName])
         } else {
             let newState = reducer(state, action)
-            localStorage.setItem(localStorageName, JSON.stringify(newState))
+            setTimeout(() => localStorage.setItem(localStorageName, JSON.stringify(newState)))
+            
             return newState
         }
     }

+ 2 - 8
src/reducers/promiseReducer.js

@@ -17,14 +17,8 @@ export function promiseReducer(  state={},
                 error: error,
             }
         }
-        // return {
-        //    ...state,
-        //    [name]: {
-        //        status: status,
-        //        payload : payload,
-        //        error: error,
-        //    }
-        // }
+        //для пользы при работе с промисами надо бы пока PENDING не делать payload undefined 
+        //при наличии старого payload 
    }
    return state
  }

+ 29 - 82
src/reducers/store.js

@@ -6,118 +6,65 @@ import { chatsReducer } from './chatsReducer'
 import { localStoredReducer } from './localStoredReducer'
 
 import { actionAboutMe } from './findUserActions'
-import { 
-   actionMsgOne, 
-   actionChatOne, 
-   actionChatLeft
-} from './chatsReducer'
 
 import {   
-   actionGetMsgById,
-   actionGetChatById, 
+   actionOnMsg,
+   actionOnChat,
+   actionOnChatLeft,
    actionFullLogout,
 } from '../actions'
 
-import msgSound from '../assets/msgSound.ogg'
-import chatSound from '../assets/chatSound.ogg'
-// const { io } = require("socket.io-client");
 
+let initialState = localStorage.getItem('state')
 
-export const store =  createStore (  combineReducers({ 
-                                    // promise: localStoredReducer(promiseReducer, 'promise'),
-                                    // auth: localStoredReducer(authReducer, 'auth'),
-                                    // chats: localStoredReducer(chatsReducer, 'chats'),
-                                    promise: promiseReducer, 
-                                    auth: authReducer, 
-                                    chats: chatsReducer
-                                                }),
-                                    applyMiddleware(thunk)
+export const store =  createStore ( combineReducers({ 
+                                       auth: authReducer,
+                                       chats: chatsReducer, 
+                                       promise: promiseReducer, 
+                                                }, 
+                                                ),
+                                    initialState ? {chats: JSON.parse(initialState)} : {},
+                                    applyMiddleware(thunk),
                         )
 
 store.dispatch(actionAboutMe())
 
-store.subscribe(() => console.log(store.getState()))
-
+// store.subscribe(() => console.log(store.getState()))
+window.onbeforeunload = () => window.localStorage.setItem('state', JSON.stringify(store.getState().chats) )
 
 ///////////////////////////////////////////////////////////////////
 
 export const socket = window.io("ws://chat.fs.a-level.com.ua")
 
-socket.on('jwt_ok',   (data) => console.log(data))
+socket.on('jwt_ok',   (data)  => console.log(data))
 socket.on('jwt_fail', (error) => {
    console.log(error)
    store.dispatch(actionFullLogout())
 })
 
-
-function playAudio(audio) {
-   const newAudio = new Audio(audio)
-   newAudio.play()
-}
-
-
-socket.on('msg', async (msg) => { 
+socket.on('msg', (msg) => { 
    console.log('пришло смс')
-   
-   const state = store.getState()
-   const myId = state.auth.payload?.sub?.id
-   const ownerId = msg.owner?._id
-   if (myId !== ownerId) {
-      playAudio(msgSound)
-   }
-
-   const chatId = msg.chat?._id
-
-   await store.dispatch(actionMsgOne(msg)) 
-
-   let msgFull = await store.dispatch(actionGetMsgById(msg._id))
-   await store.dispatch(actionMsgOne(msgFull)) 
-
-   let chatUpdated = await store.dispatch(actionGetChatById(chatId))
-   await store.dispatch(actionChatOne(chatUpdated))
+   store.dispatch(actionOnMsg(msg))
 })
 
-socket.on('chat', async (chat) => { 
+socket.on('chat', (chat) => { 
    console.log('нас добавили в чат')
-
-   const state = store.getState()
-   const myId = state.auth.payload?.sub?.id
-   // приходится делать так, так как овнер не приходит по сокету
-   const ownerId = state.chats[chat._id]?.owner?._id
-
-   if (myId !== ownerId) {
-      playAudio(chatSound)
-   }
-   store.dispatch(actionChatOne(chat)) 
-
-   let chatFull = await store.dispatch(actionGetChatById(chat._id))
-   await store.dispatch(actionChatOne(chatFull))
+   store.dispatch(actionOnChat(chat))
+
+   // нужно переподключать сокет, так как после добавления 
+   // в чат он не работает, если юзер уже был в этом чате мембером
+   let state = store.getState()
+   socket.disconnect(true)
+   socket.connect()
+   socket.emit('jwt', state.auth.token)
 })
 
-socket.on('chat_left', async (chat) => { 
+socket.on('chat_left', (chat) => { 
    console.log('нас выкинули из чата')
-
-   const state = store.getState()
-   const myId = state.auth.payload?.sub?.id
-   const ownerId = state.chats[chat._id]?.owner?._id
-
-   if (myId !== ownerId) {
-      playAudio(chatSound)
-      store.dispatch(actionChatLeft(chat)) 
-   } else {
-      store.dispatch(actionChatOne(chat)) 
-
-      let chatFull = await store.dispatch(actionGetChatById(chat._id))
-      await store.dispatch(actionChatOne(chatFull))
-   }
-
+   store.dispatch(actionOnChatLeft(chat))
 })
 
 
 
 
-// combineReducers({cart: localStoredReducer(cartReducer, 'cart'),
-//                  promise: localStoredReducer(promiseReducer, 'promise') })
-//для пользы при работе с промисами надо бы пока PENDING не делать payload undefined 
-//при наличии старого payload 
-//http://gitlab.a-level.com.ua/gitgod/react-template/src/nosaga/src/reducers/index.js#L13
+