Переглянути джерело

newChat+ava+members sendMsg

sheva77 3 роки тому
батько
коміт
6c961bca39

+ 33 - 0
111.html

@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<html lang="en">
+    <head>
+        <meta charset="UTF-8" />
+        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
+        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+        <title>Document</title>
+    </head>
+    <body>
+        <input type="file" onchange="previewFile()" /><br />
+        <img src="" height="200" alt="Image preview..." />
+        <!-- // -->
+        <!--  -->
+        <script>
+            function previewFile() {
+                var preview = document.querySelector("img");
+                var file = document.querySelector("input[type=file]").files[0];
+                var reader = new FileReader();
+                console.log(preview, file, reader);
+
+                reader.onloadend = function () {
+                    preview.src = reader.result;
+                };
+
+                if (file) {
+                    reader.readAsDataURL(file);
+                } else {
+                    preview.src = "";
+                }
+            }
+        </script>
+    </body>
+</html>

+ 90 - 0
chat_final_bootstrap/src/Actions/ActionsGqlUpsert.js

@@ -0,0 +1,90 @@
+//
+//
+import { urlUploadConst } from "../const";
+import { actionPromise } from "../Reducers";
+import { gql } from "../Actions";
+
+export const actionMessageUpsert = ({ text, chatId }) => async (dispatch) => {
+    console.log("actionMessageUpsert --- ", text, chatId);
+
+    let msgData = await dispatch(
+        actionPromise(
+            "MessageUpsert",
+            gql(
+                `mutation MessageUpsert($messageData:MessageInput){
+                    MessageUpsert(message: $messageData){
+                        _id
+                    }
+                }`,
+                { messageData: { text, chat: { _id: chatId } } }
+            )
+        )
+    );
+
+    console.log("MessageUpsert - ", msgData);
+
+    if (msgData && msgData.data && msgData.data.MessageUpsert && msgData.data.MessageUpsert._id) {
+        console.log("MessageUpsert - ", msgData);
+    }
+};
+
+const actionMediaUpsert = ({ chatId, mediaId }) => async (dispatch) => {
+    let mediaData = await dispatch(
+        actionPromise(
+            "MediaUpsert",
+            gql(
+                `mutation med($mediaData:MediaInput){
+                    MediaUpsert(media: $mediaData){
+                        _id
+                        owner{login}
+                        text
+                        url
+                        type
+                        userAvatar{login}
+                        chatAvatars{title}
+                    }
+                }`,
+                { mediaData: { _id: mediaId, chatAvatars: [{ _id: chatId }] } }
+            )
+        )
+    );
+};
+
+export const actionCreateNewChat = ({ title, members, avaFile }) => async (dispatch) => {
+    members = members.map((mem) => ({ _id: mem._id }));
+
+    let chatData = await dispatch(
+        actionPromise(
+            "ChatUpsert",
+            gql(
+                `mutation ChatUpsert($newChat:ChatInput){
+                    ChatUpsert(chat:$newChat){
+                        _id
+                        title
+                        owner{login}
+                        members{login}
+                    }
+                }`,
+                { newChat: { title, members } }
+            )
+        )
+    );
+
+    if (avaFile) {
+        let dataSingl = new FormData();
+        dataSingl.set("media", avaFile);
+        let avaUploadResult = await fetch(`${urlUploadConst}/upload`, {
+            method: "POST",
+            headers: localStorage.authToken ? { Authorization: "Bearer " + localStorage.authToken } : {},
+            body: dataSingl,
+        }).then((res) => res.json());
+
+        dispatch(actionMediaUpsert({ chatId: chatData.data.ChatUpsert._id, mediaId: avaUploadResult._id }));
+    }
+
+    // if (userData && userData.data.UserFindOne) {
+    //     dispatch(actionAuthInfo(userData.data.UserFindOne));
+    // } else {
+    //     console.log("UserFindOne - ошибка");
+    // }
+};

+ 0 - 3
chat_final_bootstrap/src/Actions/ActionsUser.js

@@ -1,3 +0,0 @@
-//
-//
-//

+ 3 - 0
chat_final_bootstrap/src/Actions/index.js

@@ -8,6 +8,7 @@ import {
 } from "./ActionsGql";
 import { actionMsgNewChat, actionCurChatId, actionMsgInsertInHead } from "./ActionsMsg";
 import { actionAddUserToChatList, actionDelUserFromChatList, actionNewChatList } from "./ActionsChatUsers";
+import { actionCreateNewChat, actionMessageUpsert } from "./ActionsGqlUpsert";
 
 export {
     actionUserInfo,
@@ -26,4 +27,6 @@ export {
     actionAddUserToChatList,
     actionNewChatList,
     actionAllUsersFind,
+    actionCreateNewChat,
+    actionMessageUpsert,
 };

+ 25 - 9
chat_final_bootstrap/src/App.scss

@@ -65,23 +65,34 @@ body {
         height: 48px;
     }
 }
