Files
kapteins-daagbok/client/src/components/ModalDialog.tsx
T
elpatron b88ce17e1d 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>
2026-05-30 10:40:31 +02:00

103 lines
3.3 KiB
TypeScript

import React, { createContext, useContext, useState, useRef, useCallback, useMemo } from 'react'
interface DialogContextType {
showAlert: (message: string, title?: string, confirmText?: string) => Promise<void>
showConfirm: (message: string, title?: string, confirmText?: string, cancelText?: string) => Promise<boolean>
}
const DialogContext = createContext<DialogContextType | undefined>(undefined)
export function useDialog() {
const context = useContext(DialogContext)
if (!context) {
throw new Error('useDialog must be used within a DialogProvider')
}
return context
}
export function DialogProvider({ children }: { children: React.ReactNode }) {
const [isOpen, setIsOpen] = useState(false)
const [title, setTitle] = useState('')
const [message, setMessage] = useState('')
const [type, setType] = useState<'alert' | 'confirm'>('alert')
const [confirmLabel, setConfirmLabel] = useState('OK')
const [cancelLabel, setCancelLabel] = useState('Cancel')
const resolveRef = useRef<((val: any) => void) | null>(null)
const showAlert = useCallback((msg: string, headerTitle?: string, btnText?: string): Promise<void> => {
setMessage(msg)
setTitle(headerTitle || '')
setType('alert')
setConfirmLabel(btnText || 'OK')
setIsOpen(true)
return new Promise<void>((resolve) => {
resolveRef.current = resolve
})
}, [])
const showConfirm = useCallback((
msg: string,
headerTitle?: string,
btnConfirm?: string,
btnCancel?: string
): Promise<boolean> => {
setMessage(msg)
setTitle(headerTitle || '')
setType('confirm')
setConfirmLabel(btnConfirm || 'Yes')
setCancelLabel(btnCancel || 'No')
setIsOpen(true)
return new Promise<boolean>((resolve) => {
resolveRef.current = resolve
})
}, [])
const handleConfirm = useCallback(() => {
setIsOpen(false)
if (resolveRef.current) {
resolveRef.current(type === 'confirm' ? true : undefined)
resolveRef.current = null
}
}, [type])
const handleCancel = useCallback(() => {
setIsOpen(false)
if (resolveRef.current) {
resolveRef.current(false)
resolveRef.current = null
}
}, [])
const contextValue = useMemo(
() => ({ showAlert, showConfirm }),
[showAlert, showConfirm]
)
return (
<DialogContext.Provider value={contextValue}>
{children}
{isOpen && (
<div className="custom-dialog-overlay" onClick={type === 'alert' ? handleConfirm : undefined}>
<div className="custom-dialog-card glass scale-in" onClick={(e) => e.stopPropagation()}>
{title && <h3 className="custom-dialog-title">{title}</h3>}
<p className="custom-dialog-message">{message}</p>
<div className="custom-dialog-actions">
{type === 'confirm' && (
<button type="button" className="btn secondary" onClick={handleCancel} style={{ width: 'auto', padding: '8px 20px', margin: 0 }}>
{cancelLabel}
</button>
)}
<button type="button" className="btn primary" onClick={handleConfirm} style={{ width: 'auto', minWidth: '80px', padding: '8px 20px', margin: 0 }}>
{confirmLabel}
</button>
</div>
</div>
</div>
)}
</DialogContext.Provider>
)
}