diff --git a/assets/icons/host.svg b/assets/icons/host.svg index b048c23c..ecbe3c50 100644 --- a/assets/icons/host.svg +++ b/assets/icons/host.svg @@ -1,44 +1,3 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + diff --git a/assets/icons/logo.svg b/assets/icons/logo.svg deleted file mode 100644 index fb277fd7..00000000 --- a/assets/icons/logo.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/assets/icons/user-host.svg b/assets/icons/user-host.svg new file mode 100644 index 00000000..528e4ee6 --- /dev/null +++ b/assets/icons/user-host.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/user.svg b/assets/icons/user.svg index 4aa94beb..39d5e860 100644 --- a/assets/icons/user.svg +++ b/assets/icons/user.svg @@ -1,42 +1,4 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + diff --git a/assets/icons/user_host.svg b/assets/icons/user_host.svg deleted file mode 100644 index 86c9fdf5..00000000 --- a/assets/icons/user_host.svg +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/components/lobby/EnterPrivateRoomModal/EnterPrivateRoomModal.tsx b/components/lobby/EnterPrivateRoomModal/EnterPrivateRoomModal.tsx deleted file mode 100644 index 591a1abd..00000000 --- a/components/lobby/EnterPrivateRoomModal/EnterPrivateRoomModal.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import type { ClipboardEvent } from "react"; - -import Modal from "@/components/shared/Modal"; -import PasswordField from "@/components/shared/PasswordField"; - -type EnterPrivateRoomModalProps = { - isOpen: boolean; - loading?: boolean; - passwordValues: string[]; - setPasswordValues: (values: string[]) => void; - onClose: () => void; - onPaste?: (e: ClipboardEvent) => void; -}; - -export default function EnterPrivateRoomModal({ - isOpen, - loading, - passwordValues, - setPasswordValues, - onClose, - onPaste, -}: EnterPrivateRoomModalProps) { - return ( - -
- -
- {loading && ( -
-
-
- )} - - ); -} diff --git a/components/lobby/EnterPrivateRoomModal/index.tsx b/components/lobby/EnterPrivateRoomModal/index.tsx deleted file mode 100644 index 37536a36..00000000 --- a/components/lobby/EnterPrivateRoomModal/index.tsx +++ /dev/null @@ -1,3 +0,0 @@ -import EnterPrivateRoomModal from "./EnterPrivateRoomModal"; - -export default EnterPrivateRoomModal; diff --git a/components/rooms/RoomBreadcrumb.tsx b/components/rooms/RoomBreadcrumb.tsx deleted file mode 100644 index e3ad1374..00000000 --- a/components/rooms/RoomBreadcrumb.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import Breadcrumb from "@/components/shared/Breadcrumb"; -import { RoomInfo } from "@/requests/rooms"; - -type RoomBreadcrumbType = { - roomInfo: RoomInfo.Room; -}; - -function RoomBreadcrumb({ roomInfo }: RoomBreadcrumbType) { - const isPublicText = (roomInfo.isLocked ? "非公開" : "公開") + "遊戲房間"; - const maxPlayerText = `${roomInfo.maxPlayers}人房`; - const statusText = - roomInfo.status === "WAITING" ? "等待玩家中" : "遊戲進行中"; - - const combinedText = roomInfo.name + "-" + maxPlayerText + "-" + statusText; - return ( - - - - - ); -} - -export default RoomBreadcrumb; diff --git a/components/rooms/RoomButtonGroup.tsx b/components/rooms/RoomButtonGroup.tsx index de81896e..33ff1416 100644 --- a/components/rooms/RoomButtonGroup.tsx +++ b/components/rooms/RoomButtonGroup.tsx @@ -1,40 +1,31 @@ import Button from "@/components/shared/Button/v2"; +import Icon from "../shared/Icon"; type RoomButtonGroupProps = { isHost: boolean; - isReady: boolean; - onToggleReady: () => void; onClickLeave: () => void; onClickClose: () => void; onClickStart: () => void; }; function RoomButtonGroup(props: RoomButtonGroupProps) { - const { - onToggleReady, - onClickLeave, - onClickClose, - onClickStart, - isHost, - isReady, - } = props; + const { onClickLeave, onClickClose, onClickStart, isHost } = props; return (
- {isHost ? ( - - ) : ( - )}
); diff --git a/components/rooms/RoomCard.tsx b/components/rooms/RoomCard.tsx deleted file mode 100644 index b383d4b0..00000000 --- a/components/rooms/RoomCard.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import { cn } from "@/lib/utils"; -import { Room } from "@/requests/rooms"; -import Icon from "@/components/shared/Icon"; -import Cover from "@/components/shared/Cover"; - -type RoomsCardProps = { - key: string; - room: Room; - active: boolean; - className?: string; - onClick: (id: string) => void; -}; -const RoomCard = ({ room, active, onClick }: RoomsCardProps) => { - const LockIcon = () => ( - - ); - - const roomCardClass = cn( - "room__card", - "relative cursor-pointer hover:border-blue2f transition-all duration-300", - "grid grid-cols-[34px_1fr] gap-[12px] rounded-[10px] border-2 border-dark1E py-[11px] pl-[11px] pr-[20px] bg-dark1E", - { - "border-blue": active, - } - ); - - const lackTotalPlayers = room.maxPlayers - room.currentPlayers; - - return ( -
onClick(room.id)}> - -
-

{room.name}

-
- {room.maxPlayers} - 人房, - {lackTotalPlayers > 0 ? ( - <> - 還缺 - {lackTotalPlayers}人 - - ) : ( - <>人數已滿 - )} -
-
- {/* 檢查是否上鎖 */} - {room.isLocked && ( -
- {LockIcon()} -
- )} -
- ); -}; - -export default RoomCard; diff --git a/components/rooms/RoomUserCardList/RoomUserCardList.tsx b/components/rooms/RoomUserCardList/RoomUserCardList.tsx index 4a759429..116f9bd3 100644 --- a/components/rooms/RoomUserCardList/RoomUserCardList.tsx +++ b/components/rooms/RoomUserCardList/RoomUserCardList.tsx @@ -1,7 +1,6 @@ import { RoomInfo } from "@/requests/rooms"; -import UserCard, { UserCardProps } from "./UserCard/UserCard"; - -const SEAT_AMOUNT = 10; +import { UserCard } from "@/features/user"; +import { generateUUID } from "@/lib/utils"; type RoomUserCardListProps = { roomInfo: RoomInfo.Room; @@ -14,40 +13,26 @@ function RoomUserCardList({ currentUserId, onKickUser, }: RoomUserCardListProps) { - function renderUserCards(users: RoomInfo.User[]) { - const userCount = users.length; - - const haveRightToKick = (userId: string) => - currentUserId === roomInfo.host.id && currentUserId !== userId; - - const userCards = users.map((user) => { - const props: UserCardProps = { - id: user.id, - nickname: user.nickname, - isReady: user.isReady, - isSelf: user.id === currentUserId, - isHost: user.id === roomInfo.host.id, - onKickUser: haveRightToKick(user.id) ? onKickUser : undefined, - }; - return ; - }); - - // render rest seats - const emptyCards = Array.from({ - length: SEAT_AMOUNT - userCount, - }).map((_, index) => { - // render wating seat - if (userCount + index < roomInfo.maxPlayers) - return ; - // render disabled seat - return ; - }); + const players = Array.isArray(roomInfo.players) ? roomInfo.players : []; + const lackTotalPlayers = Array.from( + { length: roomInfo.maxPlayers - players.length }, + generateUUID + ); - return [...userCards, ...emptyCards]; - } return (
- {renderUserCards(roomInfo.players)} + {players.map((player) => ( + + ))} + {lackTotalPlayers.map((id) => ( + + ))}
); } diff --git a/components/rooms/RoomUserCardList/UserCard/UserCard.test.tsx b/components/rooms/RoomUserCardList/UserCard/UserCard.test.tsx deleted file mode 100644 index a92bb534..00000000 --- a/components/rooms/RoomUserCardList/UserCard/UserCard.test.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import React from "react"; -import { render, screen } from "@testing-library/react"; -import "@testing-library/jest-dom"; -import UserCard, { UserCardProps } from "./UserCard"; - -describe("UserCard", () => { - describe("Available UserCard", () => { - it("should render with waiting text", () => { - const userCard = render(); - - expect(userCard.baseElement).toHaveTextContent("等待中..."); - }); - }); - - describe("Disabled UserCard", () => { - it("should render with empty textContent", () => { - const userCard = render(); - - expect(userCard.baseElement).toHaveTextContent(""); - }); - }); - - describe("UserCard with user", () => { - const UserProp: UserCardProps = { - id: "testId", - nickname: "testNickname", - isReady: false, - isSelf: false, - isHost: false, - }; - - it("should render with correct nickname", () => { - render(); - - expect(screen.getByText(UserProp.nickname)).toBeInTheDocument(); - }); - - it("should render with ready status text when user is ready", () => { - const userCard = render(); - - expect(userCard.getByText("已準備")).toBeInTheDocument(); - }); - - it("should render text with 'you' when it represents user self", () => { - const userCard = render(); - - expect(userCard.baseElement.textContent).toMatch(/你/); - expect(userCard.baseElement.textContent).not.toMatch(/mynickname/); - }); - - it("should render host suffix text when user is host ", () => { - const userCard = render(); - - expect(userCard.baseElement.textContent).toMatch(/(房主)/); - }); - - it("should render with correct nickname when it represents both of user self and host", () => { - const userCard = render(); - - expect(userCard.baseElement.textContent).toMatch(/你/); - expect(userCard.baseElement.textContent).toMatch(/(房主)/); - expect(userCard.baseElement.textContent).not.toMatch(/mynickname/); - }); - - it("shouldn't render kick button on right top when not recived onKickUser prop", () => { - render(); - - expect(screen.queryByTestId("kick-user-svg")).toBeFalsy(); - }); - - // it("shouldn render kick button on right top when recived onKickUser prop", () => { - // render( {}} />); - - // expect(screen.queryByTestId("kick-user-svg")).not.toBeFalsy(); - // }); - }); -}); diff --git a/components/rooms/RoomUserCardList/UserCard/UserCard.tsx b/components/rooms/RoomUserCardList/UserCard/UserCard.tsx deleted file mode 100644 index 81ed330b..00000000 --- a/components/rooms/RoomUserCardList/UserCard/UserCard.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import { cn } from "@/lib/utils"; -import Icon from "@/components/shared/Icon"; -import { ReactElement } from "react"; -import { RoomInfo } from "@/requests/rooms"; -export interface UserCardProps { - id: string; - nickname: string; - isReady: boolean; - isSelf: boolean; - isHost: boolean; - onKickUser?: (User: Omit) => void; -} -interface WatingUserCardProp { - isWating: boolean; -} -interface DisabledUserCardProp { - disabled: boolean; -} -function UserCard(props: WatingUserCardProp): ReactElement; -function UserCard(props: DisabledUserCardProp): ReactElement; -function UserCard(props: UserCardProps): ReactElement; -function UserCard(props: any) { - const { id, nickname, isReady, isSelf, isHost, onKickUser }: UserCardProps = - props; - const { isWating }: WatingUserCardProp = props; - const { disabled }: DisabledUserCardProp = props; - - const hostClass = "border-[4px] border-[#23A55A]"; - - const disabledClass = "opacity-30 bg-black"; - - const readyContent = ( -
- 已準備 -
- ); - - function getNameText() { - if (disabled) return ""; - if (isWating) return "等待中..."; - const suffix = isHost ? "(房主)" : ""; - const name = isSelf ? "你" : nickname; - return name + suffix; - } - const nameText = getNameText(); - - return ( -
- {onKickUser && ( -
onKickUser({ id, nickname })} - data-testid="kick-user-svg" - className={"absolute top-[5px] right-[6px] cursor-pointer"} - > - -
- )} - - {isReady && readyContent} - - {nameText} - -
- ); -} - -export default UserCard; diff --git a/components/rooms/RoomsList.tsx b/components/rooms/RoomsList.tsx deleted file mode 100644 index 4f678c9c..00000000 --- a/components/rooms/RoomsList.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { cn } from "@/lib/utils"; - -type RoomsListProps = { - className?: string; - children: React.ReactNode; -}; - -export const RoomsListWrapper = ({ className, children }: RoomsListProps) => { - const listClass = cn( - "rooms__list__wrapper", - "grid grid-cols-[repeat(auto-fill,_minmax(200px,_1fr))] gap-2.5 my-5", - className - ); - - return
{children}
; -}; - -export const RoomsList = ({ className, children }: RoomsListProps) => { - return ( -
- {children} -
- ); -}; diff --git a/components/shared/BoxFancy/BoxFancy.tsx b/components/shared/BoxFancy/BoxFancy.tsx index e2a77fc9..fa19e9bf 100644 --- a/components/shared/BoxFancy/BoxFancy.tsx +++ b/components/shared/BoxFancy/BoxFancy.tsx @@ -10,7 +10,11 @@ export type BoxFancyBorderWidthVariant = | "xLarge" | "extraLarge"; export type BoxFancyBorderRadiusVariant = BoxFancyBorderWidthVariant | "full"; -export type BoxFancyBorderGradientVariant = "none" | "purple" | "black"; +export type BoxFancyBorderGradientVariant = + | "none" + | "purple" + | "black" + | "cyberpunk"; // Gradient border with semi-transparent background tips: // The border-radius of ::before should be as consistent as possible with the original, @@ -40,6 +44,7 @@ const borderGradientVariantMap: Record = none: "", purple: "before:gradient-purple", black: "before:gradient-black", + cyberpunk: "before:gradient-cyberpunk", }; export interface BaseBoxFancyProp { diff --git a/components/shared/Breadcrumb/Breadcrumb.tsx b/components/shared/Breadcrumb/Breadcrumb.tsx index a271deae..9d08d2e6 100644 --- a/components/shared/Breadcrumb/Breadcrumb.tsx +++ b/components/shared/Breadcrumb/Breadcrumb.tsx @@ -1,6 +1,30 @@ -import React, { ReactNode } from "react"; +import { Children } from "react"; +import Link from "next/link"; import { cn } from "@/lib/utils"; -import BreadcrumbItem, { BreadcrumbItemProps } from "./BreadcrumbItem"; +import Icon from "../Icon"; + +export interface BreadcrumbItemProps { + /** The displayed text of the breadcrumb item */ + text: string; + /** The href of the breadcrumb item */ + href?: string; + /** A CSS class name for styling the breadcrumb item */ + className?: string; +} + +export const BreadcrumbItem = ({ + text, + href, + className, +}: BreadcrumbItemProps) => { + return href ? ( + + {text} + + ) : ( + {text} + ); +}; export interface BreadcrumbProps { /** `Breadcrumb.Item` with text and href */ @@ -11,22 +35,22 @@ export interface BreadcrumbProps { className?: string; } +const defaultSeparator = ; + const Breadcrumb: React.FC & { Item: React.ComponentType; -} = ({ children, separator = ">", className }) => { - const rootClass = cn(`flex space-x-2 text-base`, className); - const childrenRender = React.Children.map(children, (child, index) => ( - <> - {child} - {index < React.Children.count(children) - 1 && ( - {separator} - )} - - )); - +} = ({ children, separator = defaultSeparator, className }) => { return ( -