import { useState, useEffect, useCallback } from 'react' import { Cloud, ArrowRight, ExternalLink, Info, CheckCircle2, XCircle, RefreshCw } from 'lucide-react' import clsx from 'clsx' import { useI18n } from '../i18n' export default function VercelSync({ onMessage, authFetch }) { const { t } = useI18n() const [vercelToken, setVercelToken] = useState('') const [projectId, setProjectId] = useState('') const [teamId, setTeamId] = useState('') const [loading, setLoading] = useState(false) const [result, setResult] = useState(null) const [preconfig, setPreconfig] = useState(null) const [syncStatus, setSyncStatus] = useState(null) const apiFetch = authFetch || fetch const fetchSyncStatus = useCallback(async () => { try { const res = await apiFetch('/admin/vercel/status') if (res.ok) { const data = await res.json() setSyncStatus(data) } } catch (e) { console.error('Failed to fetch sync status:', e) } }, [apiFetch]) useEffect(() => { const loadPreconfig = async () => { try { const res = await apiFetch('/admin/vercel/config') if (res.ok) { const data = await res.json() setPreconfig(data) if (data.project_id) setProjectId(data.project_id) if (data.team_id) setTeamId(data.team_id) } } catch (e) { console.error('Failed to load preconfig:', e) } } loadPreconfig() fetchSyncStatus() // Poll every 15s to detect config changes const interval = setInterval(fetchSyncStatus, 15000) return () => clearInterval(interval) }, [fetchSyncStatus]) const handleSync = async () => { const tokenToUse = preconfig?.has_token && !vercelToken ? '__USE_PRECONFIG__' : vercelToken if (!tokenToUse && !preconfig?.has_token) { onMessage('error', t('vercel.tokenRequired')) return } if (!projectId) { onMessage('error', t('vercel.projectRequired')) return } setLoading(true) setResult(null) try { const res = await apiFetch('/admin/vercel/sync', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ vercel_token: tokenToUse, project_id: projectId, team_id: teamId || undefined, }), }) const data = await res.json() if (res.ok) { setResult({ ...data, success: true }) onMessage('success', data.message) fetchSyncStatus() } else { setResult({ ...data, success: false }) onMessage('error', data.detail || t('vercel.syncFailed')) } } catch (e) { onMessage('error', t('vercel.networkError')) } finally { setLoading(false) } } return (
{/* Configuration Form */}

{t('vercel.title')}

{syncStatus && (
{syncStatus.synced ? t('vercel.statusSynced') : syncStatus.has_synced_before ? t('vercel.statusNotSynced') : t('vercel.statusNeverSynced')}
)}

{t('vercel.description')}

{syncStatus?.last_sync_time && (

{t('vercel.lastSyncTime', { time: new Date(syncStatus.last_sync_time * 1000).toLocaleString() })}

)}
setVercelToken(e.target.value)} /> {preconfig?.has_token && !vercelToken && (
)}
setProjectId(e.target.value)} />

{t('vercel.projectIdHint')}

setTeamId(e.target.value)} />

{t('vercel.redeployHint')}

{/* Status & Guide */}
{result && (
{result.success ? (
) : (
)}

{result.success ? t('vercel.syncSucceeded') : t('vercel.syncFailedLabel')}

{result.message}

{result.deployment_url && ( )}
)}

{t('vercel.howItWorks')}

  • 1

    {t('vercel.steps.one')}

  • 2

    {t('vercel.steps.two')}

  • 3

    {t('vercel.steps.three')} DS2API_CONFIG_JSON

  • 4

    {t('vercel.steps.four')}

) }