Skip to content

Commit 6cb0e1b

Browse files
Merge pull request #332 from boostcampwm-2024/dev
[Deploy] 6주차 5차 배포
2 parents e7691d6 + 32394a0 commit 6cb0e1b

File tree

19 files changed

+315
-84
lines changed

19 files changed

+315
-84
lines changed

backend/src/room/exceptions/join-room-exceptions.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,7 @@ export class FullRoomException extends Error {
55
export class InProgressException extends Error {
66
name: "InProgressException";
77
}
8+
9+
export class RoomNotFoundException extends Error {
10+
name: "RoomNotFoundException";
11+
}

backend/src/room/room.events.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export const EMIT_EVENT = {
1515
CREATE: "server:room__create",
1616
QUIT: "server:room__quit",
1717
FULL: "server:room__full",
18+
NOT_FOUND: "server:room__not_found",
1819
JOIN: "server:room__join",
1920
IN_PROGRESS: "server:room__progress",
2021
FINISH: "server:room__finish",

backend/src/room/room.gateway.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,11 @@ import { RoomRepository } from "@/room/room.repository";
2626

2727
import { gatewayConfig } from "@/infra/infra.config";
2828

29-
import { FullRoomException, InProgressException } from "@/room/exceptions/join-room-exceptions";
29+
import {
30+
FullRoomException,
31+
InProgressException,
32+
RoomNotFoundException,
33+
} from "@/room/exceptions/join-room-exceptions";
3034
import { createAdapter } from "@socket.io/redis-adapter";
3135

3236
@WebSocketGateway(gatewayConfig)
@@ -82,6 +86,7 @@ export class RoomGateway implements OnGatewayDisconnect, OnGatewayInit, OnGatewa
8286
} catch (e) {
8387
if (e instanceof InProgressException) client.emit(EMIT_EVENT.IN_PROGRESS, {});
8488
else if (e instanceof FullRoomException) client.emit(EMIT_EVENT.FULL, {});
89+
else if (e instanceof RoomNotFoundException) client.emit(EMIT_EVENT.NOT_FOUND, {});
8590
else throw e;
8691
}
8792
}

backend/src/room/room.service.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,11 @@ import { Room, RoomStatus } from "@/room/domain/room";
1010
import { EMIT_EVENT } from "@/room/room.events";
1111
import { createHash } from "node:crypto";
1212
import { QuestionRepository } from "@/question-list/repository/question.respository";
13-
import { FullRoomException, InProgressException } from "@/room/exceptions/join-room-exceptions";
13+
import {
14+
FullRoomException,
15+
InProgressException,
16+
RoomNotFoundException,
17+
} from "@/room/exceptions/join-room-exceptions";
1418
import { InfraService } from "@/infra/infra.service";
1519
import { QuestionListRepository } from "@/question-list/repository/question-list.repository";
1620

