From df90f5c3a8cc0aeadc4b44c6e55f83dfd9be946c Mon Sep 17 00:00:00 2001 From: jaqwe2301 Date: Wed, 11 Feb 2026 16:45:31 +0900 Subject: [PATCH 1/4] =?UTF-8?q?feat:=20=ED=8C=80=EB=A3=B8=20=EC=B9=B4?= =?UTF-8?q?=EC=B9=B4=EC=98=A4=20=EA=B3=B5=EC=9C=A0=ED=95=98=EA=B8=B0=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/entities/teamroom/hooks/useTeamRoom.ts | 12 +- src/shared/lib/{ => copy}/copyText.ts | 0 src/shared/lib/kakao/KakaoSdkLoader.tsx | 50 ++++++++ src/shared/ui/modal/TeamRoomCreatedModal.tsx | 2 +- src/vite-env.d.ts | 10 ++ .../teamroom/main/ui/AddMemberBottomSheet.tsx | 111 +++++++++++------- 6 files changed, 132 insertions(+), 53 deletions(-) rename src/shared/lib/{ => copy}/copyText.ts (100%) create mode 100644 src/shared/lib/kakao/KakaoSdkLoader.tsx 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..d58bb207 --- /dev/null +++ b/src/shared/lib/kakao/KakaoSdkLoader.tsx @@ -0,0 +1,50 @@ +import { useEffect } from 'react'; + +export function KakaoSdkLoader() { + useEffect(() => { + if (typeof window === 'undefined') return; + + const KAKAO_JAVASCRIPT_KEY: string = import.meta.env + ?.VITE_KAKAO_JAVASCRIPT_KEY; + 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); + } + + window.Kakao.isInitialized(); + }; + + // 이미 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..fc6d7815 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,74 @@ 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 () => { + const token = await mutateAsync(teamRoomId); + const inviteUrl = `${window.location.origin}/invite?token=${token}`; + + window.Kakao?.Share.sendScrap({ + requestUrl: inviteUrl, + templateId: Number(KAKAO_TEMPLATE_ID), + }); }; 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} + /> +
+ ); } From f877fd6b96504e2200fab62c74ae6b8812fb12ad Mon Sep 17 00:00:00 2001 From: jaqwe2301 Date: Wed, 11 Feb 2026 16:46:22 +0900 Subject: [PATCH 2/4] =?UTF-8?q?feat:=20=EC=B9=B4=EC=B9=B4=EC=98=A4=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20=ED=99=98=EA=B2=BD=EB=B3=80=EC=88=98=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy.yml | 4 +++- Dockerfile | 6 ++++++ 2 files changed, 9 insertions(+), 1 deletion(-) 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 ./ From a5fad0a19bc6da9463ad1f69b89e063194596bcc Mon Sep 17 00:00:00 2001 From: jaqwe2301 Date: Wed, 11 Feb 2026 17:05:10 +0900 Subject: [PATCH 3/4] =?UTF-8?q?refactor:=20=EC=B9=B4=EC=B9=B4=EC=98=A4=20?= =?UTF-8?q?=EA=B3=B5=EC=9C=A0=ED=95=98=EA=B8=B0=20PR=20=ED=94=BC=EB=93=9C?= =?UTF-8?q?=EB=B0=B1=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/shared/lib/kakao/KakaoSdkLoader.tsx | 2 -- .../teamroom/main/ui/AddMemberBottomSheet.tsx | 19 +++++++++++++------ 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/shared/lib/kakao/KakaoSdkLoader.tsx b/src/shared/lib/kakao/KakaoSdkLoader.tsx index d58bb207..8d1c31f1 100644 --- a/src/shared/lib/kakao/KakaoSdkLoader.tsx +++ b/src/shared/lib/kakao/KakaoSdkLoader.tsx @@ -16,8 +16,6 @@ export function KakaoSdkLoader() { if (!window.Kakao.isInitialized()) { window.Kakao.init(KAKAO_JAVASCRIPT_KEY); } - - window.Kakao.isInitialized(); }; // 이미 script가 있으면 로드 완료 여부에 따라 init diff --git a/src/widgets/teamroom/main/ui/AddMemberBottomSheet.tsx b/src/widgets/teamroom/main/ui/AddMemberBottomSheet.tsx index fc6d7815..a50b31b0 100644 --- a/src/widgets/teamroom/main/ui/AddMemberBottomSheet.tsx +++ b/src/widgets/teamroom/main/ui/AddMemberBottomSheet.tsx @@ -69,13 +69,20 @@ export default function AddMemberBottomSheet({ }; const handleKakaoShare = async () => { - const token = await mutateAsync(teamRoomId); - const inviteUrl = `${window.location.origin}/invite?token=${token}`; + 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), - }); + window.Kakao?.Share.sendScrap({ + requestUrl: inviteUrl, + templateId: Number(KAKAO_TEMPLATE_ID), + }); + } catch (error) { + alert( + '카카오 공유하기 중 오류가 발생하였습니다. 잠시 후 다시 시도해 주세요.', + ); + console.error(error); + } }; return ( From da3e784212613300dc50c3e757ca6993fb9fbc75 Mon Sep 17 00:00:00 2001 From: jaqwe2301 Date: Wed, 11 Feb 2026 17:57:05 +0900 Subject: [PATCH 4/4] =?UTF-8?q?refactor:=20KAKAO=5FJAVASCRIPT=5FKEY=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EB=B0=96=20=EC=83=81?= =?UTF-8?q?=EC=88=98=EB=A1=9C=20=EB=B0=B0=EC=B9=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/shared/lib/kakao/KakaoSdkLoader.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/shared/lib/kakao/KakaoSdkLoader.tsx b/src/shared/lib/kakao/KakaoSdkLoader.tsx index 8d1c31f1..8eb62fa4 100644 --- a/src/shared/lib/kakao/KakaoSdkLoader.tsx +++ b/src/shared/lib/kakao/KakaoSdkLoader.tsx @@ -1,11 +1,11 @@ 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 KAKAO_JAVASCRIPT_KEY: string = import.meta.env - ?.VITE_KAKAO_JAVASCRIPT_KEY; const src = 'https://t1.kakaocdn.net/kakao_js_sdk/2.7.9/kakao.min.js'; const id = 'kakao-js-sdk';