-
Notifications
You must be signed in to change notification settings - Fork 1
feat: KakaoShareButton 컴포넌트에 카카오 SDK 로딩 및 공유 기능 추가 #346
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
Conversation
|
Warning Rate limit exceeded@cywin1018 has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 1 minutes and 43 seconds before requesting another review. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. 📒 Files selected for processing (1)
WalkthroughKakaoShareButton의 클릭 처리 로직을 비동기화하고, Kakao JS SDK를 런타임에 지연 로드하도록 변경. 환경 변수로 SDK 키/템플릿을 읽어 Kakao 공유를 수행하며, 실패 시 Web Share API와 클립보드 복사로 순차 폴백. 컴포넌트의 공개 시그니처는 그대로 유지. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor U as User
participant B as KakaoShareButton
participant L as loadKakao()
participant K as Kakao SDK
participant W as Navigator.share
participant C as Clipboard
U->>B: Click
alt onClick prop 제공됨
B->>B: 사용자 onClick 실행
note right of B: 이후 공유 로직 진행
end
B->>B: 환경변수 읽기 (VITE_KAKAO_JS_KEY / TEMPLATE_ID)
alt SDK 키 존재
B->>L: loadKakao(key)
L-->>B: SDK 준비 완료 또는 에러
alt SDK 로드 성공
alt TEMPLATE_ID 존재
B->>K: Share.sendCustom(templateId, data)
else 기본 공유
B->>K: Share.sendDefault(feed, button)
end
K-->>B: 성공/실패
else SDK 로드 실패
B->>B: Kakao 공유 생략
end
else 키 없음
B->>B: Kakao 공유 생략
end
alt Kakao 공유 실패 또는 미사용
alt Web Share 지원
B->>W: navigator.share({title, text, url})
W-->>B: 성공/실패
else 미지원/실패
B->>C: clipboard.writeText(url)
C-->>B: 완료
B->>U: alert(복사 안내)
end
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested reviewers
Poem
✨ Finishing Touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
🧹 Nitpick comments (3)
src/common/KakaoShareButton.tsx (3)
58-58: onClick이 공유를 완전히 우회합니다 — 의도 확인현재는 onClick 제공 시 공유 로직이 실행되지 않습니다. 로깅·트래킹 용도라면 onClick을 호출만 하고 공유를 계속 진행하는 편이 자연스럽습니다.
적용 diff:
- if (onClick) return onClick(); + onClick?.();
66-66: any 제거: Kakao 전역 최소 타입 선언 제안전역 any는 회귀를 만들기 쉽습니다. 간단한 타입만 선언해도 사용성/안전성이 좋아집니다.
외부 파일 추가(예: src/types/kakao.d.ts):
declare global { interface Window { Kakao: { isInitialized(): boolean; init(key: string): void; Share: { sendCustom(opts: { templateId: number; templateArgs?: Record<string, string> }): void; sendDefault(opts: { objectType: 'feed'; content: { title: string; description?: string; imageUrl?: string; link: { mobileWebUrl: string; webUrl: string }; }; buttons?: Array<{ title: string; link: { mobileWebUrl: string; webUrl: string } }>; }): void; }; }; } } export {};
109-111: UX: alert 대신 비차단형 알림/토스트 고려alert는 전체 UI를 블로킹합니다. 프로젝트의 공통 토스트 컴포넌트가 있다면 그것을 사용하세요. (위 try/catch 통합 diff에는 최종 수단으로 prompt도 추가했습니다.)
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/common/KakaoShareButton.tsx(2 hunks)
🔇 Additional comments (1)
src/common/KakaoShareButton.tsx (1)
68-90: 카카오 콘솔 화이트리스트/템플릿 변수 검증 필요
- shareUrl(https://www.dodream.site/landing)과 이미지 호스트가 카카오 플랫폼 도메인에 등록되어 있어야 전송이 성공합니다.
- sendCustom 사용 시 templateArgs 키(url)가 템플릿 변수명과 일치해야 합니다.
| // Kakao SDK 로더 | ||
| const loadKakao = (key: string) => | ||
| new Promise<void>((resolve, reject) => { | ||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
| const w = window as any; | ||
| if (w.Kakao) { | ||
| if (!w.Kakao.isInitialized()) w.Kakao.init(key); | ||
| resolve(); | ||
| return; | ||
| } | ||
| const s = document.createElement('script'); | ||
| s.src = 'https://t1.kakaocdn.net/kakao_js_sdk/2.7.6/kakao.min.js'; | ||
| s.integrity = | ||
| 'sha384-WAtVcQYcmTO/N+C1N+1m6Gp8qxh+3NlnP7X1U7qP6P5dQY/MsRBNTh+e1ahJrkEm'; | ||
| s.crossOrigin = 'anonymous'; | ||
| s.onload = () => { | ||
| try { | ||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
| (window as any).Kakao.init(key); | ||
| resolve(); | ||
| } catch (e) { | ||
| reject(e); | ||
| } | ||
| }; | ||
| s.onerror = reject; | ||
| document.head.appendChild(s); | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
SDK 중복 로딩/경합 방지: script ID로 디듀플하고 기존 스크립트에 리스너만 부착하세요
빠른 연속 클릭 시 window.Kakao가 생성되기 전까지 <script>가 중복 삽입될 수 있습니다. 스크립트에 고정 ID를 부여하고, 존재 시에는 기존 태그의 load/error에만 리스너를 추가해 디듀플링하세요.
적용 diff:
- const s = document.createElement('script');
- s.src = 'https://t1.kakaocdn.net/kakao_js_sdk/2.7.6/kakao.min.js';
- s.integrity =
- 'sha384-WAtVcQYcmTO/N+C1N+1m6Gp8qxh+3NlnP7X1U7qP6P5dQY/MsRBNTh+e1ahJrkEm';
- s.crossOrigin = 'anonymous';
- s.onload = () => {
- try {
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- (window as any).Kakao.init(key);
- resolve();
- } catch (e) {
- reject(e);
- }
- };
- s.onerror = reject;
- document.head.appendChild(s);
+ const existing = document.getElementById('kakao-sdk') as HTMLScriptElement | null;
+ if (existing) {
+ existing.addEventListener('load', () => {
+ try {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ (window as any).Kakao.init(key);
+ resolve();
+ } catch (e) {
+ reject(e);
+ }
+ }, { once: true });
+ existing.addEventListener('error', reject, { once: true });
+ return;
+ }
+ const s = document.createElement('script');
+ s.id = 'kakao-sdk';
+ s.src = 'https://t1.kakaocdn.net/kakao_js_sdk/2.7.6/kakao.min.js';
+ s.integrity =
+ 'sha384-WAtVcQYcmTO/N+C1N+1m6Gp8qxh+3NlnP7X1U7qP6P5dQY/MsRBNTh+e1ahJrkEm';
+ s.crossOrigin = 'anonymous';
+ s.onload = () => {
+ try {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ (window as any).Kakao.init(key);
+ resolve();
+ } catch (e) {
+ reject(e);
+ }
+ };
+ s.onerror = reject;
+ document.head.appendChild(s);📝 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.
| // Kakao SDK 로더 | |
| const loadKakao = (key: string) => | |
| new Promise<void>((resolve, reject) => { | |
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | |
| const w = window as any; | |
| if (w.Kakao) { | |
| if (!w.Kakao.isInitialized()) w.Kakao.init(key); | |
| resolve(); | |
| return; | |
| } | |
| const s = document.createElement('script'); | |
| s.src = 'https://t1.kakaocdn.net/kakao_js_sdk/2.7.6/kakao.min.js'; | |
| s.integrity = | |
| 'sha384-WAtVcQYcmTO/N+C1N+1m6Gp8qxh+3NlnP7X1U7qP6P5dQY/MsRBNTh+e1ahJrkEm'; | |
| s.crossOrigin = 'anonymous'; | |
| s.onload = () => { | |
| try { | |
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | |
| (window as any).Kakao.init(key); | |
| resolve(); | |
| } catch (e) { | |
| reject(e); | |
| } | |
| }; | |
| s.onerror = reject; | |
| document.head.appendChild(s); | |
| }); | |
| // Kakao SDK 로더 | |
| const loadKakao = (key: string) => | |
| new Promise<void>((resolve, reject) => { | |
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | |
| const w = window as any; | |
| if (w.Kakao) { | |
| if (!w.Kakao.isInitialized()) w.Kakao.init(key); | |
| resolve(); | |
| return; | |
| } | |
| const existing = document.getElementById('kakao-sdk') as HTMLScriptElement | null; | |
| if (existing) { | |
| existing.addEventListener( | |
| 'load', | |
| () => { | |
| try { | |
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | |
| (window as any).Kakao.init(key); | |
| resolve(); | |
| } catch (e) { | |
| reject(e); | |
| } | |
| }, | |
| { once: true } | |
| ); | |
| existing.addEventListener('error', reject, { once: true }); | |
| return; | |
| } | |
| const s = document.createElement('script'); | |
| s.id = 'kakao-sdk'; | |
| s.src = 'https://t1.kakaocdn.net/kakao_js_sdk/2.7.6/kakao.min.js'; | |
| s.integrity = | |
| 'sha384-WAtVcQYcmTO/N+C1N+1m6Gp8qxh+3NlnP7X1U7qP6P5dQY/MsRBNTh+e1ahJrkEm'; | |
| s.crossOrigin = 'anonymous'; | |
| s.onload = () => { | |
| try { | |
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | |
| (window as any).Kakao.init(key); | |
| resolve(); | |
| } catch (e) { | |
| reject(e); | |
| } | |
| }; | |
| s.onerror = reject; | |
| document.head.appendChild(s); | |
| }); |
🤖 Prompt for AI Agents
In src/common/KakaoShareButton.tsx around lines 10 to 36, the loader can append
duplicate <script> tags on rapid calls causing race conditions; add a fixed id
to the script, check document.getElementById(id) first, and if the element
exists attach onload/onerror listeners to that existing element
(resolving/rejecting appropriately) instead of creating a new tag; if it doesn't
exist create the script with that id, attach listeners, append once, and ensure
window.Kakao is only initialized once (guard with isInitialized) and that all
callers get resolved/rejected from the same event handlers.
| s.src = 'https://t1.kakaocdn.net/kakao_js_sdk/2.7.6/kakao.min.js'; | ||
| s.integrity = | ||
| 'sha384-WAtVcQYcmTO/N+C1N+1m6Gp8qxh+3NlnP7X1U7qP6P5dQY/MsRBNTh+e1ahJrkEm'; | ||
| s.crossOrigin = 'anonymous'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
SDK 버전/무결성(SRI) 해시 최신 여부 확인
고정 버전(2.7.6)과 SRI 해시가 실제 배포 파일과 불일치하면 로드가 실패합니다. 최신 권장 버전과 해시를 확인해 주세요. 또한 변경 시 반드시 해시도 같이 갱신해야 합니다.
🌐 Web query:
Kakao JavaScript SDK의 최신 안정 버전은 무엇인가요? 또한 https://t1.kakaocdn.net/kakao_js_sdk/2.7.6/kakao.min.js 의 공식 SRI 해시가 무엇인지 확인해 주세요.
💡 Result:
요약:
- 최신 안정 버전: 2.7.8 (배포일: 2025-09-03). [1]
- https://t1.kakaocdn.net/kakao_js_sdk/2.7.6/kakao.min.js 의 공식 SRI 값은 카카오 개발자 문서의 다운로드 페이지에서 제공되지만(각 버전 항목의 "Copy Integrity Value"), 제가 해당 CDN 파일이나 페이지의 복사 버튼을 직접 조회하지 못해 현재 공식 SRI 해시를 바로 가져오지 못했습니다. 다운로드 페이지에서 2.7.6 항목의 "Copy Integrity Value"를 클릭하면 integrity 속성 값을 복사할 수 있습니다. [1]
원문 참조:
- Kakao Developers — JavaScript SDK 다운로드 (버전 목록 및 "Copy Integrity Value" 안내). [https://developers.kakao.com/docs/latest/ko/javascript/download] [https://developers.kakao.com/docs/latest/en/javascript/download]
🏁 Script executed:
#!/bin/bash
# Fetch Kakao JS SDK v2.7.8 and compute its official sha384 SRI hash
curl -s https://t1.kakaocdn.net/kakao_js_sdk/2.7.8/kakao.min.js \
| openssl dgst -sha384 -binary \
| openssl base64 -A \
| sed 's/^/sha384-/'Length of output: 91
🏁 Script executed:
#!/bin/bash
# Python으로 Kakao JS SDK v2.7.6 및 v2.7.8의 sha384 SRI 해시 계산
python3 - << 'EOF'
import hashlib, base64, urllib.request
def calc_sri(version):
url = f'https://t1.kakaocdn.net/kakao_js_sdk/{version}/kakao.min.js'
data = urllib.request.urlopen(url).read()
h = hashlib.sha384(data).digest()
print(version, 'sha384-' + base64.b64encode(h).decode())
for v in ['2.7.6', '2.7.8']:
calc_sri(v)
EOFLength of output: 2620
🏁 Script executed:
#!/bin/bash
# Python으로 SSL 인증 무시하고 Kakao JS SDK v2.7.6 및 v2.7.8의 sha384 SRI 해시 계산
python3 - << 'EOF'
import hashlib, base64, urllib.request, ssl
ctx = ssl._create_unverified_context()
def calc_sri(version):
url = f'https://t1.kakaocdn.net/kakao_js_sdk/{version}/kakao.min.js'
data = urllib.request.urlopen(url, context=ctx).read()
h = hashlib.sha384(data).digest()
print(version, 'sha384-' + base64.b64encode(h).decode())
for v in ['2.7.6', '2.7.8']:
calc_sri(v)
EOFLength of output: 155
Kakao JS SDK를 v2.7.8로 업데이트하고 SRI 해시도 갱신하세요
현재 2.7.6(sha384-WAtVcQYcmTO/N+C1N+1m6Gp8qxh+3NlnP7X1U7qP6P5dQY/MsRBNTh+e1ahJrkEm)을 사용 중인데, 최신 안정 버전은 2.7.8(2025-09-03 배포)이므로 아래와 같이 변경해야 로드 실패를 방지할 수 있습니다.
- s.src = 'https://t1.kakaocdn.net/kakao_js_sdk/2.7.6/kakao.min.js';
- s.integrity = 'sha384-WAtVcQYcmTO/N+C1N+1m6Gp8qxh+3NlnP7X1U7qP6P5dQY/MsRBNTh+e1ahJrkEm';
+ s.src = 'https://t1.kakaocdn.net/kakao_js_sdk/2.7.8/kakao.min.js';
+ s.integrity = 'sha384-WUSirVbD0ASvo37f3qQZuDap8wy76aJjmGyXKOYgPL/NdAs8HhgmPlk9dz2XQsNv';Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In src/common/KakaoShareButton.tsx around lines 21–24, the included Kakao JS SDK
version and its SRI hash are outdated (2.7.6); update the script src to v2.7.8
and replace the integrity attribute with the official sha384 SRI for the 2.7.8
release (fetch the exact hash from the provider/CDN or the Kakao release notes),
keeping crossOrigin='anonymous' intact so the script loads with the correct
subresource integrity.
| onClick={async () => { | ||
| if (onClick) return onClick(); | ||
| const jsKey = import.meta.env.VITE_KAKAO_JS_KEY as string; | ||
| if (!jsKey) { | ||
| console.warn('VITE_KAKAO_JS_KEY is not set.'); | ||
| return; | ||
| } | ||
| await loadKakao(jsKey); | ||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
| const Kakao = (window as any).Kakao; | ||
| const templateId = Number(import.meta.env.VITE_KAKAO_TEMPLATE_ID); | ||
| const shareUrl = 'https://www.dodream.site/landing'; | ||
| try { | ||
| if (templateId) { | ||
| Kakao.Share.sendCustom({ | ||
| templateId, | ||
| templateArgs: { url: shareUrl }, | ||
| }); | ||
| } else { | ||
| Kakao.Share.sendDefault({ | ||
| objectType: 'feed', | ||
| content: { | ||
| title: '두드림 – 내게 딱 맞는 직업 찾기', | ||
| description: '직업/학원/구직 정보를 한 곳에서!', | ||
| imageUrl: 'https://www.dodream.site/og-image.png', | ||
| link: { mobileWebUrl: shareUrl, webUrl: shareUrl }, | ||
| }, | ||
| buttons: [ | ||
| { | ||
| title: '두드림 열기', | ||
| link: { mobileWebUrl: shareUrl, webUrl: shareUrl }, | ||
| }, | ||
| ], | ||
| }); | ||
| } | ||
| } catch (e) { | ||
| console.error('Kakao share failed', e); | ||
| // Fallback: Web Share API or clipboard copy | ||
| try { | ||
| if (navigator.share) { | ||
| await navigator.share({ | ||
| title: '두드림 – 내게 딱 맞는 직업 찾기', | ||
| text: '직업/학원/구직 정보를 한 곳에서!', | ||
| url: shareUrl, | ||
| }); | ||
| return; | ||
| } | ||
| } catch { | ||
| // ignore and continue to clipboard | ||
| } | ||
| try { | ||
| await navigator.clipboard.writeText(shareUrl); | ||
| alert( | ||
| '카카오 공유가 불가능하여 링크를 클립보드에 복사했습니다. 붙여넣기로 공유해 주세요.' | ||
| ); | ||
| } catch (err) { | ||
| console.error('Clipboard write failed', err); | ||
| } | ||
| } | ||
| }} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
loadKakao 실패 시 폴백 미작동 — try/catch 범위를 전체 Kakao 흐름으로 확장 필요
현재 try/catch가 공유 호출 부분에만 적용되어 SDK 로드 실패(네트워크, SRI mismatch 등) 시 폴백(Web Share/클립보드)이 실행되지 않습니다. 로드+공유 전체를 한 번의 try/catch로 감싸고, 키 미설정 시에도 폴백을 타도록 수정하세요.
적용 diff:
- const jsKey = import.meta.env.VITE_KAKAO_JS_KEY as string;
- if (!jsKey) {
- console.warn('VITE_KAKAO_JS_KEY is not set.');
- return;
- }
- await loadKakao(jsKey);
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- const Kakao = (window as any).Kakao;
- const templateId = Number(import.meta.env.VITE_KAKAO_TEMPLATE_ID);
- const shareUrl = 'https://www.dodream.site/landing';
- try {
- if (templateId) {
- Kakao.Share.sendCustom({
- templateId,
- templateArgs: { url: shareUrl },
- });
- } else {
- Kakao.Share.sendDefault({
- objectType: 'feed',
- content: {
- title: '두드림 – 내게 딱 맞는 직업 찾기',
- description: '직업/학원/구직 정보를 한 곳에서!',
- imageUrl: 'https://www.dodream.site/og-image.png',
- link: { mobileWebUrl: shareUrl, webUrl: shareUrl },
- },
- buttons: [
- {
- title: '두드림 열기',
- link: { mobileWebUrl: shareUrl, webUrl: shareUrl },
- },
- ],
- });
- }
- } catch (e) {
- console.error('Kakao share failed', e);
- // Fallback: Web Share API or clipboard copy
- try {
- if (navigator.share) {
- await navigator.share({
- title: '두드림 – 내게 딱 맞는 직업 찾기',
- text: '직업/학원/구직 정보를 한 곳에서!',
- url: shareUrl,
- });
- return;
- }
- } catch {
- // ignore and continue to clipboard
- }
- try {
- await navigator.clipboard.writeText(shareUrl);
- alert(
- '카카오 공유가 불가능하여 링크를 클립보드에 복사했습니다. 붙여넣기로 공유해 주세요.'
- );
- } catch (err) {
- console.error('Clipboard write failed', err);
- }
- }
+ const jsKey = import.meta.env.VITE_KAKAO_JS_KEY as string;
+ const shareUrl = 'https://www.dodream.site/landing';
+ try {
+ if (jsKey) {
+ await loadKakao(jsKey);
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const Kakao = (window as any).Kakao;
+ const templateId = Number(import.meta.env.VITE_KAKAO_TEMPLATE_ID);
+ if (templateId) {
+ Kakao.Share.sendCustom({
+ templateId,
+ templateArgs: { url: shareUrl },
+ });
+ } else {
+ Kakao.Share.sendDefault({
+ objectType: 'feed',
+ content: {
+ title: '두드림 – 내게 딱 맞는 직업 찾기',
+ description: '직업/학원/구직 정보를 한 곳에서!',
+ imageUrl: 'https://www.dodream.site/og-image.png',
+ link: { mobileWebUrl: shareUrl, webUrl: shareUrl },
+ },
+ buttons: [
+ {
+ title: '두드림 열기',
+ link: { mobileWebUrl: shareUrl, webUrl: shareUrl },
+ },
+ ],
+ });
+ }
+ return;
+ }
+ throw new Error('VITE_KAKAO_JS_KEY is not set.');
+ } catch (e) {
+ console.error('Kakao share failed', e);
+ try {
+ if (navigator.share) {
+ await navigator.share({
+ title: '두드림 – 내게 딱 맞는 직업 찾기',
+ text: '직업/학원/구직 정보를 한 곳에서!',
+ url: shareUrl,
+ });
+ return;
+ }
+ } catch {
+ // ignore and continue to clipboard
+ }
+ try {
+ await navigator.clipboard.writeText(shareUrl);
+ alert(
+ '카카오 공유가 불가능하여 링크를 클립보드에 복사했습니다. 붙여넣기로 공유해 주세요.'
+ );
+ } catch (err) {
+ console.error('Clipboard write failed', err);
+ window.prompt('링크 복사에 실패했습니다. 아래 링크를 복사해 주세요.', shareUrl);
+ }
+ }📝 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.
| onClick={async () => { | |
| if (onClick) return onClick(); | |
| const jsKey = import.meta.env.VITE_KAKAO_JS_KEY as string; | |
| if (!jsKey) { | |
| console.warn('VITE_KAKAO_JS_KEY is not set.'); | |
| return; | |
| } | |
| await loadKakao(jsKey); | |
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | |
| const Kakao = (window as any).Kakao; | |
| const templateId = Number(import.meta.env.VITE_KAKAO_TEMPLATE_ID); | |
| const shareUrl = 'https://www.dodream.site/landing'; | |
| try { | |
| if (templateId) { | |
| Kakao.Share.sendCustom({ | |
| templateId, | |
| templateArgs: { url: shareUrl }, | |
| }); | |
| } else { | |
| Kakao.Share.sendDefault({ | |
| objectType: 'feed', | |
| content: { | |
| title: '두드림 – 내게 딱 맞는 직업 찾기', | |
| description: '직업/학원/구직 정보를 한 곳에서!', | |
| imageUrl: 'https://www.dodream.site/og-image.png', | |
| link: { mobileWebUrl: shareUrl, webUrl: shareUrl }, | |
| }, | |
| buttons: [ | |
| { | |
| title: '두드림 열기', | |
| link: { mobileWebUrl: shareUrl, webUrl: shareUrl }, | |
| }, | |
| ], | |
| }); | |
| } | |
| } catch (e) { | |
| console.error('Kakao share failed', e); | |
| // Fallback: Web Share API or clipboard copy | |
| try { | |
| if (navigator.share) { | |
| await navigator.share({ | |
| title: '두드림 – 내게 딱 맞는 직업 찾기', | |
| text: '직업/학원/구직 정보를 한 곳에서!', | |
| url: shareUrl, | |
| }); | |
| return; | |
| } | |
| } catch { | |
| // ignore and continue to clipboard | |
| } | |
| try { | |
| await navigator.clipboard.writeText(shareUrl); | |
| alert( | |
| '카카오 공유가 불가능하여 링크를 클립보드에 복사했습니다. 붙여넣기로 공유해 주세요.' | |
| ); | |
| } catch (err) { | |
| console.error('Clipboard write failed', err); | |
| } | |
| } | |
| }} | |
| onClick={async () => { | |
| if (onClick) return onClick(); | |
| const jsKey = import.meta.env.VITE_KAKAO_JS_KEY as string; | |
| const shareUrl = 'https://www.dodream.site/landing'; | |
| try { | |
| if (jsKey) { | |
| await loadKakao(jsKey); | |
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | |
| const Kakao = (window as any).Kakao; | |
| const templateId = Number(import.meta.env.VITE_KAKAO_TEMPLATE_ID); | |
| if (templateId) { | |
| Kakao.Share.sendCustom({ | |
| templateId, | |
| templateArgs: { url: shareUrl }, | |
| }); | |
| } else { | |
| Kakao.Share.sendDefault({ | |
| objectType: 'feed', | |
| content: { | |
| title: '두드림 – 내게 딱 맞는 직업 찾기', | |
| description: '직업/학원/구직 정보를 한 곳에서!', | |
| imageUrl: 'https://www.dodream.site/og-image.png', | |
| link: { mobileWebUrl: shareUrl, webUrl: shareUrl }, | |
| }, | |
| buttons: [ | |
| { | |
| title: '두드림 열기', | |
| link: { mobileWebUrl: shareUrl, webUrl: shareUrl }, | |
| }, | |
| ], | |
| }); | |
| } | |
| return; | |
| } | |
| throw new Error('VITE_KAKAO_JS_KEY is not set.'); | |
| } catch (e) { | |
| console.error('Kakao share failed', e); | |
| try { | |
| if (navigator.share) { | |
| await navigator.share({ | |
| title: '두드림 – 내게 딱 맞는 직업 찾기', | |
| text: '직업/학원/구직 정보를 한 곳에서!', | |
| url: shareUrl, | |
| }); | |
| return; | |
| } | |
| } catch { | |
| // ignore and continue to clipboard | |
| } | |
| try { | |
| await navigator.clipboard.writeText(shareUrl); | |
| alert( | |
| '카카오 공유가 불가능하여 링크를 클립보드에 복사했습니다. 붙여넣기로 공유해 주세요.' | |
| ); | |
| } catch (err) { | |
| console.error('Clipboard write failed', err); | |
| window.prompt('링크 복사에 실패했습니다. 아래 링크를 복사해 주세요.', shareUrl); | |
| } | |
| } | |
| }} |
🚀 풀 리퀘스트 제안
📋 작업 내용
📸 스크린샷 (선택 사항)
📄 기타
Summary by CodeRabbit