2 Commits 23065c89a9 ... d7ef35fa64

Author SHA1 Message Date
  Ivar d7ef35fa64 added eslint and prettier, styles refactored 2 years ago
  Ivar c53c0f8e50 logout fixed 2 years ago
100 changed files with 34448 additions and 31884 deletions
  1. 31 0
      .eslintrc.js
  2. 9 0
      .prettierrc.js
  3. 28809 28770
      package-lock.json
  4. 7 0
      package.json
  5. BIN
      public/android-chrome-192x192.png
  6. BIN
      public/android-chrome-384x384.png
  7. BIN
      public/apple-touch-icon.png
  8. BIN
      public/favicon.ico
  9. 28 5
      public/index.html
  10. BIN
      public/logo192.png
  11. BIN
      public/logo512.png
  12. 22 6
      public/manifest.json
  13. BIN
      public/mstile-150x150.png
  14. 2052 0
      public/safari-pinned-tab.svg
  15. 89 96
      src/App.js
  16. 17 118
      src/App.scss
  17. 7 6
      src/App.test.js
  18. 109 100
      src/actions/authActions.js
  19. 129 168
      src/actions/chatsActions.js
  20. 49 42
      src/actions/findActions.js
  21. 41 68
      src/actions/index.js
  22. 12 18
      src/actions/mediaActions.js
  23. 170 166
      src/actions/msgActions.js
  24. 7 9
      src/actions/routeAction.js
  25. 70 84
      src/actions/socketActions.js
  26. 0 60
      src/components/Avatar.jsx
  27. 57 0
      src/components/Avatar/Avatar.jsx
  28. 8 0
      src/components/Avatar/Avatar.scss
  29. 3 0
      src/components/Avatar/index.js
  30. 90 0
      src/components/Chat/Chat.jsx
  31. 113 0
      src/components/Chat/Chat.scss
  32. 3 0
      src/components/Chat/index.js
  33. 0 103
      src/components/ChatList.jsx
  34. 26 0
      src/components/ChatList/ChatList.jsx
  35. 4 0
      src/components/ChatList/ChatList.scss
  36. 3 0
      src/components/ChatList/index.js
  37. 0 184
      src/components/ChatMngHeader.jsx
  38. 39 0
      src/components/ChatMngHeader/ChatMenu.jsx
  39. 28 0
      src/components/ChatMngHeader/ChatMinInfo.jsx
  40. 55 0
      src/components/ChatMngHeader/ChatMngHeader.jsx
  41. 61 0
      src/components/ChatMngHeader/ChatMngHeader.scss
  42. 3 0
      src/components/ChatMngHeader/index.js
  43. 0 278
      src/components/ChatModal.jsx
  44. 40 0
      src/components/ChatModal/ChatInfoModal.jsx
  45. 273 0
      src/components/ChatModal/ChatModal.jsx
  46. 135 0
      src/components/ChatModal/ChatModal.scss
  47. 3 0
      src/components/ChatModal/index.js
  48. 0 11
      src/components/FloatBtn.jsx
  49. 16 0
      src/components/FloatBtn/FloatBtn.jsx
  50. 6 0
      src/components/FloatBtn/FloatBtn.scss
  51. 3 0
      src/components/FloatBtn/index.js
  52. 12 12
      src/components/Header.jsx
  53. 0 120
      src/components/MainMenu.jsx
  54. 39 0
      src/components/MainMenu/AboutMe.jsx
  55. 18 0
      src/components/MainMenu/LogoutBtn.jsx
  56. 60 0
      src/components/MainMenu/MainMenu.jsx
  57. 44 0
      src/components/MainMenu/MainMenu.scss
  58. 3 0
      src/components/MainMenu/index.js
  59. 0 318
      src/components/Msg.jsx
  60. 202 0
      src/components/Msg/Msg.jsx
  61. 108 0
      src/components/Msg/Msg.scss
  62. 3 0
      src/components/Msg/index.js
  63. 0 43
      src/components/MsgList.jsx
  64. 26 0
      src/components/MsgList/MsgList.jsx
  65. 8 0
      src/components/MsgList/MsgList.scss
  66. 3 0
      src/components/MsgList/index.js
  67. 0 28
      src/components/Preload.jsx
  68. 0 370
      src/components/ProfileModal.jsx
  69. 164 0
      src/components/ProfileModal/PassModal.jsx
  70. 198 0
      src/components/ProfileModal/ProfileModal.jsx
  71. 95 0
      src/components/ProfileModal/ProfileModal.scss
  72. 3 0
      src/components/ProfileModal/index.js
  73. 42 43
      src/components/SearchBlock.jsx
  74. 0 301
      src/components/SendingField.jsx
  75. 148 0
      src/components/SendingField/MsgDropZone.jsx
  76. 84 0
      src/components/SendingField/SendingField.jsx
  77. 111 0
      src/components/SendingField/SendingField.scss
  78. 3 0
      src/components/SendingField/index.js
  79. 23 32
      src/components/UserCard.jsx
  80. 0 68
      src/components/UserSearch.jsx
  81. 79 0
      src/components/UserSearch/UserSearch.jsx
  82. 17 0
      src/components/UserSearch/UserSearch.scss
  83. 3 0
      src/components/UserSearch/index.js
  84. 0 0
      src/components/_unused/ContactsModal.jsx
  85. 0 0
      src/components/_unused/MediaModal.jsx
  86. 28 0
      src/components/_unused/Preload.jsx
  87. 0 0
      src/components/_unused/UserModal.jsx
  88. 32 31
      src/components/index.js
  89. 21 0
      src/constants.js
  90. 27 19
      src/helpers/dateFuncs.js
  91. 42 40
      src/helpers/decorateLinks.js
  92. 21 21
      src/helpers/getGql.js
  93. 14 13
      src/helpers/index.js
  94. 38 40
      src/helpers/passReq.js
  95. 14 13
      src/helpers/printStrReq.js
  96. 16 0
      src/helpers/sortMediaArr.js
  97. 39 43
      src/helpers/stringColorFuncs.js
  98. 15 19
      src/index.js
  99. 18 18
      src/index.scss
  100. 0 0
      src/pages/AsidePage.jsx

+ 31 - 0
.eslintrc.js

@@ -0,0 +1,31 @@
+module.exports = {
+    env: {
+        browser: true,
+        es2021: true,
+    },
+    extends: [
+        'eslint:recommended',
+        'plugin:react/recommended',
+        'plugin:prettier/recommended',
+        'prettier',
+    ],
+    parserOptions: {
+        ecmaFeatures: {
+            jsx: true,
+        },
+        ecmaVersion: 'latest',
+        sourceType: 'module',
+    },
+    plugins: ['react', 'prettier'],
+    rules: {
+        'no-undef': 0,
+        'no-var': 1,
+        'prefer-const': 1,
+        'no-unused-vars': 1,
+        'no-prototype-builtins': 1,
+        'no-useless-escape': 1,
+        'no-irregular-whitespace': 1,
+        'react/prop-types': 0,
+        'react/no-unknown-property': 1,
+    },
+}

+ 9 - 0
.prettierrc.js

@@ -0,0 +1,9 @@
+module.exports = {
+    trailingComma: 'es5',
+    tabWidth: 4,
+    semi: false,
+    singleQuote: true,
+    quoteProps: 'as-needed',
+    arrowParens: 'always',
+    // '[javascript]': { 'editor.formatOnSave': true },
+}

File diff suppressed because it is too large
+ 28809 - 28770
package-lock.json


+ 7 - 0
package.json

@@ -48,5 +48,12 @@
       "last 1 firefox version",
       "last 1 safari version"
     ]
+  },
+  "devDependencies": {
+    "eslint": "^8.9.0",
+    "eslint-config-prettier": "^8.3.0",
+    "eslint-plugin-prettier": "^4.0.0",
+    "eslint-plugin-react": "^7.28.0",
+    "prettier": "2.5.1"
   }
 }

BIN
public/android-chrome-192x192.png


BIN
public/android-chrome-384x384.png


BIN
public/apple-touch-icon.png


BIN
public/favicon.ico


+ 28 - 5
public/index.html

@@ -5,11 +5,34 @@
     <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
     <meta name="viewport" content="width=device-width, initial-scale=1" />
     <meta name="theme-color" content="#000000" />
-    <meta
-      name="description"
-      content="TopChat"
+
+    <link
+      rel="apple-touch-icon"
+      sizes="180x180"
+      href="%PUBLIC_URL%/apple-touch-icon.png"
+    />
+    <link
+      rel="mask-icon"
+      href="%PUBLIC_URL%/safari-pinned-tab.svg"
+    />
+
+    <link
+      rel="icon"
+      sizes="150x150"
+      href="%PUBLIC_URL%/mstile-150x150.png"
+    />
+
+    <link
+      rel="icon"
+      sizes="192x192"
+      href="%PUBLIC_URL%/android-chrome-192x192.png"
+    />
+
+    <link
+      rel="icon"
+      sizes="384x384"
+      href="%PUBLIC_URL%/android-chrome-384x384.png"
     />
-    <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
     <!--
       manifest.json provides metadata used when your web app is installed on a
       user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
@@ -25,7 +48,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>TopChat</title>
+    <title>Limbo</title>
   </head>
   <body>
     <noscript>You need to enable JavaScript to run this app.</noscript>

BIN
public/logo192.png


BIN
public/logo512.png


+ 22 - 6
public/manifest.json

@@ -1,21 +1,37 @@
 {
-  "short_name": "React App",
-  "name": "Create React App Sample",
+  "short_name": "Limbo",
+  "name": "Limbo",
   "icons": [
     {
       "src": "favicon.ico",
       "sizes": "64x64 32x32 24x24 16x16",
       "type": "image/x-icon"
     },
+
     {
-      "src": "logo192.png",
+      "src": "apple-touch-icon.png",
       "type": "image/png",
-      "sizes": "192x192"
+      "sizes": "180x180"
     },
     {
-      "src": "logo512.png",
+      "src": "safari-pinned-tab.svg"
+    },
+
+    {
+      "src": "mstile-150x150.png",
       "type": "image/png",
-      "sizes": "512x512"
+      "sizes": "150x150"
+    },
+    
+    {
+        "src": "/android-chrome-192x192.png",
+        "sizes": "192x192",
+        "type": "image/png"
+    },
+    {
+        "src": "/android-chrome-384x384.png",
+        "sizes": "384x384",
+        "type": "image/png"
     }
   ],
   "start_url": ".",

BIN
public/mstile-150x150.png


File diff suppressed because it is too large
+ 2052 - 0
public/safari-pinned-tab.svg


+ 89 - 96
src/App.js

@@ -1,117 +1,110 @@
-import React, {useState, useEffect, useRef, createContext } from 'react'
+import React, { useState, createContext } from 'react'
 import './App.scss'
-import {Provider, connect} from 'react-redux'
-import {Router, Route, Link, Redirect, Switch} from 'react-router-dom'
-import createHistory from "history/createBrowserHistory"
+import { Provider, connect } from 'react-redux'
+import { Router, Route, Switch } from 'react-router-dom'
+import createHistory from 'history/createBrowserHistory'
 
-import {
-  store,
-  socket
-} from "./reducers"
+import { store, socket } from './reducers'
 
 import {
-  Login, 
-  Register, 
-  Main,
-  AsidePage,
-  CChatsPage,
-  CMsgPage,
-  PageNoChat
-} from "./pages"
+    Login,
+    Register,
+    Main,
+    AsidePage,
+    CChatsPage,
+    CMsgPage,
+    PageNoChat,
+} from './pages'
 
 import Grid from '@mui/material/Grid'
 import { useTheme } from '@mui/material/styles'
 import useMediaQuery from '@mui/material/useMediaQuery'
 
-
 export const history = createHistory()
 
-
 export const AdaptiveContext = createContext(null)
 
-const AdaptiveGrid = ({ }) => {
-
-  // const [,route, histId] = history.location.pathname.split('/')
-
-  const theme = useTheme()
-  const matches = useMediaQuery(theme.breakpoints.up('sm'))
-
-  const [aside, setAside] = useState(true)
-
-  return (
-    <>
-     <AdaptiveContext.Provider value={ {setAside} }>
-
-      { (matches || aside) &&
-        <Grid item xs={12} sm={4}>
-          <AsidePage>
-            <Switch> 
-              <Route path="/main/:_id" component={CChatsPage} /> 
-              <Route path="/find" component={PageNoChat} />
-              <Route path="*" component={CChatsPage} /> 
-            </Switch>
-          </AsidePage>
-        </Grid>
-      }
-
-      { (matches || !aside) && 
-        <Grid item xs={12} sm={8}>
-          <Switch> 
-            <Route path="/main" component={PageNoChat} exact/>
-            <Route path="/main/:_id" component={CMsgPage} exact/>
-            <Route path="*" component={PageNoChat} /> 
-          </Switch>
-        </Grid>
-      }
-       
-     </AdaptiveContext.Provider>    
-    </>
-  )
+const AdaptiveGrid = () => {
+    // const [,route, histId] = history.location.pathname.split('/')
+
+    const theme = useTheme()
+    const matches = useMediaQuery(theme.breakpoints.up('sm'))
+
+    const [aside, setAside] = useState(true)
+
+    return (
+        <>
+            <AdaptiveContext.Provider value={{ setAside }}>
+                {(matches || aside) && (
+                    <Grid item xs={12} sm={4}>
+                        <AsidePage>
+                            <Switch>
+                                <Route
+                                    path="/main/:_id"
+                                    component={CChatsPage}
+                                />
+                                <Route path="/find" component={PageNoChat} />
+                                <Route path="*" component={CChatsPage} />
+                            </Switch>
+                        </AsidePage>
+                    </Grid>
+                )}
+
+                {(matches || !aside) && (
+                    <Grid item xs={12} sm={8}>
+                        <Switch>
+                            <Route path="/main" component={PageNoChat} exact />
+                            <Route
+                                path="/main/:_id"
+                                component={CMsgPage}
+                                exact
+                            />
+                            <Route path="*" component={PageNoChat} />
+                        </Switch>
+                    </Grid>
+                )}
+            </AdaptiveContext.Provider>
+        </>
+    )
 }
 
-
 const AuthSwitch = ({ token }) => {
-  
-  if (token) {
-    console.log('подключение сокета')
-    socket.emit('jwt', token)
-  }
-  
-  return (
-    <>      
-      {token ? 
-        <Main>
-
-          <AdaptiveGrid />
-
-        </Main>
-      :    
-        <Switch>        
-          <Route path="/reg" component={Register} />
-          <Route path="/login" component={Login} />
-          <Route path="*" component={Login} />
-        </Switch>
-      }
-    </>
-  )
+    if (token) {
+        console.log('подключение сокета')
+        socket.emit('jwt', token)
+    }
+
+    return (
+        <>
+            {token ? (
+                <Main>
+                    <AdaptiveGrid />
+                </Main>
+            ) : (
+                <Switch>
+                    <Route path="/reg" component={Register} />
+                    <Route path="/login" component={Login} />
+                    <Route path="*" component={Login} />
+                </Switch>
+            )}
+        </>
+    )
 }
-const CAuthSwitch = connect(state => ({ token: state.auth.token || null }))(AuthSwitch)
-
+const CAuthSwitch = connect((state) => ({ token: state.auth.token || null }))(
+    AuthSwitch
+)
 
 function App() {
-
-  window.addEventListener("contextmenu", e => e.preventDefault());
-
-  return (
-    <Router history={history}>
-      <Provider store={store}>
-          <div className="App">
-                                
-           <CAuthSwitch />
-          
-          </div>
-        </Provider>
-    </Router>  
-  )
+    // window.addEventListener("contextmenu", e => e.preventDefault());
+
+    return (
+        <Router history={history}>
+            <Provider store={store}>
+                <div className="App">
+                    <CAuthSwitch />
+                </div>
+            </Provider>
+        </Router>
+    )
 }
 export default App

+ 17 - 118
src/App.scss

@@ -1,124 +1,23 @@
-
-
 .avatarInModal {
-
-   .MuiAvatar-root {
-      width: 80px;
-      height: 80px;
-   }
-}
-
-.chatItem {
-   height: 70px;
-   max-width: 100%;
-   display: flex;
-   justify-content: flex-start;
-   align-items: center;
-   padding: 5px;
-
-   .chatAvatar {
-      flex-shrink: 0;
-      .MuiAvatar-root {
-         width: 50px;
-         height: 50px;
-      }
-   }
-
-   .chatBody {
-      display: flex;
-      flex-direction: column;
-      justify-content: space-between;
-      align-items: flex-start;
-      max-width: 100%;
-      flex-grow: 1;
-      margin-left: 15px;      
-
-      .chatTitle {
-         width: 100%;
-         display: flex;
-         justify-content: space-between;
-         align-items: center;
-
-         .chatName {
-            max-width: 70%;
-            flex-grow: 1;
-
-            overflow: hidden;
-            white-space: nowrap;
-            text-overflow: ellipsis;
-            font-weight: 500;
-         }
-
-         .chatStatus {
-            flex-shrink: 0;
-         }
-      }
-
-      .chatSubTitle {
-         width: 100%;
-         display: flex;
-         justify-content: space-between;
-         align-items: center;
-
-         .chatText {
-            max-width: 70%;
-            flex-grow: 1;
-
-            overflow: hidden;
-            white-space: nowrap;
-            text-overflow: ellipsis;
-            font-size: 14px;
-            font-weight: 300;
-         }
-
-         .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;
-            }
-         }
-      }
-   }
+    .MuiAvatar-root {
+        width: 80px;
+        height: 80px;
+    }
 }
 
-.selectedChat {
-   background-color: #1976d2dd;
-   border-radius: 5px;
-   .chatName {
-      color: #fff;
-   }
-   .chatStatus {
-      color: #fff;
-   }
-   .chatSubTitle {
-      color: #fff;
-   }
+.unstyledLink {
+    text-decoration: none;
 }
 
-.notSelectedChat {
-   background-color: #fff;
-   .chatName {
-      color: #222;
-   }
-   .chatStatus {
-      color: #555;
-   }
-   .chatSubTitle {
-      color: #555;
-   }
+.styleModalParrent {
+    position: absolute;
+    top: 50%;
+    left: 50%;
+    transform: translate(-50%, -50%);
+    background-color: #fff;
+    border: 1px solid #999;
+    padding: 24px;
+    display: flex;
+    flex-direction: column;
+    justify-content: space-between;
 }
-.notSelectedChat:hover {
-   background-color: #eee;
-}
-

+ 7 - 6
src/App.test.js

@@ -1,8 +1,9 @@
-import { render, screen } from '@testing-library/react';
-import App from './App';
+import React from 'react'
+import { render, screen } from '@testing-library/react'
+import App from './App'
 
 test('renders learn react link', () => {
-  render(<App />);
-  const linkElement = screen.getByText(/learn react/i);
-  expect(linkElement).toBeInTheDocument();
-});
+    render(<App />)
+    const linkElement = screen.getByText(/learn react/i)
+    expect(linkElement).toBeInTheDocument()
+})

+ 109 - 100
src/actions/authActions.js

