castad-pre-v0.3/castad-data/components/ShareModal.tsx

189 lines
8.8 KiB
TypeScript

import React, { useState, useEffect } from 'react';
import { X, Copy, Check, Share2, Smartphone, Link as LinkIcon } from 'lucide-react';
/**
* ShareModal 컴포넌트의 Props 정의
* @interface ShareModalProps
* @property {string} videoUrl - 공유할 비디오 파일의 URL
* @property {string} posterUrl - (현재 사용되지 않지만, 공유 데이터에 포함될 수 있는) 포스터 이미지 URL
* @property {string} businessName - 비디오의 비즈니스 이름 (파일 이름 등에 사용)
* @property {() => void} onClose - 모달을 닫을 때 호출될 콜백 함수
*/
interface ShareModalProps {
videoUrl: string;
posterUrl: string; // 현재 사용되지 않음
businessName: string;
onClose: () => void;
}
/**
* 공유 모달 컴포넌트
* 생성된 비디오를 공유하기 위한 옵션을 제공합니다. (링크 복사, 파일 직접 공유 등)
*/
const ShareModal: React.FC<ShareModalProps> = ({ videoUrl, businessName, onClose }) => {
const [isGenerating, setIsGenerating] = useState(true); // 공유 링크 생성 중 여부
const [shareLink, setShareLink] = useState(''); // 생성된 공유 링크
const [copied, setCopied] = useState(false); // 링크 복사 성공 여부
const [canShareFile, setCanShareFile] = useState(false); // Web Share API로 파일 공유 가능한지 여부
/**
* 컴포넌트 마운트 시 공유 링크를 생성하고 Web Share API 지원 여부를 확인합니다.
*/
useEffect(() => {
// 고유 ID를 기반으로 목업 공유 링크를 생성합니다. (실제 서비스에서는 백엔드에서 생성)
const uniqueId = Math.random().toString(36).substring(2, 10);
const mockLink = `${window.location.origin}/share/${uniqueId}`; // 실제 앱에서는 호스팅된 비디오 URL이 됩니다.
// 링크 생성 시뮬레이션 (1.5초 후 완료)
const timer = setTimeout(() => {
setShareLink(mockLink);
setIsGenerating(false);
}, 1500);
// Web Share API (파일 공유) 지원 여부 확인
if (navigator.share && navigator.canShare) {
setCanShareFile(true);
}
return () => clearTimeout(timer); // 컴포넌트 언마운트 시 타이머 정리
}, []);
/**
* 공유 링크를 클립보드에 복사하는 핸들러
*/
const handleCopy = async () => {
try {
await navigator.clipboard.writeText(shareLink);
setCopied(true);
setTimeout(() => setCopied(false), 2000); // 2초 후 복사 상태 초기화
} catch (err) {
console.error('링크 복사 실패:', err);
alert("링크 복사에 실패했습니다. 수동으로 복사해주세요.");
}
};
/**
* 네이티브 웹 공유 API를 사용하여 비디오 파일을 직접 공유하는 핸들러
* Instagram, KakaoTalk 등 모바일 앱으로 직접 공유할 때 유용합니다.
*/
const handleNativeShare = async () => {
try {
// 비디오 Blob URL을 File 객체로 변환하여 공유 데이터에 포함시킵니다.
const response = await fetch(videoUrl);
const blob = await response.blob();
// 파일 이름은 업체명과 광고로 구성
const file = new File([blob], `${businessName.replace(/\s+/g, '_')}_광고.mp4`, { type: 'video/mp4' });
const shareData = {
title: `${businessName} AI 광고 영상`,
text: 'BizVibe로 제작된 AI 음악 비디오 광고를 확인해보세요!',
files: [file] // 공유할 파일 배열
};
// 파일 공유가 가능한지 다시 확인 후 공유
if (navigator.canShare(shareData)) {
await navigator.share(shareData);
} else {
// 파일 공유가 지원되지 않을 경우, 텍스트와 링크만 공유하는 폴백
await navigator.share({
title: shareData.title,
text: shareData.text,
url: shareLink
});
}
} catch (err) {
console.error('공유 중 오류 발생:', err);
alert("공유 기능 사용 중 오류가 발생했습니다.");
}
};
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-sm animate-in fade-in duration-200"> {/* 모달 배경 및 페이드인 애니메이션 */}
<div className="relative w-full max-w-md mx-4 bg-[#1a1a1d] border border-purple-500/30 rounded-2xl shadow-2xl overflow-hidden">
{/* 모달 헤더 */}
<div className="p-6 border-b border-white/10 flex items-center justify-between bg-white/5">
<h3 className="text-xl font-bold text-white flex items-center gap-2">
<Share2 className="w-5 h-5 text-purple-400" />
</h3>
<button
onClick={onClose} //
className="p-2 rounded-full hover:bg-white/10 text-gray-400 hover:text-white transition-colors"
>
<X className="w-5 h-5" />
</button>
</div>
<div className="p-6 space-y-6">
{isGenerating ? (
// 링크 생성 중일 때 로딩 스피너 표시
<div className="text-center py-8 space-y-4">
<div className="relative w-16 h-16 mx-auto">
<div className="absolute inset-0 border-4 border-purple-500/30 rounded-full"></div>
<div className="absolute inset-0 border-4 border-t-purple-500 rounded-full animate-spin"></div>
</div>
<p className="text-gray-300 animate-pulse"> ...</p>
</div>
) : (
<>
{/* 고유 링크 섹션 */}
<div className="space-y-2">
<label className="text-xs font-bold text-gray-400 uppercase tracking-wider flex items-center gap-1">
<LinkIcon className="w-3 h-3" />
</label>
<div className="flex items-center gap-2 p-2 bg-black/50 rounded-xl border border-gray-700">
<input
type="text"
readOnly //
value={shareLink}
className="flex-1 bg-transparent text-sm text-purple-300 outline-none font-mono"
/>
<button
onClick={handleCopy} //
className={`p-2 rounded-lg transition-all ${
copied
? 'bg-green-500/20 text-green-400' // 복사 완료 시 초록색 강조
: 'bg-white/10 hover:bg-white/20 text-white'
}`}
>
{copied ? <Check className="w-4 h-4" /> : <Copy className="w-4 h-4" />} {/* 복사 아이콘 변경 */}
</button>
</div>
<p className="text-[10px] text-gray-500">
* . .
</p>
</div>
{/* 네이티브 공유 섹션 */}
{canShareFile && (
<button
onClick={handleNativeShare} //
className="w-full py-4 bg-gradient-to-r from-purple-600 to-pink-600 hover:from-purple-500 hover:to-pink-500 text-white font-bold rounded-xl shadow-lg shadow-purple-900/30 transform transition-all hover:scale-[1.02] flex items-center justify-center gap-3"
>
<Smartphone className="w-5 h-5" />
(Instagram, Kakao )
</button>
)}
{/* 소셜 아이콘 목업 (클릭 시 새 탭 열림) */}
<div className="grid grid-cols-3 gap-3 pt-2">
{['Twitter', 'Facebook', 'LinkedIn'].map((platform) => (
<button
key={platform}
className="py-3 rounded-xl bg-white/5 hover:bg-white/10 border border-white/5 text-gray-400 hover:text-white text-xs font-medium transition-all"
onClick={() => window.open(`https://twitter.com/intent/tweet?text=${encodeURIComponent('BizVibe로 만든 AI 음악 비디오를 확인해보세요! ' + shareLink)}`, '_blank')} //
>
{platform}
</button>
))}
</div>
</>
)}
</div>
</div>
</div>
);
};
export default ShareModal;