From 4633412407e045eeb0218993a671adfabf1f2ad6 Mon Sep 17 00:00:00 2001 From: JianMiau Date: Mon, 17 Aug 2026 11:33:16 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8E=A8=E9=80=81=E5=88=B0=20LINE=20=E6=94=B9?= =?UTF-8?q?=E7=82=BA=E5=85=A9=E6=AE=B5=E5=BC=8F=E7=A2=BA=E8=AA=8D=EF=BC=8C?= =?UTF-8?q?=E9=81=BF=E5=85=8D=E8=AA=A4=E8=A7=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 根本原因: 「推送到 LINE」按鈕與「上傳資料」「讀取指定日期」並排,單擊即送出,容易手滑把配對結果直接推進 LINE 群組,且推出去無法收回。 影響: - 第一次按只會把按鈕切換成橘色「確認推送到 LINE」並提示,第二次按才真的送出 - 6 秒內沒按第二次會自動取消並提示 - 重新產生配對、讀取指定日期、載入範例名單、更改目標日期時一律解除確認狀態,避免殘留後誤觸 修法: 新增 linePushArmed 狀態與逾時 effect,pushToLine 未確認時先進入確認狀態;各動作函式與日期輸入同步重設;App.css 新增 .is-armed 橘色脈動樣式。 Co-Authored-By: Claude Fable 5 --- src/App.css | 17 +++++++++++++++++ src/App.tsx | 36 +++++++++++++++++++++++++++++++++--- 2 files changed, 50 insertions(+), 3 deletions(-) diff --git a/src/App.css b/src/App.css index b6663d8..f293b4a 100644 --- a/src/App.css +++ b/src/App.css @@ -260,6 +260,23 @@ outline-offset: 2px; } +.upload-button.is-armed { + background: linear-gradient(135deg, #c96a1c, #e6892e); + box-shadow: 0 16px 28px rgba(201, 106, 28, 0.28); + animation: armed-pulse 1.2s ease-in-out infinite; +} + +@keyframes armed-pulse { + 0%, + 100% { + box-shadow: 0 16px 28px rgba(201, 106, 28, 0.28); + } + + 50% { + box-shadow: 0 16px 34px rgba(201, 106, 28, 0.5); + } +} + .upload-button:disabled { opacity: 0.55; cursor: not-allowed; diff --git a/src/App.tsx b/src/App.tsx index 7424037..9b17dd8 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -37,6 +37,7 @@ const STORAGE_KEYS = { const PLACEHOLDER_NAME = '那個' const TOTAL_ROUNDS = 3 +const LINE_PUSH_CONFIRM_TIMEOUT_MS = 6000 const defaultAreaA = ['建喵', '柏威', '景涵', 'Gary', '昱翔'] const defaultAreaB = ['小念', '玟瑄', 'RuRu', '肉肉', '蓉蓉'] @@ -54,6 +55,20 @@ function App() { const [actionState, setActionState] = useState('idle') const [actionMessage, setActionMessage] = useState('') const [showTestBadge, setShowTestBadge] = useState(false) + const [linePushArmed, setLinePushArmed] = useState(false) + + useEffect(() => { + if (!linePushArmed) { + return + } + + const timer = window.setTimeout(() => { + setLinePushArmed(false) + setActionState('idle') + setActionMessage('已逾時取消推送,需要時請再按一次「推送到 LINE」。') + }, LINE_PUSH_CONFIRM_TIMEOUT_MS) + return () => window.clearTimeout(timer) + }, [linePushArmed]) useEffect(() => { window.localStorage.setItem(STORAGE_KEYS.areaA, areaAInput) @@ -94,6 +109,7 @@ function App() { })) setResults(nextResults) + setLinePushArmed(false) setError('') setActionState('idle') setActionMessage('已產生配對,請按「上傳資料」。') @@ -147,6 +163,7 @@ function App() { return } + setLinePushArmed(false) setActionState('loading') setActionMessage('讀取指定日期資料中...') @@ -181,6 +198,15 @@ function App() { return } + if (!linePushArmed) { + setLinePushArmed(true) + setActionState('idle') + setActionMessage('再按一次「確認推送到 LINE」才會真的送出,6 秒內沒按會自動取消。') + return + } + + setLinePushArmed(false) + try { const timeKey = convertDateToKey(targetDate) setActionState('sharing') @@ -201,6 +227,7 @@ function App() { setAreaAInput(defaultAreaA.join('\n')) setAreaBInput(defaultAreaB.join('\n')) setResults([]) + setLinePushArmed(false) setError('') setActionState('idle') setActionMessage('') @@ -261,7 +288,10 @@ function App() { setTargetDate(event.target.value)} + onChange={(event) => { + setTargetDate(event.target.value) + setLinePushArmed(false) + }} /> ) : null}