@@ -1,60 +1,67 @@
-import {gql} from '../helpers'
-import {   
-   actionPromise, 
-   actionAuthLogin, 
-   actionAuthLogout,
-   actionAboutMe,
+import { history } from '../App'
+import { gql } from '../helpers'
+import {
+    actionPromise,
+    actionAuthLogin,
+    actionAuthLogout,
+    actionAboutMe,
+    actionChatsClear,
 } from '../reducers'
-import {   
-   actionUploadFile
-} from './mediaActions'
-
-
-   export const actionFullLogout = () => (
-      async (dispatch) => {
-         await dispatch(actionAuthLogout())
-      }
-   )   
- 
-   const actionLogin = (login, password) => (
-      actionPromise('login', gql(`query log($login: String, $password: String) {
+import { actionUploadFile } from './mediaActions'
+
+export const actionFullLogout = () => async (dispatch) => {
+    history.push('/login')
+    await dispatch(actionAuthLogout())
+    await dispatch(actionChatsClear())
+}
+
+const actionLogin = (login, password) =>
+    actionPromise(
+        'login',
+        gql(
+            `query log($login: String, $password: String) {
          login(login: $login, password: $password)
-      }`, {login, password}))
-   )
-
-   export const actionFullLogin = (login, password) => (
-      async (dispatch) => {
-         let token = await dispatch(actionLogin(login, password))
-         if (token) {
-            await dispatch(actionAuthLogin(token))
-            await dispatch(actionAboutMe())
-         }
-      }
-   )
-   
-   const actionRegister = (login, password, nick) => (
-      actionPromise('register', gql(`mutation reg($user:UserInput) {
+      }`,
+            { login, password }
+        )
+    )
+
+export const actionFullLogin = (login, password) => async (dispatch) => {
+    history.push('/main')
+    const token = await dispatch(actionLogin(login, password))
+    if (token) {
+        await dispatch(actionAuthLogin(token))
+        await dispatch(actionAboutMe())
+    }
+}
+
+const actionRegister = (login, password, nick) =>
+    actionPromise(
+        'register',
+        gql(
+            `mutation reg($user:UserInput) {
          UserUpsert(user:$user) {
          _id 
          }
       }
-      `, {user: {login, password, nick}})      
-      )
-   )
-
-   export const actionFullRegister = (login, password, nick) => (
-      async (dispatch) => {
-         let regId = await dispatch(actionRegister(login, password, nick))
-         if (regId) {
+      `,
+            { user: { login, password, nick } }
+        )
+    )
+
+export const actionFullRegister =
+    (login, password, nick) => async (dispatch) => {
+        const regId = await dispatch(actionRegister(login, password, nick))
+        if (regId) {
             await dispatch(actionFullLogin(login, password))
-         }
-      }
-   )
-
-
-
-   const actionUpdateUserAvatar = (userId, avatarId) => (
-      actionPromise('updateUserAv', gql(`mutation updateUserAv($user:UserInput) {
+        }
+    }
+
+const actionUpdateUserAvatar = (userId, avatarId) =>
+    actionPromise(
+        'updateUserAv',
+        gql(
+            `mutation updateUserAv($user:UserInput) {
          UserUpsert(user:$user) {
             _id   
             login  
@@ -63,63 +70,65 @@ import {
                url
             }
          }
-      }`, { user: {_id: userId, avatar: {_id: avatarId} } }))
-   )
-
-   const actionUpdateUserLogin = (userId, newLogin, newNick) => (
-      actionPromise('updateUser', gql(`mutation updateUser($user:UserInput) {
+      }`,
+            { user: { _id: userId, avatar: { _id: avatarId } } }
+        )
+    )
+
+const actionUpdateUserLogin = (userId, newLogin, newNick) =>
+    actionPromise(
+        'updateUser',
+        gql(
+            `mutation updateUser($user:UserInput) {
          UserUpsert(user:$user) {
             _id   
             login  
             nick
          }
-      }`, { user: {_id: userId, login: newLogin, nick: newNick } }))
-   )
-
-
-
-   export const actionSetUserInfo = (name, file, newLogin, newNick) => (
-      async (dispatch, getState) => {
-         let {auth} = getState()
-         let userId = auth.payload?.sub?.id
-         if (file) {
-            let fileObj = await dispatch(actionUploadFile(name, file))
+      }`,
+            { user: { _id: userId, login: newLogin, nick: newNick } }
+        )
+    )
+
+export const actionSetUserInfo =
+    (name, file, newLogin, newNick) => async (dispatch, getState) => {
+        const { auth } = getState()
+        const userId = auth.payload?.sub?.id
+        if (file) {
+            const fileObj = await dispatch(actionUploadFile(name, file))
             await dispatch(actionUpdateUserAvatar(userId, fileObj?._id))
-         } 
-         await dispatch(actionUpdateUserLogin(userId, newLogin, newNick))         
-         await dispatch(actionAboutMe())             
-      }
-   )
-
-   
-
-   const actionChangePass = (_id, password) => (
-      actionPromise('register', gql(`mutation reg($user:UserInput) {
+        }
+        await dispatch(actionUpdateUserLogin(userId, newLogin, newNick))
+        await dispatch(actionAboutMe())
+    }
+
+const actionChangePass = (_id, password) =>
+    actionPromise(
+        'register',
+        gql(
+            `mutation reg($user:UserInput) {
          UserUpsert(user:$user) {
          _id 
          }
       }
-      `, {user: {_id, password}})      
-      )
-   )
-
-   export const actionSetUserPass = (password) => (
-      async (dispatch, getState) => {
-         let {auth} = getState()
-         let userId = auth.payload?.sub?.id
-
-         console.log(userId, password)
-         await dispatch(actionChangePass(userId, password))         
-      }
-   )
-
-
-      // не работает
-   // export const actionRemoveUser = (userId) => (
-   //    actionPromise('changeUser', gql(`mutation changeUser($user: UserInput) {
-   //       UserDelete (user: $user){
-   //         _id
-   //       }     
-   //    }`, { user: { _id: userId }  }
-   //    ))
-   // )
+      `,
+            { user: { _id, password } }
+        )
+    )
+
+export const actionSetUserPass = (password) => async (dispatch, getState) => {
+    const { auth } = getState()
+    const userId = auth.payload?.sub?.id
+
+    await dispatch(actionChangePass(userId, password))
+}
+
+// не работает
+// export const actionRemoveUser = (userId) => (
+//    actionPromise('changeUser', gql(`mutation changeUser($user: UserInput) {
+//       UserDelete (user: $user){
+//         _id
+//       }
+//    }`, { user: { _id: userId }  }
+//    ))
+// )

+ 129 - 168
src/actions/chatsActions.js

@@ -1,20 +1,21 @@
 import { history } from '../App'
-import {gql} from '../helpers'
-import {   
-   actionPromise, 
-   actionChatList,
-   actionChatOne,
-   actionChatLeft,
-   actionAboutMe
+import { gql } from '../helpers'
+import {
+    actionPromise,
+    actionChatList,
+    actionChatOne,
+    actionChatLeft,
+    actionAboutMe,
 } from '../reducers'
 import { actionGetAllLastMsg } from './msgActions'
 import { actionUploadFile } from './mediaActions'
 
-
-
 // в массив newMemders передавать объекты только с полем _id
-const actionUpdateChat = (title, members, chatId) => (
-   actionPromise('updateChat', gql(`mutation updateChat($chat:ChatInput) {
+const actionUpdateChat = (title, members, chatId) =>
+    actionPromise(
+        'updateChat',
+        gql(
+            `mutation updateChat($chat:ChatInput) {
       ChatUpsert(chat:$chat) {
          _id
          title
@@ -41,42 +42,46 @@ const actionUpdateChat = (title, members, chatId) => (
          }
          lastModified    
       }
-   }`, { chat: {_id: chatId, title, members} }))
-)
-
+   }`,
+            { chat: { _id: chatId, title, members } }
+        )
+    )
 
 // MediaUpsert нужен только для добавления данных для загруженного файла
 // и дальнейшего отображения его через эти данные (через аватары, сообщения)
-const actionUpdateChatAvatar = (mediaId, chatId) => (
-    actionPromise('uploadFile', gql(`mutation uploadFile($media: MediaInput) {  
+const actionUpdateChatAvatar = (mediaId, chatId) =>
+    actionPromise(
+        'uploadFile',
+        gql(
+            `mutation uploadFile($media: MediaInput) {  
         MediaUpsert(media: $media) {
             _id
             url   
         }
-    }`, { media: { _id: mediaId, chatAvatars: {_id: chatId} } }
-    ))
- )
-
-
-export const actionSetChatInfo = (name, file, title, members, chatId) => (
-   async (dispatch) => {
-      let chat = await dispatch(actionUpdateChat(title, members, chatId))         
-
-      if (file && chat._id) {
-        let fileObj = await dispatch(actionUploadFile(name, file))
-         let chatAvatar = await dispatch(actionUpdateChatAvatar(fileObj?._id, chat._id))
-         await dispatch(actionChatOne({_id: chat._id, avatar: chatAvatar}))
-      } 
-   }
-)
-
-
-
-
+    }`,
+            { media: { _id: mediaId, chatAvatars: { _id: chatId } } }
+        )
+    )
+
+export const actionSetChatInfo =
+    (name, file, title, members, chatId) => async (dispatch) => {
+        const chat = await dispatch(actionUpdateChat(title, members, chatId))
+
+        if (file && chat._id) {
+            const fileObj = await dispatch(actionUploadFile(name, file))
+            const chatAvatar = await dispatch(
+                actionUpdateChatAvatar(fileObj?._id, chat._id)
+            )
+            await dispatch(actionChatOne({ _id: chat._id, avatar: chatAvatar }))
+        }
+    }
 
 // поиск по значению в массиве объектов - { 'members._id': userId }
-export const actionGetChatsByUser = (userId, skipCount=0, limitCount=50) => (
-   actionPromise('userChats', gql(`query userChats($q: String) {
+export const actionGetChatsByUser = (userId, skipCount = 0, limitCount = 50) =>
+    actionPromise(
+        'userChats',
+        gql(
+            `query userChats($q: String) {
       ChatFind (query: $q){
          _id
          title
@@ -103,40 +108,40 @@ export const actionGetChatsByUser = (userId, skipCount=0, limitCount=50) => (
          }
          lastModified
       }     
-   }`, { 
-         q: JSON.stringify([  {
-                                $or: [
-                                       { ___owner: userId }, 
-                                       { 'members._id': userId }
-                                    ]  
-                              },
-                              { 
-                                 sort: [{lastModified: -1}],  
-                                 skip: [skipCount], 
-                                 limit: [limitCount] 
-                              } 
-                           ])
-      }
-   ))
-)
-
-
-export const actionFullChatList = (userId, currentCount, limitCount=50) => (
-   async (dispatch, getState) => {
-      let payload = await dispatch(actionGetChatsByUser(userId, currentCount, limitCount))
-      if (payload) {
-         await dispatch(actionChatList(payload))
-
-         await dispatch(actionGetAllLastMsg(payload))
-      }
-   }
-)
-
-
-
+   }`,
+            {
+                q: JSON.stringify([
+                    {
+                        $or: [{ ___owner: userId }, { 'members._id': userId }],
+                    },
+                    {
+                        sort: [{ lastModified: -1 }],
+                        skip: [skipCount],
+                        limit: [limitCount],
+                    },
+                ]),
+            }
+        )
+    )
+
+export const actionFullChatList =
+    (userId, currentCount, limitCount = 50) =>
+    async (dispatch) => {
+        const payload = await dispatch(
+            actionGetChatsByUser(userId, currentCount, limitCount)
+        )
+        if (payload) {
+            await dispatch(actionChatList(payload))
+
+            await dispatch(actionGetAllLastMsg(payload))
+        }
+    }
 
-export const actionGetChatById = (chatId) => (
-   actionPromise('chatById', gql(`query chatById($q: String) {
+export const actionGetChatById = (chatId) =>
+    actionPromise(
+        'chatById',
+        gql(
+            `query chatById($q: String) {
       ChatFindOne (query: $q){
          _id
          title
@@ -163,30 +168,32 @@ export const actionGetChatById = (chatId) => (
          }
          lastModified
       }     
-   }`, { 
-         q: JSON.stringify([ { _id: chatId } ])
-      }
-   ))
-)
-
-
-
-
-export const actionChatsCount = (userId) => (
-   actionPromise('chatsCount', gql(`query chatsCount($q: String) {
+   }`,
+            {
+                q: JSON.stringify([{ _id: chatId }]),
+            }
+        )
+    )
+
+export const actionChatsCount = (userId) =>
+    actionPromise(
+        'chatsCount',
+        gql(
+            `query chatsCount($q: String) {
       ChatCount (query: $q)  
-   }`, { 
-         q: JSON.stringify([ { ___owner: userId } ])
-      }
-   ))
-)
-
-
-
+   }`,
+            {
+                q: JSON.stringify([{ ___owner: userId }]),
+            }
+        )
+    )
 
 // происходит когда юзер уходит сам, иначе в чат добавляются юзер, а не наоборот
-const actionUpdateUserChats = (userId, newChats) => (
-   actionPromise('updateUserChats', gql(`mutation updateUserChats($user:UserInput) {
+const actionUpdateUserChats = (userId, newChats) =>
+    actionPromise(
+        'updateUserChats',
+        gql(
+            `mutation updateUserChats($user:UserInput) {
       UserUpsert(user:$user) {
          _id   
          login  
@@ -195,88 +202,42 @@ const actionUpdateUserChats = (userId, newChats) => (
             _id
          }
       }
-   }`, { user: {_id: userId, chats: newChats } }))
-)
-
-export const removeUserChat = (chatId) => (
-   async (dispatch, getState) => {
-      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) {
+   }`,
+            { user: { _id: userId, chats: newChats } }
+        )
+    )
+
+export const removeUserChat = (chatId) => async (dispatch, getState) => {
+    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 [, , histId] = history.location.pathname.split('/')
+        if (histId === chatId) {
             history.push('/')
-         }
-      } else {
-         const chat = await dispatch(actionGetChatById(chatId))
-         await dispatch(actionChatOne(chat))
-      }
-
-      await dispatch(actionAboutMe()) 
-   }
-)
-
-
-
-
-
-
-
-
-
-
-
-
+        }
+    } else {
+        const chat = await dispatch(actionGetChatById(chatId))
+        await dispatch(actionChatOne(chat))
+    }
 
-// вспомогательный запрос
-// export const actionGetAllChats = (userId) => (
-//    actionPromise('getAll', gql(`query getAll($q: String){
-//       ChatFind (query: $q){
-//             _id
-//             title
-//             avatar {
-//                _id
-//                url
-//             }
-//             owner {
-//                _id
-//                login
-//                avatar {
-//                   _id
-//                   url
-//                }
-//             }
-//             members {
-//                _id
-//                login
-//                avatar {
-//                   _id
-//                   url
-//                }
-//             }
-//             lastModified
-//       }         
-//    }`, { 
-//          q: JSON.stringify([ { 'members._id': userId }, { skip: [0], limit: [100] } ])
-//          }
-//    ))
-// )
+    await dispatch(actionAboutMe())
+}
 
 // не работает
 // export const actionRemoveChat = (chatId) => (
 //    actionPromise('removeChat', gql(`mutation removeChat($chat:ChatInput) {
 //       ChatDelete(chat:$chat) {
-//          _id     
+//          _id
 //       }
 //    }`, { chat: {_id: chatId} }))
-// )
+// )

+ 49 - 42
src/actions/findActions.js

@@ -1,11 +1,11 @@
-import {gql} from '../helpers'
-import {   
-   actionPromise
-} from '../reducers'
+import { gql } from '../helpers'
+import { actionPromise } from '../reducers'
 
-
-export const actionFindUsers = (word, skipCount=0, limitCount=20) => (
-   actionPromise('findUsers', gql(`query findUsers($q: String) {
+export const actionFindUsers = (word, skipCount = 0, limitCount = 20) =>
+    actionPromise(
+        'findUsers',
+        gql(
+            `query findUsers($q: String) {
       UserFind (query: $q){
          _id
          createdAt
@@ -16,26 +16,33 @@ export const actionFindUsers = (word, skipCount=0, limitCount=20) => (
             url
          }
       }     
-   }`, { 
-         q: JSON.stringify([  {  
-                                 $or: [                                    
-                                    {login: `/${word}/`}, 
-                                    {nick: `/${word}/`}
-                                 ] 
-                              },
-                              { 
-                                 sort: [{login: 1}],  
-                                 skip: [skipCount], 
-                                 limit: [limitCount] 
-                              } 
-                           ])
-      }
-   ))
-)
+   }`,
+            {
+                q: JSON.stringify([
+                    {
+                        $or: [{ login: `/${word}/` }, { nick: `/${word}/` }],
+                    },
+                    {
+                        sort: [{ login: 1 }],
+                        skip: [skipCount],
+                        limit: [limitCount],
+                    },
+                ]),
+            }
+        )
+    )
 
-// поиск чатов конкретного юзера по названию 
-export const actionFindChatsByUser = (userId, word, skipCount=0, limitCount=20) => (
-   actionPromise('findChatsByUser', gql(`query findChatsByUser($q: String) {
+// поиск чатов конкретного юзера по названию
+export const actionFindChatsByUser = (
+    userId,
+    word,
+    skipCount = 0,
+    limitCount = 20
+) =>
+    actionPromise(
+        'findChatsByUser',
+        gql(
+            `query findChatsByUser($q: String) {
       ChatFind (query: $q){
          _id
          _id
@@ -63,21 +70,21 @@ export const actionFindChatsByUser = (userId, word, skipCount=0, limitCount=20)
          }
          lastModified  
       }     
-   }`, { 
-         q: JSON.stringify([  {  title: `/${word}/`, 
-                                 $or: [   
-                                    { ___owner: userId }, 
-                                    { 'members._id': userId }                                    
-                                 ] 
-                              },
-                              { 
-                                 sort: [{title: 1}],  
-                                 skip: [skipCount], 
-                                 limit: [limitCount] 
-                              } 
-                           ])
-      }
-   ))
-)
+   }`,
+            {
+                q: JSON.stringify([
+                    {
+                        title: `/${word}/`,
+                        $or: [{ ___owner: userId }, { 'members._id': userId }],
+                    },
+                    {
+                        sort: [{ title: 1 }],
+                        skip: [skipCount],
+                        limit: [limitCount],
+                    },
+                ]),
+            }
+        )
+    )
 
 // можно добавить еще поиск чатов по мемберам

+ 41 - 68
src/actions/index.js

@@ -1,81 +1,54 @@
-// далекие от редьюсеров экшены 
+// далекие от редьюсеров экшены
 import {
-   actionFullLogout,
-   actionFullLogin, 
-   actionFullRegister, 
-   actionSetUserInfo,
-   actionSetUserPass,
+    actionFullLogout,
+    actionFullLogin,
+    actionFullRegister,
+    actionSetUserInfo,
+    actionSetUserPass,
 } from './authActions'
 import {
-   actionGetChatsByUser,
-   actionGetChatById,
-
-   actionSetChatInfo,
-   actionFullChatList,
-   actionChatsCount,
-   removeUserChat
+    actionGetChatsByUser,
+    actionGetChatById,
+    actionSetChatInfo,
+    actionFullChatList,
+    actionChatsCount,
+    removeUserChat,
 } from './chatsActions'
 import {
-   actionGetMsgsByChat,
-   actionMsgsCount,
-   actionSendMsg,
-   actionGetAllLastMsg,
-   actionGetMsgById,
-
-   actionFullMsgsByChat,
+    actionGetMsgsByChat,
+    actionMsgsCount,
+    actionSendMsg,
+    actionGetAllLastMsg,
+    actionGetMsgById,
+    actionFullMsgsByChat,
 } from './msgActions'
-import {
-   actionUploadFile,
-} from './mediaActions'
-import {
-   actionFindUsers
-} from './findActions'
-import {
-   actionOnMsg,
-   actionOnChat,
-   actionOnChatLeft,
-} from './socketActions'
-
+import { actionUploadFile } from './mediaActions'
+import { actionFindUsers } from './findActions'
+import { actionOnMsg, actionOnChat, actionOnChatLeft } from './socketActions'
 
 export {
-   actionFullLogout,
-   actionFullLogin, 
-   actionFullRegister, 
-   actionSetUserInfo,
-   actionSetUserPass,
-} 
-export {
-   actionGetChatsByUser,
-   actionGetChatById,
-
-   actionSetChatInfo,
-   actionFullChatList,
-   actionChatsCount,
-   removeUserChat
+    actionFullLogout,
+    actionFullLogin,
+    actionFullRegister,
+    actionSetUserInfo,
+    actionSetUserPass,
 }
 export {
-   actionGetMsgsByChat,
-   actionMsgsCount,
-   actionSendMsg,
-   actionGetAllLastMsg,
-   actionGetMsgById,
-
-   actionFullMsgsByChat,
+    actionGetChatsByUser,
+    actionGetChatById,
+    actionSetChatInfo,
+    actionFullChatList,
+    actionChatsCount,
+    removeUserChat,
 }
 export {
-   actionUploadFile,
+    actionGetMsgsByChat,
+    actionMsgsCount,
+    actionSendMsg,
+    actionGetAllLastMsg,
+    actionGetMsgById,
+    actionFullMsgsByChat,
 }
-export {
-   actionFindUsers
-} 
-export {
-   actionOnMsg,
-   actionOnChat,
-   actionOnChatLeft,
-} 
-
-
-
-   
-
-
+export { actionUploadFile }
+export { actionFindUsers }
+export { actionOnMsg, actionOnChat, actionOnChatLeft }

+ 12 - 18
src/actions/mediaActions.js

@@ -1,22 +1,16 @@
-import {gql} from '../helpers'
-import {   
-   actionPromise, 
-} from '../reducers'
-
+import { actionPromise } from '../reducers'
 
 export const actionUploadFile = (name, file) => {
-    let fd = new FormData()
+    const fd = new FormData()
     fd.append(name, file)
-    return actionPromise('uploadFile', fetch(
-        'http://chat.fs.a-level.com.ua/upload', {
-            method: "POST",
-            headers: localStorage.authToken ? {Authorization: 'Bearer ' + localStorage.authToken} : {},
-            body: fd
-        }
-    ).then(res => res.json()))
+    return actionPromise(
+        'uploadFile',
+        fetch('http://chat.fs.a-level.com.ua/upload', {
+            method: 'POST',
+            headers: localStorage.authToken
+                ? { Authorization: 'Bearer ' + localStorage.authToken }
+                : {},
+            body: fd,
+        }).then((res) => res.json())
+    )
 }
-
-    
-
-
-

+ 170 - 166
src/actions/msgActions.js

@@ -1,21 +1,17 @@
-import {gql} from '../helpers'
-import {   
-   actionPromise,
-   actionMsgList,
-   actionMsgOne,
-   actionChatOne
+import { gql } from '../helpers'
+import {
+    actionPromise,
+    actionMsgList,
+    actionMsgOne,
+    actionChatOne,
 } from '../reducers'
-import {   
-   actionUploadFile,
-} from './mediaActions'
-import {   
-   actionGetChatById
-} from './chatsActions'
+import { actionUploadFile } from './mediaActions'
 
-
-
-export const actionGetMsgsByChat = (chatId, skipCount=0, limitCount=50) => (
-   actionPromise('chatMsgs', gql(`query chatMsgs($q: String) {
+export const actionGetMsgsByChat = (chatId, skipCount = 0, limitCount = 50) =>
+    actionPromise(
+        'chatMsgs',
+        gql(
+            `query chatMsgs($q: String) {
       MessageFind (query: $q){
          _id
          createdAt
@@ -52,96 +48,105 @@ export const actionGetMsgsByChat = (chatId, skipCount=0, limitCount=50) => (
             _id
          }
       }     
-   }`, { 
-         q: JSON.stringify([  { "chat._id": {$in: [chatId]} },
-                              { 
-                                 sort: [{_id: -1}],  
-                                 skip: [skipCount], 
-                                 limit: [limitCount] 
-                              } 
-                           ])
-      }
-   ))
-)
-
-export const actionFullMsgsByChat = (chatId, currentCount, limitCount=50) => (
-   async (dispatch, getState) => {
-
-      let chat = getState().chats[chatId]
-
-      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))
-         if (payload) {
-            await dispatch(actionMsgList(payload))
-         }
-      } 
-
-   }
-)
-
-
-
-const actionFirstMsgByChat = (chatId) => (
-   actionPromise('firstMsg', gql(`query firstMsg($q: String) {
+   }`,
+            {
+                q: JSON.stringify([
+                    { 'chat._id': { $in: [chatId] } },
+                    {
+                        sort: [{ _id: -1 }],
+                        skip: [skipCount],
+                        limit: [limitCount],
+                    },
+                ]),
+            }
+        )
+    )
+
+export const actionFullMsgsByChat =
+    (chatId, currentCount, limitCount = 50) =>
+    async (dispatch, getState) => {
+        const chat = getState().chats[chatId]
+
+        if (
+            !chat ||
+            !chat.messages ||
+            (chat.messages[0]?._id !== chat.firstMsgId &&
+                (chat.messages?.length ?? 0) < currentCount + limitCount)
+        ) {
+            // console.log(chat)
+
+            const payload = await dispatch(
+                actionGetMsgsByChat(chatId, currentCount, limitCount)
+            )
+            if (payload) {
+                await dispatch(actionMsgList(payload))
+            }
+        }
+    }
+
+const actionFirstMsgByChat = (chatId) =>
+    actionPromise(
+        'firstMsg',
+        gql(
+            `query firstMsg($q: String) {
       MessageFind (query: $q){
          _id
       }     
-   }`, { 
-         q: JSON.stringify([  { "chat._id": chatId },
-                              { 
-                                 sort: [{_id: 1}],
-                                 skip: [0], 
-                                 limit: [1] 
-                              } 
-                           ])
-      }
-   ))
-)
-
-
-
-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]))
-         firstMsgs.length && dispatch(actionChatOne({ _id: lastMsgs[0].chat._id,
-                                                      firstMsgId: firstMsgs[0]._id}))
-      }
-   }
-)
-
-
-
-
-export const actionMsgsCount = (chatId) => (
-   actionPromise('msgsCount', gql(`query msgsCount($q: String) {
+   }`,
+            {
+                q: JSON.stringify([
+                    { 'chat._id': chatId },
+                    {
+                        sort: [{ _id: 1 }],
+                        skip: [0],
+                        limit: [1],
+                    },
+                ]),
+            }
+        )
+    )
+
+export const actionGetAllLastMsg = (chats) => async (dispatch, getState) => {
+    const 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]))
+        firstMsgs.length &&
+            dispatch(
+                actionChatOne({
+                    _id: lastMsgs[0].chat._id,
+                    firstMsgId: firstMsgs[0]._id,
+                })
+            )
+    }
+}
+
+export const actionMsgsCount = (chatId) =>
+    actionPromise(
+        'msgsCount',
+        gql(
+            `query msgsCount($q: String) {
       MessageCount (query: $q)  
-   }`, { 
-         q: JSON.stringify([ { "chat._id": chatId } ])
-      }
-   ))
-)
-
-
-
-
-export const actionGetMsgById = (msgId) => (
-   actionPromise('msgById', gql(`query msgById($q: String) {
+   }`,
+            {
+                q: JSON.stringify([{ 'chat._id': chatId }]),
+            }
+        )
+    )
+
+export const actionGetMsgById = (msgId) =>
+    actionPromise(
+        'msgById',
+        gql(
+            `query msgById($q: String) {
       MessageFindOne (query: $q){
          _id
          createdAt
@@ -178,16 +183,18 @@ export const actionGetMsgById = (msgId) => (
             _id
          }
       }     
-   }`, { 
-         q: JSON.stringify([ { _id: msgId } ])
-      }
-   ))
-)
-
-
-
-const actionUpdateMsg = (chatId, text, media, msgId ) => (
-   actionPromise('updateMsg', gql(`mutation updateMsg($msg: MessageInput) {
+   }`,
+            {
+                q: JSON.stringify([{ _id: msgId }]),
+            }
+        )
+    )
+
+const actionUpdateMsg = (chatId, text, media, msgId) =>
+    actionPromise(
+        'updateMsg',
+        gql(
+            `mutation updateMsg($msg: MessageInput) {
       MessageUpsert(message: $msg) {
          _id
          createdAt
@@ -225,60 +232,57 @@ const actionUpdateMsg = (chatId, text, media, msgId ) => (
             _id
          }
       }
-   }`, { msg: {
-               _id: msgId, 
-               text, 
-               chat: {_id: chatId}, 
-               media, 
-               // replyTo: {_id: undefined}
-            } }
-   ))
-)
-
+   }`,
+            {
+                msg: {
+                    _id: msgId,
+                    text,
+                    chat: { _id: chatId },
+                    media,
+                    // replyTo: {_id: undefined}
+                },
+            }
+        )
+    )
 
 // медиа - массив объектов с ид медиа
-export const actionSendMsg = (chatId, text, inputName, files, msgId) => (
-   async (dispatch) => {
-// тут нужно отделить уже залитые файлы от тех которые лежат локально
-// локальные залить и получить ид, с залитых просто получить ид
-      const mediaToUpload = []
-      const media = []
-      for (const file of files) {
-         if (file.url.match(/blob/)) {
-
-            mediaToUpload.push(dispatch(actionUploadFile(inputName, file)))
-         } else {
-
-            let fileObj = file
-            media.push({_id: fileObj?._id})
-         }
-      }
-
-      let fileArr = await Promise.all(mediaToUpload)
-      if (fileArr) {
-         for (const uploadedFile of fileArr) {
-            media.push({_id: uploadedFile?._id})
-         }
-      }
-
-      let payload = await dispatch(actionUpdateMsg(chatId, text, media, msgId ))
-      // if (payload) {
-      //    await dispatch(actionMsgOne(payload))
-      //    let chatUpdated = await dispatch(actionGetChatById(chatId))
-      //    await dispatch(actionChatOne(chatUpdated))
-      // }
-   }
-)
-
-
-  
-   // const actionRemoveMsg = (msgId) => (
-   //    actionPromise('removeMsg', gql(`mutation removeMsg($msg: MessageInput) {
-   //       MessageDelete(message: $msg) {
-   //          _id 
-   //       }
-   //    }`, { msg: { _id: msgId } }
-   //    ))
-   // )
-
+export const actionSendMsg =
+    (chatId, text, inputName, files, msgId) => async (dispatch) => {
+        // тут нужно отделить уже залитые файлы от тех которые лежат локально
+        // локальные залить и получить ид, с залитых просто получить ид
+        const mediaToUpload = []
+        const media = []
+        for (const file of files) {
+            if (file.url.match(/blob/)) {
+                mediaToUpload.push(dispatch(actionUploadFile(inputName, file)))
+            } else {
+                let fileObj = file
+                media.push({ _id: fileObj?._id })
+            }
+        }
 
+        const fileArr = await Promise.all(mediaToUpload)
+        if (fileArr) {
+            for (const uploadedFile of fileArr) {
+                media.push({ _id: uploadedFile?._id })
+            }
+        }
+
+        const payload = await dispatch(
+            actionUpdateMsg(chatId, text, media, msgId)
+        ) // eslint-disable-line
+        // if (payload) {
+        //    await dispatch(actionMsgOne(payload))
+        //    let chatUpdated = await dispatch(actionGetChatById(chatId))
+        //    await dispatch(actionChatOne(chatUpdated))
+        // }
+    }
+
+// const actionRemoveMsg = (msgId) => (
+//    actionPromise('removeMsg', gql(`mutation removeMsg($msg: MessageInput) {
+//       MessageDelete(message: $msg) {
+//          _id
+//       }
+//    }`, { msg: { _id: msgId } }
+//    ))
+// )

+ 7 - 9
src/actions/routeAction.js

@@ -1,13 +1,11 @@
-
-
-function* routeWorker({match}) {
+function* routeWorker({ match }) {
     console.log(match)
     if (match.path in queries) {
-      const {name, query, variables} = queries[match.path](match)
-      yield call(promiseWorker, actionPromise(name, gql(query, variables)))
+        const { name, query, variables } = queries[match.path](match)
+        yield call(promiseWorker, actionPromise(name, gql(query, variables)))
     }
-  }
-  
-  function* routeWatcher() {
+}
+
+export function* routeWatcher() {
     yield takeEvery('ROUTE', routeWorker)
-  }
+}

+ 70 - 84
src/actions/socketActions.js

@@ -1,97 +1,83 @@
 import { history } from '../App'
-import { 
-   actionAboutMe,
-   actionMsgOne, 
-   actionMsgList,
-   actionChatOne, 
-   actionChatLeft
+import {
+    actionAboutMe,
+    actionMsgOne,
+    actionMsgList,
+    actionChatOne,
+    actionChatLeft,
 } from '../reducers'
 
-import {   
-   actionGetMsgsByChat,
-   actionGetMsgById,
-   actionGetChatById, 
-} from '.'
+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()
+    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) {
+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 [, , 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()) 
-   }
-)
+        }
+    } else {
+        dispatch(actionChatOne(chat))
+        const chatFull = await dispatch(actionGetChatById(chat._id))
+        await dispatch(actionChatOne(chatFull))
+    }
+
+    await dispatch(actionAboutMe())
+}

+ 0 - 60
src/components/Avatar.jsx

@@ -1,60 +0,0 @@
-import React from 'react';
-import Avatar from '@mui/material/Avatar';
-
-import { backURL, stringColor }  from '../helpers'
-import { connect }  from 'react-redux'
-
-const small = {
-  height: '40px', 
-  width: '40px'
-}
-const middle = {
-  height: '50px', 
-  width: '50px'
-}
-const big = {
-  height: '80px', 
-  width: '80px'
-}
-
-
-function getUrl(obj) {
-  if (obj.localUrl) {
-    return obj.localUrl
-  } else if (obj.avatar?.url) {
-    return backURL + obj.avatar?.url
-  } else {
-    return false
-  }
-}
-
-export const UserAvatar = ({  profile, bigSize=false }) => {
-  return (
-    <>
-    {
-        getUrl(profile) ?
-        <Avatar  sx={ bigSize ? big : small } 
-                alt={profile.nick || profile.login } src={getUrl(profile)} /> :
-        <Avatar  sx={ bigSize ? big : small }
-                {...stringColor.stringAvatar(profile.nick || profile.login)} /> 
-    }        
-    </>
-  ) 
-}
-export const CMyAvatar = connect( state => ({ profile: state.promise.myProfile?.payload || {} }))(UserAvatar)
-
-
-export const ChatAvatar = ({ chat, bigSize=false }) => {
-  return (
-    <>
-    {
-        getUrl(chat) ?
-        <Avatar  sx={ bigSize ? big : small } 
-                alt={chat.title } src={getUrl(chat)} /> :
-        <Avatar  sx={ bigSize ? big : small }
-                {...stringColor.stringAvatar(chat.title)} /> 
-    }        
-    </>
-  ) 
-}
-

+ 57 - 0
src/components/Avatar/Avatar.jsx

@@ -0,0 +1,57 @@
+import React from 'react'
+import Avatar from '@mui/material/Avatar'
+import { connect } from 'react-redux'
+
+import { stringColor } from '../../helpers'
+import { backURL } from '../../constants'
+
+function getUrl(obj) {
+    if (obj.localUrl) {
+        return obj.localUrl
+    } else if (obj.avatar?.url) {
+        return backURL + obj.avatar?.url
+    } else {
+        return false
+    }
+}
+
+export const UserAvatar = ({ profile, bigSize = false }) => {
+    return (
+        <>
+            {getUrl(profile) ? (
+                <Avatar
+                    className={`${bigSize ? 'big' : 'small'}`}
+                    alt={profile.nick || profile.login}
+                    src={getUrl(profile)}
+                />
+            ) : (
+                <Avatar
+                    className={`${bigSize ? 'big' : 'small'}`}
+                    {...stringColor.stringAvatar(profile.nick || profile.login)}
+                />
+            )}
+        </>
+    )
+}
+export const CMyAvatar = connect((state) => ({
+    profile: state.promise.myProfile?.payload || {},
+}))(UserAvatar)
+
+export const ChatAvatar = ({ chat, bigSize = false }) => {
+    return (
+        <>
+            {getUrl(chat) ? (
+                <Avatar
+                    className={`${bigSize ? 'big' : 'small'}`}
+                    alt={chat.title}
+                    src={getUrl(chat)}
+                />
+            ) : (
+                <Avatar
+                    className={`${bigSize ? 'big' : 'small'}`}
+                    {...stringColor.stringAvatar(chat.title)}
+                />
+            )}
+        </>
+    )
+}

+ 8 - 0
src/components/Avatar/Avatar.scss

@@ -0,0 +1,8 @@
+.small {
+    height: 40px;
+    width: 40px;
+}
+.big {
+    height: 80px;
+    width: 80px;
+}

+ 3 - 0
src/components/Avatar/index.js

@@ -0,0 +1,3 @@
+import { ChatAvatar, UserAvatar, CMyAvatar } from './Avatar'
+
+export { ChatAvatar, UserAvatar, CMyAvatar }

+ 90 - 0
src/components/Chat/Chat.jsx

@@ -0,0 +1,90 @@
+import React, { useState, useEffect, useContext } from 'react'
+import { Link } from 'react-router-dom'
+import { connect } from 'react-redux'
+import './Chat.scss'
+
+import { AdaptiveContext } from '../../App'
+import { ChatAvatar } from '..'
+
+const Chat = ({ chat, currChat, myId, lv }) => {
+    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 ||
+                (chat.messages[chat.messages.length - 1]?.media && 'медиа'))) ||
+            '...'
+    )
+
+    useEffect(() => {
+        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.messages[chat.messages.length - 1]?.media &&
+                        'медиа'))) ||
+                '...'
+        )
+    }, [chat, currChat, lv])
+
+    const contextObj = useContext(AdaptiveContext)
+    return (
+        <Link
+            style={{ textDecoration: 'none' }}
+            to={`/main/${chat._id}`}
+            onClick={() => {
+                contextObj.setAside(false)
+            }}
+        >
+            <div
+                className={`chatItem ${
+                    currChat ? 'selectedChat' : 'notSelectedChat'
+                } `}
+            >
+                <div className={'chatAvatar'}>
+                    <ChatAvatar chat={chat} />
+                </div>
+
+                <div className={'chatBody'}>
+                    <div className={'chatTitle'}>
+                        <div className={'chatName'}>{chat.title}</div>
+
+                        <div className={'chatStatus'}></div>
+                    </div>
+
+                    <div className={'chatSubTitle'}>
+                        <div className={'chatText'}>{msgText}</div>
+
+                        <div className={'chatMsgCount'}>
+                            {!!newMsgCount && (
+                                <div className={'countWrapper'}>
+                                    {newMsgCount || ''}
+                                </div>
+                            )}
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </Link>
+    )
+}
+export const CChat = connect((state) => ({
+    myId: state.promise.myProfile?.payload?._id || null,
+}))(Chat)

