// @ts-strict-ignore import React, { useEffect } from 'react'; import { useDispatch } from 'react-redux'; import { useLocation } from 'react-router-dom'; import { closeModal } from 'loot-core/client/actions'; import { send } from 'loot-core/src/platform/client/fetch'; import * as monthUtils from 'loot-core/src/shared/months'; import { useMetadataPref } from '../hooks/useMetadataPref'; import { useModalState } from '../hooks/useModalState'; import { ModalTitle, ModalHeader } from './common/Modal'; import { AccountAutocompleteModal } from './modals/AccountAutocompleteModal'; import { AccountMenuModal } from './modals/AccountMenuModal'; import { BudgetListModal } from './modals/BudgetListModal'; import { BudgetPageMenuModal } from './modals/BudgetPageMenuModal'; import { CategoryAutocompleteModal } from './modals/CategoryAutocompleteModal'; import { CategoryGroupMenuModal } from './modals/CategoryGroupMenuModal'; import { CategoryMenuModal } from './modals/CategoryMenuModal'; import { CloseAccountModal } from './modals/CloseAccountModal'; import { ConfirmCategoryDeleteModal } from './modals/ConfirmCategoryDeleteModal'; import { ConfirmTransactionDeleteModal } from './modals/ConfirmTransactionDeleteModal'; import { ConfirmTransactionEditModal } from './modals/ConfirmTransactionEditModal'; import { ConfirmUnlinkAccountModal } from './modals/ConfirmUnlinkAccountModal'; import { CoverModal } from './modals/CoverModal'; import { CreateAccountModal } from './modals/CreateAccountModal'; import { CreateEncryptionKeyModal } from './modals/CreateEncryptionKeyModal'; import { CreateLocalAccountModal } from './modals/CreateLocalAccountModal'; import { EditFieldModal } from './modals/EditFieldModal'; import { EditRuleModal } from './modals/EditRuleModal'; import { FixEncryptionKeyModal } from './modals/FixEncryptionKeyModal'; import { GoCardlessExternalMsgModal } from './modals/GoCardlessExternalMsgModal'; import { GoCardlessInitialiseModal } from './modals/GoCardlessInitialiseModal'; import { HoldBufferModal } from './modals/HoldBufferModal'; import { ImportTransactionsModal } from './modals/ImportTransactionsModal'; import { KeyboardShortcutModal } from './modals/KeyboardShortcutModal'; import { LoadBackupModal } from './modals/LoadBackupModal'; import { DeleteFileModal } from './modals/manager/DeleteFileModal'; import { ImportActualModal } from './modals/manager/ImportActualModal'; import { ImportModal } from './modals/manager/ImportModal'; import { ImportYNAB4Modal } from './modals/manager/ImportYNAB4Modal'; import { ImportYNAB5Modal } from './modals/manager/ImportYNAB5Modal'; import { ManageRulesModal } from './modals/ManageRulesModal'; import { MergeUnusedPayeesModal } from './modals/MergeUnusedPayeesModal'; import { NotesModal } from './modals/NotesModal'; import { PayeeAutocompleteModal } from './modals/PayeeAutocompleteModal'; import { ReportBalanceMenuModal } from './modals/ReportBalanceMenuModal'; import { ReportBudgetMenuModal } from './modals/ReportBudgetMenuModal'; import { ReportBudgetMonthMenuModal } from './modals/ReportBudgetMonthMenuModal'; import { ReportBudgetSummaryModal } from './modals/ReportBudgetSummaryModal'; import { RolloverBalanceMenuModal } from './modals/RolloverBalanceMenuModal'; import { RolloverBudgetMenuModal } from './modals/RolloverBudgetMenuModal'; import { RolloverBudgetMonthMenuModal } from './modals/RolloverBudgetMonthMenuModal'; import { RolloverBudgetSummaryModal } from './modals/RolloverBudgetSummaryModal'; import { RolloverToBudgetMenuModal } from './modals/RolloverToBudgetMenuModal'; import { ScheduledTransactionMenuModal } from './modals/ScheduledTransactionMenuModal'; import { SelectLinkedAccountsModal } from './modals/SelectLinkedAccountsModal'; import { SimpleFinInitialiseModal } from './modals/SimpleFinInitialiseModal'; import { SingleInputModal } from './modals/SingleInputModal'; import { TransferModal } from './modals/TransferModal'; import { DiscoverSchedules } from './schedules/DiscoverSchedules'; import { PostsOfflineNotification } from './schedules/PostsOfflineNotification'; import { ScheduleDetails } from './schedules/ScheduleDetails'; import { ScheduleLink } from './schedules/ScheduleLink'; import { NamespaceContext } from './spreadsheet/NamespaceContext'; export function Modals() { const location = useLocation(); const dispatch = useDispatch(); const { modalStack } = useModalState(); const [budgetId] = useMetadataPref('id'); useEffect(() => { if (modalStack.length > 0) { dispatch(closeModal()); } }, [location]); const modals = modalStack .map(({ name, options }) => { switch (name) { case 'keyboard-shortcuts': // don't show the hotkey help modal when a budget is not open return budgetId ? <KeyboardShortcutModal key={name} /> : null; case 'import-transactions': return <ImportTransactionsModal key={name} options={options} />; case 'add-account': return ( <CreateAccountModal key={name} upgradingAccountId={options?.upgradingAccountId} /> ); case 'add-local-account': return <CreateLocalAccountModal key={name} />; case 'close-account': return ( <CloseAccountModal key={name} account={options.account} balance={options.balance} canDelete={options.canDelete} /> ); case 'select-linked-accounts': return ( <SelectLinkedAccountsModal key={name} externalAccounts={options.accounts} requisitionId={options.requisitionId} syncSource={options.syncSource} /> ); case 'confirm-category-delete': return ( <ConfirmCategoryDeleteModal key={name} category={options.category} group={options.group} onDelete={options.onDelete} /> ); case 'confirm-unlink-account': return ( <ConfirmUnlinkAccountModal key={name} accountName={options.accountName} onUnlink={options.onUnlink} /> ); case 'confirm-transaction-edit': return ( <ConfirmTransactionEditModal key={name} onCancel={options.onCancel} onConfirm={options.onConfirm} confirmReason={options.confirmReason} /> ); case 'confirm-transaction-delete': return ( <ConfirmTransactionDeleteModal key={name} message={options.message} onConfirm={options.onConfirm} /> ); case 'load-backup': return ( <LoadBackupModal key={name} watchUpdates budgetId={options.budgetId} backupDisabled={false} /> ); case 'manage-rules': return <ManageRulesModal key={name} payeeId={options?.payeeId} />; case 'edit-rule': return ( <EditRuleModal key={name} defaultRule={options.rule} onSave={options.onSave} /> ); case 'merge-unused-payees': return ( <MergeUnusedPayeesModal key={name} payeeIds={options.payeeIds} targetPayeeId={options.targetPayeeId} /> ); case 'gocardless-init': return ( <GoCardlessInitialiseModal key={name} onSuccess={options.onSuccess} /> ); case 'simplefin-init': return ( <SimpleFinInitialiseModal key={name} onSuccess={options.onSuccess} /> ); case 'gocardless-external-msg': return ( <GoCardlessExternalMsgModal key={name} onMoveExternal={options.onMoveExternal} onClose={() => { options.onClose?.(); send('gocardless-poll-web-token-stop'); }} onSuccess={options.onSuccess} /> ); case 'create-encryption-key': return <CreateEncryptionKeyModal key={name} options={options} />; case 'fix-encryption-key': return <FixEncryptionKeyModal key={name} options={options} />; case 'edit-field': return ( <EditFieldModal key={name} name={options.name} onSubmit={options.onSubmit} onClose={options.onClose} /> ); case 'category-autocomplete': return ( <CategoryAutocompleteModal key={name} autocompleteProps={{ value: null, onSelect: options.onSelect, categoryGroups: options.categoryGroups, showHiddenCategories: options.showHiddenCategories, }} month={options.month} onClose={options.onClose} /> ); case 'account-autocomplete': return ( <AccountAutocompleteModal key={name} autocompleteProps={{ value: null, onSelect: options.onSelect, includeClosedAccounts: options.includeClosedAccounts, }} onClose={options.onClose} /> ); case 'payee-autocomplete': return ( <PayeeAutocompleteModal key={name} autocompleteProps={{ value: null, onSelect: options.onSelect, }} onClose={options.onClose} /> ); case 'new-category': return ( <SingleInputModal key={name} name={name} Header={props => ( <ModalHeader {...props} title={<ModalTitle title="New Category" shrinkOnOverflow />} /> )} inputPlaceholder="Category name" buttonText="Add" onValidate={options.onValidate} onSubmit={options.onSubmit} /> ); case 'new-category-group': return ( <SingleInputModal key={name} name={name} Header={props => ( <ModalHeader {...props} title={ <ModalTitle title="New Category Group" shrinkOnOverflow /> } /> )} inputPlaceholder="Category group name" buttonText="Add" onValidate={options.onValidate} onSubmit={options.onSubmit} /> ); case 'rollover-budget-summary': return ( <NamespaceContext.Provider key={name} value={monthUtils.sheetForMonth(options.month)} > <RolloverBudgetSummaryModal key={name} month={options.month} onBudgetAction={options.onBudgetAction} /> </NamespaceContext.Provider> ); case 'report-budget-summary': return <ReportBudgetSummaryModal key={name} month={options.month} />; case 'schedule-edit': return ( <ScheduleDetails key={name} id={options?.id || null} transaction={options?.transaction || null} /> ); case 'schedule-link': return ( <ScheduleLink key={name} transactionIds={options?.transactionIds} getTransaction={options?.getTransaction} accountName={options?.accountName} onScheduleLinked={options?.onScheduleLinked} /> ); case 'schedules-discover': return <DiscoverSchedules key={name} />; case 'schedule-posts-offline-notification': return <PostsOfflineNotification key={name} />; case 'account-menu': return ( <AccountMenuModal key={name} accountId={options.accountId} onSave={options.onSave} onEditNotes={options.onEditNotes} onCloseAccount={options.onCloseAccount} onReopenAccount={options.onReopenAccount} onClose={options.onClose} /> ); case 'category-menu': return ( <CategoryMenuModal key={name} categoryId={options.categoryId} onSave={options.onSave} onEditNotes={options.onEditNotes} onDelete={options.onDelete} onToggleVisibility={options.onToggleVisibility} onClose={options.onClose} /> ); case 'rollover-budget-menu': return ( <NamespaceContext.Provider key={name} value={monthUtils.sheetForMonth(options.month)} > <RolloverBudgetMenuModal categoryId={options.categoryId} onUpdateBudget={options.onUpdateBudget} onCopyLastMonthAverage={options.onCopyLastMonthAverage} onSetMonthsAverage={options.onSetMonthsAverage} onApplyBudgetTemplate={options.onApplyBudgetTemplate} /> </NamespaceContext.Provider> ); case 'report-budget-menu': return ( <NamespaceContext.Provider key={name} value={monthUtils.sheetForMonth(options.month)} > <ReportBudgetMenuModal categoryId={options.categoryId} onUpdateBudget={options.onUpdateBudget} onCopyLastMonthAverage={options.onCopyLastMonthAverage} onSetMonthsAverage={options.onSetMonthsAverage} onApplyBudgetTemplate={options.onApplyBudgetTemplate} /> </NamespaceContext.Provider> ); case 'category-group-menu': return ( <CategoryGroupMenuModal key={name} groupId={options.groupId} onSave={options.onSave} onAddCategory={options.onAddCategory} onEditNotes={options.onEditNotes} onSaveNotes={options.onSaveNotes} onDelete={options.onDelete} onToggleVisibility={options.onToggleVisibility} onClose={options.onClose} /> ); case 'notes': return ( <NotesModal key={name} id={options.id} name={options.name} onSave={options.onSave} /> ); case 'rollover-balance-menu': return ( <NamespaceContext.Provider key={name} value={monthUtils.sheetForMonth(options.month)} > <RolloverBalanceMenuModal categoryId={options.categoryId} onCarryover={options.onCarryover} onTransfer={options.onTransfer} onCover={options.onCover} /> </NamespaceContext.Provider> ); case 'rollover-summary-to-budget-menu': return ( <NamespaceContext.Provider key={name} value={monthUtils.sheetForMonth(options.month)} > <RolloverToBudgetMenuModal onTransfer={options.onTransfer} onCover={options.onCover} onHoldBuffer={options.onHoldBuffer} onResetHoldBuffer={options.onResetHoldBuffer} /> </NamespaceContext.Provider> ); case 'hold-buffer': return ( <NamespaceContext.Provider key={name} value={monthUtils.sheetForMonth(options.month)} > <HoldBufferModal month={options.month} onSubmit={options.onSubmit} /> </NamespaceContext.Provider> ); case 'report-balance-menu': return ( <NamespaceContext.Provider key={name} value={monthUtils.sheetForMonth(options.month)} > <ReportBalanceMenuModal categoryId={options.categoryId} onCarryover={options.onCarryover} /> </NamespaceContext.Provider> ); case 'transfer': return ( <TransferModal key={name} title={options.title} month={options.month} amount={options.amount} onSubmit={options.onSubmit} showToBeBudgeted={options.showToBeBudgeted} /> ); case 'cover': return ( <CoverModal key={name} title={options.title} month={options.month} showToBeBudgeted={options.showToBeBudgeted} category={options.category} onSubmit={options.onSubmit} /> ); case 'scheduled-transaction-menu': return ( <ScheduledTransactionMenuModal key={name} transactionId={options.transactionId} onPost={options.onPost} onSkip={options.onSkip} /> ); case 'budget-page-menu': return ( <BudgetPageMenuModal key={name} onAddCategoryGroup={options.onAddCategoryGroup} onToggleHiddenCategories={options.onToggleHiddenCategories} onSwitchBudgetFile={options.onSwitchBudgetFile} /> ); case 'rollover-budget-month-menu': return ( <NamespaceContext.Provider key={name} value={monthUtils.sheetForMonth(options.month)} > <RolloverBudgetMonthMenuModal month={options.month} onBudgetAction={options.onBudgetAction} onEditNotes={options.onEditNotes} /> </NamespaceContext.Provider> ); case 'report-budget-month-menu': return ( <NamespaceContext.Provider key={name} value={monthUtils.sheetForMonth(options.month)} > <ReportBudgetMonthMenuModal month={options.month} onBudgetAction={options.onBudgetAction} onEditNotes={options.onEditNotes} /> </NamespaceContext.Provider> ); case 'budget-list': return <BudgetListModal key={name} />; case 'delete-budget': return <DeleteFileModal key={name} file={options.file} />; case 'import': return <ImportModal key={name} />; case 'import-ynab4': return <ImportYNAB4Modal key={name} />; case 'import-ynab5': return <ImportYNAB5Modal key={name} />; case 'import-actual': return <ImportActualModal key={name} />; case 'manager-load-backup': return ( <LoadBackupModal key={name} budgetId={options.budgetId} backupDisabled={true} watchUpdates={false} /> ); default: throw new Error('Unknown modal'); } }) .map((modal, idx) => ( <React.Fragment key={modalStack[idx].name}>{modal}</React.Fragment> )); // fragment needed per TS types // eslint-disable-next-line react/jsx-no-useless-fragment return <>{modals}</>; }