From 63d60caabda0f6976e82150195366dd0146f5b51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=84=B1=EA=B2=BD?= Date: Tue, 18 Aug 2026 17:23:42 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20=EC=9D=B4=EC=A0=84=20=EA=B3=B5=EC=9C=A0?= =?UTF-8?q?=ED=95=98=EA=B8=B0=20=EA=B8=B0=EB=8A=A5=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/ContentCardSocialActions.tsx | 147 ++------------------ src/components/VideoDetailContent.tsx | 113 +++------------ src/utils/nativeShare.ts | 3 +- 3 files changed, 28 insertions(+), 235 deletions(-) diff --git a/src/components/ContentCardSocialActions.tsx b/src/components/ContentCardSocialActions.tsx index 5172b92..52d16bb 100644 --- a/src/components/ContentCardSocialActions.tsx +++ b/src/components/ContentCardSocialActions.tsx @@ -1,5 +1,4 @@ -import React, { useEffect, useLayoutEffect, useRef, useState } from 'react'; -import { createPortal } from 'react-dom'; +import React, { useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { API_URL, toggleVideoLike } from '../utils/api'; import { buildVideoShareUrl, tryNativeShare } from '../utils/nativeShare'; @@ -25,96 +24,29 @@ const ContentCardSocialActions: React.FC = ({ const [likeCount, setLikeCount] = useState(initialLikeCount); const [isLiked, setIsLiked] = useState(initialIsLiked); - const [copied, setCopied] = useState(false); - const [shareMenuOpen, setShareMenuOpen] = useState(false); - const [menuPos, setMenuPos] = useState<{ top: number; left: number } | null>(null); - const [anchorRect, setAnchorRect] = useState<{ top: number; bottom: number; left: number } | null>(null); - - const shareBtnRef = useRef(null); - const shareMenuRef = useRef(null); const likeDebounceRef = useRef | null>(null); const shareUrl = buildVideoShareUrl(API_URL, videoId); const shareTitle = storeName || t('videoDetail.kakaoDefaultTitle'); const shareDescription = t('videoDetail.kakaoDescription', { region: region ?? '' }); - useEffect(() => { - if (!shareMenuOpen) return; + const handleShareBtnClick = async (e: React.MouseEvent) => { + e.stopPropagation(); - const closeMenu = () => setShareMenuOpen(false); - - const handleClickOutside = (e: MouseEvent) => { - if ( - shareMenuRef.current && !shareMenuRef.current.contains(e.target as Node) && - shareBtnRef.current && !shareBtnRef.current.contains(e.target as Node) - ) { - closeMenu(); - } - }; - - document.addEventListener('mousedown', handleClickOutside); - window.addEventListener('scroll', closeMenu, true); - window.addEventListener('resize', closeMenu); - return () => { - document.removeEventListener('mousedown', handleClickOutside); - window.removeEventListener('scroll', closeMenu, true); - window.removeEventListener('resize', closeMenu); - }; - }, [shareMenuOpen]); - - // 메뉴가 뷰포트 밖으로 넘어가지 않도록 실제 렌더된 크기 기준으로 위치 보정 - useLayoutEffect(() => { - if (!shareMenuOpen || !anchorRect || !shareMenuRef.current) return; - const margin = 8; - const menuRect = shareMenuRef.current.getBoundingClientRect(); - - let left = anchorRect.left; - if (left + menuRect.width > window.innerWidth - margin) { - left = window.innerWidth - menuRect.width - margin; + const handled = await tryNativeShare({ + title: shareTitle, + text: shareDescription, + url: shareUrl, + }); + if (handled) { + return; } - left = Math.max(margin, left); - let top = anchorRect.bottom + 6; - if (top + menuRect.height > window.innerHeight - margin) { - top = anchorRect.top - menuRect.height - 6; - } - top = Math.max(margin, top); - - setMenuPos((prev) => (prev && prev.top === top && prev.left === left ? prev : { top, left })); - }, [shareMenuOpen, anchorRect]); - - const handleCopyLink = async () => { try { await navigator.clipboard.writeText(shareUrl); } catch { // clipboard API 미지원 환경에서는 무시 } - setCopied(true); - setTimeout(() => setCopied(false), 2000); - }; - - const handleKakaoShare = () => { - const kakao = window.Kakao; - if (kakao?.Share) { - // 공유 URL(백엔드 OG 페이지)을 스크랩해 제목/설명/이미지를 자동으로 채운다. - // 다른 채널과 동일한 shareUrl을 쓰므로 카카오만을 위한 별도 콘텐츠 지정이 필요 없다. - kakao.Share.sendScrap({ requestUrl: shareUrl }); - } else if (navigator.share) { - navigator.share({ title: shareTitle, text: shareDescription, url: shareUrl }).catch(() => {}); - } else { - handleCopyLink(); - } - setShareMenuOpen(false); - }; - - const handleFacebookShare = () => { - window.open(`https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(shareUrl)}`, '_blank', 'noopener,width=600,height=600'); - setShareMenuOpen(false); - }; - - const handleTwitterShare = () => { - window.open(`https://twitter.com/intent/tweet?url=${encodeURIComponent(shareUrl)}`, '_blank', 'noopener,width=600,height=600'); - setShareMenuOpen(false); }; const handleLikeClick = (e: React.MouseEvent) => { @@ -140,28 +72,6 @@ const ContentCardSocialActions: React.FC = ({ }, 500); }; - const handleShareBtnClick = async (e: React.MouseEvent) => { - e.stopPropagation(); - - const handled = await tryNativeShare({ - title: shareTitle, - text: shareDescription, - url: shareUrl, - }); - if (handled) { - setShareMenuOpen(false); - return; - } - - const rect = shareBtnRef.current?.getBoundingClientRect(); - if (rect) { - // 초기 추정 위치(측정 전 첫 렌더용) — useLayoutEffect가 실제 크기 기준으로 다시 보정한다 - setAnchorRect({ top: rect.top, bottom: rect.bottom, left: rect.left }); - setMenuPos({ top: rect.bottom + 6, left: rect.left }); - } - setShareMenuOpen((v) => !v); - }; - return (
e.stopPropagation()}> - - {shareMenuOpen && menuPos && createPortal( -
- - - - -
, - document.body - )}
); }; diff --git a/src/components/VideoDetailContent.tsx b/src/components/VideoDetailContent.tsx index d39cfc2..f7c27f2 100644 --- a/src/components/VideoDetailContent.tsx +++ b/src/components/VideoDetailContent.tsx @@ -32,7 +32,6 @@ const VideoDetailContent: React.FC = ({ videoId, isModa const [isLiked, setIsLiked] = useState(false); const [copied, setCopied] = useState(false); - const [shareMenuOpen, setShareMenuOpen] = useState(false); const [isLandscape, setIsLandscape] = useState(false); const [showLoginModal, setShowLoginModal] = useState(false); @@ -107,42 +106,6 @@ const VideoDetailContent: React.FC = ({ videoId, isModa const shareTitle = video?.store_name ?? t('videoDetail.kakaoDefaultTitle'); const shareDescription = t('videoDetail.kakaoDescription', { region: video?.region ?? '' }); - const handleCopyLink = async () => { - try { - await navigator.clipboard.writeText(shareUrl); - } catch { - // clipboard API 미지원 환경에서는 무시 - } - setCopied(true); - setTimeout(() => setCopied(false), 2000); - }; - - const handleKakaoShare = () => { - const kakao = window.Kakao; - if (kakao?.Share) { - // 공유 URL(백엔드 OG 페이지)을 스크랩해 제목/설명/이미지를 자동으로 채운다. - // 다른 채널과 동일한 shareUrl을 쓰므로 카카오만을 위한 별도 콘텐츠 지정이 필요 없다. - kakao.Share.sendScrap({ requestUrl: shareUrl }); - } else if (navigator.share) { - navigator.share({ title: shareTitle, text: shareDescription, url: shareUrl }).catch(() => {}); - } else { - handleCopyLink(); - } - setShareMenuOpen(false); - }; - - const handleFacebookShare = () => { - window.open(`https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(shareUrl)}`, '_blank', 'noopener,width=600,height=600'); - setShareMenuOpen(false); - }; - - const handleTwitterShare = () => { - window.open(`https://twitter.com/intent/tweet?url=${encodeURIComponent(shareUrl)}`, '_blank', 'noopener,width=600,height=600'); - setShareMenuOpen(false); - }; - - const shareMenuRef = React.useRef(null); - const handleShareButtonClick = async () => { const handled = await tryNativeShare({ title: shareTitle, @@ -150,24 +113,18 @@ const VideoDetailContent: React.FC = ({ videoId, isModa url: shareUrl, }); if (handled) { - setShareMenuOpen(false); return; } - setShareMenuOpen((open) => !open); + try { + await navigator.clipboard.writeText(shareUrl); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + } catch { + // clipboard API 미지원 환경에서는 무시 + } }; - useEffect(() => { - if (!shareMenuOpen) return; - const handleClickOutside = (e: MouseEvent) => { - if (shareMenuRef.current && !shareMenuRef.current.contains(e.target as Node)) { - setShareMenuOpen(false); - } - }; - document.addEventListener('mousedown', handleClickOutside); - return () => document.removeEventListener('mousedown', handleClickOutside); - }, [shareMenuOpen]); - const likeDebounceRef = React.useRef | null>(null); const handleLike = () => { @@ -292,52 +249,16 @@ const VideoDetailContent: React.FC = ({ videoId, isModa {likeCount} -
- - {shareMenuOpen && ( -
- {/* 카카오톡 */} - - {/* 페이스북 */} - - {/* X (트위터) */} - - {/* URL 복사 */} - -
- )} -
+ {/* 댓글 섹션 */} diff --git a/src/utils/nativeShare.ts b/src/utils/nativeShare.ts index 7946064..4278804 100644 --- a/src/utils/nativeShare.ts +++ b/src/utils/nativeShare.ts @@ -1,8 +1,7 @@ /** * 기기의 네이티브 공유 시트를 열고 요청을 처리했는지 반환합니다. * - * 사용자가 공유 시트를 닫은 경우도 정상적으로 처리된 것으로 간주해 - * 별도의 공유 메뉴가 뒤이어 열리지 않도록 합니다. + * 사용자가 공유 시트를 닫은 경우도 정상적으로 처리된 것으로 간주합니다. */ export async function tryNativeShare(data: ShareData): Promise { if (typeof navigator.share !== 'function') {