+ 113 - 0
src/components/Chat/Chat.scss

@@ -0,0 +1,113 @@
+.chatItem {
+    height: 70px;
+    max-width: 100%;
+    display: flex;
+    justify-content: flex-start;
+    align-items: center;
+    padding: 5px;
+
+    .chatAvatar {
+        flex-shrink: 0;
+        .MuiAvatar-root {
+            width: 50px;
+            height: 50px;
+        }
+    }
+
+    .chatBody {
+        display: flex;
+        flex-direction: column;
+        justify-content: space-between;
+        align-items: flex-start;
+        max-width: 100%;
+        flex-grow: 1;
+        margin-left: 15px;
+
+        .chatTitle {
+            width: 100%;
+            display: flex;
+            justify-content: space-between;
+            align-items: center;
+
+            .chatName {
+                max-width: 70%;
+                flex-grow: 1;
+
+                overflow: hidden;
+                white-space: nowrap;
+                text-overflow: ellipsis;
+                font-weight: 500;
+            }
+
+            .chatStatus {
+                flex-shrink: 0;
+            }
+        }
+
+        .chatSubTitle {
+            width: 100%;
+            display: flex;
+            justify-content: space-between;
+            align-items: center;
+
+            .chatText {
+                max-width: 70%;
+                flex-grow: 1;
+
+                overflow: hidden;
+                white-space: nowrap;
+                text-overflow: ellipsis;
+                font-size: 14px;
+                font-weight: 300;
+            }
+
+            .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;
+                }
+            }
+        }
+    }
+}
+
+.selectedChat {
+    background-color: #1976d2dd;
+    border-radius: 5px;
+    .chatName {
+        color: #fff;
+    }
+    .chatStatus {
+        color: #fff;
+    }
+    .chatSubTitle {
+        color: #fff;
+    }
+}
+
+.notSelectedChat {
+    background-color: #fff;
+    .chatName {
+        color: #222;
+    }
+    .chatStatus {
+        color: #555;
+    }
+    .chatSubTitle {
+        color: #555;
+    }
+}
+.notSelectedChat:hover {
+    background-color: #eee;
+}

+ 3 - 0
src/components/Chat/index.js

@@ -0,0 +1,3 @@
+import { CChat } from './Chat'
+
+export { CChat }

+ 0 - 103
src/components/ChatList.jsx

@@ -1,103 +0,0 @@
-import React, {useState, useEffect, useContext} from 'react';
-import List from '@mui/material/List';
-import { AdaptiveContext } from '../App'
-
-import {Link} from 'react-router-dom'
-
-import { ChatAvatar } from "../components"
-import { connect }  from 'react-redux'
-
-
-const Chat = ({ chat, currChat, myId, lv }) => {
-
-  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 || 
-                                                (chat.messages[chat.messages.length - 1]?.media && 'медиа') ) || '...')
-
-
-  useEffect(() => {
-
-    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.messages[chat.messages.length - 1]?.media && 'медиа') ) || '...' )
-  }, [chat, currChat, lv])
-
-  const contextObj = useContext(AdaptiveContext)
-  return (
-    <Link 
-      style={{ textDecoration: 'none' }}
-      to={`/main/${chat._id}`}
-      onClick={() => {
-        contextObj.setAside(false)
-      }}
-      >
-     
-      <div
-        className={`chatItem ${currChat ? "selectedChat" : "notSelectedChat"} `}
-        >
-        <div className={"chatAvatar"}>
-          <ChatAvatar chat={chat} />
-        </div>   
-
-        <div className={"chatBody"}>
-
-          <div className={"chatTitle"}>
-
-            <div className={"chatName"}>
-              {chat.title} 
-            </div> 
-
-            <div className={"chatStatus"}>
-      
-            </div> 
-          </div> 
-
-          <div className={"chatSubTitle"}>
-
-            <div className={"chatText"}>
-              {msgText}
-            </div> 
-
-            <div className={"chatMsgCount"}>
-              { !!newMsgCount && 
-                <div className={"countWrapper"}>
-                  {newMsgCount || ''}
-                </div> 
-              }
-             
-            </div> 
-          </div> 
-
-        </div>
-              
-      </div>
-    </Link>
-  )
-}
-const CChat = connect( state => ({ myId: state.promise.myProfile?.payload?._id || null }))(Chat)
-
-
-
-const ChatList = ({ chats=[], currChatId }) => {
-
-  return (
-      <List        
-        sx={{ maxWidth: '100%', bgcolor: 'background.paper' }}
-        >
-          <div>
-             
-              {chats.map(chat =>          
-                    <CChat key={chat._id} chat={chat} currChat={currChatId === chat._id} lv={chat.lastVizited} /> 
-              )}  
-          </div>  
-      </List>      
-  )
-}
-export const CChatList = connect( state => ({ chats: Object.values(state.chats).filter(el => !!el._id) }))(ChatList)
-

+ 26 - 0
src/components/ChatList/ChatList.jsx

@@ -0,0 +1,26 @@
+import React from 'react'
+import List from '@mui/material/List'
+import { connect } from 'react-redux'
+import './ChatList.scss'
+
+import { CChat } from '..'
+
+const ChatList = ({ chats = [], currChatId }) => {
+    return (
+        <List className="chatsWrapper">
+            <div>
+                {chats.map((chat) => (
+                    <CChat
+                        key={chat._id}
+                        chat={chat}
+                        currChat={currChatId === chat._id}
+                        lv={chat.lastVizited}
+                    />
+                ))}
+            </div>
+        </List>
+    )
+}
+export const CChatList = connect((state) => ({
+    chats: Object.values(state.chats).filter((el) => !!el._id),
+}))(ChatList)

+ 4 - 0
src/components/ChatList/ChatList.scss

@@ -0,0 +1,4 @@
+.chatsWrapper {
+    max-width: 100%;
+    background-color: #fff;
+}

+ 3 - 0
src/components/ChatList/index.js

@@ -0,0 +1,3 @@
+import { CChatList } from './ChatList'
+
+export { CChatList }

+ 0 - 184
src/components/ChatMngHeader.jsx

@@ -1,184 +0,0 @@
-import React, {useState, useEffect, useContext} from 'react'
-import { useTheme } from '@mui/material/styles';
-import useMediaQuery from '@mui/material/useMediaQuery';
-import IconButton from '@mui/material/IconButton';
-import ArrowBackIcon from '@mui/icons-material/ArrowBack';
-import MoreVertIcon from '@mui/icons-material/MoreVert';
-import Menu from '@mui/material/Menu';
-import MenuItem from '@mui/material/MenuItem';
-
-import {Link} from 'react-router-dom'
-
-import { AdaptiveContext } from '../App'
-
-import { ChatAvatar, CChatModal } from "../components"
-
-import { printEnding } from "../helpers"
-import { removeUserChat } from "../actions"
-import { connect } from 'react-redux'
-
-
-const chatMngBody = {
-   width: "100%",
-   display: "flex",
-   justifyContent: "flex-start",
-   aligneItems: "center"
-}
-   const blockMobile = {
-      flexShrink: 0,
-      flexGrow: 0,
-      flexBasis: "50px",
-      display: "flex",
-      justifyContent: "flex-start",
-      aligneItems: "center",
-   }
-   const blockLeft = {
-      flexGrow: 1,
-      width: "calc(100% - 100px)",
-      display: "flex",
-      justifyContent: "flex-start",
-      aligneItems: "center",
-      cursor: "pointer"
-   }
-      const blockAv = {
-         flexShrink: 0,
-         flexGrow: 0,
-         flexBasis: "40px",
-         margin: "auto 0"
-      }
-      const blockInfo = {
-         display: "flex",
-         flexDirection: "column",
-         justifyContent: "space-between",
-         overflow: "hidden",
-         whiteSpace: "nowrap",
-         textOverflow: "ellipsis",
-         marginLeft: "15px",
-         userSelect: "none",
-      }
-         const blockName = {
-            fontSize: "16px",
-            fontWeight: "500",
-         }
-         const blockMemb = {
-            fontSize: "14px",
-            fontWeight: "300",
-         }
-   const blockRight = {
-      flexShrink: 0,
-      flexGrow: 0,
-      flexBasis: "50px",
-      display: "flex",
-      justifyContent: "flex-end",
-      aligneItems: "center"
-   }
-
-
-
-const chatMinInfo = ({ chat, OPEN }) => {
-   if (chat) {
-      return (
-         <div style={blockLeft} onClick={OPEN}>
-            <div style={blockAv}>
-               <ChatAvatar chat={chat} />
-            </div>
-            <div style={blockInfo}>
-               <div style={blockName}>
-                  {chat.title}
-               </div>
-               <div style={blockMemb}>
-                  {chat.members && `${chat.members.length} участник${printEnding(String(chat.members.length))}`} 
-               </div>
-            </div>
-         </div>
-      )
-   } else {
-      return (
-         <></>
-      )
-   }
-}
-
-
-const ChatMenu = ({ chatId, onLeave }) => {
-   const [anchorEl, setAnchorEl] = useState(null)
-   const open = !!anchorEl
-
-   const handleClick = (event) => {
-     setAnchorEl(event.currentTarget)
-   }
-
-   const handleClose = () => {
-     setAnchorEl(null)
-   }
-
-   return (
-      <>
-         <IconButton 
-               style={{ color: '#fff' }} 
-               onClick={handleClick}>
-            <MoreVertIcon />
-         </IconButton>
-   
-         <Menu
-            anchorEl={anchorEl}
-            open={open}
-            onClose={handleClose}
-         >
-
-            <MenuItem onClick={handleClose}>
-               Что то сделать
-            </MenuItem>
-
-            <MenuItem onClick={() => onLeave(chatId)}>
-               Покинуть чат
-            </MenuItem>
-                                    
-         </Menu>
-      </>
-   ) 
-}
-const CChatMenu = connect(null, {onLeave: removeUserChat})(ChatMenu)
-
-
-
-const ChatMngHeader = ({ chats, chatId }) => {
-
-   const chat = chats[chatId]
-
-   const theme = useTheme()
-   const matches = useMediaQuery(theme.breakpoints.up('sm'))
-
-   const contextObj = useContext(AdaptiveContext)
-   return (
-      <div style={chatMngBody}>
-
-         { matches ||
-            <div style={ blockMobile } >
-               <Link
-                  style={{ textDecoration: 'none' }}
-                  to={`/main`}
-                  onClick={() => {
-                     contextObj.setAside(true)
-                  }}
-               >
-                  <IconButton 
-                     style={{ color: '#fff' }} 
-                     >
-                     <ArrowBackIcon />
-                  </IconButton>
-               </Link>
-            </div>
-         }
-
-         <CChatModal key={chatId} create={false} chat={chat} render={chatMinInfo} />         
-
-         <div style={blockRight}>
-            <CChatMenu chatId={chatId} />
-         </div>
-      </div>
-   )
-}
-export const CChatMngHeader = connect(state => ( { chats: state.chats || []} ))(ChatMngHeader)
-
-

+ 39 - 0
src/components/ChatMngHeader/ChatMenu.jsx

@@ -0,0 +1,39 @@
+import React, { useState } from 'react'
+import IconButton from '@mui/material/IconButton'
+import MoreVertIcon from '@mui/icons-material/MoreVert'
+import Menu from '@mui/material/Menu'
+import MenuItem from '@mui/material/MenuItem'
+import { connect } from 'react-redux'
+import './ChatMngHeader.scss'
+
+import { removeUserChat } from '../../actions'
+
+const ChatMenu = ({ chatId, onLeave }) => {
+    const [anchorEl, setAnchorEl] = useState(null)
+    const open = !!anchorEl
+
+    const handleClick = (event) => {
+        setAnchorEl(event.currentTarget)
+    }
+
+    const handleClose = () => {
+        setAnchorEl(null)
+    }
+
+    return (
+        <>
+            <IconButton style={{ color: '#fff' }} onClick={handleClick}>
+                <MoreVertIcon />
+            </IconButton>
+
+            <Menu anchorEl={anchorEl} open={open} onClose={handleClose}>
+                <MenuItem onClick={handleClose}>Что то сделать</MenuItem>
+
+                <MenuItem onClick={() => onLeave(chatId)}>
+                    Покинуть чат
+                </MenuItem>
+            </Menu>
+        </>
+    )
+}
+export const CChatMenu = connect(null, { onLeave: removeUserChat })(ChatMenu)

+ 28 - 0
src/components/ChatMngHeader/ChatMinInfo.jsx

@@ -0,0 +1,28 @@
+import React from 'react'
+import './ChatMngHeader.scss'
+
+import { ChatAvatar } from '..'
+import { printEnding } from '../../helpers'
+
+export const ChatMinInfo = ({ chat, OPEN }) => {
+    if (chat) {
+        return (
+            <div className="blockLeft" onClick={OPEN}>
+                <div className="blockAv">
+                    <ChatAvatar chat={chat} />
+                </div>
+                <div className="blockInfo">
+                    <div className="blockName">{chat.title}</div>
+                    <div className="blockMemb">
+                        {chat.members &&
+                            `${chat.members.length} участник${printEnding(
+                                String(chat.members.length)
+                            )}`}
+                    </div>
+                </div>
+            </div>
+        )
+    } else {
+        return <></>
+    }
+}

+ 55 - 0
src/components/ChatMngHeader/ChatMngHeader.jsx

@@ -0,0 +1,55 @@
+import React, { useContext } from 'react'
+import { useTheme } from '@mui/material/styles'
+import useMediaQuery from '@mui/material/useMediaQuery'
+import IconButton from '@mui/material/IconButton'
+import ArrowBackIcon from '@mui/icons-material/ArrowBack'
+import { Link } from 'react-router-dom'
+import { connect } from 'react-redux'
+import './ChatMngHeader.scss'
+
+import { AdaptiveContext } from '../../App'
+import { CChatModal } from '..'
+import { CChatMenu } from './ChatMenu'
+import { ChatMinInfo } from './ChatMinInfo'
+
+const ChatMngHeader = ({ chats, chatId }) => {
+    const chat = chats[chatId]
+
+    const theme = useTheme()
+    const matches = useMediaQuery(theme.breakpoints.up('sm'))
+
+    const contextObj = useContext(AdaptiveContext)
+    return (
+        <div className="chatMngBody">
+            {matches || (
+                <div className="blockMobile">
+                    <Link
+                        className="unstyledLink"
+                        to={`/main`}
+                        onClick={() => {
+                            contextObj.setAside(true)
+                        }}
+                    >
+                        <IconButton style={{ color: '#fff' }}>
+                            <ArrowBackIcon />
+                        </IconButton>
+                    </Link>
+                </div>
+            )}
+
+            <CChatModal
+                key={chatId}
+                create={false}
+                chat={chat}
+                render={ChatMinInfo}
+            />
+
+            <div className="blockRight">
+                <CChatMenu chatId={chatId} />
+            </div>
+        </div>
+    )
+}
+export const CChatMngHeader = connect((state) => ({
+    chats: state.chats || [],
+}))(ChatMngHeader)