-
+//
+//
+//
+//
+//
+//
+//
+.pageMain_ChatMessages {
+    max-height: calc(100vh - 250px);
+    min-height: calc(100vh - 250px);
+}
 .MessagesList {
-    max-height: calc(100vh - 350px);
+    max-height: calc(100vh - 330px);
+    min-height: calc(100vh - 330px);
+    // height: auto;
     overflow: auto;
 }
 
+.MessageInput {
+    max-height: 135px;
+    min-height: 135px;
+    background-color: darkslategray;
+}
+
 .fonRomaska {
     background: url("./images/romashki_1920x1080.jpg");
 }
 
-// .Messages {
-
-//     input {
-//         width: 70%;
-//     }
-// }
-
 .messageItem {
     // width: 75%;
     width: calc(100% - 120px);
@@ -113,6 +124,11 @@ body {
     }
 }
 
+.messagesArea {
+    max-height: calc(100vh - 20px);
+    background-color: red;
+}
+
 .ChatDashBoard {
     min-height: calc(100vh - 110px);
     padding: 20px;

+ 1 - 1
chat_final_bootstrap/src/Components/AdditionalTools.js

@@ -32,7 +32,7 @@ export const AdditionalTools = ({ _userId, onSearch = null }) => {
                     history.push("/newchat");
                 }}
             >
