stream-chat-react icon indicating copy to clipboard operation
stream-chat-react copied to clipboard

question: how do I add custom attachment objects to MessageInput state?

Open ElizabethSobiya opened this issue 1 year ago • 13 comments

Describe the bug

When customizing the MessageInput component to handle file attachments, the message does not properly accept the attachment. Specifically, when trying to send a message with an attachment (image or file), the message fails to include the attachment.

To Reproduce

Steps to reproduce the behavior:

//home component code start

const removeFilePreview = (index) => {
    const newFilePreviews = filePreviews.filter((_, i) => i !== index);
    setFilePreviews(newFilePreviews);
  };

  const handleFileClick = () => {
    const fileInput = document.createElement("input");
    fileInput.type = "file";
    fileInput.accept = "*/*";
    fileInput.multiple = true;
    fileInput.onchange = (e) => {
      const files = e.target.files;
      if (files.length > 0) {
        const previews = Array.from(files).map((file) => ({
          file,
          preview: URL.createObjectURL(file),
        }));
        setFilePreviews((prev) => [...prev, ...previews]);
      }
    };
    fileInput.click();
  };

  const isFileImage = (file) => {
    const imageExtensions = ["jpg", "jpeg", "png", "gif", "bmp", "webp"];
    const fileExtension = file.name.split(".").pop().toLowerCase();
    return imageExtensions.includes(fileExtension);
  };

  const setMainPreview = (index) => {
    const selectedPreview = filePreviews.splice(index, 1)[0];
    setFilePreviews([selectedPreview, ...filePreviews]);
  };
 {filePreviews.length > 0 && (
          <div className="absolute z-10 top-16 left-0  right-0 bottom-20 w-2/2 bg-[#E5E4E2] flex flex-col items-center justify-center p-0">
            <div className="relative w-full h-[90%]">
              <button
                onClick={() => removeFilePreview(0)}
                type="button"
                className="absolute top-0 mt-[-30px] bottom-160 left-10 text-grey p-2"
              >
                <IoCloseCircleOutline size={30} />
              </button>
              {isFileImage(filePreviews[0].file) ? (
                <img
                  src={filePreviews[0].preview}
                  alt="Preview"
                  className="object-contain h-[85%] w-full px-4"
                />
              ) : (
                <div className="flex flex-col items-center justify-center h-[85%]">
                  <FaFile size={50} className="text-gray-400 mb-2" />
                  <p className="text-gray-400">No preview available</p>
                </div>
              )}
              {filePreviews.length > 0 && (
                <div className="flex items-center justify-center mb-4 px-4 pt-5">
                  {filePreviews.map((preview, index) => (
                    <div
                      key={index}
                      className={`relative mr-2 ${
                        index === 0 ? "border-2 border-blue-500" : ""
                      }`}
                      onClick={() => setMainPreview(index)}
                    >
                      <button
                        onClick={(e) => {
                          e.stopPropagation();
                          removeFilePreview(index);
                        }}
                        type="button"
                        className="absolute top-0 right-0 text-grey p-1 opacity-0 hover:opacity-100 transition-opacity duration-300 shadow-md"
                      >
                        <IoCloseCircleOutline color="grey" size={10} />
                      </button>
                      {isFileImage(preview.file) ? (
                        <img
                          src={preview.preview}
                          alt="Preview"
                          className="object-cover w-14 h-14 rounded"
                        />
                      ) : (
                        <div className="flex flex-col items-center justify-center w-14 h-14 bg-gray-200 rounded">
                          <FaFile size={24} className="text-gray-500 mb-1" />
                          <p className="text-[10px] text-gray-500">
                            No preview
                          </p>
                        </div>
                      )}
                    </div>
                  ))}
                  <div
                    className="w-16 h-16 bg-gray-200 rounded flex items-center justify-center cursor-pointer"
                    onClick={() => handleFileClick()}
                  >
                    <IoAddCircleOutline size={24} />
                  </div>
                </div>
              )}
            </div>
          </div>
        )}
        
 <Chat client={streamClient} theme="messaging light">
              <Channel channel={channel} >
                <Window>
                  <div className="p-2 bg-[#315A9D]">
                    <CustomChannelHeader
                      onClick={toggleContactInfo}
                      isContactInfoOpen={isContactInfoOpen}
                      user={user}
                    />
                  </div>
                  <MessageList
                  />
                  <CustomMessageInput
                    overrideSubmitHandler={overrideSubmitHandler}
                    channel={channel}
                    setFilePreviews={setFilePreviews}
                    EmojiPicker={EmojiPicker}
                    filePreviews={filePreviews}
                  />
                </Window>
              </Channel>
            </Chat>