+ 61 - 0
src/components/ChatMngHeader/ChatMngHeader.scss

@@ -0,0 +1,61 @@
+.chatMngBody {
+    width: 100%;
+    display: flex;
+    justify-content: flex-start;
+    align-items: center;
+
+    .blockMobile {
+        flex-shrink: 0;
+        flex-grow: 0;
+        flex-basis: 50px;
+        display: flex;
+        justify-content: flex-start;
+        align-items: center;
+    }
+
+    .blockLeft {
+        flex-grow: 1;
+        width: calc(100% - 100px);
+        display: flex;
+        justify-content: flex-start;
+        align-items: center;
+        cursor: pointer;
+
+        .blockAv {
+            flex-shrink: 0;
+            flex-grow: 0;
+            flex-basis: 40px;
+            margin: auto 0;
+        }
+
+        .blockInfo {
+            display: flex;
+            flex-direction: column;
+            justify-content: space-between;
+            overflow: hidden;
+            white-space: nowrap;
+            text-overflow: ellipsis;
+            margin-left: 15px;
+            user-select: none;
+
+            .blockName {
+                font-size: 16px;
+                font-weight: 500;
+            }
+
+            .blockMemb {
+                font-size: 14px;
+                font-weight: 300;
+            }
+        }
+    }
+
+    .blockRight {
+        flex-shrink: 0;
+        flex-grow: 0;
+        flex-basis: 50px;
+        display: flex;
+        justify-content: flex-end;
+        align-items: center;
+    }
+}

+ 3 - 0
src/components/ChatMngHeader/index.js

@@ -0,0 +1,3 @@
+import { CChatMngHeader } from './ChatMngHeader'
+
+export { CChatMngHeader }

+ 0 - 278
src/components/ChatModal.jsx

@@ -1,278 +0,0 @@
-import React, {useEffect, useState} from 'react';
-import Box from '@mui/material/Box';
-import Modal from '@mui/material/Modal';
-import Button from '@mui/material/Button';
-import IconButton from '@mui/material/IconButton';
-import Typography from '@mui/material/Typography';
-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'
-
-
-import {useDropzone} from 'react-dropzone';
-
-import { ChatAvatar, CUserSearch, UserCard } from '.'
-
-import { printStrReq } from "../helpers"
-import {connect}  from 'react-redux'
-import { actionSetChatInfo } from '../actions'
-
-const styleModalParrent = {
-   position: 'absolute',
-   top: '50%',
-   left: '50%',
-   transform: 'translate(-50%, -50%)',
-   width: '70%',
-   maxWidth: '1000px',
-   height: '95%',
-   bgcolor: 'background.paper',
-   border: '1px solid #999',
-   boxShadow: 24,
-   p: 3,
-   display: 'flex',
-   flexDirection: 'column',
-   justifyContent: 'space-between',
-}
-
-const DelBtn = ({ onEvent }) => (
-   <IconButton edge="end" onClick={onEvent}>
-      <CancelIcon />
-   </IconButton>
-)
-
-const ChatModal = ({ minTitle="2", chat, onСonfirm, titleError, myProfile, create, render }) => {
-
-   const [open, setOpen] = useState(false)
-   const handleOpen = () =>  setOpen(true) 
-   const handleClose = () => setOpen(false)
- 
-   const [title, setTitle] = useState(chat?.title || '')
-   const [titleBlur, setTitleBlur] = useState(false)
-
-   const [img, setImg] = useState(null)
-   const {
-      getRootProps,
-      getInputProps
-   } = useDropzone({   
-      accept: 'image/*', 
-      maxFiles: 1,
-      onDrop: (acceptedFiles) => {
-         setImg(acceptedFiles[0])
-      }
-   })
-
-
-   const [members, setMembers] = useState(chat?.members || [myProfile])
-
-   const onAddMember = (newMember) => {
-      setMembers([...members, newMember])
-   }
-   const onDelMember = (i) => {
-      setMembers(members.filter((el, index) => index !== i)) 
-   }
- 
- 
-   const [wrongAlert, setWrongAlert] = useState(false)
- 
-   useEffect(() => {
-      if (titleError?.payload === null) {
-         setWrongAlert(true)
-      } else {
-         setWrongAlert(false)
-      }
-   },[titleError])
-
-   useEffect(() => {
-      setTitle(chat?.title || '')
-      setImg(null)
-      setMembers(chat?.members || [myProfile])
-   },[open])
- 
-
-
-   function prepareMembers(members) {
-      const newMembers = []
-
-      for (const member of members) {
-
-         if (create) {
-
-            if (member._id !== myProfile?._id) {
-
-               newMembers.push({_id: member._id})
-            }            
-         } else {
-            
-            newMembers.push({_id: member._id})
-         }
-        
-      }
-      return newMembers
-   }
-
-   const OpenBtn = render
-   return (
-     <>
-       <OpenBtn chat={chat} OPEN={handleOpen} />
-
-       <Modal
-         open={open}
-         onClose={handleClose}
-       >
-          
-         <Box sx={styleModalParrent}>
-
-            <Box sx={{ display: 'flex', justifyContent: 'space-between', borderBottom: '1px solid #999', pb: 1, mb: 1 }}>
-
-               <Box sx={{ display: 'flex', justifyContent: 'start' }}>
-                  <Typography variant="h6">
-                    {((chat?.owner?._id !== myProfile?._id) && !create) ?
-                                 'Информация (редактирование запрещено)' : create ? 'Создание чата' : 'Редактирование чата' } 
-                  </Typography>
-               </Box>
-
-               <Box sx={{ display: 'flex', justifyContent: 'end' }}>
-                  <IconButton aria-label="delete" onClick={handleClose}>
-                     <CloseIcon />
-                  </IconButton>
-               </Box>
-
-            </Box>     
-
-           
-
-            <Box sx={{ display: 'flex', justifyContent: 'space-between', pl: 3, pr: 6 }} >
-
-                  <Box sx={{ display: 'flex', justifyContent: 'start', alignItems: 'start', flexBasis: "35%" }}> 
-                        <section className="container">
-                           <Box {...getRootProps({className: 'dropzone'})} 
-                                 sx={{ cursor: 'pointer', height: '80px', display: 'flex' }}
-                                 className="avatarInModal" >   
-
-                               <ChatAvatar 
-                                       chat={{  title: title, avatar: {url: chat?.avatar?.url || ''},
-                                             localUrl: img && URL.createObjectURL(img)}} bigSize={true} /> 
-      
-                              <input {...getInputProps()} type="file" name="media" id='mediaUser' />
-                              <Box sx={{ pl: 4 }} >
-                                 <Typography sx={{ fontWeight: 500, fontSize: 18 }}>
-                                    Аватар
-                                 </Typography>                                 
-                              </Box>
-                           </Box>
-                        </section>
-                  </Box>
-   
-                  <Box sx={{ display: 'flex', flexDirection: 'column', justifyContent: 'start', alignItems: 'stretch',
-                              flexBasis: "35%" }}>
-
-                     <Typography sx={{ fontWeight: 500, fontSize: 18 }}>
-                        Название
-                     </Typography>
-
-                     <TextField            
-                        onChange={(e) => {
-                              setTitle(e.target.value)
-                           }
-                        }     
-                        onBlur={() => {
-                           setTitleBlur(true)              
-                        }
-                        } 
-                        onFocus={() => {
-                           setTitleBlur(false)              
-                           }
-                        }          
-                        error={titleBlur ? ((title?.length >= minTitle ) ? false : true) : false}
-                        helperText={printStrReq(title, minTitle)}            
-      
-                        inputProps={{
-                           maxLength: 50
-                        }}
-                        variant="standard"
-                        margin="none"
-                        fullWidth
-                        id="titleChat"
-                        label=""
-                        name="title"
-                        defaultValue={title}
-                        sx={{mt: 1}}
-                     />
-                  </Box>
-                  
-            </Box>
-
-
-            <Box sx={{ display: 'flex', flexDirection: 'column', justifyContent: 'center', height: '60%', mt: 2   }}>
-            
-               <Box sx={{   display: 'flex', justifyContent: 'center', mb: 1 }}>
-                  <Typography sx={{ fontWeight: 500, fontSize: 18 }}>
-                     Добавить участников
-                  </Typography>
-               </Box>
-
-               <Box sx={{ display: 'flex', justifyContent: 'space-between',  height: '100%', }} >
-
-                  <Box sx={{ flexBasis: "45%" }}>
-
-                     <Box sx={{  height: "40px", display: 'flex', justifyContent: 'center', pt: 1 }}>
-                        <Typography sx={{ fontWeight: 400, fontSize: 16 }}>
-                           Сейчас в чате
-                        </Typography>
-                     </Box>
-                     
-                     <Box 
-                        sx={{  height: "calc(100% - 40px)", overflowY: "auto" }}
-                        >
-                        <List
-                           sx={{ maxWidth: '100%', bgcolor: 'background.paper' }}
-                           >
-                           { members.map((member, i) => <UserCard key={member._id || i} user={member} 
-                                                                  render={DelBtn}  onAction={() => onDelMember(i)} />)}
-                        </List>
-                     </Box>
-                  </Box>     
-
-                  <CUserSearch open={open} alreadySearched={members} onAdd={onAddMember} />
-
-               </Box>
-
-            </Box>
-
-
-            <Box sx={{ mt: 2 }} >
-                     
-                  { wrongAlert &&
-                     <Typography component="p" variant="body2" my={2} ml={2} 
-                        sx={{fontWeight: 'medium', fontSize: '0.75rem', color: '#d32f2f'}}>
-                           Ошибка создания
-                     </Typography> 
-                  }
-      
-                  <Box sx={{ display: 'flex', justifyContent: 'center'}}>
-                     <Button  variant="contained" 
-                              disabled={  (title?.length < minTitle) ? true :
-                                         ((chat?.owner?._id !== myProfile?._id) && !create) ? true : false }
-                              onClick={() => {
-                                 onСonfirm( "media", img, title, 
-                                                         prepareMembers(members), chat?._id );
-                                 handleClose()
-                              } } >
-                        Применить 
-                     </Button>
-                  </Box>
-            </Box>
-
- 
-         </Box>
-       </Modal>
-     </>
-   )
- }
- export const CChatModal = connect(  state => ( {  chatError: state.promise.updateChat || {},
-                                                   myProfile: state.promise.myProfile?.payload || {} }), 
-                                        { onСonfirm: actionSetChatInfo })(ChatModal)
- 

+ 40 - 0
src/components/ChatModal/ChatInfoModal.jsx

@@ -0,0 +1,40 @@
+import React, { useState } from 'react'
+import Box from '@mui/material/Box'
+import Modal from '@mui/material/Modal'
+import IconButton from '@mui/material/IconButton'
+// import Button from '@mui/material/Button'
+// import Typography from '@mui/material/Typography'
+import CloseIcon from '@mui/icons-material/Close'
+// import List from '@mui/material/List'
+import { connect } from 'react-redux'
+import './ChatModal.scss'
+
+// import { ChatAvatar, UserCard } from '..'
+
+const ChatInfoModal = ({ chat, render }) => {
+    const [open, setOpen] = useState(false)
+    const handleOpen = () => setOpen(true)
+    const handleClose = () => setOpen(false)
+
+    const OpenBtn = render
+    return (
+        <>
+            <OpenBtn chat={chat} OPEN={handleOpen} />
+
+            <Modal open={open} onClose={handleClose}>
+                <Box className="styleModalParrent chatModalWrapp">
+                    <Box className="modalHead">
+                        <Box className="modalClose">
+                            <IconButton onClick={handleClose}>
+                                <CloseIcon />
+                            </IconButton>
+                        </Box>
+                    </Box>
+                </Box>
+            </Modal>
+        </>
+    )
+}
+export const CChatInfoModal = connect((state) => ({
+    chatError: state.promise.updateChat || {},
+}))(ChatInfoModal)

+ 273 - 0
src/components/ChatModal/ChatModal.jsx

@@ -0,0 +1,273 @@
+import React, { useEffect, useState } from 'react'
+import Box from '@mui/material/Box'
+import Modal from '@mui/material/Modal'
+import Button from '@mui/material/Button'
+import IconButton from '@mui/material/IconButton'
+import Typography from '@mui/material/Typography'
+import TextField from '@mui/material/TextField'
+import CloseIcon from '@mui/icons-material/Close'
+import List from '@mui/material/List'
+import { useDropzone } from 'react-dropzone'
+import { connect } from 'react-redux'
+import CancelIcon from '@mui/icons-material/Cancel'
+// import {ReactComponent as KickLogo} from '../assets/kick.svg'
+import './ChatModal.scss'
+
+import { ChatAvatar, CUserSearch, UserCard } from '..'
+import { printStrReq } from '../../helpers'
+import { actionSetChatInfo } from '../../actions'
+
+const DelBtn = ({ onEvent }) => (
+    <IconButton edge="end" onClick={onEvent}>
+        <CancelIcon />
+    </IconButton>
+)
+
+const ChatModal = ({
+    minTitle = '2',
+    chat,
+    onСonfirm,
+    titleError,
+    myProfile,
+    create,
+    render,
+}) => {
+    const [open, setOpen] = useState(false)
+    const handleOpen = () => setOpen(true)
+    const handleClose = () => setOpen(false)
+
+    const [title, setTitle] = useState(chat?.title || '')
+    const [titleBlur, setTitleBlur] = useState(false)
+
+    const [img, setImg] = useState(null)
+    const { getRootProps, getInputProps } = useDropzone({
+        accept: 'image/*',
+        maxFiles: 1,
+        onDrop: (acceptedFiles) => {
+            setImg(acceptedFiles[0])
+        },
+    })
+
+    const [members, setMembers] = useState(chat?.members || [myProfile])
+
+    const onAddMember = (newMember) => {
+        setMembers([...members, newMember])
+    }
+    const onDelMember = (i) => {
+        setMembers(members.filter((el, index) => index !== i))
+    }
+
+    const [wrongAlert, setWrongAlert] = useState(false)
+
+    useEffect(() => {
+        if (titleError?.payload === null) {
+            setWrongAlert(true)
+        } else {
+            setWrongAlert(false)
+        }
+    }, [titleError])
+
+    useEffect(() => {
+        setTitle(chat?.title || '')
+        setImg(null)
+        setMembers(chat?.members || [myProfile])
+    }, [open])
+
+    function prepareMembers(members) {
+        const newMembers = []
+
+        for (const member of members) {
+            if (create) {
+                if (member._id !== myProfile?._id) {
+                    newMembers.push({ _id: member._id })
+                }
+            } else {
+                newMembers.push({ _id: member._id })
+            }
+        }
+        return newMembers
+    }
+
+    const OpenBtn = render
+    return (
+        <>
+            <OpenBtn chat={chat} OPEN={handleOpen} />
+
+            <Modal open={open} onClose={handleClose}>
+                <Box className="styleModalParrent chatModalWrapp">
+                    <Box className="modalHead">
+                        <Box className="modalTitle">
+                            <Typography variant="h6">
+                                {chat?.owner?._id !== myProfile?._id && !create
+                                    ? 'Информация (редактирование запрещено)'
+                                    : create
+                                    ? 'Создание чата'
+                                    : 'Редактирование чата'}
+                            </Typography>
+                        </Box>
+
+                        <Box className="modalClose">
+                            <IconButton onClick={handleClose}>
+                                <CloseIcon />
+                            </IconButton>
+                        </Box>
+                    </Box>
+
+                    <Box className="modalChatInfo">
+                        <Box className="avatarBlock">
+                            <section className="container">
+                                <Box
+                                    {...getRootProps({ className: 'dropzone' })}
+                                    className="avatarBox avatarInModal"
+                                >
+                                    <ChatAvatar
+                                        chat={{
+                                            title: title,
+                                            avatar: {
+                                                url: chat?.avatar?.url || '',
+                                            },
+                                            localUrl:
+                                                img && URL.createObjectURL(img),
+                                        }}
+                                        bigSize={true}
+                                    />
+
+                                    <input
+                                        {...getInputProps()}
+                                        type="file"
+                                        name="media"
+                                        id="mediaUser"
+                                    />
+                                    <Box className="avatarTitleBox">
+                                        <Typography className="avatarTitle">
+                                            Аватар
+                                        </Typography>
+                                    </Box>
+                                </Box>
+                            </section>
+                        </Box>
+
+                        <Box className="nameBlock">
+                            <Typography className="nameTitle">
+                                Название
+                            </Typography>
+
+                            <TextField
+                                onChange={(e) => {
+                                    setTitle(e.target.value)
+                                }}
+                                onBlur={() => {
+                                    setTitleBlur(true)
+                                }}
+                                onFocus={() => {
+                                    setTitleBlur(false)
+                                }}
+                                error={
+                                    titleBlur
+                                        ? title?.length >= minTitle
+                                            ? false
+                                            : true
+                                        : false
+                                }
+                                helperText={printStrReq(title, minTitle)}
+                                inputProps={{
+                                    maxLength: 50,
+                                }}
+                                variant="standard"
+                                margin="none"
+                                fullWidth
+                                id="titleChat"
+                                label=""
+                                name="title"
+                                defaultValue={title}
+                                className="nameInput"
+                            />
+                        </Box>
+                    </Box>
+
+                    <Box className="modalMembers">
+                        <Box className="membersHead">
+                            <Typography className="membersTitle">
+                                Добавить участников
+                            </Typography>
+                        </Box>
+
+                        <Box className="membersBody">
+                            <Box className="currentMembersBlock">
+                                <Box className="currentMembersHead">
+                                    <Typography className="currentMembersTitle">
+                                        Сейчас в чате
+                                    </Typography>
+                                </Box>
+
+                                <Box className="currentMembersBody">
+                                    <List className="currentMembersList">
+                                        {members.map((member, i) => (
+                                            <UserCard
+                                                key={member._id || i}
+                                                user={member}
+                                                render={DelBtn}
+                                                onAction={() => onDelMember(i)}
+                                            />
+                                        ))}
+                                    </List>
+                                </Box>
+                            </Box>
+
+                            <CUserSearch
+                                open={open}
+                                alreadySearched={members}
+                                onAdd={onAddMember}
+                            />
+                        </Box>
+                    </Box>
+
+                    <Box className="modalSubmit">
+                        {wrongAlert && (
+                            <Typography
+                                component="p"
+                                variant="body2"
+                                className="alertBlock"
+                            >
+                                Ошибка создания
+                            </Typography>
+                        )}
+
+                        <Box className="btnBlock">
+                            <Button
+                                variant="contained"
+                                disabled={
+                                    title?.length < minTitle
+                                        ? true
+                                        : chat?.owner?._id !== myProfile?._id &&
+                                          !create
+                                        ? true
+                                        : false
+                                }
+                                onClick={() => {
+                                    onСonfirm(
+                                        'media',
+                                        img,
+                                        title,
+                                        prepareMembers(members),
+                                        chat?._id
+                                    )
+                                    handleClose()
+                                }}
+                            >
+                                Применить
+                            </Button>
+                        </Box>
+                    </Box>
+                </Box>
+            </Modal>
+        </>
+    )
+}
+export const CChatModal = connect(
+    (state) => ({
+        chatError: state.promise.updateChat || {},
+        myProfile: state.promise.myProfile?.payload || {},
+    }),
+    { onСonfirm: actionSetChatInfo }
+)(ChatModal)

+ 135 - 0
src/components/ChatModal/ChatModal.scss

@@ -0,0 +1,135 @@
+.chatModalWrapp {
+    width: 70%;
+    max-width: 1000px;
+    height: 95%;
+
+    .modalHead {
+        display: flex;
+        justify-content: space-between;
+        border-bottom: 1px solid #999;
+        padding-bottom: 8px;
+        margin-bottom: 8px;
+
+        .modalTitle {
+            display: flex;
+            justify-content: flex-start;
+        }
+
+        .modalClose {
+            display: flex;
+            justify-content: flex-end;
+        }
+    }
+
+    .modalChatInfo {
+        display: flex;
+        justify-content: space-between;
+        padding: 0 48px 0 24px;
+
+        .avatarBlock {
+            display: flex;
+            justify-content: flex-start;
+            align-items: flex-start;
+            flex-basis: 35%;
+
+            .avatarBox {
+                display: flex;
+                height: 80px;
+                cursor: pointer;
+
+                .avatarTitleBox {
+                    padding-left: 24px;
+
+                    .avatarTitle {
+                        font-weight: 500;
+                        font-size: 18;
+                    }
+                }
+            }
+
+            .nameBlock {
+                display: flex;
+                flex-direction: column;
+                justify-content: flex-start;
+                align-items: stretch;
+                flex-basis: 35%;
+
+                .nameTitle {
+                    font-weight: 500;
+                    font-size: 18px;
+                }
+
+                .nameInput {
+                    margin-top: 8px;
+                }
+            }
+        }
+    }
+
+    .modalMembers {
+        display: flex;
+        flex-direction: column;
+        justify-content: center;
+        height: 60%;
+        margin-top: 16px;
+
+        .membersHead {
+            display: flex;
+            justify-content: center;
+            margin-bottom: 8px;
+
+            .membersTitle {
+                font-weight: 500;
+                font-size: 18px;
+            }
+        }
+
+        .membersBody {
+            display: flex;
+            justify-content: space-between;
+            height: 100%;
+
+            .currentMembersBlock {
+                flex-basis: 45%;
+
+                .currentMembersHead {
+                    height: 40px;
+                    display: flex;
+                    justify-content: center;
+                    padding-top: 8px;
+
+                    .currentMembersTitle {
+                        font-weight: 400;
+                        font-size: 16px;
+                    }
+                }
+
+                .currentMembersBody {
+                    height: calc(100% - 40px);
+                    overflow-y: auto;
+
+                    .currentMembersList {
+                        max-width: 100%;
+                        background-color: #fff;
+                    }
+                }
+            }
+        }
+    }
+
+    .modalSubmit {
+        margin-top: 16px;
+
+        .alertBlock {
+            font-weight: medium;
+            font-size: 0.75rem;
+            color: #d32f2f;
+            margin: 16px 0 16px 16px;
+        }
+
+        .btnBlock {
+            display: flex;
+            justify-content: center;
+        }
+    }
+}

+ 3 - 0
src/components/ChatModal/index.js

@@ -0,0 +1,3 @@
+import { CChatModal } from './ChatModal'
+
+export { CChatModal }

+ 0 - 11
src/components/FloatBtn.jsx

@@ -1,11 +0,0 @@
-import { Fab } from '@mui/material';
-import AddIcon from '@mui/icons-material/Add';
-
-export const FloatBtn = ({}) => {
-  return (
-    <Fab color="primary" aria-label="add">
-      <AddIcon />
-    </Fab>
-  )
-}
-

+ 16 - 0
src/components/FloatBtn/FloatBtn.jsx

@@ -0,0 +1,16 @@
+import React from 'react'
+import { Fab } from '@mui/material'
+import AddIcon from '@mui/icons-material/Add'
+import Box from '@mui/material/Box'
+
+import './FloatBtn.scss'
+
+export const FloatBtn = ({ OPEN }) => {
+    return (
+        <Box onClick={OPEN} className="floatBtn">
+            <Fab color="primary">
+                <AddIcon />
+            </Fab>
+        </Box>
+    )
+}

+ 6 - 0
src/components/FloatBtn/FloatBtn.scss

@@ -0,0 +1,6 @@
+.floatBtn {
+    position: absolute;
+    top: 90%;
+    left: 65%;
+    z-index: 10;
+}

+ 3 - 0
src/components/FloatBtn/index.js

@@ -0,0 +1,3 @@
+import { FloatBtn } from './FloatBtn'
+
+export { FloatBtn }

+ 12 - 12
src/components/Header.jsx

@@ -1,14 +1,14 @@
-import React from 'react';
-import Box from '@mui/material/Box';
-import AppBar from '@mui/material/AppBar';
-import Toolbar from '@mui/material/Toolbar';
+import React from 'react'
+import Box from '@mui/material/Box'
+import AppBar from '@mui/material/AppBar'
+import Toolbar from '@mui/material/Toolbar'
 
