import { useEffect, useState } from 'react' import { AlertCircle, CheckCircle2 } from 'lucide-react' import { Button } from '@/shared/ui' import { listBranches } from '@/entities/repo' import { checkMergeStatus, createPull } from '@/entities/pull-request' import type { MergeStatus, Pull } from '@/entities/pull-request' import { Modal, modalInputClass, modalLabelClass, modalTextareaClass } from '@/shared/ui' export interface PullFormModalProps { space: string repo: string defaultBase: string onClose: () => void onCreated: (pull: Pull) => void } /** 新建合并请求:选分支、填标题描述,并实时探测 base…head 的可合并性。 */ export function PullFormModal({ space, repo, defaultBase, onClose, onCreated }: PullFormModalProps) { const [branches, setBranches] = useState([]) const [head, setHead] = useState('') const [base, setBase] = useState(defaultBase) const [title, setTitle] = useState('') const [body, setBody] = useState('') const [draft, setDraft] = useState(false) const [busy, setBusy] = useState(false) const [error, setError] = useState(null) const [probe, setProbe] = useState<{ key: string; value: MergeStatus | 'failed' } | null>(null) useEffect(() => { let active = true void listBranches(space, repo, 200) .then(page => { if (!active) return const names = page.items.map(item => item.name) setBranches(names) setHead(prev => prev || names.find(name => name !== defaultBase) || names[0] || '') }) .catch(() => undefined) return () => { active = false } }, [space, repo, defaultBase]) const pairKey = head && base && head !== base ? `${space}/${repo}:${base}...${head}` : '' // 'pending' is derived (pair selected, no result yet) so the effect only // writes state when the probe settles. const status: MergeStatus | 'pending' | null = !pairKey ? null : probe?.key === pairKey ? probe.value === 'failed' ? null : probe.value : 'pending' useEffect(() => { if (!pairKey) return let active = true const timer = setTimeout(() => { void checkMergeStatus(space, repo, base, head) .then(next => { if (active) setProbe({ key: pairKey, value: next }) }) .catch(() => { if (active) setProbe({ key: pairKey, value: 'failed' }) }) }, 400) return () => { active = false clearTimeout(timer) } }, [space, repo, base, head, pairKey]) async function submit() { if (!title.trim() || !head || !base) { setError('标题、源分支和目标分支都不能为空。') return } if (head === base) { setError('源分支和目标分支不能相同。') return } setBusy(true) setError(null) try { const pull = await createPull(space, repo, { title: title.trim(), head_branch: head, base_branch: base, body: body || null, draft, }) onCreated(pull) } catch (failure) { setError(failure instanceof Error ? failure.message : '创建失败') setBusy(false) } } return ( } >
{head && base && head !== base ? ( status === 'pending' ? (

正在比较 {base} … {head}…

) : status?.mergeable ? (

可以快进合并,无冲突{status.fast_forwardable ? '' : '(将生成合并提交)'}

) : status ? (

存在 {status.conflicts.length} 处冲突 {status.conflicts.length ? {status.conflicts.slice(0, 5).join('、')}{status.conflicts.length > 5 ? ' …' : ''} : null} ,仍可创建 PR,但需要先解决冲突才能合并。

) : null ) : null}