diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
index 760d8b34..da6db36d 100644
--- a/.github/workflows/deploy.yml
+++ b/.github/workflows/deploy.yml
@@ -24,7 +24,9 @@ jobs:
run: |
IMAGE="${{ secrets.DOCKER_USERNAME }}/yamoyo-nginx:prod"
docker build \
- --build-arg VITE_BASE_URL=${{ secrets.VITE_BASE_URL }} \
+ --build-arg VITE_BASE_URL="${{ secrets.VITE_BASE_URL }}" \
+ --build-arg VITE_KAKAO_JAVASCRIPT_KEY="${{ secrets.VITE_KAKAO_JAVASCRIPT_KEY }}" \
+ --build-arg VITE_KAKAO_TEMPLATE_ID="${{ secrets.VITE_KAKAO_TEMPLATE_ID }}" \
-t "$IMAGE" .
docker push "$IMAGE"
diff --git a/Dockerfile b/Dockerfile
index c039676c..f229238f 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -11,8 +11,14 @@ WORKDIR /app
# 빌드 인자 선언
ARG VITE_BASE_URL
+ARG VITE_KAKAO_JAVASCRIPT_KEY
+ARG VITE_KAKAO_TEMPLATE_ID
RUN test -n "$VITE_BASE_URL" || (echo "Error: VITE_BASE_URL build-arg is required" >&2 && exit 1)
+RUN test -n "$VITE_KAKAO_JAVASCRIPT_KEY" || (echo "Error: VITE_KAKAO_JAVASCRIPT_KEY build-arg is required" >&2 && exit 1)
+RUN test -n "$VITE_KAKAO_TEMPLATE_ID" || (echo "Error: VITE_KAKAO_TEMPLATE_ID build-arg is required" >&2 && exit 1)
ENV VITE_BASE_URL=${VITE_BASE_URL}
+ENV VITE_KAKAO_JAVASCRIPT_KEY=${VITE_KAKAO_JAVASCRIPT_KEY}
+ENV VITE_KAKAO_TEMPLATE_ID=${VITE_KAKAO_TEMPLATE_ID}
# 의존성 설치
COPY package*.json ./
diff --git a/src/entities/teamroom/hooks/useTeamRoom.ts b/src/entities/teamroom/hooks/useTeamRoom.ts
index 75dfe3a6..ed32b6e8 100644
--- a/src/entities/teamroom/hooks/useTeamRoom.ts
+++ b/src/entities/teamroom/hooks/useTeamRoom.ts
@@ -16,7 +16,6 @@ import type {
CreateTeamRoomRequest,
TeamRoomLifecycle,
} from '@/entities/teamroom/api/teamroom-dto';
-import { copyText } from '@/shared/lib/copyText';
/** 팀룸 목록 조회 */
export function useTeamRoomList(lifecycle: TeamRoomLifecycle = 'ACTIVE') {
@@ -77,14 +76,9 @@ export function useDeleteTeamRoom(onSuccess?: () => void) {
/** 초대 링크 생성 */
export function useCreateInviteLink() {
return useMutation({
- mutationFn: (teamRoomId: number) => createInviteLink(teamRoomId),
- onSuccess: async (data) => {
- const inviteUrl = `${window.location.origin}/invite?token=${data.token}`;
- await copyText(inviteUrl);
- alert('초대 링크가 복사되었습니다.');
- },
- onError: () => {
- alert('초대 링크 생성에 실패했습니다.');
+ mutationFn: async (teamRoomId: number) => {
+ const data = await createInviteLink(teamRoomId);
+ return data.token;
},
});
}
diff --git a/src/shared/lib/copyText.ts b/src/shared/lib/copy/copyText.ts
similarity index 100%
rename from src/shared/lib/copyText.ts
rename to src/shared/lib/copy/copyText.ts
diff --git a/src/shared/lib/kakao/KakaoSdkLoader.tsx b/src/shared/lib/kakao/KakaoSdkLoader.tsx
new file mode 100644
index 00000000..8eb62fa4
--- /dev/null
+++ b/src/shared/lib/kakao/KakaoSdkLoader.tsx
@@ -0,0 +1,48 @@
+import { useEffect } from 'react';
+
+const KAKAO_JAVASCRIPT_KEY: string = import.meta.env?.VITE_KAKAO_JAVASCRIPT_KEY;
+
+export function KakaoSdkLoader() {
+ useEffect(() => {
+ if (typeof window === 'undefined') return;
+
+ const src = 'https://t1.kakaocdn.net/kakao_js_sdk/2.7.9/kakao.min.js';
+ const id = 'kakao-js-sdk';
+
+ const initKakao = () => {
+ if (!window.Kakao) return;
+
+ // 중복 init 방지
+ if (!window.Kakao.isInitialized()) {
+ window.Kakao.init(KAKAO_JAVASCRIPT_KEY);
+ }
+ };
+
+ // 이미 script가 있으면 로드 완료 여부에 따라 init
+ const existing = document.getElementById(id) as HTMLScriptElement | null;
+ if (existing) {
+ // 이미 로드 완료된 경우 window.Kakao가 있을 수 있음
+ if (window.Kakao) initKakao();
+ else existing.addEventListener('load', initKakao);
+ return;
+ }
+
+ // script 주입
+ const script = document.createElement('script');
+ script.id = id;
+ script.src = src;
+ script.async = true;
+ script.crossOrigin = 'anonymous';
+ script.integrity =
+ 'sha384-JpLApTkB8lPskhVMhT+m5Ln8aHlnS0bsIexhaak0jOhAkMYedQoVghPfSpjNi9K1';
+
+ script.onload = initKakao;
+ script.onerror = () => {
+ console.error('[Kakao] SDK load failed');
+ };
+
+ document.head.appendChild(script);
+ }, []);
+
+ return null;
+}
diff --git a/src/shared/ui/modal/TeamRoomCreatedModal.tsx b/src/shared/ui/modal/TeamRoomCreatedModal.tsx
index bd78afd5..54402657 100644
--- a/src/shared/ui/modal/TeamRoomCreatedModal.tsx
+++ b/src/shared/ui/modal/TeamRoomCreatedModal.tsx
@@ -1,7 +1,7 @@
import { useNavigate } from 'react-router-dom';
import LinkCopyIcon from '@/shared/assets/icons/link-copy.svg?react';
-import { copyText } from '@/shared/lib/copyText';
+import { copyText } from '@/shared/lib/copy/copyText';
import ModalDim from '@/shared/ui/modal/ModalDim';
import { useModalStore } from './model/modal-store';
diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts
index b1f45c78..457d69de 100644
--- a/src/vite-env.d.ts
+++ b/src/vite-env.d.ts
@@ -1,2 +1,12 @@
///
///
+
+interface Window {
+ Kakao?: {
+ init: (key: string) => void;
+ isInitialized: () => boolean;
+ Share: {
+ sendScrap: (options: { requestUrl: string; templateId?: number }) => void;
+ };
+ };
+}
diff --git a/src/widgets/teamroom/main/ui/AddMemberBottomSheet.tsx b/src/widgets/teamroom/main/ui/AddMemberBottomSheet.tsx
index af77d61a..a50b31b0 100644
--- a/src/widgets/teamroom/main/ui/AddMemberBottomSheet.tsx
+++ b/src/widgets/teamroom/main/ui/AddMemberBottomSheet.tsx
@@ -1,8 +1,12 @@
import { useCreateInviteLink } from '@/entities/teamroom/hooks/useTeamRoom';
import LinkCopyIcon from '@/shared/assets/icons/link-copy.svg?react';
-// import KaKaoLinkIcon from '@/shared/assets/login/kakao.svg?react';
+import KaKaoLinkIcon from '@/shared/assets/login/kakao.svg?react';
+import { copyText } from '@/shared/lib/copy/copyText';
+import { KakaoSdkLoader } from '@/shared/lib/kakao/KakaoSdkLoader';
import BottomSheet from '@/shared/ui/BottomSheet';
+const KAKAO_TEMPLATE_ID: string = import.meta.env?.VITE_KAKAO_TEMPLATE_ID;
+
interface AddMemberBottomSheetProps {
isOpen: boolean;
onClose: () => void;
@@ -50,53 +54,81 @@ export default function AddMemberBottomSheet({
onClose,
teamRoomId,
}: AddMemberBottomSheetProps) {
- const createInviteLinkMutation = useCreateInviteLink();
+ const { mutateAsync } = useCreateInviteLink();
+
+ const handleCopyLink = async () => {
+ try {
+ const token = await mutateAsync(teamRoomId);
+ const inviteUrl = `${window.location.origin}/invite?token=${token}`;
+
+ await copyText(inviteUrl);
+ alert('초대 링크가 복사되었습니다.');
+ } catch {
+ alert('초대 링크 생성에 실패했습니다.');
+ }
+ };
- const handleCopyLink = () => {
- createInviteLinkMutation.mutate(teamRoomId);
+ const handleKakaoShare = async () => {
+ try {
+ const token = await mutateAsync(teamRoomId);
+ const inviteUrl = `${window.location.origin}/invite?token=${token}`;
+
+ window.Kakao?.Share.sendScrap({
+ requestUrl: inviteUrl,
+ templateId: Number(KAKAO_TEMPLATE_ID),
+ });
+ } catch (error) {
+ alert(
+ '카카오 공유하기 중 오류가 발생하였습니다. 잠시 후 다시 시도해 주세요.',
+ );
+ console.error(error);
+ }
};
return (
-
-
-
-
- 함께하고 있는 팀 멤버를 초대하세요
-
-
+ <>
+
+
+
+
+
+ 함께하고 있는 팀 멤버를 초대하세요
+
+
-
- }
- iconBgColor="bg-bg-bt-disabled"
- title="링크복사"
- description="링크로 팀원과 공유하세요."
- onClick={handleCopyLink}
- />
+
+ }
+ iconBgColor="bg-bg-bt-disabled"
+ title="링크복사"
+ description="링크로 팀원과 공유하세요."
+ onClick={handleCopyLink}
+ />
- {/* }
- iconBgColor="bg-[#F7E115]"
- title="카카오톡으로 보내기"
- onClick={() => {}}
- /> */}
-
+ }
+ iconBgColor="bg-[#F7E115]"
+ title="카카오톡으로 보내기"
+ onClick={handleKakaoShare}
+ />
+
+ >
);
}