Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@
type="text/javascript"
src="//dapi.kakao.com/v2/maps/sdk.js?appkey=%VITE_KAKAO_MAP_API_KEY%&libraries=services,clusterer&autoload=true"
></script>
<script src="https://developers.kakao.com/sdk/js/kakao.js"></script>
</body>
</html>
60 changes: 56 additions & 4 deletions src/features/event-manage/event-create/ui/ShareEventModal.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,65 @@
import profile from '../../../../../public/assets/banners/1.png';
import link from '../../../../../public/assets/event-manage/details/Link.svg';
import kakao from '../../../../../public/assets/event-manage/details/KaKao.svg';
import { shareToKakao } from '../../../../shared/lib/kakaoShare';

interface ShareEventModalProp {
interface ShareEventModalProps {
closeModal: () => void;
eventName: string;
eventDescription?: string;
eventImageUrl?: string;
eventUrl?: string;
}

const ShareEventModal = ({ closeModal, eventName }: ShareEventModalProp) => {
const ShareEventModal = ({
closeModal,
eventName,
eventDescription = '',
eventImageUrl = '',
eventUrl = window.location.href,
}: ShareEventModalProps) => {
const handleKakaoShare = async () => {
try {
await shareToKakao(eventName, eventDescription, eventImageUrl, eventUrl);
} catch (error) {
console.error('카카오 공유 실패:', error);
alert('카카오 공유하기에 실패했습니다.');
}
};

/* navigator.clipboard가 HTTPS, localhosts 에서만 작동하므로 배포 후 코드 변경해야함
const handleCopyLink = async () => {
try {
await navigator.clipboard.writeText(eventUrl);
alert('링크가 복사되었습니다!');
} catch (error) {
console.error('링크 복사 실패:', error);
alert('링크 복사에 실패했습니다.');
}
};
*/

const handleCopyLink = () => {
if (navigator.clipboard && typeof navigator.clipboard.writeText === 'function') {
navigator.clipboard
.writeText(eventUrl)
.then(() => alert('링크가 복사되었습니다!'))
.catch(err => {
console.error('복사 실패:', err);
alert('링크 복사에 실패했습니다.');
});
} else {
// Fallback (입력창 생성해서 수동 복사 유도)
const textArea = document.createElement('textarea');
textArea.value = eventUrl;
document.body.appendChild(textArea);
textArea.select();
document.execCommand('copy');
document.body.removeChild(textArea);
alert('링크가 복사되었습니다!');
}
};

return (
<div className="fixed inset-0 z-50 flex items-end justify-center">
<div className="absolute inset-0 mx-auto w-full max-w-lg bg-black bg-opacity-30" onClick={closeModal}></div>
Expand All @@ -21,12 +73,12 @@ const ShareEventModal = ({ closeModal, eventName }: ShareEventModalProp) => {
<span className="font-semibold text-lg">{eventName}</span>
</div>
<div className="flex flex-col gap-4 py-3">
<div className="flex items-center gap-4">
<div onClick={handleCopyLink} className="flex items-center gap-4 cursor-pointer">
<img src={link} alt="링크" className="w-6 h-6" />
<h2 className="text-base">링크 복사하기</h2>
</div>
<hr />
<div className="flex items-center gap-4">
<div onClick={handleKakaoShare} className="flex items-center gap-4 cursor-pointer">
<img src={kakao} alt="카카오" className="w-6 h-6" />
<h2 className="text-base">카카오톡 공유하기</h2>
</div>
Expand Down
10 changes: 9 additions & 1 deletion src/pages/event/ui/EventDetailsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,15 @@ const EventDetailsPage = () => {
) : (
<div className="flex justify-center items-center h-screen">로딩 중...</div>
)}
{isModalOpen && <ShareEventModal closeModal={closeModal} eventName={title} />}
{isModalOpen && (
<ShareEventModal
closeModal={closeModal}
eventName={title}
eventDescription={event?.result.description}
eventImageUrl={event?.result.bannerImageUrl}
eventUrl={window.location.href}
/>
)}
Comment on lines +134 to +142
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

이벤트 데이터 안전한 전달 보장 필요

ShareEventModal에 새로운 props를 전달하여 카카오 공유 기능을 지원하고 있습니다. 하지만 event가 undefined일 경우에 대한 처리가 불완전합니다.

다음과 같이 기본값을 제공하여 안전성을 개선하세요:

      {isModalOpen && (
        <ShareEventModal
          closeModal={closeModal}
          eventName={title}
-          eventDescription={event?.result.description}
-          eventImageUrl={event?.result.bannerImageUrl}
+          eventDescription={event?.result.description || '이벤트 설명이 없습니다.'}
+          eventImageUrl={event?.result.bannerImageUrl || '기본 이미지 URL'}
          eventUrl={window.location.href}
        />
      )}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{isModalOpen && (
<ShareEventModal
closeModal={closeModal}
eventName={title}
eventDescription={event?.result.description}
eventImageUrl={event?.result.bannerImageUrl}
eventUrl={window.location.href}
/>
)}
{isModalOpen && (
<ShareEventModal
closeModal={closeModal}
eventName={title}
eventDescription={event?.result.description || '이벤트 설명이 없습니다.'}
eventImageUrl={event?.result.bannerImageUrl || '기본 이미지 URL'}
eventUrl={window.location.href}
/>
)}

</>
);
};
Expand Down
50 changes: 50 additions & 0 deletions src/shared/lib/kakaoShare.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
export const initializeKakao = () => {
return new Promise<void>(resolve => {
if (window.Kakao && window.Kakao.Link) {
resolve();
return;
}

const script = document.createElement('script');
script.src = `https://developers.kakao.com/sdk/js/kakao.js`;
script.async = true;
document.head.appendChild(script);

script.onload = () => {
if (window.Kakao) {
window.Kakao.init(import.meta.env.VITE_KAKAO_MAP_API_KEY);
}
resolve();
};
});
};
Comment on lines +1 to +20
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Kakao SDK 초기화 로직 개선 필요

카카오 SDK 초기화 함수가 잘 구현되어 있지만, 몇 가지 개선할 점이 있습니다.

  1. 정적 스크립트 태그와 동적 로딩 방식의 중복 - index.html에 이미 스크립트 태그가 있는데 여기서도 동적으로 로드하고 있습니다.
  2. 옵셔널 체이닝 사용 권장 (정적 분석 도구 힌트 반영):
-  if (window.Kakao && window.Kakao.Link) {
+  if (window.Kakao?.Link) {
    resolve();
    return;
  }
  1. 초기화 실패에 대한 오류 처리 추가:
    script.onload = () => {
      if (window.Kakao) {
-        window.Kakao.init(import.meta.env.VITE_KAKAO_MAP_API_KEY);
+        try {
+          window.Kakao.init(import.meta.env.VITE_KAKAO_MAP_API_KEY);
+          console.log('카카오 SDK가 초기화되었습니다.');
+        } catch (error) {
+          console.error('카카오 SDK 초기화 실패:', error);
+        }
      }
      resolve();
    };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const initializeKakao = () => {
return new Promise<void>(resolve => {
if (window.Kakao && window.Kakao.Link) {
resolve();
return;
}
const script = document.createElement('script');
script.src = `https://developers.kakao.com/sdk/js/kakao.js`;
script.async = true;
document.head.appendChild(script);
script.onload = () => {
if (window.Kakao) {
window.Kakao.init(import.meta.env.VITE_KAKAO_MAP_API_KEY);
}
resolve();
};
});
};
export const initializeKakao = () => {
return new Promise<void>(resolve => {
if (window.Kakao?.Link) {
resolve();
return;
}
const script = document.createElement('script');
script.src = `https://developers.kakao.com/sdk/js/kakao.js`;
script.async = true;
document.head.appendChild(script);
script.onload = () => {
if (window.Kakao) {
try {
window.Kakao.init(import.meta.env.VITE_KAKAO_MAP_API_KEY);
console.log('카카오 SDK가 초기화되었습니다.');
} catch (error) {
console.error('카카오 SDK 초기화 실패:', error);
}
}
resolve();
};
});
};
🧰 Tools
🪛 Biome (1.9.4)

[error] 3-3: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


export const shareToKakao = async (title: string, description: string, imageUrl: string, linkUrl: string) => {
await initializeKakao();

if (!window.Kakao?.Link?.sendDefault) {
throw new Error('카카오 SDK가 초기화되지 않았습니다.');
}

window.Kakao.Link.sendDefault({
objectType: 'feed',
content: {
title,
description,
imageUrl,
link: {
mobileWebUrl: linkUrl,
webUrl: linkUrl,
},
},
buttons: [
{
title: '자세히 보기',
link: {
mobileWebUrl: linkUrl,
webUrl: linkUrl,
},
},
],
});
};
Comment on lines +22 to +50
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

공유 기능 구현이 잘 되었으나 검증 필요

카카오 공유 기능이 잘 구현되어 있습니다. 적절한 오류 처리와 함께 필요한 파라미터를 전달하고 있습니다.

다만, Maps API 키가 Link API에도 사용 가능한지 확인이 필요합니다. Maps API와 Share API의 키가 동일한 경우도 있지만, 다른 경우도 있을 수 있습니다:


🌐 Web query:

카카오 Maps API 키와 Share API 키가 동일한가요?

💡 Result:

카카오의 지도 서비스와 공유 기능을 사용하기 위해서는 각각 별도의 API 키가 필요합니다. 지도 관련 기능을 구현하려면 카카오 지도 API 키를, 공유 기능을 구현하려면 카카오 공유 API 키를 발급받아야 합니다. 이러한 API 키는 각 서비스의 개발자 사이트에서 신청할 수 있으며, 발급 과정에서 필요한 정보와 절차를 안내받을 수 있습니다.


카카오 공유 API 키 확인 필요

공유 기능 구현은 적절하지만, 카카오 지도 API 키(Maps API Key)와 공유 기능(Link API) 키는 별도입니다. 현재 프로젝트에 설정된 키가 공유용 키인지 반드시 검증해 주세요.

  • src/shared/lib/kakaoShare.ts에서 사용하는 키가 공유 API 전용 키인지 확인
  • Kakao Developers 콘솔에서 발급받은 **Link API(JavaScript Key 또는 REST API Key)**를 사용 중인지 점검

30 changes: 30 additions & 0 deletions src/shared/types/kakaoType.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
export interface KakaoLinkOptions {
objectType: 'feed';
content: {
title: string;
description: string;
imageUrl: string;
link: {
mobileWebUrl: string;
webUrl: string;
};
};
buttons: Array<{
title: string;
link: {
mobileWebUrl: string;
webUrl: string;
};
}>;
}

declare global {
interface Window {
Kakao: {
init: (key: string) => void;
Link: {
sendDefault: (options: KakaoLinkOptions) => void;
};
};
}
}