fix: Prevent signature alert loop when adding log events.

Stabilize dialog callbacks and dedupe signature-invalidation alerts so the UI no longer freezes after adding an event to a signed travel day.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-05-30 10:40:31 +02:00
co-authored by Cursor
parent 3849b5a2f0
commit b88ce17e1d
4 changed files with 35 additions and 16 deletions
+10 -3
View File
@@ -76,6 +76,8 @@ export default function LogEntryEditor({
}: LogEntryEditorProps) { }: LogEntryEditorProps) {
const { t, i18n } = useTranslation() const { t, i18n } = useTranslation()
const { showAlert, showConfirm } = useDialog() const { showAlert, showConfirm } = useDialog()
const showAlertRef = useRef(showAlert)
showAlertRef.current = showAlert
// General details state // General details state
const [date, setDate] = useState('') const [date, setDate] = useState('')
@@ -145,6 +147,7 @@ export default function LogEntryEditor({
const fileInputRef = useRef<HTMLInputElement | null>(null) const fileInputRef = useRef<HTMLInputElement | null>(null)
const lockedContentHashRef = useRef<string | null>(null) const lockedContentHashRef = useRef<string | null>(null)
const contentReadyRef = useRef(false) const contentReadyRef = useRef(false)
const lastSignatureAlertHashRef = useRef<string | null>(null)
const applyTrackStats = (waypoints: SavedTrack['waypoints']) => { const applyTrackStats = (waypoints: SavedTrack['waypoints']) => {
const stats = computeTrackStats(waypoints) const stats = computeTrackStats(waypoints)
@@ -252,12 +255,15 @@ export default function LogEntryEditor({
lockedContentHashRef.current = null lockedContentHashRef.current = null
setSignSkipper('') setSignSkipper('')
setSignCrew('') setSignCrew('')
void showAlert( if (lastSignatureAlertHashRef.current !== entryHash) {
lastSignatureAlertHashRef.current = entryHash
void showAlertRef.current(
t('logs.sign_cleared_re_sign'), t('logs.sign_cleared_re_sign'),
t('logs.sign_cleared_re_sign_title') t('logs.sign_cleared_re_sign_title')
) )
} }
}, [entryHash, signSkipper, signCrew, readOnly, showAlert, t]) }
}, [entryHash, signSkipper, signCrew, readOnly, t])
const confirmSignWarning = useCallback(async (): Promise<boolean> => { const confirmSignWarning = useCallback(async (): Promise<boolean> => {
return showConfirm( return showConfirm(
@@ -355,6 +361,7 @@ export default function LogEntryEditor({
setError(null) setError(null)
lockedContentHashRef.current = null lockedContentHashRef.current = null
contentReadyRef.current = false contentReadyRef.current = false
lastSignatureAlertHashRef.current = null
try { try {
if (readOnly && preloadedEntry) { if (readOnly && preloadedEntry) {
setDate(preloadedEntry.date || '') setDate(preloadedEntry.date || '')
@@ -1330,7 +1337,7 @@ export default function LogEntryEditor({
style={{ width: 'auto', padding: '10px 20px', marginLeft: 'auto', display: 'flex' }} style={{ width: 'auto', padding: '10px 20px', marginLeft: 'auto', display: 'flex' }}
> >
<Plus size={16} /> <Plus size={16} />
Add Event Entry {t('logs.add_event_btn')}
</button> </button>
</div> </div>
)} )}
+20 -10
View File
@@ -1,4 +1,4 @@
import React, { createContext, useContext, useState, useRef } from 'react' import React, { createContext, useContext, useState, useRef, useCallback, useMemo } from 'react'
interface DialogContextType { interface DialogContextType {
showAlert: (message: string, title?: string, confirmText?: string) => Promise<void> showAlert: (message: string, title?: string, confirmText?: string) => Promise<void>
@@ -25,7 +25,7 @@ export function DialogProvider({ children }: { children: React.ReactNode }) {
const resolveRef = useRef<((val: any) => void) | null>(null) const resolveRef = useRef<((val: any) => void) | null>(null)
const showAlert = (msg: string, headerTitle?: string, btnText?: string): Promise<void> => { const showAlert = useCallback((msg: string, headerTitle?: string, btnText?: string): Promise<void> => {
setMessage(msg) setMessage(msg)
setTitle(headerTitle || '') setTitle(headerTitle || '')
setType('alert') setType('alert')
@@ -35,9 +35,14 @@ export function DialogProvider({ children }: { children: React.ReactNode }) {
return new Promise<void>((resolve) => { return new Promise<void>((resolve) => {
resolveRef.current = resolve resolveRef.current = resolve
}) })
} }, [])
const showConfirm = (msg: string, headerTitle?: string, btnConfirm?: string, btnCancel?: string): Promise<boolean> => { const showConfirm = useCallback((
msg: string,
headerTitle?: string,
btnConfirm?: string,
btnCancel?: string
): Promise<boolean> => {
setMessage(msg) setMessage(msg)
setTitle(headerTitle || '') setTitle(headerTitle || '')
setType('confirm') setType('confirm')
@@ -48,26 +53,31 @@ export function DialogProvider({ children }: { children: React.ReactNode }) {
return new Promise<boolean>((resolve) => { return new Promise<boolean>((resolve) => {
resolveRef.current = resolve resolveRef.current = resolve
}) })
} }, [])
const handleConfirm = () => { const handleConfirm = useCallback(() => {
setIsOpen(false) setIsOpen(false)
if (resolveRef.current) { if (resolveRef.current) {
resolveRef.current(type === 'confirm' ? true : undefined) resolveRef.current(type === 'confirm' ? true : undefined)
resolveRef.current = null resolveRef.current = null
} }
} }, [type])
const handleCancel = () => { const handleCancel = useCallback(() => {
setIsOpen(false) setIsOpen(false)
if (resolveRef.current) { if (resolveRef.current) {
resolveRef.current(false) resolveRef.current(false)
resolveRef.current = null resolveRef.current = null
} }
} }, [])
const contextValue = useMemo(
() => ({ showAlert, showConfirm }),
[showAlert, showConfirm]
)
return ( return (
<DialogContext.Provider value={{ showAlert, showConfirm }}> <DialogContext.Provider value={contextValue}>
{children} {children}
{isOpen && ( {isOpen && (
<div className="custom-dialog-overlay" onClick={type === 'alert' ? handleConfirm : undefined}> <div className="custom-dialog-overlay" onClick={type === 'alert' ? handleConfirm : undefined}>
+1
View File
@@ -115,6 +115,7 @@
"new_entry": "Neuer Reisetag", "new_entry": "Neuer Reisetag",
"travel_details": "Reisedetails", "travel_details": "Reisedetails",
"add_event": "Neuen Logbucheintrag hinzufügen", "add_event": "Neuen Logbucheintrag hinzufügen",
"add_event_btn": "Ereignis hinzufügen",
"date": "Datum", "date": "Datum",
"day_of_travel": "Tag der Reise / Reisetag", "day_of_travel": "Tag der Reise / Reisetag",
"departure": "Start-Hafen (Reise von)", "departure": "Start-Hafen (Reise von)",
+1
View File
@@ -115,6 +115,7 @@
"new_entry": "New Travel Day", "new_entry": "New Travel Day",
"travel_details": "Travel Details", "travel_details": "Travel Details",
"add_event": "Add Event Log Record", "add_event": "Add Event Log Record",
"add_event_btn": "Add Event Entry",
"date": "Date", "date": "Date",
"day_of_travel": "Day of Travel", "day_of_travel": "Day of Travel",
"departure": "Departure Port (von)", "departure": "Departure Port (von)",