Skip to content

Commit daeae72

Browse files
authored
Merge pull request depromeet#735 from depromeet/feat/invite-team-modal
feat: 팀원추가 모달 작업 (카카오톡전달, 초대링크 복사 기능)
2 parents f701960 + 2da1c9a commit daeae72

2 files changed

Lines changed: 215 additions & 1 deletion

File tree

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
import { css } from "@emotion/react";
2+
3+
import { IconButton } from "@/component/common/button";
4+
import { Icon } from "@/component/common/Icon";
5+
import { Portal } from "@/component/common/Portal";
6+
import { Typography } from "@/component/common/typography";
7+
import { DESIGN_TOKEN_COLOR } from "@/style/designTokens";
8+
import { ANIMATION } from "@/style/common/animation";
9+
import { useToast } from "@/hooks/useToast";
10+
import { useBridge } from "@/lib/provider/bridge-provider";
11+
import { useApiGetUser } from "@/hooks/api/auth/useApiGetUser";
12+
import { useApiGetSpace } from "@/hooks/api/space/useApiGetSpace";
13+
import { shareKakaoWeb } from "@/utils/kakao/sharedKakaoLink";
14+
import { encryptId } from "@/utils/space/cryptoKey";
15+
16+
type InviteMemberModalProps = {
17+
isOpen: boolean;
18+
onClose: () => void;
19+
spaceId: string;
20+
};
21+
22+
export function InviteMemberModal({ isOpen, onClose, spaceId }: InviteMemberModalProps) {
23+
const { toast } = useToast();
24+
const { bridge } = useBridge();
25+
const { data: userData } = useApiGetUser();
26+
const { data: spaceInfo } = useApiGetSpace(spaceId);
27+
const encryptedId = encryptId(spaceId);
28+
const inviteLink = `${window.location.protocol}//${window.location.host}/space/join/${encryptedId}`;
29+
30+
const handleBackgroundClick = (e: React.MouseEvent<HTMLDivElement>) => {
31+
if (e.target === e.currentTarget) {
32+
onClose();
33+
}
34+
};
35+
36+
const handleShareKakao = async () => {
37+
if (bridge.isWebViewBridgeAvailable) {
38+
await bridge.sendShareToKakao({
39+
content: {
40+
title: `${userData?.name}님의 회고 초대장`,
41+
description: `함께 회고해요! ${userData?.name}님이 팀 레이어 스페이스에 초대했어요`,
42+
imageUrl: "https://kr.object.ncloudstorage.com/layer-bucket/small_banner.png",
43+
link: {
44+
mobileWebUrl: inviteLink,
45+
webUrl: inviteLink,
46+
},
47+
},
48+
buttons: [
49+
{
50+
title: "초대 받기",
51+
link: {
52+
mobileWebUrl: inviteLink,
53+
webUrl: inviteLink,
54+
},
55+
},
56+
],
57+
});
58+
} else {
59+
shareKakaoWeb(
60+
inviteLink,
61+
`${userData?.name}님의 회고 초대장.`,
62+
`함께 회고해요! ${userData?.name}님이 ${spaceInfo?.name} 스페이스에 초대했어요`,
63+
);
64+
}
65+
onClose();
66+
};
67+
68+
const handleCopyClipBoard = async () => {
69+
try {
70+
await navigator.clipboard.writeText(inviteLink);
71+
toast.success("링크 복사가 완료되었어요!");
72+
onClose();
73+
} catch (e) {
74+
toast.error("링크 복사에 실패했어요!");
75+
}
76+
};
77+
78+
if (!isOpen) return null;
79+
80+
return (
81+
<Portal id="modal-root">
82+
<div
83+
css={css`
84+
position: fixed;
85+
top: 0;
86+
left: 0;
87+
right: 0;
88+
bottom: 0;
89+
width: 100%;
90+
height: 100%;
91+
background-color: rgba(6, 8, 12, 0.32);
92+
display: flex;
93+
justify-content: center;
94+
align-items: center;
95+
z-index: 99999;
96+
`}
97+
onClick={handleBackgroundClick}
98+
>
99+
<div
100+
css={css`
101+
width: 100%;
102+
max-width: 31.5rem;
103+
background-color: ${DESIGN_TOKEN_COLOR.white};
104+
border-radius: 1.2rem;
105+
box-shadow: 0 0.4rem 2.4rem rgba(0, 0, 0, 0.2);
106+
display: flex;
107+
flex-direction: column;
108+
margin: 2rem;
109+
animation: ${ANIMATION.FADE_IN} 0.3s ease-out;
110+
`}
111+
>
112+
{/* 헤더 */}
113+
<header
114+
css={css`
115+
display: flex;
116+
align-items: center;
117+
justify-content: end;
118+
padding: 1rem;
119+
`}
120+
>
121+
<button
122+
css={css`
123+
display: flex;
124+
align-items: center;
125+
justify-content: center;
126+
width: 3.2rem;
127+
height: 3.2rem;
128+
border: none;
129+
background: transparent;
130+
border-radius: 0.4rem;
131+
cursor: pointer;
132+
transition: background-color 0.2s ease;
133+
134+
&:hover {
135+
background-color: ${DESIGN_TOKEN_COLOR.gray100};
136+
}
137+
`}
138+
onClick={onClose}
139+
>
140+
<Icon icon="ic_close" size={2.4} color={DESIGN_TOKEN_COLOR.gray900} />
141+
</button>
142+
</header>
143+
144+
{/* 콘텐츠 */}
145+
<div
146+
css={css`
147+
flex: 1;
148+
padding: 0 2.4rem 1.2rem 2.4rem;
149+
overflow-y: auto;
150+
display: flex;
151+
flex-direction: column;
152+
gap: 2rem;
153+
align-items: center;
154+
margin-bottom: 1.8rem;
155+
`}
156+
>
157+
<div
158+
css={css`
159+
display: flex;
160+
flex-direction: column;
161+
gap: 1.2rem;
162+
align-items: center;
163+
`}
164+
>
165+
<Typography variant="title18Bold" color="gray900">
166+
팀원을 추가 하시겠어요?
167+
</Typography>
168+
<Typography variant="subtitle16SemiBold" color="gray600">
169+
링크를 복사하여 팀원을 초대할 수 있어요
170+
</Typography>
171+
</div>
172+
<div
173+
css={css`
174+
display: flex;
175+
flex-direction: column;
176+
gap: 0.8rem;
177+
width: 100%;
178+
`}
179+
>
180+
<IconButton
181+
onClick={handleShareKakao}
182+
icon="ic_kakao"
183+
css={css`
184+
background-color: #ffe400;
185+
color: #000000;
186+
transition: all 0.5s ease;
187+
transition-delay: 0.5s;
188+
`}
189+
>
190+
카카오톡 전달
191+
</IconButton>
192+
<IconButton
193+
onClick={handleCopyClipBoard}
194+
icon="ic_copy"
195+
color="#000000"
196+
css={css`
197+
background-color: #f1f5f3;
198+
color: #000000;
199+
transition: all 0.5s ease;
200+
transition-delay: 0.5s;
201+
`}
202+
>
203+
초대링크 복사
204+
</IconButton>
205+
</div>
206+
</div>
207+
</div>
208+
</div>
209+
</Portal>
210+
);
211+
}

