Compare commits

...

5 Commits

Author SHA1 Message Date
Joel Jeremy Marquez
a61d66f23f Modal migrations 2024-10-18 23:09:51 -07:00
Joel Jeremy Marquez
c1b475777b More modals migration 2024-10-18 17:43:40 -07:00
Joel Jeremy Marquez
b1a83123f5 Modal changes 2024-10-18 05:27:18 -07:00
Joel Jeremy Marquez
71eb54e9f3 More initial changes 2024-10-18 04:58:05 -07:00
Joel Jeremy Marquez
3e2f96a32c Initial commit - unfinished 2024-10-18 04:56:34 -07:00
176 changed files with 6479 additions and 3369 deletions

View File

@@ -1,5 +1,7 @@
import { type CSSObject } from '@emotion/css/dist/declarations/src/create-instance';
import { type State } from './src/state';
// Allow images to be imported
declare module '*.png';
@@ -7,3 +9,8 @@ declare module 'react' {
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions, @typescript-eslint/no-empty-object-type
interface CSSProperties extends CSSObject {}
}
declare module 'react-redux' {
// eslint-disable-next-line @typescript-eslint/no-empty-object-type, @typescript-eslint/consistent-type-definitions
interface DefaultRootState extends State {}
}

View File

@@ -12,13 +12,6 @@ import { useTranslation } from 'react-i18next';
import { useDispatch } from 'react-redux';
import { BrowserRouter } from 'react-router-dom';
import {
closeBudget,
loadBudget,
loadGlobalPrefs,
setAppState,
sync,
} from 'loot-core/client/actions';
import { SpreadsheetProvider } from 'loot-core/client/SpreadsheetProvider';
import * as Platform from 'loot-core/src/client/platform';
import {
@@ -29,6 +22,13 @@ import {
import { useMetadataPref } from '../hooks/useMetadataPref';
import { installPolyfills } from '../polyfills';
import { ResponsiveProvider } from '../ResponsiveProvider';
import {
closeBudget,
loadBudget,
loadGlobalPrefs,
setAppState,
sync,
} from '../state/actions';
import { styles, hasHiddenScrollbars, ThemeStyle, useTheme } from '../style';
import { ExposeNavigate } from '../util/router-tools';

View File

@@ -3,8 +3,7 @@ import { Trans } from 'react-i18next';
import { useSelector } from 'react-redux';
import { useTransition, animated } from 'react-spring';
import { type State } from 'loot-core/src/client/state-types';
import { type State } from '../state';
import { theme, styles } from '../style';
import { AnimatedRefresh } from './AnimatedRefresh';

View File

@@ -10,8 +10,6 @@ import {
useHref,
} from 'react-router-dom';
import { addNotification, sync } from 'loot-core/client/actions';
import { type State } from 'loot-core/src/client/state-types';
import * as undo from 'loot-core/src/platform/client/undo';
import { useAccounts } from '../hooks/useAccounts';
@@ -19,6 +17,8 @@ import { useLocalPref } from '../hooks/useLocalPref';
import { useMetaThemeColor } from '../hooks/useMetaThemeColor';
import { useNavigate } from '../hooks/useNavigate';
import { useResponsive } from '../ResponsiveProvider';
import { type State } from '../state';
import { addNotification, sync } from '../state/actions';
import { theme } from '../style';
import { getIsOutdated, getLatestVersion } from '../util/versions';

View File

@@ -6,10 +6,9 @@ import { useLocation } from 'react-router-dom';
import { useToggle } from 'usehooks-ts';
import { pushModal } from 'loot-core/client/actions/modals';
import { useFeatureFlag } from '../hooks/useFeatureFlag';
import { SvgHelp } from '../icons/v2/Help';
import { pushModal } from '../state/actions';
import { openUrl } from '../util/router-tools';
import { Button } from './common/Button2';

View File

@@ -3,9 +3,8 @@ import React, { useState, useEffect, useRef, type CSSProperties } from 'react';
import { Trans, useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux';
import { type State } from 'loot-core/src/client/state-types';
import { useActions } from '../hooks/useActions';
import { type State } from '../state';
import { theme, styles } from '../style';
import { Button } from './common/Button2';

View File

@@ -9,8 +9,6 @@ import React, {
} from 'react';
import { useDispatch } from 'react-redux';
import { pushModal } from 'loot-core/src/client/actions/modals';
import { initiallyLoadPayees } from 'loot-core/src/client/actions/queries';
import { send } from 'loot-core/src/platform/client/fetch';
import * as undo from 'loot-core/src/platform/client/undo';
import { getNormalisedString } from 'loot-core/src/shared/normalisation';
@@ -23,6 +21,7 @@ import { useCategories } from '../hooks/useCategories';
import { usePayees } from '../hooks/usePayees';
import { useSchedules } from '../hooks/useSchedules';
import { useSelected, SelectedProvider } from '../hooks/useSelected';
import { initiallyLoadPayees, pushModal } from '../state/actions';
import { theme } from '../style';
import { Button } from './common/Button2';

View File

@@ -3,14 +3,10 @@ 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 { closeModal } from '../state/actions';
import { ModalTitle, ModalHeader } from './common/Modal';
import { AccountAutocompleteModal } from './modals/AccountAutocompleteModal';
import { AccountMenuModal } from './modals/AccountMenuModal';
import { BudgetListModal } from './modals/BudgetListModal';
@@ -67,7 +63,6 @@ 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();
@@ -84,520 +79,244 @@ export function Modals() {
const modals = modalStack
.map(({ name, options }) => {
switch (name) {
case 'goal-templates':
return budgetId ? <GoalTemplateModal key={name} /> : null;
case GoalTemplateModal.modalName:
return budgetId ? (
<GoalTemplateModal key={name} name={name} {...options} />
) : null;
case 'keyboard-shortcuts':
case KeyboardShortcutModal.modalName:
// don't show the hotkey help modal when a budget is not open
return budgetId ? <KeyboardShortcutModal key={name} /> : null;
return budgetId ? (
<KeyboardShortcutModal key={name} name={name} {...options} />
) : null;
// Must be `case ImportTransactionsModal.modalName` once component is migrated to TS
case 'import-transactions':
return <ImportTransactionsModal key={name} options={options} />;
case 'add-account':
return (
<CreateAccountModal
key={name}
upgradingAccountId={options?.upgradingAccountId}
/>
<ImportTransactionsModal key={name} name={name} {...options} />
);
case 'add-local-account':
return <CreateLocalAccountModal key={name} />;
case CreateAccountModal.modalName:
return <CreateAccountModal key={name} name={name} {...options} />;
case 'close-account':
case CreateLocalAccountModal.modalName:
return (
<CloseAccountModal
key={name}
account={options.account}
balance={options.balance}
canDelete={options.canDelete}
/>
<CreateLocalAccountModal key={name} name={name} {...options} />
);
case CloseAccountModal.modalName:
return <CloseAccountModal key={name} name={name} {...options} />;
// Must be `case SelectLinkedAccountsModal.modalName` once component is migrated to TS
case 'select-linked-accounts':
return (
<SelectLinkedAccountsModal
key={name}
externalAccounts={options.accounts}
requisitionId={options.requisitionId}
syncSource={options.syncSource}
/>
<SelectLinkedAccountsModal key={name} name={name} {...options} />
);
case 'confirm-category-delete':
case ConfirmCategoryDeleteModal.modalName:
return (
<ConfirmCategoryDeleteModal
key={name}
category={options.category}
group={options.group}
onDelete={options.onDelete}
/>
<ConfirmCategoryDeleteModal key={name} name={name} {...options} />
);
case 'confirm-unlink-account':
case ConfirmUnlinkAccountModal.modalName:
return (
<ConfirmUnlinkAccountModal
key={name}
accountName={options.accountName}
onUnlink={options.onUnlink}
/>
<ConfirmUnlinkAccountModal key={name} name={name} {...options} />
);
case 'confirm-transaction-edit':
case ConfirmTransactionEditModal.modalName:
return (
<ConfirmTransactionEditModal
key={name}
onCancel={options.onCancel}
onConfirm={options.onConfirm}
confirmReason={options.confirmReason}
/>
<ConfirmTransactionEditModal key={name} name={name} {...options} />
);
case 'confirm-transaction-delete':
case ConfirmTransactionDeleteModal.modalName:
return (
<ConfirmTransactionDeleteModal
key={name}
message={options.message}
onConfirm={options.onConfirm}
name={name}
{...options}
/>
);
case 'load-backup':
return (
<LoadBackupModal
key={name}
watchUpdates
budgetId={options.budgetId}
backupDisabled={false}
/>
);
case LoadBackupModal.modalName:
return <LoadBackupModal key={name} name={name} {...options} />;
case 'manage-rules':
return <ManageRulesModal key={name} payeeId={options?.payeeId} />;
case ManageRulesModal.modalName:
return <ManageRulesModal key={name} name={name} {...options} />;
// Must be `case EditRuleModal.modalName` once component is migrated to TS
case 'edit-rule':
return (
<EditRuleModal
key={name}
defaultRule={options.rule}
onSave={options.onSave}
/>
);
return <EditRuleModal key={name} name={name} {...options} />;
// Must be `case MergeUnusedPayeesModal.modalName` once component is migrated to TS
case 'merge-unused-payees':
return <MergeUnusedPayeesModal key={name} name={name} {...options} />;
case GoCardlessInitialiseModal.modalName:
return (
<MergeUnusedPayeesModal
key={name}
payeeIds={options.payeeIds}
targetPayeeId={options.targetPayeeId}
/>
<GoCardlessInitialiseModal key={name} name={name} {...options} />
);
case 'gocardless-init':
case SimpleFinInitialiseModal.modalName:
return (
<GoCardlessInitialiseModal
key={name}
onSuccess={options.onSuccess}
/>
<SimpleFinInitialiseModal key={name} name={name} {...options} />
);
case 'simplefin-init':
case GoCardlessExternalMsgModal.modalName:
return (
<SimpleFinInitialiseModal
key={name}
onSuccess={options.onSuccess}
/>
<GoCardlessExternalMsgModal key={name} name={name} {...options} />
);
case 'gocardless-external-msg':
case CreateEncryptionKeyModal.modalName:
return (
<GoCardlessExternalMsgModal
key={name}
onMoveExternal={options.onMoveExternal}
onClose={() => {
options.onClose?.();
send('gocardless-poll-web-token-stop');
}}
onSuccess={options.onSuccess}
/>
<CreateEncryptionKeyModal key={name} name={name} {...options} />
);
case 'create-encryption-key':
return <CreateEncryptionKeyModal key={name} options={options} />;
case 'fix-encryption-key':
return <FixEncryptionKeyModal key={name} options={options} />;
case FixEncryptionKeyModal.modalName:
return <FixEncryptionKeyModal key={name} name={name} {...options} />;
// Must be `case EditFieldModal.modalName` once component is migrated to TS
case 'edit-field':
return <EditFieldModal key={name} name={name} {...options} />;
case CategoryAutocompleteModal.modalName:
return (
<EditFieldModal
key={name}
name={options.name}
onSubmit={options.onSubmit}
onClose={options.onClose}
/>
<CategoryAutocompleteModal key={name} name={name} {...options} />
);
case 'category-autocomplete':
case AccountAutocompleteModal.modalName:
return (
<CategoryAutocompleteModal
key={name}
autocompleteProps={{
value: null,
onSelect: options.onSelect,
categoryGroups: options.categoryGroups,
showHiddenCategories: options.showHiddenCategories,
}}
month={options.month}
onClose={options.onClose}
/>
<AccountAutocompleteModal key={name} name={name} {...options} />
);
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 PayeeAutocompleteModal.modalName:
return <PayeeAutocompleteModal key={name} name={name} {...options} />;
// Create a new component for this modal
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}
/>
);
return <SingleInputModal key={name} name={name} {...options} />;
// Create a new component for this modal
case 'new-category-group':
return <SingleInputModal key={name} name={name} {...options} />;
case EnvelopeBudgetSummaryModal.modalName:
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}
/>
<EnvelopeBudgetSummaryModal key={name} name={name} {...options} />
);
case 'envelope-budget-summary':
case TrackingBudgetSummaryModal.modalName:
return (
<NamespaceContext.Provider
key={name}
value={monthUtils.sheetForMonth(options.month)}
>
<EnvelopeBudgetSummaryModal
key={name}
month={options.month}
onBudgetAction={options.onBudgetAction}
/>
</NamespaceContext.Provider>
);
case 'tracking-budget-summary':
return (
<TrackingBudgetSummaryModal key={name} month={options.month} />
<TrackingBudgetSummaryModal key={name} name={name} {...options} />
);
// Must be `case ScheduleDetails.modalName` once component is migrated to TS
case 'schedule-edit':
return (
<ScheduleDetails
key={name}
id={options?.id || null}
transaction={options?.transaction || null}
/>
);
return <ScheduleDetails key={name} name={name} {...options} />;
case 'schedule-link':
return (
<ScheduleLink
key={name}
transactionIds={options?.transactionIds}
getTransaction={options?.getTransaction}
accountName={options?.accountName}
onScheduleLinked={options?.onScheduleLinked}
/>
);
case ScheduleLink.modalName:
return <ScheduleLink key={name} name={name} {...options} />;
case 'schedules-discover':
return <DiscoverSchedules key={name} />;
case DiscoverSchedules.modalName:
return <DiscoverSchedules key={name} name={name} {...options} />;
// Must be `case PostsOfflineNotification.modalName` once component is migrated to TS
case 'schedule-posts-offline-notification':
return <PostsOfflineNotification key={name} />;
return <PostsOfflineNotification key={name} name={name} />;
case 'account-menu':
case AccountMenuModal.modalName:
return <AccountMenuModal key={name} name={name} {...options} />;
case CategoryMenuModal.modalName:
return <CategoryMenuModal key={name} name={name} {...options} />;
case EnvelopeBudgetMenuModal.modalName:
return (
<AccountMenuModal
key={name}
accountId={options.accountId}
onSave={options.onSave}
onEditNotes={options.onEditNotes}
onCloseAccount={options.onCloseAccount}
onReopenAccount={options.onReopenAccount}
onClose={options.onClose}
/>
<EnvelopeBudgetMenuModal key={name} name={name} {...options} />
);
case 'category-menu':
case TrackingBudgetMenuModal.modalName:
return (
<CategoryMenuModal
key={name}
categoryId={options.categoryId}
onSave={options.onSave}
onEditNotes={options.onEditNotes}
onDelete={options.onDelete}
onToggleVisibility={options.onToggleVisibility}
onClose={options.onClose}
/>
<TrackingBudgetMenuModal key={name} name={name} {...options} />
);
case 'envelope-budget-menu':
case CategoryGroupMenuModal.modalName:
return <CategoryGroupMenuModal key={name} name={name} {...options} />;
case NotesModal.modalName:
return <NotesModal key={name} name={name} {...options} />;
case EnvelopeBalanceMenuModal.modalName:
return (
<NamespaceContext.Provider
key={name}
value={monthUtils.sheetForMonth(options.month)}
>
<EnvelopeBudgetMenuModal
categoryId={options.categoryId}
onUpdateBudget={options.onUpdateBudget}
onCopyLastMonthAverage={options.onCopyLastMonthAverage}
onSetMonthsAverage={options.onSetMonthsAverage}
onApplyBudgetTemplate={options.onApplyBudgetTemplate}
/>
</NamespaceContext.Provider>
<EnvelopeBalanceMenuModal key={name} name={name} {...options} />
);
case 'tracking-budget-menu':
case EnvelopeToBudgetMenuModal.modalName:
return (
<NamespaceContext.Provider
key={name}
value={monthUtils.sheetForMonth(options.month)}
>
<TrackingBudgetMenuModal
categoryId={options.categoryId}
onUpdateBudget={options.onUpdateBudget}
onCopyLastMonthAverage={options.onCopyLastMonthAverage}
onSetMonthsAverage={options.onSetMonthsAverage}
onApplyBudgetTemplate={options.onApplyBudgetTemplate}
/>
</NamespaceContext.Provider>
<EnvelopeToBudgetMenuModal key={name} name={name} {...options} />
);
case 'category-group-menu':
case HoldBufferModal.modalName:
return <HoldBufferModal key={name} name={name} {...options} />;
case TrackingBalanceMenuModal.modalName:
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}
/>
<TrackingBalanceMenuModal key={name} name={name} {...options} />
);
case 'notes':
return (
<NotesModal
key={name}
id={options.id}
name={options.name}
onSave={options.onSave}
/>
);
case TransferModal.modalName:
return <TransferModal key={name} name={name} {...options} />;
case 'envelope-balance-menu':
return (
<NamespaceContext.Provider
key={name}
value={monthUtils.sheetForMonth(options.month)}
>
<EnvelopeBalanceMenuModal
categoryId={options.categoryId}
onCarryover={options.onCarryover}
onTransfer={options.onTransfer}
onCover={options.onCover}
/>
</NamespaceContext.Provider>
);
case CoverModal.modalName:
return <CoverModal key={name} name={name} {...options} />;
case 'envelope-summary-to-budget-menu':
return (
<NamespaceContext.Provider
key={name}
value={monthUtils.sheetForMonth(options.month)}
>
<EnvelopeToBudgetMenuModal
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 'tracking-balance-menu':
return (
<NamespaceContext.Provider
key={name}
value={monthUtils.sheetForMonth(options.month)}
>
<TrackingBalanceMenuModal
categoryId={options.categoryId}
onCarryover={options.onCarryover}
/>
</NamespaceContext.Provider>
);
case 'transfer':
return (
<TransferModal
key={name}
title={options.title}
categoryId={options.categoryId}
month={options.month}
amount={options.amount}
onSubmit={options.onSubmit}
showToBeBudgeted={options.showToBeBudgeted}
/>
);
case 'cover':
return (
<CoverModal
key={name}
title={options.title}
categoryId={options.categoryId}
month={options.month}
showToBeBudgeted={options.showToBeBudgeted}
onSubmit={options.onSubmit}
/>
);
case 'scheduled-transaction-menu':
case ScheduledTransactionMenuModal.modalName:
return (
<ScheduledTransactionMenuModal
key={name}
transactionId={options.transactionId}
onPost={options.onPost}
onSkip={options.onSkip}
name={name}
{...options}
/>
);
case 'budget-page-menu':
case BudgetPageMenuModal.modalName:
return <BudgetPageMenuModal key={name} name={name} {...options} />;
case EnvelopeBudgetMonthMenuModal.modalName:
return (
<BudgetPageMenuModal
key={name}
onAddCategoryGroup={options.onAddCategoryGroup}
onToggleHiddenCategories={options.onToggleHiddenCategories}
onSwitchBudgetFile={options.onSwitchBudgetFile}
/>
<EnvelopeBudgetMonthMenuModal key={name} name={name} {...options} />
);
case 'envelope-budget-month-menu':
case TrackingBudgetMonthMenuModal.modalName:
return (
<NamespaceContext.Provider
key={name}
value={monthUtils.sheetForMonth(options.month)}
>
<EnvelopeBudgetMonthMenuModal
month={options.month}
onBudgetAction={options.onBudgetAction}
onEditNotes={options.onEditNotes}
/>
</NamespaceContext.Provider>
<TrackingBudgetMonthMenuModal key={name} name={name} {...options} />
);
case 'tracking-budget-month-menu':
return (
<NamespaceContext.Provider
key={name}
value={monthUtils.sheetForMonth(options.month)}
>
<TrackingBudgetMonthMenuModal
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 'files-settings':
return <FilesSettingsModal key={name} />;
case 'confirm-change-document-dir':
case BudgetListModal.modalName:
return <BudgetListModal key={name} name={name} {...options} />;
case DeleteFileModal.modalName:
return <DeleteFileModal key={name} name={name} {...options} />;
case ImportModal.modalName:
return <ImportModal key={name} name={name} />;
case FilesSettingsModal.modalName:
return <FilesSettingsModal key={name} name={name} {...options} />;
case ConfirmChangeDocumentDirModal.modalName:
return (
<ConfirmChangeDocumentDirModal
key={name}
currentBudgetDirectory={options.currentBudgetDirectory}
newDirectory={options.newDirectory}
name={name}
{...options}
/>
);
case 'import-ynab4':
return <ImportYNAB4Modal key={name} />;
case 'import-ynab5':
return <ImportYNAB5Modal key={name} />;
case 'import-actual':
return <ImportActualModal key={name} />;
case 'out-of-sync-migrations':
return <OutOfSyncMigrationsModal key={name} />;
case ImportYNAB4Modal.modalName:
return <ImportYNAB4Modal key={name} name={name} {...options} />;
case ImportYNAB5Modal.modalName:
return <ImportYNAB5Modal key={name} name={name} {...options} />;
case ImportActualModal.modalName:
return <ImportActualModal key={name} name={name} {...options} />;
case OutOfSyncMigrationsModal.modalName:
return (
<OutOfSyncMigrationsModal key={name} name={name} {...options} />
);
default:
throw new Error('Unknown modal');

View File

@@ -10,13 +10,12 @@ import { useDispatch, useSelector } from 'react-redux';
import { css } from '@emotion/css';
import { removeNotification } from 'loot-core/client/actions';
import { type State } from 'loot-core/src/client/state-types';
import type { NotificationWithId } from 'loot-core/src/client/state-types/notifications';
import { AnimatedLoading } from '../icons/AnimatedLoading';
import { SvgDelete } from '../icons/v0';
import { useResponsive } from '../ResponsiveProvider';
import { type State } from '../state';
import { removeNotification } from '../state/actions';
import type { NotificationWithId } from '../state/notifications';
import { styles, theme } from '../style';
import { Button, ButtonWithLoading } from './common/Button2';

View File

@@ -1,10 +1,9 @@
import React from 'react';
import { useSelector } from 'react-redux';
import { type State } from 'loot-core/src/client/state-types';
import { useActions } from '../hooks/useActions';
import { SvgClose } from '../icons/v1';
import { type State } from '../state';
import { theme } from '../style';
import { Button } from './common/Button2';

View File

@@ -15,7 +15,6 @@ import { t } from 'i18next';
import { v4 as uuidv4 } from 'uuid';
import { validForTransfer } from 'loot-core/client/transfer';
import { type UndoState } from 'loot-core/server/undo';
import { useFilters } from 'loot-core/src/client/data-hooks/filters';
import {
SchedulesProvider,
@@ -45,6 +44,7 @@ import {
type TransactionEntity,
type TransactionFilterEntity,
} from 'loot-core/src/types/models';
import { type UndoState } from 'loot-core/types/server-events';
import { useAccounts } from '../../hooks/useAccounts';
import { useActions } from '../../hooks/useActions';
@@ -1857,9 +1857,9 @@ export function Account() {
const location = useLocation();
const { grouped: categoryGroups } = useCategories();
const newTransactions = useSelector(state => state.queries.newTransactions);
const newTransactions = useSelector(state => state.account.newTransactions);
const matchedTransactions = useSelector(
state => state.queries.matchedTransactions,
state => state.account.matchedTransactions,
);
const accounts = useAccounts();
const payees = usePayees();

View File

@@ -5,11 +5,10 @@ import { useParams } from 'react-router-dom';
import { t } from 'i18next';
import { unlinkAccount } from 'loot-core/client/actions';
import { authorizeBank } from '../../gocardless';
import { useAccounts } from '../../hooks/useAccounts';
import { SvgExclamationOutline } from '../../icons/v1';
import { unlinkAccount } from '../../state/actions';
import { theme } from '../../style';
import { Button } from '../common/Button2';
import { Link } from '../common/Link';

View File

@@ -16,8 +16,6 @@ import { useDispatch } from 'react-redux';
import { css, cx } from '@emotion/css';
import { createPayee } from 'loot-core/src/client/actions/queries';
import { getActivePayees } from 'loot-core/src/client/reducers/queries';
import { getNormalisedString } from 'loot-core/src/shared/normalisation';
import {
type AccountEntity,
@@ -28,6 +26,8 @@ import { useAccounts } from '../../hooks/useAccounts';
import { useCommonPayees, usePayees } from '../../hooks/usePayees';
import { SvgAdd, SvgBookmark } from '../../icons/v1';
import { useResponsive } from '../../ResponsiveProvider';
import { createPayee } from '../../state/actions';
import { getActivePayees } from '../../state/queries';
import { theme, styles } from '../../style';
import { Button } from '../common/Button';
import { TextOneLine } from '../common/TextOneLine';

View File

@@ -3,6 +3,15 @@ import React, { memo, useMemo, useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { useDispatch } from 'react-redux';
import { useSpreadsheet } from 'loot-core/src/client/SpreadsheetProvider';
import { send, listen } from 'loot-core/src/platform/client/fetch';
import * as monthUtils from 'loot-core/src/shared/months';
import { useCategories } from '../../hooks/useCategories';
import { useGlobalPref } from '../../hooks/useGlobalPref';
import { useLocalPref } from '../../hooks/useLocalPref';
import { useNavigate } from '../../hooks/useNavigate';
import { useSyncedPref } from '../../hooks/useSyncedPref';
import {
addNotification,
applyBudgetAction,
@@ -16,16 +25,7 @@ import {
pushModal,
updateCategory,
updateGroup,
} from 'loot-core/src/client/actions';
import { useSpreadsheet } from 'loot-core/src/client/SpreadsheetProvider';
import { send, listen } from 'loot-core/src/platform/client/fetch';
import * as monthUtils from 'loot-core/src/shared/months';
import { useCategories } from '../../hooks/useCategories';
import { useGlobalPref } from '../../hooks/useGlobalPref';
import { useLocalPref } from '../../hooks/useLocalPref';
import { useNavigate } from '../../hooks/useNavigate';
import { useSyncedPref } from '../../hooks/useSyncedPref';
} from '../../state/actions';
import { styles } from '../../style';
import { View } from '../common/View';
import { NamespaceContext } from '../spreadsheet/NamespaceContext';

View File

@@ -2,16 +2,6 @@ import React, { useState, useRef, type CSSProperties } from 'react';
import { Trans, useTranslation } from 'react-i18next';
import { useDispatch, useSelector } from 'react-redux';
import {
closeAndDownloadBudget,
closeAndLoadBudget,
createBudget,
downloadBudget,
getUserData,
loadAllFiles,
loadBudget,
pushModal,
} from 'loot-core/client/actions';
import {
isElectron,
isNonProductionEnvironment,
@@ -35,6 +25,16 @@ import {
} from '../../icons/v1';
import { SvgCloudUnknown, SvgKey, SvgRefreshArrow } from '../../icons/v2';
import { useResponsive } from '../../ResponsiveProvider';
import {
closeAndDownloadBudget,
closeAndLoadBudget,
createBudget,
downloadBudget,
getUserData,
loadAllFiles,
loadBudget,
pushModal,
} from '../../state/actions';
import { styles, theme } from '../../style';
import { tokens } from '../../tokens';
import { Button } from '../common/Button2';

View File

@@ -2,14 +2,9 @@ import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { Navigate, Route, Routes } from 'react-router-dom';
import {
getUserData,
loadAllFiles,
setAppState,
} from 'loot-core/client/actions';
import { useMetaThemeColor } from '../../hooks/useMetaThemeColor';
import { useResponsive } from '../../ResponsiveProvider';
import { getUserData, loadAllFiles, setAppState } from '../../state/actions';
import { theme } from '../../style';
import { tokens } from '../../tokens';
import { AppBackground } from '../AppBackground';

View File

@@ -3,10 +3,9 @@ import React, { useState } from 'react';
import { Trans, useTranslation } from 'react-i18next';
import { useDispatch } from 'react-redux';
import { createBudget } from 'loot-core/src/client/actions/budgets';
import { loggedIn } from 'loot-core/src/client/actions/user';
import { send } from 'loot-core/src/platform/client/fetch';
import { createBudget, loggedIn } from '../../../state/actions';
import { theme } from '../../../style';
import { Button } from '../../common/Button2';
import { Link } from '../../common/Link';

View File

@@ -4,11 +4,10 @@ import { Trans, useTranslation } from 'react-i18next';
import { useDispatch } from 'react-redux';
import { useParams, useSearchParams } from 'react-router-dom';
import { createBudget } from 'loot-core/src/client/actions/budgets';
import { loggedIn } from 'loot-core/src/client/actions/user';
import { send } from 'loot-core/src/platform/client/fetch';
import { AnimatedLoading } from '../../../icons/AnimatedLoading';
import { createBudget, loggedIn } from '../../../state/actions';
import { theme } from '../../../style';
import { Button, ButtonWithLoading } from '../../common/Button2';
import { BigInput } from '../../common/Input';

View File

@@ -10,16 +10,6 @@ import { useDispatch, useSelector } from 'react-redux';
import { useDebounceCallback } from 'usehooks-ts';
import {
collapseModals,
getPayees,
markAccountRead,
openAccountCloseModal,
pushModal,
reopenAccount,
syncAndDownload,
updateAccount,
} from 'loot-core/client/actions';
import {
SchedulesProvider,
useDefaultSchedulesQueryTransform,
@@ -38,6 +28,16 @@ import { useDateFormat } from '../../../hooks/useDateFormat';
import { useFailedAccounts } from '../../../hooks/useFailedAccounts';
import { useNavigate } from '../../../hooks/useNavigate';
import { usePreviewTransactions } from '../../../hooks/usePreviewTransactions';
import {
collapseModals,
getPayees,
markAccountRead,
openAccountCloseModal,
pushModal,
reopenAccount,
syncAndDownload,
updateAccount,
} from '../../../state/actions';
import { styles, theme } from '../../../style';
import { Button } from '../../common/Button2';
import { Text } from '../../common/Text';
@@ -111,7 +111,7 @@ function AccountHeader({ account }: { readonly account: AccountEntity }) {
dispatch(
pushModal('notes', {
id: `account-${id}`,
name: account.name,
title: account.name,
onSave: onSaveNotes,
}),
);

View File

@@ -1,7 +1,6 @@
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { replaceModal, syncAndDownload } from 'loot-core/src/client/actions';
import * as queries from 'loot-core/src/client/queries';
import { useAccounts } from '../../../hooks/useAccounts';
@@ -9,6 +8,7 @@ import { useFailedAccounts } from '../../../hooks/useFailedAccounts';
import { useNavigate } from '../../../hooks/useNavigate';
import { useSyncedPref } from '../../../hooks/useSyncedPref';
import { SvgAdd } from '../../../icons/v1';
import { replaceModal, syncAndDownload } from '../../../state/actions';
import { theme, styles } from '../../../style';
import { makeAmountFullStyle } from '../../budget/util';
import { Button } from '../../common/Button2';
@@ -229,7 +229,7 @@ function AccountList({
export function Accounts() {
const dispatch = useDispatch();
const accounts = useAccounts();
const updatedAccounts = useSelector(state => state.queries.updatedAccounts);
const updatedAccounts = useSelector(state => state.account.updatedAccounts);
const [_numberFormat] = useSyncedPref('numberFormat');
const numberFormat = _numberFormat || 'comma-dot';
const [hideFraction] = useSyncedPref('hideFraction');

View File

@@ -5,7 +5,6 @@ import { css } from '@emotion/css';
import { AutoTextSize } from 'auto-text-size';
import memoizeOne from 'memoize-one';
import { collapseModals, pushModal } from 'loot-core/client/actions';
import { groupById, integerToCurrency } from 'loot-core/shared/util';
import {
envelopeBudget,
@@ -31,6 +30,7 @@ import {
} from '../../../icons/v1';
import { SvgViewShow } from '../../../icons/v2';
import { useResponsive } from '../../../ResponsiveProvider';
import { collapseModals, pushModal } from '../../../state/actions';
import { theme, styles } from '../../../style';
import { BalanceWithCarryover } from '../../budget/BalanceWithCarryover';
import { makeAmountGrey, makeBalanceAmountStyle } from '../../budget/util';

View File

@@ -3,7 +3,6 @@ import { useDispatch } from 'react-redux';
import { useDebounceCallback } from 'usehooks-ts';
import { getPayees } from 'loot-core/client/actions';
import * as queries from 'loot-core/client/queries';
import { pagedQuery } from 'loot-core/client/query-helpers';
import { listen } from 'loot-core/platform/client/fetch';
@@ -13,6 +12,7 @@ import { isPreviewId } from 'loot-core/shared/transactions';
import { useDateFormat } from '../../../hooks/useDateFormat';
import { useNavigate } from '../../../hooks/useNavigate';
import { getPayees } from '../../../state/actions';
import { TextOneLine } from '../../common/TextOneLine';
import { View } from '../../common/View';
import { MobilePageHeader, Page } from '../../Page';

View File

@@ -2,6 +2,14 @@
import React, { useCallback, useEffect, useState } from 'react';
import { useDispatch } from 'react-redux';
import { useSpreadsheet } from 'loot-core/src/client/SpreadsheetProvider';
import { send, listen } from 'loot-core/src/platform/client/fetch';
import * as monthUtils from 'loot-core/src/shared/months';
import { useCategories } from '../../../hooks/useCategories';
import { useLocalPref } from '../../../hooks/useLocalPref';
import { useSyncedPref } from '../../../hooks/useSyncedPref';
import { AnimatedLoading } from '../../../icons/AnimatedLoading';
import {
applyBudgetAction,
collapseModals,
@@ -16,17 +24,10 @@ import {
updateCategory,
updateGroup,
sync,
} from 'loot-core/client/actions';
import { useSpreadsheet } from 'loot-core/src/client/SpreadsheetProvider';
import { send, listen } from 'loot-core/src/platform/client/fetch';
import * as monthUtils from 'loot-core/src/shared/months';
import { useCategories } from '../../../hooks/useCategories';
import { useLocalPref } from '../../../hooks/useLocalPref';
import { useSyncedPref } from '../../../hooks/useSyncedPref';
import { AnimatedLoading } from '../../../icons/AnimatedLoading';
} from '../../../state/actions';
import { theme } from '../../../style';
import { prewarmMonth } from '../../budget/util';
import { ModalHeader, ModalTitle } from '../../common/Modal';
import { View } from '../../common/View';
import { NamespaceContext } from '../../spreadsheet/NamespaceContext';
import { SyncRefresh } from '../../SyncRefresh';
@@ -111,6 +112,14 @@ export function Budget() {
const onOpenNewCategoryGroupModal = useCallback(() => {
dispatch(
pushModal('new-category-group', {
Header: props => (
<ModalHeader
{...props}
title={<ModalTitle title="New Category Group" shrinkOnOverflow />}
/>
),
inputPlaceholder: 'Category group name',
buttonText: 'Add',
onValidate: name => (!name ? 'Name is required.' : null),
onSubmit: async name => {
dispatch(collapseModals('budget-page-menu'));
@@ -124,6 +133,14 @@ export function Budget() {
(groupId, isIncome) => {
dispatch(
pushModal('new-category', {
Header: props => (
<ModalHeader
{...props}
title={<ModalTitle title="New Category" shrinkOnOverflow />}
/>
),
inputPlaceholder: 'Category name',
buttonText: 'Add',
onValidate: name => (!name ? 'Name is required.' : null),
onSubmit: async name => {
dispatch(collapseModals('category-group-menu'));
@@ -342,7 +359,7 @@ export function Budget() {
dispatch(
pushModal('notes', {
id,
name: group.name,
title: group.name,
onSave: onSaveNotes,
}),
);
@@ -356,7 +373,7 @@ export function Budget() {
dispatch(
pushModal('notes', {
id,
name: category.name,
title: category.name,
onSave: onSaveNotes,
}),
);
@@ -399,14 +416,12 @@ export function Budget() {
onEditNotes: onOpenCategoryNotesModal,
onDelete: onDeleteCategory,
onToggleVisibility: onToggleCategoryVisibility,
onBudgetAction,
}),
);
},
[
categories,
dispatch,
onBudgetAction,
onDeleteCategory,
onOpenCategoryNotesModal,
onSaveCategory,
@@ -428,7 +443,7 @@ export function Budget() {
dispatch(
pushModal('notes', {
id: `budget-${month}`,
name: monthUtils.format(month, 'MMMM yy'),
title: monthUtils.format(month, 'MMMM yy'),
onSave: onSaveNotes,
}),
);

View File

@@ -17,7 +17,6 @@ import {
isValid as isValidDate,
} from 'date-fns';
import { pushModal, setLastTransaction } from 'loot-core/client/actions';
import { runQuery } from 'loot-core/src/client/query-helpers';
import { send } from 'loot-core/src/platform/client/fetch';
import * as monthUtils from 'loot-core/src/shared/months';
@@ -54,6 +53,7 @@ import {
import { SvgSplit } from '../../../icons/v0';
import { SvgAdd, SvgPiggyBank, SvgTrash } from '../../../icons/v1';
import { SvgPencilWriteAlternate } from '../../../icons/v2';
import { pushModal, setLastTransaction } from '../../../state/actions';
import { styles, theme } from '../../../style';
import { Button } from '../../common/Button';
import { Text } from '../../common/Text';
@@ -628,11 +628,14 @@ const TransactionEditInner = memo(function TransactionEditInner({
case 'category':
dispatch(
pushModal('category-autocomplete', {
categoryGroups,
month: monthUtils.monthFromDate(unserializedTransaction.date),
onSelect: categoryId => {
onUpdateInner(transactionToEdit, name, categoryId);
autocompleteProps: {
value: null,
categoryGroups,
onSelect: categoryId => {
onUpdateInner(transactionToEdit, name, categoryId);
},
},
month: monthUtils.monthFromDate(unserializedTransaction.date),
onClose: () => {
onClearActiveEdit();
},
@@ -642,8 +645,11 @@ const TransactionEditInner = memo(function TransactionEditInner({
case 'account':
dispatch(
pushModal('account-autocomplete', {
onSelect: accountId => {
onUpdateInner(transactionToEdit, name, accountId);
autocompleteProps: {
value: null,
onSelect: accountId => {
onUpdateInner(transactionToEdit, name, accountId);
},
},
onClose: () => {
onClearActiveEdit();
@@ -654,8 +660,11 @@ const TransactionEditInner = memo(function TransactionEditInner({
case 'payee':
dispatch(
pushModal('payee-autocomplete', {
onSelect: payeeId => {
onUpdateInner(transactionToEdit, name, payeeId);
autocompleteProps: {
value: null,
onSelect: payeeId => {
onUpdateInner(transactionToEdit, name, payeeId);
},
},
onClose: () => {
onClearActiveEdit();
@@ -666,7 +675,7 @@ const TransactionEditInner = memo(function TransactionEditInner({
default:
dispatch(
pushModal('edit-field', {
name,
fieldName: name,
month: monthUtils.monthFromDate(unserializedTransaction.date),
onSubmit: (name, value) => {
onUpdateInner(transactionToEdit, name, value);
@@ -1278,7 +1287,7 @@ function TransactionEditUnconnected({
export const TransactionEdit = props => {
const { list: categories } = useCategories();
const payees = usePayees();
const lastTransaction = useSelector(state => state.queries.lastTransaction);
const lastTransaction = useSelector(state => state.account.lastTransaction);
const accounts = useAccounts();
const dateFormat = useDateFormat() || 'MM/dd/yyyy';

View File

@@ -8,7 +8,6 @@ import React, {
import { useDispatch } from 'react-redux';
import { Item, Section } from 'react-stately';
import { setNotificationInset } from 'loot-core/client/actions';
import { groupById, integerToCurrency } from 'loot-core/shared/util';
import * as monthUtils from 'loot-core/src/shared/months';
import { isPreviewId } from 'loot-core/src/shared/transactions';
@@ -26,6 +25,7 @@ import { useUndo } from '../../../hooks/useUndo';
import { AnimatedLoading } from '../../../icons/AnimatedLoading';
import { SvgDelete } from '../../../icons/v0';
import { SvgDotsHorizontalTriple } from '../../../icons/v1';
import { setNotificationInset } from '../../../state/actions';
import { styles, theme } from '../../../style';
import { Button } from '../../common/Button2';
import { Menu } from '../../common/Menu';

View File

@@ -68,7 +68,7 @@ export function TransactionListWithBalances({
onOpenTransaction,
onRefresh,
}) {
const newTransactions = useSelector(state => state.queries.newTransactions);
const newTransactions = useSelector(state => state.account.newTransactions);
const isNewTransaction = id => {
return newTransactions.includes(id);

View File

@@ -12,12 +12,16 @@ import {
import { View } from '../common/View';
import { SectionLabel } from '../forms';
const MODAL_NAME = 'account-autocomplete' as const;
type AccountAutocompleteModalProps = {
name: typeof MODAL_NAME;
autocompleteProps: ComponentPropsWithoutRef<typeof AccountAutocomplete>;
onClose: () => void;
onClose?: () => void;
};
export function AccountAutocompleteModal({
name = MODAL_NAME,
autocompleteProps,
onClose,
}: AccountAutocompleteModalProps) {
@@ -28,7 +32,7 @@ export function AccountAutocompleteModal({
return (
<Modal
name="account-autocomplete"
name={name}
noAnimation={!isNarrowWidth}
onClose={onClose}
containerProps={{
@@ -83,3 +87,4 @@ export function AccountAutocompleteModal({
</Modal>
);
}
AccountAutocompleteModal.modalName = MODAL_NAME;

View File

@@ -28,7 +28,10 @@ import { View } from '../common/View';
import { Notes } from '../Notes';
import { validateAccountName } from '../util/accountValidation';
const MODAL_NAME = 'account-menu' as const;
type AccountMenuModalProps = {
name: typeof MODAL_NAME;
accountId: string;
onSave: (account: AccountEntity) => void;
onCloseAccount: (accountId: string) => void;
@@ -38,6 +41,7 @@ type AccountMenuModalProps = {
};
export function AccountMenuModal({
name = MODAL_NAME,
accountId,
onSave,
onCloseAccount,
@@ -105,7 +109,7 @@ export function AccountMenuModal({
return (
<Modal
name="account-menu"
name={name}
onClose={onClose}
containerProps={{
style: {
@@ -193,6 +197,7 @@ export function AccountMenuModal({
</Modal>
);
}
AccountMenuModal.modalName = MODAL_NAME;
type AdditionalAccountMenuProps = {
account: AccountEntity;

View File

@@ -7,14 +7,20 @@ import { Text } from '../common/Text';
import { View } from '../common/View';
import { BudgetList } from '../manager/BudgetList';
export function BudgetListModal() {
const MODAL_NAME = 'budget-list' as const;
type BudgetListModalProps = {
name: typeof MODAL_NAME;
};
export function BudgetListModal({ name = MODAL_NAME }: BudgetListModalProps) {
const [id] = useMetadataPref('id');
const currentFile = useSelector(state =>
state.budgets.allFiles?.find(f => 'id' in f && f.id === id),
);
return (
<Modal name="budget-list">
<Modal name={name}>
{({ state: { close } }) => (
<>
<ModalHeader
@@ -41,3 +47,4 @@ export function BudgetListModal() {
</Modal>
);
}
BudgetListModal.modalName = MODAL_NAME;

View File

@@ -8,9 +8,16 @@ import { theme, styles } from '../../style';
import { Menu } from '../common/Menu';
import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal';
type BudgetPageMenuModalProps = ComponentPropsWithoutRef<typeof BudgetPageMenu>;
const MODAL_NAME = 'budget-page-menu' as const;
type BudgetPageMenuModalProps = ComponentPropsWithoutRef<
typeof BudgetPageMenu
> & {
name: typeof MODAL_NAME;
};
export function BudgetPageMenuModal({
name = MODAL_NAME,
onAddCategoryGroup,
onToggleHiddenCategories,
onSwitchBudgetFile,
@@ -23,7 +30,7 @@ export function BudgetPageMenuModal({
};
return (
<Modal name="budget-page-menu">
<Modal name={name}>
{({ state: { close } }) => (
<>
<ModalHeader
@@ -41,6 +48,7 @@ export function BudgetPageMenuModal({
</Modal>
);
}
BudgetPageMenuModal.modalName = MODAL_NAME;
type BudgetPageMenuProps = Omit<
ComponentPropsWithoutRef<typeof Menu>,

View File

@@ -15,13 +15,17 @@ import { View } from '../common/View';
import { SectionLabel } from '../forms';
import { NamespaceContext } from '../spreadsheet/NamespaceContext';
const MODAL_NAME = 'category-autocomplete' as const;
type CategoryAutocompleteModalProps = {
name: typeof MODAL_NAME;
autocompleteProps: ComponentPropsWithoutRef<typeof CategoryAutocomplete>;
onClose: () => void;
onClose?: () => void;
month?: string;
};
export function CategoryAutocompleteModal({
name = MODAL_NAME,
autocompleteProps,
month,
onClose,
@@ -34,7 +38,7 @@ export function CategoryAutocompleteModal({
return (
<Modal
name="category-autocomplete"
name={name}
noAnimation={!isNarrowWidth}
onClose={onClose}
containerProps={{
@@ -94,3 +98,4 @@ export function CategoryAutocompleteModal({
</Modal>
);
}
CategoryAutocompleteModal.modalName = MODAL_NAME;

View File

@@ -25,18 +25,21 @@ import { Popover } from '../common/Popover';
import { View } from '../common/View';
import { Notes } from '../Notes';
const MODAL_NAME = 'category-group-menu' as const;
type CategoryGroupMenuModalProps = {
name: typeof MODAL_NAME;
groupId: string;
onSave: (group: CategoryGroupEntity) => void;
onAddCategory: (groupId: string, isIncome: boolean) => void;
onEditNotes: (id: string) => void;
onSaveNotes: (id: string, notes: string) => void;
onDelete: (groupId: string) => void;
onToggleVisibility: (groupId: string) => void;
onClose?: () => void;
};
export function CategoryGroupMenuModal({
name = MODAL_NAME,
groupId,
onSave,
onAddCategory,
@@ -86,7 +89,7 @@ export function CategoryGroupMenuModal({
return (
<Modal
name="category-group-menu"
name={name}
onClose={onClose}
containerProps={{
style: {
@@ -168,6 +171,7 @@ export function CategoryGroupMenuModal({
</Modal>
);
}
CategoryGroupMenuModal.modalName = MODAL_NAME;
function AdditionalCategoryGroupMenu({ group, onDelete, onToggleVisibility }) {
const triggerRef = useRef(null);

View File

@@ -21,7 +21,10 @@ import { Popover } from '../common/Popover';
import { View } from '../common/View';
import { Notes } from '../Notes';
const MODAL_NAME = 'category-menu' as const;
type CategoryMenuModalProps = {
name: typeof MODAL_NAME;
categoryId: string;
onSave: (category: CategoryEntity) => void;
onEditNotes: (categoryId: string) => void;
@@ -31,6 +34,7 @@ type CategoryMenuModalProps = {
};
export function CategoryMenuModal({
name = MODAL_NAME,
categoryId,
onSave,
onEditNotes,
@@ -73,7 +77,7 @@ export function CategoryMenuModal({
return (
<Modal
name="category-menu"
name={name}
onClose={onClose}
containerProps={{
style: { height: '45vh' },
@@ -149,6 +153,7 @@ export function CategoryMenuModal({
</Modal>
);
}
CategoryMenuModal.modalName = MODAL_NAME;
function AdditionalCategoryMenu({
category,

View File

@@ -3,17 +3,17 @@ import React, { type FormEvent, useState, type CSSProperties } from 'react';
import { Form } from 'react-aria-components';
import { useDispatch } from 'react-redux';
import {
closeAccount,
forceCloseAccount,
pushModal,
} from 'loot-core/client/actions';
import { integerToCurrency } from 'loot-core/src/shared/util';
import { type AccountEntity } from 'loot-core/src/types/models';
import { useAccounts } from '../../hooks/useAccounts';
import { useCategories } from '../../hooks/useCategories';
import { useResponsive } from '../../ResponsiveProvider';
import {
closeAccount,
forceCloseAccount,
pushModal,
} from '../../state/actions';
import { styles, theme } from '../../style';
import { AccountAutocomplete } from '../autocomplete/AccountAutocomplete';
import { CategoryAutocomplete } from '../autocomplete/CategoryAutocomplete';
@@ -25,6 +25,8 @@ import { Paragraph } from '../common/Paragraph';
import { Text } from '../common/Text';
import { View } from '../common/View';
const MODAL_NAME = 'close-account' as const;
function needsCategory(
account: AccountEntity,
currentTransfer: string,
@@ -39,12 +41,14 @@ function needsCategory(
}
type CloseAccountModalProps = {
name: typeof MODAL_NAME;
account: AccountEntity;
balance: number;
canDelete: boolean;
};
export function CloseAccountModal({
name = MODAL_NAME,
account,
balance,
canDelete,
@@ -105,7 +109,7 @@ export function CloseAccountModal({
return (
<Modal
name="close-account"
name={name}
isLoading={loading}
containerProps={{ style: { width: '30vw' } }}
>
@@ -160,8 +164,11 @@ export function CloseAccountModal({
onClick: () => {
dispatch(
pushModal('account-autocomplete', {
includeClosedAccounts: false,
onSelect: onSelectAccount,
autocompleteProps: {
value: null,
includeClosedAccounts: false,
onSelect: onSelectAccount,
},
}),
);
},
@@ -198,9 +205,12 @@ export function CloseAccountModal({
onClick: () => {
dispatch(
pushModal('category-autocomplete', {
categoryGroups,
showHiddenCategories: true,
onSelect: onSelectCategory,
autocompleteProps: {
value: null,
categoryGroups,
showHiddenCategories: true,
onSelect: onSelectCategory,
},
}),
);
},
@@ -272,3 +282,4 @@ export function CloseAccountModal({
</Modal>
);
}
CloseAccountModal.modalName = MODAL_NAME;

View File

@@ -10,13 +10,17 @@ import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal';
import { Text } from '../common/Text';
import { View } from '../common/View';
const MODAL_NAME = 'confirm-category-delete' as const;
type ConfirmCategoryDeleteProps = {
category: string;
group: string;
onDelete: (categoryId: string) => void;
name: typeof MODAL_NAME;
category?: string;
group?: string;
onDelete: (id: string) => void;
};
export function ConfirmCategoryDeleteModal({
name = MODAL_NAME,
group: groupId,
category: categoryId,
onDelete,
@@ -53,10 +57,7 @@ export function ConfirmCategoryDeleteModal({
const isIncome = !!(category || group).is_income;
return (
<Modal
name="confirm-category-delete"
containerProps={{ style: { width: '30vw' } }}
>
<Modal name={name} containerProps={{ style: { width: '30vw' } }}>
{({ state: { close } }) => (
<>
<ModalHeader
@@ -144,3 +145,4 @@ export function ConfirmCategoryDeleteModal({
</Modal>
);
}
ConfirmCategoryDeleteModal.modalName = MODAL_NAME;

View File

@@ -8,12 +8,16 @@ import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal';
import { Paragraph } from '../common/Paragraph';
import { View } from '../common/View';
const MODAL_NAME = 'confirm-transaction-delete' as const;
type ConfirmTransactionDeleteProps = {
name?: typeof MODAL_NAME;
message?: string;
onConfirm: () => void;
};
export function ConfirmTransactionDeleteModal({
name = MODAL_NAME,
message = 'Are you sure you want to delete the transaction?',
onConfirm,
}: ConfirmTransactionDeleteProps) {
@@ -25,7 +29,7 @@ export function ConfirmTransactionDeleteModal({
: {};
return (
<Modal name="confirm-transaction-delete">
<Modal name={name}>
{({ state: { close } }) => (
<>
<ModalHeader
@@ -68,3 +72,4 @@ export function ConfirmTransactionDeleteModal({
</Modal>
);
}
ConfirmTransactionDeleteModal.modalName = MODAL_NAME;

View File

@@ -7,22 +7,23 @@ import { InitialFocus } from '../common/InitialFocus';
import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal';
import { View } from '../common/View';
const MODAL_NAME = 'confirm-transaction-edit' as const;
type ConfirmTransactionEditProps = {
name: typeof MODAL_NAME;
onCancel?: () => void;
onConfirm: () => void;
confirmReason: string;
};
export function ConfirmTransactionEditModal({
name = MODAL_NAME,
onCancel,
onConfirm,
confirmReason,
}: ConfirmTransactionEditProps) {
return (
<Modal
name="confirm-transaction-edit"
containerProps={{ style: { width: '30vw' } }}
>
<Modal name={name} containerProps={{ style: { width: '30vw' } }}>
{({ state: { close } }) => (
<>
<ModalHeader
@@ -109,3 +110,4 @@ export function ConfirmTransactionEditModal({
</Modal>
);
}
ConfirmTransactionEditModal.modalName = MODAL_NAME;

View File

@@ -6,20 +6,21 @@ import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal';
import { Paragraph } from '../common/Paragraph';
import { View } from '../common/View';
const MODAL_NAME = 'confirm-unlink-account' as const;
type ConfirmUnlinkAccountProps = {
name?: typeof MODAL_NAME;
accountName: string;
onUnlink: () => void;
};
export function ConfirmUnlinkAccountModal({
name = MODAL_NAME,
accountName,
onUnlink,
}: ConfirmUnlinkAccountProps) {
return (
<Modal
name="confirm-unlink-account"
containerProps={{ style: { width: '30vw' } }}
>
<Modal name={name} containerProps={{ style: { width: '30vw' } }}>
{({ state: { close } }) => (
<>
<ModalHeader
@@ -63,3 +64,4 @@ export function ConfirmUnlinkAccountModal({
</Modal>
);
}
ConfirmUnlinkAccountModal.modalName = MODAL_NAME;

View File

@@ -2,10 +2,10 @@ import React, { useCallback, useMemo, useState } from 'react';
import { Trans, useTranslation } from 'react-i18next';
import { useDispatch } from 'react-redux';
import { pushModal } from 'loot-core/client/actions';
import { type CategoryEntity } from 'loot-core/src/types/models';
import { useCategories } from '../../hooks/useCategories';
import { pushModal } from '../../state/actions';
import { styles } from '../../style';
import {
addToBeBudgetedGroup,
@@ -16,7 +16,10 @@ import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal';
import { View } from '../common/View';
import { FieldLabel, TapField } from '../mobile/MobileForms';
const MODAL_NAME = 'cover' as const;
type CoverModalProps = {
name: typeof MODAL_NAME;
title: string;
categoryId?: CategoryEntity['id'];
month: string;
@@ -25,6 +28,7 @@ type CoverModalProps = {
};
export function CoverModal({
name = MODAL_NAME,
title,
categoryId,
month,
@@ -54,11 +58,14 @@ export function CoverModal({
const onCategoryClick = useCallback(() => {
dispatch(
pushModal('category-autocomplete', {
categoryGroups,
month,
onSelect: categoryId => {
setFromCategoryId(categoryId);
autocompleteProps: {
value: null,
categoryGroups,
onSelect: categoryId => {
setFromCategoryId(categoryId);
},
},
month,
}),
);
}, [categoryGroups, dispatch, month]);
@@ -72,7 +79,7 @@ export function CoverModal({
const fromCategory = categories.find(c => c.id === fromCategoryId);
return (
<Modal name="cover">
<Modal name={name}>
{({ state: { close } }) => (
<>
<ModalHeader
@@ -111,3 +118,4 @@ export function CoverModal({
</Modal>
);
}
CoverModal.modalName = MODAL_NAME;

View File

@@ -2,7 +2,6 @@
import React, { useEffect, useRef, useState } from 'react';
import { useDispatch } from 'react-redux';
import { pushModal } from 'loot-core/client/actions';
import { send } from 'loot-core/src/platform/client/fetch';
import { authorizeBank } from '../../gocardless';
@@ -10,6 +9,7 @@ import { useGoCardlessStatus } from '../../hooks/useGoCardlessStatus';
import { useSimpleFinStatus } from '../../hooks/useSimpleFinStatus';
import { useSyncServerStatus } from '../../hooks/useSyncServerStatus';
import { SvgDotsHorizontalTriple } from '../../icons/v1';
import { pushModal } from '../../state/actions';
import { theme } from '../../style';
import { Button, ButtonWithLoading } from '../common/Button2';
import { InitialFocus } from '../common/InitialFocus';
@@ -21,11 +21,17 @@ import { Popover } from '../common/Popover';
import { Text } from '../common/Text';
import { View } from '../common/View';
type CreateAccountProps = {
const MODAL_NAME = 'add-account' as const;
type CreateAccountModalProps = {
name: typeof MODAL_NAME;
upgradingAccountId?: string;
};
export function CreateAccountModal({ upgradingAccountId }: CreateAccountProps) {
export function CreateAccountModal({
name = MODAL_NAME,
upgradingAccountId,
}: CreateAccountModalProps) {
const syncServerStatus = useSyncServerStatus();
const dispatch = useDispatch();
const [isGoCardlessSetupComplete, setIsGoCardlessSetupComplete] =
@@ -95,7 +101,7 @@ export function CreateAccountModal({ upgradingAccountId }: CreateAccountProps) {
dispatch(
pushModal('select-linked-accounts', {
accounts: newAccounts,
externalAccounts: newAccounts,
syncSource: 'simpleFin',
}),
);
@@ -180,7 +186,7 @@ export function CreateAccountModal({ upgradingAccountId }: CreateAccountProps) {
}
return (
<Modal name="add-account">
<Modal name={name}>
{({ state: { close } }) => (
<>
<ModalHeader
@@ -387,3 +393,4 @@ export function CreateAccountModal({ upgradingAccountId }: CreateAccountProps) {
</Modal>
);
}
CreateAccountModal.modalName = MODAL_NAME;

View File

@@ -5,11 +5,11 @@ import { useDispatch } from 'react-redux';
import { css } from '@emotion/css';
import { loadAllFiles, loadGlobalPrefs, sync } from 'loot-core/client/actions';
import { send } from 'loot-core/src/platform/client/fetch';
import { getCreateKeyError } from 'loot-core/src/shared/errors';
import { useResponsive } from '../../ResponsiveProvider';
import { loadAllFiles, loadGlobalPrefs, sync } from '../../state/actions';
import { styles, theme } from '../../style';
import { ButtonWithLoading } from '../common/Button2';
import { InitialFocus } from '../common/InitialFocus';
@@ -25,14 +25,16 @@ import { Paragraph } from '../common/Paragraph';
import { Text } from '../common/Text';
import { View } from '../common/View';
const MODAL_NAME = 'create-encryption-key' as const;
type CreateEncryptionKeyModalProps = {
options: {
recreate?: boolean;
};
name: typeof MODAL_NAME;
recreate?: boolean;
};
export function CreateEncryptionKeyModal({
options = {},
name = MODAL_NAME,
recreate: isRecreating,
}: CreateEncryptionKeyModalProps) {
const [password, setPassword] = useState('');
const [loading, setLoading] = useState(false);
@@ -41,8 +43,6 @@ export function CreateEncryptionKeyModal({
const { isNarrowWidth } = useResponsive();
const dispatch = useDispatch();
const isRecreating = options.recreate;
async function onCreateKey(close: () => void) {
if (password !== '' && !loading) {
setLoading(true);
@@ -65,7 +65,7 @@ export function CreateEncryptionKeyModal({
}
return (
<Modal name="create-encryption-key">
<Modal name={name}>
{({ state: { close } }) => (
<>
<ModalHeader
@@ -210,3 +210,4 @@ export function CreateEncryptionKeyModal({
</Modal>
);
}
CreateEncryptionKeyModal.modalName = MODAL_NAME;

View File

@@ -3,11 +3,11 @@ import { type FormEvent, useState } from 'react';
import { Form } from 'react-aria-components';
import { useDispatch } from 'react-redux';
import { closeModal, createAccount } from 'loot-core/client/actions';
import { toRelaxedNumber } from 'loot-core/src/shared/util';
import * as useAccounts from '../../hooks/useAccounts';
import { useNavigate } from '../../hooks/useNavigate';
import { closeModal, createAccount } from '../../state/actions';
import { theme } from '../../style';
import { Button } from '../common/Button2';
import { FormError } from '../common/FormError';
@@ -27,7 +27,15 @@ import { View } from '../common/View';
import { Checkbox } from '../forms';
import { validateAccountName } from '../util/accountValidation';
export function CreateLocalAccountModal() {
const MODAL_NAME = 'add-local-account' as const;
type CreateLocalAccountModalProps = {
name: typeof MODAL_NAME;
};
export function CreateLocalAccountModal({
name: modalName = MODAL_NAME,
}: CreateLocalAccountModalProps) {
const navigate = useNavigate();
const dispatch = useDispatch();
const accounts = useAccounts.useAccounts();
@@ -67,7 +75,7 @@ export function CreateLocalAccountModal() {
}
};
return (
<Modal name="add-local-account">
<Modal name={modalName}>
{({ state: { close } }) => (
<>
<ModalHeader
@@ -189,3 +197,4 @@ export function CreateLocalAccountModal() {
</Modal>
);
}
CreateLocalAccountModal.modalName = MODAL_NAME;

View File

@@ -15,23 +15,30 @@ import { View } from '../common/View';
import { SectionLabel } from '../forms';
import { DateSelect } from '../select/DateSelect';
export function EditFieldModal({ name, onSubmit, onClose }) {
const MODAL_NAME = 'edit-field';
export function EditFieldModal({
name: modalName = MODAL_NAME,
fieldName,
onSubmit,
onClose = undefined,
}) {
const dateFormat = useDateFormat() || 'MM/dd/yyyy';
function onSelectNote(value, mode) {
if (value != null) {
onSubmit(name, value, mode);
onSubmit(fieldName, value, mode);
}
}
function onSelect(value) {
if (value != null) {
// Process the value if needed
if (name === 'amount') {
if (fieldName === 'amount') {
value = amountToInteger(value);
}
onSubmit(name, value);
onSubmit(fieldName, value);
}
}
@@ -51,7 +58,7 @@ export function EditFieldModal({ name, onSubmit, onClose }) {
const [noteAmend, onChangeMode] = useState('replace');
switch (name) {
switch (fieldName) {
case 'date':
const today = currentDay();
label = 'Date';
@@ -216,7 +223,7 @@ export function EditFieldModal({ name, onSubmit, onClose }) {
return (
<Modal
name="edit-field"
name={modalName}
noAnimation={!isNarrowWidth}
onClose={onClose}
containerProps={{
@@ -254,3 +261,4 @@ export function EditFieldModal({ name, onSubmit, onClose }) {
</Modal>
);
}
EditFieldModal.modalName = MODAL_NAME;

View File

@@ -10,10 +10,6 @@ import { useDispatch } from 'react-redux';
import { css } from '@emotion/css';
import { v4 as uuid } from 'uuid';
import {
initiallyLoadPayees,
setUndoEnabled,
} from 'loot-core/src/client/actions/queries';
import { useSchedules } from 'loot-core/src/client/data-hooks/schedules';
import { runQuery } from 'loot-core/src/client/query-helpers';
import { send } from 'loot-core/src/platform/client/fetch';
@@ -42,6 +38,7 @@ import { useFeatureFlag } from '../../hooks/useFeatureFlag';
import { useSelected, SelectedProvider } from '../../hooks/useSelected';
import { SvgDelete, SvgAdd, SvgSubtract } from '../../icons/v0';
import { SvgAlignLeft, SvgCode, SvgInformationOutline } from '../../icons/v1';
import { initiallyLoadPayees, setUndoEnabled } from '../../state/actions';
import { styles, theme } from '../../style';
import { Button } from '../common/Button2';
import { Menu } from '../common/Menu';
@@ -57,6 +54,8 @@ import { BetweenAmountInput } from '../util/AmountInput';
import { DisplayId } from '../util/DisplayId';
import { GenericInput } from '../util/GenericInput';
const MODAL_NAME = 'edit-rule';
function updateValue(array, value, update) {
return array.map(v => (v === value ? update() : v));
}
@@ -758,12 +757,16 @@ const conditionFields = [
['amount-outflow', mapField('amount', { outflow: true })],
]);
export function EditRuleModal({ defaultRule, onSave: originalOnSave }) {
export function EditRuleModal({
name = MODAL_NAME,
rule,
onSave: originalOnSave,
}) {
const [conditions, setConditions] = useState(
defaultRule.conditions.map(parse).map(c => ({ ...c, inputKey: uuid() })),
rule.conditions.map(parse).map(c => ({ ...c, inputKey: uuid() })),
);
const [actionSplits, setActionSplits] = useState(() => {
const parsedActions = defaultRule.actions.map(parse);
const parsedActions = rule.actions.map(parse);
return parsedActions.reduce(
(acc, action) => {
const splitIndex = action.options?.splitIndex ?? 0;
@@ -775,8 +778,8 @@ export function EditRuleModal({ defaultRule, onSave: originalOnSave }) {
[{ id: uuid(), actions: [] }],
);
});
const [stage, setStage] = useState(defaultRule.stage);
const [conditionsOp, setConditionsOp] = useState(defaultRule.conditionsOp);
const [stage, setStage] = useState(rule.stage);
const [conditionsOp, setConditionsOp] = useState(rule.conditionsOp);
const [transactions, setTransactions] = useState([]);
const dispatch = useDispatch();
const scrollableEl = useRef();
@@ -964,16 +967,16 @@ export function EditRuleModal({ defaultRule, onSave: originalOnSave }) {
}
async function onSave(close) {
const rule = {
...defaultRule,
const ruleToSave = {
...rule,
stage,
conditionsOp,
conditions: conditions.map(unparse),
actions: getUnparsedActions(actionSplits),
};
const method = rule.id ? 'rule-update' : 'rule-add';
const { error, id: newId } = await send(method, rule);
const method = ruleToSave.id ? 'rule-update' : 'rule-add';
const { error, id: newId } = await send(method, ruleToSave);
if (error) {
if (error.conditionErrors) {
@@ -995,10 +998,10 @@ export function EditRuleModal({ defaultRule, onSave: originalOnSave }) {
} else {
// If adding a rule, we got back an id
if (newId) {
rule.id = newId;
ruleToSave.id = newId;
}
originalOnSave?.(rule);
originalOnSave?.(ruleToSave);
close();
}
}
@@ -1013,7 +1016,7 @@ export function EditRuleModal({ defaultRule, onSave: originalOnSave }) {
const showSplitButton = actionSplits.length > 0;
return (
<Modal name="edit-rule">
<Modal name={name}>
{({ state: { close } }) => (
<>
<ModalHeader
@@ -1274,3 +1277,4 @@ export function EditRuleModal({ defaultRule, onSave: originalOnSave }) {
</Modal>
);
}
EditRuleModal.modalName = MODAL_NAME;

View File

@@ -4,6 +4,7 @@ import React, {
} from 'react';
import { envelopeBudget } from 'loot-core/client/queries';
import * as monthUtils from 'loot-core/shared/months';
import { useCategory } from '../../hooks/useCategory';
import { theme, styles } from '../../style';
@@ -21,17 +22,46 @@ import {
import { Text } from '../common/Text';
import { View } from '../common/View';
import { CellValueText } from '../spreadsheet/CellValue';
import { NamespaceContext } from '../spreadsheet/NamespaceContext';
const MODAL_NAME = 'envelope-balance-menu' as const;
type EnvelopeBalanceMenuModalProps = ComponentPropsWithoutRef<
typeof BalanceMenu
>;
> & {
name: typeof MODAL_NAME;
month: string;
};
export function EnvelopeBalanceMenuModal({
name = MODAL_NAME,
month,
categoryId,
onCarryover,
onTransfer,
onCover,
}: EnvelopeBalanceMenuModalProps) {
return (
<NamespaceContext.Provider value={monthUtils.sheetForMonth(month)}>
<EnvelopeBalanceMenuModalInner
name={name}
categoryId={categoryId}
onCarryover={onCarryover}
onTransfer={onTransfer}
onCover={onCover}
/>
</NamespaceContext.Provider>
);
}
EnvelopeBalanceMenuModal.modalName = MODAL_NAME;
function EnvelopeBalanceMenuModalInner({
name = MODAL_NAME,
categoryId,
onCarryover,
onTransfer,
onCover,
}: Omit<EnvelopeBalanceMenuModalProps, 'month'>) {
const defaultMenuItemStyle: CSSProperties = {
...styles.mobileMenuItem,
color: theme.menuItemText,
@@ -46,7 +76,7 @@ export function EnvelopeBalanceMenuModal({
}
return (
<Modal name="envelope-balance-menu">
<Modal name={name}>
{({ state: { close } }) => (
<>
<ModalHeader

View File

@@ -6,6 +6,7 @@ import React, {
} from 'react';
import { envelopeBudget } from 'loot-core/client/queries';
import * as monthUtils from 'loot-core/shared/months';
import { amountToInteger, integerToAmount } from 'loot-core/shared/util';
import { useCategory } from '../../hooks/useCategory';
@@ -21,21 +22,51 @@ import {
import { Text } from '../common/Text';
import { View } from '../common/View';
import { FocusableAmountInput } from '../mobile/transactions/FocusableAmountInput';
import { NamespaceContext } from '../spreadsheet/NamespaceContext';
const MODAL_NAME = 'envelope-budget-menu' as const;
type EnvelopeBudgetMenuModalProps = ComponentPropsWithoutRef<
typeof BudgetMenu
> & {
name: typeof MODAL_NAME;
month: string;
categoryId: string;
onUpdateBudget: (amount: number) => void;
};
export function EnvelopeBudgetMenuModal({
name = MODAL_NAME,
month,
categoryId,
onUpdateBudget,
onCopyLastMonthAverage,
onSetMonthsAverage,
onApplyBudgetTemplate,
}: EnvelopeBudgetMenuModalProps) {
return (
<NamespaceContext.Provider value={monthUtils.sheetForMonth(month)}>
<EnvelopeBudgetModalInner
name={name}
categoryId={categoryId}
onUpdateBudget={onUpdateBudget}
onCopyLastMonthAverage={onCopyLastMonthAverage}
onSetMonthsAverage={onSetMonthsAverage}
onApplyBudgetTemplate={onApplyBudgetTemplate}
/>
</NamespaceContext.Provider>
);
}
EnvelopeBudgetMenuModal.modalName = MODAL_NAME;
function EnvelopeBudgetModalInner({
name,
categoryId,
onUpdateBudget,
onCopyLastMonthAverage,
onSetMonthsAverage,
onApplyBudgetTemplate,
}: Omit<EnvelopeBudgetMenuModalProps, 'month'>) {
const defaultMenuItemStyle: CSSProperties = {
...styles.mobileMenuItem,
color: theme.menuItemText,
@@ -62,7 +93,7 @@ export function EnvelopeBudgetMenuModal({
}
return (
<Modal name="envelope-budget-menu">
<Modal name={name}>
{({ state: { close } }) => (
<>
<ModalHeader

View File

@@ -3,7 +3,7 @@ import React, { useState, type CSSProperties } from 'react';
import { css } from '@emotion/css';
import * as monthUtils from 'loot-core/src/shared/months';
import * as monthUtils from 'loot-core/shared/months';
import { useNotes } from '../../hooks/useNotes';
import { useUndo } from '../../hooks/useUndo';
@@ -15,14 +15,41 @@ import { Button } from '../common/Button2';
import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal';
import { View } from '../common/View';
import { Notes } from '../Notes';
import { NamespaceContext } from '../spreadsheet/NamespaceContext';
const MODAL_NAME = 'envelope-budget-month-menu' as const;
type EnvelopeBudgetMonthMenuModalProps = {
name: typeof MODAL_NAME;
month: string;
onBudgetAction: (month: string, action: string, arg?: unknown) => void;
onEditNotes: (month: string) => void;
};
export function EnvelopeBudgetMonthMenuModal({
name,
month,
onBudgetAction,
onEditNotes,
}: EnvelopeBudgetMonthMenuModalProps) {
return (
<NamespaceContext.Provider
key={name}
value={monthUtils.sheetForMonth(month)}
>
<EnvelopeBudgetMonthMenuModalInner
name={name}
month={month}
onBudgetAction={onBudgetAction}
onEditNotes={onEditNotes}
/>
</NamespaceContext.Provider>
);
}
EnvelopeBudgetMonthMenuModal.modalName = MODAL_NAME;
function EnvelopeBudgetMonthMenuModalInner({
name,
month,
onBudgetAction,
onEditNotes,
@@ -59,7 +86,7 @@ export function EnvelopeBudgetMonthMenuModal({
return (
<Modal
name="envelope-budget-month-menu"
name={name}
containerProps={{
style: { height: '50vh' },
}}

View File

@@ -1,13 +1,14 @@
import React from 'react';
import { useDispatch } from 'react-redux';
import { collapseModals, pushModal } from 'loot-core/client/actions';
import { envelopeBudget } from 'loot-core/client/queries';
import * as monthUtils from 'loot-core/shared/months';
import { groupById, integerToCurrency } from 'loot-core/shared/util';
import { format, sheetForMonth, prevMonth } from 'loot-core/src/shared/months';
import { useCategories } from '../../hooks/useCategories';
import { useUndo } from '../../hooks/useUndo';
import { collapseModals, pushModal } from '../../state/actions';
import { styles } from '../../style';
import { ToBudgetAmount } from '../budget/envelope/budgetsummary/ToBudgetAmount';
import { TotalsList } from '../budget/envelope/budgetsummary/TotalsList';
@@ -15,12 +16,32 @@ import { useEnvelopeSheetValue } from '../budget/envelope/EnvelopeBudgetComponen
import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal';
import { NamespaceContext } from '../spreadsheet/NamespaceContext';
const MODAL_NAME = 'envelope-budget-summary' as const;
type EnvelopeBudgetSummaryModalProps = {
name: typeof MODAL_NAME;
onBudgetAction: (month: string, action: string, arg?: unknown) => void;
month: string;
};
export function EnvelopeBudgetSummaryModal({
name = MODAL_NAME,
month,
onBudgetAction,
}: EnvelopeBudgetSummaryModalProps) {
return (
<NamespaceContext.Provider value={monthUtils.sheetForMonth(month)}>
<EnvelopeBudgetSummaryModalInner
name={name}
month={month}
onBudgetAction={onBudgetAction}
/>
</NamespaceContext.Provider>
);
}
function EnvelopeBudgetSummaryModalInner({
name = MODAL_NAME,
month,
onBudgetAction,
}: EnvelopeBudgetSummaryModalProps) {
@@ -108,7 +129,7 @@ export function EnvelopeBudgetSummaryModal({
};
return (
<Modal name="envelope-budget-summary">
<Modal name={name}>
{({ state: { close } }) => (
<>
<ModalHeader
@@ -140,3 +161,4 @@ export function EnvelopeBudgetSummaryModal({
</Modal>
);
}
EnvelopeBudgetSummaryModal.modalName = MODAL_NAME;

View File

@@ -3,20 +3,51 @@ import React, {
type CSSProperties,
} from 'react';
import * as monthUtils from 'loot-core/shared/months';
import { theme, styles } from '../../style';
import { ToBudgetMenu } from '../budget/envelope/budgetsummary/ToBudgetMenu';
import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal';
import { NamespaceContext } from '../spreadsheet/NamespaceContext';
const MODAL_NAME = 'envelope-summary-to-budget-menu' as const;
type EnvelopeToBudgetMenuModalProps = ComponentPropsWithoutRef<
typeof ToBudgetMenu
>;
> & {
name: typeof MODAL_NAME;
month: string;
};
export function EnvelopeToBudgetMenuModal({
name = MODAL_NAME,
month,
onTransfer,
onCover,
onHoldBuffer,
onResetHoldBuffer,
}: EnvelopeToBudgetMenuModalProps) {
return (
<NamespaceContext.Provider value={monthUtils.sheetForMonth(month)}>
<EnvelopeToBudgetMenuModalInner
name={name}
onTransfer={onTransfer}
onCover={onCover}
onHoldBuffer={onHoldBuffer}
onResetHoldBuffer={onResetHoldBuffer}
/>
</NamespaceContext.Provider>
);
}
EnvelopeToBudgetMenuModal.modalName = MODAL_NAME;
function EnvelopeToBudgetMenuModalInner({
name = MODAL_NAME,
onTransfer,
onCover,
onHoldBuffer,
onResetHoldBuffer,
}: Omit<EnvelopeToBudgetMenuModalProps, 'month'>) {
const defaultMenuItemStyle: CSSProperties = {
...styles.mobileMenuItem,
color: theme.menuItemText,
@@ -25,7 +56,7 @@ export function EnvelopeToBudgetMenuModal({
};
return (
<Modal name="envelope-summary-to-budget-menu">
<Modal name={name}>
{({ state: { close } }) => (
<>
<ModalHeader

View File

@@ -2,7 +2,6 @@
import React, { useState } from 'react';
import { Form } from 'react-aria-components';
import { type FinanceModals } from 'loot-core/src/client/state-types/modals';
import { send } from 'loot-core/src/platform/client/fetch';
import { getTestKeyError } from 'loot-core/src/shared/errors';
@@ -22,15 +21,21 @@ import { Paragraph } from '../common/Paragraph';
import { Text } from '../common/Text';
import { View } from '../common/View';
const MODAL_NAME = 'fix-encryption-key' as const;
type FixEncryptionKeyModalProps = {
options: FinanceModals['fix-encryption-key'];
name: typeof MODAL_NAME;
hasExistingKey?: boolean;
cloudFileId?: string;
onSuccess?: () => void;
};
export function FixEncryptionKeyModal({
options = {},
name,
hasExistingKey,
cloudFileId,
onSuccess,
}: FixEncryptionKeyModalProps) {
const { hasExistingKey, cloudFileId, onSuccess } = options;
const [password, setPassword] = useState('');
const [error, setError] = useState('');
const [loading, setLoading] = useState(false);
@@ -58,7 +63,7 @@ export function FixEncryptionKeyModal({
}
return (
<Modal name="fix-encryption-key">
<Modal name={name}>
{({ state: { close } }) => (
<>
<ModalHeader
@@ -177,3 +182,4 @@ export function FixEncryptionKeyModal({
</Modal>
);
}
FixEncryptionKeyModal.modalName = MODAL_NAME;

View File

@@ -3,7 +3,6 @@ import React, { useEffect, useState, useRef } from 'react';
import { Trans, useTranslation } from 'react-i18next';
import { useDispatch } from 'react-redux';
import { pushModal } from 'loot-core/src/client/actions/modals';
import { sendCatch } from 'loot-core/src/platform/client/fetch';
import {
type GoCardlessInstitution,
@@ -12,6 +11,7 @@ import {
import { useGoCardlessStatus } from '../../hooks/useGoCardlessStatus';
import { AnimatedLoading } from '../../icons/AnimatedLoading';
import { pushModal } from '../../state/actions';
import { theme } from '../../style';
import { Error, Warning } from '../alerts';
import { Autocomplete } from '../autocomplete/Autocomplete';
@@ -23,6 +23,8 @@ import { View } from '../common/View';
import { FormField, FormLabel } from '../forms';
import { COUNTRY_OPTIONS } from '../util/countries';
const MODAL_NAME = 'gocardless-external-msg' as const;
function useAvailableBanks(country: string) {
const [banks, setBanks] = useState<GoCardlessInstitution[]>([]);
const [isLoading, setIsLoading] = useState(false);
@@ -72,7 +74,8 @@ function renderError(error: 'unknown' | 'timeout', t: (key: string) => string) {
);
}
type GoCardlessExternalMsgProps = {
type GoCardlessExternalMsgModalProps = {
name: typeof MODAL_NAME;
onMoveExternal: (arg: {
institutionId: string;
}) => Promise<{ error?: 'unknown' | 'timeout'; data?: GoCardlessToken }>;
@@ -81,10 +84,11 @@ type GoCardlessExternalMsgProps = {
};
export function GoCardlessExternalMsgModal({
name = MODAL_NAME,
onMoveExternal,
onSuccess,
onClose,
}: GoCardlessExternalMsgProps) {
}: GoCardlessExternalMsgModalProps) {
const { t } = useTranslation();
const dispatch = useDispatch();
@@ -237,7 +241,7 @@ export function GoCardlessExternalMsgModal({
return (
<Modal
name="gocardless-external-msg"
name={name}
onClose={onClose}
containerProps={{ style: { width: '30vw' } }}
>
@@ -320,3 +324,4 @@ export function GoCardlessExternalMsgModal({
</Modal>
);
}
GoCardlessExternalMsgModal.modalName = MODAL_NAME;

View File

@@ -18,11 +18,15 @@ import { Text } from '../common/Text';
import { View } from '../common/View';
import { FormField, FormLabel } from '../forms';
const MODAL_NAME = 'gocardless-init' as const;
type GoCardlessInitialiseProps = {
name: typeof MODAL_NAME;
onSuccess: () => void;
};
export const GoCardlessInitialiseModal = ({
name = MODAL_NAME,
onSuccess,
}: GoCardlessInitialiseProps) => {
const [secretId, setSecretId] = useState('');
@@ -55,7 +59,7 @@ export const GoCardlessInitialiseModal = ({
};
return (
<Modal name="gocardless-init" containerProps={{ style: { width: '30vw' } }}>
<Modal name={name} containerProps={{ style: { width: '30vw' } }}>
{({ state: { close } }) => (
<>
<ModalHeader
@@ -128,3 +132,4 @@ export const GoCardlessInitialiseModal = ({
</Modal>
);
};
GoCardlessInitialiseModal.modalName = MODAL_NAME;

View File

@@ -7,10 +7,18 @@ import { Text } from '../common/Text';
import { View } from '../common/View';
import { TableHeader, Row, Field } from '../table';
export function GoalTemplateModal() {
const MODAL_NAME = 'goal-templates' as const;
type GoalTemplateModalProps = {
name: typeof MODAL_NAME;
};
export function GoalTemplateModal({
name = MODAL_NAME,
}: GoalTemplateModalProps) {
const { t } = useTranslation();
return (
<Modal name="goal-templates" containerProps={{ style: { width: 850 } }}>
<Modal name={name} containerProps={{ style: { width: 850 } }}>
{({ state: { close } }) => (
<>
<ModalHeader
@@ -177,3 +185,4 @@ export function GoalTemplateModal() {
</Modal>
);
}
GoalTemplateModal.modalName = MODAL_NAME;

View File

@@ -1,6 +1,7 @@
import React, { useState } from 'react';
import { envelopeBudget } from 'loot-core/client/queries';
import * as monthUtils from 'loot-core/shared/months';
import { styles } from '../../style';
import { useEnvelopeSheetValue } from '../budget/envelope/EnvelopeBudgetComponents';
@@ -9,14 +10,34 @@ import { InitialFocus } from '../common/InitialFocus';
import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal';
import { View } from '../common/View';
import { FieldLabel } from '../mobile/MobileForms';
import { NamespaceContext } from '../spreadsheet/NamespaceContext';
import { AmountInput } from '../util/AmountInput';
const MODAL_NAME = 'hold-buffer' as const;
type HoldBufferModalProps = {
name: typeof MODAL_NAME;
month: string;
onSubmit: (amount: number) => void;
};
export function HoldBufferModal({ onSubmit }: HoldBufferModalProps) {
export function HoldBufferModal({
name,
month,
onSubmit,
}: HoldBufferModalProps) {
return (
<NamespaceContext.Provider value={monthUtils.sheetForMonth(month)}>
<HoldBufferModalInner name={name} onSubmit={onSubmit} />
</NamespaceContext.Provider>
);
}
HoldBufferModal.modalName = MODAL_NAME;
function HoldBufferModalInner({
name,
onSubmit,
}: Omit<HoldBufferModalProps, 'month'>) {
const available = useEnvelopeSheetValue(envelopeBudget.toBudget) ?? 0;
const [amount, setAmount] = useState<number>(0);
@@ -27,7 +48,7 @@ export function HoldBufferModal({ onSubmit }: HoldBufferModalProps) {
};
return (
<Modal name="hold-buffer">
<Modal name={name}>
{({ state: { close } }) => (
<>
<ModalHeader

View File

@@ -32,6 +32,8 @@ import {
stripCsvImportTransaction,
} from './utils';
const MODAL_NAME = 'import-transactions';
function getFileType(filepath) {
const m = filepath.match(/\.([^.]*)$/);
if (!m) return 'ofx';
@@ -133,7 +135,13 @@ function parseCategoryFields(trans, categories) {
return match;
}
export function ImportTransactionsModal({ options }) {
export function ImportTransactionsModal({
name = MODAL_NAME,
accountId,
filename: originalFilename,
onImported,
categories,
}) {
const dateFormat = useDateFormat() || 'MM/dd/yyyy';
const [prefs, savePrefs] = useSyncedPrefs();
const {
@@ -146,7 +154,7 @@ export function ImportTransactionsModal({ options }) {
const [multiplierAmount, setMultiplierAmount] = useState('');
const [loadingState, setLoadingState] = useState('parsing');
const [error, setError] = useState(null);
const [filename, setFilename] = useState(options.filename);
const [filename, setFilename] = useState(originalFilename);
const [transactions, setTransactions] = useState([]);
const [filetype, setFileType] = useState(null);
const [fieldMappings, setFieldMappings] = useState(null);
@@ -154,7 +162,6 @@ export function ImportTransactionsModal({ options }) {
const [flipAmount, setFlipAmount] = useState(false);
const [multiplierEnabled, setMultiplierEnabled] = useState(false);
const [reconcile, setReconcile] = useState(true);
const { accountId, categories, onImported } = options;
// This cannot be set after parsing the file, because changing it
// requires re-parsing the file. This is different from the other
@@ -416,7 +423,7 @@ export function ImportTransactionsModal({ options }) {
}
useEffect(() => {
const fileType = getFileType(options.filename);
const fileType = getFileType(originalFilename);
const parseOptions = getParseOptions(fileType, {
delimiter,
hasHeaderRow,
@@ -424,10 +431,10 @@ export function ImportTransactionsModal({ options }) {
fallbackMissingPayeeToMemo,
});
parse(options.filename, parseOptions);
parse(originalFilename, parseOptions);
}, [
parseTransactions,
options.filename,
originalFilename,
delimiter,
hasHeaderRow,
skipLines,
@@ -718,7 +725,7 @@ export function ImportTransactionsModal({ options }) {
return (
<Modal
name="import-transactions"
name={name}
isLoading={loadingState === 'parsing'}
containerProps={{ style: { width: 800 } }}
>
@@ -1069,6 +1076,7 @@ export function ImportTransactionsModal({ options }) {
</Modal>
);
}
ImportTransactionsModal.modalName = MODAL_NAME;
function getParseOptions(fileType, options = {}) {
if (fileType === 'csv') {

View File

@@ -7,6 +7,8 @@ import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal';
import { Text } from '../common/Text';
import { View } from '../common/View';
const MODAL_NAME = 'keyboard-shortcuts' as const;
type KeyIconProps = {
shortcut: string;
style?: CSSProperties;
@@ -148,13 +150,19 @@ function Shortcut({
);
}
export function KeyboardShortcutModal() {
type KeyboardShortcutModalProps = {
name: typeof MODAL_NAME;
};
export function KeyboardShortcutModal({
name = MODAL_NAME,
}: KeyboardShortcutModalProps) {
const location = useLocation();
const onBudget = location.pathname.startsWith('/budget');
const onAccounts = location.pathname.startsWith('/accounts');
const ctrl = Platform.OS === 'mac' ? '⌘' : 'Ctrl';
return (
<Modal name="keyboard-shortcuts">
<Modal name={name}>
{({ state: { close } }) => (
<>
<ModalHeader
@@ -340,3 +348,4 @@ export function KeyboardShortcutModal() {
</Modal>
);
}
KeyboardShortcutModal.modalName = MODAL_NAME;

View File

@@ -1,11 +1,11 @@
import React, { useState, useEffect } from 'react';
import { useDispatch } from 'react-redux';
import { loadBackup, makeBackup } from 'loot-core/client/actions';
import { type Backup } from 'loot-core/server/backups';
import { send, listen, unlisten } from 'loot-core/src/platform/client/fetch';
import { useMetadataPref } from '../../hooks/useMetadataPref';
import { loadBackup, makeBackup } from '../../state/actions';
import { theme } from '../../style';
import { Block } from '../common/Block';
import { Button } from '../common/Button2';
@@ -14,6 +14,8 @@ import { Text } from '../common/Text';
import { View } from '../common/View';
import { Row, Cell } from '../table';
const MODAL_NAME = 'load-backup' as const;
type BackupTableProps = {
backups: Backup[];
onSelect: (backupId: string) => void;
@@ -41,14 +43,16 @@ function BackupTable({ backups, onSelect }: BackupTableProps) {
}
type LoadBackupModalProps = {
name: typeof MODAL_NAME;
budgetId: string;
watchUpdates: boolean;
backupDisabled: boolean;
watchUpdates?: boolean;
backupDisabled?: boolean;
};
export function LoadBackupModal({
name = MODAL_NAME,
budgetId,
watchUpdates,
watchUpdates = true,
backupDisabled,
}: LoadBackupModalProps) {
const dispatch = useDispatch();
@@ -75,7 +79,7 @@ export function LoadBackupModal({
);
return (
<Modal name="load-backup" containerProps={{ style: { maxWidth: '30vw' } }}>
<Modal name={name} containerProps={{ style: { maxWidth: '30vw' } }}>
{({ state: { close } }) => (
<>
<ModalHeader
@@ -146,3 +150,4 @@ export function LoadBackupModal({
</Modal>
);
}
LoadBackupModal.modalName = MODAL_NAME;

View File

@@ -7,11 +7,17 @@ import { isNonProductionEnvironment } from 'loot-core/src/shared/environment';
import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal';
import { ManageRules } from '../ManageRules';
const MODAL_NAME = 'manage-rules' as const;
type ManageRulesModalProps = {
name: typeof MODAL_NAME;
payeeId?: string;
};
export function ManageRulesModal({ payeeId }: ManageRulesModalProps) {
export function ManageRulesModal({
name = MODAL_NAME,
payeeId,
}: ManageRulesModalProps) {
const [loading, setLoading] = useState(true);
const location = useLocation();
if (isNonProductionEnvironment()) {
@@ -23,7 +29,7 @@ export function ManageRulesModal({ payeeId }: ManageRulesModalProps) {
}
return (
<Modal name="manage-rules" isLoading={loading}>
<Modal name={name} isLoading={loading}>
{({ state: { close } }) => (
<>
<ModalHeader
@@ -36,3 +42,4 @@ export function ManageRulesModal({ payeeId }: ManageRulesModalProps) {
</Modal>
);
}
ManageRulesModal.modalName = MODAL_NAME;

View File

@@ -1,10 +1,10 @@
import React, { useState, useRef, useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { replaceModal } from 'loot-core/src/client/actions/modals';
import { send } from 'loot-core/src/platform/client/fetch';
import { usePayees } from '../../hooks/usePayees';
import { replaceModal } from '../../state/actions';
import { theme } from '../../style';
import { Information } from '../alerts';
import { Button } from '../common/Button2';
@@ -13,9 +13,15 @@ import { Paragraph } from '../common/Paragraph';
import { Text } from '../common/Text';
import { View } from '../common/View';
const MODAL_NAME = 'merge-unused-payees';
const highlightStyle = { color: theme.pageTextPositive };
export function MergeUnusedPayeesModal({ payeeIds, targetPayeeId }) {
export function MergeUnusedPayeesModal({
name = MODAL_NAME,
payeeIds,
targetPayeeId,
}) {
const allPayees = usePayees();
const modalStack = useSelector(state => state.modals.modalStack);
const isEditingRule = !!modalStack.find(m => m.name === 'edit-rule');
@@ -76,7 +82,7 @@ export function MergeUnusedPayeesModal({ payeeIds, targetPayeeId }) {
}
return (
<Modal name="merge-unused-payees">
<Modal name={name}>
{({ state: { close } }) => (
<View style={{ padding: 20, maxWidth: 500 }}>
<View>
@@ -181,3 +187,4 @@ export function MergeUnusedPayeesModal({ payeeIds, targetPayeeId }) {
</Modal>
);
}
MergeUnusedPayeesModal.modalName = MODAL_NAME;

View File

@@ -8,13 +8,21 @@ import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal';
import { View } from '../common/View';
import { Notes } from '../Notes';
const MODAL_NAME = 'notes' as const;
type NotesModalProps = {
name: typeof MODAL_NAME;
id: string;
name: string;
title: string;
onSave: (id: string, notes: string) => void;
};
export function NotesModal({ id, name, onSave }: NotesModalProps) {
export function NotesModal({
name = MODAL_NAME,
id,
title,
onSave,
}: NotesModalProps) {
const originalNotes = useNotes(id);
const [notes, setNotes] = useState(originalNotes);
@@ -28,7 +36,7 @@ export function NotesModal({ id, name, onSave }: NotesModalProps) {
return (
<Modal
name="notes"
name={name}
containerProps={{
style: { height: '50vh' },
}}
@@ -36,7 +44,7 @@ export function NotesModal({ id, name, onSave }: NotesModalProps) {
{({ state: { close } }) => (
<>
<ModalHeader
title={`Notes: ${name}`}
title={`Notes: ${title}`}
rightContent={<ModalCloseButton onPress={close} />}
/>
<View
@@ -87,3 +95,4 @@ export function NotesModal({ id, name, onSave }: NotesModalProps) {
</Modal>
);
}
NotesModal.modalName = MODAL_NAME;

View File

@@ -2,8 +2,7 @@ import React from 'react';
import { Trans, useTranslation } from 'react-i18next';
import { useDispatch } from 'react-redux';
import { closeBudget } from 'loot-core/client/actions';
import { closeBudget } from '../../state/actions';
import { Button } from '../common/Button2';
import { Link } from '../common/Link';
import { Modal, ModalHeader, ModalTitle } from '../common/Modal';
@@ -11,7 +10,15 @@ import { Paragraph } from '../common/Paragraph';
import { Text } from '../common/Text';
import { View } from '../common/View';
export function OutOfSyncMigrationsModal() {
const MODAL_NAME = 'out-of-sync-migrations' as const;
type OutOfSyncMigrationsModalProps = {
name: typeof MODAL_NAME;
};
export function OutOfSyncMigrationsModal({
name,
}: OutOfSyncMigrationsModalProps) {
const dispatch = useDispatch();
const { t } = useTranslation();
@@ -22,7 +29,7 @@ export function OutOfSyncMigrationsModal() {
};
return (
<Modal name="out-of-sync-migrations">
<Modal name={name}>
{({ state: { close } }) => (
<>
<ModalHeader
@@ -89,3 +96,4 @@ export function OutOfSyncMigrationsModal() {
</Modal>
);
}
OutOfSyncMigrationsModal.modalName = MODAL_NAME;

View File

@@ -13,12 +13,16 @@ import {
ModalHeader,
} from '../common/Modal';
const MODAL_NAME = 'payee-autocomplete' as const;
type PayeeAutocompleteModalProps = {
name: typeof MODAL_NAME;
autocompleteProps: ComponentPropsWithoutRef<typeof PayeeAutocomplete>;
onClose: () => void;
onClose?: () => void;
};
export function PayeeAutocompleteModal({
name = MODAL_NAME,
autocompleteProps,
onClose,
}: PayeeAutocompleteModalProps) {
@@ -35,7 +39,7 @@ export function PayeeAutocompleteModal({
return (
<Modal
name="payee-autocomplete"
name={name}
noAnimation={!isNarrowWidth}
onClose={onClose}
containerProps={{
@@ -81,3 +85,4 @@ export function PayeeAutocompleteModal({
</Modal>
);
}
PayeeAutocompleteModal.modalName = MODAL_NAME;

View File

@@ -19,9 +19,14 @@ import {
import { Text } from '../common/Text';
import { View } from '../common/View';
type ScheduledTransactionMenuModalProps = ScheduledTransactionMenuProps;
const MODAL_NAME = 'scheduled-transaction-menu' as const;
type ScheduledTransactionMenuModalProps = ScheduledTransactionMenuProps & {
name: typeof MODAL_NAME;
};
export function ScheduledTransactionMenuModal({
name = MODAL_NAME,
transactionId,
onSkip,
onPost,
@@ -46,7 +51,7 @@ export function ScheduledTransactionMenuModal({
}
return (
<Modal name="scheduled-transaction-menu">
<Modal name={name}>
{({ state: { close } }) => (
<>
<ModalHeader
@@ -78,6 +83,7 @@ export function ScheduledTransactionMenuModal({
</Modal>
);
}
ScheduledTransactionMenuModal.modalName = MODAL_NAME;
type ScheduledTransactionMenuProps = Omit<
ComponentPropsWithoutRef<typeof Menu>,

View File

@@ -1,14 +1,13 @@
import React, { useState } from 'react';
import { useDispatch } from 'react-redux';
import { useAccounts } from '../../hooks/useAccounts';
import {
closeModal,
linkAccount,
linkAccountSimpleFin,
unlinkAccount,
} from 'loot-core/client/actions';
import { useAccounts } from '../../hooks/useAccounts';
} from '../../state/actions';
import { theme } from '../../style';
import { Autocomplete } from '../autocomplete/Autocomplete';
import { Button } from '../common/Button2';
@@ -18,6 +17,8 @@ import { View } from '../common/View';
import { PrivacyFilter } from '../PrivacyFilter';
import { TableHeader, Table, Row, Field } from '../table';
const MODAL_NAME = 'select-linked-accounts';
const addOnBudgetAccountOption = { id: 'new-on', name: 'Create new account' };
const addOffBudgetAccountOption = {
id: 'new-off',
@@ -25,7 +26,8 @@ const addOffBudgetAccountOption = {
};
export function SelectLinkedAccountsModal({
requisitionId,
name = MODAL_NAME,
requisitionId = '',
externalAccounts,
syncSource,
}) {
@@ -114,10 +116,7 @@ export function SelectLinkedAccountsModal({
}
return (
<Modal
name="select-linked-accounts"
containerProps={{ style: { width: 800 } }}
>
<Modal name={name} containerProps={{ style: { width: 800 } }}>
{({ state: { close } }) => (
<>
<ModalHeader
@@ -260,3 +259,4 @@ function TableRow({
</Row>
);
}
SelectLinkedAccountsModal.modalName = MODAL_NAME;

View File

@@ -17,11 +17,15 @@ import { Text } from '../common/Text';
import { View } from '../common/View';
import { FormField, FormLabel } from '../forms';
const MODAL_NAME = 'simplefin-init' as const;
type SimpleFinInitialiseProps = {
name: typeof MODAL_NAME;
onSuccess: () => void;
};
export const SimpleFinInitialiseModal = ({
name = MODAL_NAME,
onSuccess,
}: SimpleFinInitialiseProps) => {
const [token, setToken] = useState('');
@@ -47,7 +51,7 @@ export const SimpleFinInitialiseModal = ({
};
return (
<Modal name="simplefin-init" containerProps={{ style: { width: 300 } }}>
<Modal name={name} containerProps={{ style: { width: 300 } }}>
{({ state: { close } }) => (
<>
<ModalHeader
@@ -102,3 +106,4 @@ export const SimpleFinInitialiseModal = ({
</Modal>
);
};
SimpleFinInitialiseModal.modalName = MODAL_NAME;

View File

@@ -20,7 +20,7 @@ type SingleInputModalProps = {
Header: ComponentType<ComponentPropsWithoutRef<typeof ModalHeader>>;
buttonText: string;
onSubmit: (value: string) => void;
onValidate?: (value: string) => string[];
onValidate?: (value: string) => string;
inputPlaceholder?: string;
};
@@ -33,7 +33,7 @@ export function SingleInputModal({
inputPlaceholder,
}: SingleInputModalProps) {
const [value, setValue] = useState('');
const [errorMessage, setErrorMessage] = useState(null);
const [errorMessage, setErrorMessage] = useState<string>(null);
const _onSubmit = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();

View File

@@ -4,6 +4,7 @@ import React, {
} from 'react';
import { trackingBudget } from 'loot-core/client/queries';
import * as monthUtils from 'loot-core/shared/months';
import { useCategory } from '../../hooks/useCategory';
import { theme, styles } from '../../style';
@@ -21,15 +22,40 @@ import {
import { Text } from '../common/Text';
import { View } from '../common/View';
import { CellValueText } from '../spreadsheet/CellValue';
import { NamespaceContext } from '../spreadsheet/NamespaceContext';
const MODAL_NAME = 'tracking-balance-menu' as const;
type TrackingBalanceMenuModalProps = ComponentPropsWithoutRef<
typeof BalanceMenu
>;
> & {
name: typeof MODAL_NAME;
month: string;
};
export function TrackingBalanceMenuModal({
name = MODAL_NAME,
month,
categoryId,
onCarryover,
}: TrackingBalanceMenuModalProps) {
return (
<NamespaceContext.Provider value={monthUtils.sheetForMonth(month)}>
<TrackingBalanceMenuModalInner
name={name}
categoryId={categoryId}
onCarryover={onCarryover}
/>
</NamespaceContext.Provider>
);
}
TrackingBalanceMenuModal.modalName = MODAL_NAME;
function TrackingBalanceMenuModalInner({
name = MODAL_NAME,
categoryId,
onCarryover,
}: Omit<TrackingBalanceMenuModalProps, 'month'>) {
const defaultMenuItemStyle: CSSProperties = {
...styles.mobileMenuItem,
color: theme.menuItemText,
@@ -44,7 +70,7 @@ export function TrackingBalanceMenuModal({
}
return (
<Modal name="tracking-balance-menu">
<Modal name={name}>
{({ state: { close } }) => (
<>
<ModalHeader

View File

@@ -6,6 +6,7 @@ import React, {
} from 'react';
import { trackingBudget } from 'loot-core/client/queries';
import * as monthUtils from 'loot-core/shared/months';
import { amountToInteger, integerToAmount } from 'loot-core/shared/util';
import { useCategory } from '../../hooks/useCategory';
@@ -21,21 +22,51 @@ import {
import { Text } from '../common/Text';
import { View } from '../common/View';
import { FocusableAmountInput } from '../mobile/transactions/FocusableAmountInput';
import { NamespaceContext } from '../spreadsheet/NamespaceContext';
const MODAL_NAME = 'tracking-budget-menu' as const;
type TrackingBudgetMenuModalProps = ComponentPropsWithoutRef<
typeof BudgetMenu
> & {
name: typeof MODAL_NAME;
month: string;
categoryId: string;
onUpdateBudget: (amount: number) => void;
};
export function TrackingBudgetMenuModal({
name = MODAL_NAME,
month,
categoryId,
onUpdateBudget,
onCopyLastMonthAverage,
onSetMonthsAverage,
onApplyBudgetTemplate,
}: TrackingBudgetMenuModalProps) {
return (
<NamespaceContext.Provider value={monthUtils.sheetForMonth(month)}>
<TrackingBudgetMenuModalInner
name={name}
categoryId={categoryId}
onUpdateBudget={onUpdateBudget}
onCopyLastMonthAverage={onCopyLastMonthAverage}
onSetMonthsAverage={onSetMonthsAverage}
onApplyBudgetTemplate={onApplyBudgetTemplate}
/>
</NamespaceContext.Provider>
);
}
TrackingBudgetMenuModal.modalName = MODAL_NAME;
function TrackingBudgetMenuModalInner({
name = MODAL_NAME,
categoryId,
onUpdateBudget,
onCopyLastMonthAverage,
onSetMonthsAverage,
onApplyBudgetTemplate,
}: Omit<TrackingBudgetMenuModalProps, 'month'>) {
const defaultMenuItemStyle: CSSProperties = {
...styles.mobileMenuItem,
color: theme.menuItemText,
@@ -62,7 +93,7 @@ export function TrackingBudgetMenuModal({
}
return (
<Modal name="tracking-budget-menu">
<Modal name={name}>
{({ state: { close } }) => (
<>
<ModalHeader

View File

@@ -15,14 +15,41 @@ import { Button } from '../common/Button2';
import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal';
import { View } from '../common/View';
import { Notes } from '../Notes';
import { NamespaceContext } from '../spreadsheet/NamespaceContext';
const MODAL_NAME = 'tracking-budget-month-menu' as const;
type TrackingBudgetMonthMenuModalProps = {
name: typeof MODAL_NAME;
month: string;
onBudgetAction: (month: string, action: string, arg?: unknown) => void;
onEditNotes: (month: string) => void;
};
export function TrackingBudgetMonthMenuModal({
name,
month,
onBudgetAction,
onEditNotes,
}: TrackingBudgetMonthMenuModalProps) {
return (
<NamespaceContext.Provider
key={name}
value={monthUtils.sheetForMonth(month)}
>
<TrackingBudgetMonthMenuModalInner
name={name}
month={month}
onBudgetAction={onBudgetAction}
onEditNotes={onEditNotes}
/>
</NamespaceContext.Provider>
);
}
TrackingBudgetMonthMenuModal.modalName = MODAL_NAME;
function TrackingBudgetMonthMenuModalInner({
name = MODAL_NAME,
month,
onBudgetAction,
onEditNotes,
@@ -59,7 +86,7 @@ export function TrackingBudgetMonthMenuModal({
return (
<Modal
name="tracking-budget-month-menu"
name={name}
containerProps={{
style: { height: '50vh' },
}}

View File

@@ -11,16 +11,20 @@ import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal';
import { Stack } from '../common/Stack';
import { NamespaceContext } from '../spreadsheet/NamespaceContext';
const MODAL_NAME = 'tracking-budget-summary' as const;
type TrackingBudgetSummaryModalProps = {
name: typeof MODAL_NAME;
month: string;
};
export function TrackingBudgetSummaryModal({
name = MODAL_NAME,
month,
}: TrackingBudgetSummaryModalProps) {
const currentMonth = monthUtils.currentMonth();
return (
<Modal name="tracking-budget-summary">
<Modal name={name}>
{({ state: { close } }) => (
<>
<ModalHeader
@@ -49,3 +53,4 @@ export function TrackingBudgetSummaryModal({
</Modal>
);
}
TrackingBudgetSummaryModal.modalName = MODAL_NAME;

View File

@@ -2,10 +2,10 @@ import React, { useMemo, useState } from 'react';
import { Trans, useTranslation } from 'react-i18next';
import { useDispatch } from 'react-redux';
import { pushModal } from 'loot-core/client/actions';
import { type CategoryEntity } from 'loot-core/types/models';
import { useCategories } from '../../hooks/useCategories';
import { pushModal } from '../../state/actions';
import { styles } from '../../style';
import {
addToBeBudgetedGroup,
@@ -18,7 +18,10 @@ import { View } from '../common/View';
import { FieldLabel, TapField } from '../mobile/MobileForms';
import { AmountInput } from '../util/AmountInput';
const MODAL_NAME = 'transfer' as const;
type TransferModalProps = {
name: typeof MODAL_NAME;
title: string;
categoryId?: CategoryEntity['id'];
month: string;
@@ -28,6 +31,7 @@ type TransferModalProps = {
};
export function TransferModal({
name = MODAL_NAME,
title,
categoryId,
month,
@@ -60,12 +64,15 @@ export function TransferModal({
const openCategoryModal = () => {
dispatch(
pushModal('category-autocomplete', {
categoryGroups,
month,
showHiddenCategories: true,
onSelect: categoryId => {
setToCategoryId(categoryId);
autocompleteProps: {
value: null,
categoryGroups,
showHiddenCategories: true,
onSelect: categoryId => {
setToCategoryId(categoryId);
},
},
month,
}),
);
};
@@ -79,7 +86,7 @@ export function TransferModal({
const toCategory = categories.find(c => c.id === toCategoryId);
return (
<Modal name="transfer">
<Modal name={name}>
{({ state: { close } }) => (
<>
<ModalHeader
@@ -145,3 +152,4 @@ export function TransferModal({
</Modal>
);
}
TransferModal.modalName = MODAL_NAME;

View File

@@ -2,9 +2,8 @@ import React, { useCallback, useState } from 'react';
import { Trans, useTranslation } from 'react-i18next';
import { useDispatch } from 'react-redux';
import { addNotification } from 'loot-core/client/actions';
import { useGlobalPref } from '../../../hooks/useGlobalPref';
import { addNotification } from '../../../state/actions';
import { theme, styles } from '../../../style';
import { Information } from '../../alerts';
import { Button, ButtonWithLoading } from '../../common/Button2';
@@ -13,6 +12,8 @@ import { Text } from '../../common/Text';
import { View } from '../../common/View';
import { Checkbox } from '../../forms';
const MODAL_NAME = 'confirm-change-document-dir' as const;
function DirectoryDisplay({ directory }: { directory: string }) {
return (
<View style={{ flexDirection: 'row', gap: '0.5rem', width: '100%' }}>
@@ -37,13 +38,17 @@ function DirectoryDisplay({ directory }: { directory: string }) {
);
}
export function ConfirmChangeDocumentDirModal({
currentBudgetDirectory,
newDirectory,
}: {
type ConfirmChangeDocumentDirModalProps = {
name: typeof MODAL_NAME;
currentBudgetDirectory: string;
newDirectory: string;
}) {
};
export function ConfirmChangeDocumentDirModal({
name = MODAL_NAME,
currentBudgetDirectory,
newDirectory,
}: ConfirmChangeDocumentDirModalProps) {
const [error, setError] = useState('');
const [loading, setLoading] = useState(false);
const [moveFiles, setMoveFiles] = useState(false);
@@ -92,7 +97,7 @@ export function ConfirmChangeDocumentDirModal({
};
return (
<Modal name="confirm-change-document-dir">
<Modal name={name}>
{({ state: { close } }) => (
<>
<ModalHeader
@@ -206,3 +211,4 @@ export function ConfirmChangeDocumentDirModal({
</Modal>
);
}
ConfirmChangeDocumentDirModal.modalName = MODAL_NAME;

View File

@@ -2,20 +2,23 @@ import React, { useState } from 'react';
import { Trans, useTranslation } from 'react-i18next';
import { useDispatch } from 'react-redux';
import { deleteBudget } from 'loot-core/client/actions';
import { type File } from 'loot-core/src/types/file';
import { deleteBudget } from '../../../state/actions';
import { theme } from '../../../style';
import { ButtonWithLoading } from '../../common/Button2';
import { Modal, ModalCloseButton, ModalHeader } from '../../common/Modal';
import { Text } from '../../common/Text';
import { View } from '../../common/View';
const MODAL_NAME = 'delete-budget' as const;
type DeleteFileProps = {
name: typeof MODAL_NAME;
file: File;
};
export function DeleteFileModal({ file }: DeleteFileProps) {
export function DeleteFileModal({ name = MODAL_NAME, file }: DeleteFileProps) {
const { t } = useTranslation();
// If the state is "broken" that means it was created by another
@@ -29,7 +32,7 @@ export function DeleteFileModal({ file }: DeleteFileProps) {
);
return (
<Modal name="delete-budget">
<Modal name={name}>
{({ state: { close } }) => (
<>
<ModalHeader
@@ -152,3 +155,4 @@ export function DeleteFileModal({ file }: DeleteFileProps) {
</Modal>
);
}
DeleteFileModal.modalName = MODAL_NAME;

View File

@@ -2,16 +2,17 @@ import React, { useState } from 'react';
import { Trans, useTranslation } from 'react-i18next';
import { useDispatch } from 'react-redux';
import { loadAllFiles, pushModal } from 'loot-core/client/actions';
import { useGlobalPref } from '../../../hooks/useGlobalPref';
import { SvgPencil1 } from '../../../icons/v2';
import { loadAllFiles, pushModal } from '../../../state/actions';
import { theme, styles } from '../../../style';
import { Button } from '../../common/Button2';
import { Modal, ModalCloseButton, ModalHeader } from '../../common/Modal';
import { Text } from '../../common/Text';
import { View } from '../../common/View';
const MODAL_NAME = 'files-settings' as const;
function FileLocationSettings() {
const [documentDir, _setDocumentDirPref] = useGlobalPref('documentDir');
const [_documentDirChanged, setDirChanged] = useState(false);
@@ -141,7 +142,11 @@ function SelfSignedCertLocationSettings() {
);
}
export function FilesSettingsModal() {
type FilesSettingsModalProps = {
name: typeof MODAL_NAME;
};
export function FilesSettingsModal({ name }: FilesSettingsModalProps) {
const dispatch = useDispatch();
function closeModal(close: () => void) {
@@ -150,7 +155,7 @@ export function FilesSettingsModal() {
}
return (
<Modal name="files-settings">
<Modal name={name}>
{({ state: { close } }) => (
<>
<ModalHeader
@@ -188,3 +193,4 @@ export function FilesSettingsModal() {
</Modal>
);
}
FilesSettingsModal.modalName = MODAL_NAME;

View File

@@ -3,8 +3,7 @@ import React, { useState } from 'react';
import { Trans, useTranslation } from 'react-i18next';
import { useDispatch } from 'react-redux';
import { importBudget } from 'loot-core/src/client/actions/budgets';
import { importBudget } from '../../../state/actions';
import { styles, theme } from '../../../style';
import { Block } from '../../common/Block';
import { ButtonWithLoading } from '../../common/Button2';
@@ -12,6 +11,8 @@ import { Modal, ModalCloseButton, ModalHeader } from '../../common/Modal';
import { Paragraph } from '../../common/Paragraph';
import { View } from '../../common/View';
const MODAL_NAME = 'import-actual' as const;
function getErrorMessage(error: string): string {
switch (error) {
case 'parse-error':
@@ -29,7 +30,13 @@ function getErrorMessage(error: string): string {
}
}
export function ImportActualModal() {
type ImportActualModalProps = {
name: typeof MODAL_NAME;
};
export function ImportActualModal({
name = MODAL_NAME,
}: ImportActualModalProps) {
const { t } = useTranslation();
const dispatch = useDispatch();
@@ -55,7 +62,7 @@ export function ImportActualModal() {
}
return (
<Modal name="import-actual" containerProps={{ style: { width: 400 } }}>
<Modal name={name} containerProps={{ style: { width: 400 } }}>
{({ state: { close } }) => (
<>
<ModalHeader
@@ -103,3 +110,4 @@ export function ImportActualModal() {
</Modal>
);
}
ImportActualModal.modalName = MODAL_NAME;

View File

@@ -2,8 +2,7 @@ import React, { useState } from 'react';
import { Trans, useTranslation } from 'react-i18next';
import { useDispatch } from 'react-redux';
import { pushModal } from 'loot-core/client/actions';
import { pushModal } from '../../../state/actions';
import { styles, theme } from '../../../style';
import { Block } from '../../common/Block';
import { Button } from '../../common/Button2';
@@ -11,6 +10,8 @@ import { Modal, ModalCloseButton, ModalHeader } from '../../common/Modal';
import { Text } from '../../common/Text';
import { View } from '../../common/View';
const MODAL_NAME = 'import' as const;
function getErrorMessage(error: 'not-ynab4' | boolean) {
switch (error) {
case 'not-ynab4':
@@ -20,7 +21,11 @@ function getErrorMessage(error: 'not-ynab4' | boolean) {
}
}
export function ImportModal() {
type ImportModalProps = {
name: typeof MODAL_NAME;
};
export function ImportModal({ name = MODAL_NAME }: ImportModalProps) {
const { t } = useTranslation();
const dispatch = useDispatch();
@@ -50,7 +55,7 @@ export function ImportModal() {
};
return (
<Modal name="import" containerProps={{ style: { width: 400 } }}>
<Modal name={name} containerProps={{ style: { width: 400 } }}>
{({ state: { close } }) => (
<>
<ModalHeader
@@ -97,3 +102,4 @@ export function ImportModal() {
</Modal>
);
}
ImportModal.modalName = MODAL_NAME;

View File

@@ -3,8 +3,7 @@ import React, { useState } from 'react';
import { Trans, useTranslation } from 'react-i18next';
import { useDispatch } from 'react-redux';
import { importBudget } from 'loot-core/src/client/actions/budgets';
import { importBudget } from '../../../state/actions';
import { styles, theme } from '../../../style';
import { Block } from '../../common/Block';
import { ButtonWithLoading } from '../../common/Button2';
@@ -12,6 +11,8 @@ import { Modal, ModalCloseButton, ModalHeader } from '../../common/Modal';
import { Paragraph } from '../../common/Paragraph';
import { View } from '../../common/View';
const MODAL_NAME = 'import-ynab4' as const;
function getErrorMessage(error: string): string {
switch (error) {
case 'not-ynab4':
@@ -21,7 +22,11 @@ function getErrorMessage(error: string): string {
}
}
export function ImportYNAB4Modal() {
type ImportYNAB4ModalProps = {
name: typeof MODAL_NAME;
};
export function ImportYNAB4Modal({ name = MODAL_NAME }: ImportYNAB4ModalProps) {
const { t } = useTranslation();
const dispatch = useDispatch();
@@ -47,7 +52,7 @@ export function ImportYNAB4Modal() {
}
return (
<Modal name="import-ynab4" containerProps={{ style: { width: 400 } }}>
<Modal name={name} containerProps={{ style: { width: 400 } }}>
{({ state: { close } }) => (
<>
<ModalHeader
@@ -96,3 +101,4 @@ export function ImportYNAB4Modal() {
</Modal>
);
}
ImportYNAB4Modal.modalName = MODAL_NAME;

View File

@@ -3,8 +3,7 @@ import React, { useState } from 'react';
import { Trans, useTranslation } from 'react-i18next';
import { useDispatch } from 'react-redux';
import { importBudget } from 'loot-core/src/client/actions/budgets';
import { importBudget } from '../../../state/actions';
import { styles, theme } from '../../../style';
import { Block } from '../../common/Block';
import { ButtonWithLoading } from '../../common/Button2';
@@ -13,6 +12,8 @@ import { Modal, ModalCloseButton, ModalHeader } from '../../common/Modal';
import { Paragraph } from '../../common/Paragraph';
import { View } from '../../common/View';
const MODAL_NAME = 'import-ynab5' as const;
function getErrorMessage(error: string): string {
switch (error) {
case 'parse-error':
@@ -24,7 +25,11 @@ function getErrorMessage(error: string): string {
}
}
export function ImportYNAB5Modal() {
type ImportYNAB5ModalProps = {
name: typeof MODAL_NAME;
};
export function ImportYNAB5Modal({ name = MODAL_NAME }: ImportYNAB5ModalProps) {
const { t } = useTranslation();
const dispatch = useDispatch();
@@ -50,7 +55,7 @@ export function ImportYNAB5Modal() {
}
return (
<Modal name="import-ynab5" containerProps={{ style: { width: 400 } }}>
<Modal name={name} containerProps={{ style: { width: 400 } }}>
{({ state: { close } }) => (
<>
<ModalHeader
@@ -108,3 +113,4 @@ export function ImportYNAB5Modal() {
</Modal>
);
}
ImportYNAB5Modal.modalName = MODAL_NAME;

View File

@@ -5,10 +5,6 @@ import { Trans, useTranslation } from 'react-i18next';
import { useDispatch } from 'react-redux';
import { useLocation } from 'react-router-dom';
import {
addNotification,
removeNotification,
} from 'loot-core/src/client/actions';
import { useDashboard } from 'loot-core/src/client/data-hooks/dashboard';
import { useReports } from 'loot-core/src/client/data-hooks/reports';
import { send } from 'loot-core/src/platform/client/fetch';
@@ -23,6 +19,7 @@ import { useAccounts } from '../../hooks/useAccounts';
import { useFeatureFlag } from '../../hooks/useFeatureFlag';
import { useNavigate } from '../../hooks/useNavigate';
import { useResponsive } from '../../ResponsiveProvider';
import { addNotification, removeNotification } from '../../state/actions';
import { breakpoints } from '../../tokens';
import { Button } from '../common/Button2';
import { Menu } from '../common/Menu';

View File

@@ -5,7 +5,6 @@ import { useParams } from 'react-router-dom';
import * as d from 'date-fns';
import { addNotification } from 'loot-core/client/actions';
import { useWidget } from 'loot-core/client/data-hooks/widget';
import { send } from 'loot-core/src/platform/client/fetch';
import * as monthUtils from 'loot-core/src/shared/months';
@@ -19,6 +18,7 @@ import {
import { useFilters } from '../../../hooks/useFilters';
import { useNavigate } from '../../../hooks/useNavigate';
import { useResponsive } from '../../../ResponsiveProvider';
import { addNotification } from '../../../state/actions';
import { theme } from '../../../style';
import { AlignedText } from '../../common/AlignedText';
import { Block } from '../../common/Block';

View File

@@ -2,7 +2,6 @@ import React, { useEffect, useState } from 'react';
import { useDispatch } from 'react-redux';
import { send, sendCatch } from 'loot-core/platform/client/fetch/index';
import { addNotification } from 'loot-core/src/client/actions';
import { calculateHasWarning } from 'loot-core/src/client/reports';
import * as monthUtils from 'loot-core/src/shared/months';
import { type CustomReportEntity } from 'loot-core/types/models/reports';
@@ -13,6 +12,7 @@ import { useFeatureFlag } from '../../../hooks/useFeatureFlag';
import { usePayees } from '../../../hooks/usePayees';
import { useSyncedPref } from '../../../hooks/useSyncedPref';
import { SvgExclamationSolid } from '../../../icons/v1';
import { addNotification } from '../../../state/actions';
import { styles } from '../../../style/index';
import { theme } from '../../../style/theme';
import { Text } from '../../common/Text';

View File

@@ -5,7 +5,6 @@ import { useParams } from 'react-router-dom';
import * as d from 'date-fns';
import { addNotification } from 'loot-core/src/client/actions';
import { useWidget } from 'loot-core/src/client/data-hooks/widget';
import { send } from 'loot-core/src/platform/client/fetch';
import * as monthUtils from 'loot-core/src/shared/months';
@@ -16,6 +15,7 @@ import { useAccounts } from '../../../hooks/useAccounts';
import { useFilters } from '../../../hooks/useFilters';
import { useNavigate } from '../../../hooks/useNavigate';
import { useResponsive } from '../../../ResponsiveProvider';
import { addNotification } from '../../../state/actions';
import { theme, styles } from '../../../style';
import { Button } from '../../common/Button2';
import { Paragraph } from '../../common/Paragraph';

View File

@@ -5,7 +5,6 @@ import { useParams } from 'react-router-dom';
import * as d from 'date-fns';
import { addNotification } from 'loot-core/client/actions';
import { useWidget } from 'loot-core/client/data-hooks/widget';
import { send } from 'loot-core/src/platform/client/fetch';
import * as monthUtils from 'loot-core/src/shared/months';
@@ -17,6 +16,7 @@ import { useFeatureFlag } from '../../../hooks/useFeatureFlag';
import { useFilters } from '../../../hooks/useFilters';
import { useNavigate } from '../../../hooks/useNavigate';
import { useResponsive } from '../../../ResponsiveProvider';
import { addNotification } from '../../../state/actions';
import { theme, styles } from '../../../style';
import { AlignedText } from '../../common/AlignedText';
import { Block } from '../../common/Block';

View File

@@ -1,11 +1,11 @@
import React from 'react';
import { getPayeesById } from 'loot-core/src/client/reducers/queries';
import { describeSchedule } from 'loot-core/src/shared/schedules';
import { type ScheduleEntity } from 'loot-core/src/types/models';
import { usePayees } from '../../hooks/usePayees';
import { useSchedules } from '../../hooks/useSchedules';
import { getPayeesById } from '../../state/queries';
import { Value } from './Value';

View File

@@ -27,6 +27,8 @@ import { DisplayId } from '../util/DisplayId';
import { ScheduleAmountCell } from './SchedulesTable';
const MODAL_NAME = 'schedules-discover' as const;
const ROW_HEIGHT = 43;
function DiscoverSchedulesTable({
@@ -139,7 +141,13 @@ function DiscoverSchedulesTable({
);
}
export function DiscoverSchedules() {
type DiscoverSchedulesProps = {
name: typeof MODAL_NAME;
};
export function DiscoverSchedules({
name = MODAL_NAME,
}: DiscoverSchedulesProps) {
const { t } = useTranslation();
const { data, isLoading } = useSendPlatformRequest('schedule/discover');
@@ -187,10 +195,7 @@ export function DiscoverSchedules() {
}
return (
<Modal
name="schedules-discover"
containerProps={{ style: { width: 850, height: 650 } }}
>
<Modal name={name} containerProps={{ style: { width: 850, height: 650 } }}>
{({ state: { close } }) => (
<>
<ModalHeader
@@ -242,3 +247,4 @@ export function DiscoverSchedules() {
</Modal>
);
}
DiscoverSchedules.modalName = MODAL_NAME;

View File

@@ -3,10 +3,10 @@ import { Trans, useTranslation } from 'react-i18next';
import { useDispatch } from 'react-redux';
import { useLocation } from 'react-router-dom';
import { popModal } from 'loot-core/client/actions';
import { send } from 'loot-core/src/platform/client/fetch';
import { useFormatList } from '../../hooks/useFormatList';
import { popModal } from '../../state/actions';
import { theme } from '../../style';
import { Button } from '../common/Button2';
import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal';
@@ -15,7 +15,9 @@ import { Stack } from '../common/Stack';
import { Text } from '../common/Text';
import { DisplayId } from '../util/DisplayId';
export function PostsOfflineNotification() {
const MODAL_NAME = 'schedule-posts-offline-notification';
export function PostsOfflineNotification({ name = MODAL_NAME }) {
const { t } = useTranslation();
const location = useLocation();
@@ -36,7 +38,7 @@ export function PostsOfflineNotification() {
const payeeNamesList = useFormatList(payeesList, t.language);
return (
<Modal name="schedule-posts-offline-notification">
<Modal name={name}>
{({ state: { close } }) => (
<>
<ModalHeader
@@ -102,3 +104,4 @@ export function PostsOfflineNotification() {
</Modal>
);
}
PostsOfflineNotification.modalName = MODAL_NAME;

View File

@@ -4,8 +4,6 @@ import { useDispatch } from 'react-redux';
import { t } from 'i18next';
import { getPayeesById } from 'loot-core/client/reducers/queries';
import { pushModal } from 'loot-core/src/client/actions/modals';
import { runQuery, liveQuery } from 'loot-core/src/client/query-helpers';
import { send, sendCatch } from 'loot-core/src/platform/client/fetch';
import * as monthUtils from 'loot-core/src/shared/months';
@@ -15,6 +13,8 @@ import { extractScheduleConds } from 'loot-core/src/shared/schedules';
import { useDateFormat } from '../../hooks/useDateFormat';
import { usePayees } from '../../hooks/usePayees';
import { useSelected, SelectedProvider } from '../../hooks/useSelected';
import { pushModal } from '../../state/actions';
import { getPayeesById } from '../../state/queries';
import { theme } from '../../style';
import { AccountAutocomplete } from '../autocomplete/AccountAutocomplete';
import { PayeeAutocomplete } from '../autocomplete/PayeeAutocomplete';
@@ -33,6 +33,8 @@ import { SimpleTransactionsTable } from '../transactions/SimpleTransactionsTable
import { AmountInput, BetweenAmountInput } from '../util/AmountInput';
import { GenericInput } from '../util/GenericInput';
const MODAL_NAME = 'schedule-edit';
function updateScheduleConditions(schedule, fields) {
const conds = extractScheduleConds(schedule._conditions);
@@ -73,7 +75,7 @@ function updateScheduleConditions(schedule, fields) {
};
}
export function ScheduleDetails({ id, transaction }) {
export function ScheduleDetails({ name = MODAL_NAME, id, transaction }) {
const { t } = useTranslation();
const adding = id == null;
@@ -457,7 +459,7 @@ export function ScheduleDetails({ id, transaction }) {
// This is derived from the date
const repeats = state.fields.date ? !!state.fields.date.frequency : false;
return (
<Modal name="schedule-edit">
<Modal name={name}>
{({ state: { close } }) => (
<>
<ModalHeader
@@ -848,3 +850,4 @@ function NoTransactionsMessage(props) {
</View>
);
}
ScheduleDetails.modalName = MODAL_NAME;

View File

@@ -3,7 +3,6 @@ import React, { useCallback, useRef, useState } from 'react';
import { Trans, useTranslation } from 'react-i18next';
import { useDispatch } from 'react-redux';
import { pushModal } from 'loot-core/client/actions';
import { useSchedules } from 'loot-core/src/client/data-hooks/schedules';
import { send } from 'loot-core/src/platform/client/fetch';
import { type Query } from 'loot-core/src/shared/query';
@@ -13,6 +12,7 @@ import {
} from 'loot-core/src/types/models';
import { SvgAdd } from '../../icons/v0';
import { pushModal } from '../../state/actions';
import { Button } from '../common/Button2';
import { InitialFocus } from '../common/InitialFocus';
import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal';
@@ -22,17 +22,23 @@ import { View } from '../common/View';
import { ROW_HEIGHT, SchedulesTable } from './SchedulesTable';
export function ScheduleLink({
transactionIds: ids,
getTransaction,
accountName,
onScheduleLinked,
}: {
const MODAL_NAME = 'schedule-link' as const;
type ScheduleLinkProps = {
name: typeof MODAL_NAME;
transactionIds: string[];
getTransaction: (transactionId: string) => TransactionEntity;
accountName?: string;
onScheduleLinked?: (schedule: ScheduleEntity) => void;
}) {
};
export function ScheduleLink({
name = MODAL_NAME,
transactionIds: ids,
getTransaction,
accountName,
onScheduleLinked,
}: ScheduleLinkProps) {
const { t } = useTranslation();
const dispatch = useDispatch();
@@ -69,7 +75,7 @@ export function ScheduleLink({
return (
<Modal
name="schedule-link"
name={name}
containerProps={{
style: {
width: 800,
@@ -149,3 +155,4 @@ export function ScheduleLink({
</Modal>
);
}
ScheduleLink.modalName = MODAL_NAME;

View File

@@ -2,9 +2,8 @@
import React from 'react';
import { useDispatch } from 'react-redux';
import { pushModal } from 'loot-core/client/actions';
import { useMetadataPref } from '../../hooks/useMetadataPref';
import { pushModal } from '../../state/actions';
import { theme } from '../../style';
import { Button } from '../common/Button2';
import { Link } from '../common/Link';

View File

@@ -2,9 +2,7 @@ import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useDispatch, useSelector } from 'react-redux';
import { moveAccount } from 'loot-core/src/client/actions';
import * as queries from 'loot-core/src/client/queries';
import { type State } from 'loot-core/src/client/state-types';
import { type AccountEntity } from 'loot-core/types/models';
import { useAccounts } from '../../hooks/useAccounts';
@@ -14,6 +12,8 @@ import { useFailedAccounts } from '../../hooks/useFailedAccounts';
import { useLocalPref } from '../../hooks/useLocalPref';
import { useOffBudgetAccounts } from '../../hooks/useOffBudgetAccounts';
import { useUpdatedAccounts } from '../../hooks/useUpdatedAccounts';
import { type State } from '../../state';
import { moveAccount } from '../../state/actions';
import { theme } from '../../style';
import { View } from '../common/View';

View File

@@ -5,7 +5,6 @@ import { useDispatch } from 'react-redux';
import { css } from '@emotion/css';
import { Resizable } from 're-resizable';
import { closeBudget, replaceModal } from 'loot-core/src/client/actions';
import * as Platform from 'loot-core/src/client/platform';
import { useGlobalPref } from '../../hooks/useGlobalPref';
@@ -16,6 +15,7 @@ import { useResizeObserver } from '../../hooks/useResizeObserver';
import { SvgExpandArrow } from '../../icons/v0';
import { SvgAdd } from '../../icons/v1';
import { useResponsive } from '../../ResponsiveProvider';
import { closeBudget, replaceModal } from '../../state/actions';
import { styles, theme } from '../../style';
import { Button } from '../common/Button2';
import { InitialFocus } from '../common/InitialFocus';

View File

@@ -3,12 +3,12 @@ import { useHotkeys } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next';
import { useDispatch } from 'react-redux';
import { pushModal } from 'loot-core/client/actions';
import { isPreviewId } from 'loot-core/shared/transactions';
import { validForTransfer } from 'loot-core/src/client/transfer';
import { type TransactionEntity } from 'loot-core/types/models';
import { useSelectedItems } from '../../hooks/useSelected';
import { pushModal } from '../../state/actions';
import { Menu } from '../common/Menu';
import { SelectedItemsButton } from '../table';

View File

@@ -6,10 +6,6 @@ import {
parseISO,
} from 'date-fns';
import {
getAccountsById,
getCategoriesById,
} from 'loot-core/src/client/reducers/queries';
import { integerToCurrency } from 'loot-core/src/shared/util';
import { useAccounts } from '../../hooks/useAccounts';
@@ -18,6 +14,7 @@ import { useDateFormat } from '../../hooks/useDateFormat';
import { usePayees } from '../../hooks/usePayees';
import { useSelectedItems, useSelectedDispatch } from '../../hooks/useSelected';
import { SvgArrowsSynchronize } from '../../icons/v2';
import { getAccountsById, getCategoriesById } from '../../state/queries';
import { styles, theme } from '../../style';
import { Cell, Field, Row, SelectCell, Table } from '../table';
import { DisplayId } from '../util/DisplayId';

View File

@@ -1,7 +1,6 @@
import React, { useRef, useCallback, useLayoutEffect } from 'react';
import { useDispatch } from 'react-redux';
import { pushModal } from 'loot-core/client/actions';
import { send } from 'loot-core/src/platform/client/fetch';
import {
splitTransaction,
@@ -13,6 +12,7 @@ import {
import { getChangedValues, applyChanges } from 'loot-core/src/shared/util';
import { useNavigate } from '../../hooks/useNavigate';
import { pushModal } from '../../state/actions';
import { theme } from '../../style';
import { TransactionTable } from './TransactionsTable';

View File

@@ -20,13 +20,7 @@ import {
isValid as isDateValid,
} from 'date-fns';
import { pushModal } from 'loot-core/client/actions';
import { useCachedSchedules } from 'loot-core/src/client/data-hooks/schedules';
import {
getAccountsById,
getPayeesById,
getCategoriesById,
} from 'loot-core/src/client/reducers/queries';
import { evalArithmetic } from 'loot-core/src/shared/arithmetic';
import { currentDay } from 'loot-core/src/shared/months';
import * as monthUtils from 'loot-core/src/shared/months';
@@ -58,6 +52,12 @@ import {
SvgCalendar,
SvgHyperlink2,
} from '../../icons/v2';
import { pushModal } from '../../state/actions';
import {
getAccountsById,
getPayeesById,
getCategoriesById,
} from '../../state/queries';
import { styles, theme } from '../../style';
import { AccountAutocomplete } from '../autocomplete/AccountAutocomplete';
import { CategoryAutocomplete } from '../autocomplete/CategoryAutocomplete';
@@ -586,8 +586,11 @@ function PayeeCell({
onSelect={() =>
dispatch(
pushModal('payee-autocomplete', {
onSelect: payeeId => {
onUpdate('payee', payeeId);
autocompleteProps: {
value: null,
onSelect: payeeId => {
onUpdate('payee', payeeId);
},
},
}),
)

View File

@@ -1,12 +1,12 @@
// @ts-strict-ignore
import { type Store } from 'redux';
import * as sharedListeners from 'loot-core/src/client/shared-listeners';
import type { State } from 'loot-core/src/client/state-types';
import { listen } from 'loot-core/src/platform/client/fetch';
import * as undo from 'loot-core/src/platform/client/undo';
import { type BoundActions } from './hooks/useActions';
import * as sharedListeners from './sharedListeners';
import { type State } from './state';
export function handleGlobalEvents(actions: BoundActions, store: Store<State>) {
listen('server-error', () => {
@@ -21,8 +21,8 @@ export function handleGlobalEvents(actions: BoundActions, store: Store<State>) {
});
});
listen('schedules-offline', ({ payees }) => {
actions.pushModal('schedule-posts-offline-notification', { payees });
listen('schedules-offline', () => {
actions.pushModal('schedule-posts-offline-notification');
});
// This is experimental: we sync data locally automatically when

View File

@@ -1,8 +1,9 @@
import { type Dispatch } from 'loot-core/client/actions/types';
import { pushModal } from 'loot-core/src/client/actions/modals';
import { send } from 'loot-core/src/platform/client/fetch';
import { type GoCardlessToken } from 'loot-core/src/types/models';
import { type Dispatch } from './state';
import { pushModal } from './state/actions';
function _authorize(
dispatch: Dispatch,
upgradingAccountId: string | undefined,
@@ -47,12 +48,15 @@ export async function authorizeBank(
onSuccess: async data => {
dispatch(
pushModal('select-linked-accounts', {
accounts: data.accounts,
externalAccounts: data.accounts,
requisitionId: data.id,
upgradingAccountId,
// upgradingAccountId,
syncSource: 'goCardless',
}),
);
},
onClose: () => {
send('gocardless-poll-web-token-stop');
},
});
}

View File

@@ -1,8 +1,8 @@
import { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { getAccounts } from 'loot-core/src/client/actions';
import { type State } from 'loot-core/src/client/state-types';
import { type State } from '../state';
import { getAccounts } from '../state/actions';
export function useAccounts() {
const dispatch = useDispatch();

View File

@@ -4,11 +4,16 @@ import { useDispatch } from 'react-redux';
import { bindActionCreators } from 'redux';
import { type ThunkAction } from 'redux-thunk';
import * as actions from 'loot-core/src/client/actions';
import type { Action, State } from 'loot-core/src/client/state-types';
import type { State } from '../state';
import * as actions from '../state/actions';
type ActionReturnType<T extends (...args: unknown[]) => unknown> =
ReturnType<T> extends ThunkAction<infer ReturnType, State, never, Action>
ReturnType<T> extends ThunkAction<
infer ReturnType,
State,
never,
actions.Action
>
? ReturnType
: ReturnType<T>;

View File

@@ -1,8 +1,8 @@
import { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { getCategories } from 'loot-core/src/client/actions';
import { type State } from 'loot-core/src/client/state-types';
import { type State } from '../state';
import { getCategories } from '../state/actions';
export function useCategories() {
const dispatch = useDispatch();

Some files were not shown because too many files have changed in this diff Show More