-export const Header = ({children}) => (
-   <Box sx={{ flexGrow: 1 }} >
-      <AppBar position="static" >
-         <Toolbar variant="dense" sx={{ minHeight:"60px" }}>
-            {children}
-         </Toolbar>
-      </AppBar>
-   </Box>
+export const Header = ({ children }) => (
+    <Box sx={{ flexGrow: 1 }}>
+        <AppBar position="static">
+            <Toolbar variant="dense" sx={{ minHeight: '60px' }}>
+                {children}
+            </Toolbar>
+        </AppBar>
+    </Box>
 )

+ 0 - 120
src/components/MainMenu.jsx

@@ -1,120 +0,0 @@
-import React, {useState} from 'react'
-import IconButton from '@mui/material/IconButton';
-import MenuIcon from '@mui/icons-material/Menu';
-
-import Drawer from '@mui/material/Drawer';
-import Box from '@mui/material/Box';
-import List from '@mui/material/List';
-import Divider from '@mui/material/Divider';
-import ListItem from '@mui/material/ListItem';
-import ListItemText from '@mui/material/ListItemText';
-import Typography from '@mui/material/Typography';
-
-import {actionFullLogout} from "../actions"
-import {connect}  from 'react-redux';
-
-import {CProfileModal, CMyAvatar} from '.'
-
-
-const LogoutBtn = ({onLogout}) => (
-   <>
-      <ListItem button onClick={() => onLogout()} >
-         <ListItemText primary={'Выйти'} />
-      </ListItem>
-   </>
-)
-const CLogoutBtn = connect(null, {onLogout: actionFullLogout})(LogoutBtn)
-
-
-const AboutMe = ({ myProfile }) => {
-   const {login, nick} = myProfile
-return (
-   <>
-      <Box sx={{ display: 'flex', flexDirection: 'column', 
-                     justifyContent: 'start', alignItems: 'center', 
-                     background: '#dddddd44' }}>
-         
-         <Box  sx={{ m: 2, mb: 1 }}
-               className="avatarInModal">
-            <CMyAvatar bigSize={true} />
-         </Box> 
-
-         <Box sx={{ mx: 2, display: 'flex', justifyContent: 'center', 
-                     alignSelf: 'start',  maxWidth: '90%' }}>
-            <Typography component="h6" variant="h6" 
-                        sx={{ overflow: 'hidden', whiteSpace: 'nowrap', textOverflow: 'ellipsis' }}>
-               {nick}
-            </Typography>
-         </Box> 
-
-         <Box sx={{ mx: 2, mb: 2, display: 'flex', justifyContent: 'center', 
-                     alignSelf: 'start', maxWidth: '90%' }}>
-            <Typography variant="body" 
-                        sx={{ overflow: 'hidden', whiteSpace: 'nowrap', textOverflow: 'ellipsis' }}>
-               {login}
-            </Typography>
-         </Box> 
-
-      </Box>
-   </>
-) 
-}
-const CAboutMe = connect( state => ({ myProfile: state.promise.myProfile?.payload || {} }))(AboutMe)
-
-
-export const MenuDrawer = ({ }) => {
-   const [state, setState] = useState(false);
-   const open = !!state
-
- 
-   const toggleDrawer = (open) => (event) => {
-     if ( event.type === 'keydown' && 
-          ( event.key === 'Tab' || 
-            event.key === 'Shift')) {
-       return
-     } 
-     setState(open)
-   } 
-
-   return (
-     <div>
-         <IconButton edge="start" color="inherit" sx={{ mr: 1 }}
-            onClick={toggleDrawer(true)}
-            >
-            
-            <MenuIcon />
-         </IconButton>
-         
-
-         <Drawer
-            open={state}
-            onClose={toggleDrawer(false)}
-            >
-            <Box
-               sx={{ width: 250, overflow: 'hidden',  }}
-               role="presentation"
-               >
-
-               <CAboutMe />    
-
-
-               <List>
-                  <CProfileModal />
-
-                  <ListItem button >
-                     <ListItemText primary={'Контакты'} />
-                  </ListItem>
-
-                  <Divider />
-
-                  <CLogoutBtn />
-
-               </List>
-            </Box>
-
-         </Drawer>
-     </div>
-   );
- }
-
-

+ 39 - 0
src/components/MainMenu/AboutMe.jsx

@@ -0,0 +1,39 @@
+import React from 'react'
+import Box from '@mui/material/Box'
+import Typography from '@mui/material/Typography'
+import { connect } from 'react-redux'
+import './MainMenu.scss'
+
+import { CMyAvatar } from '..'
+
+const AboutMe = ({ myProfile }) => {
+    const { login, nick } = myProfile
+    return (
+        <>
+            <Box className="aboutMe">
+                <Box className="avatarInModal myAvatar">
+                    <CMyAvatar bigSize={true} />
+                </Box>
+
+                <Box className="nickBlock">
+                    <Typography
+                        component="h6"
+                        variant="h6"
+                        className="nickText"
+                    >
+                        {nick}
+                    </Typography>
+                </Box>
+
+                <Box className="loginBlock">
+                    <Typography variant="body" className="loginText">
+                        {login}
+                    </Typography>
+                </Box>
+            </Box>
+        </>
+    )
+}
+export const CAboutMe = connect((state) => ({
+    myProfile: state.promise.myProfile?.payload || {},
+}))(AboutMe)

+ 18 - 0
src/components/MainMenu/LogoutBtn.jsx

@@ -0,0 +1,18 @@
+import React from 'react'
+import ListItem from '@mui/material/ListItem'
+import ListItemText from '@mui/material/ListItemText'
+import { connect } from 'react-redux'
+import './MainMenu.scss'
+
+import { actionFullLogout } from '../../actions'
+
+const LogoutBtn = ({ onLogout }) => (
+    <>
+        <ListItem button onClick={() => onLogout()}>
+            <ListItemText primary={'Выйти'} />
+        </ListItem>
+    </>
+)
+export const CLogoutBtn = connect(null, { onLogout: actionFullLogout })(
+    LogoutBtn
+)

+ 60 - 0
src/components/MainMenu/MainMenu.jsx

@@ -0,0 +1,60 @@
+import React, { useState } from 'react'
+import IconButton from '@mui/material/IconButton'
+import MenuIcon from '@mui/icons-material/Menu'
+import Drawer from '@mui/material/Drawer'
+import Box from '@mui/material/Box'
+import List from '@mui/material/List'
+import Divider from '@mui/material/Divider'
+import ListItem from '@mui/material/ListItem'
+import ListItemText from '@mui/material/ListItemText'
+import './MainMenu.scss'
+
+import { CAboutMe } from './AboutMe'
+import { CLogoutBtn } from './LogoutBtn'
+import { CProfileModal } from '..'
+
+export const MenuDrawer = () => {
+    const [state, setState] = useState(false)
+    const open = !!state
+
+    const toggleDrawer = (open) => (event) => {
+        if (
+            event.type === 'keydown' &&
+            (event.key === 'Tab' || event.key === 'Shift')
+        ) {
+            return
+        }
+        setState(open)
+    }
+
+    return (
+        <div>
+            <IconButton
+                edge="start"
+                color="inherit"
+                sx={{ mr: 1 }}
+                onClick={toggleDrawer(true)}
+            >
+                <MenuIcon />
+            </IconButton>
+
+            <Drawer open={open} onClose={toggleDrawer(false)}>
+                <Box className="menuBox" role="presentation">
+                    <CAboutMe />
+
+                    <List>
+                        <CProfileModal />
+
+                        <ListItem button>
+                            <ListItemText primary={'Контакты'} />
+                        </ListItem>
+
+                        <Divider />
+
+                        <CLogoutBtn />
+                    </List>
+                </Box>
+            </Drawer>
+        </div>
+    )
+}

+ 44 - 0
src/components/MainMenu/MainMenu.scss

@@ -0,0 +1,44 @@
+.menuBox {
+    width: 250px;
+    overflow: hidden;
+
+    .aboutMe {
+        display: flex;
+        flex-direction: column;
+        justify-content: start;
+        align-items: center;
+        background: #dddddd44;
+
+        .myAvatar {
+            margin: 10px 10px 5px 10px;
+        }
+
+        .nickBlock {
+            margin: 0 10px;
+            display: flex;
+            justify-content: center;
+            align-self: start;
+            max-width: 90%;
+
+            .nickText {
+                overflow: hidden;
+                white-space: nowrap;
+                text-overflow: ellipsis;
+            }
+        }
+
+        .loginBlock {
+            margin: 0 10px 10px 10px;
+            display: flex;
+            justify-content: center;
+            align-self: start;
+            max-width: 90%;
+
+            .loginText {
+                overflow: hidden;
+                white-space: nowrap;
+                text-overflow: ellipsis;
+            }
+        }
+    }
+}

+ 3 - 0
src/components/MainMenu/index.js

@@ -0,0 +1,3 @@
+import { MenuDrawer } from './MainMenu'
+
+export { MenuDrawer }

+ 0 - 318
src/components/Msg.jsx

@@ -1,318 +0,0 @@
-import React, {useState, useEffect, useRef} from 'react'
-import ReactDOM from "react-dom";
-import FileDownloadIcon from '@mui/icons-material/FileDownload';
-import InsertDriveFileIcon from '@mui/icons-material/InsertDriveFile';
-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 } from '../helpers'
-
-import { UserAvatar, CMyAvatar } from '.'
-
-
-const msgBlock = {
-   alignSelf: "start",
-   display: "flex",
-   justifyContent: "flex-start",
-   alignItems: "start",
-   margin: "2px",
-   marginLeft: "10px",
-   marginRight: "5%",
-   maxWidth: "calc(95% - 10px)",
-   minWidth: "40%",
-   wordWrap: "break-word",
-}
-
-const myMsgBlock = {
-   alignSelf: "end",
-   display: "flex",
-   justifyContent: "flex-start",
-   alignItems: "start",
-   margin: "2px",
-   marginRight: "10px",
-   marginLeft: "5%",
-   maxWidth: "calc(95% - 10px)",
-   minWidth: "40%",
-   wordWrap: "break-word",
-}
-
-const avBlock = {
-   alignSelf: "end",
-   width: "40px",
-   marginRight: "10px"
-}
-
-const bodyBlock = {
-   display: "flex",
-   flexDirection: "column",
-   justifyContent: "flex-start",
-   borderRadius: "5px",
-   backgroundColor: "#fff",
-   width: "calc(100% - 50px)",
-   padding: "10px"
-}
-
-const myBodyBlock = {
-   display: "flex",
-   flexDirection: "column",
-   justifyContent: "flex-start",
-   borderRadius: "5px",
-   backgroundColor: "#1976d255",
-   width: "calc(100% - 50px)",
-   padding: "10px"
-}
-
-const nameBlock = {
-   fontWeight: 600,
-   fontSize: "12px",
-   alignSelf: "start",
-   maxWidth: "100%",
-   marginBottom: "5px",
-}
-const contentBlock = {
-   alignSelf: "stretch",
-   maxWidth: "100%",
-}
-const dateBlock = {
-   alignSelf: "end",
-   maxWidth: "100%",
-   fontSize: "10px",
-   color: "#555"
-}
-
-const mediaBlock = {
-   marginBottom: "20px"
-}
-const textBlock = {
-}
-
-const imgStyle = { 
-   maxWidth: "100%", 
-   width: "auto",
-   maxHeight: "400px", 
-   height: "auto",
-   margin: "0 5px"
-}
-const downloadStyle = { 
-   display: "flex",
-   maxWidth: "300px",
-   minHeight: "54px",
-   borderRadius: "30px",
-   textDecoration: "none",
-   margin: "5px 0",
-   padding: "10px",
-   backgroundColor: "#eeee"
-}
-const imgDownload = { 
-   alignSelf: "center",
-   borderRadius: "50%",
-   backgroundColor: "#1976d2",
-   height: "45px",
-   width: "45px",
-   display: "flex",
-   justifyContent: "center",
-   alignItems: "center",
-   marginRight: "10px",
-}
-const textDownload = { 
-   alignSelf: "start",
-   // marginTop: "10px",
-   maxWidth: "calc(100% - 55px)",
-   fontSize: 14, 
-   fontWeight: 500,
-   color: "#000",
-   wordWrap: "break-word",
-   overflow: "hidden" 
-}
-
-
-
-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, nextMsg } = msg
-   const { nick, login, avatar } = owner
-
-   const allMedia = {}
-   if (media) for (const file of media) {
-      if (file.type) {
-
-         const [objName, ...rest] = file.type.split('/') || []
-
-         if (allMedia.hasOwnProperty(objName)) {
-
-            allMedia[objName].push(file)
-         } else {
-            allMedia[objName] = [file]
-         }
-      }  
-   }
-   // console.log(allMedia)
-
-
-   const [anchorEl, setAnchorEl] = useState(null)
-   const [top, setTop] = useState(0)
-   const [left, setLeft] = useState(0)
- 
-   const handleClick = (e) => {
-      e.preventDefault()
-      setTop(e.clientY)
-      setLeft(e.clientX)
-      setAnchorEl(e.currentTarget)
-   }
-
-   const handleClose = () => {
-      setAnchorEl(null)
-   }
- 
-   const open = !!anchorEl
-
-   
-
-
-   const nameBlockNew = {...nameBlock, color: stringColor.stringToColor(nick || login)}
-   return (
-         <>
-          <Popover
-            open={open}
-            anchorEl={anchorEl}
-            onClose={handleClose}
-            anchorReference={'anchorPosition'}
-            anchorPosition={{
-               top: top,
-               left: left
-            }}
-         >
-              
-            <List sx={{ width: '100%', maxWidth: 300, bgcolor: 'background.paper' }}>
-
-               <ListItemButton onClick={() => {
-                  handleClose()
-               }}>
-                  Ответить
-               </ListItemButton>
-
-               {  (myId === owner._id) && 
-
-                  <ListItemButton onClick={() => {
-                     onEdit({...msg});
-                     handleClose()
-                  }} >
-                     Редактировать
-                  </ListItemButton>
-               }
-
-            </List>
-
-         </Popover>
-
-
-         <div onContextMenu={handleClick}
-
-            style={  (myId === owner._id) ? 
-                        ( (nextMsg?.owner?._id === owner._id) ? 
-                           {...myMsgBlock, marginBottom: "2px"} : {...myMsgBlock, marginBottom: "15px"}) : 
-                              ( (nextMsg?.owner?._id  === owner._id) ? 
-                                 {...msgBlock, marginBottom: "2px"} : {...msgBlock, marginBottom: "15px"})
-               }
-         >
-
-            <div style={avBlock} >
-               { (nextMsg?.owner?._id  === owner._id && 
-                     nextMsg?.createdAt - createdAt < 600000) || 
-                           ( (myId === owner._id) ? 
-                                 <CMyAvatar /> : 
-                                       <UserAvatar profile={owner} /> ) }
-            </div>
-
-            <div style={(myId === owner._id) ? myBodyBlock : bodyBlock} >
-
-               <div style={(myId === owner._id) ? nameBlock : nameBlockNew} >
-                  { (myId === owner._id) ? (myNick || myLogin) : (nick || login) }
-               </div>
-
-               <div style={contentBlock} >
-                  { (Object.keys(allMedia).length > 0) &&
-                     <div style={mediaBlock}>
-                        { Object.keys(allMedia).map((key) => 
-                           <div key={key} > 
-                              {
-                                 (key === 'image') &&
-                                       allMedia[key].map((mediaObj) => 
-                                                                     <img
-                                                                        key={mediaObj.url}
-                                                                        style={imgStyle}
-                                                                        src={backURL + mediaObj.url} 
-                                                                        type={mediaObj.type}
-                                                                     />
-                                                                                       ) ||
-                                    (key === 'video') &&
-                                          allMedia[key].map((mediaObj) => 
-                                                                        <video 
-                                                                           key={mediaObj.url}
-                                                                           style={imgStyle}
-                                                                           src={backURL + mediaObj.url} 
-                                                                           controls   
-                                                                           preload={'metadata'}                                                                
-                                                                        >
-                                                                        </video>
-                                                                                       ) ||
-                                       (key === 'audio') &&
-                                             allMedia[key].map((mediaObj) => 
-                                                                           <div 
-                                                                              key={mediaObj.url} 
-                                                                           >
-                                                                              <audio 
-                                                                                 src={backURL + mediaObj.url}
-                                                                                 controls
-                                                                              >
-                                                                              </audio>
-                                                                           </div>                                     
-                                                                                                )  ||
-                                                allMedia[key].map((mediaObj) => 
-                                                                              <a 
-                                                                                 key={mediaObj.url} 
-                                                                                 href={backURL + mediaObj.url}
-                                                                                 download={mediaObj.originalFileName} 
-                                                                                 style={downloadStyle}                                                                       
-                                                                              >
-                                                                                 <div 
-                                                                                    style={imgDownload}>
-                                                                                    <FileDownloadIcon 
-                                                                                       style={{ 
-                                                                                          fontSize: 35, 
-                                                                                          color: "#ddd",
-                                                                                             }} />
-                                                                                 </div>
-                                                                                 <div 
-                                                                                    style={textDownload} >
-                                                                                    {mediaObj.originalFileName}
-                                                                                 </div>
-                                                                              </a>
-                                                                                                   )
-                              }
-                           </div> 
-                        )}
-                     </div>
-                  }
-
-                  <pre style={textBlock} >
-                     {text}
-                  </pre>
-                  
-               </div>
-
-               <div style={dateBlock} >
-                  {dateFromStamp(createdAt)}
-               </div>
-            </div>
-         </div> 
-      </>
-   )
-}
-export const CMsg = connect( state => ({ myProfile: state.promise.myProfile?.payload || {}}))(Msg)
-

+ 202 - 0
src/components/Msg/Msg.jsx

@@ -0,0 +1,202 @@
+import React, { useState } from 'react'
+import FileDownloadIcon from '@mui/icons-material/FileDownload'
+import Popover from '@mui/material/Popover'
+import List from '@mui/material/List'
+import ListItemButton from '@mui/material/ListItemButton'
+import { connect } from 'react-redux'
+
+import './Msg.scss'
+
+import { dateFromStamp, stringColor, sortMediaArr } from '../../helpers'
+import { backURL } from '../../constants'
+import { UserAvatar, CMyAvatar } from '..'
+
+const Msg = ({ msg, myProfile, onEdit }) => {
+    const myId = myProfile?._id || null
+    const myLogin = myProfile?.login || null
+    const myNick = myProfile?.nick || null
+
+    const { text, owner, media, createdAt, nextMsg } = msg
+    const { nick, login } = owner
+
+    const allMedia = sortMediaArr(media)
+
+    const [anchorEl, setAnchorEl] = useState(null)
+    const [top, setTop] = useState(0)
+    const [left, setLeft] = useState(0)
+
+    const handleClick = (e) => {
+        e.preventDefault()
+        setTop(e.clientY)
+        setLeft(e.clientX)
+        setAnchorEl(e.currentTarget)
+    }
+
+    const handleClose = () => {
+        setAnchorEl(null)
+    }
+
+    const open = !!anchorEl
+    const myMsg = myId === owner._id
+
+    const nameColor = {
+        color: myMsg ? '#000' : stringColor.stringToColor(nick || login),
+    }
+
+    // const matchLink = text.match(linkRegEx)
+    // const matchYoutube = text.match(youtubeRegEx)
+    // console.log(matchLink, matchYoutube)
+
+    return (
+        <>
+            <Popover
+                open={open}
+                anchorEl={anchorEl}
+                onClose={handleClose}
+                anchorReference={'anchorPosition'}
+                anchorPosition={{
+                    top: top,
+                    left: left,
+                }}
+            >
+                <List
+                    sx={{
+                        width: '100%',
+                        maxWidth: 300,
+                        bgcolor: 'background.paper',
+                    }}
+                >
+                    <ListItemButton
+                        onClick={() => {
+                            handleClose()
+                        }}
+                    >
+                        Ответить
+                    </ListItemButton>
+
+                    {myMsg && (
+                        <ListItemButton
+                            onClick={() => {
+                                onEdit({ ...msg })
+                                handleClose()
+                            }}
+                        >
+                            Редактировать
+                        </ListItemButton>
+                    )}
+                </List>
+            </Popover>
+
+            <div
+                onContextMenu={handleClick}
+                className={`msgBlock ${
+                    myMsg
+                        ? nextMsg?.owner?._id === owner._id
+                            ? 'myMsg nearMsg'
+                            : 'myMsg distantMsg'
+                        : nextMsg?.owner?._id === owner._id
+                        ? 'otherMsg nearMsg'
+                        : 'otherMsg distantMsg'
+                }`}
+            >
+                <div className="avBlock">
+                    {(nextMsg?.owner?._id === owner._id &&
+                        nextMsg?.createdAt - createdAt < 600000) ||
+                        (myMsg ? (
+                            <CMyAvatar />
+                        ) : (
+                            <UserAvatar profile={owner} />
+                        ))}
+                </div>
+
+                <div className={`bodyBlock ${myMsg && 'myBodyBlock'}`}>
+                    <div className="nameBlock" style={nameColor}>
+                        {myMsg ? myNick || myLogin : nick || login}
+                    </div>
+
+                    <div className="contentBlock">
+                        {Object.keys(allMedia).length > 0 && (
+                            <div className="mediaBlock">
+                                {Object.keys(allMedia).map((key) => (
+                                    <div key={key}>
+                                        {(key === 'image' &&
+                                            allMedia[key].map((mediaObj) => (
+                                                <img
+                                                    key={mediaObj.url}
+                                                    className="imgStyle"
+                                                    src={backURL + mediaObj.url}
+                                                    type={mediaObj.type}
+                                                />
+                                            ))) ||
+                                            (key === 'video' &&
+                                                allMedia[key].map(
+                                                    (mediaObj) => (
+                                                        <video
+                                                            key={mediaObj.url}
+                                                            className="imgStyle"
+                                                            src={
+                                                                backURL +
+                                                                mediaObj.url
+                                                            }
+                                                            controls
+                                                            preload={'metadata'}
+                                                        ></video>
+                                                    )
+                                                )) ||
+                                            (key === 'audio' &&
+                                                allMedia[key].map(
+                                                    (mediaObj) => (
+                                                        <div key={mediaObj.url}>
+                                                            <audio
+                                                                src={
+                                                                    backURL +
+                                                                    mediaObj.url
+                                                                }
+                                                                controls
+                                                            ></audio>
+                                                        </div>
+                                                    )
+                                                )) ||
+                                            allMedia[key].map((mediaObj) => (
+                                                <a
+                                                    key={mediaObj.url}
+                                                    href={
+                                                        backURL + mediaObj.url
+                                                    }
+                                                    download={
+                                                        mediaObj.originalFileName
+                                                    }
+                                                    className="downloadStyle"
+                                                >
+                                                    <div className="imgDownload">
+                                                        <FileDownloadIcon
+                                                            style={{
+                                                                fontSize: 35,
+                                                                color: '#ddd',
+                                                            }}
+                                                        />
+                                                    </div>
+                                                    <div className="textDownload">
+                                                        {
+                                                            mediaObj.originalFileName
+                                                        }
+                                                    </div>
+                                                </a>
+                                            ))}
+                                    </div>
+                                ))}
+                            </div>
+                        )}
+
+                        <pre className="textBlock">{text}</pre>
+                    </div>
+
+                    <div className="dateBlock">{dateFromStamp(createdAt)}</div>
+                </div>
+            </div>
+        </>
+    )
+}
+export const CMsg = connect((state) => ({
+    myProfile: state.promise.myProfile?.payload || {},
+}))(Msg)

+ 108 - 0
src/components/Msg/Msg.scss

