Skip to content

Commit

Permalink
Merge pull request #427 from Game-as-a-Service/refactor/room
Browse files Browse the repository at this point in the history
refactor: chat components, fix user info form bug
  • Loading branch information
JohnsonMao authored Dec 20, 2024
2 parents 9df70cb + de6395b commit 1a74129
Show file tree
Hide file tree
Showing 30 changed files with 306 additions and 244 deletions.
18 changes: 18 additions & 0 deletions .github/dependabot.yaml
Original file line number Diff line number Diff line change
@@ -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"
Original file line number Diff line number Diff line change
@@ -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",
Expand Down Expand Up @@ -32,7 +32,6 @@ export const Playground: Story = {
};

Playground.args = {
userId: "玩家名字3",
friendList: mockFriendList,
lobbyMessages: mockLobbyMessages,
roomMessages: mockRoomMessages,
Expand Down
134 changes: 134 additions & 0 deletions components/shared/Chat/Chat.tsx
Original file line number Diff line number Diff line change
@@ -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<MessageType, "content" | "target">) => void;
};

export default function Chat({
roomId,
lobbyMessages,
friendList,
roomMessages,
className,
defaultTarget,
onSubmit,
}: Readonly<ChatProps>) {
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 && (
<div className={cn("w-80 gradient-purple rounded-lg", className)}>
<div className="h-full body-bg border border-transparent bg-clip-padding rounded-lg overflow-hidden">
<ChatHeader
tabs={chatTabs}
activeTab={activeTab}
onToggle={handleToggleTab}
/>
<div className="relative h-[calc(100%-182px)] bg-primary-50/4">
{isFriendList ? (
<ChatFriendList
userId={currentUser.id}
friendList={friendList}
onToggle={handleToggleTarget}
/>
) : (
<>
{friendRoom && (
<div className="absolute top-0 inset-x-0 z-10 flex items-center fz-14 text-primary-100 bg-primary-900 p-2 border-b border-gradient-purple">
<button
onClick={() => handleToggleTab("friend")}
aria-label="go to friend tag"
>
<Icon
name="NavArrowLeft"
className="mr-4 w-6 h-6 stroke-primary-100"
/>
</button>
{getTargetUser(friendRoom, currentUser.id)}
</div>
)}
<ChatMessages userId={currentUser.id} messages={messages} />
</>
)}
</div>
<ChatInput onSubmit={handleSubmit} disabled={isFriendList} />
</div>
</div>
)
);
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import Avatar from "../../Avatar";
import type { UserInfo } from "@/requests/users";

import Avatar from "../Avatar";

type User = Pick<UserInfo, "id" | "nickname">;

export type FriendType = {
target: string;
lastUser: string | null;
lastUser: User | null;
lastContent: string | null;
isRead: boolean;
};
Expand Down Expand Up @@ -31,7 +35,7 @@ export default function ChatFriendList({
<div className="p-4 pt-2 pr-0">
{friendList.map(({ target, lastUser, lastContent, isRead }) => {
const targetUser = getTargetUser(target, userId);
const isMe = lastUser === userId;
const isMe = lastUser?.id === userId;

return (
<div
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
import { FormEventHandler, useState } from "react";
import { cn } from "@/lib/utils";
import Icon from "@/components/shared/Icon";
import type { MessageType } from "./ChatMessages";

type ChatInputProps = {
roomId?: string;
disabled: boolean;
onSubmit: (message: Pick<MessageType, "content" | "target">) => void;
onSubmit: (content: string) => void;
};

export default function ChatInput({
roomId,
disabled,
onSubmit,
}: Readonly<ChatInputProps>) {
Expand All @@ -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 (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ import Avatar from "@/components/shared/Avatar";
import type { UserInfo } from "@/requests/users";

type User = Pick<UserInfo, "id" | "nickname">;
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.
Expand Down Expand Up @@ -40,14 +41,14 @@ export default function ChatMessages({
<div className="flex flex-col justify-end min-h-full max-h-[calc(100%-182px)]">
<div ref={messagesRef} className="overflow-y-scroll scrollbar">
<div className="p-4 pr-0">
{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 (
<div
key={from + content}
key={`${from.id}_${content}_${timestamp}`}
className={cn(
"flex items-end mb-6 last-of-type:mb-0",
isSameUser && "mb-2",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,59 +1,82 @@
import type { MessageType } from "../ChatMessages";
import type { FriendType } from "../ChatFriendList";

const user1 = { id: "玩家名字1", nickname: "玩家名字1" };
const user2 = { id: "玩家名字2", nickname: "玩家名字2" };
const user3 = { id: "玩家名字3", nickname: "玩家名字3" };
const system = { id: "SYSTEM", nickname: "SYSTEM" };

export const mockLobbyMessages: MessageType[] = [
{ from: "玩家名字2", target: "lobby", content: "文字文字文字文字" },
{ from: "玩家名字1", target: "lobby", content: "文字文字文字文字文字" },
{
from: "玩家名字1",
from: user2,
target: "lobby",
content: "文字文字文字文字",
timestamp: "",
},
{
from: user1,
target: "lobby",
content: "文字文字文字文字文字文字文字",
timestamp: "",
},
{
from: user3,
target: "lobby",
content: "文字文字文字文字",
timestamp: "",
},
{ from: "玩家名字3", target: "lobby", content: "文字文字文字文字" },
{
from: "玩家名字3",
from: user3,
target: "lobby",
content: "文字文字文字文字文字文字文字",
timestamp: "",
},
{
from: user1,
target: "lobby",
content: "文字",
timestamp: "",
},
{ from: "玩家名字1", target: "lobby", content: "文字" },
{
from: "玩家名字1",
from: user1,
target: "lobby",
content:
"文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字",
timestamp: "",
},
];

export const mockRoomMessages: MessageType[] = [
{
from: "System",
from: system,
target: "ABC_ROOM",
content: "小王子的花、白日做夢、黑月幻貓、90年代社畜 已加入遊戲房聊天室",
timestamp: "",
},
];

export const mockFriendList: FriendType[] = [
{
lastUser: "玩家A",
target: "玩家A___玩家名字3",
lastUser: user1,
target: "玩家名字1___玩家名字5",
lastContent: "玩家A送出的最後一句話",
isRead: false,
},
{
lastUser: "玩家名字3",
target: "玩家B___玩家名字3",
lastUser: user3,
target: "玩家名字3___玩家名字5",
lastContent: "你送出的最後一句話",
isRead: true,
},
{
lastUser: "玩家C",
target: "玩家C___玩家名字3",
lastUser: user2,
target: "玩家名字2___玩家名字5",
lastContent: "玩家C送出的最後一句話",
isRead: false,
},
{
lastUser: null,
target: "玩家D___玩家名字3",
target: "玩家名字4___玩家名字5",
lastContent: null,
isRead: true,
},
Expand All @@ -65,5 +88,5 @@ export const createMockFriendMessages = ({
lastContent,
}: FriendType): MessageType[] => {
if (!lastUser || !lastContent) return [];
return [{ from: lastUser, target, content: lastContent }];
return [{ from: lastUser, target, content: lastContent, timestamp: "" }];
};
1 change: 1 addition & 0 deletions components/shared/Chat/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from "./Chat";
Loading

0 comments on commit 1a74129

Please sign in to comment.