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;