From c56c5ffa41c41d1d01daae3fa48a4cdefe598d7a Mon Sep 17 00:00:00 2001 From: Chris-1010 <122332721@umail.ucc.ie> Date: Wed, 26 Feb 2025 13:02:31 +0000 Subject: [PATCH] REFACTOR: Repositioned chat toggle functionality and context management; UPDATE: Improve stream time display; REFACTOR: Update Video Page Styles; Co-authored-by: JustIceO7 --- frontend/src/components/Stream/ChatPanel.tsx | 40 ++++++- frontend/src/context/ChatContext.tsx | 26 +++++ frontend/src/pages/VideoPage.tsx | 107 +++++++++---------- 3 files changed, 113 insertions(+), 60 deletions(-) create mode 100644 frontend/src/context/ChatContext.tsx diff --git a/frontend/src/components/Stream/ChatPanel.tsx b/frontend/src/components/Stream/ChatPanel.tsx index 8db6924..526e5e4 100644 --- a/frontend/src/components/Stream/ChatPanel.tsx +++ b/frontend/src/components/Stream/ChatPanel.tsx @@ -1,10 +1,12 @@ import React, { useState, useEffect, useRef } from "react"; import Input from "../Input/Input"; -import Button from "../Input/Button"; +import Button, { ToggleButton } from "../Input/Button"; import AuthModal from "../Auth/AuthModal"; import { useAuthModal } from "../../hooks/useAuthModal"; import { useAuth } from "../../context/AuthContext"; import { useSocket } from "../../context/SocketContext"; +import { useChat } from "../../context/ChatContext"; +import { ArrowLeftFromLineIcon, ArrowRightFromLine } from "lucide-react"; interface ChatMessage { chatter_username: string; @@ -22,6 +24,7 @@ const ChatPanel: React.FC = ({ onViewerCountChange, }) => { const { isLoggedIn, username, userId } = useAuth(); + const { showChat, setShowChat } = useChat(); const { showAuthModal, setShowAuthModal } = useAuthModal(); const { socket, isConnected } = useSocket(); const [messages, setMessages] = useState([]); @@ -94,6 +97,25 @@ const ChatPanel: React.FC = ({ } }, [messages]); + // Keyboard shortcut to toggle chat + useEffect(() => { + const handleKeyPress = (e: KeyboardEvent) => { + if (e.key === "c" && document.activeElement == document.body) { + toggleChat(); + } + }; + + document.addEventListener("keydown", handleKeyPress); + + return () => { + document.removeEventListener("keydown", handleKeyPress); + }; + }, [showChat]); + + const toggleChat = () => { + setShowChat(!showChat); + }; + const sendChat = () => { if (!inputMessage.trim() || !socket || !isConnected) { console.log("Invalid message or socket not connected"); @@ -119,11 +141,23 @@ const ChatPanel: React.FC = ({ return (
+ {/* Toggle Button for Chat */} + + {/* Chat Header */} -

+

Stream Chat

diff --git a/frontend/src/context/ChatContext.tsx b/frontend/src/context/ChatContext.tsx new file mode 100644 index 0000000..3fba489 --- /dev/null +++ b/frontend/src/context/ChatContext.tsx @@ -0,0 +1,26 @@ +import { createContext, useContext, useState, ReactNode } from "react"; + +interface ChatContextType { + showChat: boolean; + setShowChat: (show: boolean) => void; +} + +const ChatContext = createContext(undefined); + +export function ChatProvider({ children }: { children: ReactNode }) { + const [showChat, setShowChat] = useState(true); + + return ( + + {children} + + ); +} + +export function useChat() { + const context = useContext(ChatContext); + if (context === undefined) { + throw new Error("useChat must be used within a ChatProvider"); + } + return context; +} \ No newline at end of file diff --git a/frontend/src/pages/VideoPage.tsx b/frontend/src/pages/VideoPage.tsx index 18c3bf3..02a7b73 100644 --- a/frontend/src/pages/VideoPage.tsx +++ b/frontend/src/pages/VideoPage.tsx @@ -1,15 +1,15 @@ -import React, { useState, useEffect, lazy, Suspense } from "react"; -import { ToggleButton } from "../components/Input/Button"; -import ChatPanel from "../components/Stream/ChatPanel"; +import React, { lazy, Suspense, useEffect, useState } from "react"; import { useNavigate, useParams } from "react-router-dom"; -import { useAuthModal } from "../hooks/useAuthModal"; -import { useAuth } from "../context/AuthContext"; -import { useFollow } from "../hooks/useFollow"; -import VideoPlayer from "../components/Stream/VideoPlayer"; -import { SocketProvider } from "../context/SocketContext"; import AuthModal from "../components/Auth/AuthModal"; import DynamicPageContent from "../components/Layout/DynamicPageContent"; +import ChatPanel from "../components/Stream/ChatPanel"; +import VideoPlayer from "../components/Stream/VideoPlayer"; +import { useAuth } from "../context/AuthContext"; import { useSidebar } from "../context/SidebarContext"; +import { SocketProvider } from "../context/SocketContext"; +import { useAuthModal } from "../hooks/useAuthModal"; +import { useFollow } from "../hooks/useFollow"; +import { useChat } from "../context/ChatContext"; // Lazy load the CheckoutForm component const CheckoutForm = lazy(() => import("../components/Checkout/CheckoutForm")); @@ -30,14 +30,13 @@ const VideoPage: React.FC = ({ streamerId }) => { const { streamerName } = useParams<{ streamerName: string }>(); const [streamData, setStreamData] = useState(); const [viewerCount, setViewerCount] = useState(0); - const [isChatOpen, setIsChatOpen] = useState(true); const { showSideBar } = useSidebar(); const { isFollowing, checkFollowStatus, followUser, unfollowUser } = useFollow(); const { showAuthModal, setShowAuthModal } = useAuthModal(); const [isStripeReady, setIsStripeReady] = useState(false); const [showCheckout, setShowCheckout] = useState(false); - // const showReturn = window.location.search.includes("session_id"); //! Not used + const { showChat } = useChat(); const navigate = useNavigate(); const [isSubscribed, setIsSubscribed] = useState(false); const [timeStarted, setTimeStarted] = useState(""); @@ -55,6 +54,9 @@ const VideoPage: React.FC = ({ streamerId }) => { }; }, [showCheckout]); + // Increment minutes of stream time every minute + useEffect; + useEffect(() => { // Fetch stream data for this streamer fetch(`/api/streams/${streamerId}/data`).then((res) => { @@ -72,20 +74,6 @@ const VideoPage: React.FC = ({ streamerId }) => { }; setStreamData(transformedData); - const time = Math.floor( - (Date.now() - new Date(data.start_time).getTime()) / 60000 // Convert to minutes - ); - - if (time < 60) setTimeStarted(`${time}m ago`); - else if (time < 1440) - setTimeStarted(`${Math.floor(time / 60)}h ${time % 60}m ago`); - else - setTimeStarted( - `${Math.floor(time / 1440)}d ${Math.floor((time % 1440) / 60)}h ${ - time % 60 - }m ago` - ); - // Check if the logged-in user is following this streamer if (isLoggedIn) checkFollowStatus(data.username); }) @@ -95,20 +83,42 @@ const VideoPage: React.FC = ({ streamerId }) => { }); }, [streamerId]); - // Keyboard shortcut to toggle chat + // Time counter using DD:HH:MM:SS format useEffect(() => { - const handleKeyPress = (e: KeyboardEvent) => { - if (e.key === "c" && document.activeElement == document.body) { - setIsChatOpen((prev) => !prev); - } + if (!streamData?.startTime) return; + + // Initial calculation + const startTime = new Date(streamData.startTime).getTime(); + + const calculateTimeDifference = () => { + // Get the difference in seconds + const diffInSeconds = Math.floor((Date.now() - startTime) / 1000); + + // Calculate days, hours, minutes, seconds + const days = Math.floor(diffInSeconds / 86400); + const hours = Math.floor((diffInSeconds % 86400) / 3600); + const minutes = Math.floor((diffInSeconds % 3600) / 60); + const seconds = diffInSeconds % 60; + + // Format as DD:HH:MM:SS + setTimeStarted( + `${days.toString().padStart(2, "0")}:${hours + .toString() + .padStart(2, "0")}:${minutes.toString().padStart(2, "0")}:${seconds + .toString() + .padStart(2, "0")}` + ); }; - document.addEventListener("keydown", handleKeyPress); + // Calculate immediately + calculateTimeDifference(); - return () => { - document.removeEventListener("keydown", handleKeyPress); - }; - }, []); + // Set up interval to update every second + const intervalId = setInterval(calculateTimeDifference, 1000); + + // Clean up interval on component unmount + return () => clearInterval(intervalId); + }, [streamData?.startTime]); // Re-run if startTime changes // Load Stripe in the background when component mounts useEffect(() => { @@ -119,10 +129,6 @@ const VideoPage: React.FC = ({ streamerId }) => { loadStripe(); }, []); - const toggleChat = () => { - setIsChatOpen((prev) => !prev); - }; - // Checks if user is subscribed useEffect(() => { fetch(`/api/user/subscription/${streamerName}/expiration`) @@ -138,24 +144,11 @@ const VideoPage: React.FC = ({ streamerId }) => { return ( - {/* Toggle Button for Chat */} - - {isChatOpen ? "Hide Chat" : "Show Chat"} - - - Press C - - -
= ({ streamerId }) => { {/* Streamer Info */}
-
+
{!isFollowing ? ( ) : ( )}
@@ -237,8 +231,7 @@ const VideoPage: React.FC = ({ streamerId }) => {
-
- Started +
{streamData ? timeStarted : "Loading..."}