From 9d10f4a9dc1a13133ee6612ecce3c9bda08cea60 Mon Sep 17 00:00:00 2001 From: Johnson Mao Date: Sun, 17 Nov 2024 21:44:35 +0800 Subject: [PATCH 1/4] refactor(shared): chat components --- .../shared/Chat/{v2 => }/Chat.stories.tsx | 3 +- components/shared/Chat/Chat.tsx | 134 ++++++++++++++++++ .../shared/Chat/{v2 => }/ChatFriendList.tsx | 10 +- .../shared/Chat/{v2 => }/ChatHeader.tsx | 0 components/shared/Chat/{v2 => }/ChatInput.tsx | 10 +- .../shared/Chat/{v2 => }/ChatMessages.tsx | 13 +- .../shared/Chat/{v2 => }/__mocks__/mock.tsx | 55 ++++--- components/shared/Chat/index.ts | 1 + components/shared/Chat/v2/Chat.tsx | 122 ---------------- containers/layout/AppLayout.tsx | 7 +- containers/provider/AuthProvider.tsx | 4 +- containers/provider/ChatroomProvider.tsx | 7 +- contexts/ChatroomContext.tsx | 2 +- hooks/useChat.ts | 19 +-- hooks/useCookie.ts | 10 +- hooks/useUser.ts | 19 ++- 16 files changed, 231 insertions(+), 185 deletions(-) rename components/shared/Chat/{v2 => }/Chat.stories.tsx (92%) create mode 100644 components/shared/Chat/Chat.tsx rename components/shared/Chat/{v2 => }/ChatFriendList.tsx (89%) rename components/shared/Chat/{v2 => }/ChatHeader.tsx (100%) rename components/shared/Chat/{v2 => }/ChatInput.tsx (87%) rename components/shared/Chat/{v2 => }/ChatMessages.tsx (88%) rename components/shared/Chat/{v2 => }/__mocks__/mock.tsx (54%) create mode 100644 components/shared/Chat/index.ts delete mode 100644 components/shared/Chat/v2/Chat.tsx diff --git a/components/shared/Chat/v2/Chat.stories.tsx b/components/shared/Chat/Chat.stories.tsx similarity index 92% rename from components/shared/Chat/v2/Chat.stories.tsx rename to components/shared/Chat/Chat.stories.tsx index 1f0a7e92..95e4abff 100644 --- a/components/shared/Chat/v2/Chat.stories.tsx +++ b/components/shared/Chat/Chat.stories.tsx @@ -1,10 +1,10 @@ import { Meta, StoryObj } from "@storybook/react"; -import Chat from "./Chat"; import { mockFriendList, mockLobbyMessages, mockRoomMessages, } from "./__mocks__/mock"; +import Chat from "."; const meta: Meta = { title: "Room/Chat", @@ -32,7 +32,6 @@ export const Playground: Story = { }; Playground.args = { - userId: "玩家名字3", friendList: mockFriendList, lobbyMessages: mockLobbyMessages, roomMessages: mockRoomMessages, diff --git a/components/shared/Chat/Chat.tsx b/components/shared/Chat/Chat.tsx new file mode 100644 index 00000000..22efea65 --- /dev/null +++ b/components/shared/Chat/Chat.tsx @@ -0,0 +1,134 @@ +import { useEffect, useState } from "react"; +import Icon from "@/components/shared/Icon"; +import { cn } from "@/lib/utils"; +import useAuth from "@/hooks/context/useAuth"; +import ChatHeader, { ChatTab } from "./ChatHeader"; +import ChatMessages from "./ChatMessages"; +import ChatFriendList, { FriendType, getTargetUser } from "./ChatFriendList"; +import ChatInput from "./ChatInput"; +import type { MessageType } from "./ChatMessages"; +// import { createMockFriendMessages } from "./__mocks__/mock"; + +type ChatProps = { + roomId?: string; + lobbyMessages: MessageType[]; + friendList: FriendType[]; + roomMessages: MessageType[]; + className?: string; + defaultTarget?: ChatTab["id"]; + onSubmit: (message: Pick) => void; +}; + +export default function Chat({ + roomId, + lobbyMessages, + friendList, + roomMessages, + className, + defaultTarget, + onSubmit, +}: Readonly) { + const { currentUser } = useAuth(); + const [messages, setMessages] = useState(lobbyMessages); + const [target, setTarget] = useState<[ChatTab["id"], string | null]>([ + defaultTarget || "lobby", + null, + ]); + const [activeTab, friendRoom] = target; + const isFriendList = activeTab === "friend" && !friendRoom; + // const notifications = friendList.filter((item) => !item.isRead).length; + const chatTabs: ChatTab[] = ( + [ + { id: "lobby", text: "遊戲大廳" }, + { id: "room", text: "遊戲房" }, + ] as const + ).filter((tab) => (roomId ? true : tab.id !== "room")); + + useEffect(() => { + const mockMessages = { + lobby: lobbyMessages, + room: roomMessages, + friend: null, + }[activeTab]; + + if (mockMessages) { + setMessages(mockMessages); + return; + } + + // const friend = friendList.find((item) => item.target === friendRoom); + + // if (!friend) { + // setMessages([]); + // return; + // } + + // // Call friend chat room message API? + // setMessages(createMockFriendMessages(friend)); + }, [activeTab, friendRoom, friendList, roomMessages, lobbyMessages]); + + const handleToggleTab = (id: ChatTab["id"]) => setTarget([id, null]); + + const handleToggleTarget = (id: FriendType["target"]) => + setTarget(["friend", id]); + + const handleSubmit = (content: string) => { + switch (activeTab) { + case "friend": + if (!friendRoom) return; + onSubmit({ target: `TODO:Other`, content }); + break; + case "room": + if (!roomId) return; + onSubmit({ target: `ROOM_${roomId}`, content }); + break; + case "lobby": + onSubmit({ target: "LOBBY", content }); + break; + default: + break; + } + }; + + return ( + currentUser && ( +
+
+ +
+ {isFriendList ? ( + + ) : ( + <> + {friendRoom && ( +
+ + {getTargetUser(friendRoom, currentUser.id)} +
+ )} + + + )} +
+ +
+
+ ) + ); +} diff --git a/components/shared/Chat/v2/ChatFriendList.tsx b/components/shared/Chat/ChatFriendList.tsx similarity index 89% rename from components/shared/Chat/v2/ChatFriendList.tsx rename to components/shared/Chat/ChatFriendList.tsx index 3966ceb7..da6d982e 100644 --- a/components/shared/Chat/v2/ChatFriendList.tsx +++ b/components/shared/Chat/ChatFriendList.tsx @@ -1,8 +1,12 @@ -import Avatar from "../../Avatar"; +import type { UserInfo } from "@/requests/users"; + +import Avatar from "../Avatar"; + +type User = Pick; export type FriendType = { target: string; - lastUser: string | null; + lastUser: User | null; lastContent: string | null; isRead: boolean; }; @@ -31,7 +35,7 @@ export default function ChatFriendList({
{friendList.map(({ target, lastUser, lastContent, isRead }) => { const targetUser = getTargetUser(target, userId); - const isMe = lastUser === userId; + const isMe = lastUser?.id === userId; return (
) => void; + onSubmit: (content: string) => void; }; export default function ChatInput({ - roomId, disabled, onSubmit, }: Readonly) { @@ -22,11 +19,8 @@ export default function ChatInput({ if (disabled) return; const content = value.trim(); if (!content) return; + onSubmit(content); setValue(""); - onSubmit({ - target: roomId ? `ROOM_${roomId}` : "TODO:OTHER", - content, - }); }; return ( diff --git a/components/shared/Chat/v2/ChatMessages.tsx b/components/shared/Chat/ChatMessages.tsx similarity index 88% rename from components/shared/Chat/v2/ChatMessages.tsx rename to components/shared/Chat/ChatMessages.tsx index 2cd7f02d..d55cfdc5 100644 --- a/components/shared/Chat/v2/ChatMessages.tsx +++ b/components/shared/Chat/ChatMessages.tsx @@ -4,10 +4,11 @@ import Avatar from "@/components/shared/Avatar"; import type { UserInfo } from "@/requests/users"; type User = Pick; +type System = { id: "SYSTEM"; nickname: "SYSTEM" }; export type MessageType = { /** The source of the message. */ - from: "SYSTEM" | User; + from: System | User; /** The content of the user message. */ content: string; /** The recipient of the message. @@ -40,14 +41,14 @@ export default function ChatMessages({
- {messages.map(({ from, content }, index) => { - const isSystem = from === "SYSTEM"; - const isMe = from === userId; - const isSameUser = from === messages[index + 1]?.from; + {messages.map(({ from, content, timestamp }, index) => { + const isSystem = from.id === "SYSTEM"; + const isMe = from.id === userId; + const isSameUser = from.id === messages[index + 1]?.from.id; return (
{ if (!lastUser || !lastContent) return []; - return [{ from: lastUser, target, content: lastContent }]; + return [{ from: lastUser, target, content: lastContent, timestamp: "" }]; }; diff --git a/components/shared/Chat/index.ts b/components/shared/Chat/index.ts new file mode 100644 index 00000000..308d054f --- /dev/null +++ b/components/shared/Chat/index.ts @@ -0,0 +1 @@ +export { default } from "./Chat"; diff --git a/components/shared/Chat/v2/Chat.tsx b/components/shared/Chat/v2/Chat.tsx deleted file mode 100644 index 67b48af2..00000000 --- a/components/shared/Chat/v2/Chat.tsx +++ /dev/null @@ -1,122 +0,0 @@ -import { useEffect, useState } from "react"; -import Icon from "@/components/shared/Icon"; -import ChatHeader, { ChatTab } from "./ChatHeader"; -import ChatMessages from "./ChatMessages"; -import ChatFriendList, { FriendType, getTargetUser } from "./ChatFriendList"; -import ChatInput from "./ChatInput"; -import type { MessageType } from "./ChatMessages"; -import { createMockFriendMessages } from "./__mocks__/mock"; -import { cn } from "@/lib/utils"; - -export type ChatProps = { - userId: string; - roomId?: string; - lobbyMessages: MessageType[]; - friendList: FriendType[]; - roomMessages: MessageType[]; - className?: string; - defaultTarget?: ChatTab["id"]; - onSubmit: (message: Pick) => void; -}; - -export default function Chat({ - userId, - roomId, - lobbyMessages, - friendList, - roomMessages, - className, - defaultTarget, - onSubmit, -}: Readonly) { - const [messages, setMessages] = useState(lobbyMessages); - const [target, setTarget] = useState<[ChatTab["id"], string | null]>([ - defaultTarget || "lobby", - null, - ]); - const [activeTab, friendRoom] = target; - const isFriendList = activeTab === "friend" && !friendRoom; - const notifications = friendList.filter((item) => !item.isRead).length; - const chatTabs: ChatTab[] = [ - { id: "lobby", text: "遊戲大廳" }, - { id: "friend", text: "好友聊天", notifications }, - { id: "room", text: "遊戲房" }, - ]; - - useEffect(() => { - const mockMessages = { - lobby: lobbyMessages, - room: roomMessages, - friend: null, - }[activeTab]; - - if (mockMessages) { - setMessages(mockMessages); - return; - } - - const friend = friendList.find((item) => item.target === friendRoom); - - if (!friend) { - setMessages([]); - return; - } - - // Call friend chat room message API? - setMessages(createMockFriendMessages(friend)); - }, [activeTab, friendRoom, friendList, roomMessages, lobbyMessages]); - - const handleToggleTab = (id: ChatTab["id"]) => setTarget([id, null]); - - const handleToggleTarget = (id: FriendType["target"]) => - setTarget(["friend", id]); - - const handleSubmit = (message: Pick) => { - if (activeTab === "friend" && !friendRoom) return; - onSubmit(message); - }; - - return ( -
-
- -
- {isFriendList ? ( - - ) : ( - <> - {friendRoom && ( -
- - {getTargetUser(friendRoom, userId)} -
- )} - - - )} -
- -
-
- ); -} diff --git a/containers/layout/AppLayout.tsx b/containers/layout/AppLayout.tsx index 0beedee0..5119ced4 100644 --- a/containers/layout/AppLayout.tsx +++ b/containers/layout/AppLayout.tsx @@ -2,7 +2,7 @@ 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 Chat from "@/components/shared/Chat"; import useChat from "@/hooks/useChat"; import Head from "next/head"; import SearchBar from "@/components/shared/SearchBar"; @@ -18,7 +18,7 @@ export default function AppLayout({ children }: PropsWithChildren) { isChatVisible, openChat, toggleChatVisibility, - handleSubmitText, + sendChatMessage, } = useChat(); const roomPathname = "/rooms/[roomId]"; const isSearchBarVisible = ["/", "/rooms"].includes(router.pathname); @@ -70,7 +70,6 @@ export default function AppLayout({ children }: PropsWithChildren) {
)} diff --git a/containers/provider/AuthProvider.tsx b/containers/provider/AuthProvider.tsx index 038fdcae..cc8289bc 100644 --- a/containers/provider/AuthProvider.tsx +++ b/containers/provider/AuthProvider.tsx @@ -17,7 +17,9 @@ const AuthProvider: FC = ({ children }) => { setToken, currentUser, setCurrentUser: (currentUser: UserInfo | null) => - setCurrentUser(currentUser), + setCurrentUser((pre) => + currentUser ? { ...pre, ...currentUser } : null + ), }} > {children} diff --git a/containers/provider/ChatroomProvider.tsx b/containers/provider/ChatroomProvider.tsx index 446146bf..f66d81da 100644 --- a/containers/provider/ChatroomProvider.tsx +++ b/containers/provider/ChatroomProvider.tsx @@ -3,16 +3,14 @@ import { PropsWithChildren, useCallback, useEffect, useState } from "react"; import useAuth from "@/hooks/context/useAuth"; import useSocketCore from "@/hooks/context/useSocketCore"; import { SOCKET_EVENT } from "@/contexts/SocketContext"; -import type { MessageType } from "@/components/shared/Chat/v2/ChatMessages"; +import type { MessageType } from "@/components/shared/Chat/ChatMessages"; export type ChatroomContextType = ReturnType; function useChatroomCore() { const { socket } = useSocketCore(); const { currentUser } = useAuth(); - const [lastMessage, setLastMessage] = useState( - undefined - ); + const [lastMessage, setLastMessage] = useState(null); /** * Dispatches a socket event to the server. @@ -70,6 +68,7 @@ function useChatroomCore() { const leaveChatroom = useCallback( (roomId: string) => { + setLastMessage(null); if (!currentUser) return; if (socket) { const payload = { diff --git a/contexts/ChatroomContext.tsx b/contexts/ChatroomContext.tsx index e1fdff65..ec09a37a 100644 --- a/contexts/ChatroomContext.tsx +++ b/contexts/ChatroomContext.tsx @@ -2,7 +2,7 @@ import { createContext } from "react"; import { ChatroomContextType } from "@/containers/provider/ChatroomProvider"; const defaultValue: ChatroomContextType = { - lastMessage: undefined, + lastMessage: null, sendChatMessage: () => {}, joinChatroom: () => {}, leaveChatroom: () => {}, diff --git a/hooks/useChat.ts b/hooks/useChat.ts index 1e4614a9..558e39b2 100644 --- a/hooks/useChat.ts +++ b/hooks/useChat.ts @@ -1,5 +1,5 @@ import { useCallback, useEffect, useState } from "react"; -import type { MessageType } from "@/components/shared/Chat/v2/ChatMessages"; +import type { MessageType } from "@/components/shared/Chat/ChatMessages"; import useChatroom from "./context/useChatroom"; import useSocketCore from "./context/useSocketCore"; import useUser from "./useUser"; @@ -31,23 +31,13 @@ export default function useChat() { // update message list while received new message from websocket useEffect(() => { - if (!lastMessage || !roomId) return; - if (lastMessage.target === `ROOM_${roomId}`) { + if (roomId && lastMessage) { setMessageList((prev) => [...prev, lastMessage]); + } else if (!roomId) { + setMessageList([]); } }, [lastMessage, roomId]); - const handleSubmitText = ( - message: Pick - ) => { - if (!message.content) return; - const data: Pick = { - content: message.content, - target: message.target, - }; - sendChatMessage(data); - }; - return { roomId, messageList, @@ -55,6 +45,5 @@ export default function useChat() { openChat, sendChatMessage, toggleChatVisibility, - handleSubmitText, }; } diff --git a/hooks/useCookie.ts b/hooks/useCookie.ts index b6bc529b..d51ae70e 100644 --- a/hooks/useCookie.ts +++ b/hooks/useCookie.ts @@ -4,6 +4,7 @@ import cookie from "js-cookie"; enum CookieKey { TOKEN = "_token", ROOM_ID = "_room_id", + GAME_URL = "_game_url", } interface CookieOperator { @@ -42,9 +43,14 @@ const useCookie = () => { return generator(CookieKey.ROOM_ID); }, []); + const gameUrlOperator = useMemo(() => { + return generator(CookieKey.GAME_URL); + }, []); + return { - token: tokenOperator, - roomId: roomIdOperator, + tokenOperator, + roomIdOperator, + gameUrlOperator, }; }; diff --git a/hooks/useUser.ts b/hooks/useUser.ts index 5c654828..1e631ed5 100644 --- a/hooks/useUser.ts +++ b/hooks/useUser.ts @@ -13,7 +13,7 @@ import { useCallback } from "react"; const useUser = () => { const { fetch } = useRequest(); const { setToken: setTokenCtx } = useAuth(); - const { token: tokenOperator, roomId: roomIdOperator } = useCookie(); + const { tokenOperator, roomIdOperator, gameUrlOperator } = useCookie(); const getLoginEndpoint = async (type: LoginType) => { return await fetch(getLoginEndpointReq(type)); @@ -69,6 +69,21 @@ const useUser = () => { [roomIdOperator] ); + const getGameUrl = () => { + return gameUrlOperator.get() || ""; + }; + + const updateGameUrl = useCallback( + (gameUrl?: string) => { + if (gameUrl) { + gameUrlOperator.set(gameUrl); + } else { + gameUrlOperator.remove(); + } + }, + [gameUrlOperator] + ); + return { getLoginEndpoint, getMockToken, @@ -80,6 +95,8 @@ const useUser = () => { getCurrentUser, getRoomId, updateRoomId, + getGameUrl, + updateGameUrl, }; }; From effe6853a84da82de0856aaec97d3114af852bc0 Mon Sep 17 00:00:00 2001 From: Johnson Mao Date: Sun, 17 Nov 2024 21:51:09 +0800 Subject: [PATCH 2/4] fix(user): user info form data flow bug --- components/shared/Header.tsx | 18 ++++-------- components/shared/Toast/constants.ts | 2 +- features/user/components/UserCard.tsx | 2 +- features/user/components/UserInfoForm.tsx | 26 +++++++++++------ pages/rooms/[roomId]/index.tsx | 34 ++++++++++------------- 5 files changed, 39 insertions(+), 43 deletions(-) diff --git a/components/shared/Header.tsx b/components/shared/Header.tsx index 0fcf1b37..401ec33b 100644 --- a/components/shared/Header.tsx +++ b/components/shared/Header.tsx @@ -1,10 +1,9 @@ -import { useEffect, useState } from "react"; +import { useState } from "react"; import Icon, { IconName } from "@/components/shared/Icon"; import Badge from "@/components/shared/Badge"; import { cn } from "@/lib/utils"; import { UserInfoForm } from "@/features/user"; -import useUser from "@/hooks/useUser"; -import { UserInfo } from "@/requests/users"; +import useAuth from "@/hooks/context/useAuth"; import Modal from "./Modal"; import Cover from "./Cover"; @@ -33,13 +32,7 @@ export default function Header({ onClickChatButton, }: Readonly) { const [isUserInfoVisible, setIsUserInfoVisible] = useState(false); - // TODO: 待優化登入就應可以取使用者資料 - const [currentUserInfo, setCurrentUserInfo] = useState(); - const { getCurrentUser } = useUser(); - - useEffect(() => { - getCurrentUser().then((result) => setCurrentUserInfo(result)); - }, []); + const { currentUser } = useAuth(); const buttons: ButtonProps[] = [ { @@ -92,7 +85,7 @@ export default function Header({ ))}
- {isUserInfoVisible && currentUserInfo && ( + {isUserInfoVisible && currentUser && ( setIsUserInfoVisible(false)} + onSuccess={() => setIsUserInfoVisible(false)} /> )} diff --git a/components/shared/Toast/constants.ts b/components/shared/Toast/constants.ts index fa84b00a..3f517c4e 100644 --- a/components/shared/Toast/constants.ts +++ b/components/shared/Toast/constants.ts @@ -3,7 +3,7 @@ import { UseToastOptions } from "./useToast"; export const MAX_TOAST_QUEUE_SIZE = 100; export const MAX_TOAST_MOUNT_SIZE = 6; export const DEFAULT_TOAST_DURATION = 3500; -export const DEFAULT_TOAST_POSITION = "bottom"; +export const DEFAULT_TOAST_POSITION = "top"; export const DEFAULT_TOAST_MANUAL_CLOSE_PLAN = "fullBody"; export const INITIAL_TOAST_POSITION: Record< Required["position"], diff --git a/features/user/components/UserCard.tsx b/features/user/components/UserCard.tsx index b2590606..87679aaf 100644 --- a/features/user/components/UserCard.tsx +++ b/features/user/components/UserCard.tsx @@ -37,7 +37,7 @@ function UserCard({ id, nickname, isSelf, isHost }: Readonly) { )} >
-

非凡之人

+ {/*

非凡之人

*/}

{nickname}

diff --git a/features/user/components/UserInfoForm.tsx b/features/user/components/UserInfoForm.tsx index 66446ea6..b81575b0 100644 --- a/features/user/components/UserInfoForm.tsx +++ b/features/user/components/UserInfoForm.tsx @@ -6,38 +6,46 @@ import Input from "@/components/shared/Input"; import useRequest from "@/hooks/useRequest"; import { UserInfo, putUserinfoEndpoint } from "@/requests/users"; import { useToast } from "@/components/shared/Toast"; +import useAuth from "@/hooks/context/useAuth"; -interface UserInfoFormProps extends UserInfo { +interface UserInfoFormProps { + userInfo: UserInfo; + onSuccess: () => void; onCancel: () => void; } type UserInfoFormErrors = Partial>; function UserInfoForm({ + userInfo, + onSuccess, onCancel, - ...userInfoProps }: Readonly) { const { fetch } = useRequest(); const toast = useToast(); - const [userInfo, setUserInfo] = useState(userInfoProps); + const { setCurrentUser } = useAuth(); + const [data, setData] = useState(userInfo); const [errors, setErrors] = useState({}); const nicknameInputRef = useRef(null); const handleSubmit = async (event: FormEvent) => { event.preventDefault(); - if (Object.keys(errors).length) { + if (Object.values(errors).some(Boolean)) { nicknameInputRef.current?.focus(); return; } try { - await fetch( + const result = await fetch( putUserinfoEndpoint({ - ...userInfo, - nickname: userInfo.nickname.trim(), + ...data, + nickname: data.nickname.trim(), }), { toast: { show: false } } ); + toast({ state: "success", children: "修改成功" }); + setCurrentUser(result); + onSuccess(); } catch (error) { if (error instanceof AxiosError) { toast( @@ -61,7 +69,7 @@ function UserInfoForm({ // Regex pattern for special characters. Allow only alphanumeric and spaces const validNameRegex = /^[a-zA-Z0-9\u4E00-\u9FFF ]+$/; - setUserInfo((prev) => ({ ...prev, nickname })); + setData((prev) => ({ ...prev, nickname })); if (nickname.trim().length === 0) { setErrors((pre) => ({ ...pre, nickname: "不可空白" })); @@ -91,7 +99,7 @@ function UserInfoForm({ onChange={handleNicknameChange} hintText={errors.nickname} error={!!errors.nickname} - value={userInfo.nickname} + value={data.nickname} autoFocus />
diff --git a/pages/rooms/[roomId]/index.tsx b/pages/rooms/[roomId]/index.tsx index 05a276d2..27488086 100644 --- a/pages/rooms/[roomId]/index.tsx +++ b/pages/rooms/[roomId]/index.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from "react"; +import { useEffect, useRef, useState } from "react"; import { GetStaticProps, GetStaticPaths } from "next"; import { useRouter } from "next/router"; import { serverSideTranslations } from "next-i18next/serverSideTranslations"; @@ -20,7 +20,6 @@ import { RoomInfo, leaveRoom, playerReady, - playerCancelReady, startGame, } from "@/requests/rooms"; import { GameType, getAllGamesEndpoint } from "@/requests/games"; @@ -41,13 +40,14 @@ export default function Room() { updateUserReadyStatus, cleanUpRoom, } = useRoom(); + const isFirstReady = useRef(true); const { socket } = useSocketCore(); const { currentUser, token } = useAuth(); - const { updateRoomId } = useUser(); + const { updateRoomId, updateGameUrl, getGameUrl } = useUser(); const { Popup, firePopup } = usePopup(); const { fetch } = useRequest(); const { query, replace } = useRouter(); - const [gameUrl, setGameUrl] = useState(""); + const [gameUrl, setGameUrl] = useState(getGameUrl); const [gameList, setGameList] = useState([]); const roomId = query.roomId as string; const player = roomInfo.players.find( @@ -111,11 +111,13 @@ export default function Room() { socket.on(SOCKET_EVENT.GAME_STARTED, ({ gameUrl }: { gameUrl: string }) => { updateRoomStatus("PLAYING"); setGameUrl(`${gameUrl}?token=${token}`); + updateGameUrl(`${gameUrl}?token=${token}`); }); socket.on(SOCKET_EVENT.GAME_ENDED, () => { updateRoomStatus("WAITING"); setGameUrl(""); + updateGameUrl(); firePopup({ title: `遊戲已結束!`, }); @@ -170,6 +172,7 @@ export default function Room() { await fetch(closeRoom(roomId)); replace("/rooms"); updateRoomId(); + updateGameUrl(); } catch (err) { firePopup({ title: "error!" }); } @@ -189,6 +192,7 @@ export default function Room() { await fetch(leaveRoom(roomId)); replace("/rooms"); updateRoomId(); + updateGameUrl(); } catch (err) { firePopup({ title: "error!" }); } @@ -203,35 +207,25 @@ export default function Room() { }); }; - // Event: toggle ready - const handleToggleReady = async () => { - try { - player?.isReady - ? await fetch(playerCancelReady(roomId)) - : await fetch(playerReady(roomId)); - } catch (err) { - firePopup({ title: `error!` }); - } - }; - - // Event: start game const handleStart = async () => { try { - // Check all players are ready const allReady = roomInfo.players.every((player) => player.isReady); if (!allReady) return firePopup({ title: "尚有玩家未準備就緒" }); const result = await fetch(startGame(roomId)); - setGameUrl(`${result.url}?token=${token}`); + const newGameUrl = `${result.url}?token=${token}`; + setGameUrl(newGameUrl); + updateGameUrl(newGameUrl); } catch (err) { firePopup({ title: `error!` }); } }; useEffect(() => { - if (!player?.isReady && roomId) { + if (!player?.isReady && roomId && isFirstReady.current) { fetch(playerReady(roomId)); + isFirstReady.current = false; } - }, [player?.isReady, roomId, fetch]); + }, [player?.isReady, roomId, fetch, isFirstReady]); return (
From 7d9e17db873d79186dd5118eeca6fda2ae0e4aba Mon Sep 17 00:00:00 2001 From: Johnson Mao Date: Sun, 17 Nov 2024 21:52:52 +0800 Subject: [PATCH 3/4] chore: i18n --- features/room/components/CreateRoomForm.tsx | 2 +- features/room/components/JoinLockRoomForm.tsx | 2 +- features/room/hooks/useJoinRoom.ts | 2 +- pages/index.tsx | 2 +- pages/login.tsx | 2 +- pages/rooms/index.tsx | 4 ++-- public/locales/zh_TW/common.json | 11 +++++++++++ public/locales/zh_TW/rooms.json | 9 --------- 8 files changed, 18 insertions(+), 16 deletions(-) delete mode 100644 public/locales/zh_TW/rooms.json diff --git a/features/room/components/CreateRoomForm.tsx b/features/room/components/CreateRoomForm.tsx index b6242505..e77de09b 100644 --- a/features/room/components/CreateRoomForm.tsx +++ b/features/room/components/CreateRoomForm.tsx @@ -55,7 +55,7 @@ function CreateRoomForm({ }); const { fetch } = useRequest(); const router = useRouter(); - const { t } = useTranslation("rooms"); + const { t } = useTranslation(); const [isLockRoom, setIsLockRoom] = useState(false); const passwordInputRef = useRef(null); const gameNameInputRef = useRef(null); diff --git a/features/room/components/JoinLockRoomForm.tsx b/features/room/components/JoinLockRoomForm.tsx index 4f9f1136..e797b882 100644 --- a/features/room/components/JoinLockRoomForm.tsx +++ b/features/room/components/JoinLockRoomForm.tsx @@ -11,7 +11,7 @@ interface JoinLockRoomFormProps extends PropsWithChildren { } function JoinLockRoomForm({ id, children }: Readonly) { - const { t } = useTranslation("rooms"); + const { t } = useTranslation(); const { handleJoinRoom } = useJoinRoom(id); const [password, setPassword] = useState(""); const [errorMessage, setErrorMessage] = useState(""); diff --git a/features/room/hooks/useJoinRoom.ts b/features/room/hooks/useJoinRoom.ts index 4da7503b..2da0e9e2 100644 --- a/features/room/hooks/useJoinRoom.ts +++ b/features/room/hooks/useJoinRoom.ts @@ -9,7 +9,7 @@ import useUser from "@/hooks/useUser"; import { useToast } from "@/components/shared/Toast"; function useJoinRoom(id: string) { - const { t } = useTranslation("rooms"); + const { t } = useTranslation(); const { fetch } = useRequest(); const { updateRoomId } = useUser(); const toast = useToast(); diff --git a/pages/index.tsx b/pages/index.tsx index 9bd84dec..c806e954 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -86,7 +86,7 @@ export default function Home() { export const getStaticProps: GetStaticProps = async ({ locale }) => { return { props: { - ...(await serverSideTranslations(locale ?? "zh-TW", ["rooms"])), + ...(await serverSideTranslations(locale ?? "zh-TW")), }, }; }; diff --git a/pages/login.tsx b/pages/login.tsx index de926aeb..04e0c1f4 100644 --- a/pages/login.tsx +++ b/pages/login.tsx @@ -107,7 +107,7 @@ export default Login; export const getStaticProps: GetStaticProps = async ({ locale }) => { return { props: { - ...(await serverSideTranslations(locale ?? "zh-TW", ["rooms"])), + ...(await serverSideTranslations(locale ?? "zh-TW")), }, }; }; diff --git a/pages/rooms/index.tsx b/pages/rooms/index.tsx index 4ef2bc59..080981fe 100644 --- a/pages/rooms/index.tsx +++ b/pages/rooms/index.tsx @@ -73,7 +73,7 @@ function TabPaneContent({ tabKey }: Readonly>) { } const Rooms = () => { - const { t } = useTranslation("rooms"); + const { t } = useTranslation(); const tabs: TabItemType[] = [ { @@ -100,7 +100,7 @@ const Rooms = () => { export const getStaticProps: GetStaticProps = async ({ locale }) => { return { props: { - ...(await serverSideTranslations(locale ?? "zh-TW", ["rooms"])), + ...(await serverSideTranslations(locale ?? "zh-TW")), }, }; }; diff --git a/public/locales/zh_TW/common.json b/public/locales/zh_TW/common.json index e69de29b..1d7c88ad 100644 --- a/public/locales/zh_TW/common.json +++ b/public/locales/zh_TW/common.json @@ -0,0 +1,11 @@ +{ + "room_is_full": "房間人數已滿! R003", + "wrong_password": "密碼錯誤", + "you_can_only_join_1_room": "一人只能進入一間房! P003", + "P005": "不在這個房間中 P005", + "rooms_list": "查看房間列表", + "rooms_waiting": "玩家募集中", + "rooms_playing": "進行中", + "enter_game_password": "請輸入遊戲密碼", + "U003": "暱稱已重複" +} diff --git a/public/locales/zh_TW/rooms.json b/public/locales/zh_TW/rooms.json deleted file mode 100644 index 87a64d39..00000000 --- a/public/locales/zh_TW/rooms.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "room_is_full": "房間人數已滿!", - "wrong_password": "密碼錯誤", - "you_can_only_join_1_room": "一人只能進入一間房!", - "rooms_list": "查看房間列表", - "rooms_waiting": "正在等待玩家配對", - "rooms_playing": "遊戲已開始", - "enter_game_password": "請輸入遊戲密碼" -} From de6395bfe10a5fb4b30fee13408ffbea7f44fc35 Mon Sep 17 00:00:00 2001 From: Johnson Mao Date: Tue, 17 Dec 2024 22:20:23 +0800 Subject: [PATCH 4/4] ci: add dependa bot config --- .github/dependabot.yaml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 .github/dependabot.yaml diff --git a/.github/dependabot.yaml b/.github/dependabot.yaml new file mode 100644 index 00000000..78772916 --- /dev/null +++ b/.github/dependabot.yaml @@ -0,0 +1,18 @@ +version: 2 +updates: + - package-ecosystem: "npm" + directory: "/" + schedule: + interval: "weekly" + assignees: + - "JohnsonMao" + open-pull-requests-limit: 3 + labels: + - "security" + commit-message: + prefix: "[SECURITY]" + ignore: + - dependency-name: "*" + update-types: + - "version-update:semver-patch" + - "version-update:semver-minor"