forked from 77media/video-flow
219 lines
6.9 KiB
TypeScript
219 lines
6.9 KiB
TypeScript
'use client';
|
||
|
||
import React, { useState, useEffect } from 'react';
|
||
import { Modal, Button, Spin, message } from 'antd';
|
||
import { CheckCircleOutlined, CloseCircleOutlined } from '@ant-design/icons';
|
||
import { Loader2 } from 'lucide-react';
|
||
import { baseUrl } from '@/lib/env';
|
||
|
||
interface TwitterCallbackModalProps {
|
||
/** 是否显示弹框 */
|
||
visible: boolean;
|
||
/** 关闭弹框回调 */
|
||
onClose: () => void;
|
||
/** 项目信息 */
|
||
project: {
|
||
project_id: string;
|
||
name?: string;
|
||
final_video_url?: string;
|
||
final_simple_video_url?: string;
|
||
};
|
||
/** URL 参数 */
|
||
urlParams: {
|
||
state: string;
|
||
code: string;
|
||
};
|
||
}
|
||
|
||
enum CallbackStatus {
|
||
LOADING = 'loading',
|
||
SUCCESS = 'success',
|
||
FAILED = 'failed'
|
||
}
|
||
|
||
export default function TwitterCallbackModal({
|
||
visible,
|
||
onClose,
|
||
project,
|
||
urlParams
|
||
}: TwitterCallbackModalProps) {
|
||
const [callbackStatus, setCallbackStatus] = useState<CallbackStatus>(CallbackStatus.LOADING);
|
||
const [callbackData, setCallbackData] = useState<any>(null);
|
||
|
||
/**
|
||
* 处理 Twitter 授权回调
|
||
*/
|
||
const handleTwitterCallback = async () => {
|
||
try {
|
||
setCallbackStatus(CallbackStatus.LOADING);
|
||
|
||
// 从localStorage获取用户信息
|
||
const currentUser = JSON.parse(localStorage.getItem('currentUser') || '{}');
|
||
const userId = currentUser.id || currentUser.userId;
|
||
|
||
if (!userId) {
|
||
throw new Error('用户ID不存在,请先登录');
|
||
}
|
||
|
||
// 调用 Twitter 授权回调接口
|
||
const response = await fetch(`${baseUrl}/api/video-share/x/auth/callback?state=${encodeURIComponent(urlParams.state)}&code=${encodeURIComponent(urlParams.code)}`, {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'Authorization': `Bearer ${localStorage.getItem('token') || ''}`
|
||
}
|
||
});
|
||
|
||
const data = await response.json();
|
||
|
||
if (data.successful && data.code === 0) {
|
||
setCallbackData(data.data);
|
||
setCallbackStatus(CallbackStatus.SUCCESS);
|
||
message.success('Twitter 授权成功!');
|
||
} else {
|
||
throw new Error(data.message || 'Twitter 授权回调失败');
|
||
}
|
||
} catch (error) {
|
||
console.error('Twitter 授权回调失败:', error);
|
||
setCallbackStatus(CallbackStatus.FAILED);
|
||
message.error(`Twitter 授权失败: ${error instanceof Error ? error.message : '未知错误'}`);
|
||
}
|
||
};
|
||
|
||
// 组件挂载时自动处理回调
|
||
useEffect(() => {
|
||
if (visible && urlParams.state && urlParams.code) {
|
||
handleTwitterCallback();
|
||
}
|
||
}, [visible, urlParams.state, urlParams.code]);
|
||
|
||
/**
|
||
* 重新尝试授权
|
||
*/
|
||
const handleRetry = () => {
|
||
setCallbackStatus(CallbackStatus.LOADING);
|
||
handleTwitterCallback();
|
||
};
|
||
|
||
/**
|
||
* 关闭弹框
|
||
*/
|
||
const handleClose = () => {
|
||
setCallbackStatus(CallbackStatus.LOADING);
|
||
setCallbackData(null);
|
||
onClose();
|
||
};
|
||
|
||
return (
|
||
<Modal
|
||
title={
|
||
<div className="flex items-center gap-2">
|
||
<div className="w-5 h-5 rounded-full bg-blue-400 flex items-center justify-center">
|
||
<span className="text-white text-xs font-bold">X</span>
|
||
</div>
|
||
<span className="text-white font-medium">Twitter 授权回调</span>
|
||
</div>
|
||
}
|
||
open={visible}
|
||
onCancel={handleClose}
|
||
footer={null}
|
||
width={480}
|
||
className="twitter-callback-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="space-y-6">
|
||
{/* 项目信息 */}
|
||
<div className="bg-black/20 backdrop-blur-sm rounded-lg p-4 border border-white/20">
|
||
<h3 className="text-white font-medium text-sm mb-2">
|
||
分享视频: {project.name || 'Unnamed Project'}
|
||
</h3>
|
||
<p className="text-white/50 text-xs">
|
||
视频链接: {project.final_video_url || project.final_simple_video_url || 'N/A'}
|
||
</p>
|
||
</div>
|
||
|
||
{/* 回调状态显示 */}
|
||
<div className="flex flex-col items-center space-y-4">
|
||
{callbackStatus === CallbackStatus.LOADING && (
|
||
<>
|
||
<Loader2 className="w-12 h-12 animate-spin text-blue-400" />
|
||
<div className="text-center">
|
||
<p className="text-white font-medium mb-1">正在处理 Twitter 授权...</p>
|
||
<p className="text-white/60 text-sm">请稍候,正在验证授权信息</p>
|
||
</div>
|
||
</>
|
||
)}
|
||
|
||
{callbackStatus === CallbackStatus.SUCCESS && (
|
||
<>
|
||
<CheckCircleOutlined className="w-12 h-12 text-green-400" />
|
||
<div className="text-center">
|
||
<p className="text-white font-medium mb-1">授权成功!</p>
|
||
<p className="text-white/60 text-sm mb-4">
|
||
Twitter 授权已完成,可以开始分享视频
|
||
</p>
|
||
{callbackData && (
|
||
<div className="bg-green-500/10 border border-green-500/20 rounded-lg p-3 mb-4">
|
||
<p className="text-green-400 text-xs">
|
||
授权 Token: {callbackData.access_token ? '已获取' : '未获取'}
|
||
</p>
|
||
</div>
|
||
)}
|
||
<Button
|
||
type="primary"
|
||
onClick={handleClose}
|
||
className="bg-blue-500 hover:bg-blue-600 border-blue-500"
|
||
>
|
||
完成
|
||
</Button>
|
||
</div>
|
||
</>
|
||
)}
|
||
|
||
{callbackStatus === CallbackStatus.FAILED && (
|
||
<>
|
||
<CloseCircleOutlined className="w-12 h-12 text-red-400" />
|
||
<div className="text-center">
|
||
<p className="text-white font-medium mb-1">授权失败</p>
|
||
<p className="text-white/60 text-sm mb-4">
|
||
Twitter 授权处理失败,请重试
|
||
</p>
|
||
<div className="flex gap-2 justify-center">
|
||
<Button
|
||
type="default"
|
||
onClick={handleRetry}
|
||
className="bg-gray-600 hover:bg-gray-700 border-gray-600"
|
||
>
|
||
重试
|
||
</Button>
|
||
<Button
|
||
type="default"
|
||
onClick={handleClose}
|
||
className="bg-gray-600 hover:bg-gray-700 border-gray-600"
|
||
>
|
||
关闭
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
</>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</Modal>
|
||
);
|
||
}
|