From 37e1e25d68c2dce46e2ad1f39a11d47de83f4df2 Mon Sep 17 00:00:00 2001 From: surinkwon Date: Tue, 10 Sep 2024 00:36:31 +0900 Subject: [PATCH 01/12] =?UTF-8?q?design:=20setting=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=20UI=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/AppRouter.tsx | 3 +- .../src/components/backlog/BacklogHeader.tsx | 4 +- frontend/src/pages/setting/SettingPage.tsx | 95 +++++++++++++++++++ 3 files changed, 100 insertions(+), 2 deletions(-) create mode 100644 frontend/src/pages/setting/SettingPage.tsx diff --git a/frontend/src/AppRouter.tsx b/frontend/src/AppRouter.tsx index 21f66d2..57fa031 100644 --- a/frontend/src/AppRouter.tsx +++ b/frontend/src/AppRouter.tsx @@ -22,6 +22,7 @@ import UnfinishedStoryPage from "./pages/backlog/UnfinishedStoryPage"; import BacklogPage from "./pages/backlog/BacklogPage"; import FinishedStoryPage from "./pages/backlog/FinishedStoryPage"; import EpicPage from "./pages/backlog/EpicPage"; +import SettingPage from "./pages/setting/SettingPage"; type RouteType = "PRIVATE" | "PUBLIC"; @@ -96,7 +97,7 @@ const router = createBrowserRouter([ element: , }, { path: ROUTER_URL.SPRINT, element:
sprint Page
}, - { path: ROUTER_URL.SETTINGS, element:
setting Page
}, + { path: ROUTER_URL.SETTINGS, element: }, ], }, ]), diff --git a/frontend/src/components/backlog/BacklogHeader.tsx b/frontend/src/components/backlog/BacklogHeader.tsx index 8cab6d7..12deb19 100644 --- a/frontend/src/components/backlog/BacklogHeader.tsx +++ b/frontend/src/components/backlog/BacklogHeader.tsx @@ -14,7 +14,9 @@ const BacklogHeader = () => { return (
-

{TAB_TITLE[lastPath]} 백로그

+

+ {TAB_TITLE[lastPath]} 백로그 +

우선순위 내림차순

diff --git a/frontend/src/pages/setting/SettingPage.tsx b/frontend/src/pages/setting/SettingPage.tsx new file mode 100644 index 0000000..7db9f9e --- /dev/null +++ b/frontend/src/pages/setting/SettingPage.tsx @@ -0,0 +1,95 @@ +const SettingPage = () => ( +
+
+

프로젝트 설정

+
+
+
+ +
+ +

+ 프로젝트 이름을 입력해주세요 +

+
+
+
+ +
+ +

+ 프로젝트 주제를 입력해주세요 +

+
+
+ +
+
+

멤버 관리

+
+
+
+

닉네임

+

역할

+

작업

+
+
+
+ {/* */} +
+

lesserTest

+
+
+

멤버

+
+
+ +
+
+
+
+
+

프로젝트 삭제

+

프로젝트를 삭제한 후 되돌릴 수 없습니다.

+
+ +
+
+); + +export default SettingPage; From 792b1b102984d780787b67406f35f71297f4439e Mon Sep 17 00:00:00 2001 From: surinkwon Date: Tue, 10 Sep 2024 14:06:01 +0900 Subject: [PATCH 02/12] =?UTF-8?q?feat:=20=ED=94=84=EB=A1=9C=EC=A0=9D?= =?UTF-8?q?=ED=8A=B8=20=EC=9D=B4=EB=A6=84,=20=EC=A3=BC=EC=A0=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EB=A1=9C=EC=A7=81=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/setting/InformationInput.tsx | 39 +++++++ .../setting/InformationSettingSection.tsx | 104 ++++++++++++++++++ frontend/src/pages/setting/SettingPage.tsx | 52 +-------- 3 files changed, 146 insertions(+), 49 deletions(-) create mode 100644 frontend/src/components/setting/InformationInput.tsx create mode 100644 frontend/src/components/setting/InformationSettingSection.tsx diff --git a/frontend/src/components/setting/InformationInput.tsx b/frontend/src/components/setting/InformationInput.tsx new file mode 100644 index 0000000..93b622b --- /dev/null +++ b/frontend/src/components/setting/InformationInput.tsx @@ -0,0 +1,39 @@ +import { ChangeEvent } from "react"; + +interface InformationInputProps { + label: string; + inputId: string; + inputValue: string; + errorMessage: string; + onChange: (event: ChangeEvent) => void; +} + +const InformationInput = ({ + label, + inputId, + inputValue, + errorMessage, + onChange, +}: InformationInputProps) => ( +
+ +
+ +

{errorMessage}

+
+
+); + +export default InformationInput; diff --git a/frontend/src/components/setting/InformationSettingSection.tsx b/frontend/src/components/setting/InformationSettingSection.tsx new file mode 100644 index 0000000..f675a36 --- /dev/null +++ b/frontend/src/components/setting/InformationSettingSection.tsx @@ -0,0 +1,104 @@ +import { ChangeEvent, useMemo, useState } from "react"; +import InformationInput from "./InformationInput"; + +interface InformationSettingSectionProps { + title: string; + subject: string; +} + +const InformationSettingSection = ({ + title, + subject, +}: InformationSettingSectionProps) => { + const [titleValue, setTitleValue] = useState(title); + const [titleErrorMessage, setTitleErrorMessage] = useState(""); + const [subjectValue, setSubjectValue] = useState(subject); + const [subjectErrorMessage, setSubjectErrorMessage] = useState(""); + const submitActivated = useMemo( + () => + !( + !titleValue || + !subjectValue || + (titleValue === title && subjectValue === subject) + ), + [titleValue, subjectValue] + ); + + const handleTitleChange = (event: ChangeEvent) => { + const { value } = event.target; + + setTitleValue(value); + + if (!value.trim()) { + setTitleErrorMessage("프로젝트 이름을 입력해주세요."); + return; + } + + if (value.length > 255) { + setTitleErrorMessage("프로젝트 이름이 너무 깁니다."); + return; + } + + setTitleErrorMessage(""); + }; + + const handleSubjectChange = (event: ChangeEvent) => { + const { value } = event.target; + + setSubjectValue(value); + + if (!value.trim()) { + setSubjectErrorMessage("프로젝트 주제를 입력해주세요."); + return; + } + + if (value.length > 255) { + setSubjectErrorMessage("프로젝트 주제가 너무 깁니다."); + return; + } + + setSubjectErrorMessage(""); + }; + + const handleSubmit = () => {}; + + return ( + <> +
+

프로젝트 설정

+
+
+ + +
+ {!submitActivated && ( +
+ )} + + +
+
+ + ); +}; + +export default InformationSettingSection; diff --git a/frontend/src/pages/setting/SettingPage.tsx b/frontend/src/pages/setting/SettingPage.tsx index 7db9f9e..7bf5dd0 100644 --- a/frontend/src/pages/setting/SettingPage.tsx +++ b/frontend/src/pages/setting/SettingPage.tsx @@ -1,54 +1,8 @@ +import InformationSettingSection from "../../components/setting/InformationSettingSection"; + const SettingPage = () => (
-
-

프로젝트 설정

-
-
-
- -
- -

- 프로젝트 이름을 입력해주세요 -

-
-
-
- -
- -

- 프로젝트 주제를 입력해주세요 -

-
-
- -
+

멤버 관리

From a496944f187289c0be4cae3573afd8ac7f39939f Mon Sep 17 00:00:00 2001 From: surinkwon Date: Tue, 10 Sep 2024 14:33:32 +0900 Subject: [PATCH 03/12] =?UTF-8?q?feat:=20=EB=A9=A4=EB=B2=84=20=EA=B4=80?= =?UTF-8?q?=EB=A6=AC=20=EA=B5=AC=EC=97=AD=20=EC=BB=B4=ED=8F=AC=EB=84=8C?= =?UTF-8?q?=ED=8A=B8=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../setting/InformationSettingSection.tsx | 2 +- .../src/components/setting/MemberBlock.tsx | 32 ++++++++++++ .../setting/MemberSettingSection.tsx | 26 ++++++++++ frontend/src/pages/setting/SettingPage.tsx | 49 ++++++++----------- frontend/src/stores/useMemberStore.ts | 2 +- frontend/src/types/DTO/landingDTO.ts | 3 ++ 6 files changed, 84 insertions(+), 30 deletions(-) create mode 100644 frontend/src/components/setting/MemberBlock.tsx create mode 100644 frontend/src/components/setting/MemberSettingSection.tsx diff --git a/frontend/src/components/setting/InformationSettingSection.tsx b/frontend/src/components/setting/InformationSettingSection.tsx index f675a36..99ffe8a 100644 --- a/frontend/src/components/setting/InformationSettingSection.tsx +++ b/frontend/src/components/setting/InformationSettingSection.tsx @@ -64,7 +64,7 @@ const InformationSettingSection = ({ return ( <> -
+

프로젝트 설정

diff --git a/frontend/src/components/setting/MemberBlock.tsx b/frontend/src/components/setting/MemberBlock.tsx new file mode 100644 index 0000000..e4c319f --- /dev/null +++ b/frontend/src/components/setting/MemberBlock.tsx @@ -0,0 +1,32 @@ +import useMemberStore from "../../stores/useMemberStore"; +import { LandingMemberDTO } from "../../types/DTO/landingDTO"; + +interface MemberBlockProps extends LandingMemberDTO {} + +const MemberBlock = ({ username, imageUrl, role }: MemberBlockProps) => { + const myRole = useMemberStore((state) => state.myInfo.role); + + return ( +
+
+ {username} +

{username}

+
+
+

{role}

+
+
+ {myRole === "LEADER" && ( + + )} +
+
+ ); +}; + +export default MemberBlock; diff --git a/frontend/src/components/setting/MemberSettingSection.tsx b/frontend/src/components/setting/MemberSettingSection.tsx new file mode 100644 index 0000000..c5d1ab4 --- /dev/null +++ b/frontend/src/components/setting/MemberSettingSection.tsx @@ -0,0 +1,26 @@ +import { LandingMemberDTO } from "../../types/DTO/landingDTO"; +import MemberBlock from "./MemberBlock"; + +interface MemberSettingSectionProps { + memberList: LandingMemberDTO[]; +} + +const MemberSettingSection = ({ memberList }: MemberSettingSectionProps) => ( +
+
+

멤버 관리

+
+
+
+

닉네임

+

역할

+

작업

+
+
+ {...memberList.map((member) => )} +
+
+
+); + +export default MemberSettingSection; diff --git a/frontend/src/pages/setting/SettingPage.tsx b/frontend/src/pages/setting/SettingPage.tsx index 7bf5dd0..00630c7 100644 --- a/frontend/src/pages/setting/SettingPage.tsx +++ b/frontend/src/pages/setting/SettingPage.tsx @@ -1,36 +1,29 @@ import InformationSettingSection from "../../components/setting/InformationSettingSection"; +import MemberSettingSection from "../../components/setting/MemberSettingSection"; +import { LandingMemberDTO } from "../../types/DTO/landingDTO"; + +const memberList: LandingMemberDTO[] = [ + { id: 1, username: "lesserTest", role: "LEADER", imageUrl: "", status: "on" }, + { + id: 2, + username: "lesserTest2", + role: "MEMBER", + imageUrl: "", + status: "on", + }, + { + id: 3, + username: "lesserTest3", + role: "MEMBER", + imageUrl: "", + status: "on", + }, +]; const SettingPage = () => (
-
-

멤버 관리

-
-
-
-

닉네임

-

역할

-

작업

-
-
-
- {/* */} -
-

lesserTest

-
-
-

멤버

-
-
- -
-
-
+

프로젝트 삭제

diff --git a/frontend/src/stores/useMemberStore.ts b/frontend/src/stores/useMemberStore.ts index ba76552..4ec6cef 100644 --- a/frontend/src/stores/useMemberStore.ts +++ b/frontend/src/stores/useMemberStore.ts @@ -15,7 +15,7 @@ interface MemberState extends InitialMemberState { } const initialState: InitialMemberState = { - myInfo: { id: -1, username: "", imageUrl: "", status: "on" }, + myInfo: { id: -1, username: "", imageUrl: "", status: "on", role: "MEMBER" }, memberList: [], }; diff --git a/frontend/src/types/DTO/landingDTO.ts b/frontend/src/types/DTO/landingDTO.ts index 4d152ae..7d3230c 100644 --- a/frontend/src/types/DTO/landingDTO.ts +++ b/frontend/src/types/DTO/landingDTO.ts @@ -2,6 +2,8 @@ import { MemoColorType } from "../common/landing"; export type MemberStatus = "on" | "off" | "away"; +export type MemberRole = "LEADER" | "MEMBER"; + export interface LandingProjectDTO { title: string; subject: string; @@ -13,6 +15,7 @@ export interface LandingMemberDTO { username: string; imageUrl: string; status: MemberStatus; + role: MemberRole; } export interface LandingSprintDTO { From d8c8058fad8124ebba15f526b8d97093331db8b7 Mon Sep 17 00:00:00 2001 From: surinkwon Date: Tue, 10 Sep 2024 15:40:02 +0900 Subject: [PATCH 04/12] =?UTF-8?q?feat:=20=ED=94=84=EB=A1=9C=EC=A0=9D?= =?UTF-8?q?=ED=8A=B8=20=EC=82=AD=EC=A0=9C=20=EB=A1=9C=EC=A7=81=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../setting/MemberSettingSection.tsx | 2 +- .../components/setting/ProjectDeleteModal.tsx | 82 +++++++++++++++++++ .../setting/ProjectDeleteSection.tsx | 31 +++++++ frontend/src/pages/setting/SettingPage.tsx | 14 +--- 4 files changed, 116 insertions(+), 13 deletions(-) create mode 100644 frontend/src/components/setting/ProjectDeleteModal.tsx create mode 100644 frontend/src/components/setting/ProjectDeleteSection.tsx diff --git a/frontend/src/components/setting/MemberSettingSection.tsx b/frontend/src/components/setting/MemberSettingSection.tsx index c5d1ab4..82bdfaa 100644 --- a/frontend/src/components/setting/MemberSettingSection.tsx +++ b/frontend/src/components/setting/MemberSettingSection.tsx @@ -16,7 +16,7 @@ const MemberSettingSection = ({ memberList }: MemberSettingSectionProps) => (

역할

작업

-
+
{...memberList.map((member) => )}
diff --git a/frontend/src/components/setting/ProjectDeleteModal.tsx b/frontend/src/components/setting/ProjectDeleteModal.tsx new file mode 100644 index 0000000..97608d1 --- /dev/null +++ b/frontend/src/components/setting/ProjectDeleteModal.tsx @@ -0,0 +1,82 @@ +import { ChangeEvent, MouseEventHandler, useState } from "react"; +import Closed from "../../assets/icons/closed.svg?react"; + +interface ProjectDeleteModalProps { + projectTitle: string; + close: () => void; +} + +const ProjectDeleteModal = ({ + projectTitle, + close, +}: ProjectDeleteModalProps) => { + const [inputValue, setInputValue] = useState(""); + const [confirmed, setConfirmed] = useState(false); + + const handleInputChange = (event: ChangeEvent) => { + const { value } = event.target; + + setInputValue(value); + + if (value === projectTitle) { + setConfirmed(true); + } else { + setConfirmed(false); + } + }; + + const handleCloseClick: MouseEventHandler< + HTMLButtonElement | HTMLDivElement + > = ({ target, currentTarget }: React.MouseEvent) => { + if (target !== currentTarget) { + return; + } + close(); + }; + + return ( +
+
+
+

|프로젝트 삭제

+ +
+

프로젝트 삭제 후 되돌릴 수 없습니다.

+
+

+ 삭제하시려면 "{projectTitle}"을(를) 입력해주세요. +

+ +
+ {!confirmed && ( +
+ )} + +
+
+
+
+ ); +}; + +export default ProjectDeleteModal; diff --git a/frontend/src/components/setting/ProjectDeleteSection.tsx b/frontend/src/components/setting/ProjectDeleteSection.tsx new file mode 100644 index 0000000..066735c --- /dev/null +++ b/frontend/src/components/setting/ProjectDeleteSection.tsx @@ -0,0 +1,31 @@ +import { useModal } from "../../hooks/common/modal/useModal"; +import ProjectDeleteModal from "./ProjectDeleteModal"; + +interface ProjectDeleteSectionProps { + projectTitle: string; +} + +const ProjectDeleteSection = ({ projectTitle }: ProjectDeleteSectionProps) => { + const { open, close } = useModal(true); + const handleDeleteButtonClick = () => { + open(); + }; + + return ( +
+
+

프로젝트 삭제

+

프로젝트를 삭제한 후 되돌릴 수 없습니다.

+
+ +
+ ); +}; + +export default ProjectDeleteSection; diff --git a/frontend/src/pages/setting/SettingPage.tsx b/frontend/src/pages/setting/SettingPage.tsx index 00630c7..298dc41 100644 --- a/frontend/src/pages/setting/SettingPage.tsx +++ b/frontend/src/pages/setting/SettingPage.tsx @@ -1,5 +1,6 @@ import InformationSettingSection from "../../components/setting/InformationSettingSection"; import MemberSettingSection from "../../components/setting/MemberSettingSection"; +import ProjectDeleteSection from "../../components/setting/ProjectDeleteSection"; import { LandingMemberDTO } from "../../types/DTO/landingDTO"; const memberList: LandingMemberDTO[] = [ @@ -24,18 +25,7 @@ const SettingPage = () => (
-
-
-

프로젝트 삭제

-

프로젝트를 삭제한 후 되돌릴 수 없습니다.

-
- -
+
); From 06e9ec30726e732f5eeda32de5e8c79929e78c42 Mon Sep 17 00:00:00 2001 From: surinkwon Date: Thu, 12 Sep 2024 20:35:57 +0900 Subject: [PATCH 05/12] =?UTF-8?q?feat:=20setting=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=20=EC=86=8C=EC=BC=93=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../setting/InformationSettingSection.tsx | 32 +++++----- .../setting/useSettingProjectInfoSocket.ts | 58 +++++++++++++++++++ .../hooks/pages/setting/useSettingSocket.ts | 10 ++++ frontend/src/pages/setting/SettingPage.tsx | 22 ++++--- frontend/src/types/DTO/settingDTO.ts | 18 ++++++ frontend/src/types/common/setting.ts | 27 +++++++++ 6 files changed, 147 insertions(+), 20 deletions(-) create mode 100644 frontend/src/hooks/pages/setting/useSettingProjectInfoSocket.ts create mode 100644 frontend/src/hooks/pages/setting/useSettingSocket.ts create mode 100644 frontend/src/types/DTO/settingDTO.ts create mode 100644 frontend/src/types/common/setting.ts diff --git a/frontend/src/components/setting/InformationSettingSection.tsx b/frontend/src/components/setting/InformationSettingSection.tsx index 99ffe8a..50beeac 100644 --- a/frontend/src/components/setting/InformationSettingSection.tsx +++ b/frontend/src/components/setting/InformationSettingSection.tsx @@ -1,18 +1,19 @@ -import { ChangeEvent, useMemo, useState } from "react"; +import { ChangeEvent, useEffect, useMemo, useState } from "react"; import InformationInput from "./InformationInput"; - -interface InformationSettingSectionProps { - title: string; - subject: string; -} - -const InformationSettingSection = ({ - title, - subject, -}: InformationSettingSectionProps) => { - const [titleValue, setTitleValue] = useState(title); +import { useOutletContext } from "react-router-dom"; +import { Socket } from "socket.io-client"; +import useSettingProjectInfoSocket from "../../hooks/pages/setting/useSettingProjectInfoSocket"; + +interface InformationSettingSectionProps {} + +const InformationSettingSection = ({}: InformationSettingSectionProps) => { + const { socket }: { socket: Socket } = useOutletContext(); + const { + projectInfo: { title, subject }, + } = useSettingProjectInfoSocket(socket); + const [titleValue, setTitleValue] = useState(""); const [titleErrorMessage, setTitleErrorMessage] = useState(""); - const [subjectValue, setSubjectValue] = useState(subject); + const [subjectValue, setSubjectValue] = useState(""); const [subjectErrorMessage, setSubjectErrorMessage] = useState(""); const submitActivated = useMemo( () => @@ -62,6 +63,11 @@ const InformationSettingSection = ({ const handleSubmit = () => {}; + useEffect(() => { + setTitleValue(title); + setSubjectValue(subject); + }, [title, subject]); + return ( <>
diff --git a/frontend/src/hooks/pages/setting/useSettingProjectInfoSocket.ts b/frontend/src/hooks/pages/setting/useSettingProjectInfoSocket.ts new file mode 100644 index 0000000..0f9d5f7 --- /dev/null +++ b/frontend/src/hooks/pages/setting/useSettingProjectInfoSocket.ts @@ -0,0 +1,58 @@ +import { useEffect, useState } from "react"; +import { Socket } from "socket.io-client"; +import { SettingDTO, SettingProjectDTO } from "../../../types/DTO/settingDTO"; +import { + SettingSocketData, + SettingSocketDomain, + SettingSocketProjectInfoAction, +} from "../../../types/common/setting"; + +const useSettingProjectInfoSocket = (socket: Socket) => { + const [projectInfo, setProjectInfo] = useState({ + title: "", + subject: "", + }); + + const handleInitEvent = (content: SettingDTO) => { + const { project } = content; + setProjectInfo(project); + }; + + const handleProjectInfoEvent = ( + action: SettingSocketProjectInfoAction, + content: SettingProjectDTO + ) => { + switch (action) { + case SettingSocketProjectInfoAction.UPDATE: + setProjectInfo(content); + break; + } + }; + + const handleOnProjectInfoSetting = ({ + domain, + action, + content, + }: SettingSocketData) => { + switch (domain) { + case SettingSocketDomain.INIT: + handleInitEvent(content); + break; + case SettingSocketDomain.PROJECT_INFO: + handleProjectInfoEvent(action, content); + break; + } + }; + + useEffect(() => { + socket.on("setting", handleOnProjectInfoSetting); + + return () => { + socket.off("setting", handleOnProjectInfoSetting); + }; + }, []); + + return { projectInfo }; +}; + +export default useSettingProjectInfoSocket; diff --git a/frontend/src/hooks/pages/setting/useSettingSocket.ts b/frontend/src/hooks/pages/setting/useSettingSocket.ts new file mode 100644 index 0000000..5c8f74e --- /dev/null +++ b/frontend/src/hooks/pages/setting/useSettingSocket.ts @@ -0,0 +1,10 @@ +import { useEffect } from "react"; +import { Socket } from "socket.io-client"; + +const useSettingSocket = (socket: Socket) => { + useEffect(() => { + socket.emit("joinLanding"); + }, []); +}; + +export default useSettingSocket; diff --git a/frontend/src/pages/setting/SettingPage.tsx b/frontend/src/pages/setting/SettingPage.tsx index 298dc41..f829200 100644 --- a/frontend/src/pages/setting/SettingPage.tsx +++ b/frontend/src/pages/setting/SettingPage.tsx @@ -1,7 +1,10 @@ +import { Socket } from "socket.io-client"; import InformationSettingSection from "../../components/setting/InformationSettingSection"; import MemberSettingSection from "../../components/setting/MemberSettingSection"; import ProjectDeleteSection from "../../components/setting/ProjectDeleteSection"; import { LandingMemberDTO } from "../../types/DTO/landingDTO"; +import { useOutletContext } from "react-router-dom"; +import useSettingSocket from "../../hooks/pages/setting/useSettingSocket"; const memberList: LandingMemberDTO[] = [ { id: 1, username: "lesserTest", role: "LEADER", imageUrl: "", status: "on" }, @@ -21,12 +24,17 @@ const memberList: LandingMemberDTO[] = [ }, ]; -const SettingPage = () => ( -
- - - -
-); +const SettingPage = () => { + const { socket }: { socket: Socket } = useOutletContext(); + useSettingSocket(socket); + + return ( +
+ + + +
+ ); +}; export default SettingPage; diff --git a/frontend/src/types/DTO/settingDTO.ts b/frontend/src/types/DTO/settingDTO.ts new file mode 100644 index 0000000..2ffe4bc --- /dev/null +++ b/frontend/src/types/DTO/settingDTO.ts @@ -0,0 +1,18 @@ +import { MemberRole } from "./landingDTO"; + +export interface SettingProjectDTO { + title: string; + subject: string; +} + +export interface SettingMemberDTO { + id: number; + username: string; + imageUrl: string; + role: MemberRole; +} + +export interface SettingDTO { + project: SettingProjectDTO; + member: SettingMemberDTO[]; +} diff --git a/frontend/src/types/common/setting.ts b/frontend/src/types/common/setting.ts new file mode 100644 index 0000000..6780efb --- /dev/null +++ b/frontend/src/types/common/setting.ts @@ -0,0 +1,27 @@ +import { SettingDTO, SettingProjectDTO } from "../DTO/settingDTO"; + +export enum SettingSocketDomain { + INIT = "setting", + PROJECT_INFO = "projectInfo", +} + +export enum SettingSocketProjectInfoAction { + UPDATE = "update", + DELETE = "delete", +} + +interface SettingSocketInitData { + domain: SettingSocketDomain.INIT; + action: "init"; + content: SettingDTO; +} + +interface SettingSocketProjectInfoData { + domain: SettingSocketDomain.PROJECT_INFO; + action: SettingSocketProjectInfoAction; + content: SettingProjectDTO; +} + +export type SettingSocketData = + | SettingSocketInitData + | SettingSocketProjectInfoData; From 46e02fbe92521192d3b0bc7258ba1b5c99a6a63b Mon Sep 17 00:00:00 2001 From: surinkwon Date: Thu, 12 Sep 2024 20:39:01 +0900 Subject: [PATCH 06/12] =?UTF-8?q?design:=20=ED=8E=98=EC=9D=B4=EC=A7=80=20?= =?UTF-8?q?=ED=81=AC=EA=B8=B0=EC=97=90=20=EB=A7=9E=EA=B2=8C=20setting=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20input=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20width=20=EC=A1=B0=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/setting/InformationInput.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/setting/InformationInput.tsx b/frontend/src/components/setting/InformationInput.tsx index 93b622b..13efe75 100644 --- a/frontend/src/components/setting/InformationInput.tsx +++ b/frontend/src/components/setting/InformationInput.tsx @@ -24,7 +24,7 @@ const InformationInput = ({
Date: Fri, 13 Sep 2024 15:33:48 +0900 Subject: [PATCH 07/12] =?UTF-8?q?feat:=20setting=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=20=ED=94=84=EB=A1=9C=EC=A0=9D=ED=8A=B8=20=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=20=EC=88=98=EC=A0=95=20API=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../setting/InformationSettingSection.tsx | 26 +++++---- .../setting/useSettingProjectInfoSocket.ts | 56 ++----------------- .../hooks/pages/setting/useSettingSocket.ts | 53 +++++++++++++++++- frontend/src/pages/setting/SettingPage.tsx | 6 +- 4 files changed, 77 insertions(+), 64 deletions(-) diff --git a/frontend/src/components/setting/InformationSettingSection.tsx b/frontend/src/components/setting/InformationSettingSection.tsx index 50beeac..551ea9e 100644 --- a/frontend/src/components/setting/InformationSettingSection.tsx +++ b/frontend/src/components/setting/InformationSettingSection.tsx @@ -4,16 +4,20 @@ import { useOutletContext } from "react-router-dom"; import { Socket } from "socket.io-client"; import useSettingProjectInfoSocket from "../../hooks/pages/setting/useSettingProjectInfoSocket"; -interface InformationSettingSectionProps {} - -const InformationSettingSection = ({}: InformationSettingSectionProps) => { +interface InformationSettingSectionProps { + title: string; + subject: string; +} + +const InformationSettingSection = ({ + title, + subject, +}: InformationSettingSectionProps) => { const { socket }: { socket: Socket } = useOutletContext(); - const { - projectInfo: { title, subject }, - } = useSettingProjectInfoSocket(socket); - const [titleValue, setTitleValue] = useState(""); + const { emitProjectInfoUpdateEvent } = useSettingProjectInfoSocket(socket); + const [titleValue, setTitleValue] = useState(title); const [titleErrorMessage, setTitleErrorMessage] = useState(""); - const [subjectValue, setSubjectValue] = useState(""); + const [subjectValue, setSubjectValue] = useState(subject); const [subjectErrorMessage, setSubjectErrorMessage] = useState(""); const submitActivated = useMemo( () => @@ -22,7 +26,7 @@ const InformationSettingSection = ({}: InformationSettingSectionProps) => { !subjectValue || (titleValue === title && subjectValue === subject) ), - [titleValue, subjectValue] + [title, subject, titleValue, subjectValue] ); const handleTitleChange = (event: ChangeEvent) => { @@ -61,7 +65,9 @@ const InformationSettingSection = ({}: InformationSettingSectionProps) => { setSubjectErrorMessage(""); }; - const handleSubmit = () => {}; + const handleSubmit = () => { + emitProjectInfoUpdateEvent({ title: titleValue, subject: subjectValue }); + }; useEffect(() => { setTitleValue(title); diff --git a/frontend/src/hooks/pages/setting/useSettingProjectInfoSocket.ts b/frontend/src/hooks/pages/setting/useSettingProjectInfoSocket.ts index 0f9d5f7..00ec667 100644 --- a/frontend/src/hooks/pages/setting/useSettingProjectInfoSocket.ts +++ b/frontend/src/hooks/pages/setting/useSettingProjectInfoSocket.ts @@ -1,58 +1,14 @@ -import { useEffect, useState } from "react"; import { Socket } from "socket.io-client"; -import { SettingDTO, SettingProjectDTO } from "../../../types/DTO/settingDTO"; -import { - SettingSocketData, - SettingSocketDomain, - SettingSocketProjectInfoAction, -} from "../../../types/common/setting"; const useSettingProjectInfoSocket = (socket: Socket) => { - const [projectInfo, setProjectInfo] = useState({ - title: "", - subject: "", - }); - - const handleInitEvent = (content: SettingDTO) => { - const { project } = content; - setProjectInfo(project); - }; - - const handleProjectInfoEvent = ( - action: SettingSocketProjectInfoAction, - content: SettingProjectDTO - ) => { - switch (action) { - case SettingSocketProjectInfoAction.UPDATE: - setProjectInfo(content); - break; - } + const emitProjectInfoUpdateEvent = (content: { + title: string; + subject: string; + }) => { + socket.emit("projectInfo", { action: "update", content }); }; - const handleOnProjectInfoSetting = ({ - domain, - action, - content, - }: SettingSocketData) => { - switch (domain) { - case SettingSocketDomain.INIT: - handleInitEvent(content); - break; - case SettingSocketDomain.PROJECT_INFO: - handleProjectInfoEvent(action, content); - break; - } - }; - - useEffect(() => { - socket.on("setting", handleOnProjectInfoSetting); - - return () => { - socket.off("setting", handleOnProjectInfoSetting); - }; - }, []); - - return { projectInfo }; + return { emitProjectInfoUpdateEvent }; }; export default useSettingProjectInfoSocket; diff --git a/frontend/src/hooks/pages/setting/useSettingSocket.ts b/frontend/src/hooks/pages/setting/useSettingSocket.ts index 5c8f74e..7f1f549 100644 --- a/frontend/src/hooks/pages/setting/useSettingSocket.ts +++ b/frontend/src/hooks/pages/setting/useSettingSocket.ts @@ -1,10 +1,59 @@ -import { useEffect } from "react"; +import { useEffect, useState } from "react"; import { Socket } from "socket.io-client"; +import { + SettingSocketData, + SettingSocketDomain, + SettingSocketProjectInfoAction, +} from "../../../types/common/setting"; +import { SettingDTO, SettingProjectDTO } from "../../../types/DTO/settingDTO"; const useSettingSocket = (socket: Socket) => { + const [projectInfo, setProjectInfo] = useState({ + title: "", + subject: "", + }); + + const handleInitEvent = (content: SettingDTO) => { + const { project } = content; + setProjectInfo(project); + }; + + const handleProjectInfoEvent = ( + action: SettingSocketProjectInfoAction, + content: SettingProjectDTO + ) => { + switch (action) { + case SettingSocketProjectInfoAction.UPDATE: + setProjectInfo(content); + break; + } + }; + + const handleOnProjectInfoSetting = ({ + domain, + action, + content, + }: SettingSocketData) => { + switch (domain) { + case SettingSocketDomain.INIT: + handleInitEvent(content); + break; + case SettingSocketDomain.PROJECT_INFO: + handleProjectInfoEvent(action, content); + break; + } + }; + useEffect(() => { - socket.emit("joinLanding"); + socket.emit("joinSetting"); + socket.on("setting", handleOnProjectInfoSetting); + + return () => { + socket.off("setting", handleOnProjectInfoSetting); + }; }, []); + + return { projectInfo }; }; export default useSettingSocket; diff --git a/frontend/src/pages/setting/SettingPage.tsx b/frontend/src/pages/setting/SettingPage.tsx index f829200..5f9882b 100644 --- a/frontend/src/pages/setting/SettingPage.tsx +++ b/frontend/src/pages/setting/SettingPage.tsx @@ -26,11 +26,13 @@ const memberList: LandingMemberDTO[] = [ const SettingPage = () => { const { socket }: { socket: Socket } = useOutletContext(); - useSettingSocket(socket); + const { + projectInfo: { title, subject }, + } = useSettingSocket(socket); return (
- +
From 4c61c2f33fcdbc4f81179ad33ed5a4a8e439b60a Mon Sep 17 00:00:00 2001 From: surinkwon Date: Wed, 18 Sep 2024 12:32:40 +0900 Subject: [PATCH 08/12] =?UTF-8?q?feat:=20=EC=82=AD=EC=A0=9C=20API=20?= =?UTF-8?q?=EC=97=B0=EA=B2=B0=20=EB=A1=9C=EC=A7=81=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/setting/ProjectDeleteModal.tsx | 19 +++++++++++++++++-- .../setting/ProjectDeleteSection.tsx | 5 ++++- frontend/src/hooks/common/socket/useSocket.ts | 18 ++++++++++++++++-- .../pages/setting/useSettingProjectSocket.ts | 11 +++++++++++ frontend/src/pages/setting/SettingPage.tsx | 2 +- 5 files changed, 49 insertions(+), 6 deletions(-) create mode 100644 frontend/src/hooks/pages/setting/useSettingProjectSocket.ts diff --git a/frontend/src/components/setting/ProjectDeleteModal.tsx b/frontend/src/components/setting/ProjectDeleteModal.tsx index 97608d1..9301295 100644 --- a/frontend/src/components/setting/ProjectDeleteModal.tsx +++ b/frontend/src/components/setting/ProjectDeleteModal.tsx @@ -1,18 +1,24 @@ import { ChangeEvent, MouseEventHandler, useState } from "react"; +import useSettingProjectSocket from "../../hooks/pages/setting/useSettingProjectSocket"; import Closed from "../../assets/icons/closed.svg?react"; +import { Socket } from "socket.io-client"; interface ProjectDeleteModalProps { projectTitle: string; + socket: Socket; close: () => void; } const ProjectDeleteModal = ({ projectTitle, + socket, close, }: ProjectDeleteModalProps) => { const [inputValue, setInputValue] = useState(""); const [confirmed, setConfirmed] = useState(false); + const { emitProjectDeleteEvent } = useSettingProjectSocket(socket); + const handleInputChange = (event: ChangeEvent) => { const { value } = event.target; @@ -34,6 +40,12 @@ const ProjectDeleteModal = ({ close(); }; + const handleDeleteButtonClick = () => { + if (confirmed) { + emitProjectDeleteEvent(); + } + }; + return (
{!confirmed && ( -
+
)} diff --git a/frontend/src/components/setting/ProjectDeleteSection.tsx b/frontend/src/components/setting/ProjectDeleteSection.tsx index 066735c..1bfcfaf 100644 --- a/frontend/src/components/setting/ProjectDeleteSection.tsx +++ b/frontend/src/components/setting/ProjectDeleteSection.tsx @@ -1,3 +1,5 @@ +import { useOutletContext } from "react-router-dom"; +import { Socket } from "socket.io-client"; import { useModal } from "../../hooks/common/modal/useModal"; import ProjectDeleteModal from "./ProjectDeleteModal"; @@ -7,8 +9,9 @@ interface ProjectDeleteSectionProps { const ProjectDeleteSection = ({ projectTitle }: ProjectDeleteSectionProps) => { const { open, close } = useModal(true); + const { socket }: { socket: Socket } = useOutletContext(); const handleDeleteButtonClick = () => { - open(); + open(); }; return ( diff --git a/frontend/src/hooks/common/socket/useSocket.ts b/frontend/src/hooks/common/socket/useSocket.ts index de0fada..f575984 100644 --- a/frontend/src/hooks/common/socket/useSocket.ts +++ b/frontend/src/hooks/common/socket/useSocket.ts @@ -1,7 +1,9 @@ -import { io } from "socket.io-client"; -import { BASE_URL } from "../../../constants/path"; import { useEffect, useState } from "react"; +import { useNavigate } from "react-router-dom"; +import { io } from "socket.io-client"; +import { BASE_URL, ROUTER_URL } from "../../../constants/path"; import { getAccessToken } from "../../../apis/utils/authAPI"; +import { SettingSocketData } from "../../../types/common/setting"; const useSocket = (projectId: string) => { const WS_URL = `${BASE_URL}/project-${projectId}`; @@ -15,6 +17,16 @@ const useSocket = (projectId: string) => { }) ); const [connected, setConnected] = useState(false); + const navigate = useNavigate(); + + const handleProjectDeleted = ({ domain, action }: SettingSocketData) => { + if (domain === "projectInfo" && action === "delete") { + alert("프로젝트가 삭제되었습니다."); + setTimeout(() => { + navigate(ROUTER_URL.PROJECTS); + }, 1000); + } + }; useEffect(() => { const handleOnConnect = () => { @@ -27,11 +39,13 @@ const useSocket = (projectId: string) => { socket.connect(); socket.on("connect", handleOnConnect); socket.on("disconnect", handleOnDisconnect); + socket.on("main", handleProjectDeleted); return () => { socket.disconnect(); socket.off("connect", handleOnConnect); socket.off("disconnect", handleOnDisconnect); + socket.off("main", handleProjectDeleted); }; }, []); diff --git a/frontend/src/hooks/pages/setting/useSettingProjectSocket.ts b/frontend/src/hooks/pages/setting/useSettingProjectSocket.ts new file mode 100644 index 0000000..2423f6a --- /dev/null +++ b/frontend/src/hooks/pages/setting/useSettingProjectSocket.ts @@ -0,0 +1,11 @@ +import { Socket } from "socket.io-client"; + +const useSettingProjectSocket = (socket: Socket) => { + const emitProjectDeleteEvent = () => { + socket.emit("projectInfo", { action: "delete", content: {} }); + }; + + return { emitProjectDeleteEvent }; +}; + +export default useSettingProjectSocket; diff --git a/frontend/src/pages/setting/SettingPage.tsx b/frontend/src/pages/setting/SettingPage.tsx index 5f9882b..990bce2 100644 --- a/frontend/src/pages/setting/SettingPage.tsx +++ b/frontend/src/pages/setting/SettingPage.tsx @@ -34,7 +34,7 @@ const SettingPage = () => {
- +
); }; From 9230c6f291072a8db432d82da0d0afee5a544585 Mon Sep 17 00:00:00 2001 From: surinkwon Date: Wed, 18 Sep 2024 12:38:56 +0900 Subject: [PATCH 09/12] =?UTF-8?q?feat:=20=EC=B4=88=EB=8C=80=20=EB=A7=81?= =?UTF-8?q?=ED=81=AC=20=EB=B3=B5=EC=82=AC=20=EB=B2=84=ED=8A=BC=EC=9D=B4=20?= =?UTF-8?q?=EB=A6=AC=EB=8D=94=EC=97=90=EA=B2=8C=EB=A7=8C=20=EB=B3=B4?= =?UTF-8?q?=EC=9D=B4=EB=8F=84=EB=A1=9D=20=ED=95=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../landing/member/LandingMember.tsx | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/frontend/src/components/landing/member/LandingMember.tsx b/frontend/src/components/landing/member/LandingMember.tsx index b26ed7a..4dd8245 100644 --- a/frontend/src/components/landing/member/LandingMember.tsx +++ b/frontend/src/components/landing/member/LandingMember.tsx @@ -94,12 +94,20 @@ const LandingMember = ({ projectTitle }: LandingMemberProps) => { />

| 함께하는 사람들

- + {myInfo.role === "LEADER" && ( +
+ + | + +
+ )}
{memberList.map((memberData: LandingMemberDTO) => ( From d6a05adfb738c505d19f6bfa38dab2109c6774ef Mon Sep 17 00:00:00 2001 From: surinkwon Date: Wed, 18 Sep 2024 12:41:58 +0900 Subject: [PATCH 10/12] =?UTF-8?q?feat:=20=ED=94=84=EB=A1=9C=EC=A0=9D?= =?UTF-8?q?=ED=8A=B8=20=EC=82=AD=EC=A0=9C=20=EC=8B=9C=20=ED=94=84=EB=A1=9C?= =?UTF-8?q?=EC=A0=9D=ED=8A=B8=20=EC=9D=B4=EB=A6=84=EC=9D=84=20=EB=B3=B5?= =?UTF-8?q?=EC=82=AC=ED=95=A0=20=EC=88=98=20=EC=97=86=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=ED=95=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/setting/ProjectDeleteModal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/setting/ProjectDeleteModal.tsx b/frontend/src/components/setting/ProjectDeleteModal.tsx index 9301295..1cb8ac0 100644 --- a/frontend/src/components/setting/ProjectDeleteModal.tsx +++ b/frontend/src/components/setting/ProjectDeleteModal.tsx @@ -60,7 +60,7 @@ const ProjectDeleteModal = ({

프로젝트 삭제 후 되돌릴 수 없습니다.

-

+

삭제하시려면 "{projectTitle}"을(를) 입력해주세요.

Date: Thu, 19 Sep 2024 00:10:33 +0900 Subject: [PATCH 11/12] =?UTF-8?q?feat:=20=ED=94=84=EB=A1=9C=EC=A0=9D?= =?UTF-8?q?=ED=8A=B8=20=EC=A0=95=EB=B3=B4=20=EC=88=98=EC=A0=95=20=EC=8B=9C?= =?UTF-8?q?=20=EB=9E=9C=EB=94=A9=20=ED=8E=98=EC=9D=B4=EC=A7=80=EC=97=90?= =?UTF-8?q?=EB=8F=84=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/landing/useLandingProjectSocket.ts | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/frontend/src/hooks/common/landing/useLandingProjectSocket.ts b/frontend/src/hooks/common/landing/useLandingProjectSocket.ts index a86f4ab..098b9a4 100644 --- a/frontend/src/hooks/common/landing/useLandingProjectSocket.ts +++ b/frontend/src/hooks/common/landing/useLandingProjectSocket.ts @@ -6,6 +6,11 @@ import { LandingSocketData, LandingSocketDomain, } from "../../../types/common/landing"; +import { + SettingSocketData, + SettingSocketDomain, +} from "../../../types/common/setting"; +import { SettingProjectDTO } from "../../../types/DTO/settingDTO"; const useLandingProjectSocket = (socket: Socket) => { const [project, setProject] = useState( @@ -17,11 +22,22 @@ const useLandingProjectSocket = (socket: Socket) => { setProject(project); }; - const handleOnLanding = ({ domain, content }: LandingSocketData) => { - if (domain !== LandingSocketDomain.INIT) { + const handleProjectInfoEvent = (content: SettingProjectDTO) => { + setProject({ ...project, title: content.title, subject: content.subject }); + }; + + const handleOnLanding = ({ + domain, + content, + }: LandingSocketData | SettingSocketData) => { + if (domain === SettingSocketDomain.PROJECT_INFO) { + handleProjectInfoEvent(content); + } + + if (domain === LandingSocketDomain.INIT) { + handleInitEvent(content); return; } - handleInitEvent(content); }; useEffect(() => { From b96ef8179a4b7dadad322d130461239b2732adbe Mon Sep 17 00:00:00 2001 From: surinkwon Date: Thu, 19 Sep 2024 00:15:52 +0900 Subject: [PATCH 12/12] =?UTF-8?q?test:=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=EC=97=90=20=EB=A9=A4=EB=B2=84=20=EC=97=AD?= =?UTF-8?q?=ED=95=A0=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/test/sortMemberByStatus.test.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/frontend/src/test/sortMemberByStatus.test.ts b/frontend/src/test/sortMemberByStatus.test.ts index cbf6504..f205cc0 100644 --- a/frontend/src/test/sortMemberByStatus.test.ts +++ b/frontend/src/test/sortMemberByStatus.test.ts @@ -4,12 +4,12 @@ import sortMemberByStatus from "../utils/sortMemberByStatus"; describe("sortMemberByStatus test", () => { it("on, away, off 순 정렬 테스트", () => { const memberList: LandingMemberDTO[] = [ - { id: 1, username: "", imageUrl: "", status: "off" }, - { id: 2, username: "", imageUrl: "", status: "on" }, - { id: 3, username: "", imageUrl: "", status: "away" }, - { id: 4, username: "", imageUrl: "", status: "on" }, - { id: 5, username: "", imageUrl: "", status: "off" }, - { id: 6, username: "", imageUrl: "", status: "away" }, + { id: 1, username: "", imageUrl: "", status: "off", role: "LEADER" }, + { id: 2, username: "", imageUrl: "", status: "on", role: "MEMBER" }, + { id: 3, username: "", imageUrl: "", status: "away", role: "MEMBER" }, + { id: 4, username: "", imageUrl: "", status: "on", role: "MEMBER" }, + { id: 5, username: "", imageUrl: "", status: "off", role: "MEMBER" }, + { id: 6, username: "", imageUrl: "", status: "away", role: "MEMBER" }, ]; const sortedMemberIdList = memberList