From acd625b45f19c71b6e359668dac99b3caa2991de Mon Sep 17 00:00:00 2001 From: Johnson Mao <tutelary.maomao@gmail.com> Date: Sun, 20 Oct 2024 14:22:20 +0800 Subject: [PATCH] refactor: website style --- components/rooms/GameWindow.tsx | 8 +-- components/shared/Chat/v2/Chat.tsx | 4 +- containers/layout/AppLayout.tsx | 27 ++++++-- hooks/useChat.ts | 7 +- hooks/useUser.ts | 17 +++-- pages/_document.tsx | 2 +- pages/auth/token/[token].tsx | 2 +- pages/index.tsx | 8 +-- pages/rooms/[roomId]/index.tsx | 107 +++++++++++++++++++---------- styles/global.css | 4 ++ 10 files changed, 123 insertions(+), 63 deletions(-) diff --git a/components/rooms/GameWindow.tsx b/components/rooms/GameWindow.tsx index bd666ea9..235ff274 100644 --- a/components/rooms/GameWindow.tsx +++ b/components/rooms/GameWindow.tsx @@ -1,14 +1,12 @@ type GameWindowProps = { + className?: string; gameUrl: string; }; -export default function GameWindow({ gameUrl }: GameWindowProps) { +export default function GameWindow({ className, gameUrl }: GameWindowProps) { return ( <div> - <iframe - className="absolute inset-0 m-auto w-[95%] h-[95vh] border" - src={gameUrl} - > + <iframe className={className} src={gameUrl}> <p>Your browser does not support iframes.</p> </iframe> </div> diff --git a/components/shared/Chat/v2/Chat.tsx b/components/shared/Chat/v2/Chat.tsx index 3e4567ad..ec4faee8 100644 --- a/components/shared/Chat/v2/Chat.tsx +++ b/components/shared/Chat/v2/Chat.tsx @@ -15,6 +15,7 @@ export type ChatProps = { friendList: FriendType[]; roomMessages: MessageType[]; className?: string; + defaultTarget?: ChatTab["id"]; onSubmit: (message: Pick<MessageType, "content" | "target">) => void; }; @@ -25,11 +26,12 @@ export default function Chat({ friendList, roomMessages, className, + defaultTarget, onSubmit, }: Readonly<ChatProps>) { const [messages, setMessages] = useState(lobbyMessages); const [target, setTarget] = useState<[ChatTab["id"], string | null]>([ - "lobby", + defaultTarget || "lobby", null, ]); const [activeTab, friendRoom] = target; diff --git a/containers/layout/AppLayout.tsx b/containers/layout/AppLayout.tsx index 3a1746d4..2b01e9a5 100644 --- a/containers/layout/AppLayout.tsx +++ b/containers/layout/AppLayout.tsx @@ -1,38 +1,51 @@ -import { PropsWithChildren } from "react"; +import { PropsWithChildren, useEffect } from "react"; +import { useRouter } from "next/router"; import Header from "@/components/shared/Header"; import Sidebar from "@/components/shared/Sidebar"; import Chat from "@/components/shared/Chat/v2/Chat"; import useChat from "@/hooks/useChat"; export default function Layout({ children }: PropsWithChildren) { + const router = useRouter(); const { roomId, messageList, isChatVisible, + openChat, toggleChatVisibility, handleSubmitText, } = useChat(); + const roomPathname = "/rooms/[roomId]"; + + useEffect(() => { + if (router.pathname === roomPathname) { + openChat(); + } + }, [router.pathname, openChat]); return ( <> <Header - className="sticky top-0 z-40" + className="fixed top-0 inset-x-0 z-40" onClickChatButton={toggleChatVisibility} /> - <div className="ml-2 mr-4 my-6 flex grow"> - <div className="shrink-0"> - <Sidebar className="sticky top-20 z-30 h-main-height" /> + <div className="pl-2 pt-20 flex grow"> + <div className="shrink-0 w-18"> + <Sidebar className="fixed top-20 bottom-6 z-30" /> </div> <main className="grow overflow-x-hidden">{children}</main> {isChatVisible && ( - <div className="shrink-0"> + <div className="shrink-0 w-80 mr-4"> <Chat - className="sticky top-20 z-30 h-main-height" + className="fixed top-20 bottom-6 z-30" userId="" roomId={roomId} friendList={[]} lobbyMessages={[]} roomMessages={messageList} + defaultTarget={ + router.pathname === roomPathname ? "room" : "lobby" + } onSubmit={handleSubmitText} /> </div> diff --git a/hooks/useChat.ts b/hooks/useChat.ts index 7770dc1f..1e4614a9 100644 --- a/hooks/useChat.ts +++ b/hooks/useChat.ts @@ -1,4 +1,4 @@ -import { useEffect, useState } from "react"; +import { useCallback, useEffect, useState } from "react"; import type { MessageType } from "@/components/shared/Chat/v2/ChatMessages"; import useChatroom from "./context/useChatroom"; import useSocketCore from "./context/useSocketCore"; @@ -17,6 +17,10 @@ export default function useChat() { setIsChatVisible((prev) => !prev); }; + const openChat = useCallback(() => { + setIsChatVisible(true); + }, []); + // join chatroom by roomId useEffect(() => { if (!roomId) return; @@ -48,6 +52,7 @@ export default function useChat() { roomId, messageList, isChatVisible, + openChat, sendChatMessage, toggleChatVisibility, handleSubmitText, diff --git a/hooks/useUser.ts b/hooks/useUser.ts index 6fca55d4..5c654828 100644 --- a/hooks/useUser.ts +++ b/hooks/useUser.ts @@ -58,13 +58,16 @@ const useUser = () => { return roomIdOperator.get(); }; - const updateRoomId = (roomId?: string) => { - if (roomId) { - roomIdOperator.set(roomId); - } else { - roomIdOperator.remove(); - } - }; + const updateRoomId = useCallback( + (roomId?: string) => { + if (roomId) { + roomIdOperator.set(roomId); + } else { + roomIdOperator.remove(); + } + }, + [roomIdOperator] + ); return { getLoginEndpoint, diff --git a/pages/_document.tsx b/pages/_document.tsx index 22632ceb..ed031e8b 100644 --- a/pages/_document.tsx +++ b/pages/_document.tsx @@ -12,7 +12,7 @@ export default function Document() { <meta name="og:title" content={siteTitle} /> <meta name="twitter:card" content="summary_large_image" /> </Head> - <body className="body-bg text-primary-200"> + <body className="body-bg min-h-screen text-primary-200"> <Main /> <NextScript /> </body> diff --git a/pages/auth/token/[token].tsx b/pages/auth/token/[token].tsx index 75531572..837e1b70 100644 --- a/pages/auth/token/[token].tsx +++ b/pages/auth/token/[token].tsx @@ -18,7 +18,7 @@ const Token: NextPageWithProps = () => { } }, [token, login, push]); - return <h1>{token}</h1>; + return <></>; }; Token.getLayout = (page: ReactElement) => page; diff --git a/pages/index.tsx b/pages/index.tsx index 5f67e110..91ae05fb 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -71,7 +71,7 @@ function CarouselCard({ draggable={false} priority fill - objectFit="cover" + className="object-cover" onError={onImageError} /> <div className="m-4 flex gap-4"> @@ -106,7 +106,7 @@ function CarouselCard({ draggable={false} priority fill - objectFit="cover" + className="object-cover" onError={onImageError} /> )} @@ -181,12 +181,12 @@ const TabPaneContent = ({ > <picture className="relative aspect-game-cover overflow-hidden"> <Image - src={game.img} + src={game.img || gameDefaultCoverImg.src} alt={game.name} draggable={false} priority fill - objectFit="cover" + className="object-cover" onError={onImageError} /> </picture> diff --git a/pages/rooms/[roomId]/index.tsx b/pages/rooms/[roomId]/index.tsx index b0ae9c3f..f859f768 100644 --- a/pages/rooms/[roomId]/index.tsx +++ b/pages/rooms/[roomId]/index.tsx @@ -1,6 +1,7 @@ -import { useEffect, useState } from "react"; -import { useRouter } from "next/router"; +import { ReactEventHandler, useEffect, useState } from "react"; import { GetStaticProps, GetStaticPaths } from "next"; +import { useRouter } from "next/router"; +import Image from "next/image"; import { serverSideTranslations } from "next-i18next/serverSideTranslations"; import RoomUserCardList from "@/components/rooms/RoomUserCardList"; import RoomButtonGroup from "@/components/rooms/RoomButtonGroup"; @@ -22,10 +23,18 @@ import { playerCancelReady, startGame, } from "@/requests/rooms"; +import { GameType, getAllGamesEndpoint } from "@/requests/games"; import useUser from "@/hooks/useUser"; +import gameDefaultCoverImg from "@/public/images/game-default-cover.png"; type User = Omit<RoomInfo.User, "isReady">; +const onImageError: ReactEventHandler<HTMLImageElement> = (e) => { + if (e.target instanceof HTMLImageElement) { + e.target.src = gameDefaultCoverImg.src; + } +}; + export default function Room() { const { roomInfo, @@ -44,16 +53,27 @@ export default function Room() { const { fetch } = useRequest(); const { query, replace } = useRouter(); const [gameUrl, setGameUrl] = useState(""); + const [gameList, setGameList] = useState<GameType[]>([]); const roomId = query.roomId as string; const player = roomInfo.players.find( (player) => player.id === currentUser?.id ); const isHost = roomInfo.host.id === currentUser?.id; + const gameInfo = gameList.find((game) => game.id === roomInfo.game.id); + + useEffect(() => { + fetch(getAllGamesEndpoint()).then(setGameList); + }, [fetch]); useEffect(() => { async function getRoomInfo() { - const roomInfo = await fetch(getRoomInfoEndpoint(roomId)); - initializeRoom(roomInfo); + try { + const roomInfo = await fetch(getRoomInfoEndpoint(roomId)); + initializeRoom(roomInfo); + } catch (err) { + updateRoomId(); + replace("/rooms"); + } } getRoomInfo(); @@ -119,6 +139,7 @@ export default function Room() { socket, currentUser?.id, roomId, + updateRoomId, addPlayer, removePlayer, updateUserReadyStatus, @@ -211,40 +232,54 @@ export default function Room() { }; return ( - <section className="px-6"> - <div className="relative w-full h-[280px] overflow-hidden"> - <img - className="absolute inset-0 w-full h-full object-cover" - src="https://images.unsplash.com/photo-1601987177651-8edfe6c20009?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2070&q=80" - alt="cover" + <section className="px-4"> + {gameUrl ? ( + <GameWindow + className="h-[calc(100dvh-104px)] w-full" + gameUrl={gameUrl} /> - <div className="absolute top-0 left-0 right-0 bottom-0 bg-gradient-to-t from-[#0f0919] to-50% to-[#170D2500]"></div> - <div className="m-2 py-1 px-2 w-fit bg-gray-950/50 backdrop-blur-sm rounded-lg text-sm"> - <RoomBreadcrumb roomInfo={roomInfo} /> - </div> - <div className="m-2 py-1 px-2 w-fit bg-gray-950/50 backdrop-blur-sm rounded-lg text-sm"> - {roomInfo.isLocked ? "非公開" : "公開"} - </div> - <div className="m-2 py-1 px-2 w-fit bg-gray-950/50 backdrop-blur-sm rounded-lg text-sm"> - {roomInfo.currentPlayers} / {roomInfo.maxPlayers} 人 - </div> - <div className="absolute bottom-0 right-0 flex items-center"> - <RoomButtonGroup - onToggleReady={handleToggleReady} - onClickClose={handleClickClose} - onClickLeave={handleLeave} - onClickStart={handleStart} - isHost={isHost} - isReady={isHost || !!player?.isReady} + ) : ( + <> + <div className="relative w-full h-[280px] overflow-hidden"> + {roomInfo.currentPlayers && ( + <Image + src={gameInfo?.img || gameDefaultCoverImg.src} + alt={gameInfo?.name || "default game cover"} + draggable={false} + priority + fill + className="object-cover" + onError={onImageError} + /> + )} + <div className="absolute top-0 left-0 right-0 bottom-0 bg-gradient-to-t from-[#0f0919] to-50% to-[#170D2500]"></div> + <div className="m-2 py-1 px-2 w-fit bg-gray-950/50 backdrop-blur-sm rounded-lg text-sm"> + <RoomBreadcrumb roomInfo={roomInfo} /> + </div> + <div className="m-2 py-1 px-2 w-fit bg-gray-950/50 backdrop-blur-sm rounded-lg text-sm"> + {roomInfo.isLocked ? "非公開" : "公開"} + </div> + <div className="m-2 py-1 px-2 w-fit bg-gray-950/50 backdrop-blur-sm rounded-lg text-sm"> + {roomInfo.currentPlayers} / {roomInfo.maxPlayers} 人 + </div> + <div className="absolute bottom-0 right-0 flex items-center"> + <RoomButtonGroup + onToggleReady={handleToggleReady} + onClickClose={handleClickClose} + onClickLeave={handleLeave} + onClickStart={handleStart} + isHost={isHost} + isReady={isHost || !!player?.isReady} + /> + </div> + </div> + <RoomUserCardList + roomInfo={roomInfo} + currentUserId={currentUser?.id} + onKickUser={handleClickKick} /> - </div> - </div> - <RoomUserCardList - roomInfo={roomInfo} - currentUserId={currentUser?.id} - onKickUser={handleClickKick} - /> - {gameUrl && <GameWindow gameUrl={gameUrl} />} + </> + )} <Popup /> </section> ); diff --git a/styles/global.css b/styles/global.css index b5c61c95..83cb701b 100644 --- a/styles/global.css +++ b/styles/global.css @@ -1,3 +1,7 @@ @tailwind base; @tailwind components; @tailwind utilities; + +html { + color-scheme: dark; +}