-                New Chat
+                <i class="bi bi-chat-left-text"></i> New Chat
             </Button>
             {/* <Button className="gradient" variant="secondary btn-sm  m-2">
                 Join to another chat

+ 1 - 1
chat_final_bootstrap/src/Components/ButtonLogout.js

@@ -4,7 +4,7 @@ import { Button } from "react-bootstrap";
 
 const ButtonLogout = ({ onLogout, isLoggedIn }) => (
     <Button className="gradient" variant="secondary btn-sm" onClick={onLogout} disabled={!isLoggedIn}>
-        Logout
+        <i class="bi bi-door-open"></i> Logout
     </Button>
 );
 export const CButtonLogout = connect((s) => ({ isLoggedIn: s.auth.login }), { onLogout: actionAuthLogout })(

+ 36 - 29
chat_final_bootstrap/src/Components/ChatContain.js

@@ -145,7 +145,9 @@ const CMessagesList = connect(
     { getMoreMessages: actionSearchMessagesByChatId }
 )(MessagesList);
 
-const Messages = ({ _id = "", messages, getMsg }) => {
+const Messages = ({ _id = "", chatInfo, messages, getMsg }) => {
+    if (chatInfo) chatInfo = chatInfo.filter((chat) => chat._id === _id);
+
     // id чата,
 
     // console.log("Messages - id - ", _id);
@@ -154,8 +156,8 @@ const Messages = ({ _id = "", messages, getMsg }) => {
     //     !(messages && messages[_id] && messages[_id][0] && messages[_id][0].chat && messages[_id][0].chat._id)
     // );
 
-    let avatar = messages && messages[_id] && messages[_id][0] && messages[_id][0].chat && messages[_id][0].chat.avatar;
-    let title = messages && messages[_id] && messages[_id][0] && messages[_id][0].chat && messages[_id][0].chat.title;
+    let avatar = chatInfo && chatInfo[0] && chatInfo[0].avatar && chatInfo[0].avatar.url;
+    let title = chatInfo && chatInfo[0] && chatInfo[0].title;
 
     useEffect(() => {
         if (
@@ -168,34 +170,39 @@ const Messages = ({ _id = "", messages, getMsg }) => {
     }, [_id]);
 
     return (
-        <div>
-            <div className="position-relative  mb-3 border-bottom border-2 border-success ">
-                <div className="d-flex justify-content-start align-items-center">
-                    <div className="avatarka">
-                        {avatar && avatar.url ? (
-                            <img src={`${urlUploadConst}/${avatar.url}`}></img>
-                        ) : (
-                            <div className="d-flex justify-content-center align-items-center bg-success border border-2 border-success gradient">
-                                <div className="fs-5 text-light fw-bolder">
-                                    {title &&
-                                        `${title.split(" ")[0][0].toUpperCase()}` +
-                                            `${
-                                                (title.split(" ").slice(1).pop() &&
-                                                    title.split(" ").slice(1).pop()[0].toUpperCase()) ||
-                                                ""
-                                            }`}
+        <div className="mb-2">
+            {chatInfo && chatInfo[0] && (
+                <div className="position-relative  mb-3 border-bottom border-2 border-success ">
+                    <div className="d-flex justify-content-start align-items-center">
+                        <div className="avatarka">
+                            {avatar ? (
+                                <img
+                                    src={`${urlUploadConst}/${avatar}`}
+                                    className=" border border-2 border-success"
+                                ></img>
+                            ) : (
+                                <div className="d-flex justify-content-center align-items-center bg-success border border-2 border-success gradient">
+                                    <div className="fs-5 text-light fw-bolder">
+                                        {title &&
+                                            `${title.split(" ")[0][0].toUpperCase()}` +
+                                                `${
+                                                    (title.split(" ").slice(1).pop() &&
+                                                        title.split(" ").slice(1).pop()[0].toUpperCase()) ||
+                                                    ""
+                                                }`}
+                                    </div>
                                 </div>
-                            </div>
-                        )}
+                            )}
+                        </div>
+
+                        <div className="fs-4 fw-bolder ms-2">{`${title}`}</div>
                     </div>
 
-                    <div className="fs-4 fw-bolder ms-2">{`${title}`}</div>
+                    <span className="position-absolute bottom-0 end-0  badge rounded-pill bg-secondary">
+                        {` _chatId: ${_id}`} <span className="visually-hidden">id чата</span>
+                    </span>
                 </div>
-
-                <span className="position-absolute bottom-0 end-0  badge rounded-pill bg-secondary">
-                    {` _chatId: ${_id}`} <span className="visually-hidden">id чата</span>
-                </span>
-            </div>
+            )}
             <div>
                 <CMessagesList />
             </div>
@@ -203,9 +210,9 @@ const Messages = ({ _id = "", messages, getMsg }) => {
     );
 };
 
-const CMessages = connect((s) => ({ _id: s.curChatId.curChatId, messages: s.msg }), {
+const CMessages = connect((s) => ({ _id: s.curChatId.curChatId, messages: s.msg, chatInfo: s.auth.chats }), {
     getMsg: actionGetMessagesByChatId,
 })(Messages);
 // - id chata
 
-export const ChatContain = ({ _chatId = "" }) => <div>{!!_chatId && <CMessages />}</div>;
+export const ChatMessages = ({ _chatId = "" }) => <div>{!!_chatId && <CMessages />}</div>;

+ 7 - 4
chat_final_bootstrap/src/Components/ChatsList.js

@@ -28,15 +28,18 @@ const ChatItem = ({ _id = "", avatar, title, messages, userId, currentChatId })
                     <div className="d-flex justify-content-start align-items-center">
                         <div className="avatarka ">
                             {avatar && avatar.url ? (
-                                <img src={`${urlUploadConst}/${avatar.url}`}></img>
+                                <img
+                                    src={`${urlUploadConst}/${avatar.url}`}
+                                    className=" border border-2 border-success"
+                                ></img>
                             ) : (
                                 <div className="d-flex justify-content-center align-items-center bg-success border border-2 border-success gradient">
                                     <div className="fs-5 text-light fw-bolder">
                                         {title &&
-                                            `${title.split(" ")[0][0].toUpperCase()}` +
+                                            `${title.trim().split(" ")[0][0].toUpperCase()}` +
                                                 `${
-                                                    (title.split(" ").slice(1).pop() &&
-                                                        title.split(" ").slice(1).pop()[0].toUpperCase()) ||
+                                                    (title.trim().split(" ").slice(1).pop() &&
+                                                        title.trim().split(" ").slice(1).pop()[0].toUpperCase()) ||
                                                     ""
                                                 }`}
                                     </div>

+ 8 - 8
chat_final_bootstrap/src/Components/LoginInfo.js

@@ -5,20 +5,20 @@ import { actionUserInfo } from "../Actions";
 
 const LoginInfo = ({ login, nick, _id, getUserInfo }) => {
     useEffect(() => {
-        getUserInfo(_id);
-    }, [_id]);
-
-    if (!nick) {
-        nick = login;
-    }
+        // здесь нужен именно undefined, так как из базы может прийти null или ""
+        // undefined - значит еще не было инфы из базы
+        if (nick === undefined) {
+            getUserInfo(_id);
+        }
+    }, []);
 
     return (
         <span className="mx-2 text-white ">
             {login ? (
                 <span className="text-nowrap">
-                    <span className="text-nowrap"> {`${login}`} </span>
+                    <span className="text-nowrap"> {`${nick ? nick : login}`} </span>
                     {/* {" / "}
-                    <span className="text-nowrap">{`Nick: ${nick}`}</span>
+                    <span className="text-nowrap">{`Login: ${login}`}</span>
                     <br />
                     <span className="text-nowrap">{`_id: ${_id}`}</span> */}
                 </span>

+ 176 - 0
chat_final_bootstrap/src/Components/NewChatDashBoard.js

