@@ -103,7 +109,10 @@ const LandingMember = ({ projectTitle }: LandingMemberProps) => {
초대링크 복사
|
-
diff --git a/frontend/src/components/landing/project/LandingProject.tsx b/frontend/src/components/landing/project/LandingProject.tsx
index 2da8e08..c134b97 100644
--- a/frontend/src/components/landing/project/LandingProject.tsx
+++ b/frontend/src/components/landing/project/LandingProject.tsx
@@ -4,6 +4,7 @@ import LandingProjectLink from "./LandingProjectLink";
import { useOutletContext } from "react-router-dom";
import useLandingProjectSocket from "../../../hooks/common/landing/useLandingProjectSocket";
+import useMemberStore from "../../../stores/useMemberStore";
interface LandingProjectProps {
projectId: string;
@@ -12,6 +13,7 @@ interface LandingProjectProps {
const LandingProject = ({ projectId }: LandingProjectProps) => {
const { socket }: { socket: Socket } = useOutletContext();
const { project } = useLandingProjectSocket(socket);
+ const { role } = useMemberStore((state) => state.myInfo);
return (
@@ -22,10 +24,12 @@ const LandingProject = ({ projectId }: LandingProjectProps) => {
);
diff --git a/frontend/src/components/landing/project/LandingProjectLink.tsx b/frontend/src/components/landing/project/LandingProjectLink.tsx
index 8653fcd..d6f4bab 100644
--- a/frontend/src/components/landing/project/LandingProjectLink.tsx
+++ b/frontend/src/components/landing/project/LandingProjectLink.tsx
@@ -12,7 +12,7 @@ const LandingProjectLink = ({ projectId, type }: LandingProjectLinkProps) => {
return (
diff --git a/frontend/src/components/main/PageLinkIcons.tsx b/frontend/src/components/main/PageLinkIcons.tsx
index 78b2566..d42e53d 100644
--- a/frontend/src/components/main/PageLinkIcons.tsx
+++ b/frontend/src/components/main/PageLinkIcons.tsx
@@ -5,34 +5,41 @@ import SprintIcon from "../../assets/icons/sprint.svg?react";
import SettingIcon from "../../assets/icons/settings.svg?react";
import { LINK_URL } from "../../constants/path";
import { ProjectSidebarProps } from "../../types/common/main";
+import useMemberStore from "../../stores/useMemberStore";
-const PageLinkIcons = ({ pathname, projectId }: ProjectSidebarProps) => (
-
-);
+const PageLinkIcons = ({ pathname, projectId }: ProjectSidebarProps) => {
+ const { role } = useMemberStore((state) => state.myInfo);
+
+ return (
+
+
+
+
+ {role === "LEADER" && (
+
+ )}
+
+ );
+};
export default PageLinkIcons;
diff --git a/frontend/src/components/setting/JoinRequestBlock.tsx b/frontend/src/components/setting/JoinRequestBlock.tsx
new file mode 100644
index 0000000..077c84d
--- /dev/null
+++ b/frontend/src/components/setting/JoinRequestBlock.tsx
@@ -0,0 +1,38 @@
+import useMemberStore from "../../stores/useMemberStore";
+import { SettingJoinRequestDTO } from "../../types/DTO/settingDTO";
+
+interface JoinRequestBlockProps extends SettingJoinRequestDTO {}
+
+const JoinRequestBlock = ({ username, imageUrl }: JoinRequestBlockProps) => {
+ const myRole = useMemberStore((state) => state.myInfo.role);
+
+ return (
+
+
+

+
{username}
+
+
+
+ {myRole === "LEADER" && (
+ <>
+
+ 참여 수락
+
+
+ 참여 거절
+
+ >
+ )}
+
+
+ );
+};
+
+export default JoinRequestBlock;
diff --git a/frontend/src/components/setting/MemberBlock.tsx b/frontend/src/components/setting/MemberBlock.tsx
index e4c319f..a6ba88d 100644
--- a/frontend/src/components/setting/MemberBlock.tsx
+++ b/frontend/src/components/setting/MemberBlock.tsx
@@ -1,10 +1,11 @@
import useMemberStore from "../../stores/useMemberStore";
-import { LandingMemberDTO } from "../../types/DTO/landingDTO";
+import { SettingMemberDTO } from "../../types/DTO/settingDTO";
-interface MemberBlockProps extends LandingMemberDTO {}
+interface MemberBlockProps extends SettingMemberDTO {}
const MemberBlock = ({ username, imageUrl, role }: MemberBlockProps) => {
const myRole = useMemberStore((state) => state.myInfo.role);
+ const myUserName = useMemberStore((state) => state.myInfo.username);
return (
@@ -16,7 +17,7 @@ const MemberBlock = ({ username, imageUrl, role }: MemberBlockProps) => {
{role}
- {myRole === "LEADER" && (
+ {myRole === "LEADER" && myUserName !== username && (
(
+const MemberSettingSection = ({
+ memberList,
+ joinRequestList,
+}: MemberSettingSectionProps) => (
멤버 관리
@@ -18,6 +26,7 @@ const MemberSettingSection = ({ memberList }: MemberSettingSectionProps) => (
{...memberList.map((member) => )}
+ {...joinRequestList.map((request) => )}
diff --git a/frontend/src/constants/path.ts b/frontend/src/constants/path.ts
index 5f54ea7..3f10454 100644
--- a/frontend/src/constants/path.ts
+++ b/frontend/src/constants/path.ts
@@ -9,7 +9,8 @@ export const API_URL = {
NICKNAME_AVAILABLILITY: "/member/availability",
GITHUB_USERNAME: "/auth/github/username",
PROJECT: "/project",
- PROJECT_JOIN: "/project/join",
+ PROJECT_JOIN: "/project/join-request",
+ INVITE_PREVIEW: "/project/invite-preview",
};
export const ROUTER_URL = {
diff --git a/frontend/src/hooks/common/member/useUpdateUserStatus.ts b/frontend/src/hooks/common/member/useUpdateUserStatus.ts
index 943f623..13db870 100644
--- a/frontend/src/hooks/common/member/useUpdateUserStatus.ts
+++ b/frontend/src/hooks/common/member/useUpdateUserStatus.ts
@@ -3,6 +3,7 @@ import { Socket } from "socket.io-client";
import {
LandingSocketData,
LandingSocketDomain,
+ LandingSocketInviteLinkAction,
LandingSocketMemberAction,
} from "../../../types/common/landing";
import { LandingDTO, LandingMemberDTO } from "../../../types/DTO/landingDTO";
@@ -76,6 +77,16 @@ const useUpdateUserStatus = (
}
};
+ const handleInviteLinkEvent = (
+ action: LandingSocketInviteLinkAction,
+ content: { inviteLinkId: string }
+ ) => {
+ if (action === "update") {
+ alert("초대링크가 변경되었습니다.");
+ inviteLinkIdRef.current = content.inviteLinkId;
+ }
+ };
+
const handleOnLanding = ({ domain, action, content }: LandingSocketData) => {
switch (domain) {
case LandingSocketDomain.INIT:
@@ -84,6 +95,9 @@ const useUpdateUserStatus = (
case LandingSocketDomain.MEMBER:
handleMemberEvent(action, content);
break;
+ case LandingSocketDomain.INVITE_LINK:
+ handleInviteLinkEvent(action, content);
+ break;
}
};
diff --git a/frontend/src/hooks/common/socket/useSocket.ts b/frontend/src/hooks/common/socket/useSocket.ts
index f575984..d0395a2 100644
--- a/frontend/src/hooks/common/socket/useSocket.ts
+++ b/frontend/src/hooks/common/socket/useSocket.ts
@@ -24,7 +24,7 @@ const useSocket = (projectId: string) => {
alert("프로젝트가 삭제되었습니다.");
setTimeout(() => {
navigate(ROUTER_URL.PROJECTS);
- }, 1000);
+ }, 500);
}
};
diff --git a/frontend/src/hooks/pages/setting/useSettingSocket.ts b/frontend/src/hooks/pages/setting/useSettingSocket.ts
index 7f1f549..522de06 100644
--- a/frontend/src/hooks/pages/setting/useSettingSocket.ts
+++ b/frontend/src/hooks/pages/setting/useSettingSocket.ts
@@ -5,17 +5,28 @@ import {
SettingSocketDomain,
SettingSocketProjectInfoAction,
} from "../../../types/common/setting";
-import { SettingDTO, SettingProjectDTO } from "../../../types/DTO/settingDTO";
+import {
+ SettingDTO,
+ SettingJoinRequestDTO,
+ SettingMemberDTO,
+ SettingProjectDTO,
+} from "../../../types/DTO/settingDTO";
const useSettingSocket = (socket: Socket) => {
const [projectInfo, setProjectInfo] = useState
({
title: "",
subject: "",
});
+ const [memberList, setMemberList] = useState([]);
+ const [joinRequestList, setJoinRequestList] = useState<
+ SettingJoinRequestDTO[]
+ >([]);
const handleInitEvent = (content: SettingDTO) => {
- const { project } = content;
+ const { project, member, joinRequestList } = content;
setProjectInfo(project);
+ setMemberList(member);
+ setJoinRequestList(joinRequestList ? joinRequestList : []);
};
const handleProjectInfoEvent = (
@@ -53,7 +64,7 @@ const useSettingSocket = (socket: Socket) => {
};
}, []);
- return { projectInfo };
+ return { projectInfo, memberList, joinRequestList };
};
export default useSettingSocket;
diff --git a/frontend/src/pages/invite/InvitePage.tsx b/frontend/src/pages/invite/InvitePage.tsx
index d2771e2..1d56131 100644
--- a/frontend/src/pages/invite/InvitePage.tsx
+++ b/frontend/src/pages/invite/InvitePage.tsx
@@ -1,13 +1,21 @@
-import { useLocation, useNavigate, useParams } from "react-router-dom";
+import { Link, useLocation, useNavigate, useParams } from "react-router-dom";
import { checkAccessToken } from "../../apis/utils/authAPI";
import { ROUTER_URL } from "../../constants/path";
-import { useEffect } from "react";
+import { useEffect, useState } from "react";
import { STORAGE_KEY } from "../../constants/storageKey";
import { postJoinProject } from "../../apis/api/projectAPI";
+import { InvitePreview } from "../../types/DTO/inviteDTO";
+import { getInvitePreview } from "../../apis/api/inviteAPI";
const InvitePage = () => {
const { projectTitle, projectId: projectUUID } = useParams();
const { pathname } = useLocation();
+ const [projectInfo, setProjectInfo] = useState({
+ id: -1,
+ title: "",
+ subject: "",
+ leaderUsername: "",
+ });
const navigate = useNavigate();
const handleJoinButtonClick = async () => {
@@ -15,7 +23,7 @@ const InvitePage = () => {
switch (response.status) {
case 201:
- alert("프로젝트에 참여되었습니다.");
+ alert("참여 요청을 보냈습니다.");
navigate("/projects");
break;
case 200:
@@ -35,6 +43,17 @@ const InvitePage = () => {
navigate(ROUTER_URL.LOGIN, { replace: true });
}
+ getInvitePreview(projectUUID!)
+ .then((response) => {
+ setProjectInfo(response.data);
+ })
+ .catch((error) => {
+ if (error.response.status === 404) {
+ alert("유효하지 않은 요청 링크입니다.");
+ navigate("/projects");
+ }
+ });
+
return () => {
if (checkAccessToken()) {
sessionStorage.removeItem(STORAGE_KEY.REDIRECT);
@@ -43,18 +62,44 @@ const InvitePage = () => {
}, []);
return (
-
-
- 프로젝트{projectTitle}에 초대되었습니다.
- {projectUUID}
-
- 참여하기
-
-
+
+
+
+
+
+ {projectInfo.leaderUsername}
+ 님의
+
+
+ {projectTitle}
+
+
{projectInfo.subject}
+
+ {projectInfo.leaderUsername}님의 {projectInfo.title}
+
+
+ 프로젝트에 참여하고 싶다면 요청을 보내세요.
+
+
+
+ 프로젝트 참여 요청
+
+
+
+ 내 프로젝트
+
+
+
+
+
+
);
};
diff --git a/frontend/src/pages/setting/SettingPage.tsx b/frontend/src/pages/setting/SettingPage.tsx
index 990bce2..b9de6b1 100644
--- a/frontend/src/pages/setting/SettingPage.tsx
+++ b/frontend/src/pages/setting/SettingPage.tsx
@@ -2,38 +2,21 @@ 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" },
- {
- id: 2,
- username: "lesserTest2",
- role: "MEMBER",
- imageUrl: "",
- status: "on",
- },
- {
- id: 3,
- username: "lesserTest3",
- role: "MEMBER",
- imageUrl: "",
- status: "on",
- },
-];
-
const SettingPage = () => {
const { socket }: { socket: Socket } = useOutletContext();
const {
projectInfo: { title, subject },
+ memberList,
+ joinRequestList,
} = useSettingSocket(socket);
return (
);
diff --git a/frontend/src/types/DTO/inviteDTO.ts b/frontend/src/types/DTO/inviteDTO.ts
new file mode 100644
index 0000000..7a971cc
--- /dev/null
+++ b/frontend/src/types/DTO/inviteDTO.ts
@@ -0,0 +1,6 @@
+export interface InvitePreview {
+ id: number;
+ title: string;
+ subject: string;
+ leaderUsername: string;
+}
diff --git a/frontend/src/types/DTO/settingDTO.ts b/frontend/src/types/DTO/settingDTO.ts
index 2ffe4bc..98f59c0 100644
--- a/frontend/src/types/DTO/settingDTO.ts
+++ b/frontend/src/types/DTO/settingDTO.ts
@@ -12,7 +12,15 @@ export interface SettingMemberDTO {
role: MemberRole;
}
+export interface SettingJoinRequestDTO {
+ id: number;
+ memberId: number;
+ username: string;
+ imageUrl: string;
+}
+
export interface SettingDTO {
project: SettingProjectDTO;
member: SettingMemberDTO[];
+ joinRequestList: SettingJoinRequestDTO[];
}
diff --git a/frontend/src/types/common/landing.ts b/frontend/src/types/common/landing.ts
index 18f2322..fcad701 100644
--- a/frontend/src/types/common/landing.ts
+++ b/frontend/src/types/common/landing.ts
@@ -10,6 +10,7 @@ export enum LandingSocketDomain {
MEMO = "memo",
MEMBER = "member",
LINK = "link",
+ INVITE_LINK = "inviteLink",
}
export enum LandingSocketMemoAction {
@@ -30,6 +31,10 @@ export enum LandingSocketLinkAction {
DELETE = "delete",
}
+export enum LandingSocketInviteLinkAction {
+ UPDATE = "update",
+}
+
interface LandingSocketInitData {
domain: LandingSocketDomain.INIT;
action: "init";
@@ -54,11 +59,18 @@ interface LandingSocketLinkData {
content: LandingLinkDTO;
}
+interface LandingSocketInviteLinkData {
+ domain: LandingSocketDomain.INVITE_LINK;
+ action: LandingSocketInviteLinkAction;
+ content: { inviteLinkId: string };
+}
+
export type LandingSocketData =
| LandingSocketInitData
| LandingSocketMemoData
| LandingSocketMemberData
- | LandingSocketLinkData;
+ | LandingSocketLinkData
+ | LandingSocketInviteLinkData;
export enum MemoColorStyle {
yellow = "bg-[#FFD966]",