@@ -74,7 +78,7 @@ export class RoomService {
7478
const room = Room.fromEntity(await this.roomRepository.getRoom(roomId));
7579

7680
if (!socket) throw new Error("Invalid Socket");
77-
if (!room.entity) throw new Error("RoomEntity Not found");
81+
if (!room.entity) throw new RoomNotFoundException();
7882

7983
await this.infraService.joinRoom(socket, room.entity.id);
8084

frontend/src/components/common/Animate/NotFound.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,13 @@ const NotFound = ({ message, className, redirect }: NotFoundProps) => {
2020
style={{ width: 200 }}
2121
className={"dark:invert"}
2222
/>
23-
<p className={"text-medium-m text-gray-500 text-center flex flex-col gap-0.5"}>
24-
{message?.split("\n").map(text => (<p>{text}</p>))}
25-
</p>
23+
<div
24+
className={
25+
"text-medium-m text-gray-500 text-center flex flex-col gap-0.5"
26+
}
27+
>
28+
{message?.split("\n").map((text) => <p key={text}>{text}</p>)}
29+
</div>
2630
{redirect && (
2731
<Link
2832
className={

frontend/src/components/common/Sidebar/Sidebar.tsx

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { useNavigate } from "react-router-dom";
22
import { useEffect, useState } from "react";
3-
import { MdDarkMode, MdLightMode } from "react-icons/md";
3+
// import { MdDarkMode, MdLightMode } from "react-icons/md";
44
import { FaGithub } from "react-icons/fa6";
5-
import useTheme from "@hooks/useTheme.ts";
5+
// import useTheme from "@hooks/useTheme.ts";
66
import useAuth from "@/hooks/useAuth";
77
import {
88
authenticatedRoutes,
@@ -15,7 +15,7 @@ const Sidebar = () => {
1515
const { isLoggedIn, logOut } = useAuth();
1616
const [selected, setSelected] = useState<string>("");
1717
const [currentRoutes, setCurrentRoutes] = useState<Route[]>([]);
18-
const { theme, toggleTheme } = useTheme();
18+
// const { theme, toggleTheme } = useTheme();
1919
const navigate = useNavigate();
2020

2121
useEffect(() => {
@@ -86,16 +86,16 @@ const Sidebar = () => {
8686
<FaGithub /> BOOSKIT
8787
</span>
8888
</a>
89-
<button
90-
onClick={toggleTheme}
91-
className={
92-
"text-xl dark:bg-gray-100 dark:text-gray-black border border-gray-200 rounded-full p-2 dark:border-gray-200 hover:bg-gray-200/80 dark:hover:bg-gray-200/80 transition-colors"
93-
}
94-
aria-roledescription={"라이트모드와 다크모드 간 전환 버튼"}
95-
aria-label={"테마 변경버튼"}
96-
>
97-
{theme === "light" ? <MdLightMode /> : <MdDarkMode />}
98-
</button>
89+
{/*<button*/}
90+
{/* onClick={toggleTheme}*/}
91+
{/* className={*/}
92+
{/* "text-xl dark:bg-gray-100 dark:text-gray-black border border-gray-200 rounded-full p-2 dark:border-gray-200 hover:bg-gray-200/80 dark:hover:bg-gray-200/80 transition-colors"*/}
93+
{/* }*/}
94+
{/* aria-roledescription={"라이트모드와 다크모드 간 전환 버튼"}*/}
95+
{/* aria-label={"테마 변경버튼"}*/}
96+
{/*>*/}
97+
{/* {theme === "light" ? <MdLightMode /> : <MdDarkMode />}*/}
98+
{/*</button>*/}
9999
</div>
100100
</nav>
101101
);

frontend/src/components/session/CommonTools.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ interface CommonToolsProps {
2828
videoLoading: boolean;
2929
isHost: boolean;
3030
roomId: string;
31+
setShouldBlock: (shouldBlock: boolean) => void;
3132
}
3233

3334
const CommonTools = ({
@@ -43,6 +44,7 @@ const CommonTools = ({
4344
videoLoading,
4445
isHost,
4546
roomId,
47+
setShouldBlock,
4648
}: CommonToolsProps) => {
4749
const navigate = useNavigate();
4850
const toast = useToast();
@@ -52,13 +54,15 @@ const CommonTools = ({
5254
const existHandler = () => {
5355
socket?.emit(SESSION_EMIT_EVENT.LEAVE, { roomId });
5456
toast.success("메인 화면으로 이동합니다.");
57+
setShouldBlock(false);
5558
navigate("/sessions");
5659
};
5760

5861
const destroyAndExitHandler = () => {
5962
socket?.off(SESSION_EMIT_EVENT.FINISH);
6063
socket?.emit(SESSION_EMIT_EVENT.FINISH, { roomId });
6164
toast.success("메인 화면으로 이동합니다.");
65+
setShouldBlock(false);
6266
navigate("/sessions");
6367
};
6468

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
import VideoContainer from "@components/session/VideoContainer.tsx";
2+
import { useCallback, useEffect, useState } from "react";
3+
import useToast from "@hooks/useToast.ts";
4+
5+
interface MediaPreviewModalProps {
6+
modal: UseModalReturn;
7+
nickname?: string;
8+
setNickname: (nickname: string) => void;
9+
isVideoOn: boolean;
10+
setIsVideoOn: (value: boolean) => void;
11+
onConfirm: () => void;
12+
onReject: () => void;
13+
setReady: (ready: boolean) => void;
14+
getMediaStream: (type: "video" | "audio") => Promise<MediaStream | undefined>;
15+
}
16+
17+
interface UseModalReturn {
18+
dialogRef: React.RefObject<HTMLDialogElement>;
19+
isOpen: boolean;
20+
openModal: () => void;
21+
closeModal: () => void;
22+
}
23+
24+
const MediaPreviewModal = ({
25+
modal,
26+
nickname,
27+
setNickname,
28+
onConfirm,
29+
onReject,
30+
getMediaStream,
31+
isVideoOn,
32+
setIsVideoOn,
33+
setReady,
34+
}: MediaPreviewModalProps) => {
35+
const [preview, setPreview] = useState<MediaStream | undefined>();
36+
const toast = useToast();
37+
38+
const getMediaPreview = useCallback(async () => {
39+
const mediaStream = await getMediaStream("video");
40+
if (!mediaStream) {
41+
toast.error(
42+
"비디오 장치를 찾을 수 없습니다. 비디오 장치 없이 세션에 참가합니다."
43+
);
44+
}
45+
setPreview(mediaStream);
46+
}, []);
47+
48+
useEffect(() => {
49+
getMediaPreview();
50+
51+
return () => {
52+
preview?.getTracks().forEach((track) => track.stop());
53+
};
54+
}, []);
55+
56+
useEffect(() => {
57+
if (modal.dialogRef.current) {
58+
const dialog = modal.dialogRef.current;
59+
const handleEscape = (event: globalThis.KeyboardEvent) => {
60+
if (event.key === "Escape") {
61+
event.preventDefault();
62+
onReject();
63+
setReady(false);
64+
}
65+
};
66+
dialog.addEventListener("keydown", (event) => handleEscape(event));
67+
}
68+
}, [modal.dialogRef.current]);
69+
70+
return (
71+
modal.isOpen && (
72+
<dialog
73+
ref={modal.dialogRef}
74+
className={
75+
"flex flex-col items-center rounded-custom-l px-10 py-6 w-[640px] shadow-lg"
76+
}
77+
>
78+
<h3 className={"text-bold-m"}>비디오 미리보기</h3>
79+
<div className={"w-[400px] p-4"}>
80+
<VideoContainer
81+
nickname={nickname || ""}
82+
isMicOn={true}
83+
isVideoOn={isVideoOn}
84+
isLocal={true}
85+
isSpeaking={false}
86+
reaction={""}
87+
stream={isVideoOn ? preview : undefined}
88+
videoCount={1}
89+
/>
90+
</div>
91+
<div className={"text-medium-r mt-4 flex w-full justify-center gap-4"}>
92+
<label
93+
className={
94+
"inline-flex gap-2 items-center rounded-custom-m px-4 py-2 bg-transparent"
95+
}
96+
id={"checkbox"}
97+
>
98+
<input
99+
defaultChecked={!isVideoOn || false}
100+
className={"w-6 h-6"}
101+
type={"checkbox"}
102+
title={"dd"}
103+
onClick={() => setIsVideoOn(!isVideoOn)}
104+
/>
105+
<span>내 비디오 끄고 참가하기</span>
106+
</label>
107+
<input
108+
className={
109+
"rounded-custom-m px-4 py-2 bg-gray-50 hover:bg-gray-100"
110+
}
111+
type={"text"}
112+
defaultValue={nickname}
113+
placeholder={"닉네임 변경"}
114+
onChange={(e) => setNickname(e.target.value)}
115+
/>
116+
</div>
117+
<div
118+
className={"text-semibold-r mt-4 flex w-full justify-center gap-4"}
119+
>
120+
<button
121+
onClick={() => {
122+
onReject();
123+
setReady(false);
124+
preview?.getTracks().forEach((track) => track.stop());
125+
}}
126+
className={
127+
"rounded-custom-m px-16 py-4 bg-gray-50 hover:bg-gray-100"
128+
}
129+
>
130+
세션 나가기
131+
</button>
132+
<button
133+
onClick={() => {
134+
onConfirm();
135+
setReady(true);
136+
modal.closeModal();
137+
}}
138+
className={
139+
"rounded-custom-m px-16 py-4 bg-green-500 text-white hover:bg-green-600"
140+
}
141+
>
142+
세션 참가
143+
</button>
144+
</div>
145+
</dialog>
146+
)
147+
);
148+
};
149+
150+
export default MediaPreviewModal;

frontend/src/components/session/VideoContainer.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ interface VideoContainerProps {
1616
isLocal: boolean;
1717
isSpeaking: boolean;
1818
reaction: string;
19-
stream: MediaStream;
19+
stream: MediaStream | undefined;
2020
videoLoading?: boolean;
2121
videoCount: number;
2222
}
@@ -94,7 +94,7 @@ const VideoContainer = ({
9494
className={`relative ${getVideoLayoutClass(videoCount)} ${speakingEffect} rounded-custom-l aspect-[4/3]`}
9595
>
9696
<div className="absolute inset-0 bg-gray-black rounded-custom-l overflow-hidden z-10">
97-
<DisplayMediaStream mediaStream={stream} isLocal={isLocal} />
97+
<DisplayMediaStream mediaStream={stream!} isLocal={isLocal} />
9898
<div className="inline-flex gap-4 absolute bottom-2 w-full justify-between px-2">
9999
<p
100100
className={`bg-grayscale-500 ${localNickName} bg-opacity-70 text-white px-2 py-0.5 rounded`}

frontend/src/constants/WebSocket/SessionEvent.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,5 @@ export const SESSION_LISTEN_EVENT = {
1414
FINISH: "server:room__finish",
1515
CHANGE_HOST: "server:room__change_host",
1616
REACTION: "server:room__reaction",
17+
NOT_FOUND: "server:room__not_found",
1718
} as const;

0 commit comments

Comments
 (0)