@@ -0,0 +1,176 @@
+import { ButtonToMain } from "../Components";
+import { urlUploadConst } from "../const";
+import { actionDelUserFromChatList, actionCreateNewChat } from "../Actions";
+import { connect } from "react-redux";
+import { useEffect } from "react";
+import { useState } from "react";
+import { store } from "../Reducers";
+import { useRef } from "react";
+import history from "../history";
+import { CUserInfo } from "../Layout";
+import { CAllUsersList } from "../Layout";
+import { useDropzone } from "react-dropzone";
+import logo from "../images/logo23.png";
+import chat_square_text from "../icons/chat-square-text.svg";
+import userEvent from "@testing-library/user-event";
+
+const MemberItem = ({ member, myId, dellFromList = null }) => {
+    return (
+        <span className="fs-5">
+            <span className="badge bg-success  m-1 rounded-pill gradient">
+                {member.nick || member.login}
+
+                <button
+                    type="button"
+                    className="btn-close ms-2 text-light "
+                    aria-label="Close"
+                    disabled={member._id === myId}
+                    onClick={() => {
+                        if (typeof dellFromList === "function") dellFromList(member._id);
+                    }}
+                ></button>
+            </span>
+        </span>
+    );
+};
+
+const CMemberItem = connect(
+    (s, { member }) => ({
+        member,
+        myId: s.auth.payloadId,
+    }),
+    { dellFromList: actionDelUserFromChatList }
+)(MemberItem);
+
+const NewChatDashBoard = ({ members = {}, createNewChat = null }) => {
+    const [chatMembers, setChatMembers] = useState(Object.values(members));
+    const [inpTitle, setInpTitle] = useState("");
+    const uploadRef = useRef(null);
+    const uploadImgRef = useRef(null);
+    const [srcAva, setSrcAva] = useState("");
+
+    // console.log(members);
+
+    useEffect(() => {
+        setChatMembers(Object.values(members));
+    }, [members]);
+
+    const doCreateNewChat = () => {
+        createNewChat({
+            title: inpTitle,
+            members: chatMembers,
+            avaFile: uploadRef.current && uploadRef.current.files && uploadRef.current.files[0],
+        });
+    };
+
+    function previewFile() {
+        let preview = uploadImgRef.current;
+        let file = uploadRef.current && uploadRef.current.files && uploadRef.current.files[0];
+        let reader = new FileReader();
+
+        reader.onloadend = function () {
+            setSrcAva(reader.result);
+        };
+
+        if (file) {
+            // console.log(file);
+            reader.readAsDataURL(file);
+        } else {
+            setSrcAva("");
+        }
+    }
+
+    return (
+        <>
+            <div className="ChatDashBoard bg-light">
+                <h4>ChatDashBoard</h4>
+                <table className="table table-bordered align-middle">
+                    <tbody>
+                        <tr>
+                            <td className="fw-bolder">Chat title:</td>
+                            <td>
+                                <input
+                                    className="form-control mb-2 p-2 border border-success border-2"
+                                    placeholder="Input title for new chat"
+                                    onChange={(e) => setInpTitle(e.target.value)}
+                                ></input>
+                            </td>
+                        </tr>
+                        <tr>
+                            <td>Chat avatar:</td>
+                            <td>
+                                <div className="avatarka ">
+                                    {srcAva ? (
+                                        <img
+                                            // src={`${urlUploadConst}/${avatarUrl}`}
+                                            src={srcAva}
+                                            className="border border-2 border-success"
+                                        ></img>
+                                    ) : (
+                                        <div className="d-flex justify-content-center align-items-center bg-success border border-2 border-success gradient">
+                                            <div className="fs-5 text-light fw-bolder">
+                                                {!!inpTitle &&
+                                                    `${
+                                                        inpTitle.trim().split(" ")[0] &&
+                                                        inpTitle.trim().split(" ")[0][0] &&
+                                                        inpTitle.trim().split(" ")[0][0].toUpperCase()
+                                                    }` +
+                                                        `${
+                                                            (inpTitle.trim().split(" ").slice(1).pop() &&
+                                                                inpTitle.trim().split(" ").slice(1).pop()[0] &&
+                                                                inpTitle
+                                                                    .trim()
+                                                                    .split(" ")
+                                                                    .slice(1)
+                                                                    .pop()[0]
+                                                                    .toUpperCase()) ||
+                                                            ""
+                                                        }`}
+                                            </div>
+                                        </div>
+                                    )}
+                                </div>
+                                <input
+                                    type="file"
+                                    onChange={previewFile}
+                                    ref={uploadRef}
+                                    accept="image/*,image/jpeg"
+                                    className="form-control form-control-sm m-2"
+                                    aria-label="file example"
+                                />
+                                {/* <br />
+                                <img src={srcAva} height="200" alt="Image preview..." ref={uploadImgRef}></img> */}
+
+                                {/*  */}
+
+                                {/* <span className="avatarka">
+                                    <img
+                                        src={chat_square_text}
+                                        className="border border-2 border-success bg-light "
+                                        alt="Avatar"
+                                    />
+                                </span> */}
+                            </td>
+                        </tr>
+                        <tr>
+                            <td>Chat members:</td>
+                            <td>
+                                {chatMembers.map((member) => (
+                                    <CMemberItem key={member._id} member={member} />
+                                ))}
+                            </td>
+                        </tr>
+                    </tbody>
+                </table>
+                <button className="btn-success gradient rounded" disabled={!inpTitle} onClick={doCreateNewChat}>
+                    Create new chat
+                </button>
+                {!inpTitle && <span className="text-danger"> You should fill in the 'Chat title' field.</span>}
+            </div>
+        </>
+    );
+};
+
+export const CNewChatDashBoard = connect((s) => ({ members: s.newChatUsers }), { createNewChat: actionCreateNewChat })(
+    NewChatDashBoard
+);

+ 13 - 2
chat_final_bootstrap/src/Components/index.js

