video-flow-b/components/common/TwitterCallbackModal.tsx
2025-10-20 19:59:08 +08:00

224 lines
6.9 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, 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`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${localStorage.getItem('token') || ''}`
},
body: JSON.stringify({
user_id: userId,
state: urlParams.state,
code: urlParams.code,
})
});
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>
);
}