Skip to content

Commit f0f986c

Browse files
authored
Merge pull request #340 from boostcampwm2023/feature/invite-page
feat: 참여 요청 기능 구현, 초대 링크 변경 기능 구현
2 parents d0706f2 + 2e79fa2 commit f0f986c

File tree

17 files changed

+237
-79
lines changed

17 files changed

+237
-79
lines changed

frontend/src/apis/api/inviteAPI.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { API_URL } from "../../constants/path";
2+
import { authAPI } from "../utils/authAPI";
3+
4+
export const getInvitePreview = async (inviteLinkId: string) => {
5+
const response = await authAPI.get(
6+
`${API_URL.INVITE_PREVIEW}/${inviteLinkId}`
7+
);
8+
9+
return response;
10+
};

frontend/src/components/landing/member/LandingMember.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,12 @@ const LandingMember = ({ projectTitle }: LandingMemberProps) => {
7373
});
7474
}
7575

76+
const handleChangeInviteLinkClick = () => {
77+
console.log("Asdfsdf");
78+
79+
socket.emit("inviteLink", { action: "update", content: {} });
80+
};
81+
7682
return (
7783
<div className="w-full px-6 py-6 overflow-y-scroll rounded-lg shadow-box bg-gradient-to-tr to-light-green-linear-from from-light-green scrollbar-thin scrollbar-thumb-light-green scrollbar-track-transparent scrollbar-thumb-rounded-full">
7884
<div className="flex flex-col gap-3">
@@ -103,7 +109,10 @@ const LandingMember = ({ projectTitle }: LandingMemberProps) => {
103109
초대링크 복사
104110
</button>
105111
<span>|</span>
106-
<button className="text-xxs hover:underline" onClick={() => {}}>
112+
<button
113+
className="text-xxs hover:underline"
114+
onClick={handleChangeInviteLinkClick}
115+
>
107116
링크 변경
108117
</button>
109118
</div>

frontend/src/components/landing/project/LandingProject.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import LandingProjectLink from "./LandingProjectLink";
44
import { useOutletContext } from "react-router-dom";
55

66
import useLandingProjectSocket from "../../../hooks/common/landing/useLandingProjectSocket";
7+
import useMemberStore from "../../../stores/useMemberStore";
78

89
interface LandingProjectProps {
910
projectId: string;
@@ -12,6 +13,7 @@ interface LandingProjectProps {
1213
const LandingProject = ({ projectId }: LandingProjectProps) => {
1314
const { socket }: { socket: Socket } = useOutletContext();
1415
const { project } = useLandingProjectSocket(socket);
16+
const { role } = useMemberStore((state) => state.myInfo);
1517

1618
return (
1719
<div className="flex flex-col justify-between w-full p-6 rounded-lg shadow-box">
@@ -22,10 +24,12 @@ const LandingProject = ({ projectId }: LandingProjectProps) => {
2224
</p>
2325
</div>
2426
<div className="text-xs">{project.subject}</div>
25-
<div className="flex justify-between">
27+
<div className="flex justify-between gap-4">
2628
<LandingProjectLink projectId={projectId} type="BACKLOG" />
2729
<LandingProjectLink projectId={projectId} type="SPRINT" />
28-
<LandingProjectLink projectId={projectId} type="SETTINGS" />
30+
{role === "LEADER" && (
31+
<LandingProjectLink projectId={projectId} type="SETTINGS" />
32+
)}
2933
</div>
3034
</div>
3135
);

frontend/src/components/landing/project/LandingProjectLink.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ const LandingProjectLink = ({ projectId, type }: LandingProjectLinkProps) => {
1212
return (
1313
<Link
1414
to={LINK_URL.BACKLOG(projectId)}
15-
className={`w-[8.75rem] h-[5rem] rounded-lg flex justify-center gap-2 items-center ${color} hover:shadow-button`}
15+
className={`w-full h-[5rem] rounded-lg flex justify-center gap-2 items-center ${color} hover:shadow-button`}
1616
>
1717
<Icon height={36} width={36} fill="#FFFFFF" />
1818
<div className="flex flex-col items-center gap-0 text-white text-[1rem] font-semibold">

frontend/src/components/main/PageLinkIcons.tsx

Lines changed: 35 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -5,34 +5,41 @@ import SprintIcon from "../../assets/icons/sprint.svg?react";
55
import SettingIcon from "../../assets/icons/settings.svg?react";
66
import { LINK_URL } from "../../constants/path";
77
import { ProjectSidebarProps } from "../../types/common/main";
8+
import useMemberStore from "../../stores/useMemberStore";
89

9-
const PageLinkIcons = ({ pathname, projectId }: ProjectSidebarProps) => (
10-
<div className="flex flex-col pl-[0.9375rem] pt-[1.5625rem] w-[5.3125rem] gap-5">
11-
<PageIcon
12-
Icon={LandingIcon}
13-
activated={pathname === LINK_URL.MAIN(projectId)}
14-
to={LINK_URL.MAIN(projectId)}
15-
pageName="메인페이지"
16-
/>
17-
<PageIcon
18-
Icon={BacklogIcon}
19-
activated={pathname.split("/").includes("backlog")}
20-
to={LINK_URL.BACKLOG(projectId)}
21-
pageName="백로그"
22-
/>
23-
<PageIcon
24-
Icon={SprintIcon}
25-
activated={pathname === LINK_URL.SPRINT(projectId)}
26-
to={LINK_URL.SPRINT(projectId)}
27-
pageName="스프린트"
28-
/>
29-
<PageIcon
30-
Icon={SettingIcon}
31-
activated={pathname === LINK_URL.SETTINGS(projectId)}
32-
to={LINK_URL.SETTINGS(projectId)}
33-
pageName="프로젝트 설정"
34-
/>
35-
</div>
36-
);
10+
const PageLinkIcons = ({ pathname, projectId }: ProjectSidebarProps) => {
11+
const { role } = useMemberStore((state) => state.myInfo);
12+
13+
return (
14+
<div className="flex flex-col pl-[0.9375rem] pt-[1.5625rem] w-[5.3125rem] gap-5">
15+
<PageIcon
16+
Icon={LandingIcon}
17+
activated={pathname === LINK_URL.MAIN(projectId)}
18+
to={LINK_URL.MAIN(projectId)}
19+
pageName="메인페이지"
20+
/>
21+
<PageIcon
22+
Icon={BacklogIcon}
23+
activated={pathname.split("/").includes("backlog")}
24+
to={LINK_URL.BACKLOG(projectId)}
25+
pageName="백로그"
26+
/>
27+
<PageIcon
28+
Icon={SprintIcon}
29+
activated={pathname === LINK_URL.SPRINT(projectId)}
30+
to={LINK_URL.SPRINT(projectId)}
31+
pageName="스프린트"
32+
/>
33+
{role === "LEADER" && (
34+
<PageIcon
35+
Icon={SettingIcon}
36+
activated={pathname === LINK_URL.SETTINGS(projectId)}
37+
to={LINK_URL.SETTINGS(projectId)}
38+
pageName="프로젝트 설정"
39+
/>
40+
)}
41+
</div>
42+
);
43+
};
3744

3845
export default PageLinkIcons;
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import useMemberStore from "../../stores/useMemberStore";
2+
import { SettingJoinRequestDTO } from "../../types/DTO/settingDTO";
3+
4+
interface JoinRequestBlockProps extends SettingJoinRequestDTO {}
5+
6+
const JoinRequestBlock = ({ username, imageUrl }: JoinRequestBlockProps) => {
7+
const myRole = useMemberStore((state) => state.myInfo.role);
8+
9+
return (
10+
<div className="flex w-full gap-3">
11+
<div className="w-[16.25rem] flex gap-3 items-center">
12+
<img className="w-8 h-8 rounded-full" src={imageUrl} alt={username} />
13+
<p className="">{username}</p>
14+
</div>
15+
<div className="w-[18.75rem]"></div>
16+
<div className="w-[30rem]">
17+
{myRole === "LEADER" && (
18+
<>
19+
<button
20+
className="px-2 py-1 mr-3 text-white rounded w-fit text-xxs bg-middle-green"
21+
type="button"
22+
>
23+
참여 수락
24+
</button>
25+
<button
26+
className="px-2 py-1 text-white rounded w-fit bg-error-red text-xxs"
27+
type="button"
28+
>
29+
참여 거절
30+
</button>
31+
</>
32+
)}
33+
</div>
34+
</div>
35+
);
36+
};
37+
38+
export default JoinRequestBlock;

frontend/src/components/setting/MemberBlock.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import useMemberStore from "../../stores/useMemberStore";
2-
import { LandingMemberDTO } from "../../types/DTO/landingDTO";
2+
import { SettingMemberDTO } from "../../types/DTO/settingDTO";
33

4-
interface MemberBlockProps extends LandingMemberDTO {}
4+
interface MemberBlockProps extends SettingMemberDTO {}
55

66
const MemberBlock = ({ username, imageUrl, role }: MemberBlockProps) => {
77
const myRole = useMemberStore((state) => state.myInfo.role);
8+
const myUserName = useMemberStore((state) => state.myInfo.username);
89

910
return (
1011
<div className="flex w-full gap-3">
@@ -16,7 +17,7 @@ const MemberBlock = ({ username, imageUrl, role }: MemberBlockProps) => {
1617
<p className="">{role}</p>
1718
</div>
1819
<div className="w-[30rem]">
19-
{myRole === "LEADER" && (
20+
{myRole === "LEADER" && myUserName !== username && (
2021
<button
2122
className="px-2 py-1 text-white rounded w-fit bg-error-red text-xxs"
2223
type="button"

frontend/src/components/setting/MemberSettingSection.tsx

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,19 @@
1-
import { LandingMemberDTO } from "../../types/DTO/landingDTO";
1+
import JoinRequestBlock from "./JoinRequestBlock";
22
import MemberBlock from "./MemberBlock";
3+
import {
4+
SettingJoinRequestDTO,
5+
SettingMemberDTO,
6+
} from "../../types/DTO/settingDTO";
37

48
interface MemberSettingSectionProps {
5-
memberList: LandingMemberDTO[];
9+
memberList: SettingMemberDTO[];
10+
joinRequestList: SettingJoinRequestDTO[];
611
}
712

8-
const MemberSettingSection = ({ memberList }: MemberSettingSectionProps) => (
13+
const MemberSettingSection = ({
14+
memberList,
15+
joinRequestList,
16+
}: MemberSettingSectionProps) => (
917
<div className="mb-5">
1018
<div className="mb-2">
1119
<p className="font-bold text-m text-middle-green">멤버 관리</p>
@@ -18,6 +26,7 @@ const MemberSettingSection = ({ memberList }: MemberSettingSectionProps) => (
1826
</div>
1927
<div className="flex flex-col gap-3 overflow-y-auto scrollbar-thin">
2028
{...memberList.map((member) => <MemberBlock {...member} />)}
29+
{...joinRequestList.map((request) => <JoinRequestBlock {...request} />)}
2130
</div>
2231
</div>
2332
</div>

frontend/src/constants/path.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ export const API_URL = {
99
NICKNAME_AVAILABLILITY: "/member/availability",
1010
GITHUB_USERNAME: "/auth/github/username",
1111
PROJECT: "/project",
12-
PROJECT_JOIN: "/project/join",
12+
PROJECT_JOIN: "/project/join-request",
13+
INVITE_PREVIEW: "/project/invite-preview",
1314
};
1415

1516
export const ROUTER_URL = {

frontend/src/hooks/common/member/useUpdateUserStatus.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { Socket } from "socket.io-client";
33
import {
44
LandingSocketData,
55
LandingSocketDomain,
6+
LandingSocketInviteLinkAction,
67
LandingSocketMemberAction,
78
} from "../../../types/common/landing";
89
import { LandingDTO, LandingMemberDTO } from "../../../types/DTO/landingDTO";
@@ -76,6 +77,16 @@ const useUpdateUserStatus = (
7677
}
7778
};
7879

80+
const handleInviteLinkEvent = (
81+
action: LandingSocketInviteLinkAction,
82+
content: { inviteLinkId: string }
83+
) => {
84+
if (action === "update") {
85+
alert("초대링크가 변경되었습니다.");
86+
inviteLinkIdRef.current = content.inviteLinkId;
87+
}
88+
};
89+
7990
const handleOnLanding = ({ domain, action, content }: LandingSocketData) => {
8091
switch (domain) {
8192
case LandingSocketDomain.INIT:
@@ -84,6 +95,9 @@ const useUpdateUserStatus = (
8495
case LandingSocketDomain.MEMBER:
8596
handleMemberEvent(action, content);
8697
break;
98+
case LandingSocketDomain.INVITE_LINK:
99+
handleInviteLinkEvent(action, content);
100+
break;
87101
}
88102
};
89103

0 commit comments

Comments
 (0)