///home component

import React, { useState } from "react";
import {
  MessageInput,
  ChatAutoComplete,
  useMessageInputContext,
  useChannelActionContext,
  Attachment,
} from "stream-chat-react";
import Picker from "@emoji-mart/react";
import { FaMapMarkerAlt, FaImage, FaRegFileAlt } from "react-icons/fa";
import { BsEmojiSmile } from "react-icons/bs";
import {
  IoAddCircleOutline,
  IoMicOutline,
  IoCloseCircleOutline,
} from "react-icons/io5";
import "./style.css";
import { PiHeadphones } from "react-icons/pi";
import Modal from "react-modal";
import client from "../../streamClient";

const customStyles = {
  content: {
    top: "68%",
    left: "28%",
    bottom: "0%",
    width: "170px",
    height: "230px",
    borderRadius: "20px",
  },
  overlay: {
    backgroundColor: "transparent",
  },
};

Modal.setAppElement("#root");

const CustomMessageInput = ({
  isContactInfoOpen,
  filePreviews,
  setFilePreviews,
  // overrideSubmitHandler,
  ...props
}) => {
  const [isOpen, setIsOpen] = useState(false);
  const [isEmojiPickerOpen, setIsEmojiPickerOpen] = useState(false);
  const { handleSubmit, text, setText, uploadNewFiles } =
    useMessageInputContext();
  const { sendMessage } = useChannelActionContext();
  console.log(filePreviews, "previews");

  const toggleModal = () => {
    setIsOpen(!isOpen);
  };

  const handleSmileClick = () => {
    setIsEmojiPickerOpen(!isEmojiPickerOpen);
  };

  const handleEmojiSelect = (emoji) => {
    setText((prevText) => prevText + emoji.native);
    setIsEmojiPickerOpen(false);
  };

  const handleFileChange = (e) => {
    const files = e.target.files;
    if (files.length > 0) {
      const previews = Array.from(files).map((file) => ({
        file,
        preview: URL.createObjectURL(file),
      }));
      setFilePreviews((prev) => [...prev, ...previews]);
    }
    toggleModal();
  };

  const handleFileClick = (acceptType) => {
    const fileInput = document.createElement("input");
    fileInput.type = "file";
    fileInput.accept = acceptType;
    fileInput.multiple = true;
    fileInput.onchange = handleFileChange;
    fileInput.click();
  };

  const handleLocationClick = () => {
    if (navigator.geolocation) {
      navigator.geolocation.getCurrentPosition(
        (position) => {
          console.log("Latitude:", position.coords.latitude);
          console.log("Longitude:", position.coords.longitude);
        },
        (error) => {
          console.error("Error getting location:", error);
        }
      );
    } else {
      console.error("Geolocation is not supported by this browser.");
    }
  };

  const handleSend = async (event) => {
    event.preventDefault();
    if (filePreviews.length > 0) {
      await sendMessage(filePreviews.map((preview) => preview.file));
    }
    await handleSubmit(event);
    setFilePreviews([]);
  };
  const overrideSubmitHandler = (message, filePreviews) => {
    let updatedMessage = message;
     console.log(message, 'message14')
    if (message.attachments) {
      message.attachments.forEach((attachment) => {
        if (attachment.type === 'image') {
          const updatedAttachment = {
            ...filePreviews,
          };

          updatedMessage = {
            ...message,
            attachments: [updatedAttachment],
          };
        }
      });
    }

    sendMessage(updatedMessage);
  };
  // const meg = channel.sendImage(filePreviews[0])

  return (
    <div className={`sticky bottom-0 bg-[#DEE3EF] text-gray-800 p-3`}>
      <div className="flex items-center">
        <div
          className="cursor-pointer mr-2 p-2 rounded-full bg-[#315A9D] text-white flex items-center justify-center"
          onClick={handleSmileClick}
        >
          <BsEmojiSmile size={20} />
        </div>
        <div
          className="cursor-pointer mr-2 p-2 rounded-full bg-[#315A9D] text-white flex items-center justify-center"
          onClick={toggleModal}
        >
          {isOpen ? (
            <IoCloseCircleOutline size={20} />
          ) : (
            <IoAddCircleOutline size={20} />
          )}
        </div>
        <MessageInput
          // doFileUploadRequest={(file, channel) => {
          //   return client.sendFile(filePreviews, file);
          // }}
          // doImageUploadRequest={(file, channel) => {
          //   return client.sendFile(filePreviews, file);
          // doFileUploadRequest = {filePreviews}
          // attachments={filePreviews}
          {...props}
          Input={(inputProps) => (
            <div className="flex-1 relative">
              <ChatAutoComplete
                {...inputProps}
                value={text}
                // filePreviews={filePreviews}
                // onChange={(e) => setText(e.target.value)}
                className="bg-white text-gray-800 placeholder-gray-400 px-4 py-1 rounded-lg border border-gray-300"
                placeholder="Type Message..."
                name="message"
                overrideSubmitHandler={overrideSubmitHandler}
                // onKeyDown={(e) => {
                //   if (
                //     (e.key === "Enter" || e.key === "Return") &&
                //     !e.shiftKey
                //   ) {
                //     handleSend(e);
                //   }
                // }}
              />
            </div>
          )}
        />
        <div className="cursor-pointer ml-2 p-2 rounded-full bg-[#315A9D] text-white flex items-center justify-center">
          <IoMicOutline size={20} />
        </div>
      </div>

      {isEmojiPickerOpen && (
        <div className="absolute bottom-12 left-2 z-10">
          <Picker onEmojiSelect={handleEmojiSelect} />
        </div>
      )}

      <Modal
        isOpen={isOpen}
        onRequestClose={toggleModal}
        style={customStyles}
        contentLabel="Example Modal"
      >
        <div className="flex flex-col space-y-4 h-auto">
          <label className="flex items-center cursor-pointer">
            <FaRegFileAlt color="#315A9D" className="mr-2" />
            <span>Document</span>
            <input
              type="file"
              style={{ display: "none" }}
              accept=".pdf,.doc,.docx,.txt,.xls,.xlsx,.ppt,.pptx,.odt"
              onChange={handleFileChange}
            />
          </label>
          <button
            className="flex items-center"
            onClick={() => handleFileClick("image/*")}
          >
            <FaImage color="#315A9D" className="mr-2" /> Gallery
          </button>
          <button
            className="flex items-center"
            onClick={() => handleFileClick("audio/*")}
          >
            <PiHeadphones color="#315A9D" className="mr-2" /> Audio
          </button>
          <button
            className="flex items-center"
            onClick={() => handleFileClick("video/*")}
          >
            <FaMapMarkerAlt color="#315A9D" className="mr-2" /> Video
          </button>
          <button className="flex items-center" onClick={handleLocationClick}>
            <FaMapMarkerAlt color="#315A9D" className="mr-2" /> Location
          </button>
        </div>
      </Modal>
    </div>
  );
};

export default CustomMessageInput;

-Implement the above CustomMessageInput component in your project. -Try to send a message with an attachment.

  • Observe the issue where the message does not send with the attachment.
  • Please let me know if you need any additional information or further assistance.

Expected behavior

A clear and concise description of what you expected to happen.

onClicking the document/image/audi and so on from modal I want to open that and pass that to message input .

Package version

  • stream-chat-react:^11.18.1
  • stream-chat-css:
  • stream-chat-js: "react": "^18.3.1",

Desktop (please complete the following information):

  • OS: [e.g. iOS] iOS
  • Browser [e.g. chrome, safari] Chrome
  • Version [e.g. 22] Version 125.0.6422.142

Additional context

Here's the corrected sentence:

This issue is blocking my development . I tried using overrideSubmitHandler, but this causes a delay in sending the message and not getting attachments.

ElizabethSobiya avatar Jun 09 '24 12:06 ElizabethSobiya