@@ -0,0 +1,108 @@
+.msgBlock {
+    display: flex;
+    justify-content: flex-start;
+    align-items: flex-start;
+    margin: 2px;
+    max-width: calc(95% - 10px);
+    min-width: 40%;
+    word-wrap: break-word;
+
+    .avBlock {
+        align-self: end;
+        width: 40px;
+        margin-right: 10px;
+    }
+
+    .bodyBlock {
+        display: flex;
+        flex-direction: column;
+        justify-content: flex-start;
+        border-radius: 5px;
+        background-color: #fff;
+        width: calc(100% - 50px);
+        padding: 10px;
+
+        .nameBlock {
+            font-weight: 600;
+            font-size: 12px;
+            align-self: flex-start;
+            max-width: 100%;
+            margin-bottom: 5px;
+        }
+
+        .contentBlock {
+            align-self: stretch;
+            max-width: 100%;
+
+            .mediaBlock {
+                margin-bottom: 20px;
+
+                .imgStyle {
+                    max-width: 100%;
+                    width: auto;
+                    max-height: 400px;
+                    height: auto;
+                    margin: 0 5px;
+                }
+
+                .downloadStyle {
+                    display: flex;
+                    max-width: 300px;
+                    min-height: 54px;
+                    border-radius: 30px;
+                    text-decoration: none;
+                    margin: 5px 0;
+                    padding: 10px;
+                    background-color: #eeee;
+
+                    .imgDownload {
+                        align-self: center;
+                        border-radius: 50%;
+                        background-color: #1976d2;
+                        height: 45px;
+                        width: 45px;
+                        display: flex;
+                        justify-content: center;
+                        align-items: center;
+                        margin-right: 10px;
+                    }
+                    .textDownload {
+                        align-self: flex-start;
+                        max-width: calc(100% - 55px);
+                        font-size: 14;
+                        font-weight: 500;
+                        color: #000;
+                        word-wrap: break-word;
+                        overflow: hidden;
+                    }
+                }
+            }
+        }
+
+        .dateBlock {
+            align-self: end;
+            max-width: 100%;
+            font-size: 10px;
+            color: #555;
+        }
+    }
+    .myBodyBlock {
+        background-color: #1976d255;
+    }
+}
+.otherMsg {
+    align-self: flex-start;
+    margin-left: 10px;
+    margin-right: 5%;
+}
+.myMsg {
+    align-self: end;
+    margin-right: 10px;
+    margin-left: 5%;
+}
+.nearMsg {
+    margin-bottom: 2px;
+}
+.distantMsg {
+    margin-bottom: 15px;
+}

+ 3 - 0
src/components/Msg/index.js

@@ -0,0 +1,3 @@
+import { CMsg } from './Msg'
+
+export { CMsg }

+ 0 - 43
src/components/MsgList.jsx

@@ -1,43 +0,0 @@
-import React, {useState, useEffect, useRef} from 'react'
-
-import { connect } from 'react-redux'
-
-import { CMsg, MsgMenu } from '.'
-
-const msgsWrapper = {
-   display: "flex",
-   flexDirection: "column",
-   justifyContent: "flex-start",
-   alignItems: "stretch",
-   paddingTop: "10px",
-   paddingBottom: "5px"
-}
-
-const MsgList = ({chats, chatId, onEdit }) => {
-
-   const msgArr = chats[chatId]?.messages
-
-
-   return (
-      <div 
-         style={msgsWrapper}
-      >
-         { msgArr ?
-
-            msgArr.map(msg => 
-                              <CMsg               
-                                 key={msg.nextMsg?._id || null} 
-                                 msg={msg} 
-                                 onEdit={onEdit}
-                               /> ) :
-               
-                     <div>сообщений нет</div>         
-         }  
-
-      </div>
-   )
-}
-export const CMsgList = connect(state => ( { chats: state.chats } ))(MsgList)
-
-
-

+ 26 - 0
src/components/MsgList/MsgList.jsx

@@ -0,0 +1,26 @@
+import React from 'react'
+import { connect } from 'react-redux'
+import './MsgList.scss'
+
+import { CMsg } from '..'
+
+const MsgList = ({ chats, chatId, onEdit }) => {
+    const msgArr = chats[chatId]?.messages
+
+    return (
+        <div className="msgsWrapper">
+            {msgArr ? (
+                msgArr.map((msg) => (
+                    <CMsg
+                        key={msg.nextMsg?._id || null}
+                        msg={msg}
+                        onEdit={onEdit}
+                    />
+                ))
+            ) : (
+                <div>сообщений нет</div>
+            )}
+        </div>
+    )
+}
+export const CMsgList = connect((state) => ({ chats: state.chats }))(MsgList)

+ 8 - 0
src/components/MsgList/MsgList.scss

@@ -0,0 +1,8 @@
+.msgsWrapper {
+    display: flex;
+    flex-direction: column;
+    justify-content: flex-start;
+    align-items: stretch;
+    padding-top: 10px;
+    padding-bottom: 5px;
+}

+ 3 - 0
src/components/MsgList/index.js

@@ -0,0 +1,3 @@
+import { CMsgList } from './MsgList'
+
+export { CMsgList }

+ 0 - 28
src/components/Preload.jsx

@@ -1,28 +0,0 @@
-import React from 'react';
-import {connect}   from 'react-redux';
-import { CircularProgress } from '@mui/material';
-
-const Preloader = () => (  
-   <CircularProgress />
-)
-
-const RejectedAlert = ({error}) => (
-  <div>
-    Ошибка {error}
-  </div>
-)
-
-const Preloaded = ({promiseName, promiseState, children}) => (
-  <>
-  { promiseState[promiseName]?.status === 'RESOLVED' ? children :
-      promiseState[promiseName]?.status === 'REJECTED' ? 
-            <RejectedAlert error={promiseState[promiseName]?.error}/> :
-         <Preloader />
-  }
-  </>
-)
-export const CPreloaded = connect(state => ({promiseState: state.promise}))(Preloaded)
-
-{/* <CPreloaded promiseName={}>
-  <CCategoryById />
-</CPreloaded>  */}

+ 0 - 370
src/components/ProfileModal.jsx

@@ -1,370 +0,0 @@
-import React, {useEffect, useState} from 'react';
-import { useTheme } from '@mui/material/styles';
-import useMediaQuery from '@mui/material/useMediaQuery';
-import Box from '@mui/material/Box';
-import Modal from '@mui/material/Modal';
-import ListItem from '@mui/material/ListItem';
-import ListItemText from '@mui/material/ListItemText';
-import Button from '@mui/material/Button';
-import IconButton from '@mui/material/IconButton';
-import Typography from '@mui/material/Typography';
-import TextField from '@mui/material/TextField';
-import CloseIcon from '@mui/icons-material/Close';
-
-import {useDropzone} from 'react-dropzone';
-
-import {UserAvatar} from '.'
-
-import { printStrReq, passReq } from "../helpers";
-import {connect}  from 'react-redux'
-import {actionSetUserInfo, actionSetUserPass} from '../actions'
-
-
-const styleModalParrent = {
-  position: 'absolute',
-  top: '50%',
-  left: '50%',
-  transform: 'translate(-50%, -50%)',
-  width: '70%',
-  maxWidth: '600px',
-  bgcolor: 'background.paper',
-  border: '1px solid #999',
-  boxShadow: 24,
-  p: 3,
-  display: 'flex',
-  flexDirection: 'column',
-  justifyContent: 'space-between',
-}
-
-
-const PassModal = ({ onСonfirm, regError}) => {
-   
-   const [open, setOpen] = useState(false);
-   const handleOpen = () => {
-     setOpen(true);
-   }
-   const handleClose = () => {
-     setOpen(false);
-   }
-
-   const [pass, setPass] = useState('')
-   const [passBlur, setPassBlur] = useState(false)
-
-   const [pass2, setPass2] = useState('')
-   const [pass2Blur, setPass2Blur] = useState(false)
-
-
-   const [wrongAlert, setWrongAlert] = useState(false)
-
-   useEffect(() => {
-      if (regError?.payload === null) {
-         setWrongAlert(true)
-      } else {
-         setWrongAlert(false)
-      }
-   },[regError])
-
-
-   return (
-     <>
-      <Button variant="text" color="error" onClick={handleOpen} >
-         Новый пароль
-      </Button> 
-
-       <Modal
-         hideBackdrop
-         open={open}
-         onClose={handleClose}
-       >
-         <Box sx={{ ...styleModalParrent,   width: '50%', maxWidth: '400px', }}>
-            <Box sx={{ display: 'flex', justifyContent: 'end' }}>
-               <IconButton aria-label="delete" onClick={handleClose}>
-                  <CloseIcon />
-               </IconButton>
-            </Box>
-
-
-            <Box sx={{ display: 'flex', flexDirection: 'column', justifyContent: 'center', mt: 4 }}>
-               <Typography variant="body1">
-                  Введите новый пароль
-               </Typography>
-               <TextField
-                  onChange={(e) => {
-                        e.target.value = e.target.value.trim()
-                        setPass(e.target.value.trim())
-                     }
-                  }  
-                  onBlur={() => {
-                        setPassBlur(true)              
-                     }
-                  } 
-                  onFocus={() => {
-                        setPassBlur(false)              
-                     }
-                  }
-                  error={passBlur ? (passReq.checkPass(pass) ? false : true) : false}               
-                  helperText={passReq.printPassReq(pass)}            
-   
-                  inputProps={{
-                     maxLength: 100
-                     }}
-                  required
-                  variant="standard"
-                  margin="none"
-                  fullWidth
-                  name="password"
-                  label=""
-                  type="password"
-                  id="passwordUser"
-                  sx={{mt: 1}}
-               />
-            </Box>
-
-            <Box sx={{ display: 'flex', flexDirection: 'column', justifyContent: 'center', mt: 4 }}>
-               <Typography variant="body1">
-                  Повторите новый пароль
-               </Typography>
-               <TextField
-                  onChange={(e) => {
-                        e.target.value = e.target.value.trim()
-                        setPass2(e.target.value.trim())
-                     }
-                  }  
-                  onBlur={() => {
-                        setPass2Blur(true)              
-                     }
-                  } 
-                  onFocus={() => {
-                        setPass2Blur(false)              
-                     }
-                  }
-                  error={pass2Blur ? (passReq.checkPass(pass2) ? false : true) : false}               
-                  helperText={passReq.printPassReq(pass2)}            
-   
-                  inputProps={{
-                     maxLength: 100
-                     }}
-                  required
-                  variant="standard"
-                  margin="none"
-                  fullWidth
-                  name="password"
-                  label=""
-                  type="password"
-                  id="passwordUser2"
-                  sx={{mt: 1}}
-               />
-            </Box>
-
-
-            { wrongAlert &&
-               <Typography component="p" variant="body2" mt={1} ml={2} 
-                  sx={{fontWeight: 'medium', fontSize: '0.75rem', color: '#d32f2f'}}>
-                     Неверный пароль
-               </Typography> 
-            }
-
-            <Box sx={{ display: 'flex', justifyContent: 'end', mt: 2}}>
-               <Button  variant="contained" 
-                        disabled={( passReq.checkPass(pass) && 
-                                    pass === pass2 ) ? false : true}
-
-                        onClick={() => {
-                           onСonfirm(pass);
-                           handleClose()
-                        } }>
-                  Изменить 
-               </Button>
-            </Box>
-
-         </Box>
-       </Modal>
-     </>
-   );
- }
- const CPassModal = connect( (state) => ({regError: state.promise.changePass || {}}), 
-                              {onСonfirm : actionSetUserPass})(PassModal)
-
-
-
-
-
-
-
-
-const ProfileModal = ({minLog='2', myProfile, onСonfirm, logError}) => {
-  const [open, setOpen] = useState(false)
-
-   const [login, setLogin] = useState(myProfile.login)
-   const [logBlur, setLogBlur] = useState(false)
-   const [nick, setNick] = useState(myProfile.nick)
-
-   const [img, setImg] = useState(null)
-   const {
-      acceptedFiles,
-      getRootProps,
-      getInputProps
-   } = useDropzone({   
-      accept: 'image/*', 
-      maxFiles: 1,
-      onDrop: (acceptedFiles) => {
-         setImg(acceptedFiles[0])
-      }
-   });
-
-  const handleOpen = () => setOpen(true)
-  const handleClose = () => (setOpen(false))
-
-  const [wrongAlert, setWrongAlert] = useState(false)
-
-  useEffect(() => {
-     if (logError?.payload === null) {
-        setWrongAlert(true)
-     } else {
-        setWrongAlert(false)
-     }
-  },[logError])
-
-
-  const theme = useTheme()
-  const matches = useMediaQuery(theme.breakpoints.up('sm'))
-  return (
-    <div>
-         <ListItem button onClick={handleOpen} >
-            <ListItemText primary={'Мой профиль'} />
-         </ListItem>
-
-      <Modal
-        open={open}
-        onClose={handleClose}
-        aria-labelledby="modal-modal-title"
-        aria-describedby="modal-modal-description"
-      >
-        <Box sx={styleModalParrent}>
-
-            <Box sx={{ display: 'flex', justifyContent: 'end' }}>
-               <IconButton aria-label="delete" onClick={handleClose}>
-                  <CloseIcon />
-               </IconButton>
-            </Box>
-
-            <Box sx={{ display: 'flex', justifyContent: 'center' }}>
-               <Typography variant="h5">
-                  Мой профиль
-               </Typography>
-            </Box>
-            
-            <Box sx={{ display: 'flex', justifyContent: 'start', mt: 2,  }}>
-
-
-               <section className="container">
-                  <Box {...getRootProps({className: 'dropzone'})} 
-                        sx={{ cursor: 'pointer', height: '80px', display: 'flex' }} 
-                        className="avatarInModal" >
-
-                     <UserAvatar profile={{  login: login, nick: nick, avatar: {url: myProfile.avatar?.url || ''},
-                                    localUrl: img && URL.createObjectURL(img)}} bigSize={true} />
-
-                     <input {...getInputProps()} type="file" name="media" id='mediaUser' />
-
-                     { matches &&
-                        <Box sx={{ p: '20px', ml: 1 }} >Изменить аватар</Box>
-                     }
-
-                  </Box>
-               </section>
-
-            </Box>
-
-            <Box sx={{ display: 'flex', flexDirection: 'column', justifyContent: 'center', mt: 4 }}>
-               <Typography variant="body1">
-                  Изменить логин
-               </Typography>
-               <TextField            
-                  onChange={(e) => {
-                        e.target.value = e.target.value.trim()
-                        setLogin(e.target.value)
-                     }
-                  }     
-                  onBlur={() => {
-                     setLogBlur(true)              
-                  }
-                  } 
-                  onFocus={() => {
-                        setLogBlur(false)              
-                     }
-                  }          
-                  error={logBlur ? ((login?.length >= minLog ) ? false : true) : false}
-                  helperText={printStrReq(login, minLog)}            
-
-                  inputProps={{
-                     maxLength: 25
-                  }}
-                  variant="standard"
-                  margin="none"
-                  fullWidth
-                  id="loginMain"
-                  label=""
-                  name="login"
-                  defaultValue={login}
-                  sx={{mt: 1}}
-               />
-            </Box>
-            
-            <Box sx={{ display: 'flex', flexDirection: 'column', justifyContent: 'center', mt: 4 }}>
-               <Typography variant="body1" >
-                  Изменить ник
-               </Typography>
-               <TextField  
-                  onChange={(e) => {
-                        setNick(e.target.value)
-                     }
-                  }           
-                  helperText={''}            
-
-                  variant="standard"
-                  margin="none"
-                  fullWidth
-                  id="nickMain"
-                  label=""
-                  name="nick"
-                  defaultValue={nick}
-                  sx={{mt: 1}}
-               />
-            </Box>  
-
-
-            <Box sx={{ display: 'flex', justifyContent: 'center', mt: 4 }}>
-                  <CPassModal />
-            </Box>
-
-            { wrongAlert ?
-                  <Typography component="p" variant="body2" mt={2} ml={2} 
-                     sx={{fontWeight: 'medium', fontSize: '0.75rem', color: '#d32f2f'}}>
-                        Логин уже существует
-                  </Typography> :
-                  <></>
-               }
-
-            <Box sx={{ display: 'flex', justifyContent: 'end', mt: 2}}>
-               <Button  variant="contained" 
-                        disabled={(login?.length >= minLog) ? false : true}
-                        onClick={() => {
-                           onСonfirm("media", img, login, nick);
-                           handleClose()
-                        } }>
-                  Применить 
-               </Button>
-            </Box>
-
-
-        </Box>
-      </Modal>
-    </div>
-  );
-}
-export const CProfileModal = connect(  state => (  {  myProfile: state.promise?.myProfile?.payload || {},
-                                                      logError: state.promise.updateUser || {} }), 
-                                       {onСonfirm : actionSetUserInfo})(ProfileModal)
-
-

+ 164 - 0
src/components/ProfileModal/PassModal.jsx

@@ -0,0 +1,164 @@
+import React, { useEffect, useState } from 'react'
+import Box from '@mui/material/Box'
+import Modal from '@mui/material/Modal'
+import Button from '@mui/material/Button'
+import IconButton from '@mui/material/IconButton'
+import Typography from '@mui/material/Typography'
+import TextField from '@mui/material/TextField'
+import CloseIcon from '@mui/icons-material/Close'
+import { connect } from 'react-redux'
+import './ProfileModal.scss'
+
+import { passReq } from '../../helpers'
+import { actionSetUserPass } from '../../actions'
+
+const PassModal = ({ onСonfirm, regError }) => {
+    const [open, setOpen] = useState(false)
+    const handleOpen = () => {
+        setOpen(true)
+    }
+    const handleClose = () => {
+        setOpen(false)
+    }
+
+    const [pass, setPass] = useState('')
+    const [passBlur, setPassBlur] = useState(false)
+
+    const [pass2, setPass2] = useState('')
+    const [pass2Blur, setPass2Blur] = useState(false)
+
+    const [wrongAlert, setWrongAlert] = useState(false)
+
+    useEffect(() => {
+        if (regError?.payload === null) {
+            setWrongAlert(true)
+        } else {
+            setWrongAlert(false)
+        }
+    }, [regError])
+
+    return (
+        <>
+            <Button variant="text" color="error" onClick={handleOpen}>
+                Новый пароль
+            </Button>
+
+            <Modal hideBackdrop open={open} onClose={handleClose}>
+                <Box className="styleModalParrent passModalWrapp">
+                    <Box className="closeBlock">
+                        <IconButton onClick={handleClose}>
+                            <CloseIcon />
+                        </IconButton>
+                    </Box>
+
+                    <Box className="passBlock">
+                        <Typography variant="body1">
+                            Введите новый пароль
+                        </Typography>
+                        <TextField
+                            onChange={(e) => {
+                                e.target.value = e.target.value.trim()
+                                setPass(e.target.value.trim())
+                            }}
+                            onBlur={() => {
+                                setPassBlur(true)
+                            }}
+                            onFocus={() => {
+                                setPassBlur(false)
+                            }}
+                            error={
+                                passBlur
+                                    ? passReq.checkPass(pass)
+                                        ? false
+                                        : true
+                                    : false
+                            }
+                            helperText={passReq.printPassReq(pass)}
+                            inputProps={{
+                                maxLength: 100,
+                            }}
+                            required
+                            variant="standard"
+                            margin="none"
+                            fullWidth
+                            name="password"
+                            label=""
+                            type="password"
+                            id="passwordUser"
+                            className="passInput"
+                        />
+                    </Box>
+
+                    <Box className="passBlock">
+                        <Typography variant="body1">
+                            Повторите новый пароль
+                        </Typography>
+                        <TextField
+                            onChange={(e) => {
+                                e.target.value = e.target.value.trim()
+                                setPass2(e.target.value.trim())
+                            }}
+                            onBlur={() => {
+                                setPass2Blur(true)
+                            }}
+                            onFocus={() => {
+                                setPass2Blur(false)
+                            }}
+                            error={
+                                pass2Blur
+                                    ? passReq.checkPass(pass2)
+                                        ? false
+                                        : true
+                                    : false
+                            }
+                            helperText={passReq.printPassReq(pass2)}
+                            inputProps={{
+                                maxLength: 100,
+                            }}
+                            required
+                            variant="standard"
+                            margin="none"
+                            fullWidth
+                            name="password"
+                            label=""
+                            type="password"
+                            id="passwordUser2"
+                            className="passInput"
+                        />
+                    </Box>
+
+                    {wrongAlert && (
+                        <Typography
+                            component="p"
+                            variant="body2"
+                            className="alertBlock"
+                        >
+                            Неверный пароль
+                        </Typography>
+                    )}
+
+                    <Box className="btnBlock">
+                        <Button
+                            variant="contained"
+                            disabled={
+                                passReq.checkPass(pass) && pass === pass2
+                                    ? false
+                                    : true
+                            }
+                            onClick={() => {
+                                onСonfirm(pass)
+                                handleClose()
+                            }}
+                        >
+                            Изменить
+                        </Button>
+                    </Box>
+                </Box>
+            </Modal>
+        </>
+    )
+}
+export const CPassModal = connect(
+    (state) => ({ regError: state.promise.changePass || {} }),
+    { onСonfirm: actionSetUserPass }
+)(PassModal)

+ 198 - 0
src/components/ProfileModal/ProfileModal.jsx

@@ -0,0 +1,198 @@
+import React, { useEffect, useState } from 'react'
+import { useTheme } from '@mui/material/styles'
+import useMediaQuery from '@mui/material/useMediaQuery'
+import Box from '@mui/material/Box'
+import Modal from '@mui/material/Modal'
+import ListItem from '@mui/material/ListItem'
+import ListItemText from '@mui/material/ListItemText'
+import Button from '@mui/material/Button'
+import IconButton from '@mui/material/IconButton'
+import Typography from '@mui/material/Typography'
+import TextField from '@mui/material/TextField'
+import CloseIcon from '@mui/icons-material/Close'
+import { connect } from 'react-redux'
+import { useDropzone } from 'react-dropzone'
+import './ProfileModal.scss'
+
+import { CPassModal } from './PassModal'
+import { UserAvatar } from '..'
+import { printStrReq } from '../../helpers'
+import { actionSetUserInfo } from '../../actions'
+
+const ProfileModal = ({ minLog = '2', myProfile, onСonfirm, logError }) => {
+    const [open, setOpen] = useState(false)
+
+    const [login, setLogin] = useState(myProfile.login)
+    const [logBlur, setLogBlur] = useState(false)
+    const [nick, setNick] = useState(myProfile.nick)
+
+    const [img, setImg] = useState(null)
+    const { getRootProps, getInputProps } = useDropzone({
+        accept: 'image/*',
+        maxFiles: 1,
+        onDrop: (acceptedFiles) => {
+            setImg(acceptedFiles[0])
+        },
+    })
+
+    const handleOpen = () => setOpen(true)
+    const handleClose = () => setOpen(false)
+
+    const [wrongAlert, setWrongAlert] = useState(false)
+
+    useEffect(() => {
+        if (logError?.payload === null) {
+            setWrongAlert(true)
+        } else {
+            setWrongAlert(false)
+        }
+    }, [logError])
+
+    const theme = useTheme()
+    const matches = useMediaQuery(theme.breakpoints.up('sm'))
+    return (
+        <div>
+            <ListItem button onClick={handleOpen}>
+                <ListItemText primary={'Мой профиль'} />
+            </ListItem>
+
+            <Modal open={open} onClose={handleClose}>
+                <Box className="styleModalParrent profileModalWrapp">
+                    <Box className="closeBlock">
+                        <IconButton onClick={handleClose}>
+                            <CloseIcon />
+                        </IconButton>
+                    </Box>
+
+                    <Box className="titleBlock">
+                        <Typography variant="h5">Мой профиль</Typography>
+                    </Box>
+
+                    <Box className="avatarBlock">
+                        <section className="container">
+                            <Box
+                                {...getRootProps({ className: 'dropzone' })}
+                                className="avatarBox avatarInModal"
+                            >
+                                <UserAvatar
+                                    profile={{
+                                        login: login,
+                                        nick: nick,
+                                        avatar: {
+                                            url: myProfile.avatar?.url || '',
+                                        },
+                                        localUrl:
+                                            img && URL.createObjectURL(img),
+                                    }}
+                                    bigSize={true}
+                                />
+
+                                <input
+                                    {...getInputProps()}
+                                    type="file"
+                                    name="media"
+                                    id="mediaUser"
+                                />
+
+                                {matches && (
+                                    <Box className="avatarTitle">
+                                        Изменить аватар
+                                    </Box>
+                                )}
+                            </Box>
+                        </section>
+                    </Box>
+
+                    <Box className="nameBlock">
+                        <Typography variant="body1">Изменить логин</Typography>
+                        <TextField
+                            onChange={(e) => {
+                                e.target.value = e.target.value.trim()
+                                setLogin(e.target.value)
+                            }}
+                            onBlur={() => {
+                                setLogBlur(true)
+                            }}
+                            onFocus={() => {
+                                setLogBlur(false)
+                            }}
+                            error={
+                                logBlur
+                                    ? login?.length >= minLog
+                                        ? false
+                                        : true
+                                    : false
+                            }
+                            helperText={printStrReq(login, minLog)}
+                            inputProps={{
+                                maxLength: 25,
+                            }}
+                            variant="standard"
+                            margin="none"
+                            fullWidth
+                            id="loginMain"
+                            label=""
+                            name="login"
+                            defaultValue={login}
+                            className="nameInput"
+                        />
+                    </Box>
+
+                    <Box className="nameBlock">
+                        <Typography variant="body1">Изменить ник</Typography>
+                        <TextField
+                            onChange={(e) => {
+                                setNick(e.target.value)
+                            }}
+                            helperText={''}
+                            variant="standard"
+                            margin="none"
+                            fullWidth
+                            id="nickMain"
+                            label=""
+                            name="nick"
+                            defaultValue={nick}
+                            className="nameInput"
+                        />
+                    </Box>
+
+                    <Box className="passModalBlock">
+                        <CPassModal />
+                    </Box>
+
+                    {wrongAlert ? (
+                        <Typography
+                            component="p"
+                            variant="body2"
+                            className="alertBlock"
+                        >
+                            Логин уже существует
+                        </Typography>
+                    ) : (
+                        <></>
+                    )}
+
+                    <Box className="btnBlock">
+                        <Button
+                            variant="contained"
+                            disabled={login?.length >= minLog ? false : true}
+                            onClick={() => {
+                                onСonfirm('media', img, login, nick)
+                                handleClose()
+                            }}
+                        >
+                            Применить
+                        </Button>
+                    </Box>
+                </Box>
+            </Modal>
+        </div>
+    )
+}
+export const CProfileModal = connect(
+    (state) => ({
+        myProfile: state.promise?.myProfile?.payload || {},
+        logError: state.promise.updateUser || {},
+    }),
+    { onСonfirm: actionSetUserInfo }
+)(ProfileModal)

