forked from 77media/video-flow
352 lines
10 KiB
TypeScript
352 lines
10 KiB
TypeScript
'use client';
|
||
|
||
import React, { useState, useEffect } from 'react';
|
||
import { Modal, Button, Spin, QRCode, message } from 'antd';
|
||
import {
|
||
Youtube,
|
||
Instagram,
|
||
Share2,
|
||
Copy,
|
||
ExternalLink,
|
||
Loader2
|
||
} from 'lucide-react';
|
||
import { shareApiUrl } from '@/lib/env';
|
||
import { VideoShareForm } from './VideoShareForm';
|
||
|
||
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 [showShareForm, setShowShareForm] = useState(false);
|
||
const [authError, setAuthError] = useState<string | null>(null);
|
||
const [selectedPlatform, setSelectedPlatform] = useState<string>('');
|
||
|
||
/**
|
||
* 监听 URL 参数变化,处理授权回调
|
||
*/
|
||
useEffect(() => {
|
||
if (!visible) return;
|
||
|
||
const urlParams = new URLSearchParams(window.location.search);
|
||
const platform = urlParams.get('platform');
|
||
const status = urlParams.get('status');
|
||
const error = urlParams.get('error');
|
||
|
||
if (platform && status) {
|
||
// 统一转换成小写
|
||
const normalizedPlatform = platform.toLowerCase();
|
||
setSelectedPlatform(normalizedPlatform);
|
||
|
||
if (status === 'success') {
|
||
setAuthError(null);
|
||
setShowShareForm(true);
|
||
message.success(`${platform} 授权成功!`);
|
||
} else if (status === 'error') {
|
||
setAuthError(error || '授权失败,请重试');
|
||
setShowShareForm(true);
|
||
message.error(`${platform} 授权失败`);
|
||
}
|
||
|
||
// 清理 URL 参数
|
||
const newUrl = window.location.pathname;
|
||
window.history.replaceState({}, '', newUrl);
|
||
}
|
||
}, [visible]);
|
||
|
||
/**
|
||
* 获取用户ID
|
||
*/
|
||
const getUserId = () => {
|
||
const currentUser = JSON.parse(localStorage.getItem('currentUser') || '{}');
|
||
return currentUser.id || currentUser.userId;
|
||
};
|
||
|
||
/**
|
||
* 通用平台授权检查
|
||
*/
|
||
const checkPlatformAuth = async (apiPath: string, platformName: string, platformId: string) => {
|
||
const userId = getUserId();
|
||
|
||
if (!userId) {
|
||
message.error('用户ID不存在,请先登录');
|
||
return;
|
||
}
|
||
|
||
const response = await fetch(`${shareApiUrl}${apiPath}?user_id=${userId}`, {
|
||
method: 'GET',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'Authorization': `Bearer ${localStorage.getItem('token') || ''}`,
|
||
'ngrok-skip-browser-warning': 'true'
|
||
}
|
||
});
|
||
|
||
const data = await response.json();
|
||
|
||
if (data.successful && data.code === 0) {
|
||
const { has_valid_token, auth_url } = data.data;
|
||
|
||
if (!has_valid_token) {
|
||
// 需要用户授权,跳转到授权页面
|
||
window.open(auth_url, '_blank');
|
||
message.info('请在新窗口完成授权');
|
||
} else {
|
||
// 用户已授权,直接显示上传表单
|
||
setSelectedPlatform(platformId);
|
||
setAuthError(null);
|
||
setShowShareForm(true);
|
||
message.success(`${platformName} 已授权,可以直接上传视频`);
|
||
}
|
||
} else {
|
||
message.error(data.message || `获取${platformName}授权信息失败`);
|
||
}
|
||
};
|
||
|
||
/**
|
||
* 处理平台分享
|
||
*/
|
||
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') {
|
||
message.info('Instagram分享功能正在开发中,敬请期待!');
|
||
} else if (platform.apiPath) {
|
||
await checkPlatformAuth(platform.apiPath, platform.name, platformId);
|
||
} else {
|
||
console.log(`Share to ${platformId} not implemented yet`);
|
||
}
|
||
} catch (error) {
|
||
console.error(`Share to ${platformId} failed:`, error);
|
||
message.error(`操作失败:${error instanceof Error ? error.message : '未知错误'}`);
|
||
} 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);
|
||
message.success('视频链接已复制到剪贴板');
|
||
} else {
|
||
message.error('视频链接不存在');
|
||
}
|
||
} catch (error) {
|
||
console.error('复制链接失败:', error);
|
||
message.error('复制链接失败,请手动复制');
|
||
}
|
||
};
|
||
|
||
// 获取视频链接用于生成二维码
|
||
const getVideoUrl = () => {
|
||
return project.final_video_url || project.final_simple_video_url || '';
|
||
};
|
||
|
||
/**
|
||
* 处理视频分享提交成功
|
||
*/
|
||
const handleVideoShare = (videoUrl: string, videoName: string) => {
|
||
// 上传成功后的处理
|
||
setShowShareForm(false);
|
||
setSelectedPlatform('');
|
||
setAuthError(null);
|
||
|
||
// 延迟关闭主对话框,让用户看到成功提示
|
||
setTimeout(() => {
|
||
onClose();
|
||
}, 1000);
|
||
};
|
||
|
||
/**
|
||
* 关闭分享表单
|
||
*/
|
||
const handleCancelShareForm = () => {
|
||
setShowShareForm(false);
|
||
setAuthError(null);
|
||
setSelectedPlatform('');
|
||
};
|
||
|
||
return (
|
||
<Modal
|
||
title={
|
||
<div className="flex items-center gap-2">
|
||
<Share2 className="w-5 h-5 text-white" />
|
||
<span className="text-white font-medium">
|
||
{showShareForm ? `分享到 ${selectedPlatform}` : 'Share Video'}
|
||
</span>
|
||
</div>
|
||
}
|
||
open={visible}
|
||
onCancel={onClose}
|
||
footer={null}
|
||
width={showShareForm ? 500 : 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',
|
||
}
|
||
}}
|
||
>
|
||
{showShareForm ? (
|
||
/* 显示视频分享表单 */
|
||
<VideoShareForm
|
||
onCancel={handleCancelShareForm}
|
||
onShare={handleVideoShare}
|
||
error={authError}
|
||
platform={selectedPlatform}
|
||
videoUrl={getVideoUrl()}
|
||
videoName={project.name || ''}
|
||
/>
|
||
) : (
|
||
/* 显示平台选择界面 */
|
||
<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>
|
||
)}
|
||
</Modal>
|
||
);
|
||
}
|