131 lines
4.3 KiB
TypeScript
131 lines
4.3 KiB
TypeScript
|
|
import { useEffect, useState } from 'react'
|
|||
|
|
import { api, ApiError } from '../api/client'
|
|||
|
|
import {
|
|||
|
|
connectionModeDescription,
|
|||
|
|
connectionModeLabel,
|
|||
|
|
connectionReadyLabel,
|
|||
|
|
} from '../lib/connectionMode'
|
|||
|
|
import type { ThreadsAccountConnectionData } from '../types/api'
|
|||
|
|
import { useOnboarding } from '../onboarding/OnboardingContext'
|
|||
|
|
import { AcLink, Badge, ChoiceCard, ErrorText, SectionTitle, SuccessText } from './ui'
|
|||
|
|
|
|||
|
|
type AccountConnectionModeProps = {
|
|||
|
|
accountId: string
|
|||
|
|
connectionsPath: string
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function AccountConnectionMode({ accountId, connectionsPath }: AccountConnectionModeProps) {
|
|||
|
|
const { refresh: refreshOnboarding } = useOnboarding()
|
|||
|
|
const [connection, setConnection] = useState<ThreadsAccountConnectionData | null>(null)
|
|||
|
|
const [loading, setLoading] = useState(true)
|
|||
|
|
const [busy, setBusy] = useState(false)
|
|||
|
|
const [error, setError] = useState('')
|
|||
|
|
const [message, setMessage] = useState('')
|
|||
|
|
|
|||
|
|
const load = async () => {
|
|||
|
|
if (!accountId) return
|
|||
|
|
setLoading(true)
|
|||
|
|
setError('')
|
|||
|
|
try {
|
|||
|
|
const data = await api.get<ThreadsAccountConnectionData>(
|
|||
|
|
`/api/v1/threads-accounts/${encodeURIComponent(accountId)}/connection`,
|
|||
|
|
{ auth: true },
|
|||
|
|
)
|
|||
|
|
setConnection(data)
|
|||
|
|
} catch (e) {
|
|||
|
|
setError(e instanceof ApiError ? e.message : '載入連線設定失敗')
|
|||
|
|
setConnection(null)
|
|||
|
|
} finally {
|
|||
|
|
setLoading(false)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
useEffect(() => {
|
|||
|
|
load().catch(() => undefined)
|
|||
|
|
}, [accountId])
|
|||
|
|
|
|||
|
|
const saveDevMode = async (devMode: boolean) => {
|
|||
|
|
if (!accountId) return
|
|||
|
|
setBusy(true)
|
|||
|
|
setError('')
|
|||
|
|
setMessage('')
|
|||
|
|
try {
|
|||
|
|
const data = await api.patch<ThreadsAccountConnectionData>(
|
|||
|
|
`/api/v1/threads-accounts/${encodeURIComponent(accountId)}/connection`,
|
|||
|
|
{ dev_mode: devMode },
|
|||
|
|
{ auth: true },
|
|||
|
|
)
|
|||
|
|
setConnection(data)
|
|||
|
|
setMessage(devMode ? '已切換為開發模式(爬蟲)' : '已切換為正式模式(API)')
|
|||
|
|
await refreshOnboarding()
|
|||
|
|
} catch (e) {
|
|||
|
|
setError(e instanceof ApiError ? e.message : '儲存失敗')
|
|||
|
|
} finally {
|
|||
|
|
setBusy(false)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const prefs = connection?.prefs
|
|||
|
|
const devMode = !!prefs?.dev_mode
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<div className="grid gap-4">
|
|||
|
|
<div className="flex flex-wrap items-start justify-between gap-3">
|
|||
|
|
<div className="grid gap-2">
|
|||
|
|
<SectionTitle>連線模式</SectionTitle>
|
|||
|
|
<p className="text-sm leading-relaxed text-ink-secondary">
|
|||
|
|
決定此帳號走 Threads 官方 API,或本機爬蟲。OAuth、Chrome 同步等細節在{' '}
|
|||
|
|
<AcLink to={connectionsPath}>連線設定</AcLink>。
|
|||
|
|
</p>
|
|||
|
|
</div>
|
|||
|
|
{connection ? (
|
|||
|
|
<div className="flex flex-wrap gap-2">
|
|||
|
|
<Badge tone={devMode ? 'warning' : 'brand'}>{connectionModeLabel(devMode)}</Badge>
|
|||
|
|
<Badge
|
|||
|
|
tone={
|
|||
|
|
devMode
|
|||
|
|
? connection.browser_connected
|
|||
|
|
? 'success'
|
|||
|
|
: 'warning'
|
|||
|
|
: connection.api_connected
|
|||
|
|
? 'success'
|
|||
|
|
: 'warning'
|
|||
|
|
}
|
|||
|
|
>
|
|||
|
|
{connectionReadyLabel(devMode, connection.browser_connected, connection.api_connected)}
|
|||
|
|
</Badge>
|
|||
|
|
</div>
|
|||
|
|
) : null}
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{loading ? (
|
|||
|
|
<p className="text-sm text-muted">載入連線模式…</p>
|
|||
|
|
) : prefs ? (
|
|||
|
|
<>
|
|||
|
|
<p className="text-sm text-ink-secondary">{connectionModeDescription(devMode)}</p>
|
|||
|
|
<div className="grid gap-3 sm:grid-cols-2">
|
|||
|
|
<ChoiceCard
|
|||
|
|
title="正式模式(API)"
|
|||
|
|
hint="搜尋、發文、留言走官方 API"
|
|||
|
|
active={!devMode}
|
|||
|
|
disabled={busy || !devMode}
|
|||
|
|
onClick={() => saveDevMode(false)}
|
|||
|
|
/>
|
|||
|
|
<ChoiceCard
|
|||
|
|
title="開發模式(爬蟲)"
|
|||
|
|
hint="本機除錯用,需同步 Chrome Session"
|
|||
|
|
active={devMode}
|
|||
|
|
disabled={busy || devMode}
|
|||
|
|
onClick={() => saveDevMode(true)}
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
</>
|
|||
|
|
) : (
|
|||
|
|
<p className="text-sm text-ink-secondary">無法載入連線設定。</p>
|
|||
|
|
)}
|
|||
|
|
|
|||
|
|
<ErrorText message={error} />
|
|||
|
|
<SuccessText message={message} />
|
|||
|
|
</div>
|
|||
|
|
)
|
|||
|
|
}
|