From 01a69e2b6bac791e11434b701c9a5a8091d94a41 Mon Sep 17 00:00:00 2001 From: Justin Xiao Date: Mon, 17 Jul 2023 22:20:49 +0800 Subject: [PATCH] refactor: optimize and refactor codebase - Extracte chatroom titile as a component - Update introduction in entry page - add me attribute avioding in SocketMessage --- frontend/src/app/home/page.tsx | 143 ++++--------------- frontend/src/app/page.tsx | 21 ++- frontend/src/components/ChatroomTitle.tsx | 160 ++++++++++++++++++++++ frontend/src/config/index.ts | 2 +- frontend/src/types/next.ts | 10 ++ 5 files changed, 218 insertions(+), 118 deletions(-) create mode 100644 frontend/src/components/ChatroomTitle.tsx diff --git a/frontend/src/app/home/page.tsx b/frontend/src/app/home/page.tsx index 9a7aa6e..c245fb5 100644 --- a/frontend/src/app/home/page.tsx +++ b/frontend/src/app/home/page.tsx @@ -1,34 +1,20 @@ "use client"; import { - Button, createStyles, Card, Container, - Group, Text, Divider, - Input, ScrollArea, TextInput, ActionIcon, - Popover, - CopyButton, - Tooltip, } from "@mantine/core"; -import { - IconArrowRight, - IconCheck, - IconCopy, - IconSettings, - IconEdit, - IconPlugOff, -} from "@tabler/icons-react"; -import NameModal from "@/components/NameModal"; -import { useDisclosure } from "@mantine/hooks"; +import { IconArrowRight } from "@tabler/icons-react"; import useSocketStore from "@/store/socket"; import { useEffect, useRef, useState, useLayoutEffect } from "react"; -import useBasicStore from "@/store/basic"; -import { SocketMessage } from "@/types/next"; +import { MessageWithMe, SocketMessage } from "@/types/next"; +import { toast } from "react-hot-toast"; +import ChatroomTitle from "@/components/ChatRoomTitle"; const useStyles = createStyles((theme) => ({ rightMessageField: { @@ -64,45 +50,42 @@ const useStyles = createStyles((theme) => ({ })); export default function Home() { - const storeName = useBasicStore((state) => state.name); - const { socket, emit, disconnect } = useSocketStore((state) => state); + const { socket, connect, emit } = useSocketStore((state) => state); // deconstructing socket and its method from socket store - const chatViewportRef = useRef(null); - const inputRef = useRef(null); - const [name, setName] = useState(null); // avoiding Next.js hydration error - const [socketId, setSocketId] = useState(); // avoiding Next.js hydration error - const [targetSocketId, setTargetSocketId] = useState(""); - const [message, setMessage] = useState(""); - const [messages, setMessages] = useState([]); + const chatViewportRef = useRef(null); // binding chat viewport ref + const messageInputRef = useRef(null); // binding message input ref + + const [targetSocketId, setTargetSocketId] = useState(""); // target socket id input value + const [message, setMessage] = useState(""); // message input value + const [messages, setMessages] = useState([]); // show messages on ScrollArea - const [opened, { open, close }] = useDisclosure(false); const { classes } = useStyles(); const scrollToBottom = () => { - console.log("scrollToBottom", chatViewportRef.current?.scrollHeight); chatViewportRef?.current?.scrollTo({ top: chatViewportRef.current.scrollHeight, behavior: "smooth", }); }; const sendMessage = () => { - if (!socketId || !message) return; - emit("message", { from: socketId, to: targetSocketId, timestamp: Date.now(), message }); + if (!message) return toast.error("Please enter a message"); + if (!socket?.connected) return toast.error("Please reconnect server first"); + if (!targetSocketId) return toast.error("Please enter a target socket id"); + emit("message", { from: socket?.id, to: targetSocketId, timestamp: Date.now(), message }); setMessage(""); - inputRef.current?.focus(); + messageInputRef.current?.focus(); }; useEffect(() => { - setName(storeName); - if (!storeName) open(); + connect(); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [storeName]); + }, []); useEffect(() => { - setSocketId(socket?.id); + console.log("socket", socket?.id); socket?.on("message", (message: SocketMessage) => { - console.log("message", message); - setMessages((state) => [...state, message]); + // console.log("message", message); + setMessages((state) => [...state, { ...message, me: message.from === socket?.id }]); }); return () => { socket?.off("message"); @@ -110,88 +93,24 @@ export default function Home() { }, [socket]); useLayoutEffect(() => { - scrollToBottom(); // DOM更新完畢後,才執行scrollToBottom + scrollToBottom(); // Execute after DOM render }, [messages]); return ( <> - - - - {name} - - - - - - - - - - SocketID - {socketId && ( - - {({ copied, copy }) => ( - - - {copied ? ( - - ) : ( - - )} - - - )} - - )} - - - Actions - - - - - - - - - - - - - - - - - - To: - setTargetSocketId(e.target.value)} - /> - - - + setTargetSocketId(e.target.value)} + /> {messages.map((message, index) => { return (
{message.message} @@ -213,7 +130,7 @@ export default function Home() { setMessage(e.target.value)} mt={10} @@ -232,8 +149,6 @@ export default function Home() { /> - - ); } diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx index 7d03b7f..4b7fcbd 100644 --- a/frontend/src/app/page.tsx +++ b/frontend/src/app/page.tsx @@ -1,5 +1,5 @@ "use client"; -import { Container, Title, createStyles, rem } from "@mantine/core"; +import { Container, Title, createStyles, rem, Text, Code, Mark } from "@mantine/core"; import { Button } from "@mantine/core"; import { useRouter } from "next/navigation"; @@ -59,8 +59,23 @@ export default function Home() { > Tiny Socket.io demo - Based on Next.js 13, Socket.io. - + + Based on <a href="https://nextjs.org/">Next.js13</a>, + <a href="https://mantine.dev/">Mantine</a>, + <a href="https://socket.io/">Socket.io</a>, + <a href="https://zustand-demo.pmnd.rs/">Zustand</a>. + + + This repo implements a simple chat app with Socket.io and Next.js 13. +
+ You can use npm run dev to access + Next.js mock socket.io server + to test the app locally. +
+ Or use npm run prod to access the + express server + in the backend folder to test like the production scenario. +