@@ -1,10 +1,21 @@
 import { CAdditionalTools } from "./AdditionalTools";
 import { ChatsList } from "./ChatsList";
-import { ChatContain } from "./ChatContain";
+import { ChatMessages } from "./ChatMessages";
 import { CButtonLogout } from "./ButtonLogout";
 import { CButtonUpload } from "./ButtonUpload";
 import { ButtonToMain } from "./ButtonToMain";
 import { CLoginInfo } from "./LoginInfo";
 import { Counter } from "./Counter";
+import { CNewChatDashBoard } from "./NewChatDashBoard";
 
-export { CAdditionalTools, ChatsList, ChatContain, CButtonLogout, CLoginInfo, CButtonUpload, ButtonToMain, Counter };
+export {
+    CAdditionalTools,
+    ChatsList,
+    ChatMessages,
+    CButtonLogout,
+    CLoginInfo,
+    CButtonUpload,
+    ButtonToMain,
+    Counter,
+    CNewChatDashBoard,
+};

+ 12 - 10
chat_final_bootstrap/src/Layout/AllUsersList.js

@@ -45,14 +45,13 @@ const AllUsersConst = [
     },
 ];
 
-const UserItem = ({ _id, login, nick, avatar, myId, addUserToList = null, delUserFromList = null }) => {
-    const [isSelected, setIsSelected] = useState(false);
+const UserItem = ({ _id, login, nick, avatar, myId, addUserToList = null, delUserFromList = null, newChatUsers }) => {
     const avatarUrl = avatar && avatar.url;
     nick = !!nick ? nick : login;
 
     const doSelectUser = () => {
         if (_id !== myId) {
-            if (isSelected) {
+            if (_id in newChatUsers) {
                 if (typeof addUserToList === "function") {
                     delUserFromList(_id);
                 }
@@ -61,7 +60,6 @@ const UserItem = ({ _id, login, nick, avatar, myId, addUserToList = null, delUse
                     addUserToList({ _id, login, nick, avatar });
                 }
             }
-            setIsSelected((prev) => !prev);
         }
     };
 
@@ -69,7 +67,7 @@ const UserItem = ({ _id, login, nick, avatar, myId, addUserToList = null, delUse
         <>
             <li
                 className={`list-group-item list-group-item-${
-                    isSelected || _id === myId ? "success" : "light"
+                    _id in newChatUsers || _id === myId ? "success" : "light"
                 } m-1 gradient shadow-sm border-2 `}
                 onClick={doSelectUser}
             >
@@ -110,12 +108,15 @@ const UserItem = ({ _id, login, nick, avatar, myId, addUserToList = null, delUse
     );
 };
 
-const CUserItem = connect(null, { delUserFromList: actionDelUserFromChatList, addUserToList: actionAddUserToChatList })(
-    UserItem
-);
+const CUserItem = connect(
+    (s) => ({
+        newChatUsers: s.newChatUsers,
+    }),
+    { delUserFromList: actionDelUserFromChatList, addUserToList: actionAddUserToChatList }
+)(UserItem);
 
 const AllUsersList = ({
-    searchUserResultArr = [],
+    searchUserResultArr,
     myId,
     myLogin,
     myNick,
@@ -127,6 +128,7 @@ const AllUsersList = ({
     //  FIXME: следующая строка - для отладки без инета
     // searchUserResultArr = AllUsersConst;
 
+    // создаем новый список пользователей нового чата со мной во главе
     useEffect(() => {
         if (typeof createNewChatList === "function")
             createNewChatList({ _id: myId, login: myLogin, nick: myNick, avatar: { url: myAvatarUrl } });
@@ -137,7 +139,7 @@ const AllUsersList = ({
     // если из базы ничего еще не пришло по поиску пользователей,
     // то посмотрим на всех, с кeм мы вообще уже общались в чатах
 
-    if (!searchUserStr || !searchUserResultArr || !searchUserResultArr.length) {
+    if (!searchUserStr || !searchUserResultArr) {
         let tempObj = {};
         for (let chat of myChats) {
             for (let member of chat.members) {

+ 7 - 5
chat_final_bootstrap/src/Layout/Sidebar.js

@@ -3,10 +3,12 @@ import { CAdditionalTools, ChatsList } from "../Components";
 
 export const Sidebar = () => {
     return (
-        <aside className="bg-light gradient shadow-sm border-2 rounded-3">
-            <CUserInfo />
-            <CAdditionalTools />
-            <ChatsList className="ChatsList" />
-        </aside>
+        <>
+            <div className="bg-light gradient shadow-sm border-2 rounded-3">
+                <CUserInfo />
+                <CAdditionalTools />
+                <ChatsList className="ChatsList" />
+            </div>
+        </>
     );
 };

+ 68 - 7
chat_final_bootstrap/src/Pages/PageMain.js

@@ -1,10 +1,68 @@
-import { ChatContain } from "../Components";
+import { ChatMessages } from "../Components";
 import { Sidebar } from "../Layout";
 import { store } from "../Reducers";
 import history from "../history";
-import { actionSearchMessagesByChatId, actionCurChatId } from "../Actions";
+import { actionSearchMessagesByChatId, actionCurChatId, actionMessageUpsert } from "../Actions";
 import { connect } from "react-redux";
-import { useState, useEffect } from "react";
+import { useState, useEffect, useRef } from "react";
+import userEvent from "@testing-library/user-event";
+
+const MessageInput = ({ curChatId: { curChatId } = {}, messageUpsert }) => {
+    const textRef = useRef("");
+    const [text, setText] = useState("");
+
+    const textTyping = (e) => {
+        setText(e.target.value);
+        // console.log(e);
+    };
+
+    // отправка по Ctrl+Enter но не по Enter
+    const sendMsgByEnterKey = (e) => {
+        if (["NumpadEnter", "Enter"].includes(e.code) && e.ctrlKey && text.trim()) {
+            sendMsg();
+        }
+    };
+
+    const sendMsg = () => {
+        if (text.trim()) {
+            // console.log(text.trim());
+            setText("");
+            textRef.current.value = "";
+            messageUpsert({ text: text.trim(), chatId: curChatId });
+        }
+    };
+
+    return (
+        <>
+            {curChatId && (
+                <div className="bg-light shadow-sm border border-2 rounded-3 MessageInput">
+                    {/* <div> InputArea</div>
+                    <div> {`${curChatId}`}</div> */}
+                    <div className="position-relative">
+                        <textarea
+                            className="form-control h-75"
+                            placeholder="Write a message..."
+                            ref={textRef}
+                            onChange={textTyping}
+                            onKeyUp={sendMsgByEnterKey}
+                        ></textarea>
+                        <span
+                            className="position-absolute bottom-0 end-0  badge rounded-pill bg-success"
+                            role-button="true"
+                            onClick={sendMsg}
+                        >
+                            <i class="bi bi-chat-dots"></i> <i class="bi bi-reply-fill"></i>
+                        </span>
+                    </div>
+                </div>
+            )}
+        </>
+    );
+};
+
+export const CMessageInput = connect((s) => ({ curChatId: s.curChatId }), { messageUpsert: actionMessageUpsert })(
+    MessageInput
+);
 
 //prettier-ignore
 const PageMain = ({
@@ -42,16 +100,19 @@ const PageMain = ({
         if (typeof setCurId === "function") setCurId(_chatId);
     }, [_chatId]);
 
-    
+  
 
     return (
-        <div className="container-fluid">
+        <div className="container-fluid ">
             <div className="row g-3">
-                <div className="col-md-4">
+                <div className="col-md-4  bg-light">
                     <Sidebar />
                 </div>
                 <div className="col-md-8">
-                    <ChatContain _chatId={_chatId} />
+                    <div className="pageMain_ChatMessages mb-2">
+                        <ChatMessages _chatId={_chatId} />
+                    </div>
+                    <CMessageInput />
                 </div>
             </div>
         </div>

+ 8 - 55
chat_final_bootstrap/src/Pages/PageNewChat.js

@@ -1,6 +1,6 @@
-import { ButtonToMain } from "../Components";
+import { ButtonToMain, CNewChatDashBoard } from "../Components";
 import { urlUploadConst } from "../const";
-import { actionGetAllUsers, actionAllUsersFind } from "../Actions";
+import { actionGetAllUsers, actionAllUsersFind, actionDelUserFromChatList } from "../Actions";
 import { connect } from "react-redux";
 import { useEffect } from "react";
 import { useState } from "react";
@@ -9,58 +9,10 @@ import { useRef } from "react";
 import history from "../history";
 import { CUserInfo } from "../Layout";
 import { CAllUsersList } from "../Layout";
+import { useDropzone } from "react-dropzone";
 import logo from "../images/logo23.png";
 import chat_square_text from "../icons/chat-square-text.svg";
-
-const MemberItem = ({ member }) => <pre>{member.login}</pre>;
-
-const ChatDashBoard = ({ members = {} }) => {
-    const [chatMembers, setChatMembers] = useState(Object.values(members));
-    useEffect(() => setChatMembers(Object.values(members)), [members]);
-
-    return (
-        <>
-            <div className="ChatDashBoard bg-light">
-                <h4>ChatDashBoard</h4>
-                <table className="table table-bordered">
-                    <tbody>
-                        <tr>
-                            <td>Chat title:</td>
-                            <td>
-                                <input
-                                    className="form-control mb-2 p-2 border border-success border-2"
-                                    placeholder="Input title for new chat"
-                                ></input>
-                            </td>
-                        </tr>
-                        <tr>
-                            <td>Chat avatar:</td>
-                            <td>
-                                <span className="avatarka">
-                                    <img
-                                        src={chat_square_text}
-                                        className="border border-2 border-success bg-light "
-                                        alt="Avatar"
-                                    />
-                                </span>
-                            </td>
-                        </tr>
-                        <tr>
-                            <td>Chat members:</td>
-                            <td>
-                                {chatMembers.map((member) => (
-                                    <MemberItem key={member._id} member={member} />
-                                ))}
-                            </td>
-                        </tr>
-                    </tbody>
-                </table>
-            </div>
-        </>
-    );
-};
-
-const CChatDashBoard = connect((s) => ({ members: s.newChatUsers }))(ChatDashBoard);
+import userEvent from "@testing-library/user-event";
 
 const PageNewChat = ({ doSearchUsers = null }) => {
     const [searchUserStr, setSearchUserStr] = useState("");
@@ -70,6 +22,7 @@ const PageNewChat = ({ doSearchUsers = null }) => {
         history.push("/");
     }
 
+    // если ввели поиск пользователей - будем искать
     useEffect(() => {
         if (searchUserStr && typeof doSearchUsers === "function") {
             doSearchUsers(0, searchUserStr);
@@ -83,9 +36,9 @@ const PageNewChat = ({ doSearchUsers = null }) => {
 
     return (
         <>
-            <div className="PageMain container-fluid">
+            <div className="container-fluid">
                 <div className="row g-3">
-                    <div className="col-md-4  bg-light">
+                    <div className="col-md-4  bg-light gradient shadow-sm border-2 rounded-3">
                         <CUserInfo />
                         <ButtonToMain />
 
@@ -100,7 +53,7 @@ const PageNewChat = ({ doSearchUsers = null }) => {
                         <CAllUsersList searchUserStr={searchUserStr} />
                     </div>
                     <div className="col-md-8">
-                        <CChatDashBoard />
+                        <CNewChatDashBoard />
                     </div>
                 </div>
             </div>

+ 2 - 2
chat_final_bootstrap/src/Pages/PageSearch.js

@@ -1,4 +1,4 @@
-import { ChatContain } from "../Components";
+import { ChatMessages } from "../Components";
 import { Sidebar } from "../Layout";
 import { store } from "../Reducers";
 import history from "../history";
@@ -51,7 +51,7 @@ const PageMain = ({
                     <Sidebar />
                 </div>
                 <div className="col-md-8">
-                    <ChatContain _chatId={_chatId} />
+                    <ChatMessages _chatId={_chatId} />
                 </div>
             </div>
         </div>

+ 41 - 22
chat_final_bootstrap/src/Pages/PageUpload.js

@@ -7,30 +7,43 @@ import { ButtonToMain } from "../Components";
 export const PageUpload = () => {
     const [fl, setFl] = useState(false);
     const resultArray = useRef([]);
+    const uploadImgRef = useRef(null);
 
     function MyDropzone() {
         const onDrop = useCallback(async (acceptedFiles) => {
-            // console.log("МАССИВ", typeof acceptedFiles[0], acceptedFiles[0]);
+            console.log("МАССИВ", acceptedFiles);
+
+            let reader = new FileReader();
+
+            reader.onloadend = function () {
+                uploadImgRef.current.src = reader.result;
+            };
+
+            if (acceptedFiles[0]) {
+                reader.readAsDataURL(acceptedFiles[0]);
+            } else {
+                uploadImgRef.current.src = "";
+            }
 
             setFl(false);
 
-            let aaryOfFatchs = acceptedFiles.map((file) => {
-                let dataSingl = new FormData();
-                dataSingl.set("media", file);
-                return fetch(`${urlUploadConst}/upload`, {
-                    method: "POST",
-                    headers: localStorage.authToken ? { Authorization: "Bearer " + localStorage.authToken } : {},
-                    body: dataSingl,
-                }).then((res) => res.json());
-            });
+            // let aaryOfFatchs = acceptedFiles.map((file) => {
+            //     let dataSingl = new FormData();
+            //     dataSingl.set("media", file);
+            //     return fetch(`${urlUploadConst}/upload`, {
+            //         method: "POST",
+            //         headers: localStorage.authToken ? { Authorization: "Bearer " + localStorage.authToken } : {},
+            //         body: dataSingl,
+            //     }).then((res) => res.json());
+            // });
 
-            resultArray.current = [];
+            // resultArray.current = [];
 
-            await Promise.all(aaryOfFatchs)
-                .then((responses) => responses.forEach((response) => resultArray.current.push(response)))
-                .catch((e) => alert("Произошла ошибка.", e));
+            // await Promise.all(aaryOfFatchs)
+            //     .then((responses) => responses.forEach((response) => resultArray.current.push(response)))
+            //     .catch((e) => alert("Произошла ошибка.", e));
 
-            console.log(resultArray.current);
+            // console.log(resultArray.current);
 
             setFl(true);
         }, []);
@@ -43,7 +56,7 @@ export const PageUpload = () => {
                 {isDragActive ? (
                     <p>Drop the files here ...</p>
                 ) : (
-                    <p>Drag 'n' drop some files here, or click to select files</p>
+                    <p className="border border-success">Drag 'n' drop some files here, or click to select files</p>
                 )}
             </div>
         );
@@ -51,15 +64,21 @@ export const PageUpload = () => {
 
     return (
         <>
-            <div>
+            <div className="m-2">
                 PageUpload
                 <ButtonToMain />
+                <br />
+                <br />
+                <MyDropzone />
+                <img src="" height="200" alt="Image preview..." ref={uploadImgRef}></img>
+                <br />
+                <br />
+                Результат
+                <br />
+                {resultArray.current.map((res) => (
+                    <img src={`${urlUploadConst}/${res.url}`} width="100px" key={res._id} />
+                ))}
             </div>
-            <br />
-            <MyDropzone />
-            {resultArray.current.map((res) => (
-                <img src={`${urlUploadConst}/${res.url}`} width="100px" key={res._id} />
-            ))}
         </>
     );
 };

+ 2 - 2
chat_final_bootstrap/src/Reducers/index.js

@@ -79,11 +79,11 @@ function newChatUsersReduser(state = {}, action) {
 
     if (action.type === "DELETE_USER_FROM_CHAT_LIST") {
         delete state[action._id];
-        return state;
+        return { ...state };
     }
 
     if (action.type === "NEW_CHAT_LIST") {
-        return { ...state, ...action.user };
+        return { ...action.user };
     }
 
     return state;

Різницю між файлами не показано, бо вона завелика
+ 20 - 0
fetch_authtoken.txt


+ 94 - 0
temp2 - дерево вариантов.js

@@ -0,0 +1,94 @@
+let testArr = [
+    ["USA", "Mexico"],
+    ["Green", "Red", "Blue"],
+    ["Metall", "Glass", "Plastic"],
+];
+
+function arrayOfTree(arr = []) {
+    let iter = arr.reduce((a, b) => a * b.length, 1); // 18
+    let res = Array.from(Array(iter), () => Array(arr.length)); // 18*3
+
+    let step = iter;
+    let i = 0;
+
+    for (let k = 0; k < arr.length; k++) {
+        step = step / arr[k].length; // 9, 3, 1
+        // console.log("step - ", step);
+        i = 0;
+        for (let j = 0; j < iter; j++) {
+            res[j][k] = arr[k][Math.floor(i++ / step) % arr[k].length];
+        }
+    }
+
+    return res;
+}
+
+console.log(arrayOfTree(testArr));
+
+// не боится пустого массива на входе
+
+//////////////////
+//////////////////
+//////////////////
+//////////////////
+//////////////////
+//////////////////
+
+var list = [
+    ["USA", "Mexico"],
+    ["Green", "Red", "Blue"],
+    ["Metall", "Glass", "Plastic"],
+];
+var tree = [];
+
+list.forEach(function (listArrayValue, listKey) {
+    let secondListKeyElement = 1;
+    if (listKey === 0) {
+        addElementsToSecondBranch(list, tree, secondListKeyElement, listArrayValue);
+    }
+});
+
+function addElementsToSecondBranch(list, tree, secondListKeyElement, listArrayValue) {
+    listArrayValue.forEach(function (branchKey) {
+        tree[branchKey] = [];
+        if (typeof tree[branchKey] !== "undefined" && typeof list[secondListKeyElement] !== "undefined") {
+            addElementsToSecondBranch(list, tree[branchKey], secondListKeyElement + 1, list[secondListKeyElement]);
+        }
+    });
+}
+
+console.log(tree);
+
+/////////////
+/////////////
+/////////////
+/////////////
+/////////////
+/////////////
+/////////////
+/////////////
+
+let testArr = [
+    ["USA", "Mexico"],
+    ["Green", "Red", "Blue"],
+    ["Metall", "Glass", "Plastic"],
+];
+
+(() => {
+    const inc = (indexes) => {
+        let i;
+        for (i = testArr.length - 1; i >= 0 && testArr[i].length <= (indexes[i] || 0) + 1; i--);
+        if (i === -1) return NaN;
+        indexes[i] = (indexes[i] || 0) + 1;
+        i < testArr.length - 1 && (indexes[i + 1] = 0);
+        return [...indexes];
+    };
+
+    let indexes = [0, 0, 0],
+        result = [];
+
+    do {
+        result.push(indexes.map((i, j) => testArr[j][i || 0]));
+    } while ((indexes = inc(indexes)));
+    return result;
+})();

+ 0 - 27
temp2.js

@@ -1,27 +0,0 @@
-let testArr = [
-    ["USA", "Mexico"],
-    ["Green", "Red", "Blue"],
-    ["Metall", "Glass", "Plastic"],
-];
-
-function arrayOfTree(arr = []) {
-    let iter = arr.reduce((a, b) => a * b.length, 1); // 18
-    let res = Array.from(Array(iter), () => Array(arr.length)); // 18*3
-
-    let step = iter;
-    let i = 0;
-
-    for (let k = 0; k < arr.length; k++) {
-        step = step / arr[k].length; // 9, 3, 1
-        // console.log("step - ", step);
-        i = 0;
-        for (let j = 0; j < iter; j++) {
-            res[j][k] = arr[k][Math.floor(i++ / step) % arr[k].length];
-        }
-    }
-    return res;
-}
-
-console.log(arrayOfTree(testArr));
-
-// не боится пустого массива на входе