TwitchClone / src / components / chatTwo / index.tsx
index.tsx
Raw
import React, { useEffect, useState } from "react";
import Pusher from "pusher-js";
import { useSession } from "next-auth/react";
import { api } from "../../utils/api";
import { SendMessageInput } from "../../types/sendMessage";
import VideoPlayer from "../videoPlayer";
import UserUpload from "../uploadContent";
import ColorPicker from "../colorPicker";
import { User } from "next-auth";
import { Content, UserFollows } from "@prisma/client";
import CollapseChatSvg from "../svgs/collapseChat";
import GearSvg from "../svgs/gear";
import ReplySvg from "../svgs/reply";

interface ChatProps {
  roomId: string;
  playbackId?: string; // Add videoId to ChatProps
  videoData?: {
    id: string;
    createdAt: Date;
    updatedAt: Date;
    roomId: string;
    playbackId: string;
    streamKey: string;
    status?: string;
  };
  chatExpand: boolean;
  handleChatExpand: () => void;
  streamerData?: User & {
    videos: Content[];
    following: UserFollows[];
    followers: UserFollows[];
  };
}
type PusherMessageData = {
  message: {
    id: string;
    content: string;
    roomId: string;
    createdAt: string;
    authorId?: string;
    replyTo?: string;
    originalMessageId?: string;
    author: {
      id: string;
      name: string;
      email: string;
      emailVerified?: boolean;
      image: string;
      color?: string;
    };
  };
  originalMessage?: {
    id: string;
    content: string;
    roomId: string;
    createdAt: string;
    authorId?: string;
    replyTo?: string;
    author: {
      id: string;
      color?: string;
      name: string;
      email: string;
      emailVerified?: boolean;
      image: string;
    };
  };
};
type MessageWithReplies = {
  message: PusherMessageData["message"];
  replies?: PusherMessageData["message"][];
  originalMessage?: PusherMessageData["originalMessage"];
};