+ 95 - 0
src/components/ProfileModal/ProfileModal.scss

@@ -0,0 +1,95 @@
+.profileModalWrapp {
+    width: 70%;
+    max-width: 600px;
+
+    .closeBlock {
+        display: flex;
+        justify-content: flex-end;
+    }
+
+    .titleBlock {
+        display: flex;
+        justify-content: center;
+    }
+
+    .avatarBlock {
+        display: flex;
+        justify-content: flex-start;
+        margin-top: 16px;
+
+        .avatarBox {
+            display: flex;
+            height: 80px;
+            cursor: pointer;
+
+            .avatarTitle {
+                padding: 20px;
+                margin-left: 8px;
+            }
+        }
+    }
+
+    .nameBlock {
+        display: flex;
+        flex-direction: column;
+        justify-content: center;
+        margin-top: 32px;
+
+        .nameInput {
+            margin-top: 8px;
+        }
+    }
+
+    .passModalBlock {
+        display: flex;
+        justify-content: center;
+        margin-top: 32px;
+    }
+
+    .alertBlock {
+        font-weight: medium;
+        font-size: 0.75rem;
+        color: #d32f2f;
+        margin: 16px 0 0 16px;
+    }
+
+    .btnBlock {
+        display: flex;
+        justify-content: flex-end;
+        margin-top: 16px;
+    }
+}
+
+.passModalWrapp {
+    width: 50%;
+    max-width: 400px;
+
+    .closeBlock {
+        display: flex;
+        justify-content: flex-end;
+    }
+
+    .passBlock {
+        display: flex;
+        flex-direction: column;
+        justify-content: center;
+        margin-top: 32px;
+
+        .passInput {
+            margin-top: 8px;
+        }
+    }
+
+    .alertBlock {
+        font-weight: medium;
+        font-size: 0.75rem;
+        color: #d32f2f;
+        margin: 8px 0 0 16px;
+    }
+
+    .btnBlock {
+        display: flex;
+        justify-content: flex-end;
+        margin-top: 16px;
+    }
+}

+ 3 - 0
src/components/ProfileModal/index.js

@@ -0,0 +1,3 @@
+import { CProfileModal } from './ProfileModal'
+
+export { CProfileModal }

+ 42 - 43
src/components/SearchBlock.jsx

@@ -1,51 +1,50 @@
-import React from 'react';
-import { styled, alpha } from '@mui/material/styles';
-import InputBase from '@mui/material/InputBase';
-import SearchIcon from '@mui/icons-material/Search';
+import React from 'react'
+import { styled, alpha } from '@mui/material/styles'
+import InputBase from '@mui/material/InputBase'
+import SearchIcon from '@mui/icons-material/Search'
 
 const Search = styled('div')(({ theme }) => ({
-  position: 'relative',
-  borderRadius: theme.shape.borderRadius,
-  backgroundColor: alpha(theme.palette.common.white, 0.15),
-  '&:hover': {
-    backgroundColor: alpha(theme.palette.common.white, 0.25),
-  },
-  marginLeft: 1,
-  width: '100%',
-}));
+    position: 'relative',
+    borderRadius: theme.shape.borderRadius,
+    backgroundColor: alpha(theme.palette.common.white, 0.15),
+    '&:hover': {
+        backgroundColor: alpha(theme.palette.common.white, 0.25),
+    },
+    marginLeft: 1,
+    width: '100%',
+}))
 
 const SearchIconWrapper = styled('div')(({ theme }) => ({
-  padding: theme.spacing(0, 2),
-  height: '100%',
-  position: 'absolute',
-  pointerEvents: 'none',
-  display: 'flex',
-  alignItems: 'center',
-  justifyContent: 'center',
-}));
+    padding: theme.spacing(0, 2),
+    height: '100%',
+    position: 'absolute',
+    pointerEvents: 'none',
+    display: 'flex',
+    alignItems: 'center',
+    justifyContent: 'center',
+}))
 
 const StyledInputBase = styled(InputBase)(({ theme }) => ({
-  color: 'inherit',
-  '& .MuiInputBase-input': {
-    padding: theme.spacing(1, 1, 1, 0),
-    // vertical padding + font size from searchIcon
-    paddingLeft: `calc(1em + ${theme.spacing(4)})`,
-    transition: theme.transitions.create('width'),
-    width: '100%',
-  },
-}));
+    color: 'inherit',
+    '& .MuiInputBase-input': {
+        padding: theme.spacing(1, 1, 1, 0),
+        // vertical padding + font size from searchIcon
+        paddingLeft: `calc(1em + ${theme.spacing(4)})`,
+        transition: theme.transitions.create('width'),
+        width: '100%',
+    },
+}))
 
-export const SearchBlock = ({ setInput, text="Поиск" }) => {
-   return (
-   <Search>
-      <SearchIconWrapper>
-        <SearchIcon />
-      </SearchIconWrapper>
-      <StyledInputBase
-        onChange={(e) => setInput(e.target.value)}
-        placeholder={text}
-      />
-   </Search>
-   )
+export const SearchBlock = ({ setInput, text = 'Поиск' }) => {
+    return (
+        <Search>
+            <SearchIconWrapper>
+                <SearchIcon />
+            </SearchIconWrapper>
+            <StyledInputBase
+                onChange={(e) => setInput(e.target.value)}
+                placeholder={text}
+            />
+        </Search>
+    )
 }
-

+ 0 - 301
src/components/SendingField.jsx

@@ -1,301 +0,0 @@
-import React, {useState, useEffect, useRef} from 'react';
-import Box from '@mui/material/Box';
-import Button from '@mui/material/Button';
-import TextareaAutosize from '@mui/base/TextareaAutosize';
-import SendIcon from '@mui/icons-material/Send';
-import AttachFileIcon from '@mui/icons-material/AttachFile';
-import CloseIcon from '@mui/icons-material/Close';
-
-import {useDropzone} from 'react-dropzone';
-import {render} from 'react-dom';
-import {SortableContainer, SortableElement} from 'react-sortable-hoc';
-import {arrayMoveImmutable} from 'array-move';
-
-import { backURL }  from '../helpers'
-import {connect}  from 'react-redux'
-import {
-   actionSendMsg
-} from "../actions"
-
-const containerWrapp = {
-   display: 'flex', 
-   flexDirection: 'column',
-   padding: '5px',
-}
-
-const thumbsContainer = {
-   flexGrow: '0',
-   display: 'flex',
-   flexDirection: 'row',
-   flexWrap: 'wrap',
-   width: '100%',
-   overflow: 'auto',
- }
- 
- const thumb = {
-   flexGrow: '0',
-   display: 'inline-flex',   
-   borderRadius: 2,
-   border: '1px solid #eaeaea',
-   marginBottom: 8,
-   marginRight: 8,
-   width: 100,
-   height: 118,
-   padding: 4,
-   boxSizing: 'border-box',
-   position: 'relative',
-   backgroundColor: '#fff'
- }
- 
- const thumbInner = {
-   paddingTop: 18,
-   width: "100%",
-   minWidth: 0,
-   overflow: 'hidden'
- }
-
- const closeBtn = {
-    position: 'absolute',
-    right: 0,
- }
-
- const img = {
-   margin: '0 auto',
-   display: 'block',
-   objectFit: "cover",
-   maxWidth: 'auto',
-   maxHeight: '100%'
- }
-
- const other = {
-   margin: '0 auto',
-   display: 'block',
-   maxWidth: 'auto',
-   maxHeight: '100%',
-   userSelect: 'none',
- }
- const othTitle = {
-   fontWeight: 500,
-   fontSize: "14px"
- }
- const othContent = {
-   marginTop: "2px",
-   fontWeight: 300,
-   fontSize: "12px"
-}
-
-
-
-
- const SortableItem = SortableElement(({onDelete, file}) => (
-   <div style={thumb}>         
-      <div style={closeBtn}>
-         <CloseIcon 
-            fontSize="small" 
-            sx={{ cursor: 'pointer' }}
-            onClick={onDelete} 
-         />
-      </div>         
-      <div style={thumbInner}>  
-      { 
-         (file.type.split('/')[0] === 'image') ? 
-            <img
-               src={file.url}
-               style={img}
-            />  :
-            
-            (file.type.split('/')[0] === 'video') ?
-               <video 
-                  autoPlay 
-                  muted
-                  loop
-                  // не работает
-                  // playbackrate={'10'}
-                  src={file.url}
-                  style={img}
-               >
-               </video> :
-
-               <div
-                  style={other}
-               >
-                  <div style={othTitle}>{file.type.split('/')[0]}</div>
-                  <div style={othContent}>{file.name}</div>                  
-               </div>
-      }         
-
-      </div>     
-   </div>
- ));
-
- const SortableList = SortableContainer(({children}) => {
-
-   return (
-      <div style={thumbsContainer}>
-         {children}
-      </div>
-   )
- });
-
-
-
-
-const MsgDropZone = ({ setText, setFiles, setMsgId, files, text, onEnter }) => {
-
-   const {
-      acceptedFiles,
-      getRootProps, 
-      getInputProps
-   } = useDropzone({
-         noKeyboard: true,
-         onDrop: async (acceptedFiles) => {
-            // console.log(acceptedFiles)
-            await setFiles([...files, 
-               ...acceptedFiles.map(file => Object.assign(file, {
-                  url: URL.createObjectURL(file)
-               }))
-            ])
-
-         }
-      });
-
-   const onDelete = (i) => {
-      setFiles( (files) => files.filter((el, index) => index !== i) )
-   }
-
-   const onSortEnd = ({oldIndex, newIndex}) => {
-      setFiles( (files) => (
-        arrayMoveImmutable(files, oldIndex, newIndex)
-      ));
-    }
-   
-   // useEffect(() => {
-   // // Make sure to revoke the data uris to avoid memory leaks
-   // files.forEach(file => URL.revokeObjectURL(file.url));
-   // }, [files]);
-   const textArea = useRef(null)
-
-   useEffect(() => {
-      textArea.current.focus()
-   })
-
-  return (
-   <>
-      <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>
-      
-
-         <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>
-   </>
-  )
-}
-
-
-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)
-
-
-   useEffect(() => {
-      setText(msg?.text || '')
-      setFiles(msg?.media.map(mediaFile =>( {...mediaFile, url: backURL +  mediaFile.url} )) || [])
-      setMsgId(msg?._id || null)
-   },[msg])
-
-   return (
-      <Box sx={{ display: 'flex', alignItems: 'stratch', flexDirection: 'column',
-            height: '100%', width: '100%'}} >
-      
-         <Box sx={{ flexGrow: 1, flexShrink: 1, overflow: 'auto', backgroundColor: '#fff' }}>
-
-            { msgId &&
-               <Box sx={{ flexGrow: 1, flexShrink: 1}}>
-                  <Button 
-                     sx={{ width: "100%" }}
-                     variant="contained" 
-                     onClick={() => {
-                        setText('')
-                        setFiles([])
-                        setMsgId()
-                     }}
-                  >
-                     Отменить редактирование
-                  </Button>
-               </Box>
-            }
-            
-            <MsgDropZone 
-               setText={setText} 
-               setFiles={setFiles} 
-               setMsgId={setMsgId}
-               files={files} 
-               text={text} 
-               onEnter={() => onSend(chatId, text.trim(), "media", files, msgId)} />
-         
-         </Box>         
-         <Box sx={{ flexGrow: 1, flexShrink: 1}}>
-            <Button 
-               sx={{ width: "100%" }}
-               variant="contained" 
-               endIcon={<SendIcon />} 
-               onClick={() => {
-                  (text.match(/^\s*$/) && files.length === 0) ||
-                        onSend(chatId, text.trim(), "media", files, msgId) 
-                  setText('')
-                  setFiles([])
-                  setMsgId()
-               }}
-            >
-               Отправить
-            </Button>
-         </Box>
-         
-      </Box>
-   )
-}
-export const CSendingField= connect( null,
-                           {onSend: actionSendMsg})(SendingField)

+ 148 - 0
src/components/SendingField/MsgDropZone.jsx

@@ -0,0 +1,148 @@
+import React, { useEffect, useRef } from 'react'
+import TextareaAutosize from '@mui/base/TextareaAutosize'
+import AttachFileIcon from '@mui/icons-material/AttachFile'
+import CloseIcon from '@mui/icons-material/Close'
+import { useDropzone } from 'react-dropzone'
+import { SortableContainer, SortableElement } from 'react-sortable-hoc'
+import { arrayMoveImmutable } from 'array-move'
+import './SendingField.scss'
+
+const SortableItem = SortableElement(({ onDelete, file }) => (
+    <div className="thumb">
+        <div className="closeBtn">
+            <CloseIcon
+                fontSize="small"
+                sx={{ cursor: 'pointer' }}
+                onClick={onDelete}
+            />
+        </div>
+        <div className="thumbInner">
+            {file.type &&
+                (file.type.split('/')[0] === 'image' ? (
+                    <img src={file.url} className="img" />
+                ) : file.type.split('/')[0] === 'video' ? (
+                    <video
+                        autoPlay
+                        muted
+                        loop
+                        src={file.url}
+                        className="img"
+                    ></video>
+                ) : (
+                    <div className="other">
+                        <div className="othTitle">
+                            {file.type.split('/')[0]}
+                        </div>
+                        <div className="othContent">{file.name}</div>
+                    </div>
+                ))}
+        </div>
+    </div>
+))
+
+const SortableList = SortableContainer(({ children }) => {
+    return <div className="thumbsContainer">{children}</div>
+})
+
+export const MsgDropZone = ({
+    setText,
+    setFiles,
+    setMsgId,
+    files,
+    text,
+    onEnter,
+}) => {
+    const { getRootProps, getInputProps } = useDropzone({
+        noKeyboard: true,
+        onDrop: async (acceptedFiles) => {
+            // console.log(acceptedFiles)
+            await setFiles([
+                ...files,
+                ...acceptedFiles.map((file) =>
+                    Object.assign(file, {
+                        url: URL.createObjectURL(file),
+                    })
+                ),
+            ])
+        },
+    })
+
+    const onDelete = (i) => {
+        setFiles((files) => files.filter((el, index) => index !== i))
+    }
+
+    const onSortEnd = ({ oldIndex, newIndex }) => {
+        setFiles((files) => arrayMoveImmutable(files, oldIndex, newIndex))
+    }
+
+    // useEffect(() => {
+    // // Make sure to revoke the data uris to avoid memory leaks
+    // files.forEach(file => URL.revokeObjectURL(file.url));
+    // }, [files]);
+    const textArea = useRef(null)
+
+    useEffect(() => {
+        textArea.current.focus()
+    })
+
+    return (
+        <>
+            <section className="containerWrapp">
+                <SortableList
+                    onSortEnd={onSortEnd}
+                    axis={'xy'}
+                    pressDelay={100}
+                >
+                    {files.map((file, i) => {
+                        // console.log(file)
+                        if (file)
+                            return (
+                                <SortableItem
+                                    key={file.url}
+                                    index={i}
+                                    file={file}
+                                    onDelete={() => onDelete(i)}
+                                    axis={'xy'}
+                                />
+                            )
+                    })}
+                </SortableList>
+
+                <div
+                    {...getRootProps({ className: 'dropzone' })}
+                    className="inputContainer"
+                >
+                    <input
+                        {...getInputProps()}
+                        type="file"
+                        name="media"
+                        id="media"
+                    />
+
+                    <AttachFileIcon fontSize="large" className="attachFile" />
+
+                    <TextareaAutosize
+                        ref={textArea}
+                        value={text}
+                        minRows={4}
+                        maxRows={10}
+                        placeholder="Написать сообщение..."
+                        className="textareaAutosize"
+                        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>
+        </>
+    )
+}

+ 84 - 0
src/components/SendingField/SendingField.jsx

@@ -0,0 +1,84 @@
+import React, { useState, useEffect } from 'react'
+import Box from '@mui/material/Box'
+import Button from '@mui/material/Button'
+import SendIcon from '@mui/icons-material/Send'
+import { connect } from 'react-redux'
+import './SendingField.scss'
+
+import { MsgDropZone } from './MsgDropZone'
+import { actionSendMsg } from '../../actions'
+import { backURL } from '../../constants'
+
+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)
+
+    useEffect(() => {
+        setText(msg?.text || '')
+        setFiles(
+            msg?.media.map((mediaFile) => ({
+                ...mediaFile,
+                url: backURL + mediaFile.url,
+            })) || []
+        )
+        setMsgId(msg?._id || null)
+    }, [msg])
+
+    return (
+        <Box className="sendingField">
+            <Box className="sendingBox">
+                {msgId && (
+                    <Box className="buttonEditBox">
+                        <Button
+                            className="buttonEdit"
+                            variant="contained"
+                            onClick={() => {
+                                setText('')
+                                setFiles([])
+                                setMsgId()
+                            }}
+                        >
+                            Отменить редактирование
+                        </Button>
+                    </Box>
+                )}
+
+                <MsgDropZone
+                    setText={setText}
+                    setFiles={setFiles}
+                    setMsgId={setMsgId}
+                    files={files}
+                    text={text}
+                    onEnter={() =>
+                        onSend(chatId, text.trim(), 'media', files, msgId)
+                    }
+                />
+            </Box>
+            <Box className="buttonSendBox">
+                <Button
+                    className="buttonSend"
+                    variant="contained"
+                    endIcon={<SendIcon />}
+                    onClick={() => {
+                        ;(text.match(/^\s*$/) && files.length === 0) ||
+                            onSend(chatId, text.trim(), 'media', files, msgId)
+                        setText('')
+                        setFiles([])
+                        setMsgId()
+                    }}
+                >
+                    Отправить
+                </Button>
+            </Box>
+        </Box>
+    )
+}
+export const CSendingField = connect(null, { onSend: actionSendMsg })(
+    SendingField
+)

+ 111 - 0
src/components/SendingField/SendingField.scss

@@ -0,0 +1,111 @@
+.sendingField {
+    display: flex;
+    align-items: stretch;
+    flex-direction: column;
+    height: 100%;
+    width: 100%;
+
+    .sendingBox {
+        flex-grow: 1;
+        flex-shrink: 1;
+        overflow: auto;
+        background-color: #fff;
+
+        .buttonEditBox {
+            flex-grow: 1;
+            flex-shrink: 1;
+            .buttonEdit {
+                width: 100%;
+            }
+        }
+
+        .containerWrapp {
+            display: flex;
+            flex-direction: column;
+            padding: 5px;
+
+            .thumbsContainer {
+                flex-grow: 0;
+                display: flex;
+                flex-direction: row;
+                flex-wrap: wrap;
+                width: 100%;
+                overflow: auto;
+
+                .thumb {
+                    flex-grow: 0;
+                    display: inline-flex;
+                    border-radius: 2px;
+                    border: 1px solid #eaeaea;
+                    margin-bottom: 8px;
+                    margin-right: 8px;
+                    width: 100px;
+                    height: 118px;
+                    padding: 4px;
+                    box-sizing: border-box;
+                    position: relative;
+                    background-color: #fff;
+
+                    .closeBtn {
+                        position: absolute;
+                        right: 0;
+                    }
+
+                    .thumbInner {
+                        padding-top: 18px;
+                        width: 100%;
+                        min-width: 0;
+                        overflow: hidden;
+
+                        .img {
+                            margin: 0 auto;
+                            display: block;
+                            object-fit: cover;
+                            max-width: auto;
+                            max-height: 100%;
+                        }
+
+                        .other {
+                            margin: 0 auto;
+                            display: block;
+                            max-width: auto;
+                            max-height: 100%;
+                            user-select: none;
+
+                            .othTitle {
+                                font-weight: 500;
+                                font-size: 14px;
+                            }
+                            .othContent {
+                                margin-top: 2px;
+                                font-weight: 300;
+                                font-size: 12px;
+                            }
+                        }
+                    }
+                }
+            }
+
+            .inputContainer {
+                display: flex;
+                align-items: center;
+
+                .attachFile {
+                    cursor: pointer;
+                }
+
+                .textareaAutosize {
+                    width: 100%;
+                }
+            }
+        }
+    }
+
+    .buttonSendBox {
+        flex-grow: 1;
+        flex-shrink: 1;
+        .buttonSend {
+            width: 100%;
+        }
+    }
+}

+ 3 - 0
src/components/SendingField/index.js

@@ -0,0 +1,3 @@
+import { CSendingField } from './SendingField'
+
+export { CSendingField }

+ 23 - 32
src/components/UserCard.jsx

@@ -1,37 +1,28 @@
-import React from 'react';
-import ListItem from '@mui/material/ListItem';
-import Divider from '@mui/material/Divider';
-import ListItemText from '@mui/material/ListItemText';
-import ListItemAvatar from '@mui/material/ListItemAvatar';
+import React from 'react'
+import ListItem from '@mui/material/ListItem'
+import Divider from '@mui/material/Divider'
+import ListItemText from '@mui/material/ListItemText'
+import ListItemAvatar from '@mui/material/ListItemAvatar'
 
 import { UserAvatar } from '.'
 
-
-
-export const UserCard= ({ user, render, onAction, disabled=false }) => {
-
-   const ManageBtn = render
-   return (
-      <>
-         <ListItem
-            sx={ disabled ? { bgcolor: '#ddd' } : {} }
-            secondaryAction={
-               disabled ?
-               <></> :
-               <ManageBtn onEvent={onAction} />
-            }
+export const UserCard = ({ user, render, onAction, disabled = false }) => {
+    const ManageBtn = render
+    return (
+        <>
+            <ListItem
+                sx={disabled ? { bgcolor: '#ddd' } : {}}
+                secondaryAction={
+                    disabled ? <></> : <ManageBtn onEvent={onAction} />
+                }
             >
+                <ListItemAvatar>
+                    <UserAvatar profile={user} />
+                </ListItemAvatar>
 
-            <ListItemAvatar>
-               <UserAvatar profile={user} />
-            </ListItemAvatar>
-
-            <ListItemText
-               primary={user.login}
-               secondary={user.nick}
-            />
-         </ListItem>
-      <Divider variant="inset" component="li" />
-     </>
-   )
- }
+                <ListItemText primary={user.login} secondary={user.nick} />
+            </ListItem>
+            <Divider variant="inset" component="li" />
+        </>
+    )
+}

+ 0 - 68
src/components/UserSearch.jsx

