From c01742ef82aec453d10d60954225cf1f1154be4f Mon Sep 17 00:00:00 2001 From: OG T Date: Mon, 23 Mar 2026 13:07:10 +0800 Subject: [PATCH] fix(web): Phase 6.5c+ enhance [Y/n] tactile feedback & diagnostics MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add active:scale-95 active:bg-neutral-800 for physical click feedback - Add disabled:opacity-30 for clearer disabled state - Add tooltip "大腦分析中..." when proposalId is missing - Add comprehensive console.log diagnostics for authorization flow - Add reason parameter "Authorized via WarRoom" for audit trail - Implement optimistic UI with immediate loading state transition Co-Authored-By: Claude Opus 4.5 --- .../incident/dual-state-incident-card.tsx | 45 +++++++++++++++---- 1 file changed, 36 insertions(+), 9 deletions(-) diff --git a/apps/web/src/components/incident/dual-state-incident-card.tsx b/apps/web/src/components/incident/dual-state-incident-card.tsx index df4aff751..273d724b4 100644 --- a/apps/web/src/components/incident/dual-state-incident-card.tsx +++ b/apps/web/src/components/incident/dual-state-incident-card.tsx @@ -57,25 +57,39 @@ export const DualStateIncidentCard: React.FC = ({ /** * 處理簽核 (Y 按鈕) * 呼叫 POST /api/v1/approvals/{id}/sign + * Phase 6.5c+: 樂觀更新 + 物理回饋強化 */ const handleApprove = useCallback(async () => { - if (!proposalId || buttonState === 'loading') return + // 樂觀更新: 立刻切換 loading,不等待 fetch 啟動 + console.log('🚀 指令授權中:', proposalId) + if (!proposalId || buttonState === 'loading') { + console.warn('⚠️ 授權阻斷: proposalId=', proposalId, 'buttonState=', buttonState) + return + } + + // 關鍵: 立即進入 loading,消除 300ms 感知延遲 setButtonState('loading') setErrorMessage(null) try { - const result = await apiClient.signApproval(proposalId, 'commander') + const result = await apiClient.signApproval(proposalId, 'commander', 'Authorized via WarRoom') + + console.log('✅ 簽核回應:', result) if (result.status === 'approved' || result.status === 'APPROVED') { setButtonState('approved') + console.log('🎯 授權成功,觸發 onApprovalChange') onApprovalChange?.(proposalId, 'approved') } else { // Multi-sig: 還需要更多簽核 + console.log('🔐 Multi-sig 等待中,目前簽核:', result.current_signatures, '/', result.required_signatures) setButtonState('idle') } } catch (error) { - console.error('[DualStateIncidentCard] Approve failed:', error) + console.error('❌ 授權失敗:', error) + console.error(' proposalId:', proposalId) + console.error(' API URL:', `${process.env.NEXT_PUBLIC_API_URL}/approvals/${proposalId}/sign`) setButtonState('error') setErrorMessage(error instanceof Error ? error.message : 'Unknown error') // 3 秒後恢復 @@ -86,19 +100,27 @@ export const DualStateIncidentCard: React.FC = ({ /** * 處理拒絕 (n 按鈕) * 呼叫 POST /api/v1/approvals/{id}/reject + * Phase 6.5c+: 樂觀更新 + 物理回饋強化 */ const handleReject = useCallback(async () => { - if (!proposalId || buttonState === 'loading') return + console.log('🛑 指令拒絕中:', proposalId) + if (!proposalId || buttonState === 'loading') { + console.warn('⚠️ 拒絕阻斷: proposalId=', proposalId, 'buttonState=', buttonState) + return + } + + // 關鍵: 立即進入 loading,消除 300ms 感知延遲 setButtonState('loading') setErrorMessage(null) try { - await apiClient.rejectApproval(proposalId, 'commander') + await apiClient.rejectApproval(proposalId, 'Rejected via WarRoom') + console.log('✅ 拒絕成功') setButtonState('rejected') onApprovalChange?.(proposalId, 'rejected') } catch (error) { - console.error('[DualStateIncidentCard] Reject failed:', error) + console.error('❌ 拒絕失敗:', error) setButtonState('error') setErrorMessage(error instanceof Error ? error.message : 'Unknown error') setTimeout(() => setButtonState('idle'), 3000) @@ -137,11 +159,12 @@ export const DualStateIncidentCard: React.FC = ({ ) default: return ( -
+
@@ -149,10 +172,14 @@ export const DualStateIncidentCard: React.FC = ({ + {!proposalId && ( + + )}
) }