diff --git a/src/components/dashboard/settings/SettingAccordion.tsx b/src/components/dashboard/settings/SettingAccordion.tsx index 4105c997..64014baf 100644 --- a/src/components/dashboard/settings/SettingAccordion.tsx +++ b/src/components/dashboard/settings/SettingAccordion.tsx @@ -3,6 +3,7 @@ import InvoiceDetail from '@/components/dashboard/settings/sections/invoice/Invo import AccountMapping from '@/components/dashboard/settings/sections/account/AccountMapping' import ProductMapping from '@/components/dashboard/settings/sections/product/ProductMapping' import Accordion from '@/components/ui/Accordion' +import ConfirmModal from '@/components/ui/ConfirmModal' import Divider from '@/components/ui/Divider' import { useInvoiceDetailSettings, @@ -39,7 +40,6 @@ export default function SettingAccordion({ const { settingState, - submitInvoiceSettings, cancelInvoiceSettings, isLoading, changeSettings, @@ -47,6 +47,10 @@ export default function SettingAccordion({ bankAccountOptions, bankAccountsError, canSave, + showBankDepositWarning, + requestInvoiceSettingsSave, + confirmBankDepositChange, + cancelBankDepositChange, } = useInvoiceDetailSettings() const { @@ -172,7 +176,7 @@ export default function SettingAccordion({ variant="primary" prefixIcon="Check" disabled={!canSave} - onClick={submitInvoiceSettings} + onClick={requestInvoiceSettingsSave} /> )} @@ -202,6 +206,13 @@ export default function SettingAccordion({ ) })} + ) } diff --git a/src/components/ui/ConfirmModal.tsx b/src/components/ui/ConfirmModal.tsx new file mode 100644 index 00000000..171037f7 --- /dev/null +++ b/src/components/ui/ConfirmModal.tsx @@ -0,0 +1,92 @@ +'use client' +import { useEffect, useId, useRef } from 'react' +import { createPortal } from 'react-dom' +import { Button } from 'copilot-design-system' + +type ConfirmModalProps = { + open: boolean + title: string + description: string + confirmLabel?: string + cancelLabel?: string + onConfirm: () => void + onCancel: () => void +} + +export default function ConfirmModal({ + open, + title, + description, + confirmLabel = 'Continue', + cancelLabel = 'Cancel', + onConfirm, + onCancel, +}: ConfirmModalProps) { + const titleId = useId() + const descId = useId() + const dialogRef = useRef(null) + + // On open, focus into the dialog; on close, restore focus to the opener. + useEffect(() => { + if (!open) return + const previouslyFocused = document.activeElement as HTMLElement | null + const buttons = dialogRef.current?.querySelectorAll('button') + buttons?.[0]?.focus() + return () => previouslyFocused?.focus() + }, [open]) + + // Escape cancels; Tab is trapped between the dialog's buttons. + useEffect(() => { + if (!open) return + const onKeyDown = (e: KeyboardEvent) => { + if (e.key === 'Escape') return onCancel() + if (e.key !== 'Tab') return + const buttons = Array.from( + dialogRef.current?.querySelectorAll('button') ?? [], + ) + if (buttons.length === 0) return + const first = buttons[0] + const last = buttons[buttons.length - 1] + if (e.shiftKey && document.activeElement === first) { + e.preventDefault() + last.focus() + } else if (!e.shiftKey && document.activeElement === last) { + e.preventDefault() + first.focus() + } + } + document.addEventListener('keydown', onKeyDown) + return () => document.removeEventListener('keydown', onKeyDown) + }, [open, onCancel]) + + if (!open) return null + + return createPortal( +
+
e.stopPropagation()} + > +

+ {title} +

+

+ {description} +

+
+
+
+
, + document.body, + ) +} diff --git a/src/hook/useSettings.ts b/src/hook/useSettings.ts index 5d0ceaf0..5c2e4e9e 100644 --- a/src/hook/useSettings.ts +++ b/src/hook/useSettings.ts @@ -442,6 +442,7 @@ export const useInvoiceDetailSettings = () => { initialInvoiceSetting, ) const [showButton, setShowButton] = useState(false) + const [showBankDepositWarning, setShowBankDepositWarning] = useState(false) const [intialSettingState, setIntialSettingState] = useState< InvoiceSettingType | undefined >() @@ -522,10 +523,29 @@ export const useInvoiceDetailSettings = () => { setSettingState(intialSettingState || initialInvoiceSetting) } + // Warn only when the bank-deposit flag actually changed vs the saved value. + const bankDepositFlagChanged = + !!intialSettingState && + settingState.bankDepositFeeFlag !== intialSettingState.bankDepositFeeFlag + + const requestInvoiceSettingsSave = () => { + if (bankDepositFlagChanged) { + setShowBankDepositWarning(true) + return + } + submitInvoiceSettings() + } + + const confirmBankDepositChange = () => { + setShowBankDepositWarning(false) + submitInvoiceSettings() + } + + const cancelBankDepositChange = () => setShowBankDepositWarning(false) + return { settingState, changeSettings, - submitInvoiceSettings, cancelInvoiceSettings, error, isLoading, @@ -533,6 +553,10 @@ export const useInvoiceDetailSettings = () => { bankAccountOptions, bankAccountsError, canSave, + showBankDepositWarning, + requestInvoiceSettingsSave, + confirmBankDepositChange, + cancelBankDepositChange, } }