@@ -1,68 +0,0 @@
-import React, {useEffect, useState} from 'react';
-import List from '@mui/material/List';
-import Box from '@mui/material/Box';
-import IconButton from '@mui/material/IconButton';
-import AddIcon from '@mui/icons-material/Add';
-
-import { UserCard, SearchBlock } from '.'
-
-import { connect }  from 'react-redux'
-import { actionFindUsers } from '../actions'
-
-
-const AddBtn = ({onEvent}) => (
-   <IconButton edge="end" onClick={onEvent}>
-      <AddIcon />
-   </IconButton>
-)
-
-const UserSearch = ({ findedUsers, onSearch, alreadySearched=[], onAdd, open }) => {
-
-   const [finded, setFinded] = useState([])
-   const [input, setInput] = useState(null)
-
-   useEffect( () => {
-      let timeout
-      if (input !== null) {
-         timeout = setTimeout(() => {
-            onSearch(input)
-         }, 500)
-      }
-      return () => {
-         clearTimeout(timeout)
-      }
-   },[input])
-
-   useEffect(() => {
-      setFinded(findedUsers)
-   },[findedUsers])
-   
-   useEffect(() => {
-      setInput(null)
-      setFinded([])
-   },[open])
-
-   return (
- 
-      <Box sx={{ flexBasis: "45%" }} >
-         <Box sx={{ height: "40px" }}>
-            <SearchBlock setInput={setInput} text={'Найти пользователя'} />
-         </Box>
-
-         <Box sx={{ height: "calc(100% - 40px)", overflowY: "auto" }}>
-            <List
-               sx={{ maxWidth: '100%', bgcolor: 'background.paper' }}
-               >
-               { finded.map((user) =>    
-                                    <UserCard key={user._id} user={user} 
-                                       render={AddBtn}  onAction={() => onAdd(user)} 
-                                          disabled={!!alreadySearched.find((searchedUser) => searchedUser._id === user._id)} />)}
-            </List>
-         </Box>
-      </Box>
-
-   )
- }
- export const CUserSearch = connect( state => ( { findedUsers: state.promise.findUsers?.payload || [] }), 
-                                        { onSearch: actionFindUsers } )(UserSearch)
- 

+ 79 - 0
src/components/UserSearch/UserSearch.jsx

@@ -0,0 +1,79 @@
+import React, { useEffect, useState } from 'react'
+import List from '@mui/material/List'
+import Box from '@mui/material/Box'
+import IconButton from '@mui/material/IconButton'
+import AddIcon from '@mui/icons-material/Add'
+import { connect } from 'react-redux'
+import './UserSearch.scss'
+
+import { UserCard, SearchBlock } from '..'
+import { actionFindUsers } from '../../actions'
+
+const AddBtn = ({ onEvent }) => (
+    <IconButton edge="end" onClick={onEvent}>
+        <AddIcon />
+    </IconButton>
+)
+
+const UserSearch = ({
+    findedUsers,
+    onSearch,
+    alreadySearched = [],
+    onAdd,
+    open,
+}) => {
+    const [finded, setFinded] = useState([])
+    const [input, setInput] = useState(null)
+
+    useEffect(() => {
+        let timeout
+        if (input !== null) {
+            timeout = setTimeout(() => {
+                onSearch(input)
+            }, 500)
+        }
+        return () => {
+            clearTimeout(timeout)
+        }
+    }, [input])
+
+    useEffect(() => {
+        setFinded(findedUsers)
+    }, [findedUsers])
+
+    useEffect(() => {
+        setInput(null)
+        setFinded([])
+    }, [open])
+
+    return (
+        <Box className="userSearchBox">
+            <Box className="searchField">
+                <SearchBlock setInput={setInput} text={'Найти пользователя'} />
+            </Box>
+
+            <Box className="usersBox">
+                <List className="usersList">
+                    {finded.map((user) => (
+                        <UserCard
+                            key={user._id}
+                            user={user}
+                            render={AddBtn}
+                            onAction={() => onAdd(user)}
+                            disabled={
+                                !!alreadySearched.find(
+                                    (searchedUser) =>
+                                        searchedUser._id === user._id
+                                )
+                            }
+                        />
+                    ))}
+                </List>
+            </Box>
+        </Box>
+    )
+}
+export const CUserSearch = connect(
+    (state) => ({ findedUsers: state.promise.findUsers?.payload || [] }),
+    { onSearch: actionFindUsers }
+)(UserSearch)

+ 17 - 0
src/components/UserSearch/UserSearch.scss

@@ -0,0 +1,17 @@
+.userSearchBox {
+    flex-basis: 45%;
+
+    .searchField {
+        height: 40px;
+    }
+
+    .usersBox {
+        height: calc(100% - 40px);
+        overflow-y: auto;
+
+        .usersList {
+            max-width: 100%;
+            background-color: #fff;
+        }
+    }
+}

+ 3 - 0
src/components/UserSearch/index.js

@@ -0,0 +1,3 @@
+import { CUserSearch } from './UserSearch'
+
+export { CUserSearch }

src/components/MediaModal.jsx → src/components/_unused/ContactsModal.jsx


src/reducers/contactsReducer.js → src/components/_unused/MediaModal.jsx


+ 28 - 0
src/components/_unused/Preload.jsx

@@ -0,0 +1,28 @@
+import React from 'react'
+import { connect } from 'react-redux'
+import { CircularProgress } from '@mui/material'
+
+const Preloader = () => <CircularProgress />
+
+const RejectedAlert = ({ error }) => <div>Ошибка {error}</div>
+
+const Preloaded = ({ promiseName, promiseState, children }) => (
+    <>
+        {promiseState[promiseName]?.status === 'RESOLVED' ? (
+            children
+        ) : promiseState[promiseName]?.status === 'REJECTED' ? (
+            <RejectedAlert error={promiseState[promiseName]?.error} />
+        ) : (
+            <Preloader />
+        )}
+    </>
+)
+export const CPreloaded = connect((state) => ({ promiseState: state.promise }))(
+    Preloaded
+)
+
+{
+    /* <CPreloaded promiseName={}>
+  <CCategoryById />
+</CPreloaded>  */
+}

+ 0 - 0
src/components/_unused/UserModal.jsx


+ 32 - 31
src/components/index.js

@@ -1,34 +1,35 @@
 // универсальные компоненты, повторяющиеся на многих страницах
-import {CChatList} from './ChatList'
-import {CMsgList} from './MsgList'
-import {CMsg} from './Msg'
-import {CPreloaded} from './Preload'
-import { MenuDrawer} from './MainMenu'
-import {Header} from './Header'
-import {SearchBlock} from './SearchBlock'
-import {FloatBtn} from './FloatBtn'
-import {ChatAvatar, UserAvatar, CMyAvatar} from './Avatar'
-import {CProfileModal} from './ProfileModal'
-import {CChatModal} from './ChatModal'
-import {CUserSearch} from './UserSearch'
-import {UserCard} from './UserCard'
-import {CSendingField} from './SendingField'
-import {CChatMngHeader} from './ChatMngHeader'
 
+import { CChat } from './Chat'
+import { CChatList } from './ChatList'
+import { CMsgList } from './MsgList'
+import { CMsg } from './Msg'
+import { CPreloaded } from './_unused/Preload'
+import { MenuDrawer } from './MainMenu'
+import { Header } from './Header'
+import { SearchBlock } from './SearchBlock'
+import { FloatBtn } from './FloatBtn'
+import { ChatAvatar, UserAvatar, CMyAvatar } from './Avatar'
+import { CProfileModal } from './ProfileModal'
+import { CChatModal } from './ChatModal'
+import { CUserSearch } from './UserSearch'
+import { UserCard } from './UserCard'
+import { CSendingField } from './SendingField'
+import { CChatMngHeader } from './ChatMngHeader'
 
-
-export {CChatList}
-export {CMsgList} 
-export {CMsg} 
-export {CPreloaded} 
-export { MenuDrawer} 
-export {Header}  
-export {SearchBlock}  
-export {FloatBtn}  
-export {ChatAvatar, UserAvatar, CMyAvatar} 
-export {CProfileModal} 
-export {CChatModal} 
-export {CUserSearch} 
-export {UserCard} 
-export {CSendingField}  
-export {CChatMngHeader}  
+export { CChat }
+export { CChatList }
+export { CMsgList }
+export { CMsg }
+export { CPreloaded }
+export { MenuDrawer }
+export { Header }
+export { SearchBlock }
+export { FloatBtn }
+export { ChatAvatar, UserAvatar, CMyAvatar }
+export { CProfileModal }
+export { CChatModal }
+export { CUserSearch }
+export { UserCard }
+export { CSendingField }
+export { CChatMngHeader }

+ 21 - 0
src/constants.js

@@ -0,0 +1,21 @@
+export const backURL = 'http://chat.fs.a-level.com.ua/'
+
+export const months = [
+    '01',
+    '02',
+    '03',
+    '04',
+    '05',
+    '06',
+    '07',
+    '08',
+    '09',
+    '10',
+    '11',
+    '12',
+]
+
+export const linkRegEx =
+    /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/
+export const youtubeRegEx =
+    /http(?:s?):\/\/(?:www\.)?youtu(?:be\.com\/watch\?v=|\.be\/)([\w\-\_]*)(&(amp;)?‌​[\w\?‌​=]*)?/

+ 27 - 19
src/helpers/dateFuncs.js

@@ -1,21 +1,29 @@
-
-
-
+import { months } from '../constants'
 
 export function dateFromStamp(createdAt) {
-   let date = new Date(Number(createdAt))
-   // console.log(createdAt, date)
-   let dayDate = (date.getDate() < 10) ? '0' + date.getDate() : date.getDate() 
-   let yearDate = date.getFullYear() 
-
-   let monthDate = date.getMonth()
-   let months = ['01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12']
-   let curMonth = months[monthDate] 
-
-   let hours = (date.getHours() < 10) ? '0' + date.getHours()  : date.getHours() 
-   let minutes = (date.getMinutes() < 10) ? '0' + date.getMinutes()  : date.getMinutes()
-
-   let dateInFormat = hours + ':' + minutes + ' - ' + dayDate + '.' + curMonth + '.' +  yearDate
-
-   return dateInFormat
-}
+    const date = new Date(Number(createdAt))
+    // console.log(createdAt, date)
+    const dayDate = date.getDate() < 10 ? '0' + date.getDate() : date.getDate()
+    const yearDate = date.getFullYear()
+
+    const monthDate = date.getMonth()
+
+    const curMonth = months[monthDate]
+
+    const hours = date.getHours() < 10 ? '0' + date.getHours() : date.getHours()
+    const minutes =
+        date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes()
+
+    const dateInFormat =
+        hours +
+        ':' +
+        minutes +
+        ' - ' +
+        dayDate +
+        '.' +
+        curMonth +
+        '.' +
+        yearDate
+
+    return dateInFormat
+}

+ 42 - 40
src/helpers/decorateLinks.js

@@ -1,43 +1,45 @@
+import React from 'react'
+import { linkRegEx, youtubeRegEx } from '../constants'
 
+export const decorateLinks = (text) => {
+    const matchLink = text.match(linkRegEx)
+    const matchYoutube = text.match(youtubeRegEx)
 
-export const decorateLinks = (text, style) => {
+    if (matchLink) {
+        const [link] = matchLink
+        if (link) {
+            const newLink = (
+                <a href={link} style="color:blue">
+                    {link}
+                </a>
+            )
+            let newText = null
+            newText = text.replace(link, newLink)
+            if (matchYoutube) {
+                const [, youtubeKey] = matchYoutube
 
-   const linkRegEx = /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/
-   const youtubeRegEx = /http(?:s?):\/\/(?:www\.)?youtu(?:be\.com\/watch\?v=|\.be\/)([\w\-\_]*)(&(amp;)?‌​[\w\?‌​=]*)?/
-
-   let matchLink = text.match(linkRegEx)
-   let matchYoutube = text.match(youtubeRegEx)
-
-   if (matchLink) {
-       let [link] = matchLink
-       if (link) {
-           let newLink = <a href={link} style="color:blue">{link}</a>
-           let newText = null
-           newText = text.replace(link, newLink)
-           if (matchYoutube) {
-               let [, youtubeKey] = matchYoutube
-   
-               if (youtubeKey) {
-                   return (
-                     <>
-                        <pre>{newText}</pre> <br/> <iframe width="560" height="315" 
-                        src="https://www.youtube.com/embed/${youtubeKey}" title="YouTube video player" 
-                        frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; 
-                        picture-in-picture" allowfullscreen></iframe>
-                     </>
-                   )
-
-               } 
-           } else {
-               return (
-                  <pre>{newText}</pre>
-               ) 
-           }
-       }
-
-   } else {
-       return (
-         <pre>{text}</pre>
-       ) 
-   }
-}
+                if (youtubeKey) {
+                    return (
+                        <>
+                            <pre>{newText}</pre> <br />{' '}
+                            <iframe
+                                width="560"
+                                height="315"
+                                src="https://www.youtube.com/embed/${youtubeKey}"
+                                title="YouTube video player"
+                                frameBorder="0"
+                                allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; 
+                        picture-in-picture"
+                                allowFullScreen
+                            ></iframe>
+                        </>
+                    )
+                }
+            } else {
+                return <pre>{newText}</pre>
+            }
+        }
+    } else {
+        return <pre>{text}</pre>
+    }
+}

+ 21 - 21
src/helpers/getGql.js

@@ -1,24 +1,24 @@
+import { backURL } from '../constants'
 
+const getGQL =
+    (url) =>
+    async (query, variables = {}) => {
+        const incomingData = await fetch(url, {
+            method: 'POST',
+            headers: {
+                'Content-Type': 'application/json',
+                ...(localStorage.authToken
+                    ? { Authorization: 'Bearer ' + localStorage.authToken }
+                    : {}),
+            },
+            body: JSON.stringify({ query, variables }),
+        })
+        const obj = await incomingData.json()
+        if (!obj.data && obj.errors) {
+            throw new Error(JSON.stringify(obj.errors))
+        } else {
+            return obj.data[Object.keys(obj.data)[0]]
+        }
+    }
 
-const getGQL = url => (
-   async (query, variables={}) => {
-       let obj = await fetch(url, {
-         method: 'POST',
-         headers: {
-           "Content-Type": "application/json",
-           ...(localStorage.authToken ? {Authorization: "Bearer " + localStorage.authToken} : {})
-         },
-         body: JSON.stringify({ query, variables })
-       })
-       let a = await obj.json()
-       if (!a.data && a.errors) {
-           throw new Error(JSON.stringify(a.errors))
-       } else {
-           return a.data[Object.keys(a.data)[0]]
-       }      
-   }
- )
- 
-export const backURL = 'http://chat.fs.a-level.com.ua/'
 export const gql = getGQL(backURL + 'graphql')
- 

+ 14 - 13
src/helpers/index.js

@@ -1,15 +1,16 @@
 // вспомогательные функции
-import {backURL, gql} from './getGql'
-import {printStrReq, printEnding} from './printStrReq'
-import {passReq} from './passReq'
-import {dateFromStamp} from './dateFuncs'
-import {stringColor} from './stringColorFuncs'
-import {decorateLinks} from './decorateLinks'
-
-export {backURL, gql} 
-export {printStrReq, printEnding} 
-export {passReq} 
-export {dateFromStamp} 
-export {stringColor} 
-export {decorateLinks} 
+import { gql } from './getGql'
+import { printStrReq, printEnding } from './printStrReq'
+import { passReq } from './passReq'
+import { dateFromStamp } from './dateFuncs'
+import { stringColor } from './stringColorFuncs'
+import { decorateLinks } from './decorateLinks'
+import { sortMediaArr } from './sortMediaArr'
 
+export { gql }
+export { printStrReq, printEnding }
+export { passReq }
+export { dateFromStamp }
+export { stringColor }
+export { decorateLinks }
+export { sortMediaArr }

+ 38 - 40
src/helpers/passReq.js

@@ -1,46 +1,44 @@
-
 import { printStrReq } from '.'
 
 export const passReq = {
+    minPass: '2',
+    char: true,
+    bigChar: true,
+    number: true,
 
-   minPass:'2', 
-   char:true, 
-   bigChar:true, 
-   number:true,
-
-   checkPass(password) {
-      if (  password.length >= this.minPass && 
-            !password.match(/\s/) && 
-            (this.char ? password.match(/[a-zа-яё]/) : true) && 
-            (this.bigChar ? password.match(/[A-ZА-ЯЁ]/) : true) && 
-            (this.number ? password.match(/[\d]/) : true) ) {
-      return true
-      } else {
-         return false
-      }
-   },
-   
-   printPassReq(password) {
-      let str = ''
-      if (this.checkPass(password)) {
-         str += 'Пароль подходит '
-      } else if (password.match(/\s/)) {
-         str += 'Пароль не должен содержать пробелы '
-      } else {
-         str += 'Пароль должен содержать: '
-         str += printStrReq(password, this.minPass) + ','
-         if (!(this.char ? password.match(/[a-zа-яё]/) : true)) {
-            str += ' строчные буквы,'
-         }
-         if (!(this.bigChar ? password.match(/[A-ZА-ЯЁ]/) : true)) {
-            str += ' прописные буквы,'
-         }
-         if  (!(this.number ? password.match(/[\d]/) : true)) {
-            str += ' цифры,'
-         }
-      }
-      return str.slice(0, -1)
-   }
+    checkPass(password) {
+        if (
+            password.length >= this.minPass &&
+            !password.match(/\s/) &&
+            (this.char ? password.match(/[a-zа-яё]/) : true) &&
+            (this.bigChar ? password.match(/[A-ZА-ЯЁ]/) : true) &&
+            (this.number ? password.match(/[\d]/) : true)
+        ) {
+            return true
+        } else {
+            return false
+        }
+    },
 
+    printPassReq(password) {
+        let str = ''
+        if (this.checkPass(password)) {
+            str += 'Пароль подходит '
+        } else if (password.match(/\s/)) {
+            str += 'Пароль не должен содержать пробелы '
+        } else {
+            str += 'Пароль должен содержать: '
+            str += printStrReq(password, this.minPass) + ','
+            if (!(this.char ? password.match(/[a-zа-яё]/) : true)) {
+                str += ' строчные буквы,'
+            }
+            if (!(this.bigChar ? password.match(/[A-ZА-ЯЁ]/) : true)) {
+                str += ' прописные буквы,'
+            }
+            if (!(this.number ? password.match(/[\d]/) : true)) {
+                str += ' цифры,'
+            }
+        }
+        return str.slice(0, -1)
+    },
 }
-

+ 14 - 13
src/helpers/printStrReq.js

@@ -1,17 +1,18 @@
-
-
-
 export const printEnding = (amount) => {
-   return (((amount[amount.length - 1] == 1) && (amount[amount.length - 2] != 1)) ? '' :
-               ((amount[amount.length - 1] > 1) && (amount[amount.length - 1] < 5) && (amount[amount.length - 2] != 1)) ? 'а' :
-                  'ов')
+    return amount[amount.length - 1] == 1 && amount[amount.length - 2] != 1
+        ? ''
+        : amount[amount.length - 1] > 1 &&
+          amount[amount.length - 1] < 5 &&
+          amount[amount.length - 2] != 1
+        ? 'а'
+        : 'ов'
 }
 
-
 export const printStrReq = (str, amount) => {
-   if (str === undefined) {
-      return ''
-   } 
-   return (str.length >= amount) ? "" : 
-   ` минимум ${amount} символ${ printEnding(amount) }`
-}
+    if (str === undefined) {
+        return ''
+    }
+    return str.length >= amount
+        ? ''
+        : ` минимум ${amount} символ${printEnding(amount)}`
+}

+ 16 - 0
src/helpers/sortMediaArr.js

@@ -0,0 +1,16 @@
+export function sortMediaArr(media) {
+    const allMedia = {}
+    if (media)
+        for (const file of media) {
+            if (file.type) {
+                const [objName] = file.type.split('/') || []
+
+                if (Object.prototype.hasOwnProperty.call(allMedia, objName)) {
+                    allMedia[objName].push(file)
+                } else {
+                    allMedia[objName] = [file]
+                }
+            }
+        }
+    return allMedia
+}

+ 39 - 43
src/helpers/stringColorFuncs.js

@@ -1,49 +1,45 @@
-
-
 export const stringColor = {
-
-   stringToColor(string) {
-      if (!string) {
-        // return '#' + Math.floor(Math.random()*16777215).toString(16);
-        return '#aaa'
-      }
-      let hash = 0;    
-      for (let i = 0; i < string?.length; i += 1) {
-        hash = string.charCodeAt(i) + ((hash << 5) - hash);
-      }    
-      let color = '#'; 
-      for (let i = 0; i < 3; i += 1) {
-        const value = (hash >> (i * 8)) & 0xff;
-        color += `00${value.toString(16)}`.substr(-2);
-      }   
-      return color;
+    stringToColor(string) {
+        if (!string) {
+            // return '#' + Math.floor(Math.random()*16777215).toString(16);
+            return '#aaa'
+        }
+        let hash = 0
+        for (let i = 0; i < string?.length; i += 1) {
+            hash = string.charCodeAt(i) + ((hash << 5) - hash)
+        }
+        let color = '#'
+        for (let i = 0; i < 3; i += 1) {
+            const value = (hash >> (i * 8)) & 0xff
+            color += `00${value.toString(16)}`.substr(-2)
+        }
+        return color
     },
-    
+
     stringSplit(str) {
-      if (!str) {
-        return 
-      }
-      let titleStr = ''
-      let letterAmount = 2
-      for (const word of str.split(' ')) {
-        if (letterAmount <= 0) {
-          break
+        if (!str) {
+            return
+        }
+        let titleStr = ''
+        let letterAmount = 2
+        for (const word of str.split(' ')) {
+            if (letterAmount <= 0) {
+                break
+            }
+            letterAmount--
+            if (word) {
+                titleStr += word[0].toUpperCase()
+            }
         }
-        letterAmount--
-        if (word) {
-          titleStr += word[0].toUpperCase()
-        }  
-      }
-      return titleStr
+        return titleStr
     },
-    
-    stringAvatar(name) { 
-      return {
-        sx: {
-          bgcolor: this.stringToColor(name),
-        },
-        children: this.stringSplit(name)
-      };
-    }
-}
 
+    stringAvatar(name) {
+        return {
+            sx: {
+                bgcolor: this.stringToColor(name),
+            },
+            children: this.stringSplit(name),
+        }
+    },
+}

+ 15 - 19
src/index.js

@@ -1,27 +1,23 @@
-import React from 'react';
-import ReactDOM from 'react-dom';
-import './index.scss';
-import App from './App';
-import reportWebVitals from './reportWebVitals';
+import React from 'react'
+import ReactDOM from 'react-dom'
+import './index.scss'
+import App from './App'
+import reportWebVitals from './reportWebVitals'
 
-import '@fontsource/roboto/300.css';
-import '@fontsource/roboto/400.css';
-import '@fontsource/roboto/500.css';
-import '@fontsource/roboto/700.css';
+import '@fontsource/roboto/300.css'
+import '@fontsource/roboto/400.css'
+import '@fontsource/roboto/500.css'
+import '@fontsource/roboto/700.css'
 
 // import { AccessAlarm, ThreeDRotation } from '@mui/icons-material';
 
-ReactDOM.render(
- 
-    <App />
-  ,
-  document.getElementById('root')
-);
+ReactDOM.render(<App />, document.getElementById('root'))
 
-
-{/* <React.StrictMode>
-</React.StrictMode> */}
+{
+    /* <React.StrictMode>
+</React.StrictMode> */
+}
 // If you want to start measuring performance in your app, pass a function
 // to log results (for example: reportWebVitals(console.log))
 // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
-reportWebVitals();
+reportWebVitals()

+ 18 - 18
src/index.scss

@@ -1,25 +1,25 @@
 body {
-  margin: 0;
-  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
-    'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
-    sans-serif;
-  -webkit-font-smoothing: antialiased;
-  -moz-osx-font-smoothing: grayscale;
+    margin: 0;
+    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto',
+        'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans',
+        'Helvetica Neue', sans-serif;
+    -webkit-font-smoothing: antialiased;
+    -moz-osx-font-smoothing: grayscale;
 }
 
 code {
-  font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
-    monospace;
+    font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
+        monospace;
 }
 
 pre {
-  margin: 0;
-  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
-  'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
-  sans-serif;
-  word-wrap: break-word;
-  white-space: pre-wrap;
-  white-space: -moz-pre-wrap; 
-  white-space: -pre-wrap;     
-  white-space: -o-pre-wrap;
-}
+    margin: 0;
+    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto',
+        'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans',
+        'Helvetica Neue', sans-serif;
+    word-wrap: break-word;
+    white-space: pre-wrap;
+    white-space: -moz-pre-wrap;
+    white-space: -pre-wrap;
+    white-space: -o-pre-wrap;
+}

+ 0 - 0
src/pages/AsidePage.jsx


Some files were not shown because too many files changed in this diff