video-flow-b/components/common/ShareModal.tsx
2025-10-20 19:39:22 +08:00

317 lines
9.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use client';
import React, { useState, useEffect } from 'react';
import { Modal, Button, Spin, QRCode } from 'antd';
import {
Youtube,
Instagram,
Share2,
Copy,
ExternalLink,
Loader2
} from 'lucide-react';
import { shareApiUrl } from '@/lib/env';
import TwitterCallbackModal from './TwitterCallbackModal';
interface ShareModalProps {
/** 是否显示弹框 */
visible: boolean;
/** 关闭弹框回调 */
onClose: () => void;
/** 项目信息 */
project: {
project_id: string;
name?: string;
final_video_url?: string;
final_simple_video_url?: string;
};
}
interface SharePlatform {
id: string;
name: string;
icon: React.ReactNode;
color: string;
description: string;
apiPath: string | null;
}
const sharePlatforms: SharePlatform[] = [
{
id: 'youtube',
name: 'YouTube',
icon: <Youtube className="w-6 h-6" />,
color: 'text-red-500',
description: 'Share to YouTube',
apiPath: '/api/video-share/youtube/auth/url'
},
{
id: 'tiktok',
name: 'TikTok',
icon: <Share2 className="w-6 h-6" />,
color: 'text-black',
description: 'Share to TikTok',
apiPath: '/api/video-share/tiktok/auth/url'
},
{
id: 'instagram',
name: 'Instagram',
icon: <Instagram className="w-6 h-6" />,
color: 'text-pink-500',
description: 'Share to Instagram',
apiPath: null // 开发中
},
{
id: 'reddit',
name: 'Reddit',
icon: <ExternalLink className="w-6 h-6" />,
color: 'text-orange-500',
description: 'Share to Reddit',
apiPath: '/api/video-share/reddit/auth/url'
},
{
id: 'twitter',
name: 'X (Twitter)',
icon: <ExternalLink className="w-6 h-6" />,
color: 'text-blue-400',
description: 'Share to X (Twitter)',
apiPath: '/api/video-share/x/auth/url'
},
{
id: 'copy',
name: 'Copy Link',
icon: <Copy className="w-6 h-6" />,
color: 'text-gray-400',
description: 'Copy video link to clipboard',
apiPath: null // 本地功能
}
];
export default function ShareModal({ visible, onClose, project }: ShareModalProps) {
const [loadingPlatform, setLoadingPlatform] = useState<string | null>(null);
const [twitterCallbackVisible, setTwitterCallbackVisible] = useState(false);
const [twitterCallbackParams, setTwitterCallbackParams] = useState<{
state: string;
code: string;
} | null>(null);
/**
* 检查 URL 参数并处理 Twitter 回调
*/
useEffect(() => {
if (typeof window !== 'undefined') {
const urlParams = new URLSearchParams(window.location.search);
const state = urlParams.get('state');
const code = urlParams.get('code');
// 检查是否是 Twitter 授权回调
if (state && code && window.location.pathname.includes('/api/video-share/x/auth/callback')) {
setTwitterCallbackParams({ state, code });
setTwitterCallbackVisible(true);
// 清理 URL 参数
const newUrl = window.location.pathname;
window.history.replaceState({}, document.title, newUrl);
}
}
}, []);
/**
* 获取用户ID
*/
const getUserId = () => {
const currentUser = JSON.parse(localStorage.getItem('currentUser') || '{}');
return currentUser.id || currentUser.userId;
};
/**
* 通用平台授权检查
*/
const checkPlatformAuth = async (apiPath: string, platformName: string) => {
const userId = getUserId();
if (!userId) {
console.error('用户ID不存在请先登录');
return;
}
// Twitter 平台保存项目信息到 localStorage
if (platformName.toLowerCase().includes('twitter') || platformName.toLowerCase().includes('x')) {
localStorage.setItem('currentShareProject', JSON.stringify(project));
}
const response = await fetch(`${shareApiUrl}${apiPath}?user_id=${userId}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${localStorage.getItem('token') || ''}`
}
});
const data = await response.json();
if (data.successful && data.code === 0) {
const { has_valid_token, auth_url, access_token } = data.data;
if (!has_valid_token) {
// 需要用户授权,跳转到授权页面
window.open(auth_url, '_blank');
} else {
// 用户已授权直接使用access_token进行第三方登录
console.log(`${platformName}用户已授权access_token:`, access_token);
// TODO: 实现视频上传逻辑
}
} else {
console.error(`获取${platformName}授权信息失败:`, data.message);
}
};
/**
* 处理平台分享
*/
const handlePlatformShare = async (platformId: string) => {
setLoadingPlatform(platformId);
try {
const platform = sharePlatforms.find(p => p.id === platformId);
if (!platform) return;
if (platformId === 'copy') {
await handleCopyLink();
} else if (platformId === 'instagram') {
alert('Instagram分享功能正在开发中敬请期待');
} else if (platform.apiPath) {
await checkPlatformAuth(platform.apiPath, platform.name);
} else {
console.log(`Share to ${platformId} not implemented yet`);
}
} catch (error) {
console.error(`Share to ${platformId} failed:`, error);
} finally {
setLoadingPlatform(null);
}
};
/**
* 复制链接处理
*/
const handleCopyLink = async () => {
try {
const videoUrl = project.final_video_url || project.final_simple_video_url;
if (videoUrl) {
await navigator.clipboard.writeText(videoUrl);
console.log('视频链接已复制到剪贴板');
// TODO: 显示复制成功提示
} else {
console.error('视频链接不存在');
}
} catch (error) {
console.error('复制链接失败:', error);
}
};
// 获取视频链接用于生成二维码
const getVideoUrl = () => {
return project.final_video_url || project.final_simple_video_url || '';
};
return (
<Modal
title={
<div className="flex items-center gap-2">
<Share2 className="w-5 h-5 text-white" />
<span className="text-white font-medium">Share Video</span>
</div>
}
open={visible}
onCancel={onClose}
footer={null}
width={720}
className="share-modal"
styles={{
content: {
backgroundColor: 'rgba(27, 27, 27, 0.8)',
backdropFilter: 'blur(20px)',
border: '1px solid rgba(255, 255, 255, 0.1)',
},
header: {
backgroundColor: 'rgba(27, 27, 27, 0.6)',
backdropFilter: 'blur(20px)',
borderBottom: '1px solid rgba(255, 255, 255, 0.1)',
},
body: {
backgroundColor: 'transparent',
}
}}
>
<div className="flex gap-6">
{/* 左侧:二维码区域 */}
<div className="flex-1 flex flex-col items-center justify-center space-y-4">
<div className="bg-white/90 backdrop-blur-sm p-4 rounded-lg border border-white/20">
<QRCode
value={getVideoUrl()}
size={160}
color="#000000"
bgColor="#ffffff"
errorLevel="M"
/>
</div>
</div>
{/* 右侧:平台选择区域 */}
<div className="flex-1 space-y-4">
<div className="text-center mb-4">
<h4 className="text-white font-medium text-sm mb-2">Share to Platform</h4>
<p className="text-white/60 text-xs">
Choose a platform to share your video
</p>
</div>
{/* 分享平台选择 */}
<div className="grid grid-cols-2 gap-3">
{sharePlatforms.map((platform) => (
<Button
key={platform.id}
type="text"
className={`
h-16 flex flex-col items-center justify-center gap-2
bg-black/20 backdrop-blur-sm hover:bg-white/10 border border-white/20
transition-all duration-300 rounded-lg
${loadingPlatform === platform.id ? 'opacity-50' : ''}
`}
disabled={loadingPlatform !== null}
onClick={() => handlePlatformShare(platform.id)}
>
<div className={`${platform.color} ${loadingPlatform === platform.id ? 'opacity-50' : ''}`}>
{loadingPlatform === platform.id ? (
<Loader2 className="w-6 h-6 animate-spin" />
) : (
platform.icon
)}
</div>
<span className="text-white text-xs font-medium">
{platform.name}
</span>
</Button>
))}
</div>
</div>
</div>
{/* Twitter 授权回调弹框 */}
{twitterCallbackParams && (
<TwitterCallbackModal
visible={twitterCallbackVisible}
onClose={() => {
setTwitterCallbackVisible(false);
setTwitterCallbackParams(null);
}}
project={project}
urlParams={twitterCallbackParams}
/>
)}
</Modal>
);
}