apps/web/src/component/retrospect/space/members/MemberManagement.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { useApiKickMember } from "@/hooks/api/space/members/useApiKickMembers";
1414
import { useApiGetMemers } from "@/hooks/api/space/members/useApiGetMembers";
1515
import { useApiOptionsGetSpaceInfo } from "@/hooks/api/space/useApiOptionsGetSpaceInfo";
1616
import { useQueries } from "@tanstack/react-query";
17+
import { InviteMemberModal } from "@/component/common/Modal/Member/InviteMemberModal";
1718

1819
export default function MemberManagement({ spaceId }: { spaceId: string }) {
1920
const [{ data: spaceInfo }] = useQueries({
@@ -32,6 +33,7 @@ export default function MemberManagement({ spaceId }: { spaceId: string }) {
3233

3334
const [isOpen, setIsOpen] = useState(false); // 팀원 관리 드롭다운 열림 여부
3435
const [isEditOpen, setIsEditOpen] = useState(false); // 팀원 관리 드롭다운 내부 편집 버튼 열림 여부
36+
const [isModalOpen, setIsModalOpen] = useState(false); // 팀원 초대 모달 열림 여부
3537

3638
// 팀원 관리 드롭다운 내부 뷰 타입
3739
// main: 팀원 관리 뷰, leaderChange: 대표자 변경 뷰, memberDelete: 팀원 삭제 뷰
@@ -65,7 +67,7 @@ export default function MemberManagement({ spaceId }: { spaceId: string }) {
6567
};
6668

6769
const handleAddMember = () => {
68-
// TODO: 팀원 추가 로직 구현 -> 팀원추가 뷰가 따로 없는것 같습니다..?
70+
setIsModalOpen(true);
6971
};
7072

7173
const handleMemberClick = () => {
@@ -222,6 +224,7 @@ export default function MemberManagement({ spaceId }: { spaceId: string }) {
222224
{isDeleteConfirmModalOpen && (
223225
<MemberDeleteConfirmModal onConfirm={handleConfirmMemberDelete} onCancel={() => setIsDeleteConfirmModalOpen(false)} />
224226
)}
227+
<InviteMemberModal isOpen={isModalOpen} onClose={() => setIsModalOpen(false)} spaceId={spaceId} />
225228
</div>
226229
);
227230
}

0 commit comments

Comments
 (0)