const Chat: React.FC<ChatProps> = ({
  roomId,
  playbackId,
  videoData,
  streamerData,
  handleChatExpand,
}) => {
  console.log("videoData:", videoData);
  console.log("roomId", roomId);
  console.log("streamerData", streamerData);
  const [messages, setMessages] = useState<MessageWithReplies[]>([]);
  const [inputMessage, setInputMessage] = useState<null | string>();
  const [sending, setSending] = useState(false);
  const [isLoading, setIsLoading] = useState(true);
  const [isReply, setIsReply] = useState<null | boolean>(null);
  const [replyId, setReplyId] = useState<null | string>(null);
  const { data: user, status } = useSession();
  const [showColorPicker, setShowColorPicker] = useState(false);

  // if (!user || !user.user?.id || !user.user?.name) {
  //   return <div>Loading user information...</div>;
  // }
  useEffect(() => {
    setIsLoading(!videoData);
  }, [videoData]);
  useEffect(() => {
    if (!user || !user.user?.id || !user.user?.name) {
      return;
    }
  }, [user, streamerData]);

  console.log("User Profile, Chat:", user);
  useEffect(() => {
    console.log("new message");
    console.log("PUSHER_APP_KEY:", process.env.PUSHER_APP_KEY);
    const pusherClient = new Pusher(
      process.env.PUSHER_APP_KEY || "6bb7f6c549686920de7a",
      {
        cluster: "us3",
        forceTLS: true,
      }
    );
    // console.log(pusherClient, "pusherClient");
    const channel = pusherClient.subscribe(`chat-${roomId}`);
    console.log("channel", channel);
    channel.bind("new-message", (data: PusherMessageData) => {
      console.log("Received new message:", data);
      setMessages((prev) => {
        // If it's a reply, update the original message with the reply
        if (
          data.message.replyTo &&
          data.originalMessage &&
          data.originalMessage.id
        ) {
          console.log("reply", data.message);
          const updatedMessages = prev.map((msg) => {
            if (
              data.originalMessage &&
              msg.message.id === data.originalMessage.id
            ) {
              return {
                ...msg,
                replies: msg.replies
                  ? [...msg.replies, data.message]
                  : [data.message],
              };
            }
            return msg;
          });
          return updatedMessages;
        }
        console.log("not reply", data.message);
        // If it's not a reply, add the message to the list
        return [...prev, { message: data.message }];
      });
    });

    return () => {
      channel.unbind("new-message");
      channel.unbind_all();
      channel.cancelSubscription();
      //unbind before unmount
      channel.unsubscribe();
      pusherClient.disconnect();
      console.log("unsubscribing");
    };
  }, []);

  const sendMessageMutation = api.lambda.sendMessage.useMutation();
  useEffect(() => {
    if (sending && inputMessage && user && user.user) {
      console.log(isReply, "isReply");
      const messageInput: SendMessageInput = {
        content: inputMessage,
        userId: user.user?.id,
        reply: !!isReply, // Simplify this line
        replyTo: isReply ? replyId : null, // Keep this line as it is
      };
      console.log("roomid", roomId);
      console.log("messageInput", messageInput);
      sendMessageMutation.mutate(
        {
          content: messageInput.content,
          roomId: roomId,
          reply: messageInput.reply,
          replyToId:
            messageInput.replyTo !== null && messageInput.replyTo !== undefined
              ? messageInput.replyTo
              : undefined,
        },
        {
          onSuccess: (data) => {
            console.log("data", data);
            setIsReply(false);
            setReplyId(null);
            setInputMessage(null);
            setSending(false);
          },
          onError: (error) => {
            console.error("Error sending message:", error);
            setSending(false);
          },
        }
      );
    }
  }, [sending, inputMessage, sendMessageMutation]);

  const send = () => {
    setSending(false);
    try {
      setInputMessage(null);
    } catch (error) {
      console.error("Error sending message:", error);
    }
  };
  useEffect(() => {
    if (!sending) {
      return;
    }

    void (() => {
      try {
        send();
      } catch (error) {
        console.error("Error sending message:", error);
      } finally {
        setSending(false);
        setInputMessage(null);
      }
    })();

    // void send();
  }, [sending]);

  const sendMessage = () => {
    if (!inputMessage) {
      console.log("Cannot send an empty message");
      return;
    }
    const trimmedMessage = inputMessage.trim();
    if (!trimmedMessage) {
      console.log("Cannot send an empty message");
      return;
    }
    if (user && user.user?.id) {
      console.log(user.user.id, "sendMessage username");
      setSending(true);
      console.log("roomid", roomId);
      console.log("messageInput", inputMessage);
    }
  };
  const toggleColorPicker = () => {
    setShowColorPicker(!showColorPicker);
  };

  const replyToMessage = (messageId: string) => {
    if (!isReply) {
      console.log("replyToMessage", messageId);
      setIsReply(true);
      setReplyId(messageId);
    } else {
      setIsReply(false);
      setReplyId(null);
    }
  };

  console.log("vido props", {
    videoData,
    isLoading,
    playbackId,
  });
  return (
    <div className="flex  w-full bg-gray-500">
      <div className="flex  w-full flex-col">
        {/* h-calc adjust for navbar  */}
        {/* <div className="flex h-[calc(100%-4.8rem)] w-full flex-col items-center justify-center bg-black "> */}
        <div className="relative flex h-full w-full flex-col items-center justify-center bg-black ">
          <div className="absolute top-0 flex w-full items-center justify-start bg-gray-500 py-2 font-semibold text-white">
            <div className="group flex cursor-pointer pl-2 ">
              <div
                onClick={handleChatExpand}
                className="flex rounded-md p-1 group-hover:bg-[rgba(255,255,255,0.3)]"
              >
                {streamerData?.id && <CollapseChatSvg />}
              </div>
            </div>
            <div className="flex min-w-full items-center justify-center text-center">
              {streamerData?.id && (
                <h1 className="flex w-full items-center justify-center pr-[3.3rem]">{`STREAM CHAT`}</h1>
              )}
            </div>
          </div>
          {messages.map((message, index) => (
            <div key={index}>
              <div className="m-1 rounded-lg bg-gray-800 p-2 transition-colors duration-200 ease-in hover:bg-gray-700">
                <span
                  style={{ color: message.message.author.color }}
                  className="text-white"
                >
                  {message.message.author.id}:
                </span>
                <span className="text-white">{message.message.content}</span>
                <button
                  onClick={() => replyToMessage(message.message.id)}
                  className="group ml-2 inline-block focus:outline-none"
                >
                  <ReplySvg />
                  {/* <svg
                    className="h-4 w-4 fill-white transition-colors duration-200
      ease-in group-hover:text-gray-300"
                    xmlns="http://www.w3.org/2000/svg"
                    viewBox="0 0 24 24"
                  >
                    <title>reply</title>
                    <path d="M10,9V5L3,12L10,19V14.9C15,14.9 18.5,16.5 21,20C20,15 17,10 10,9Z" />
                  </svg> */}
                </button>
              </div>
              {message.replies?.map((reply, replyIndex) => (
                <div
                  key={`reply-${replyIndex}`}
                  className="mb-1 w-full rounded-lg bg-gray-800 p-2 text-sm transition-colors duration-200 ease-in hover:bg-gray-700"
                >
                  {replyIndex === 0 && (
                    <div className="text-xs">
                      <span className="text-gray-400">
                        {message.message.author.id}
                      </span>
                      <span className="text-gray-400">(original):</span>
                      <span className="text-gray-400">
                        {message.message.content}
                      </span>
                    </div>
                  )}
                  <div className="m-1 rounded-lg bg-gray-800 p-2 transition-colors duration-200 ease-in hover:bg-gray-700">
                    <span
                      style={{ color: message.message.author.color }}
                      className="text-white"
                    >
                      {reply.author.id}:
                    </span>
                    <span className="text-white">{reply.content}</span>
                  </div>
                  <button
                    onClick={() => replyToMessage(message.message.id)}
                    className="group ml-2 inline-block focus:outline-none"
                  >
                    <ReplySvg />
                    {/* <svg
                      className="h-4 w-4 fill-white transition-colors duration-200
      ease-in group-hover:text-gray-300"
                      xmlns="http://www.w3.org/2000/svg"
                      viewBox="0 0 24 24"
                    >
                      <title>reply</title>
                      <path d="M10,9V5L3,12L10,19V14.9C15,14.9 18.5,16.5 21,20C20,15 17,10 10,9Z" />
                    </svg> */}
                  </button>
                </div>
              ))}
            </div>
          ))}
        </div>
        <div className="flex h-[2.33rem]">
          <input
            type="text"
            value={inputMessage || ""}
            onChange={(e) => setInputMessage(e.target.value)}
            className="w-full flex-grow bg-white text-black"
          />
          <button
            className="group flex items-center justify-center whitespace-nowrap bg-white text-black hover:bg-black"
            onClick={() => {
              sendMessage();
            }}
          >
            <a className="px-3 text-[0.8rem] group-hover:text-white">Send</a>
          </button>
          <button
            onClick={toggleColorPicker}
            className="group flex items-center justify-center whitespace-nowrap focus:outline-none"
          >
            <GearSvg />
            {/* <svg
              className="h-4 w-4 fill-white transition-colors duration-200"
              xmlns="http://www.w3.org/2000/svg"
              viewBox="0 0 24 24"
            >
              <title>cog</title>
              <path d="M12,15.5A3.5,3.5 0 0,1 8.5,12A3.5,3.5 0 0,1 12,8.5A3.5,3.5 0 0,1 15.5,12A3.5,3.5 0 0,1 12,15.5M19.43,12.97C19.47,12.65 19.5,12.33 19.5,12C19.5,11.67 19.47,11.34 19.43,11L21.54,9.37C21.73,9.22 21.78,8.95 21.66,8.73L19.66,5.27C19.54,5.05 19.27,4.96 19.05,5.05L16.56,6.05C16.04,5.66 15.5,5.32 14.87,5.07L14.5,2.42C14.46,2.18 14.25,2 14,2H10C9.75,2 9.54,2.18 9.5,2.42L9.13,5.07C8.5,5.32 7.96,5.66 7.44,6.05L4.95,5.05C4.73,4.96 4.46,5.05 4.34,5.27L2.34,8.73C2.21,8.95 2.27,9.22 2.46,9.37L4.57,11C4.53,11.34 4.5,11.67 4.5,12C4.5,12.33 4.53,12.65 4.57,12.97L2.46,14.63C2.27,14.78 2.21,15.05 2.34,15.27L4.34,18.73C4.46,18.95 4.73,19.03 4.95,18.95L7.44,17.94C7.96,18.34 8.5,18.68 9.13,18.93L9.5,21.58C9.54,21.82 9.75,22 10,22H14C14.25,22 14.46,21.82 14.5,21.58L14.87,18.93C15.5,18.67 16.04,18.34 16.56,17.94L19.05,18.95C19.27,19.03 19.54,18.95 19.66,18.73L21.66,15.27C21.78,15.05 21.73,14.78 21.54,14.63L19.43,12.97Z" />
            </svg> */}
          </button>
        </div>
      </div>
      {showColorPicker && (
        <div
          className="absolute top-0 right-0 h-auto w-auto rounded-lg bg-white p-4 shadow-lg"
          style={{ zIndex: 30 }}
        >
          <ColorPicker userId={user?.user?.id || ""} />
        </div>
      )}
    </div>
  );
};

export default Chat;