Skip to content
Snippets Groups Projects
Modals.tsx 8.28 KiB
import React, { useEffect } from 'react';
import { useSelector } from 'react-redux';
import { useLocation } from 'react-router-dom';

import { send } from 'loot-core/src/platform/client/fetch';

import { useActions } from '../hooks/useActions';
import useCategories from '../hooks/useCategories';
import useSyncServerStatus from '../hooks/useSyncServerStatus';
import { type CommonModalProps } from '../types/modals';

import BudgetSummary from './modals/BudgetSummary';
import CloseAccount from './modals/CloseAccount';
import ConfirmCategoryDelete from './modals/ConfirmCategoryDelete';
import CreateAccount from './modals/CreateAccount';
import CreateEncryptionKey from './modals/CreateEncryptionKey';
import CreateLocalAccount from './modals/CreateLocalAccount';
import EditField from './modals/EditField';
import EditRule from './modals/EditRule';
import FixEncryptionKey from './modals/FixEncryptionKey';
import GoCardlessExternalMsg from './modals/GoCardlessExternalMsg';
import GoCardlessInitialise from './modals/GoCardlessInitialise';
import ImportTransactions from './modals/ImportTransactions';
import LoadBackup from './modals/LoadBackup';
import ManageRulesModal from './modals/ManageRulesModal';
import MergeUnusedPayees from './modals/MergeUnusedPayees';
import PlaidExternalMsg from './modals/PlaidExternalMsg';
import SelectLinkedAccounts from './modals/SelectLinkedAccounts';
import DiscoverSchedules from './schedules/DiscoverSchedules';
import ScheduleDetails from './schedules/EditSchedule';
import ScheduleLink from './schedules/LinkSchedule';
import PostsOfflineNotification from './schedules/PostsOfflineNotification';

export default function Modals() {
  const modalStack = useSelector(state => state.modals.modalStack);
  const isHidden = useSelector(state => state.modals.isHidden);
  const accounts = useSelector(state => state.queries.accounts);
  const { grouped: categoryGroups, list: categories } = useCategories();
  const budgetId = useSelector(
    state => state.prefs.local && state.prefs.local.id,
  );
  const actions = useActions();
  const location = useLocation();

  useEffect(() => {
    if (modalStack.length > 0) {
      actions.closeModal();
    }
  }, [location]);

  const syncServerStatus = useSyncServerStatus();

  let modals = modalStack
    .map(({ name, options }, idx) => {
      const modalProps: CommonModalProps = {
        onClose: actions.popModal,
        onBack: actions.popModal,
        showBack: idx > 0,
        isCurrent: idx === modalStack.length - 1,
        isHidden,
        stackIndex: idx,
      };

      switch (name) {
        case 'import-transactions':
          return (
            <ImportTransactions modalProps={modalProps} options={options} />
          );

        case 'add-account':
          return (
            <CreateAccount
              modalProps={modalProps}
              syncServerStatus={syncServerStatus}
            />
          );

        case 'add-local-account':
          return (
            <CreateLocalAccount modalProps={modalProps} actions={actions} />
          );

        case 'close-account':
          return (
            <CloseAccount
              modalProps={modalProps}
              account={options.account}
              balance={options.balance}
              canDelete={options.canDelete}
              accounts={accounts.filter(acct => acct.closed === 0)}
              categoryGroups={categoryGroups}
              actions={actions}
            />
          );

        case 'select-linked-accounts':
          return (
            <SelectLinkedAccounts
              modalProps={modalProps}
              externalAccounts={options.accounts}
              requisitionId={options.requisitionId}
              localAccounts={accounts.filter(acct => acct.closed === 0)}
              actions={actions}
            />
          );

        case 'confirm-category-delete':
          return (
            <ConfirmCategoryDelete
              modalProps={modalProps}
              category={
                'category' in options &&
                categories.find(c => c.id === options.category)
              }
              group={
                'group' in options &&
                categoryGroups.find(g => g.id === options.group)
              }
              categoryGroups={categoryGroups}
              onDelete={options.onDelete}
            />
          );

        case 'load-backup':
          return (
            <LoadBackup
              watchUpdates
              budgetId={budgetId}
              modalProps={modalProps}
              actions={actions}
              backupDisabled={false}
            />
          );

        case 'manage-rules':
          return (
            <ManageRulesModal
              modalProps={modalProps}
              payeeId={options.payeeId}
            />
          );

        case 'edit-rule':
          return (
            <EditRule
              modalProps={modalProps}
              defaultRule={options.rule}
              onSave={options.onSave}
            />
          );

        case 'merge-unused-payees':
          return (
            <MergeUnusedPayees
              modalProps={modalProps}
              payeeIds={options.payeeIds}
              targetPayeeId={options.targetPayeeId}
            />
          );

        case 'plaid-external-msg':
          return (
            <PlaidExternalMsg
              modalProps={modalProps}
              onMoveExternal={options.onMoveExternal}
              onClose={() => {
                options.onClose?.();
                send('poll-web-token-stop');
              }}
              onSuccess={options.onSuccess}
            />
          );

        case 'gocardless-init':
          return (
            <GoCardlessInitialise
              modalProps={modalProps}
              onSuccess={options.onSuccess}
            />
          );

        case 'gocardless-external-msg':
          return (
            <GoCardlessExternalMsg
              modalProps={modalProps}
              onMoveExternal={options.onMoveExternal}
              onClose={() => {
                options.onClose?.();
                send('gocardless-poll-web-token-stop');
              }}
              onSuccess={options.onSuccess}
            />
          );

        case 'create-encryption-key':
          return (
            <CreateEncryptionKey
              key={name}
              modalProps={modalProps}
              actions={actions}
              options={options}
            />
          );

        case 'fix-encryption-key':
          return (
            <FixEncryptionKey
              key={name}
              modalProps={modalProps}
              actions={actions}
              options={options}
            />
          );

        case 'edit-field':
          return (
            <EditField
              key={name}
              modalProps={modalProps}
              name={options.name}
              onSubmit={options.onSubmit}
            />
          );

        case 'budget-summary':
          return (
            <BudgetSummary
              key={name}
              modalProps={modalProps}
              month={options.month}
            />
          );

        case 'schedule-edit':
          return (
            <ScheduleDetails
              key={name}
              modalProps={modalProps}
              id={options?.id || null}
              actions={actions}
            />
          );

        case 'schedule-link':
          return (
            <ScheduleLink
              key={name}
              modalProps={modalProps}
              actions={actions}
              transactionIds={options?.transactionIds}
            />
          );

        case 'schedules-discover':
          return (
            <DiscoverSchedules
              key={name}
              modalProps={modalProps}
              actions={actions}
            />
          );

        case 'schedule-posts-offline-notification':
          return (
            <PostsOfflineNotification
              key={name}
              modalProps={modalProps}
              actions={actions}
            />
          );

        default:
          console.error('Unknown modal:', name);
          return null;
      }
    })
    .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}</>;
}