推送到 LINE 改為兩段式確認,避免誤觸
根本原因: 「推送到 LINE」按鈕與「上傳資料」「讀取指定日期」並排,單擊即送出,容易手滑把配對結果直接推進 LINE 群組,且推出去無法收回。 影響: - 第一次按只會把按鈕切換成橘色「確認推送到 LINE」並提示,第二次按才真的送出 - 6 秒內沒按第二次會自動取消並提示 - 重新產生配對、讀取指定日期、載入範例名單、更改目標日期時一律解除確認狀態,避免殘留後誤觸 修法: 新增 linePushArmed 狀態與逾時 effect,pushToLine 未確認時先進入確認狀態;各動作函式與日期輸入同步重設;App.css 新增 .is-armed 橘色脈動樣式。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+17
@@ -260,6 +260,23 @@
|
|||||||
outline-offset: 2px;
|
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 {
|
.upload-button:disabled {
|
||||||
opacity: 0.55;
|
opacity: 0.55;
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
|
|||||||
+33
-3
@@ -37,6 +37,7 @@ const STORAGE_KEYS = {
|
|||||||
|
|
||||||
const PLACEHOLDER_NAME = '那個'
|
const PLACEHOLDER_NAME = '那個'
|
||||||
const TOTAL_ROUNDS = 3
|
const TOTAL_ROUNDS = 3
|
||||||
|
const LINE_PUSH_CONFIRM_TIMEOUT_MS = 6000
|
||||||
|
|
||||||
const defaultAreaA = ['建喵', '柏威', '景涵', 'Gary', '昱翔']
|
const defaultAreaA = ['建喵', '柏威', '景涵', 'Gary', '昱翔']
|
||||||
const defaultAreaB = ['小念', '玟瑄', 'RuRu', '肉肉', '蓉蓉']
|
const defaultAreaB = ['小念', '玟瑄', 'RuRu', '肉肉', '蓉蓉']
|
||||||
@@ -54,6 +55,20 @@ function App() {
|
|||||||
const [actionState, setActionState] = useState<ActionState>('idle')
|
const [actionState, setActionState] = useState<ActionState>('idle')
|
||||||
const [actionMessage, setActionMessage] = useState('')
|
const [actionMessage, setActionMessage] = useState('')
|
||||||
const [showTestBadge, setShowTestBadge] = useState(false)
|
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(() => {
|
useEffect(() => {
|
||||||
window.localStorage.setItem(STORAGE_KEYS.areaA, areaAInput)
|
window.localStorage.setItem(STORAGE_KEYS.areaA, areaAInput)
|
||||||
@@ -94,6 +109,7 @@ function App() {
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
setResults(nextResults)
|
setResults(nextResults)
|
||||||
|
setLinePushArmed(false)
|
||||||
setError('')
|
setError('')
|
||||||
setActionState('idle')
|
setActionState('idle')
|
||||||
setActionMessage('已產生配對,請按「上傳資料」。')
|
setActionMessage('已產生配對,請按「上傳資料」。')
|
||||||
@@ -147,6 +163,7 @@ function App() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setLinePushArmed(false)
|
||||||
setActionState('loading')
|
setActionState('loading')
|
||||||
setActionMessage('讀取指定日期資料中...')
|
setActionMessage('讀取指定日期資料中...')
|
||||||
|
|
||||||
@@ -181,6 +198,15 @@ function App() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!linePushArmed) {
|
||||||
|
setLinePushArmed(true)
|
||||||
|
setActionState('idle')
|
||||||
|
setActionMessage('再按一次「確認推送到 LINE」才會真的送出,6 秒內沒按會自動取消。')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setLinePushArmed(false)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const timeKey = convertDateToKey(targetDate)
|
const timeKey = convertDateToKey(targetDate)
|
||||||
setActionState('sharing')
|
setActionState('sharing')
|
||||||
@@ -201,6 +227,7 @@ function App() {
|
|||||||
setAreaAInput(defaultAreaA.join('\n'))
|
setAreaAInput(defaultAreaA.join('\n'))
|
||||||
setAreaBInput(defaultAreaB.join('\n'))
|
setAreaBInput(defaultAreaB.join('\n'))
|
||||||
setResults([])
|
setResults([])
|
||||||
|
setLinePushArmed(false)
|
||||||
setError('')
|
setError('')
|
||||||
setActionState('idle')
|
setActionState('idle')
|
||||||
setActionMessage('')
|
setActionMessage('')
|
||||||
@@ -261,7 +288,10 @@ function App() {
|
|||||||
<input
|
<input
|
||||||
type="date"
|
type="date"
|
||||||
value={targetDate}
|
value={targetDate}
|
||||||
onChange={(event) => setTargetDate(event.target.value)}
|
onChange={(event) => {
|
||||||
|
setTargetDate(event.target.value)
|
||||||
|
setLinePushArmed(false)
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
<button
|
<button
|
||||||
@@ -282,12 +312,12 @@ function App() {
|
|||||||
</button>
|
</button>
|
||||||
{results.length > 0 ? (
|
{results.length > 0 ? (
|
||||||
<button
|
<button
|
||||||
className="primary-button upload-button"
|
className={`primary-button upload-button${linePushArmed ? ' is-armed' : ''}`}
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => void pushToLine()}
|
onClick={() => void pushToLine()}
|
||||||
disabled={actionState === 'saving' || actionState === 'loading'}
|
disabled={actionState === 'saving' || actionState === 'loading'}
|
||||||
>
|
>
|
||||||
推送到 LINE
|
{linePushArmed ? '確認推送到 LINE' : '推送到 LINE'}
|
||||||
</button>
|
</button>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user