import { useEffect, useRef, useState } from 'react' import { useNavigate } from 'react-router-dom' import { useOnboarding } from '../onboarding/OnboardingContext' import { useThreadsAccount } from '../threads/ThreadsAccountContext' import { threadsAccountConnected, threadsAccountLabel, threadsAccountStatusLabel, } from '../lib/threadsAccount' import { ApiError } from '../api/client' import { Button, ErrorText, Field, Input } from './ui' import { AcIcon } from './AcIcon' function AccountAvatar({ connected, size = 'md', }: { connected: boolean size?: 'sm' | 'md' }) { const iconWrap = size === 'sm' ? '!h-7 !w-7' : '!h-8 !w-8' return ( ) } export function AccountSwitcher() { const navigate = useNavigate() const { accounts, activeAccountId, activeAccount, loading, switchAccount, createAccount } = useThreadsAccount() const { hasAccounts, refresh: refreshOnboarding } = useOnboarding() const [open, setOpen] = useState(false) const [panel, setPanel] = useState<'list' | 'create'>('list') const [creating, setCreating] = useState(false) const [displayName, setDisplayName] = useState('') const [error, setError] = useState('') const rootRef = useRef(null) useEffect(() => { if (!open) return const onPointer = (event: MouseEvent) => { if (!rootRef.current?.contains(event.target as Node)) { setOpen(false) setPanel('list') } } const onKey = (event: KeyboardEvent) => { if (event.key === 'Escape') { if (panel === 'create') { setPanel('list') setError('') return } setOpen(false) } } document.addEventListener('mousedown', onPointer) document.addEventListener('keydown', onKey) return () => { document.removeEventListener('mousedown', onPointer) document.removeEventListener('keydown', onKey) } }, [open, panel]) const connected = threadsAccountConnected(activeAccount) const label = loading ? '載入中' : threadsAccountLabel(activeAccount) function openCreatePanel() { setPanel('create') setDisplayName('') setError('') } async function pickAccount(id: string) { setOpen(false) setPanel('list') await switchAccount(id) const snapshot = await refreshOnboarding() navigate(snapshot.isComplete ? `/threads/${id}/publish` : '/settings') } async function handleCreate() { setCreating(true) setError('') try { const created = await createAccount({ displayName: displayName.trim() || undefined, }) setOpen(false) setPanel('list') const snapshot = await refreshOnboarding() navigate(snapshot.isComplete ? `/threads/${created.id}/publish` : '/settings') } catch (e) { setError(e instanceof ApiError ? e.message : '建立帳號失敗') } finally { setCreating(false) } } return ( { setOpen((v) => !v) if (open) setPanel('list') }} className={`ac-account-trigger ${!loading && !hasAccounts ? 'ac-account-trigger--prompt' : ''}`} aria-expanded={open} aria-haspopup="listbox" aria-label={`經營帳號:${label}`} > {label} {open ? '▴' : '▾'} {open ? ( {panel === 'list' ? ( <> 經營帳號 {accounts.length ? ( accounts.map((account) => { const isActive = account.id === activeAccountId const itemConnected = threadsAccountConnected(account) return ( pickAccount(account.id)} className={`ac-account-menu-item ${isActive ? 'ac-account-menu-item--active' : ''}`} > {threadsAccountLabel(account)} {threadsAccountStatusLabel(account)} {isActive ? ( 使用中 ) : null} ) }) ) : ( 尚未建立 Threads 帳號 )} 新增經營帳號 > ) : ( { setPanel('list') setError('') }} > ← 返回 新增經營帳號 建立後進入連線設定;人設請到側欄「人設庫」建立並綁定。 setDisplayName(e.target.value)} placeholder="側欄顯示用,不填則自動命名" autoFocus /> {creating ? '建立中…' : '建立並設定連線'} )} ) : null} ) }
經營帳號
尚未建立 Threads 帳號
新增經營帳號
建立後進入連線設定;人設請到側欄「人設庫」建立並綁定。