Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: 참여 요청 기능 구현, 초대 링크 변경 기능 구현 #340

Merged
merged 11 commits into from
Oct 5, 2024
Merged
10 changes: 10 additions & 0 deletions frontend/src/apis/api/inviteAPI.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { API_URL } from "../../constants/path";
import { authAPI } from "../utils/authAPI";

export const getInvitePreview = async (inviteLinkId: string) => {
const response = await authAPI.get(
`${API_URL.INVITE_PREVIEW}/${inviteLinkId}`
);

return response;
};
11 changes: 10 additions & 1 deletion frontend/src/components/landing/member/LandingMember.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,12 @@ const LandingMember = ({ projectTitle }: LandingMemberProps) => {
});
}

const handleChangeInviteLinkClick = () => {
console.log("Asdfsdf");

socket.emit("inviteLink", { action: "update", content: {} });
};

return (
<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">
<div className="flex flex-col gap-3">
Expand Down Expand Up @@ -103,7 +109,10 @@ const LandingMember = ({ projectTitle }: LandingMemberProps) => {
초대링크 복사
</button>
<span>|</span>
<button className="text-xxs hover:underline" onClick={() => {}}>
<button
className="text-xxs hover:underline"
onClick={handleChangeInviteLinkClick}
>
링크 변경
</button>
</div>
Expand Down
8 changes: 6 additions & 2 deletions frontend/src/components/landing/project/LandingProject.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 (
<div className="flex flex-col justify-between w-full p-6 rounded-lg shadow-box">
Expand All @@ -22,10 +24,12 @@ const LandingProject = ({ projectId }: LandingProjectProps) => {
</p>
</div>
<div className="text-xs">{project.subject}</div>
<div className="flex justify-between">
<div className="flex justify-between gap-4">
<LandingProjectLink projectId={projectId} type="BACKLOG" />
<LandingProjectLink projectId={projectId} type="SPRINT" />
<LandingProjectLink projectId={projectId} type="SETTINGS" />
{role === "LEADER" && (
<LandingProjectLink projectId={projectId} type="SETTINGS" />
)}
</div>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const LandingProjectLink = ({ projectId, type }: LandingProjectLinkProps) => {
return (
<Link
to={LINK_URL.BACKLOG(projectId)}
className={`w-[8.75rem] h-[5rem] rounded-lg flex justify-center gap-2 items-center ${color} hover:shadow-button`}
className={`w-full h-[5rem] rounded-lg flex justify-center gap-2 items-center ${color} hover:shadow-button`}
>
<Icon height={36} width={36} fill="#FFFFFF" />
<div className="flex flex-col items-center gap-0 text-white text-[1rem] font-semibold">
Expand Down
63 changes: 35 additions & 28 deletions frontend/src/components/main/PageLinkIcons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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) => (
<div className="flex flex-col pl-[0.9375rem] pt-[1.5625rem] w-[5.3125rem] gap-5">
<PageIcon
Icon={LandingIcon}
activated={pathname === LINK_URL.MAIN(projectId)}
to={LINK_URL.MAIN(projectId)}
pageName="메인페이지"
/>
<PageIcon
Icon={BacklogIcon}
activated={pathname.split("/").includes("backlog")}
to={LINK_URL.BACKLOG(projectId)}
pageName="백로그"
/>
<PageIcon
Icon={SprintIcon}
activated={pathname === LINK_URL.SPRINT(projectId)}
to={LINK_URL.SPRINT(projectId)}
pageName="스프린트"
/>
<PageIcon
Icon={SettingIcon}
activated={pathname === LINK_URL.SETTINGS(projectId)}
to={LINK_URL.SETTINGS(projectId)}
pageName="프로젝트 설정"
/>
</div>
);
const PageLinkIcons = ({ pathname, projectId }: ProjectSidebarProps) => {
const { role } = useMemberStore((state) => state.myInfo);

return (
<div className="flex flex-col pl-[0.9375rem] pt-[1.5625rem] w-[5.3125rem] gap-5">
<PageIcon
Icon={LandingIcon}
activated={pathname === LINK_URL.MAIN(projectId)}
to={LINK_URL.MAIN(projectId)}
pageName="메인페이지"
/>
<PageIcon
Icon={BacklogIcon}
activated={pathname.split("/").includes("backlog")}
to={LINK_URL.BACKLOG(projectId)}
pageName="백로그"
/>
<PageIcon
Icon={SprintIcon}
activated={pathname === LINK_URL.SPRINT(projectId)}
to={LINK_URL.SPRINT(projectId)}
pageName="스프린트"
/>
{role === "LEADER" && (
<PageIcon
Icon={SettingIcon}
activated={pathname === LINK_URL.SETTINGS(projectId)}
to={LINK_URL.SETTINGS(projectId)}
pageName="프로젝트 설정"
/>
)}
</div>
);
};

export default PageLinkIcons;
38 changes: 38 additions & 0 deletions frontend/src/components/setting/JoinRequestBlock.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className="flex w-full gap-3">
<div className="w-[16.25rem] flex gap-3 items-center">
<img className="w-8 h-8 rounded-full" src={imageUrl} alt={username} />
<p className="">{username}</p>
</div>
<div className="w-[18.75rem]"></div>
<div className="w-[30rem]">
{myRole === "LEADER" && (
<>
<button
className="px-2 py-1 mr-3 text-white rounded w-fit text-xxs bg-middle-green"
type="button"
>
참여 수락
</button>
<button
className="px-2 py-1 text-white rounded w-fit bg-error-red text-xxs"
type="button"
>
참여 거절
</button>
</>
)}
</div>
</div>
);
};

export default JoinRequestBlock;
7 changes: 4 additions & 3 deletions frontend/src/components/setting/MemberBlock.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className="flex w-full gap-3">
Expand All @@ -16,7 +17,7 @@ const MemberBlock = ({ username, imageUrl, role }: MemberBlockProps) => {
<p className="">{role}</p>
</div>
<div className="w-[30rem]">
{myRole === "LEADER" && (
{myRole === "LEADER" && myUserName !== username && (
<button
className="px-2 py-1 text-white rounded w-fit bg-error-red text-xxs"
type="button"
Expand Down
15 changes: 12 additions & 3 deletions frontend/src/components/setting/MemberSettingSection.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
import { LandingMemberDTO } from "../../types/DTO/landingDTO";
import JoinRequestBlock from "./JoinRequestBlock";
import MemberBlock from "./MemberBlock";
import {
SettingJoinRequestDTO,
SettingMemberDTO,
} from "../../types/DTO/settingDTO";

interface MemberSettingSectionProps {
memberList: LandingMemberDTO[];
memberList: SettingMemberDTO[];
joinRequestList: SettingJoinRequestDTO[];
}

const MemberSettingSection = ({ memberList }: MemberSettingSectionProps) => (
const MemberSettingSection = ({
memberList,
joinRequestList,
}: MemberSettingSectionProps) => (
<div className="mb-5">
<div className="mb-2">
<p className="font-bold text-m text-middle-green">멤버 관리</p>
Expand All @@ -18,6 +26,7 @@ const MemberSettingSection = ({ memberList }: MemberSettingSectionProps) => (
</div>
<div className="flex flex-col gap-3 overflow-y-auto scrollbar-thin">
{...memberList.map((member) => <MemberBlock {...member} />)}
{...joinRequestList.map((request) => <JoinRequestBlock {...request} />)}
</div>
</div>
</div>
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/constants/path.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down
14 changes: 14 additions & 0 deletions frontend/src/hooks/common/member/useUpdateUserStatus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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:
Expand All @@ -84,6 +95,9 @@ const useUpdateUserStatus = (
case LandingSocketDomain.MEMBER:
handleMemberEvent(action, content);
break;
case LandingSocketDomain.INVITE_LINK:
handleInviteLinkEvent(action, content);
break;
}
};

Expand Down
2 changes: 1 addition & 1 deletion frontend/src/hooks/common/socket/useSocket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const useSocket = (projectId: string) => {
alert("프로젝트가 삭제되었습니다.");
setTimeout(() => {
navigate(ROUTER_URL.PROJECTS);
}, 1000);
}, 500);
}
};

Expand Down
17 changes: 14 additions & 3 deletions frontend/src/hooks/pages/setting/useSettingSocket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<SettingProjectDTO>({
title: "",
subject: "",
});
const [memberList, setMemberList] = useState<SettingMemberDTO[]>([]);
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 = (
Expand Down Expand Up @@ -53,7 +64,7 @@ const useSettingSocket = (socket: Socket) => {
};
}, []);

return { projectInfo };
return { projectInfo, memberList, joinRequestList };
};

export default useSettingSocket;
Loading
Loading