From ed65805d534f95d8bb8af387a251cb760fc5f7ba Mon Sep 17 00:00:00 2001 From: Joel Jeremy Marquez <joeljeremy.marquez@gmail.com> Date: Sat, 20 Jul 2024 09:03:58 -0700 Subject: [PATCH] Port finance modals to use new Modal component based on React Aria Modal (#2946) * React Aria Modal POC * Fix imports * Use composition * Fix typecheck and lint errors * VRT * Fix schedule details modal header * Fix typecheck error + VRT * Fix typecheck error * Release notes * Update release note * Fix props * Update modal props * useModalState hook * VRT * VRT * Fix typecheck error * Fix modal close * VRT * Fix test * Fix typecheck error --- .../e2e/page-models/account-page.js | 2 +- ...ph-and-checks-visuals-1-chromium-linux.png | Bin 82514 -> 82512 bytes .../desktop-client/src/components/Modals.tsx | 166 +---- .../src/components/common/Modal.tsx | 7 +- .../src/components/common/Modal2.tsx | 460 ++++++++++++ .../components/mobile/budget/BudgetTable.jsx | 100 ++- .../src/components/mobile/budget/index.tsx | 20 + .../modals/AccountAutocompleteModal.tsx | 99 +-- .../components/modals/AccountMenuModal.tsx | 148 ++-- .../src/components/modals/BudgetListModal.tsx | 52 +- .../components/modals/BudgetPageMenuModal.tsx | 32 +- .../modals/CategoryAutocompleteModal.tsx | 107 +-- .../modals/CategoryGroupMenuModal.tsx | 166 ++--- .../components/modals/CategoryMenuModal.tsx | 151 ++-- .../components/modals/CloseAccountModal.tsx | 273 ++++---- .../modals/ConfirmCategoryDelete.tsx | 155 +++-- .../modals/ConfirmTransactionDelete.tsx | 75 +- .../modals/ConfirmTransactionEdit.tsx | 142 ++-- .../modals/ConfirmUnlinkAccount.tsx | 68 +- .../src/components/modals/CoverModal.tsx | 66 +- .../components/modals/CreateAccountModal.tsx | 380 +++++----- .../modals/CreateEncryptionKeyModal.tsx | 284 ++++---- .../modals/CreateLocalAccountModal.tsx | 252 +++---- .../src/components/modals/EditField.jsx | 77 +- .../src/components/modals/EditRule.jsx | 467 +++++++------ .../modals/FixEncryptionKeyModal.tsx | 226 +++--- .../modals/GoCardlessExternalMsg.tsx | 135 ++-- .../modals/GoCardlessInitialise.tsx | 130 ++-- .../src/components/modals/HoldBufferModal.tsx | 99 ++- .../components/modals/ImportTransactions.jsx | 586 ++++++++-------- .../modals/KeyboardShortcutModal.tsx | 305 ++++---- .../src/components/modals/LoadBackup.jsx | 130 ++-- .../components/modals/ManageRulesModal.tsx | 21 +- .../components/modals/MergeUnusedPayees.jsx | 30 +- .../src/components/modals/NotesModal.tsx | 107 ++- .../modals/PayeeAutocompleteModal.tsx | 87 +-- .../modals/ReportBalanceMenuModal.tsx | 116 ++-- .../modals/ReportBudgetMenuModal.tsx | 103 +-- .../modals/ReportBudgetMonthMenuModal.tsx | 226 +++--- .../modals/ReportBudgetSummaryModal.tsx | 49 +- .../modals/RolloverBalanceMenuModal.tsx | 118 ++-- .../modals/RolloverBudgetMenuModal.tsx | 103 +-- .../modals/RolloverBudgetMonthMenuModal.tsx | 234 +++---- .../modals/RolloverBudgetSummaryModal.tsx | 61 +- .../modals/RolloverToBudgetMenuModal.tsx | 32 +- .../modals/ScheduledTransactionMenuModal.tsx | 66 +- .../modals/SelectLinkedAccounts.jsx | 66 +- .../components/modals/SimpleFinInitialise.tsx | 98 +-- .../components/modals/SingleInputModal.tsx | 107 +-- .../src/components/modals/TransferModal.tsx | 118 ++-- .../schedules/DiscoverSchedules.tsx | 94 +-- .../schedules/PostsOfflineNotification.jsx | 120 ++-- .../components/schedules/ScheduleDetails.jsx | 655 +++++++++--------- .../src/components/schedules/ScheduleLink.tsx | 139 ++-- .../desktop-client/src/hooks/useModalState.ts | 44 ++ .../loot-core/src/client/actions/modals.ts | 4 +- .../src/client/state-types/modals.d.ts | 8 + upcoming-release-notes/2946.md | 6 + 58 files changed, 4496 insertions(+), 3676 deletions(-) create mode 100644 packages/desktop-client/src/components/common/Modal2.tsx create mode 100644 packages/desktop-client/src/hooks/useModalState.ts create mode 100644 upcoming-release-notes/2946.md diff --git a/packages/desktop-client/e2e/page-models/account-page.js b/packages/desktop-client/e2e/page-models/account-page.js index c4fb46932..aa23d0418 100644 --- a/packages/desktop-client/e2e/page-models/account-page.js +++ b/packages/desktop-client/e2e/page-models/account-page.js @@ -106,7 +106,7 @@ export class AccountPage { await this.menuButton.click(); await this.page.getByRole('button', { name: 'Close Account' }).click(); return new CloseAccountModal( - this.page.locator('css=[aria-modal]'), + this.page.getByTestId('close-account-modal'), this.page, ); } diff --git a/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-loads-cash-flow-graph-and-checks-visuals-1-chromium-linux.png b/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-loads-cash-flow-graph-and-checks-visuals-1-chromium-linux.png index 171ae0751a2e5a7e649672bbc41d46f13d599d69..7ee3aa5afa31bb300e22542905fba7cbc6769e66 100644 GIT binary patch literal 82512 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%0DIJq^|i$*OI_&%#0 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�_|017IRd8YZphx&77PLEBTwOKg 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 literal 82514 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�fYk5UnYIbA!GNizLfw)>eG)@4twpgV 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 diff --git a/packages/desktop-client/src/components/Modals.tsx b/packages/desktop-client/src/components/Modals.tsx index 01f1505f4..07711a2d2 100644 --- a/packages/desktop-client/src/components/Modals.tsx +++ b/packages/desktop-client/src/components/Modals.tsx @@ -1,17 +1,17 @@ // @ts-strict-ignore import React, { useEffect } from 'react'; -import { useSelector } from 'react-redux'; +import { useDispatch } from 'react-redux'; import { useLocation } from 'react-router-dom'; -import { type State } from 'loot-core/src/client/state-types'; +import { closeModal } from 'loot-core/client/actions'; import { type PopModalAction } from 'loot-core/src/client/state-types/modals'; import { send } from 'loot-core/src/platform/client/fetch'; import * as monthUtils from 'loot-core/src/shared/months'; -import { useActions } from '../hooks/useActions'; +import { useModalState } from '../hooks/useModalState'; import { useSyncServerStatus } from '../hooks/useSyncServerStatus'; -import { ModalTitle } from './common/Modal'; +import { ModalTitle, ModalHeader } from './common/Modal2'; import { AccountAutocompleteModal } from './modals/AccountAutocompleteModal'; import { AccountMenuModal } from './modals/AccountMenuModal'; import { BudgetListModal } from './modals/BudgetListModal'; @@ -71,67 +71,43 @@ export type CommonModalProps = { }; export function Modals() { - const modalStack = useSelector((state: State) => state.modals.modalStack); - const isHidden = useSelector((state: State) => state.modals.isHidden); - const actions = useActions(); const location = useLocation(); + const dispatch = useDispatch(); + const { modalStack } = useModalState(); useEffect(() => { if (modalStack.length > 0) { - actions.closeModal(); + dispatch(closeModal()); } }, [location]); const syncServerStatus = useSyncServerStatus(); const modals = modalStack - .map(({ name, options }, idx) => { - const modalProps: CommonModalProps = { - onClose: actions.popModal, - onBack: actions.popModal, - showBack: idx > 0, - isCurrent: idx === modalStack.length - 1, - isHidden, - stackIndex: idx, - }; - + .map(({ name, options }) => { switch (name) { case 'keyboard-shortcuts': - return <KeyboardShortcutModal modalProps={modalProps} />; + return <KeyboardShortcutModal />; case 'import-transactions': - return ( - <ImportTransactions - key={name} - modalProps={modalProps} - options={options} - /> - ); + return <ImportTransactions key={name} options={options} />; case 'add-account': return ( <CreateAccountModal key={name} - modalProps={modalProps} syncServerStatus={syncServerStatus} upgradingAccountId={options?.upgradingAccountId} /> ); case 'add-local-account': - return ( - <CreateLocalAccountModal - key={name} - modalProps={modalProps} - actions={actions} - /> - ); + return <CreateLocalAccountModal key={name} />; case 'close-account': return ( <CloseAccountModal key={name} - modalProps={modalProps} account={options.account} balance={options.balance} canDelete={options.canDelete} @@ -142,10 +118,8 @@ export function Modals() { return ( <SelectLinkedAccounts key={name} - modalProps={modalProps} externalAccounts={options.accounts} requisitionId={options.requisitionId} - actions={actions} syncSource={options.syncSource} /> ); @@ -154,7 +128,6 @@ export function Modals() { return ( <ConfirmCategoryDelete key={name} - modalProps={modalProps} category={options.category} group={options.group} onDelete={options.onDelete} @@ -165,7 +138,6 @@ export function Modals() { return ( <ConfirmUnlinkAccount key={name} - modalProps={modalProps} accountName={options.accountName} onUnlink={options.onUnlink} /> @@ -175,7 +147,6 @@ export function Modals() { return ( <ConfirmTransactionEdit key={name} - modalProps={modalProps} onCancel={options.onCancel} onConfirm={options.onConfirm} confirmReason={options.confirmReason} @@ -186,7 +157,6 @@ export function Modals() { return ( <ConfirmTransactionDelete key={name} - modalProps={modalProps} onConfirm={options.onConfirm} /> ); @@ -197,26 +167,17 @@ export function Modals() { key={name} watchUpdates budgetId={options.budgetId} - modalProps={modalProps} - actions={actions} backupDisabled={false} /> ); case 'manage-rules': - return ( - <ManageRulesModal - key={name} - modalProps={modalProps} - payeeId={options?.payeeId} - /> - ); + return <ManageRulesModal key={name} payeeId={options?.payeeId} />; case 'edit-rule': return ( <EditRule key={name} - modalProps={modalProps} defaultRule={options.rule} onSave={options.onSave} /> @@ -226,7 +187,6 @@ export function Modals() { return ( <MergeUnusedPayees key={name} - modalProps={modalProps} payeeIds={options.payeeIds} targetPayeeId={options.targetPayeeId} /> @@ -234,27 +194,18 @@ export function Modals() { case 'gocardless-init': return ( - <GoCardlessInitialise - key={name} - modalProps={modalProps} - onSuccess={options.onSuccess} - /> + <GoCardlessInitialise key={name} onSuccess={options.onSuccess} /> ); case 'simplefin-init': return ( - <SimpleFinInitialise - key={name} - modalProps={modalProps} - onSuccess={options.onSuccess} - /> + <SimpleFinInitialise key={name} onSuccess={options.onSuccess} /> ); case 'gocardless-external-msg': return ( <GoCardlessExternalMsg key={name} - modalProps={modalProps} onMoveExternal={options.onMoveExternal} onClose={() => { options.onClose?.(); @@ -265,28 +216,15 @@ export function Modals() { ); case 'create-encryption-key': - return ( - <CreateEncryptionKeyModal - key={name} - modalProps={modalProps} - options={options} - /> - ); + return <CreateEncryptionKeyModal key={name} options={options} />; case 'fix-encryption-key': - return ( - <FixEncryptionKeyModal - key={name} - modalProps={modalProps} - options={options} - /> - ); + return <FixEncryptionKeyModal key={name} options={options} />; case 'edit-field': return ( <EditField key={name} - modalProps={modalProps} name={options.name} onSubmit={options.onSubmit} onClose={options.onClose} @@ -297,7 +235,6 @@ export function Modals() { return ( <CategoryAutocompleteModal key={name} - modalProps={modalProps} autocompleteProps={{ value: null, onSelect: options.onSelect, @@ -313,7 +250,6 @@ export function Modals() { return ( <AccountAutocompleteModal key={name} - modalProps={modalProps} autocompleteProps={{ value: null, onSelect: options.onSelect, @@ -327,7 +263,6 @@ export function Modals() { return ( <PayeeAutocompleteModal key={name} - modalProps={modalProps} autocompleteProps={{ value: null, onSelect: options.onSelect, @@ -340,8 +275,13 @@ export function Modals() { return ( <SingleInputModal key={name} - modalProps={modalProps} - title={<ModalTitle title="New Category" shrinkOnOverflow />} + name={name} + Header={props => ( + <ModalHeader + {...props} + title={<ModalTitle title="New Category" shrinkOnOverflow />} + /> + )} inputPlaceholder="Category name" buttonText="Add" onValidate={options.onValidate} @@ -353,8 +293,15 @@ export function Modals() { return ( <SingleInputModal key={name} - modalProps={modalProps} - title={<ModalTitle title="New Category Group" shrinkOnOverflow />} + name={name} + Header={props => ( + <ModalHeader + {...props} + title={ + <ModalTitle title="New Category Group" shrinkOnOverflow /> + } + /> + )} inputPlaceholder="Category group name" buttonText="Add" onValidate={options.onValidate} @@ -370,7 +317,6 @@ export function Modals() { > <RolloverBudgetSummaryModal key={name} - modalProps={modalProps} month={options.month} onBudgetAction={options.onBudgetAction} /> @@ -378,21 +324,13 @@ export function Modals() { ); case 'report-budget-summary': - return ( - <ReportBudgetSummaryModal - key={name} - modalProps={modalProps} - month={options.month} - /> - ); + return <ReportBudgetSummaryModal key={name} month={options.month} />; case 'schedule-edit': return ( <ScheduleDetails key={name} - modalProps={modalProps} id={options?.id || null} - actions={actions} transaction={options?.transaction || null} /> ); @@ -401,36 +339,21 @@ export function Modals() { return ( <ScheduleLink key={name} - modalProps={modalProps} - actions={actions} transactionIds={options?.transactionIds} getTransaction={options?.getTransaction} /> ); case 'schedules-discover': - return ( - <DiscoverSchedules - key={name} - modalProps={modalProps} - actions={actions} - /> - ); + return <DiscoverSchedules key={name} />; case 'schedule-posts-offline-notification': - return ( - <PostsOfflineNotification - key={name} - modalProps={modalProps} - actions={actions} - /> - ); + return <PostsOfflineNotification key={name} />; case 'account-menu': return ( <AccountMenuModal key={name} - modalProps={modalProps} accountId={options.accountId} onSave={options.onSave} onEditNotes={options.onEditNotes} @@ -444,11 +367,11 @@ export function Modals() { return ( <CategoryMenuModal key={name} - modalProps={modalProps} categoryId={options.categoryId} onSave={options.onSave} onEditNotes={options.onEditNotes} onDelete={options.onDelete} + onToggleVisibility={options.onToggleVisibility} onClose={options.onClose} /> ); @@ -460,7 +383,6 @@ export function Modals() { value={monthUtils.sheetForMonth(options.month)} > <RolloverBudgetMenuModal - modalProps={modalProps} categoryId={options.categoryId} onUpdateBudget={options.onUpdateBudget} onCopyLastMonthAverage={options.onCopyLastMonthAverage} @@ -477,7 +399,6 @@ export function Modals() { value={monthUtils.sheetForMonth(options.month)} > <ReportBudgetMenuModal - modalProps={modalProps} categoryId={options.categoryId} onUpdateBudget={options.onUpdateBudget} onCopyLastMonthAverage={options.onCopyLastMonthAverage} @@ -491,13 +412,13 @@ export function Modals() { return ( <CategoryGroupMenuModal key={name} - modalProps={modalProps} groupId={options.groupId} onSave={options.onSave} onAddCategory={options.onAddCategory} onEditNotes={options.onEditNotes} onSaveNotes={options.onSaveNotes} onDelete={options.onDelete} + onToggleVisibility={options.onToggleVisibility} onClose={options.onClose} /> ); @@ -506,7 +427,6 @@ export function Modals() { return ( <NotesModal key={name} - modalProps={modalProps} id={options.id} name={options.name} onSave={options.onSave} @@ -520,7 +440,6 @@ export function Modals() { value={monthUtils.sheetForMonth(options.month)} > <RolloverBalanceMenuModal - modalProps={modalProps} categoryId={options.categoryId} onCarryover={options.onCarryover} onTransfer={options.onTransfer} @@ -536,7 +455,6 @@ export function Modals() { value={monthUtils.sheetForMonth(options.month)} > <RolloverToBudgetMenuModal - modalProps={modalProps} onTransfer={options.onTransfer} onCover={options.onCover} onHoldBuffer={options.onHoldBuffer} @@ -552,7 +470,6 @@ export function Modals() { value={monthUtils.sheetForMonth(options.month)} > <HoldBufferModal - modalProps={modalProps} month={options.month} onSubmit={options.onSubmit} /> @@ -566,7 +483,6 @@ export function Modals() { value={monthUtils.sheetForMonth(options.month)} > <ReportBalanceMenuModal - modalProps={modalProps} categoryId={options.categoryId} onCarryover={options.onCarryover} /> @@ -577,7 +493,6 @@ export function Modals() { return ( <TransferModal key={name} - modalProps={modalProps} title={options.title} month={options.month} amount={options.amount} @@ -590,7 +505,6 @@ export function Modals() { return ( <CoverModal key={name} - modalProps={modalProps} title={options.title} month={options.month} showToBeBudgeted={options.showToBeBudgeted} @@ -602,7 +516,6 @@ export function Modals() { return ( <ScheduledTransactionMenuModal key={name} - modalProps={modalProps} transactionId={options.transactionId} onPost={options.onPost} onSkip={options.onSkip} @@ -613,7 +526,6 @@ export function Modals() { return ( <BudgetPageMenuModal key={name} - modalProps={modalProps} onAddCategoryGroup={options.onAddCategoryGroup} onToggleHiddenCategories={options.onToggleHiddenCategories} onSwitchBudgetFile={options.onSwitchBudgetFile} @@ -627,7 +539,6 @@ export function Modals() { value={monthUtils.sheetForMonth(options.month)} > <RolloverBudgetMonthMenuModal - modalProps={modalProps} month={options.month} onBudgetAction={options.onBudgetAction} onEditNotes={options.onEditNotes} @@ -642,7 +553,6 @@ export function Modals() { value={monthUtils.sheetForMonth(options.month)} > <ReportBudgetMonthMenuModal - modalProps={modalProps} month={options.month} onBudgetAction={options.onBudgetAction} onEditNotes={options.onEditNotes} @@ -651,7 +561,7 @@ export function Modals() { ); case 'budget-list': - return <BudgetListModal key={name} modalProps={modalProps} />; + return <BudgetListModal key={name} />; default: console.error('Unknown modal:', name); diff --git a/packages/desktop-client/src/components/common/Modal.tsx b/packages/desktop-client/src/components/common/Modal.tsx index 802bac03d..e3a65d694 100644 --- a/packages/desktop-client/src/components/common/Modal.tsx +++ b/packages/desktop-client/src/components/common/Modal.tsx @@ -322,7 +322,8 @@ type ModalButtonsProps = { children: ReactNode; }; -export const ModalButtons = ({ +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const ModalButtons = ({ style, leftContent, focusButton = false, @@ -367,7 +368,7 @@ type ModalTitleProps = { shrinkOnOverflow?: boolean; }; -export function ModalTitle({ +function ModalTitle({ title, isEditable, getStyle, @@ -466,7 +467,7 @@ type ModalCloseButtonProps = { style?: CSSProperties; }; -export function ModalCloseButton({ onClick, style }: ModalCloseButtonProps) { +function ModalCloseButton({ onClick, style }: ModalCloseButtonProps) { return ( <Button type="bare" diff --git a/packages/desktop-client/src/components/common/Modal2.tsx b/packages/desktop-client/src/components/common/Modal2.tsx new file mode 100644 index 000000000..84fab17c4 --- /dev/null +++ b/packages/desktop-client/src/components/common/Modal2.tsx @@ -0,0 +1,460 @@ +import React, { + useEffect, + useRef, + useLayoutEffect, + useState, + type ReactNode, + type ComponentPropsWithoutRef, + type ComponentPropsWithRef, +} from 'react'; +import { + ModalOverlay as ReactAriaModalOverlay, + Modal as ReactAriaModal, + Dialog, +} from 'react-aria-components'; +import { useHotkeysContext } from 'react-hotkeys-hook'; + +import { AutoTextSize } from 'auto-text-size'; + +import { useModalState } from '../../hooks/useModalState'; +import { AnimatedLoading } from '../../icons/AnimatedLoading'; +import { SvgLogo } from '../../icons/logo'; +import { SvgDelete } from '../../icons/v0'; +import { type CSSProperties, styles, theme } from '../../style'; +import { tokens } from '../../tokens'; + +import { Button } from './Button'; +import { Input } from './Input'; +import { Text } from './Text'; +import { TextOneLine } from './TextOneLine'; +import { View } from './View'; + +type ModalProps = ComponentPropsWithRef<typeof ReactAriaModal> & { + name: string; + isLoading?: boolean; + noAnimation?: boolean; + style?: CSSProperties; + onClose?: () => void; + containerProps?: { + style?: CSSProperties; + }; +}; + +export const Modal = ({ + name, + isLoading = false, + noAnimation = false, + style, + children, + onClose, + containerProps, + ...props +}: ModalProps) => { + const { enableScope, disableScope } = useHotkeysContext(); + + // This deactivates any key handlers in the "app" scope + useEffect(() => { + enableScope(name); + return () => disableScope(name); + }, [enableScope, disableScope, name]); + + const { isHidden, isActive, onClose: closeModal } = useModalState(); + + const handleOnClose = () => { + closeModal(); + onClose?.(); + }; + + return ( + <ReactAriaModalOverlay + data-testid={`${name}-modal`} + isDismissable + defaultOpen={true} + onOpenChange={isOpen => !isOpen && handleOnClose?.()} + style={{ + position: 'fixed', + inset: 0, + zIndex: 3000, + overflowY: 'auto', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + fontSize: 14, + ...style, + }} + {...props} + > + <ReactAriaModal> + {modalProps => ( + <Dialog aria-label="Modal dialog"> + <ModalContentContainer + noAnimation={noAnimation} + isActive={isActive(name)} + {...containerProps} + style={{ + flex: 1, + padding: 10, + willChange: 'opacity, transform', + maxWidth: '90vw', + minWidth: '90vw', + maxHeight: '90vh', + minHeight: 0, + borderRadius: 6, + //border: '1px solid ' + theme.modalBorder, + color: theme.pageText, + backgroundColor: theme.modalBackground, + opacity: isHidden ? 0 : 1, + [`@media (min-width: ${tokens.breakpoint_small})`]: { + minWidth: tokens.breakpoint_small, + }, + ...styles.shadowLarge, + ...styles.lightScrollbar, + ...containerProps?.style, + }} + > + <View style={{ paddingTop: 0, flex: 1 }}> + {typeof children === 'function' + ? children(modalProps) + : children} + </View> + {isLoading && ( + <View + style={{ + position: 'absolute', + top: 0, + left: 0, + right: 0, + bottom: 0, + backgroundColor: theme.pageBackground, + alignItems: 'center', + justifyContent: 'center', + zIndex: 1000, + }} + > + <AnimatedLoading + style={{ width: 20, height: 20 }} + color={theme.pageText} + /> + </View> + )} + </ModalContentContainer> + </Dialog> + )} + </ReactAriaModal> + </ReactAriaModalOverlay> + ); +}; + +type ModalContentContainerProps = { + style?: CSSProperties; + noAnimation?: boolean; + isActive?: boolean; + children: ReactNode; +}; + +const ModalContentContainer = ({ + style, + noAnimation, + isActive, + children, +}: ModalContentContainerProps) => { + const contentRef = useRef<HTMLDivElement>(null); + const mounted = useRef(false); + const rotateFactor = useRef(Math.random() * 10 - 5); + + useLayoutEffect(() => { + if (!contentRef.current) { + return; + } + + function setProps() { + if (!contentRef.current) { + return; + } + + if (isActive) { + contentRef.current.style.transform = 'translateY(0px) scale(1)'; + contentRef.current.style.pointerEvents = 'auto'; + } else { + contentRef.current.style.transform = `translateY(-40px) scale(.95) rotate(${rotateFactor.current}deg)`; + contentRef.current.style.pointerEvents = 'none'; + } + } + + if (!mounted.current) { + if (noAnimation) { + contentRef.current.style.opacity = '1'; + contentRef.current.style.transform = 'translateY(0px) scale(1)'; + + setTimeout(() => { + if (contentRef.current) { + contentRef.current.style.transition = + 'opacity .1s, transform .1s cubic-bezier(.42, 0, .58, 1)'; + } + }, 0); + } else { + contentRef.current.style.opacity = '0'; + contentRef.current.style.transform = 'translateY(10px) scale(1)'; + + setTimeout(() => { + if (contentRef.current) { + mounted.current = true; + contentRef.current.style.transition = + 'opacity .1s, transform .1s cubic-bezier(.42, 0, .58, 1)'; + contentRef.current.style.opacity = '1'; + setProps(); + } + }, 0); + } + } else { + setProps(); + } + }, [noAnimation, isActive]); + + return ( + <View + innerRef={contentRef} + style={{ + ...style, + ...(noAnimation && !isActive && { display: 'none' }), + }} + > + {children} + </View> + ); +}; + +type ModalButtonsProps = { + style?: CSSProperties; + leftContent?: ReactNode; + focusButton?: boolean; + children: ReactNode; +}; + +export const ModalButtons = ({ + style, + leftContent, + focusButton = false, + children, +}: ModalButtonsProps) => { + const containerRef = useRef<HTMLDivElement>(null); + + useEffect(() => { + if (focusButton && containerRef.current) { + const button = containerRef.current.querySelector<HTMLButtonElement>( + 'button:not([data-hidden])', + ); + + if (button) { + button.focus(); + } + } + }, [focusButton]); + + return ( + <View + innerRef={containerRef} + style={{ + flexDirection: 'row', + marginTop: 30, + ...style, + }} + > + {leftContent} + <View style={{ flex: 1 }} /> + {children} + </View> + ); +}; + +type ModalHeaderProps = { + leftContent?: ReactNode; + showLogo?: boolean; + title?: ReactNode; + rightContent?: ReactNode; +}; + +export function ModalHeader({ + leftContent, + showLogo, + title, + rightContent, +}: ModalHeaderProps) { + return ( + <View + aria-label="Modal header" + style={{ + justifyContent: 'center', + alignItems: 'center', + position: 'relative', + height: 60, + }} + > + <View + style={{ + position: 'absolute', + left: 0, + }} + > + {leftContent} + </View> + + {(title || showLogo) && ( + <View + style={{ + textAlign: 'center', + // We need to force a width for the text-overflow + // ellipses to work because we are aligning center. + width: 'calc(100% - 60px)', + }} + > + {showLogo && ( + <SvgLogo + width={30} + height={30} + style={{ justifyContent: 'center', alignSelf: 'center' }} + /> + )} + {title && + (typeof title === 'string' || typeof title === 'number' ? ( + <ModalTitle title={`${title}`} /> + ) : ( + title + ))} + </View> + )} + + {rightContent && ( + <View + style={{ + position: 'absolute', + right: 0, + }} + > + {rightContent} + </View> + )} + </View> + ); +} + +type ModalTitleProps = { + title: string; + isEditable?: boolean; + getStyle?: (isEditing: boolean) => CSSProperties; + onEdit?: (isEditing: boolean) => void; + onTitleUpdate?: (newName: string) => void; + shrinkOnOverflow?: boolean; +}; + +export function ModalTitle({ + title, + isEditable, + getStyle, + onTitleUpdate, + shrinkOnOverflow = false, +}: ModalTitleProps) { + const [isEditing, setIsEditing] = useState(false); + + const onTitleClick = () => { + if (isEditable) { + setIsEditing(true); + } + }; + + const _onTitleUpdate = (newTitle: string) => { + if (newTitle !== title) { + onTitleUpdate?.(newTitle); + } + setIsEditing(false); + }; + + const inputRef = useRef<HTMLInputElement>(null); + useEffect(() => { + if (isEditing) { + if (inputRef.current) { + inputRef.current.scrollLeft = 0; + } + } + }, [isEditing]); + + const style = getStyle?.(isEditing); + + return isEditing ? ( + <Input + inputRef={inputRef} + style={{ + fontSize: 25, + fontWeight: 700, + textAlign: 'center', + ...style, + }} + focused={isEditing} + defaultValue={title} + onUpdate={_onTitleUpdate} + onKeyDown={e => { + if (e.key === 'Enter') { + e.preventDefault(); + _onTitleUpdate?.(e.currentTarget.value); + } + }} + /> + ) : ( + <View + style={{ + flexDirection: 'row', + justifyContent: 'center', + alignItems: 'center', + }} + > + {shrinkOnOverflow ? ( + <AutoTextSize + as={Text} + minFontSizePx={15} + maxFontSizePx={25} + onClick={onTitleClick} + style={{ + fontSize: 25, + fontWeight: 700, + textAlign: 'center', + ...(isEditable && styles.underlinedText), + ...style, + }} + > + {title} + </AutoTextSize> + ) : ( + <TextOneLine + onClick={onTitleClick} + style={{ + fontSize: 25, + fontWeight: 700, + textAlign: 'center', + ...(isEditable && styles.underlinedText), + ...style, + }} + > + {title} + </TextOneLine> + )} + </View> + ); +} + +type ModalCloseButtonProps = { + onClick: ComponentPropsWithoutRef<typeof Button>['onClick']; + style?: CSSProperties; +}; + +export function ModalCloseButton({ onClick, style }: ModalCloseButtonProps) { + return ( + <Button + type="bare" + onClick={onClick} + style={{ padding: '10px 10px' }} + aria-label="Close" + > + <SvgDelete width={10} style={style} /> + </Button> + ); +} diff --git a/packages/desktop-client/src/components/mobile/budget/BudgetTable.jsx b/packages/desktop-client/src/components/mobile/budget/BudgetTable.jsx index 7da6005d2..4bac2bee7 100644 --- a/packages/desktop-client/src/components/mobile/budget/BudgetTable.jsx +++ b/packages/desktop-client/src/components/mobile/budget/BudgetTable.jsx @@ -70,7 +70,10 @@ function ToBudget({ toBudget, onClick, show3Cols }) { <Button type="bare" style={{ maxWidth: sidebarColumnWidth }} - onClick={onClick} + onPointerUp={e => { + e.stopPropagation(); + onClick?.(); + }} > <View> <Label @@ -140,7 +143,10 @@ function Saved({ projected, onClick, show3Cols }) { <Button type="bare" style={{ maxWidth: sidebarColumnWidth }} - onClick={onClick} + onPointerUp={e => { + e.stopPropagation(); + onClick?.(); + }} > <View> <View> @@ -267,7 +273,10 @@ function BudgetCell({ type="financial" getStyle={makeAmountGrey} data-testid={name} - onClick={onOpenCategoryBudgetMenu} + onPointerUp={e => { + e.stopPropagation(); + onOpenCategoryBudgetMenu(); + }} {...props} /> ); @@ -436,7 +445,10 @@ const ExpenseCategory = memo(function ExpenseCategory({ style={{ maxWidth: sidebarColumnWidth, }} - onClick={() => onEdit?.(category.id)} + onPointerUp={e => { + e.stopPropagation(); + onEdit?.(category.id); + }} > <View style={{ @@ -525,7 +537,10 @@ const ExpenseCategory = memo(function ExpenseCategory({ binding={spent} getStyle={makeAmountGrey} type="financial" - onClick={onShowActivity} + onPointerUp={e => { + e.stopPropagation(); + onShowActivity(); + }} formatter={value => ( <Button type="bare" @@ -560,7 +575,13 @@ const ExpenseCategory = memo(function ExpenseCategory({ width: columnWidth, }} > - <span role="button" onClick={() => onOpenBalanceMenu?.()}> + <span + role="button" + onPointerUp={e => { + e.stopPropagation(); + onOpenBalanceMenu(); + }} + > <BalanceWithCarryover carryover={carryover} balance={balance} @@ -710,7 +731,10 @@ const ExpenseGroupHeader = memo(function ExpenseGroupHeader({ hoveredStyle={{ backgroundColor: 'transparent', }} - onClick={() => onToggleCollapse?.(group.id)} + onPointerUp={e => { + e.stopPropagation(); + onToggleCollapse?.(group.id); + }} > <SvgExpandArrow width={8} @@ -727,7 +751,10 @@ const ExpenseGroupHeader = memo(function ExpenseGroupHeader({ style={{ maxWidth: sidebarColumnWidth, }} - onClick={() => onEdit?.(group.id)} + onPointerUp={e => { + e.stopPropagation(); + onEdit?.(group.id); + }} > <View style={{ @@ -862,7 +889,7 @@ const ExpenseGroupHeader = memo(function ExpenseGroupHeader({ {/* {editMode && ( <View> <Button - onClick={() => onAddCategory(group.id, group.is_income)} + onPointerUp={() => onAddCategory(group.id, group.is_income)} style={{ padding: 10 }} > <Add width={15} height={15} /> @@ -937,7 +964,10 @@ const IncomeGroupHeader = memo(function IncomeGroupHeader({ hoveredStyle={{ backgroundColor: 'transparent', }} - onClick={() => onToggleCollapse?.(group.id)} + onPointerUp={e => { + e.stopPropagation(); + onToggleCollapse?.(group.id); + }} > <SvgExpandArrow width={8} @@ -954,7 +984,10 @@ const IncomeGroupHeader = memo(function IncomeGroupHeader({ style={{ maxWidth: sidebarColumnWidth, }} - onClick={() => onEdit?.(group.id)} + onPointerUp={e => { + e.stopPropagation(); + onEdit?.(group.id); + }} > <View style={{ @@ -1100,7 +1133,10 @@ const IncomeCategory = memo(function IncomeCategory({ style={{ maxWidth: sidebarColumnWidth, }} - onClick={() => onEdit?.(category.id)} + onPointerUp={e => { + e.stopPropagation(); + onEdit?.(category.id); + }} > <View style={{ @@ -1225,20 +1261,20 @@ const IncomeCategory = memo(function IncomeCategory({ // <MathOperations emitter={emitter} /> // <View style={{ flex: 1 }} /> // <Button -// onClick={() => emitter.emit('moveUp')} +// onPointerUp={() => emitter.emit('moveUp')} // style={{ marginRight: 5 }} // data-testid="up" // > // <ArrowThinUp width={13} height={13} /> // </Button> // <Button -// onClick={() => emitter.emit('moveDown')} +// onPointerUp={() => emitter.emit('moveDown')} // style={{ marginRight: 5 }} // data-testid="down" // > // <ArrowThinDown width={13} height={13} /> // </Button> -// <Button onClick={() => emitter.emit('done')} data-testid="done"> +// <Button onPointerUp={() => emitter.emit('done')} data-testid="done"> // Done // </Button> // </View> @@ -1627,7 +1663,10 @@ export function BudgetTable({ }} hoveredStyle={noBackgroundColorStyle} activeStyle={noBackgroundColorStyle} - onClick={() => onOpenBudgetPageMenu?.()} + onPointerUp={e => { + e.stopPropagation(); + onOpenBudgetPageMenu?.(); + }} > <SvgLogo width="20" height="20" /> <SvgCheveronRight @@ -1751,7 +1790,10 @@ function BudgetTableHeader({ <Button type="bare" disabled={show3Cols} - onClick={toggleSpentColumn} + onPointerUp={e => { + e.stopPropagation(); + toggleSpentColumn(); + }} style={buttonStyle} > <View style={{ alignItems: 'flex-end' }}> @@ -1812,7 +1854,10 @@ function BudgetTableHeader({ <Button type="bare" disabled={show3Cols} - onClick={toggleSpentColumn} + onPointerUp={e => { + e.stopPropagation(); + toggleSpentColumn(); + }} style={buttonStyle} > <View style={{ alignItems: 'flex-end' }}> @@ -1926,7 +1971,12 @@ function MonthSelector({ > <Button type="bare" - onClick={prevEnabled && onPrevMonth} + onPointerUp={e => { + e.stopPropagation(); + if (prevEnabled) { + onPrevMonth(); + } + }} style={{ ...styles.noTapHighlight, ...arrowButtonStyle, @@ -1949,13 +1999,21 @@ function MonthSelector({ margin: '0 5px', ...styles.underlinedText, }} - onClick={() => onOpenMonthMenu?.(month)} + onPointerUp={e => { + e.stopPropagation(); + onOpenMonthMenu?.(month); + }} > {monthUtils.format(month, 'MMMM ‘yy')} </Text> <Button type="bare" - onClick={nextEnabled && onNextMonth} + onPointerUp={e => { + e.stopPropagation(); + if (nextEnabled) { + onNextMonth(); + } + }} style={{ ...styles.noTapHighlight, ...arrowButtonStyle, diff --git a/packages/desktop-client/src/components/mobile/budget/index.tsx b/packages/desktop-client/src/components/mobile/budget/index.tsx index 9fcc4ab9a..91e846115 100644 --- a/packages/desktop-client/src/components/mobile/budget/index.tsx +++ b/packages/desktop-client/src/components/mobile/budget/index.tsx @@ -169,6 +169,15 @@ function BudgetInner(props: BudgetInnerProps) { } }; + const onToggleGroupVisibility = groupId => { + const group = categoryGroups.find(g => g.id === groupId); + onSaveGroup({ + ...group, + hidden: !!!group.hidden, + }); + dispatch(collapseModals('category-group-menu')); + }; + const onSaveCategory = category => { dispatch(updateCategory(category)); }; @@ -196,6 +205,15 @@ function BudgetInner(props: BudgetInnerProps) { } }; + const onToggleCategoryVisibility = categoryId => { + const category = categories.find(c => c.id === categoryId); + onSaveCategory({ + ...category, + hidden: !!!category.hidden, + }); + dispatch(collapseModals('category-menu')); + }; + const onReorderCategory = (id, { inGroup, aroundCategory }) => { let groupId, targetId; @@ -323,6 +341,7 @@ function BudgetInner(props: BudgetInnerProps) { onAddCategory: onOpenNewCategoryModal, onEditNotes: onOpenCategoryGroupNotesModal, onDelete: onDeleteGroup, + onToggleVisibility: onToggleGroupVisibility, }), ); }; @@ -335,6 +354,7 @@ function BudgetInner(props: BudgetInnerProps) { onSave: onSaveCategory, onEditNotes: onOpenCategoryNotesModal, onDelete: onDeleteCategory, + onToggleVisibility: onToggleCategoryVisibility, onBudgetAction, }), ); diff --git a/packages/desktop-client/src/components/modals/AccountAutocompleteModal.tsx b/packages/desktop-client/src/components/modals/AccountAutocompleteModal.tsx index 025c9a265..0c1d06a00 100644 --- a/packages/desktop-client/src/components/modals/AccountAutocompleteModal.tsx +++ b/packages/desktop-client/src/components/modals/AccountAutocompleteModal.tsx @@ -3,27 +3,24 @@ import React, { type ComponentPropsWithoutRef } from 'react'; import { useResponsive } from '../../ResponsiveProvider'; import { theme } from '../../style'; import { AccountAutocomplete } from '../autocomplete/AccountAutocomplete'; -import { ModalCloseButton, Modal, ModalTitle } from '../common/Modal'; +import { + ModalCloseButton, + Modal, + ModalTitle, + ModalHeader, +} from '../common/Modal2'; import { View } from '../common/View'; import { SectionLabel } from '../forms'; -import { type CommonModalProps } from '../Modals'; type AccountAutocompleteModalProps = { - modalProps: CommonModalProps; autocompleteProps: ComponentPropsWithoutRef<typeof AccountAutocomplete>; onClose: () => void; }; export function AccountAutocompleteModal({ - modalProps, autocompleteProps, onClose, }: AccountAutocompleteModalProps) { - const _onClose = () => { - modalProps.onClose(); - onClose?.(); - }; - const { isNarrowWidth } = useResponsive(); const defaultAutocompleteProps = { containerProps: { style: { height: isNarrowWidth ? '90vh' : 275 } }, @@ -31,51 +28,57 @@ export function AccountAutocompleteModal({ return ( <Modal - title={ - <ModalTitle - title="Account" - getStyle={() => ({ color: theme.menuAutoCompleteText })} - /> - } + name="account-autocomplete" noAnimation={!isNarrowWidth} - showHeader={isNarrowWidth} - focusAfterClose={false} - {...modalProps} - onClose={_onClose} - style={{ - height: isNarrowWidth ? '85vh' : 275, - backgroundColor: theme.menuAutoCompleteBackground, + onClose={onClose} + containerProps={{ + style: { + height: isNarrowWidth ? '85vh' : 275, + backgroundColor: theme.menuAutoCompleteBackground, + }, }} - CloseButton={props => ( - <ModalCloseButton - {...props} - style={{ color: theme.menuAutoCompleteText }} - /> - )} > - {() => ( - <View> - {!isNarrowWidth && ( - <SectionLabel - title="Account" - style={{ - alignSelf: 'center', - color: theme.menuAutoCompleteText, - marginBottom: 10, - }} + {({ state: { close } }) => ( + <> + {isNarrowWidth && ( + <ModalHeader + title={ + <ModalTitle + title="Account" + getStyle={() => ({ color: theme.menuAutoCompleteText })} + /> + } + rightContent={ + <ModalCloseButton + onClick={close} + style={{ color: theme.menuAutoCompleteText }} + /> + } /> )} - <View style={{ flex: 1 }}> - <AccountAutocomplete - focused={true} - embedded={true} - closeOnBlur={false} - onClose={_onClose} - {...defaultAutocompleteProps} - {...autocompleteProps} - /> + <View> + {!isNarrowWidth && ( + <SectionLabel + title="Account" + style={{ + alignSelf: 'center', + color: theme.menuAutoCompleteText, + marginBottom: 10, + }} + /> + )} + <View style={{ flex: 1 }}> + <AccountAutocomplete + focused={true} + embedded={true} + closeOnBlur={false} + onClose={close} + {...defaultAutocompleteProps} + {...autocompleteProps} + /> + </View> </View> - </View> + </> )} </Modal> ); diff --git a/packages/desktop-client/src/components/modals/AccountMenuModal.tsx b/packages/desktop-client/src/components/modals/AccountMenuModal.tsx index 44fbafb33..2c4299e29 100644 --- a/packages/desktop-client/src/components/modals/AccountMenuModal.tsx +++ b/packages/desktop-client/src/components/modals/AccountMenuModal.tsx @@ -9,14 +9,17 @@ import { SvgNotesPaper } from '../../icons/v2'; import { type CSSProperties, styles, theme } from '../../style'; import { Button } from '../common/Button2'; import { Menu } from '../common/Menu'; -import { Modal, ModalTitle } from '../common/Modal'; +import { + Modal, + ModalCloseButton, + ModalHeader, + ModalTitle, +} from '../common/Modal2'; import { Popover } from '../common/Popover'; import { View } from '../common/View'; -import { type CommonModalProps } from '../Modals'; import { Notes } from '../Notes'; type AccountMenuModalProps = { - modalProps: CommonModalProps; accountId: string; onSave: (account: AccountEntity) => void; onCloseAccount: (accountId: string) => void; @@ -26,7 +29,6 @@ type AccountMenuModalProps = { }; export function AccountMenuModal({ - modalProps, accountId, onSave, onCloseAccount, @@ -37,11 +39,6 @@ export function AccountMenuModal({ const account = useAccount(accountId); const originalNotes = useNotes(`account-${accountId}`); - const _onClose = () => { - modalProps?.onClose(); - onClose?.(); - }; - const onRename = (newName: string) => { if (!account) { return; @@ -77,69 +74,84 @@ export function AccountMenuModal({ return ( <Modal - title={ - <ModalTitle isEditable title={account.name} onTitleUpdate={onRename} /> - } - showHeader - focusAfterClose={false} - {...modalProps} - onClose={_onClose} - style={{ - height: '45vh', + name="account-menu" + onClose={onClose} + containerProps={{ + style: { + height: '45vh', + }, }} - leftHeaderContent={ - <AdditionalAccountMenu - account={account} - onClose={onCloseAccount} - onReopen={onReopenAccount} - /> - } > - <View - style={{ - flex: 1, - flexDirection: 'column', - }} - > - <View - style={{ - overflowY: 'auto', - flex: 1, - }} - > - <Notes - notes={ - originalNotes && originalNotes.length > 0 - ? originalNotes - : 'No notes' + {({ state: { close } }) => ( + <> + <ModalHeader + leftContent={ + <AdditionalAccountMenu + account={account} + onClose={onCloseAccount} + onReopen={onReopenAccount} + /> + } + title={ + <ModalTitle + isEditable + title={account.name} + onTitleUpdate={onRename} + /> } - editable={false} - focused={false} - getStyle={() => ({ - borderRadius: 6, - ...((!originalNotes || originalNotes.length === 0) && { - justifySelf: 'center', - alignSelf: 'center', - color: theme.pageTextSubdued, - }), - })} + rightContent={<ModalCloseButton onClick={close} />} /> - </View> - <View - style={{ - flexDirection: 'row', - flexWrap: 'wrap', - justifyContent: 'space-between', - alignContent: 'space-between', - paddingTop: 10, - }} - > - <Button style={buttonStyle} onPress={_onEditNotes}> - <SvgNotesPaper width={20} height={20} style={{ paddingRight: 5 }} /> - Edit notes - </Button> - </View> - </View> + <View + style={{ + flex: 1, + flexDirection: 'column', + }} + > + <View + style={{ + overflowY: 'auto', + flex: 1, + }} + > + <Notes + notes={ + originalNotes && originalNotes.length > 0 + ? originalNotes + : 'No notes' + } + editable={false} + focused={false} + getStyle={() => ({ + borderRadius: 6, + ...((!originalNotes || originalNotes.length === 0) && { + justifySelf: 'center', + alignSelf: 'center', + color: theme.pageTextSubdued, + }), + })} + /> + </View> + <View + style={{ + flexDirection: 'row', + flexWrap: 'wrap', + justifyContent: 'space-between', + alignContent: 'space-between', + paddingTop: 10, + }} + > + <Button style={buttonStyle} onPress={_onEditNotes}> + <SvgNotesPaper + width={20} + height={20} + style={{ paddingRight: 5 }} + /> + Edit notes + </Button> + </View> + </View> + </> + )} </Modal> ); } diff --git a/packages/desktop-client/src/components/modals/BudgetListModal.tsx b/packages/desktop-client/src/components/modals/BudgetListModal.tsx index 5d5906dfe..af44d7960 100644 --- a/packages/desktop-client/src/components/modals/BudgetListModal.tsx +++ b/packages/desktop-client/src/components/modals/BudgetListModal.tsx @@ -2,42 +2,42 @@ import React from 'react'; import { useSelector } from 'react-redux'; import { useLocalPref } from '../../hooks/useLocalPref'; -import { Modal } from '../common/Modal'; +import { Modal, ModalHeader, ModalCloseButton } from '../common/Modal2'; import { Text } from '../common/Text'; import { View } from '../common/View'; import { BudgetList } from '../manager/BudgetList'; -import { type CommonModalProps } from '../Modals'; -type BudgetListModalProps = { - modalProps: CommonModalProps; -}; - -export function BudgetListModal({ modalProps }: BudgetListModalProps) { +export function BudgetListModal() { const [id] = useLocalPref('id'); const currentFile = useSelector(state => state.budgets.allFiles?.find(f => 'id' in f && f.id === id), ); return ( - <Modal - title="Switch Budget File" - showHeader - focusAfterClose={false} - {...modalProps} - > - <View - style={{ - justifyContent: 'center', - alignItems: 'center', - margin: '20px 0', - }} - > - <Text style={{ fontSize: 17, fontWeight: 400 }}>Switching from:</Text> - <Text style={{ fontSize: 17, fontWeight: 700 }}> - {currentFile?.name} - </Text> - </View> - <BudgetList showHeader={false} quickSwitchMode={true} /> + <Modal name="budget-list"> + {({ state: { close } }) => ( + <> + <ModalHeader + title="Switch Budget File" + rightContent={<ModalCloseButton onClick={close} />} + /> + <View + style={{ + justifyContent: 'center', + alignItems: 'center', + margin: '20px 0', + }} + > + <Text style={{ fontSize: 17, fontWeight: 400 }}> + Switching from: + </Text> + <Text style={{ fontSize: 17, fontWeight: 700 }}> + {currentFile?.name} + </Text> + </View> + <BudgetList showHeader={false} quickSwitchMode={true} /> + </> + )} </Modal> ); } diff --git a/packages/desktop-client/src/components/modals/BudgetPageMenuModal.tsx b/packages/desktop-client/src/components/modals/BudgetPageMenuModal.tsx index b6b164e52..43f943f41 100644 --- a/packages/desktop-client/src/components/modals/BudgetPageMenuModal.tsx +++ b/packages/desktop-client/src/components/modals/BudgetPageMenuModal.tsx @@ -3,17 +3,11 @@ import React, { type ComponentPropsWithoutRef } from 'react'; import { useLocalPref } from '../../hooks/useLocalPref'; import { type CSSProperties, theme, styles } from '../../style'; import { Menu } from '../common/Menu'; -import { Modal } from '../common/Modal'; -import { type CommonModalProps } from '../Modals'; +import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal2'; -type BudgetPageMenuModalProps = ComponentPropsWithoutRef< - typeof BudgetPageMenu -> & { - modalProps: CommonModalProps; -}; +type BudgetPageMenuModalProps = ComponentPropsWithoutRef<typeof BudgetPageMenu>; export function BudgetPageMenuModal({ - modalProps, onAddCategoryGroup, onToggleHiddenCategories, onSwitchBudgetFile, @@ -26,13 +20,21 @@ export function BudgetPageMenuModal({ }; return ( - <Modal showHeader focusAfterClose={false} {...modalProps}> - <BudgetPageMenu - getItemStyle={() => defaultMenuItemStyle} - onAddCategoryGroup={onAddCategoryGroup} - onToggleHiddenCategories={onToggleHiddenCategories} - onSwitchBudgetFile={onSwitchBudgetFile} - /> + <Modal name="budget-page-menu"> + {({ state: { close } }) => ( + <> + <ModalHeader + showLogo + rightContent={<ModalCloseButton onClick={close} />} + /> + <BudgetPageMenu + getItemStyle={() => defaultMenuItemStyle} + onAddCategoryGroup={onAddCategoryGroup} + onToggleHiddenCategories={onToggleHiddenCategories} + onSwitchBudgetFile={onSwitchBudgetFile} + /> + </> + )} </Modal> ); } diff --git a/packages/desktop-client/src/components/modals/CategoryAutocompleteModal.tsx b/packages/desktop-client/src/components/modals/CategoryAutocompleteModal.tsx index e158b63f1..cfdab70bf 100644 --- a/packages/desktop-client/src/components/modals/CategoryAutocompleteModal.tsx +++ b/packages/desktop-client/src/components/modals/CategoryAutocompleteModal.tsx @@ -5,30 +5,27 @@ import * as monthUtils from 'loot-core/src/shared/months'; import { useResponsive } from '../../ResponsiveProvider'; import { theme } from '../../style'; import { CategoryAutocomplete } from '../autocomplete/CategoryAutocomplete'; -import { ModalCloseButton, Modal, ModalTitle } from '../common/Modal'; +import { + ModalCloseButton, + Modal, + ModalTitle, + ModalHeader, +} from '../common/Modal2'; import { View } from '../common/View'; import { SectionLabel } from '../forms'; -import { type CommonModalProps } from '../Modals'; import { NamespaceContext } from '../spreadsheet/NamespaceContext'; type CategoryAutocompleteModalProps = { - modalProps: CommonModalProps; autocompleteProps: ComponentPropsWithoutRef<typeof CategoryAutocomplete>; onClose: () => void; month?: string; }; export function CategoryAutocompleteModal({ - modalProps, autocompleteProps, month, onClose, }: CategoryAutocompleteModalProps) { - const _onClose = () => { - modalProps.onClose(); - onClose?.(); - }; - const { isNarrowWidth } = useResponsive(); const defaultAutocompleteProps = { @@ -37,56 +34,62 @@ export function CategoryAutocompleteModal({ return ( <Modal - title={ - <ModalTitle - title="Category" - getStyle={() => ({ color: theme.menuAutoCompleteText })} - /> - } + name="category-autocomplete" noAnimation={!isNarrowWidth} - showHeader={isNarrowWidth} - focusAfterClose={false} - {...modalProps} - onClose={_onClose} - style={{ - height: isNarrowWidth ? '85vh' : 275, - backgroundColor: theme.menuAutoCompleteBackground, + onClose={onClose} + containerProps={{ + style: { + height: isNarrowWidth ? '85vh' : 275, + backgroundColor: theme.menuAutoCompleteBackground, + }, }} - CloseButton={props => ( - <ModalCloseButton - {...props} - style={{ color: theme.menuAutoCompleteText }} - /> - )} > - {() => ( - <View> - {!isNarrowWidth && ( - <SectionLabel - title="Category" - style={{ - alignSelf: 'center', - color: theme.menuAutoCompleteText, - marginBottom: 10, - }} + {({ state: { close } }) => ( + <> + {isNarrowWidth && ( + <ModalHeader + title={ + <ModalTitle + title="Category" + getStyle={() => ({ color: theme.menuAutoCompleteText })} + /> + } + rightContent={ + <ModalCloseButton + onClick={close} + style={{ color: theme.menuAutoCompleteText }} + /> + } /> )} - <View style={{ flex: 1 }}> - <NamespaceContext.Provider - value={month ? monthUtils.sheetForMonth(month) : ''} - > - <CategoryAutocomplete - focused={true} - embedded={true} - closeOnBlur={false} - showSplitOption={false} - onClose={_onClose} - {...defaultAutocompleteProps} - {...autocompleteProps} + <View> + {!isNarrowWidth && ( + <SectionLabel + title="Category" + style={{ + alignSelf: 'center', + color: theme.menuAutoCompleteText, + marginBottom: 10, + }} /> - </NamespaceContext.Provider> + )} + <View style={{ flex: 1 }}> + <NamespaceContext.Provider + value={month ? monthUtils.sheetForMonth(month) : ''} + > + <CategoryAutocomplete + focused={true} + embedded={true} + closeOnBlur={false} + showSplitOption={false} + onClose={close} + {...defaultAutocompleteProps} + {...autocompleteProps} + /> + </NamespaceContext.Provider> + </View> </View> - </View> + </> )} </Modal> ); diff --git a/packages/desktop-client/src/components/modals/CategoryGroupMenuModal.tsx b/packages/desktop-client/src/components/modals/CategoryGroupMenuModal.tsx index 4989e6a0c..d155b21d8 100644 --- a/packages/desktop-client/src/components/modals/CategoryGroupMenuModal.tsx +++ b/packages/desktop-client/src/components/modals/CategoryGroupMenuModal.tsx @@ -10,41 +10,40 @@ import { SvgNotesPaper, SvgViewHide, SvgViewShow } from '../../icons/v2'; import { type CSSProperties, styles, theme } from '../../style'; import { Button } from '../common/Button2'; import { Menu } from '../common/Menu'; -import { Modal, ModalTitle } from '../common/Modal'; +import { + Modal, + ModalCloseButton, + ModalHeader, + ModalTitle, +} from '../common/Modal2'; import { Popover } from '../common/Popover'; import { View } from '../common/View'; -import { type CommonModalProps } from '../Modals'; import { Notes } from '../Notes'; type CategoryGroupMenuModalProps = { - modalProps: CommonModalProps; 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({ - modalProps, groupId, onSave, onAddCategory, onEditNotes, onDelete, + onToggleVisibility, onClose, }: CategoryGroupMenuModalProps) { const { grouped: categoryGroups } = useCategories(); const group = categoryGroups.find(g => g.id === groupId); const notes = useNotes(group.id); - const _onClose = () => { - modalProps?.onClose(); - onClose?.(); - }; - const onRename = newName => { if (newName !== group.name) { onSave?.({ @@ -62,18 +61,14 @@ export function CategoryGroupMenuModal({ onEditNotes?.(group.id); }; - const _onToggleVisibility = () => { - onSave?.({ - ...group, - hidden: !!!group.hidden, - }); - _onClose(); - }; - const _onDelete = () => { onDelete?.(group.id); }; + const _onToggleVisibility = () => { + onToggleVisibility?.(group.id); + }; + const buttonStyle: CSSProperties = { ...styles.mediumText, height: styles.mobileMinHeight, @@ -86,70 +81,85 @@ export function CategoryGroupMenuModal({ return ( <Modal - title={ - <ModalTitle isEditable title={group.name} onTitleUpdate={onRename} /> - } - showHeader - focusAfterClose={false} - {...modalProps} - onClose={_onClose} - style={{ - height: '45vh', + name="category-group-menu" + onClose={onClose} + containerProps={{ + style: { + height: '45vh', + }, }} - leftHeaderContent={ - <AdditionalCategoryGroupMenu - group={group} - onDelete={_onDelete} - onToggleVisibility={_onToggleVisibility} - /> - } > - <View - style={{ - flex: 1, - flexDirection: 'column', - }} - > - <View - style={{ - overflowY: 'auto', - flex: 1, - }} - > - <Notes - notes={notes?.length > 0 ? notes : 'No notes'} - editable={false} - focused={false} - getStyle={() => ({ - ...styles.mediumText, - borderRadius: 6, - ...((!notes || notes.length === 0) && { - justifySelf: 'center', - alignSelf: 'center', - color: theme.pageTextSubdued, - }), - })} + {({ state: { close } }) => ( + <> + <ModalHeader + leftContent={ + <AdditionalCategoryGroupMenu + group={group} + onDelete={_onDelete} + onToggleVisibility={_onToggleVisibility} + /> + } + title={ + <ModalTitle + isEditable + title={group.name} + onTitleUpdate={onRename} + /> + } + rightContent={<ModalCloseButton onClick={close} />} /> - </View> - <View - style={{ - flexDirection: 'row', - flexWrap: 'wrap', - justifyContent: 'space-between', - alignContent: 'space-between', - paddingTop: 10, - }} - > - <Button style={buttonStyle} onPress={_onAddCategory}> - <SvgAdd width={17} height={17} style={{ paddingRight: 5 }} /> - Add category - </Button> - <Button style={buttonStyle} onPress={_onEditNotes}> - <SvgNotesPaper width={20} height={20} style={{ paddingRight: 5 }} /> - Edit notes - </Button> - </View> - </View> + <View + style={{ + flex: 1, + flexDirection: 'column', + }} + > + <View + style={{ + overflowY: 'auto', + flex: 1, + }} + > + <Notes + notes={notes?.length > 0 ? notes : 'No notes'} + editable={false} + focused={false} + getStyle={() => ({ + ...styles.mediumText, + borderRadius: 6, + ...((!notes || notes.length === 0) && { + justifySelf: 'center', + alignSelf: 'center', + color: theme.pageTextSubdued, + }), + })} + /> + </View> + <View + style={{ + flexDirection: 'row', + flexWrap: 'wrap', + justifyContent: 'space-between', + alignContent: 'space-between', + paddingTop: 10, + }} + > + <Button style={buttonStyle} onPress={_onAddCategory}> + <SvgAdd width={17} height={17} style={{ paddingRight: 5 }} /> + Add category + </Button> + <Button style={buttonStyle} onPress={_onEditNotes}> + <SvgNotesPaper + width={20} + height={20} + style={{ paddingRight: 5 }} + /> + Edit notes + </Button> + </View> + </View> + </> + )} </Modal> ); } diff --git a/packages/desktop-client/src/components/modals/CategoryMenuModal.tsx b/packages/desktop-client/src/components/modals/CategoryMenuModal.tsx index 9451de7cb..c69919627 100644 --- a/packages/desktop-client/src/components/modals/CategoryMenuModal.tsx +++ b/packages/desktop-client/src/components/modals/CategoryMenuModal.tsx @@ -11,36 +11,36 @@ import { SvgNotesPaper, SvgViewHide, SvgViewShow } from '../../icons/v2'; import { type CSSProperties, styles, theme } from '../../style'; import { Button } from '../common/Button2'; import { Menu } from '../common/Menu'; -import { Modal, ModalTitle } from '../common/Modal'; +import { + Modal, + ModalCloseButton, + ModalHeader, + ModalTitle, +} from '../common/Modal2'; import { Popover } from '../common/Popover'; import { View } from '../common/View'; -import { type CommonModalProps } from '../Modals'; import { Notes } from '../Notes'; type CategoryMenuModalProps = { - modalProps: CommonModalProps; categoryId: string; onSave: (category: CategoryEntity) => void; - onEditNotes: (id: string) => void; + onEditNotes: (categoryId: string) => void; onDelete: (categoryId: string) => void; + onToggleVisibility: (categoryId: string) => void; onClose?: () => void; }; export function CategoryMenuModal({ - modalProps, categoryId, onSave, onEditNotes, onDelete, + onToggleVisibility, onClose, }: CategoryMenuModalProps) { const category = useCategory(categoryId); const categoryGroup = useCategoryGroup(category?.cat_group); const originalNotes = useNotes(category.id); - const _onClose = () => { - modalProps?.onClose(); - onClose?.(); - }; const onRename = newName => { if (newName !== category.name) { @@ -52,11 +52,7 @@ export function CategoryMenuModal({ }; const _onToggleVisibility = () => { - onSave?.({ - ...category, - hidden: !category.hidden, - }); - _onClose(); + onToggleVisibility?.(category.id); }; const _onEditNotes = () => { @@ -77,66 +73,79 @@ export function CategoryMenuModal({ return ( <Modal - title={ - <ModalTitle isEditable title={category.name} onTitleUpdate={onRename} /> - } - showHeader - focusAfterClose={false} - {...modalProps} - onClose={_onClose} - style={{ - height: '45vh', + name="category-menu" + onClose={onClose} + containerProps={{ + style: { height: '45vh' }, }} - leftHeaderContent={ - <AdditionalCategoryMenu - category={category} - categoryGroup={categoryGroup} - onDelete={_onDelete} - onToggleVisibility={_onToggleVisibility} - /> - } > - <View - style={{ - flex: 1, - flexDirection: 'column', - }} - > - <View - style={{ - overflowY: 'auto', - flex: 1, - }} - > - <Notes - notes={originalNotes?.length > 0 ? originalNotes : 'No notes'} - editable={false} - focused={false} - getStyle={() => ({ - borderRadius: 6, - ...((!originalNotes || originalNotes.length === 0) && { - justifySelf: 'center', - alignSelf: 'center', - color: theme.pageTextSubdued, - }), - })} + {({ state: { close } }) => ( + <> + <ModalHeader + leftContent={ + <AdditionalCategoryMenu + category={category} + categoryGroup={categoryGroup} + onDelete={_onDelete} + onToggleVisibility={_onToggleVisibility} + /> + } + title={ + <ModalTitle + isEditable + title={category.name} + onTitleUpdate={onRename} + /> + } + rightContent={<ModalCloseButton onClick={close} />} /> - </View> - <View - style={{ - flexDirection: 'row', - flexWrap: 'wrap', - justifyContent: 'space-between', - alignContent: 'space-between', - paddingTop: 10, - }} - > - <Button style={buttonStyle} onPress={_onEditNotes}> - <SvgNotesPaper width={20} height={20} style={{ paddingRight: 5 }} /> - Edit notes - </Button> - </View> - </View> + <View + style={{ + flex: 1, + flexDirection: 'column', + }} + > + <View + style={{ + overflowY: 'auto', + flex: 1, + }} + > + <Notes + notes={originalNotes?.length > 0 ? originalNotes : 'No notes'} + editable={false} + focused={false} + getStyle={() => ({ + borderRadius: 6, + ...((!originalNotes || originalNotes.length === 0) && { + justifySelf: 'center', + alignSelf: 'center', + color: theme.pageTextSubdued, + }), + })} + /> + </View> + <View + style={{ + flexDirection: 'row', + flexWrap: 'wrap', + justifyContent: 'space-between', + alignContent: 'space-between', + paddingTop: 10, + }} + > + <Button style={buttonStyle} onPress={_onEditNotes}> + <SvgNotesPaper + width={20} + height={20} + style={{ paddingRight: 5 }} + /> + Edit notes + </Button> + </View> + </View> + </> + )} </Modal> ); } diff --git a/packages/desktop-client/src/components/modals/CloseAccountModal.tsx b/packages/desktop-client/src/components/modals/CloseAccountModal.tsx index 93c87019f..c1668a701 100644 --- a/packages/desktop-client/src/components/modals/CloseAccountModal.tsx +++ b/packages/desktop-client/src/components/modals/CloseAccountModal.tsx @@ -20,11 +20,10 @@ import { CategoryAutocomplete } from '../autocomplete/CategoryAutocomplete'; import { Button } from '../common/Button2'; import { FormError } from '../common/FormError'; import { Link } from '../common/Link'; -import { Modal } from '../common/Modal'; +import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal2'; import { Paragraph } from '../common/Paragraph'; import { Text } from '../common/Text'; import { View } from '../common/View'; -import { type CommonModalProps } from '../Modals'; function needsCategory( account: AccountEntity, @@ -43,14 +42,12 @@ type CloseAccountModalProps = { account: AccountEntity; balance: number; canDelete: boolean; - modalProps: CommonModalProps; }; export function CloseAccountModal({ account, balance, canDelete, - modalProps, }: CloseAccountModalProps) { const accounts = useAccounts().filter(a => a.closed === 0); const { grouped: categoryGroups, list: categories } = useCategories(); @@ -103,163 +100,173 @@ export function CloseAccountModal({ dispatch( closeAccount(account.id, transferAccountId || null, categoryId || null), ); - modalProps.onClose(); } }; return ( <Modal - title="Close Account" - {...modalProps} - style={{ flex: 0 }} - loading={loading} + name="close-account" + isLoading={loading} + containerProps={{ style: { width: '30vw' } }} > - {() => ( - <View> - <Paragraph> - Are you sure you want to close <strong>{account.name}</strong>?{' '} - {canDelete ? ( - <span> - This account has no transactions so it will be permanently - deleted. - </span> - ) : ( - <span> - This account has transactions so we can’t permanently delete it. - </span> - )} - </Paragraph> - <Form onSubmit={onSubmit}> - {balance !== 0 && ( - <View> - <Paragraph> - This account has a balance of{' '} - <strong>{integerToCurrency(balance)}</strong>. To close this - account, select a different account to transfer this balance - to: - </Paragraph> - - <View style={{ marginBottom: 15 }}> - <AccountAutocomplete - includeClosedAccounts={false} - value={transferAccountId} - inputProps={{ - placeholder: 'Select account...', - ...(isNarrowWidth && { - value: transferAccount?.name || '', - style: { - ...narrowStyle, - }, - onClick: () => { - dispatch( - pushModal('account-autocomplete', { - includeClosedAccounts: false, - onSelect: onSelectAccount, - }), - ); - }, - }), - }} - onSelect={onSelectAccount} - /> - </View> - - {transferError && ( - <FormError style={{ marginBottom: 15 }}> - Transfer is required - </FormError> - )} + {({ state: { close } }) => ( + <> + <ModalHeader + title="Close Account" + rightContent={<ModalCloseButton onClick={close} />} + /> + <View> + <Paragraph> + Are you sure you want to close <strong>{account.name}</strong>?{' '} + {canDelete ? ( + <span> + This account has no transactions so it will be permanently + deleted. + </span> + ) : ( + <span> + This account has transactions so we can’t permanently delete + it. + </span> + )} + </Paragraph> + <Form + onSubmit={e => { + onSubmit(e); + close(); + }} + > + {balance !== 0 && ( + <View> + <Paragraph> + This account has a balance of{' '} + <strong>{integerToCurrency(balance)}</strong>. To close this + account, select a different account to transfer this balance + to: + </Paragraph> - {needsCategory(account, transferAccountId, accounts) && ( <View style={{ marginBottom: 15 }}> - <Paragraph> - Since you are transferring the balance from a budgeted - account to an off-budget account, this transaction must be - categorized. Select a category: - </Paragraph> - - <CategoryAutocomplete - categoryGroups={categoryGroups} - value={categoryId} + <AccountAutocomplete + includeClosedAccounts={false} + value={transferAccountId} inputProps={{ - placeholder: 'Select category...', + placeholder: 'Select account...', ...(isNarrowWidth && { - value: category?.name || '', + value: transferAccount?.name || '', style: { ...narrowStyle, }, onClick: () => { dispatch( - pushModal('category-autocomplete', { - categoryGroups, - showHiddenCategories: true, - onSelect: onSelectCategory, + pushModal('account-autocomplete', { + includeClosedAccounts: false, + onSelect: onSelectAccount, }), ); }, }), }} - onSelect={onSelectCategory} + onSelect={onSelectAccount} /> - - {categoryError && ( - <FormError>Category is required</FormError> - )} </View> - )} - </View> - )} - {!canDelete && ( - <View style={{ marginBottom: 15 }}> - <Text style={{ fontSize: 12 }}> - You can also{' '} - <Link - variant="text" - onClick={() => { - setLoading(true); + {transferError && ( + <FormError style={{ marginBottom: 15 }}> + Transfer is required + </FormError> + )} - dispatch(forceCloseAccount(account.id)); - modalProps.onClose(); - }} - style={{ color: theme.errorText }} - > - force close - </Link>{' '} - the account which will delete it and all its transactions - permanently. Doing so may change your budget unexpectedly - since money in it may vanish. - </Text> - </View> - )} + {needsCategory(account, transferAccountId, accounts) && ( + <View style={{ marginBottom: 15 }}> + <Paragraph> + Since you are transferring the balance from a budgeted + account to an off-budget account, this transaction must + be categorized. Select a category: + </Paragraph> - <View - style={{ - flexDirection: 'row', - justifyContent: 'flex-end', - }} - > - <Button - style={{ - marginRight: 10, - height: isNarrowWidth ? styles.mobileMinHeight : undefined, - }} - onPress={modalProps.onClose} - > - Cancel - </Button> - <Button - type="submit" - variant="primary" + <CategoryAutocomplete + categoryGroups={categoryGroups} + value={categoryId} + inputProps={{ + placeholder: 'Select category...', + ...(isNarrowWidth && { + value: category?.name || '', + style: { + ...narrowStyle, + }, + onClick: () => { + dispatch( + pushModal('category-autocomplete', { + categoryGroups, + showHiddenCategories: true, + onSelect: onSelectCategory, + }), + ); + }, + }), + }} + onSelect={onSelectCategory} + /> + + {categoryError && ( + <FormError>Category is required</FormError> + )} + </View> + )} + </View> + )} + + {!canDelete && ( + <View style={{ marginBottom: 15 }}> + <Text style={{ fontSize: 12 }}> + You can also{' '} + <Link + variant="text" + onClick={() => { + setLoading(true); + + dispatch(forceCloseAccount(account.id)); + close(); + }} + style={{ color: theme.errorText }} + > + force close + </Link>{' '} + the account which will delete it and all its transactions + permanently. Doing so may change your budget unexpectedly + since money in it may vanish. + </Text> + </View> + )} + + <View style={{ - height: isNarrowWidth ? styles.mobileMinHeight : undefined, + flexDirection: 'row', + justifyContent: 'flex-end', }} > - Close Account - </Button> - </View> - </Form> - </View> + <Button + style={{ + marginRight: 10, + height: isNarrowWidth ? styles.mobileMinHeight : undefined, + }} + onPress={close} + > + Cancel + </Button> + <Button + type="submit" + variant="primary" + style={{ + height: isNarrowWidth ? styles.mobileMinHeight : undefined, + }} + > + Close Account + </Button> + </View> + </Form> + </View> + </> )} </Modal> ); diff --git a/packages/desktop-client/src/components/modals/ConfirmCategoryDelete.tsx b/packages/desktop-client/src/components/modals/ConfirmCategoryDelete.tsx index a3726e0b9..7713a6a09 100644 --- a/packages/desktop-client/src/components/modals/ConfirmCategoryDelete.tsx +++ b/packages/desktop-client/src/components/modals/ConfirmCategoryDelete.tsx @@ -6,20 +6,17 @@ import { theme } from '../../style'; import { CategoryAutocomplete } from '../autocomplete/CategoryAutocomplete'; import { Block } from '../common/Block'; import { Button } from '../common/Button2'; -import { Modal } from '../common/Modal'; +import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal2'; import { Text } from '../common/Text'; import { View } from '../common/View'; -import { type CommonModalProps } from '../Modals'; type ConfirmCategoryDeleteProps = { - modalProps: CommonModalProps; category: string; group: string; onDelete: (categoryId: string) => void; }; export function ConfirmCategoryDelete({ - modalProps, group: groupId, category: categoryId, onDelete, @@ -56,82 +53,92 @@ export function ConfirmCategoryDelete({ const isIncome = !!(category || group).is_income; return ( - <Modal title="Confirm Delete" {...modalProps} style={{ flex: 0 }}> - {() => ( - <View style={{ lineHeight: 1.5 }}> - {group ? ( - <Block> - Categories in the group <strong>{group.name}</strong> are used by - existing transaction - {!isIncome && - ' or it has a positive leftover balance currently'}.{' '} - <strong>Are you sure you want to delete it?</strong> If so, you - must select another category to transfer existing transactions and - balance to. - </Block> - ) : ( - <Block> - <strong>{category.name}</strong> is used by existing transactions - {!isIncome && - ' or it has a positive leftover balance currently'}.{' '} - <strong>Are you sure you want to delete it?</strong> If so, you - must select another category to transfer existing transactions and - balance to. - </Block> - )} + <Modal + name="confirm-category-delete" + containerProps={{ style: { width: '30vw' } }} + > + {({ state: { close } }) => ( + <> + <ModalHeader + title="Confirm Delete" + rightContent={<ModalCloseButton onClick={close} />} + /> + <View style={{ lineHeight: 1.5 }}> + {group ? ( + <Block> + Categories in the group <strong>{group.name}</strong> are used + by existing transaction + {!isIncome && + ' or it has a positive leftover balance currently'} + . <strong>Are you sure you want to delete it?</strong> If so, + you must select another category to transfer existing + transactions and balance to. + </Block> + ) : ( + <Block> + <strong>{category.name}</strong> is used by existing + transactions + {!isIncome && + ' or it has a positive leftover balance currently'} + . <strong>Are you sure you want to delete it?</strong> If so, + you must select another category to transfer existing + transactions and balance to. + </Block> + )} - {error && renderError(error)} + {error && renderError(error)} - <View - style={{ - marginTop: 20, - flexDirection: 'row', - justifyContent: 'flex-start', - alignItems: 'center', - }} - > - <Text>Transfer to:</Text> + <View + style={{ + marginTop: 20, + flexDirection: 'row', + justifyContent: 'flex-start', + alignItems: 'center', + }} + > + <Text>Transfer to:</Text> + + <View style={{ flex: 1, marginLeft: 10, marginRight: 30 }}> + <CategoryAutocomplete + categoryGroups={ + group + ? categoryGroups.filter( + g => g.id !== group.id && !!g.is_income === isIncome, + ) + : categoryGroups + .filter(g => !!g.is_income === isIncome) + .map(g => ({ + ...g, + categories: g.categories.filter( + c => c.id !== category.id, + ), + })) + } + value={transferCategory} + inputProps={{ + placeholder: 'Select category...', + }} + onSelect={category => setTransferCategory(category)} + showHiddenCategories={true} + /> + </View> - <View style={{ flex: 1, marginLeft: 10, marginRight: 30 }}> - <CategoryAutocomplete - categoryGroups={ - group - ? categoryGroups.filter( - g => g.id !== group.id && !!g.is_income === isIncome, - ) - : categoryGroups - .filter(g => !!g.is_income === isIncome) - .map(g => ({ - ...g, - categories: g.categories.filter( - c => c.id !== category.id, - ), - })) - } - value={transferCategory} - inputProps={{ - placeholder: 'Select category...', + <Button + variant="primary" + onPress={() => { + if (!transferCategory) { + setError('required-transfer'); + } else { + onDelete(transferCategory); + close(); + } }} - onSelect={category => setTransferCategory(category)} - showHiddenCategories={true} - /> + > + Delete + </Button> </View> - - <Button - variant="primary" - onPress={() => { - if (!transferCategory) { - setError('required-transfer'); - } else { - onDelete(transferCategory); - modalProps.onClose(); - } - }} - > - Delete - </Button> </View> - </View> + </> )} </Modal> ); diff --git a/packages/desktop-client/src/components/modals/ConfirmTransactionDelete.tsx b/packages/desktop-client/src/components/modals/ConfirmTransactionDelete.tsx index f66ea339c..3c1ceaba0 100644 --- a/packages/desktop-client/src/components/modals/ConfirmTransactionDelete.tsx +++ b/packages/desktop-client/src/components/modals/ConfirmTransactionDelete.tsx @@ -3,18 +3,15 @@ import React from 'react'; import { useResponsive } from '../../ResponsiveProvider'; import { styles } from '../../style'; import { Button } from '../common/Button2'; -import { Modal } from '../common/Modal'; +import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal2'; import { Paragraph } from '../common/Paragraph'; import { View } from '../common/View'; -import { type CommonModalProps } from '../Modals'; type ConfirmTransactionDeleteProps = { - modalProps: CommonModalProps; onConfirm: () => void; }; export function ConfirmTransactionDelete({ - modalProps, onConfirm, }: ConfirmTransactionDeleteProps) { const { isNarrowWidth } = useResponsive(); @@ -25,36 +22,46 @@ export function ConfirmTransactionDelete({ : {}; return ( - <Modal title="Confirm Delete" {...modalProps}> - <View style={{ lineHeight: 1.5 }}> - <Paragraph>Are you sure you want to delete the transaction?</Paragraph> - <View - style={{ - flexDirection: 'row', - justifyContent: 'flex-end', - }} - > - <Button - style={{ - marginRight: 10, - ...narrowButtonStyle, - }} - onPress={modalProps.onClose} - > - Cancel - </Button> - <Button - variant="primary" - style={narrowButtonStyle} - onPress={() => { - onConfirm(); - modalProps.onClose(); - }} - > - Delete - </Button> - </View> - </View> + <Modal name="confirm-transaction-delete"> + {({ state: { close } }) => ( + <> + <ModalHeader + title="Confirm Delete" + rightContent={<ModalCloseButton onClick={close} />} + /> + <View style={{ lineHeight: 1.5 }}> + <Paragraph> + Are you sure you want to delete the transaction? + </Paragraph> + <View + style={{ + flexDirection: 'row', + justifyContent: 'flex-end', + }} + > + <Button + style={{ + marginRight: 10, + ...narrowButtonStyle, + }} + onPress={close} + > + Cancel + </Button> + <Button + variant="primary" + style={narrowButtonStyle} + onPress={() => { + onConfirm(); + close(); + }} + > + Delete + </Button> + </View> + </View> + </> + )} </Modal> ); } diff --git a/packages/desktop-client/src/components/modals/ConfirmTransactionEdit.tsx b/packages/desktop-client/src/components/modals/ConfirmTransactionEdit.tsx index 0d88859c5..6b445bda6 100644 --- a/packages/desktop-client/src/components/modals/ConfirmTransactionEdit.tsx +++ b/packages/desktop-client/src/components/modals/ConfirmTransactionEdit.tsx @@ -3,97 +3,105 @@ import React from 'react'; import { Block } from '../common/Block'; import { Button } from '../common/Button2'; -import { Modal } from '../common/Modal'; +import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal2'; import { View } from '../common/View'; -import { type CommonModalProps } from '../Modals'; type ConfirmTransactionEditProps = { - modalProps: Partial<CommonModalProps>; onCancel?: () => void; onConfirm: () => void; confirmReason: string; }; export function ConfirmTransactionEdit({ - modalProps, onCancel, onConfirm, confirmReason, }: ConfirmTransactionEditProps) { return ( - <Modal title="Reconciled Transaction" {...modalProps} style={{ flex: 0 }}> - {() => ( - <View style={{ lineHeight: 1.5 }}> - {confirmReason === 'batchDeleteWithReconciled' ? ( - <Block> - Deleting reconciled transactions may bring your reconciliation out - of balance. - </Block> - ) : confirmReason === 'batchEditWithReconciled' ? ( - <Block> - Editing reconciled transactions may bring your reconciliation out - of balance. - </Block> - ) : confirmReason === 'batchDuplicateWithReconciled' ? ( - <Block> - Duplicating reconciled transactions may bring your reconciliation - out of balance. - </Block> - ) : confirmReason === 'editReconciled' ? ( - <Block> - Saving your changes to this reconciled transaction may bring your - reconciliation out of balance. - </Block> - ) : confirmReason === 'unlockReconciled' ? ( - <Block> - Unlocking this transaction means you won‘t be warned about changes - that can impact your reconciled balance. (Changes to amount, - account, payee, etc). - </Block> - ) : confirmReason === 'deleteReconciled' ? ( - <Block> - Deleting this reconciled transaction may bring your reconciliation - out of balance. - </Block> - ) : ( - <Block>Are you sure you want to edit this transaction?</Block> - )} + <Modal + name="confirm-transaction-edit" + containerProps={{ style: { width: '30vw' } }} + > + {({ state: { close } }) => ( + <> + <ModalHeader + title="Reconciled Transaction" + rightContent={<ModalCloseButton onClick={close} />} + /> + <View style={{ lineHeight: 1.5 }}> + {confirmReason === 'batchDeleteWithReconciled' ? ( + <Block> + Deleting reconciled transactions may bring your reconciliation + out of balance. + </Block> + ) : confirmReason === 'batchEditWithReconciled' ? ( + <Block> + Editing reconciled transactions may bring your reconciliation + out of balance. + </Block> + ) : confirmReason === 'batchDuplicateWithReconciled' ? ( + <Block> + Duplicating reconciled transactions may bring your + reconciliation out of balance. + </Block> + ) : confirmReason === 'editReconciled' ? ( + <Block> + Saving your changes to this reconciled transaction may bring + your reconciliation out of balance. + </Block> + ) : confirmReason === 'unlockReconciled' ? ( + <Block> + Unlocking this transaction means you won‘t be warned about + changes that can impact your reconciled balance. (Changes to + amount, account, payee, etc). + </Block> + ) : confirmReason === 'deleteReconciled' ? ( + <Block> + Deleting this reconciled transaction may bring your + reconciliation out of balance. + </Block> + ) : ( + <Block>Are you sure you want to edit this transaction?</Block> + )} - <View - style={{ - marginTop: 20, - flexDirection: 'row', - justifyContent: 'flex-start', - alignItems: 'center', - }} - > <View style={{ + marginTop: 20, flexDirection: 'row', - justifyContent: 'flex-end', + justifyContent: 'flex-start', + alignItems: 'center', }} > - <Button - style={{ marginRight: 10 }} - onPress={() => { - modalProps.onClose(); - onCancel(); + <View + style={{ + flexDirection: 'row', + justifyContent: 'flex-end', }} > - Cancel - </Button> - <Button - variant="primary" - onPress={() => { - modalProps.onClose(); - onConfirm(); - }} - > - Confirm - </Button> + <Button + aria-label="Cancel" + style={{ marginRight: 10 }} + onPress={() => { + close(); + onCancel(); + }} + > + Cancel + </Button> + <Button + aria-label="Confirm" + variant="primary" + onPress={() => { + close(); + onConfirm(); + }} + > + Confirm + </Button> + </View> </View> </View> - </View> + </> )} </Modal> ); diff --git a/packages/desktop-client/src/components/modals/ConfirmUnlinkAccount.tsx b/packages/desktop-client/src/components/modals/ConfirmUnlinkAccount.tsx index 88fca3fe7..e3d5fb535 100644 --- a/packages/desktop-client/src/components/modals/ConfirmUnlinkAccount.tsx +++ b/packages/desktop-client/src/components/modals/ConfirmUnlinkAccount.tsx @@ -1,55 +1,61 @@ import React from 'react'; import { Button } from '../common/Button2'; -import { Modal } from '../common/Modal'; +import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal2'; import { Paragraph } from '../common/Paragraph'; import { View } from '../common/View'; -import { type CommonModalProps } from '../Modals'; type ConfirmUnlinkAccountProps = { - modalProps: CommonModalProps; accountName: string; onUnlink: () => void; }; export function ConfirmUnlinkAccount({ - modalProps, accountName, onUnlink, }: ConfirmUnlinkAccountProps) { return ( - <Modal title="Confirm Unlink" {...modalProps} style={{ flex: 0 }}> - {() => ( - <View style={{ lineHeight: 1.5 }}> - <Paragraph> - Are you sure you want to unlink <strong>{accountName}</strong>? - </Paragraph> + <Modal + name="confirm-unlink-account" + containerProps={{ style: { width: '30vw' } }} + > + {({ state: { close } }) => ( + <> + <ModalHeader + title="Confirm Unlink" + rightContent={<ModalCloseButton onClick={close} />} + /> + <View style={{ lineHeight: 1.5 }}> + <Paragraph> + Are you sure you want to unlink <strong>{accountName}</strong>? + </Paragraph> - <Paragraph> - Transactions will no longer be synchronized with this account and - must be manually entered. - </Paragraph> + <Paragraph> + Transactions will no longer be synchronized with this account and + must be manually entered. + </Paragraph> - <View - style={{ - flexDirection: 'row', - justifyContent: 'flex-end', - }} - > - <Button style={{ marginRight: 10 }} onPress={modalProps.onClose}> - Cancel - </Button> - <Button - variant="primary" - onPress={() => { - onUnlink(); - modalProps.onClose(); + <View + style={{ + flexDirection: 'row', + justifyContent: 'flex-end', }} > - Unlink - </Button> + <Button style={{ marginRight: 10 }} onPress={close}> + Cancel + </Button> + <Button + variant="primary" + onPress={() => { + onUnlink(); + close(); + }} + > + Unlink + </Button> + </View> </View> - </View> + </> )} </Modal> ); diff --git a/packages/desktop-client/src/components/modals/CoverModal.tsx b/packages/desktop-client/src/components/modals/CoverModal.tsx index 490f0f4f0..fbf9e8f14 100644 --- a/packages/desktop-client/src/components/modals/CoverModal.tsx +++ b/packages/desktop-client/src/components/modals/CoverModal.tsx @@ -8,13 +8,11 @@ import { useInitialMount } from '../../hooks/useInitialMount'; import { styles } from '../../style'; import { addToBeBudgetedGroup } from '../budget/util'; import { Button } from '../common/Button2'; -import { Modal } from '../common/Modal'; +import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal2'; import { View } from '../common/View'; import { FieldLabel, TapField } from '../mobile/MobileForms'; -import { type CommonModalProps } from '../Modals'; type CoverModalProps = { - modalProps: CommonModalProps; title: string; month: string; showToBeBudgeted?: boolean; @@ -22,7 +20,6 @@ type CoverModalProps = { }; export function CoverModal({ - modalProps, title, month, showToBeBudgeted = true, @@ -57,8 +54,6 @@ export function CoverModal({ if (categoryId) { onSubmit?.(categoryId); } - - modalProps.onClose(); }; const initialMount = useInitialMount(); @@ -72,31 +67,42 @@ export function CoverModal({ const fromCategory = categories.find(c => c.id === fromCategoryId); return ( - <Modal title={title} showHeader focusAfterClose={false} {...modalProps}> - <View> - <FieldLabel title="Cover from category:" /> - <TapField value={fromCategory?.name} onClick={onCategoryClick} /> - </View> + <Modal name="cover"> + {({ state: { close } }) => ( + <> + <ModalHeader + title={title} + rightContent={<ModalCloseButton onClick={close} />} + /> + <View> + <FieldLabel title="Cover from category:" /> + <TapField value={fromCategory?.name} onClick={onCategoryClick} /> + </View> - <View - style={{ - justifyContent: 'center', - alignItems: 'center', - paddingTop: 10, - }} - > - <Button - variant="primary" - style={{ - height: styles.mobileMinHeight, - marginLeft: styles.mobileEditingPadding, - marginRight: styles.mobileEditingPadding, - }} - onPress={() => _onSubmit(fromCategoryId)} - > - Transfer - </Button> - </View> + <View + style={{ + justifyContent: 'center', + alignItems: 'center', + paddingTop: 10, + }} + > + <Button + variant="primary" + style={{ + height: styles.mobileMinHeight, + marginLeft: styles.mobileEditingPadding, + marginRight: styles.mobileEditingPadding, + }} + onPress={() => { + _onSubmit(fromCategoryId); + close(); + }} + > + Transfer + </Button> + </View> + </> + )} </Modal> ); } diff --git a/packages/desktop-client/src/components/modals/CreateAccountModal.tsx b/packages/desktop-client/src/components/modals/CreateAccountModal.tsx index b9cd815ef..c23e67aaf 100644 --- a/packages/desktop-client/src/components/modals/CreateAccountModal.tsx +++ b/packages/desktop-client/src/components/modals/CreateAccountModal.tsx @@ -14,21 +14,18 @@ import { theme } from '../../style'; import { Button, ButtonWithLoading } from '../common/Button2'; import { Link } from '../common/Link'; import { Menu } from '../common/Menu'; -import { Modal } from '../common/Modal'; +import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal2'; import { Paragraph } from '../common/Paragraph'; import { Popover } from '../common/Popover'; import { Text } from '../common/Text'; import { View } from '../common/View'; -import { type CommonModalProps } from '../Modals'; type CreateAccountProps = { - modalProps: CommonModalProps; syncServerStatus: SyncServerStatus; upgradingAccountId?: string; }; export function CreateAccountModal({ - modalProps, syncServerStatus, upgradingAccountId, }: CreateAccountProps) { @@ -179,204 +176,211 @@ export function CreateAccountModal({ const simpleFinSyncFeatureFlag = useFeatureFlag('simpleFinSync'); return ( - <Modal title={title} {...modalProps}> - {() => ( - <View style={{ maxWidth: 500, gap: 30, color: theme.pageText }}> - {upgradingAccountId == null && ( - <View style={{ gap: 10 }}> - <Button - variant="primary" - style={{ - padding: '10px 0', - fontSize: 15, - fontWeight: 600, - }} - onPress={onCreateLocalAccount} - > - Create local account - </Button> - <View style={{ lineHeight: '1.4em', fontSize: 15 }}> - <Text> - <strong>Create a local account</strong> if you want to add - transactions manually. You can also{' '} - <Link - variant="external" - to="https://actualbudget.org/docs/transactions/importing" - linkColor="muted" - > - import QIF/OFX/QFX files into a local account - </Link> - . - </Text> - </View> - </View> - )} - <View style={{ gap: 10 }}> - {syncServerStatus === 'online' ? ( - <> - <View + <Modal name="add-account"> + {({ state: { close } }) => ( + <> + <ModalHeader + title={title} + rightContent={<ModalCloseButton onClick={close} />} + /> + <View style={{ maxWidth: 500, gap: 30, color: theme.pageText }}> + {upgradingAccountId == null && ( + <View style={{ gap: 10 }}> + <Button + variant="primary" style={{ - flexDirection: 'row', - gap: 10, - alignItems: 'center', + padding: '10px 0', + fontSize: 15, + fontWeight: 600, }} + onPress={onCreateLocalAccount} > - <ButtonWithLoading - isDisabled={syncServerStatus !== 'online'} + Create local account + </Button> + <View style={{ lineHeight: '1.4em', fontSize: 15 }}> + <Text> + <strong>Create a local account</strong> if you want to add + transactions manually. You can also{' '} + <Link + variant="external" + to="https://actualbudget.org/docs/transactions/importing" + linkColor="muted" + > + import QIF/OFX/QFX files into a local account + </Link> + . + </Text> + </View> + </View> + )} + <View style={{ gap: 10 }}> + {syncServerStatus === 'online' ? ( + <> + <View style={{ - padding: '10px 0', - fontSize: 15, - fontWeight: 600, - flex: 1, + flexDirection: 'row', + gap: 10, + alignItems: 'center', }} - onPress={onConnectGoCardless} > - {isGoCardlessSetupComplete - ? 'Link bank account with GoCardless' - : 'Set up GoCardless for bank sync'} - </ButtonWithLoading> - {isGoCardlessSetupComplete && ( - <> - <Button - ref={triggerRef} - variant="bare" - onPress={() => setGoCardlessMenuOpen(true)} - aria-label="GoCardless menu" - > - <SvgDotsHorizontalTriple - width={15} - height={15} - style={{ transform: 'rotateZ(90deg)' }} - /> - </Button> - - <Popover - triggerRef={triggerRef} - isOpen={menuGoCardlessOpen} - onOpenChange={() => setGoCardlessMenuOpen(false)} - > - <Menu - onMenuSelect={item => { - if (item === 'reconfigure') { - onGoCardlessReset(); - } - }} - items={[ - { - name: 'reconfigure', - text: 'Reset GoCardless credentials', - }, - ]} - /> - </Popover> - </> - )} - </View> - <Text style={{ lineHeight: '1.4em', fontSize: 15 }}> - <strong> - Link a <em>European</em> bank account - </strong>{' '} - to automatically download transactions. GoCardless provides - reliable, up-to-date information from hundreds of banks. - </Text> - {simpleFinSyncFeatureFlag === true && ( - <> - <View + <ButtonWithLoading + isDisabled={syncServerStatus !== 'online'} style={{ - flexDirection: 'row', - gap: 10, - marginTop: '18px', - alignItems: 'center', + padding: '10px 0', + fontSize: 15, + fontWeight: 600, + flex: 1, }} + onPress={onConnectGoCardless} > - <ButtonWithLoading - isDisabled={syncServerStatus !== 'online'} - isLoading={loadingSimpleFinAccounts} + {isGoCardlessSetupComplete + ? 'Link bank account with GoCardless' + : 'Set up GoCardless for bank sync'} + </ButtonWithLoading> + {isGoCardlessSetupComplete && ( + <> + <Button + ref={triggerRef} + variant="bare" + onPress={() => setGoCardlessMenuOpen(true)} + aria-label="GoCardless menu" + > + <SvgDotsHorizontalTriple + width={15} + height={15} + style={{ transform: 'rotateZ(90deg)' }} + /> + </Button> + + <Popover + triggerRef={triggerRef} + isOpen={menuGoCardlessOpen} + onOpenChange={() => setGoCardlessMenuOpen(false)} + > + <Menu + onMenuSelect={item => { + if (item === 'reconfigure') { + onGoCardlessReset(); + } + }} + items={[ + { + name: 'reconfigure', + text: 'Reset GoCardless credentials', + }, + ]} + /> + </Popover> + </> + )} + </View> + <Text style={{ lineHeight: '1.4em', fontSize: 15 }}> + <strong> + Link a <em>European</em> bank account + </strong>{' '} + to automatically download transactions. GoCardless provides + reliable, up-to-date information from hundreds of banks. + </Text> + {simpleFinSyncFeatureFlag === true && ( + <> + <View style={{ - padding: '10px 0', - fontSize: 15, - fontWeight: 600, - flex: 1, + flexDirection: 'row', + gap: 10, + marginTop: '18px', + alignItems: 'center', }} - onPress={onConnectSimpleFin} > - {isSimpleFinSetupComplete - ? 'Link bank account with SimpleFIN' - : 'Set up SimpleFIN for bank sync'} - </ButtonWithLoading> - {isSimpleFinSetupComplete && ( - <> - <Button - ref={triggerRef} - variant="bare" - onPress={() => setSimplefinMenuOpen(true)} - aria-label="SimpleFIN menu" - > - <SvgDotsHorizontalTriple - width={15} - height={15} - style={{ transform: 'rotateZ(90deg)' }} - /> - </Button> - <Popover - triggerRef={triggerRef} - isOpen={menuSimplefinOpen} - onOpenChange={() => setSimplefinMenuOpen(false)} - > - <Menu - onMenuSelect={item => { - if (item === 'reconfigure') { - onSimpleFinReset(); - } - }} - items={[ - { - name: 'reconfigure', - text: 'Reset SimpleFIN credentials', - }, - ]} - /> - </Popover> - </> - )} - </View> - <Text style={{ lineHeight: '1.4em', fontSize: 15 }}> - <strong> - Link a <em>North American</em> bank account - </strong>{' '} - to automatically download transactions. SimpleFIN provides - reliable, up-to-date information from hundreds of banks. - </Text> - </> - )} - </> - ) : ( - <> - <Button - isDisabled - style={{ - padding: '10px 0', - fontSize: 15, - fontWeight: 600, - }} - > - Set up bank sync - </Button> - <Paragraph style={{ fontSize: 15 }}> - Connect to an Actual server to set up{' '} - <Link - variant="external" - to="https://actualbudget.org/docs/advanced/bank-sync" - linkColor="muted" + <ButtonWithLoading + isDisabled={syncServerStatus !== 'online'} + isLoading={loadingSimpleFinAccounts} + style={{ + padding: '10px 0', + fontSize: 15, + fontWeight: 600, + flex: 1, + }} + onPress={onConnectSimpleFin} + > + {isSimpleFinSetupComplete + ? 'Link bank account with SimpleFIN' + : 'Set up SimpleFIN for bank sync'} + </ButtonWithLoading> + {isSimpleFinSetupComplete && ( + <> + <Button + ref={triggerRef} + variant="bare" + onPress={() => setSimplefinMenuOpen(true)} + aria-label="SimpleFIN menu" + > + <SvgDotsHorizontalTriple + width={15} + height={15} + style={{ transform: 'rotateZ(90deg)' }} + /> + </Button> + <Popover + triggerRef={triggerRef} + isOpen={menuSimplefinOpen} + onOpenChange={() => setSimplefinMenuOpen(false)} + > + <Menu + onMenuSelect={item => { + if (item === 'reconfigure') { + onSimpleFinReset(); + } + }} + items={[ + { + name: 'reconfigure', + text: 'Reset SimpleFIN credentials', + }, + ]} + /> + </Popover> + </> + )} + </View> + <Text style={{ lineHeight: '1.4em', fontSize: 15 }}> + <strong> + Link a <em>North American</em> bank account + </strong>{' '} + to automatically download transactions. SimpleFIN + provides reliable, up-to-date information from hundreds + of banks. + </Text> + </> + )} + </> + ) : ( + <> + <Button + isDisabled + style={{ + padding: '10px 0', + fontSize: 15, + fontWeight: 600, + }} > - automatic syncing - </Link> - . - </Paragraph> - </> - )} + Set up bank sync + </Button> + <Paragraph style={{ fontSize: 15 }}> + Connect to an Actual server to set up{' '} + <Link + variant="external" + to="https://actualbudget.org/docs/advanced/bank-sync" + linkColor="muted" + > + automatic syncing + </Link> + . + </Paragraph> + </> + )} + </View> </View> - </View> + </> )} </Modal> ); diff --git a/packages/desktop-client/src/components/modals/CreateEncryptionKeyModal.tsx b/packages/desktop-client/src/components/modals/CreateEncryptionKeyModal.tsx index 1c39b5aa9..8844739a4 100644 --- a/packages/desktop-client/src/components/modals/CreateEncryptionKeyModal.tsx +++ b/packages/desktop-client/src/components/modals/CreateEncryptionKeyModal.tsx @@ -1,5 +1,6 @@ // @ts-strict-ignore import React, { useState } from 'react'; +import { Form } from 'react-aria-components'; import { useDispatch } from 'react-redux'; import { css } from 'glamor'; @@ -14,21 +15,23 @@ import { ButtonWithLoading } from '../common/Button2'; import { InitialFocus } from '../common/InitialFocus'; import { Input } from '../common/Input'; import { Link } from '../common/Link'; -import { Modal, ModalButtons } from '../common/Modal'; +import { + Modal, + ModalButtons, + ModalCloseButton, + ModalHeader, +} from '../common/Modal2'; import { Paragraph } from '../common/Paragraph'; import { Text } from '../common/Text'; import { View } from '../common/View'; -import { type CommonModalProps } from '../Modals'; type CreateEncryptionKeyModalProps = { - modalProps: CommonModalProps; options: { recreate?: boolean; }; }; export function CreateEncryptionKeyModal({ - modalProps, options = {}, }: CreateEncryptionKeyModalProps) { const [password, setPassword] = useState(''); @@ -57,146 +60,153 @@ export function CreateEncryptionKeyModal({ dispatch(sync()); setLoading(false); - modalProps.onClose(); } } return ( - <Modal - {...modalProps} - title={isRecreating ? 'Generate new key' : 'Enable encryption'} - onClose={modalProps.onClose} - > - <View - style={{ - maxWidth: 600, - overflowX: 'hidden', - overflowY: 'auto', - flex: 1, - }} - > - {!isRecreating ? ( - <> - <Paragraph style={{ marginTop: 5 }}> - To enable end-to-end encryption, you need to create a key. We will - generate a key based on a password and use it to encrypt from now - on. <strong>This requires a sync reset</strong> and all other - devices will have to revert to this version of your data.{' '} - <Link - variant="external" - to="https://actualbudget.org/docs/getting-started/sync/#end-to-end-encryption" - linkColor="purple" - > - Learn more - </Link> - </Paragraph> - <Paragraph> - <ul - className={`${css({ - marginTop: 0, - '& li': { marginBottom: 8 }, - })}`} - > - <li> - <strong>Important:</strong> if you forget this password{' '} - <em>and</em> you don’t have any local copies of your data, you - will lose access to all your data. The data cannot be - decrypted without the password. - </li> - <li> - This key only applies to this file. You will need to generate - a new key for each file you want to encrypt. - </li> - <li> - If you’ve already downloaded your data on other devices, you - will need to reset them. Actual will automatically take you - through this process. - </li> - <li> - It is recommended for the encryption password to be different - than the log-in password in order to better protect your data. - </li> - </ul> - </Paragraph> - </> - ) : ( - <> - <Paragraph style={{ marginTop: 5 }}> - This will generate a new key for encrypting your data.{' '} - <strong>This requires a sync reset</strong> and all other devices - will have to revert to this version of your data. Actual will take - you through that process on those devices.{' '} - <Link - variant="external" - to="https://actualbudget.org/docs/getting-started/sync/#end-to-end-encryption" - linkColor="purple" - > - Learn more - </Link> - </Paragraph> - <Paragraph> - Key generation is randomized. The same password will create - different keys, so this will change your key regardless of the - password being different. - </Paragraph> - </> - )} - </View> - <form - onSubmit={e => { - e.preventDefault(); - onCreateKey(); - }} - > - <View style={{ alignItems: 'center' }}> - <Text style={{ fontWeight: 600, marginBottom: 3 }}>Password</Text> - - {error && ( - <View - style={{ - color: theme.errorText, - textAlign: 'center', - fontSize: 13, - marginBottom: 3, - }} - > - {error} - </View> - )} - - <InitialFocus> - <Input - type={showPassword ? 'text' : 'password'} - style={{ - width: isNarrowWidth ? '100%' : '50%', - height: isNarrowWidth ? styles.mobileMinHeight : undefined, - }} - onChange={e => setPassword(e.target.value)} - /> - </InitialFocus> - <Text style={{ marginTop: 5 }}> - <label style={{ userSelect: 'none' }}> - <input - type="checkbox" - onClick={() => setShowPassword(!showPassword)} - />{' '} - Show password - </label> - </Text> - </View> - - <ModalButtons style={{ marginTop: 20 }}> - <ButtonWithLoading - variant="primary" + <Modal name="create-encryption-key"> + {({ state: { close } }) => ( + <> + <ModalHeader + title={isRecreating ? 'Generate new key' : 'Enable encryption'} + rightContent={<ModalCloseButton onClick={close} />} + /> + <View style={{ - height: isNarrowWidth ? styles.mobileMinHeight : undefined, + maxWidth: 600, + overflowX: 'hidden', + overflowY: 'auto', + flex: 1, }} - isLoading={loading} > - Enable - </ButtonWithLoading> - </ModalButtons> - </form> + {!isRecreating ? ( + <> + <Paragraph style={{ marginTop: 5 }}> + To enable end-to-end encryption, you need to create a key. We + will generate a key based on a password and use it to encrypt + from now on. <strong>This requires a sync reset</strong> and + all other devices will have to revert to this version of your + data.{' '} + <Link + variant="external" + to="https://actualbudget.org/docs/getting-started/sync/#end-to-end-encryption" + linkColor="purple" + > + Learn more + </Link> + </Paragraph> + <Paragraph> + <ul + className={`${css({ + marginTop: 0, + '& li': { marginBottom: 8 }, + })}`} + > + <li> + <strong>Important:</strong> if you forget this password{' '} + <em>and</em> you don’t have any local copies of your data, + you will lose access to all your data. The data cannot be + decrypted without the password. + </li> + <li> + This key only applies to this file. You will need to + generate a new key for each file you want to encrypt. + </li> + <li> + If you’ve already downloaded your data on other devices, + you will need to reset them. Actual will automatically + take you through this process. + </li> + <li> + It is recommended for the encryption password to be + different than the log-in password in order to better + protect your data. + </li> + </ul> + </Paragraph> + </> + ) : ( + <> + <Paragraph style={{ marginTop: 5 }}> + This will generate a new key for encrypting your data.{' '} + <strong>This requires a sync reset</strong> and all other + devices will have to revert to this version of your data. + Actual will take you through that process on those devices.{' '} + <Link + variant="external" + to="https://actualbudget.org/docs/getting-started/sync/#end-to-end-encryption" + linkColor="purple" + > + Learn more + </Link> + </Paragraph> + <Paragraph> + Key generation is randomized. The same password will create + different keys, so this will change your key regardless of the + password being different. + </Paragraph> + </> + )} + </View> + <Form + onSubmit={e => { + e.preventDefault(); + onCreateKey(); + close(); + }} + > + <View style={{ alignItems: 'center' }}> + <Text style={{ fontWeight: 600, marginBottom: 3 }}>Password</Text> + + {error && ( + <View + style={{ + color: theme.errorText, + textAlign: 'center', + fontSize: 13, + marginBottom: 3, + }} + > + {error} + </View> + )} + + <InitialFocus> + <Input + type={showPassword ? 'text' : 'password'} + style={{ + width: isNarrowWidth ? '100%' : '50%', + height: isNarrowWidth ? styles.mobileMinHeight : undefined, + }} + onChange={e => setPassword(e.target.value)} + /> + </InitialFocus> + <Text style={{ marginTop: 5 }}> + <label style={{ userSelect: 'none' }}> + <input + type="checkbox" + onClick={() => setShowPassword(!showPassword)} + />{' '} + Show password + </label> + </Text> + </View> + + <ModalButtons style={{ marginTop: 20 }}> + <ButtonWithLoading + type="submit" + style={{ + height: isNarrowWidth ? styles.mobileMinHeight : undefined, + }} + isLoading={loading} + variant="primary" + > + Enable + </ButtonWithLoading> + </ModalButtons> + </Form> + </> + )} </Modal> ); } diff --git a/packages/desktop-client/src/components/modals/CreateLocalAccountModal.tsx b/packages/desktop-client/src/components/modals/CreateLocalAccountModal.tsx index 0e3dcc079..2689ddd12 100644 --- a/packages/desktop-client/src/components/modals/CreateLocalAccountModal.tsx +++ b/packages/desktop-client/src/components/modals/CreateLocalAccountModal.tsx @@ -1,10 +1,11 @@ // @ts-strict-ignore import React, { 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 { type BoundActions } from '../../hooks/useActions'; import { useNavigate } from '../../hooks/useNavigate'; import { theme } from '../../style'; import { Button } from '../common/Button2'; @@ -13,22 +14,20 @@ import { InitialFocus } from '../common/InitialFocus'; import { InlineField } from '../common/InlineField'; import { Input } from '../common/Input'; import { Link } from '../common/Link'; -import { Modal, ModalButtons, ModalTitle } from '../common/Modal'; +import { + Modal, + ModalButtons, + ModalCloseButton, + ModalHeader, + ModalTitle, +} from '../common/Modal2'; import { Text } from '../common/Text'; import { View } from '../common/View'; import { Checkbox } from '../forms'; -import { type CommonModalProps } from '../Modals'; -type CreateLocalAccountProps = { - modalProps: CommonModalProps; - actions: BoundActions; -}; - -export function CreateLocalAccountModal({ - modalProps, - actions, -}: CreateLocalAccountProps) { +export function CreateLocalAccountModal() { const navigate = useNavigate(); + const dispatch = useDispatch(); const [name, setName] = useState(''); const [offbudget, setOffbudget] = useState(false); const [balance, setBalance] = useState('0'); @@ -48,132 +47,135 @@ export function CreateLocalAccountModal({ setBalanceError(balanceError); if (!nameError && !balanceError) { - actions.closeModal(); - const id = await actions.createAccount( - name, - toRelaxedNumber(balance), - offbudget, + dispatch(closeModal()); + const id = await dispatch( + createAccount(name, toRelaxedNumber(balance), offbudget), ); navigate('/accounts/' + id); } }; return ( - <Modal - title={<ModalTitle title="Create Local Account" shrinkOnOverflow />} - {...modalProps} - > - {() => ( - <View> - <Form onSubmit={onSubmit}> - <InlineField label="Name" width="100%"> - <InitialFocus> - <Input - name="name" - value={name} - onChange={event => setName(event.target.value)} - onBlur={event => { - const name = event.target.value.trim(); - setName(name); - if (name && nameError) { - setNameError(false); - } - }} - style={{ flex: 1 }} - /> - </InitialFocus> - </InlineField> - {nameError && ( - <FormError style={{ marginLeft: 75 }}>Name is required</FormError> - )} - - <View - style={{ - width: '100%', - flexDirection: 'row', - justifyContent: 'flex-end', - }} - > - <View style={{ flexDirection: 'column' }}> - <View - style={{ - flexDirection: 'row', - justifyContent: 'flex-end', - }} - > - <Checkbox - id="offbudget" - name="offbudget" - checked={offbudget} - onChange={() => setOffbudget(!offbudget)} + <Modal name="add-local-account"> + {({ state: { close } }) => ( + <> + <ModalHeader + title={<ModalTitle title="Create Local Account" shrinkOnOverflow />} + rightContent={<ModalCloseButton onClick={close} />} + /> + <View> + <Form onSubmit={onSubmit}> + <InlineField label="Name" width="100%"> + <InitialFocus> + <Input + name="name" + value={name} + onChange={event => setName(event.target.value)} + onBlur={event => { + const name = event.target.value.trim(); + setName(name); + if (name && nameError) { + setNameError(false); + } + }} + style={{ flex: 1 }} /> - <label - htmlFor="offbudget" + </InitialFocus> + </InlineField> + {nameError && ( + <FormError style={{ marginLeft: 75 }}> + Name is required + </FormError> + )} + + <View + style={{ + width: '100%', + flexDirection: 'row', + justifyContent: 'flex-end', + }} + > + <View style={{ flexDirection: 'column' }}> + <View style={{ - userSelect: 'none', - verticalAlign: 'center', + flexDirection: 'row', + justifyContent: 'flex-end', }} > - Off-budget - </label> - </View> - <div - style={{ - textAlign: 'right', - fontSize: '0.7em', - color: theme.pageTextLight, - marginTop: 3, - }} - > - <Text> - This cannot be changed later. <br /> {'\n'} - See{' '} - <Link - variant="external" - linkColor="muted" - to="https://actualbudget.org/docs/accounts/#off-budget-accounts" + <Checkbox + id="offbudget" + name="offbudget" + checked={offbudget} + onChange={() => setOffbudget(!offbudget)} + /> + <label + htmlFor="offbudget" + style={{ + userSelect: 'none', + verticalAlign: 'center', + }} > - Accounts Overview - </Link>{' '} - for more information. - </Text> - </div> + Off-budget + </label> + </View> + <div + style={{ + textAlign: 'right', + fontSize: '0.7em', + color: theme.pageTextLight, + marginTop: 3, + }} + > + <Text> + This cannot be changed later. <br /> {'\n'} + See{' '} + <Link + variant="external" + linkColor="muted" + to="https://actualbudget.org/docs/accounts/#off-budget-accounts" + > + Accounts Overview + </Link>{' '} + for more information. + </Text> + </div> + </View> </View> - </View> - <InlineField label="Balance" width="100%"> - <Input - name="balance" - inputMode="decimal" - value={balance} - onChange={event => setBalance(event.target.value)} - onBlur={event => { - const balance = event.target.value.trim(); - setBalance(balance); - if (validateBalance(balance) && balanceError) { - setBalanceError(false); - } - }} - style={{ flex: 1 }} - /> - </InlineField> - {balanceError && ( - <FormError style={{ marginLeft: 75 }}> - Balance must be a number - </FormError> - )} + <InlineField label="Balance" width="100%"> + <Input + name="balance" + inputMode="decimal" + value={balance} + onChange={event => setBalance(event.target.value)} + onBlur={event => { + const balance = event.target.value.trim(); + setBalance(balance); + if (validateBalance(balance) && balanceError) { + setBalanceError(false); + } + }} + style={{ flex: 1 }} + /> + </InlineField> + {balanceError && ( + <FormError style={{ marginLeft: 75 }}> + Balance must be a number + </FormError> + )} - <ModalButtons> - <Button onPress={() => modalProps.onBack()}>Back</Button> - <Button - type="submit" - variant="primary" - style={{ marginLeft: 10 }} - > - Create - </Button> - </ModalButtons> - </Form> - </View> + <ModalButtons> + <Button onPress={close}>Back</Button> + <Button + type="submit" + variant="primary" + style={{ marginLeft: 10 }} + > + Create + </Button> + </ModalButtons> + </Form> + </View> + </> )} </Modal> ); diff --git a/packages/desktop-client/src/components/modals/EditField.jsx b/packages/desktop-client/src/components/modals/EditField.jsx index 3ffc5168b..e34eb7e58 100644 --- a/packages/desktop-client/src/components/modals/EditField.jsx +++ b/packages/desktop-client/src/components/modals/EditField.jsx @@ -10,23 +10,18 @@ import { useResponsive } from '../../ResponsiveProvider'; import { theme } from '../../style'; import { Button } from '../common/Button2'; import { Input } from '../common/Input'; -import { Modal } from '../common/Modal'; +import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal2'; import { View } from '../common/View'; import { SectionLabel } from '../forms'; import { DateSelect } from '../select/DateSelect'; -export function EditField({ modalProps, name, onSubmit, onClose }) { +export function EditField({ name, onSubmit, onClose }) { const dateFormat = useDateFormat() || 'MM/dd/yyyy'; - const onCloseInner = () => { - modalProps.onClose(); - onClose?.(); - }; function onSelectNote(value, mode) { if (value != null) { onSubmit(name, value, mode); } - onCloseInner(); } function onSelect(value) { @@ -38,7 +33,6 @@ export function EditField({ modalProps, name, onSubmit, onClose }) { onSubmit(name, value); } - onCloseInner(); } const itemStyle = { @@ -62,7 +56,7 @@ export function EditField({ modalProps, name, onSubmit, onClose }) { const today = currentDay(); label = 'Date'; minWidth = 350; - editor = ( + editor = ({ close }) => ( <DateSelect value={formatDate(parseISO(today), dateFormat)} dateFormat={dateFormat} @@ -71,6 +65,7 @@ export function EditField({ modalProps, name, onSubmit, onClose }) { onUpdate={() => {}} onSelect={date => { onSelect(dayFromDate(parseDate(date, 'yyyy-MM-dd', new Date()))); + close(); }} /> ); @@ -78,7 +73,7 @@ export function EditField({ modalProps, name, onSubmit, onClose }) { case 'notes': label = 'Notes'; - editor = ( + editor = ({ close }) => ( <> <View style={{ @@ -192,7 +187,10 @@ export function EditField({ modalProps, name, onSubmit, onClose }) { id="noteInput" autoFocus focused={true} - onEnter={e => onSelectNote(e.target.value, noteAmend)} + onEnter={e => { + onSelectNote(e.target.value, noteAmend); + close(); + }} style={inputStyle} /> </> @@ -201,10 +199,13 @@ export function EditField({ modalProps, name, onSubmit, onClose }) { case 'amount': label = 'Amount'; - editor = ( + editor = ({ close }) => ( <Input focused={true} - onEnter={e => onSelect(e.target.value)} + onEnter={e => { + onSelect(e.target.value); + close(); + }} style={inputStyle} /> ); @@ -215,34 +216,40 @@ export function EditField({ modalProps, name, onSubmit, onClose }) { return ( <Modal - title={label} + name="edit-field" noAnimation={!isNarrowWidth} - showHeader={isNarrowWidth} - focusAfterClose={false} - {...modalProps} - onClose={onCloseInner} - style={{ - flex: 0, - height: isNarrowWidth ? '85vh' : 275, - padding: '15px 10px', - ...(minWidth && { minWidth }), - backgroundColor: theme.menuAutoCompleteBackground, + onClose={onClose} + containerProps={{ + style: { + height: isNarrowWidth ? '85vh' : 275, + padding: '15px 10px', + ...(minWidth && { minWidth }), + backgroundColor: theme.menuAutoCompleteBackground, + }, }} > - {() => ( - <View> - {!isNarrowWidth && ( - <SectionLabel + {({ state: { close } }) => ( + <> + {isNarrowWidth && ( + <ModalHeader title={label} - style={{ - alignSelf: 'center', - color: theme.menuAutoCompleteText, - marginBottom: 10, - }} + rightContent={<ModalCloseButton onClick={close} />} /> )} - <View style={{ flex: 1 }}>{editor}</View> - </View> + <View> + {!isNarrowWidth && ( + <SectionLabel + title={label} + style={{ + alignSelf: 'center', + color: theme.menuAutoCompleteText, + marginBottom: 10, + }} + /> + )} + <View style={{ flex: 1 }}>{editor({ close })}</View> + </View> + </> )} </Modal> ); diff --git a/packages/desktop-client/src/components/modals/EditRule.jsx b/packages/desktop-client/src/components/modals/EditRule.jsx index b54ec5d65..2256bc287 100644 --- a/packages/desktop-client/src/components/modals/EditRule.jsx +++ b/packages/desktop-client/src/components/modals/EditRule.jsx @@ -42,7 +42,7 @@ import { SvgInformationOutline } from '../../icons/v1'; import { styles, theme } from '../../style'; import { Button } from '../common/Button2'; import { Menu } from '../common/Menu'; -import { Modal } from '../common/Modal'; +import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal2'; import { Select } from '../common/Select'; import { Stack } from '../common/Stack'; import { Text } from '../common/Text'; @@ -671,7 +671,7 @@ const conditionFields = [ ['amount-outflow', mapField('amount', { outflow: true })], ]); -export function EditRule({ modalProps, defaultRule, onSave: originalOnSave }) { +export function EditRule({ defaultRule, onSave: originalOnSave }) { const [conditions, setConditions] = useState( defaultRule.conditions.map(parse), ); @@ -888,7 +888,6 @@ export function EditRule({ modalProps, defaultRule, onSave: originalOnSave }) { } originalOnSave?.(rule); - modalProps.onClose(); } } @@ -902,256 +901,264 @@ export function EditRule({ modalProps, defaultRule, onSave: originalOnSave }) { const showSplitButton = actionSplits.length > 0; return ( - <Modal - title="Rule" - {...modalProps} - style={{ ...modalProps.style, flex: 'inherit' }} - > - {() => ( - <View - style={{ - maxWidth: '100%', - width: 900, - height: '80vh', - flexGrow: 0, - flexShrink: 0, - flexBasis: 'auto', - overflow: 'hidden', - color: theme.pageTextLight, - }} - > + <Modal name="edit-rule"> + {({ state: { close } }) => ( + <> + <ModalHeader + title="Rule" + rightContent={<ModalCloseButton onClick={close} />} + /> <View style={{ - flexDirection: 'row', - alignItems: 'center', - marginBottom: 15, - padding: '0 20px', + maxWidth: '100%', + width: 900, + height: '80vh', + flexGrow: 0, + flexShrink: 0, + flexBasis: 'auto', + overflow: 'hidden', + color: theme.pageTextLight, }} > - <Text style={{ marginRight: 15 }}>Stage of rule:</Text> - - <Stack direction="row" align="center" spacing={1}> - <StageButton - selected={stage === 'pre'} - onSelect={() => onChangeStage('pre')} - > - Pre - </StageButton> - <StageButton - selected={stage === null} - onSelect={() => onChangeStage(null)} - > - Default - </StageButton> - <StageButton - selected={stage === 'post'} - onSelect={() => onChangeStage('post')} - > - Post - </StageButton> - - <StageInfo /> - </Stack> - </View> + <View + style={{ + flexDirection: 'row', + alignItems: 'center', + marginBottom: 15, + padding: '0 20px', + }} + > + <Text style={{ marginRight: 15 }}>Stage of rule:</Text> - <View - innerRef={scrollableEl} - style={{ - borderBottom: '1px solid ' + theme.tableBorder, - padding: 20, - overflow: 'auto', - maxHeight: 'calc(100% - 300px)', - }} - > - <View style={{ flexShrink: 0 }}> - <View style={{ marginBottom: 30 }}> - <Text style={{ marginBottom: 15 }}> - If - <FieldSelect - data-testid="conditions-op" - style={{ display: 'inline-flex' }} - fields={[ - ['and', 'all'], - ['or', 'any'], - ]} - value={conditionsOp} - onChange={onChangeConditionsOp} - /> - of these conditions match: - </Text> + <Stack direction="row" align="center" spacing={1}> + <StageButton + selected={stage === 'pre'} + onSelect={() => onChangeStage('pre')} + > + Pre + </StageButton> + <StageButton + selected={stage === null} + onSelect={() => onChangeStage(null)} + > + Default + </StageButton> + <StageButton + selected={stage === 'post'} + onSelect={() => onChangeStage('post')} + > + Post + </StageButton> - <ConditionsList - conditionsOp={conditionsOp} - conditions={conditions} - editorStyle={editorStyle} - isSchedule={isSchedule} - onChangeConditions={conds => setConditions(conds)} - /> - </View> + <StageInfo /> + </Stack> + </View> - <Text style={{ marginBottom: 15 }}> - Then apply these actions: - </Text> - <View style={{ flex: 1 }}> - {actionSplits.length === 0 && ( - <Button - style={{ alignSelf: 'flex-start' }} - onPress={addInitialAction} - > - Add action - </Button> - )} - <Stack spacing={2} data-testid="action-split-list"> - {actionSplits.map(({ id, actions }, splitIndex) => ( - <View - key={id} - nativeStyle={ - actionSplits.length > 1 - ? { - borderColor: theme.tableBorder, - borderWidth: '1px', - borderRadius: '5px', - padding: '5px', - } - : {} - } + <View + innerRef={scrollableEl} + style={{ + borderBottom: '1px solid ' + theme.tableBorder, + padding: 20, + overflow: 'auto', + maxHeight: 'calc(100% - 300px)', + }} + > + <View style={{ flexShrink: 0 }}> + <View style={{ marginBottom: 30 }}> + <Text style={{ marginBottom: 15 }}> + If + <FieldSelect + data-testid="conditions-op" + style={{ display: 'inline-flex' }} + fields={[ + ['and', 'all'], + ['or', 'any'], + ]} + value={conditionsOp} + onChange={onChangeConditionsOp} + /> + of these conditions match: + </Text> + + <ConditionsList + conditionsOp={conditionsOp} + conditions={conditions} + editorStyle={editorStyle} + isSchedule={isSchedule} + onChangeConditions={conds => setConditions(conds)} + /> + </View> + + <Text style={{ marginBottom: 15 }}> + Then apply these actions: + </Text> + <View style={{ flex: 1 }}> + {actionSplits.length === 0 && ( + <Button + style={{ alignSelf: 'flex-start' }} + onPress={addInitialAction} > - {actionSplits.length > 1 && ( - <Stack - direction="row" - justify="space-between" - spacing={1} - > - <Text - style={{ - ...styles.smallText, - marginBottom: '10px', - }} + Add action + </Button> + )} + <Stack spacing={2} data-testid="action-split-list"> + {actionSplits.map(({ id, actions }, splitIndex) => ( + <View + key={id} + nativeStyle={ + actionSplits.length > 1 + ? { + borderColor: theme.tableBorder, + borderWidth: '1px', + borderRadius: '5px', + padding: '5px', + } + : {} + } + > + {actionSplits.length > 1 && ( + <Stack + direction="row" + justify="space-between" + spacing={1} > - {splitIndex === 0 - ? 'Apply to all' - : `Split ${splitIndex}`} - </Text> - {splitIndex && ( - <Button - variant="bare" - onPress={() => onRemoveSplit(splitIndex)} + <Text style={{ - width: 20, - height: 20, + ...styles.smallText, + marginBottom: '10px', }} - aria-label="Delete split" > - <SvgDelete + {splitIndex === 0 + ? 'Apply to all' + : `Split ${splitIndex}`} + </Text> + {splitIndex && ( + <Button + variant="bare" + onPress={() => onRemoveSplit(splitIndex)} style={{ - width: 8, - height: 8, - color: 'inherit', + width: 20, + height: 20, }} + aria-label="Delete split" + > + <SvgDelete + style={{ + width: 8, + height: 8, + color: 'inherit', + }} + /> + </Button> + )} + </Stack> + )} + <Stack spacing={2} data-testid="action-list"> + {actions.map((action, actionIndex) => ( + <View key={actionIndex}> + <ActionEditor + ops={['set', 'link-schedule']} + action={action} + editorStyle={editorStyle} + onChange={(name, value) => { + onChangeAction(action, name, value); + }} + onDelete={() => onRemoveAction(action)} + onAdd={() => + addActionToSplitAfterIndex( + splitIndex, + actionIndex, + ) + } /> - </Button> - )} + </View> + ))} </Stack> - )} - <Stack spacing={2} data-testid="action-list"> - {actions.map((action, actionIndex) => ( - <View key={actionIndex}> - <ActionEditor - ops={['set', 'link-schedule']} - action={action} - editorStyle={editorStyle} - onChange={(name, value) => { - onChangeAction(action, name, value); - }} - onDelete={() => onRemoveAction(action)} - onAdd={() => - addActionToSplitAfterIndex( - splitIndex, - actionIndex, - ) - } - /> - </View> - ))} - </Stack> - - {actions.length === 0 && ( - <Button - style={{ alignSelf: 'flex-start', marginTop: 5 }} - onPress={() => - addActionToSplitAfterIndex(splitIndex, -1) - } - > - Add action - </Button> - )} - </View> - ))} - </Stack> - {showSplitButton && ( + + {actions.length === 0 && ( + <Button + style={{ alignSelf: 'flex-start', marginTop: 5 }} + onPress={() => + addActionToSplitAfterIndex(splitIndex, -1) + } + > + Add action + </Button> + )} + </View> + ))} + </Stack> + {showSplitButton && ( + <Button + style={{ alignSelf: 'flex-start', marginTop: 15 }} + onPress={() => { + addActionToSplitAfterIndex(actionSplits.length, -1); + }} + data-testid="add-split-transactions" + > + {actionSplits.length > 1 + ? 'Add another split' + : 'Split into multiple transactions'} + </Button> + )} + </View> + </View> + </View> + + <SelectedProvider instance={selectedInst}> + <View style={{ padding: '20px', flex: 1 }}> + <View + style={{ + flexDirection: 'row', + alignItems: 'center', + marginBottom: 12, + }} + > + <Text style={{ color: theme.pageTextLight, marginBottom: 0 }}> + This rule applies to these transactions: + </Text> + + <View style={{ flex: 1 }} /> + <Button + isDisabled={selectedInst.items.size === 0} + onPress={onApply} + > + Apply actions ({selectedInst.items.size}) + </Button> + </View> + + <SimpleTransactionsTable + transactions={transactions} + fields={getTransactionFields( + conditions, + getActions(actionSplits), + )} + style={{ + border: '1px solid ' + theme.tableBorder, + borderRadius: '6px 6px 0 0', + }} + /> + + <Stack + direction="row" + justify="flex-end" + style={{ marginTop: 20 }} + > + <Button onClick={close}>Cancel</Button> <Button - style={{ alignSelf: 'flex-start', marginTop: 15 }} + variant="primary" onPress={() => { - addActionToSplitAfterIndex(actionSplits.length, -1); + onSave(); + close(); }} - data-testid="add-split-transactions" > - {actionSplits.length > 1 - ? 'Add another split' - : 'Split into multiple transactions'} + Save </Button> - )} + </Stack> </View> - </View> + </SelectedProvider> </View> - - <SelectedProvider instance={selectedInst}> - <View style={{ padding: '20px', flex: 1 }}> - <View - style={{ - flexDirection: 'row', - alignItems: 'center', - marginBottom: 12, - }} - > - <Text style={{ color: theme.pageTextLight, marginBottom: 0 }}> - This rule applies to these transactions: - </Text> - - <View style={{ flex: 1 }} /> - <Button - isDisabled={selectedInst.items.size === 0} - onPress={onApply} - > - Apply actions ({selectedInst.items.size}) - </Button> - </View> - - <SimpleTransactionsTable - transactions={transactions} - fields={getTransactionFields( - conditions, - getActions(actionSplits), - )} - style={{ - border: '1px solid ' + theme.tableBorder, - borderRadius: '6px 6px 0 0', - }} - /> - - <Stack - direction="row" - justify="flex-end" - style={{ marginTop: 20 }} - > - <Button onPress={() => modalProps.onClose()}>Cancel</Button> - <Button variant="primary" onPress={() => onSave()}> - Save - </Button> - </Stack> - </View> - </SelectedProvider> - </View> + </> )} </Modal> ); diff --git a/packages/desktop-client/src/components/modals/FixEncryptionKeyModal.tsx b/packages/desktop-client/src/components/modals/FixEncryptionKeyModal.tsx index 4efbe26ed..c48a6e175 100644 --- a/packages/desktop-client/src/components/modals/FixEncryptionKeyModal.tsx +++ b/packages/desktop-client/src/components/modals/FixEncryptionKeyModal.tsx @@ -12,19 +12,21 @@ import { Button, ButtonWithLoading } from '../common/Button2'; import { InitialFocus } from '../common/InitialFocus'; import { Input } from '../common/Input'; import { Link } from '../common/Link'; -import { Modal, ModalButtons } from '../common/Modal'; +import { + Modal, + ModalButtons, + ModalCloseButton, + ModalHeader, +} from '../common/Modal2'; import { Paragraph } from '../common/Paragraph'; import { Text } from '../common/Text'; import { View } from '../common/View'; -import { type CommonModalProps } from '../Modals'; type FixEncryptionKeyModalProps = { - modalProps: CommonModalProps; options: FinanceModals['fix-encryption-key']; }; export function FixEncryptionKeyModal({ - modalProps, options = {}, }: FixEncryptionKeyModalProps) { const { hasExistingKey, cloudFileId, onSuccess } = options; @@ -50,122 +52,128 @@ export function FixEncryptionKeyModal({ return; } - modalProps.onClose(); onSuccess?.(); } } return ( - <Modal - {...modalProps} - title={ - hasExistingKey ? 'Unable to decrypt file' : 'This file is encrypted' - } - onClose={modalProps.onClose} - > - <View - style={{ - maxWidth: 500, - overflowX: 'hidden', - overflowY: 'auto', - flex: 1, - }} - > - {hasExistingKey ? ( - <Paragraph> - This file was encrypted with a different key than you are currently - using. This probably means you changed your password. Enter your - current password to update your key.{' '} - <Link - variant="external" - to="https://actualbudget.org/docs/getting-started/sync/#end-to-end-encryption" - > - Learn more - </Link> - </Paragraph> - ) : ( - <Paragraph> - We don’t have a key that encrypts or decrypts this file. Enter the - password for this file to create the key for encryption.{' '} - <Link - variant="external" - to="https://actualbudget.org/docs/getting-started/sync/#end-to-end-encryption" - > - Learn more - </Link> - </Paragraph> - )} - </View> - <Form - onSubmit={e => { - e.preventDefault(); - onUpdateKey(); - }} - > - <View - style={{ - marginTop: 15, - flexDirection: 'column', - alignItems: 'center', - }} - > - <Text style={{ fontWeight: 600, marginBottom: 5 }}>Password</Text>{' '} - {error && ( + <Modal name="fix-encryption-key"> + {({ state: { close } }) => ( + <> + <ModalHeader + title={ + hasExistingKey + ? 'Unable to decrypt file' + : 'This file is encrypted' + } + rightContent={<ModalCloseButton onClick={close} />} + /> + <View + style={{ + maxWidth: 500, + overflowX: 'hidden', + overflowY: 'auto', + flex: 1, + }} + > + {hasExistingKey ? ( + <Paragraph> + This file was encrypted with a different key than you are + currently using. This probably means you changed your password. + Enter your current password to update your key.{' '} + <Link + variant="external" + to="https://actualbudget.org/docs/getting-started/sync/#end-to-end-encryption" + > + Learn more + </Link> + </Paragraph> + ) : ( + <Paragraph> + We don’t have a key that encrypts or decrypts this file. Enter + the password for this file to create the key for encryption.{' '} + <Link + variant="external" + to="https://actualbudget.org/docs/getting-started/sync/#end-to-end-encryption" + > + Learn more + </Link> + </Paragraph> + )} + </View> + <Form + onSubmit={e => { + e.preventDefault(); + onUpdateKey(); + close(); + }} + > <View style={{ - color: theme.errorText, - textAlign: 'center', - fontSize: 13, - marginBottom: 3, + marginTop: 15, + flexDirection: 'column', + alignItems: 'center', }} > - {error} + <Text style={{ fontWeight: 600, marginBottom: 5 }}>Password</Text>{' '} + {error && ( + <View + style={{ + color: theme.errorText, + textAlign: 'center', + fontSize: 13, + marginBottom: 3, + }} + > + {error} + </View> + )} + <InitialFocus> + <Input + type={showPassword ? 'text' : 'password'} + style={{ + width: isNarrowWidth ? '100%' : '50%', + height: isNarrowWidth ? styles.mobileMinHeight : undefined, + }} + onChange={e => setPassword(e.target.value)} + /> + </InitialFocus> + <Text style={{ marginTop: 5 }}> + <label style={{ userSelect: 'none' }}> + <input + type="checkbox" + onClick={() => setShowPassword(!showPassword)} + />{' '} + Show password + </label> + </Text> </View> - )} - <InitialFocus> - <Input - type={showPassword ? 'text' : 'password'} - style={{ - width: isNarrowWidth ? '100%' : '50%', - height: isNarrowWidth ? styles.mobileMinHeight : undefined, - }} - onChange={e => setPassword(e.target.value)} - /> - </InitialFocus> - <Text style={{ marginTop: 5 }}> - <label style={{ userSelect: 'none' }}> - <input - type="checkbox" - onClick={() => setShowPassword(!showPassword)} - />{' '} - Show password - </label> - </Text> - </View> - <ModalButtons style={{ marginTop: 20 }}> - <Button - variant="normal" - style={{ - height: isNarrowWidth ? styles.mobileMinHeight : undefined, - marginRight: 10, - }} - onPress={() => modalProps.onBack()} - > - Back - </Button> - <ButtonWithLoading - type="submit" - variant="primary" - style={{ - height: isNarrowWidth ? styles.mobileMinHeight : undefined, - }} - isLoading={loading} - > - {hasExistingKey ? 'Update key' : 'Create key'} - </ButtonWithLoading> - </ModalButtons> - </Form> + <ModalButtons style={{ marginTop: 20 }}> + <Button + variant="normal" + style={{ + height: isNarrowWidth ? styles.mobileMinHeight : undefined, + marginRight: 10, + }} + onPress={close} + > + Back + </Button> + <ButtonWithLoading + type="submit" + variant="primary" + style={{ + height: isNarrowWidth ? styles.mobileMinHeight : undefined, + }} + isLoading={loading} + > + {hasExistingKey ? 'Update key' : 'Create key'} + </ButtonWithLoading> + </ModalButtons> + </Form> + </> + )} </Modal> ); } diff --git a/packages/desktop-client/src/components/modals/GoCardlessExternalMsg.tsx b/packages/desktop-client/src/components/modals/GoCardlessExternalMsg.tsx index 9cb6e106b..2dbbc049e 100644 --- a/packages/desktop-client/src/components/modals/GoCardlessExternalMsg.tsx +++ b/packages/desktop-client/src/components/modals/GoCardlessExternalMsg.tsx @@ -16,11 +16,10 @@ import { Error, Warning } from '../alerts'; import { Autocomplete } from '../autocomplete/Autocomplete'; import { Button } from '../common/Button2'; import { Link } from '../common/Link'; -import { Modal } from '../common/Modal'; +import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal2'; import { Paragraph } from '../common/Paragraph'; import { View } from '../common/View'; import { FormField, FormLabel } from '../forms'; -import { type CommonModalProps } from '../Modals'; import { COUNTRY_OPTIONS } from './countries'; @@ -74,7 +73,6 @@ function renderError(error: 'unknown' | 'timeout') { } type GoCardlessExternalMsgProps = { - modalProps: CommonModalProps; onMoveExternal: (arg: { institutionId: string; }) => Promise<{ error?: 'unknown' | 'timeout'; data?: GoCardlessToken }>; @@ -83,10 +81,9 @@ type GoCardlessExternalMsgProps = { }; export function GoCardlessExternalMsg({ - modalProps, onMoveExternal, onSuccess, - onClose: originalOnClose, + onClose, }: GoCardlessExternalMsgProps) { const dispatch = useDispatch(); @@ -126,11 +123,6 @@ export function GoCardlessExternalMsg({ setSuccess(true); } - function onClose() { - originalOnClose?.(); - modalProps.onClose(); - } - async function onContinue() { setWaiting('accounts'); await onSuccess(data.current); @@ -232,69 +224,78 @@ export function GoCardlessExternalMsg({ return ( <Modal - title="Link Your Bank" - {...modalProps} + name="gocardless-external-msg" onClose={onClose} - style={{ flex: 0 }} + containerProps={{ style: { width: '30vw' } }} > - {() => ( - <View> - <Paragraph style={{ fontSize: 15 }}> - To link your bank account, you will be redirected to a new page - where GoCardless will ask to connect to your bank. GoCardless will - not be able to withdraw funds from your accounts. - </Paragraph> + {({ state: { close } }) => ( + <> + <ModalHeader + title="Link Your Bank" + rightContent={<ModalCloseButton onClick={close} />} + /> + <View> + <Paragraph style={{ fontSize: 15 }}> + To link your bank account, you will be redirected to a new page + where GoCardless will ask to connect to your bank. GoCardless will + not be able to withdraw funds from your accounts. + </Paragraph> - {error && renderError(error)} + {error && renderError(error)} - {waiting || isConfigurationLoading ? ( - <View style={{ alignItems: 'center', marginTop: 15 }}> - <AnimatedLoading - color={theme.pageTextDark} - style={{ width: 20, height: 20 }} - /> - <View style={{ marginTop: 10, color: theme.pageText }}> - {isConfigurationLoading - ? 'Checking GoCardless configuration..' - : waiting === 'browser' - ? 'Waiting on GoCardless...' - : waiting === 'accounts' - ? 'Loading accounts...' - : null} - </View> + {waiting || isConfigurationLoading ? ( + <View style={{ alignItems: 'center', marginTop: 15 }}> + <AnimatedLoading + color={theme.pageTextDark} + style={{ width: 20, height: 20 }} + /> + <View style={{ marginTop: 10, color: theme.pageText }}> + {isConfigurationLoading + ? 'Checking GoCardless configuration..' + : waiting === 'browser' + ? 'Waiting on GoCardless...' + : waiting === 'accounts' + ? 'Loading accounts...' + : null} + </View> - {waiting === 'browser' && ( - <Link variant="text" onClick={onJump} style={{ marginTop: 10 }}> - (Account linking not opening in a new tab? Click here) - </Link> - )} - </View> - ) : success ? ( - <Button - variant="primary" - style={{ - padding: '10px 0', - fontSize: 15, - fontWeight: 600, - marginTop: 10, - }} - onPress={onContinue} - > - Success! Click to continue → - </Button> - ) : isConfigured || isGoCardlessSetupComplete ? ( - renderLinkButton() - ) : ( - <> - <Paragraph style={{ color: theme.errorText }}> - GoCardless integration has not yet been configured. - </Paragraph> - <Button variant="primary" onPress={onGoCardlessInit}> - Configure GoCardless integration + {waiting === 'browser' && ( + <Link + variant="text" + onClick={onJump} + style={{ marginTop: 10 }} + > + (Account linking not opening in a new tab? Click here) + </Link> + )} + </View> + ) : success ? ( + <Button + variant="primary" + style={{ + padding: '10px 0', + fontSize: 15, + fontWeight: 600, + marginTop: 10, + }} + onPress={onContinue} + > + Success! Click to continue → </Button> - </> - )} - </View> + ) : isConfigured || isGoCardlessSetupComplete ? ( + renderLinkButton() + ) : ( + <> + <Paragraph style={{ color: theme.errorText }}> + GoCardless integration has not yet been configured. + </Paragraph> + <Button variant="primary" onPress={onGoCardlessInit}> + Configure GoCardless integration + </Button> + </> + )} + </View> + </> )} </Modal> ); diff --git a/packages/desktop-client/src/components/modals/GoCardlessInitialise.tsx b/packages/desktop-client/src/components/modals/GoCardlessInitialise.tsx index 021d488a3..3ba1ea3d0 100644 --- a/packages/desktop-client/src/components/modals/GoCardlessInitialise.tsx +++ b/packages/desktop-client/src/components/modals/GoCardlessInitialise.tsx @@ -7,19 +7,21 @@ import { Error } from '../alerts'; import { ButtonWithLoading } from '../common/Button2'; import { Input } from '../common/Input'; import { Link } from '../common/Link'; -import { Modal, ModalButtons } from '../common/Modal'; -import type { ModalProps } from '../common/Modal'; +import { + Modal, + ModalButtons, + ModalCloseButton, + ModalHeader, +} from '../common/Modal2'; import { Text } from '../common/Text'; import { View } from '../common/View'; import { FormField, FormLabel } from '../forms'; type GoCardlessInitialiseProps = { - modalProps?: Partial<ModalProps>; onSuccess: () => void; }; export const GoCardlessInitialise = ({ - modalProps, onSuccess, }: GoCardlessInitialiseProps) => { const [secretId, setSecretId] = useState(''); @@ -47,69 +49,79 @@ export const GoCardlessInitialise = ({ ]); onSuccess(); - modalProps.onClose(); setIsLoading(false); }; return ( - <Modal title="Set-up GoCardless" size={{ width: 300 }} {...modalProps}> - <View style={{ display: 'flex', gap: 10 }}> - <Text> - In order to enable bank-sync via GoCardless (only for EU banks) you - will need to create access credentials. This can be done by creating - an account with{' '} - <Link - variant="external" - to="https://actualbudget.org/docs/advanced/bank-sync/" - linkColor="purple" - > - GoCardless - </Link> - . - </Text> - - <FormField> - <FormLabel title="Secret ID:" htmlFor="secret-id-field" /> - <Input - id="secret-id-field" - type="password" - value={secretId} - onChangeValue={value => { - setSecretId(value); - setIsValid(true); - }} + <Modal name="gocardless-init" containerProps={{ style: { width: '30vw' } }}> + {({ state: { close } }) => ( + <> + <ModalHeader + title="Set-up GoCardless" + rightContent={<ModalCloseButton onClick={close} />} /> - </FormField> + <View style={{ display: 'flex', gap: 10 }}> + <Text> + In order to enable bank-sync via GoCardless (only for EU banks) + you will need to create access credentials. This can be done by + creating an account with{' '} + <Link + variant="external" + to="https://actualbudget.org/docs/advanced/bank-sync/" + linkColor="purple" + > + GoCardless + </Link> + . + </Text> - <FormField> - <FormLabel title="Secret Key:" htmlFor="secret-key-field" /> - <Input - id="secret-key-field" - type="password" - value={secretKey} - onChangeValue={value => { - setSecretKey(value); - setIsValid(true); - }} - /> - </FormField> + <FormField> + <FormLabel title="Secret ID:" htmlFor="secret-id-field" /> + <Input + id="secret-id-field" + type="password" + value={secretId} + onChangeValue={value => { + setSecretId(value); + setIsValid(true); + }} + /> + </FormField> + + <FormField> + <FormLabel title="Secret Key:" htmlFor="secret-key-field" /> + <Input + id="secret-key-field" + type="password" + value={secretKey} + onChangeValue={value => { + setSecretKey(value); + setIsValid(true); + }} + /> + </FormField> - {!isValid && ( - <Error> - It is required to provide both the secret id and secret key. - </Error> - )} - </View> + {!isValid && ( + <Error> + It is required to provide both the secret id and secret key. + </Error> + )} + </View> - <ModalButtons> - <ButtonWithLoading - variant="primary" - isLoading={isLoading} - onPress={onSubmit} - > - Save and continue - </ButtonWithLoading> - </ModalButtons> + <ModalButtons> + <ButtonWithLoading + variant="primary" + isLoading={isLoading} + onPress={() => { + onSubmit(); + close(); + }} + > + Save and continue + </ButtonWithLoading> + </ModalButtons> + </> + )} </Modal> ); }; diff --git a/packages/desktop-client/src/components/modals/HoldBufferModal.tsx b/packages/desktop-client/src/components/modals/HoldBufferModal.tsx index 292f92c8a..d2fe1cb4a 100644 --- a/packages/desktop-client/src/components/modals/HoldBufferModal.tsx +++ b/packages/desktop-client/src/components/modals/HoldBufferModal.tsx @@ -5,23 +5,18 @@ import { rolloverBudget } from 'loot-core/client/queries'; import { styles } from '../../style'; import { Button } from '../common/Button2'; import { InitialFocus } from '../common/InitialFocus'; -import { Modal } from '../common/Modal'; +import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal2'; import { View } from '../common/View'; import { FieldLabel } from '../mobile/MobileForms'; -import { type CommonModalProps } from '../Modals'; import { useSheetValue } from '../spreadsheet/useSheetValue'; import { AmountInput } from '../util/AmountInput'; type HoldBufferModalProps = { - modalProps: CommonModalProps; month: string; onSubmit: (amount: number) => void; }; -export function HoldBufferModal({ - modalProps, - onSubmit, -}: HoldBufferModalProps) { +export function HoldBufferModal({ onSubmit }: HoldBufferModalProps) { const available = useSheetValue(rolloverBudget.toBudget); const [amount, setAmount] = useState<number>(0); @@ -29,54 +24,58 @@ export function HoldBufferModal({ if (newAmount) { onSubmit?.(newAmount); } - - modalProps.onClose(); }; return ( - <Modal - title="Hold Buffer" - showHeader - focusAfterClose={false} - {...modalProps} - > - <View> - <FieldLabel title="Hold this amount:" /> - <InitialFocus> - <AmountInput - value={available} - autoDecimals={true} + <Modal name="hold-buffer"> + {({ state: { close } }) => ( + <> + <ModalHeader + title="Hold Buffer" + rightContent={<ModalCloseButton onClick={close} />} + /> + <View> + <FieldLabel title="Hold this amount:" /> + <InitialFocus> + <AmountInput + value={available} + autoDecimals={true} + style={{ + marginLeft: styles.mobileEditingPadding, + marginRight: styles.mobileEditingPadding, + }} + inputStyle={{ + height: styles.mobileMinHeight, + }} + onUpdate={setAmount} + onEnter={() => { + _onSubmit(amount); + close(); + }} + /> + </InitialFocus> + </View> + <View style={{ - marginLeft: styles.mobileEditingPadding, - marginRight: styles.mobileEditingPadding, + justifyContent: 'center', + alignItems: 'center', + paddingTop: 10, }} - inputStyle={{ - height: styles.mobileMinHeight, - }} - onUpdate={setAmount} - onEnter={() => _onSubmit(amount)} - /> - </InitialFocus> - </View> - <View - style={{ - justifyContent: 'center', - alignItems: 'center', - paddingTop: 10, - }} - > - <Button - variant="primary" - style={{ - height: styles.mobileMinHeight, - marginLeft: styles.mobileEditingPadding, - marginRight: styles.mobileEditingPadding, - }} - onPress={() => _onSubmit(amount)} - > - Hold - </Button> - </View> + > + <Button + variant="primary" + style={{ + height: styles.mobileMinHeight, + marginLeft: styles.mobileEditingPadding, + marginRight: styles.mobileEditingPadding, + }} + onPress={() => _onSubmit(amount)} + > + Hold + </Button> + </View> + </> + )} </Modal> ); } diff --git a/packages/desktop-client/src/components/modals/ImportTransactions.jsx b/packages/desktop-client/src/components/modals/ImportTransactions.jsx index 1c41776d7..56242cd4f 100644 --- a/packages/desktop-client/src/components/modals/ImportTransactions.jsx +++ b/packages/desktop-client/src/components/modals/ImportTransactions.jsx @@ -14,9 +14,9 @@ import { useDateFormat } from '../../hooks/useDateFormat'; import { useLocalPrefs } from '../../hooks/useLocalPrefs'; import { SvgDownAndRightArrow } from '../../icons/v2'; import { theme, styles } from '../../style'; -import { Button, ButtonWithLoading } from '../common/Button'; +import { Button, ButtonWithLoading } from '../common/Button2'; import { Input } from '../common/Input'; -import { Modal } from '../common/Modal'; +import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal2'; import { Select } from '../common/Select'; import { Stack } from '../common/Stack'; import { Text } from '../common/Text'; @@ -836,7 +836,7 @@ function FieldMappings({ ); } -export function ImportTransactions({ modalProps, options }) { +export function ImportTransactions({ options }) { const dateFormat = useDateFormat() || 'MM/dd/yyyy'; const prefs = useLocalPrefs(); const { @@ -1206,8 +1206,6 @@ export function ImportTransactions({ modalProps, options }) { if (onImported) { onImported(didChange); } - - modalProps.onClose(); } const runImportPreviewCallback = useCallback(async () => { @@ -1382,309 +1380,323 @@ export function ImportTransactions({ modalProps, options }) { return ( <Modal - title={ - 'Import transactions' + (filetype ? ` (${filetype.toUpperCase()})` : '') - } - {...modalProps} - loading={loadingState === 'parsing'} - style={{ width: 800 }} + name="import-transactions" + isLoading={loadingState === 'parsing'} + containerProps={{ style: { width: 800 } }} > - {error && !error.parsed && ( - <View style={{ alignItems: 'center', marginBottom: 15 }}> - <Text style={{ marginRight: 10, color: theme.errorText }}> - <strong>Error:</strong> {error.message} - </Text> - </View> - )} - {(!error || !error.parsed) && ( - <View - style={{ - flex: 'unset', - height: 300, - border: '1px solid ' + theme.tableBorder, - }} - > - <TableHeader headers={headers} /> - - <TableWithNavigator - items={transactions.filter( - trans => - !trans.isMatchedTransaction || - (trans.isMatchedTransaction && reconcile), - )} - fields={['payee', 'category', 'amount']} - style={{ backgroundColor: theme.tableHeaderBackground }} - getItemKey={index => index} - renderEmpty={() => { - return ( - <View - style={{ - textAlign: 'center', - marginTop: 25, - color: theme.tableHeaderText, - fontStyle: 'italic', - }} - > - No transactions found - </View> - ); - }} - renderItem={({ key, style, item }) => ( - <View key={key} style={style}> - <Transaction - transaction={item} - showParsed={filetype === 'csv' || filetype === 'qif'} - parseDateFormat={parseDateFormat} - dateFormat={dateFormat} - fieldMappings={fieldMappings} - splitMode={splitMode} - inOutMode={inOutMode} - outValue={outValue} - flipAmount={flipAmount} - multiplierAmount={multiplierAmount} - categories={categories.list} - onCheckTransaction={onCheckTransaction} - reconcile={reconcile} - /> - </View> - )} + {({ state: { close } }) => ( + <> + <ModalHeader + title={ + 'Import transactions' + + (filetype ? ` (${filetype.toUpperCase()})` : '') + } + rightContent={<ModalCloseButton onClick={close} />} /> - </View> - )} - {error && error.parsed && ( - <View - style={{ - color: theme.errorText, - alignItems: 'center', - marginTop: 10, - }} - > - <Text style={{ maxWidth: 450, marginBottom: 15 }}> - <strong>Error:</strong> {error.message} - </Text> - {error.parsed && ( - <Button onPress={() => onNewFile()}>Select new file...</Button> + {error && !error.parsed && ( + <View style={{ alignItems: 'center', marginBottom: 15 }}> + <Text style={{ marginRight: 10, color: theme.errorText }}> + <strong>Error:</strong> {error.message} + </Text> + </View> )} - </View> - )} - - {filetype === 'csv' && ( - <View style={{ marginTop: 10 }}> - <FieldMappings - transactions={transactions} - onChange={onUpdateFields} - mappings={fieldMappings} - splitMode={splitMode} - inOutMode={inOutMode} - hasHeaderRow={hasHeaderRow} - /> - </View> - )} - - {isOfxFile(filetype) && ( - <CheckboxOption - id="form_fallback_missing_payee" - checked={fallbackMissingPayeeToMemo} - onChange={() => { - setFallbackMissingPayeeToMemo(state => !state); - parse( - filename, - getParseOptions('ofx', { - fallbackMissingPayeeToMemo: !fallbackMissingPayeeToMemo, - }), - ); - }} - > - Use Memo as a fallback for empty Payees - </CheckboxOption> - )} - {(isOfxFile(filetype) || isCamtFile(filetype)) && ( - <CheckboxOption - id="form_dont_reconcile" - checked={reconcile} - onChange={() => { - setReconcile(!reconcile); - }} - > - Merge with existing transactions - </CheckboxOption> - )} - - {/*Import Options */} - {(filetype === 'qif' || filetype === 'csv') && ( - <View style={{ marginTop: 10 }}> - <Stack - direction="row" - align="flex-start" - spacing={1} - style={{ marginTop: 5 }} - > - {/*Date Format */} - <View> - {(filetype === 'qif' || filetype === 'csv') && ( - <DateFormatSelect - transactions={transactions} - fieldMappings={fieldMappings} - parseDateFormat={parseDateFormat} - onChange={value => { - setParseDateFormat(value); - runImportPreview(); - }} - /> + {(!error || !error.parsed) && ( + <View + style={{ + flex: 'unset', + height: 300, + border: '1px solid ' + theme.tableBorder, + }} + > + <TableHeader headers={headers} /> + + <TableWithNavigator + items={transactions.filter( + trans => + !trans.isMatchedTransaction || + (trans.isMatchedTransaction && reconcile), + )} + fields={['payee', 'category', 'amount']} + style={{ backgroundColor: theme.tableHeaderBackground }} + getItemKey={index => index} + renderEmpty={() => { + return ( + <View + style={{ + textAlign: 'center', + marginTop: 25, + color: theme.tableHeaderText, + fontStyle: 'italic', + }} + > + No transactions found + </View> + ); + }} + renderItem={({ key, style, item }) => ( + <View key={key} style={style}> + <Transaction + transaction={item} + showParsed={filetype === 'csv' || filetype === 'qif'} + parseDateFormat={parseDateFormat} + dateFormat={dateFormat} + fieldMappings={fieldMappings} + splitMode={splitMode} + inOutMode={inOutMode} + outValue={outValue} + flipAmount={flipAmount} + multiplierAmount={multiplierAmount} + categories={categories.list} + onCheckTransaction={onCheckTransaction} + reconcile={reconcile} + /> + </View> + )} + /> + </View> + )} + {error && error.parsed && ( + <View + style={{ + color: theme.errorText, + alignItems: 'center', + marginTop: 10, + }} + > + <Text style={{ maxWidth: 450, marginBottom: 15 }}> + <strong>Error:</strong> {error.message} + </Text> + {error.parsed && ( + <Button onPress={() => onNewFile()}>Select new file...</Button> )} </View> + )} - {/* CSV Options */} - {filetype === 'csv' && ( - <View style={{ marginLeft: 10, gap: 5 }}> - <SectionLabel title="CSV OPTIONS" /> - <label - style={{ - display: 'flex', - flexDirection: 'row', - gap: 5, - alignItems: 'baseline', - }} - > - Delimiter: - <Select - options={[ - [',', ','], - [';', ';'], - ['|', '|'], - ['\t', 'tab'], - ]} - value={delimiter} - onChange={value => { - setDelimiter(value); - parse( - filename, - getParseOptions('csv', { - delimiter: value, - hasHeaderRow, - }), - ); - }} - style={{ width: 50 }} - /> - </label> - <CheckboxOption - id="form_has_header" - checked={hasHeaderRow} - onChange={() => { - setHasHeaderRow(!hasHeaderRow); - parse( - filename, - getParseOptions('csv', { - delimiter, - hasHeaderRow: !hasHeaderRow, - }), - ); - }} - > - File has header row - </CheckboxOption> - <CheckboxOption - id="clear_on_import" - checked={clearOnImport} - onChange={() => { - setClearOnImport(!clearOnImport); - }} - > - Clear transactions on import - </CheckboxOption> - <CheckboxOption - id="form_dont_reconcile" - checked={reconcile} - onChange={() => { - setReconcile(!reconcile); - }} - > - Merge with existing transactions - </CheckboxOption> - </View> - )} + {filetype === 'csv' && ( + <View style={{ marginTop: 10 }}> + <FieldMappings + transactions={transactions} + onChange={onUpdateFields} + mappings={fieldMappings} + splitMode={splitMode} + inOutMode={inOutMode} + hasHeaderRow={hasHeaderRow} + /> + </View> + )} - <View style={{ flex: 1 }} /> - - <View style={{ marginRight: 10, gap: 5 }}> - <SectionLabel title="AMOUNT OPTIONS" /> - <CheckboxOption - id="form_flip" - checked={flipAmount} - disabled={splitMode || inOutMode} - onChange={() => { - setFlipAmount(!flipAmount); - runImportPreview(); - }} + {isOfxFile(filetype) && ( + <CheckboxOption + id="form_fallback_missing_payee" + checked={fallbackMissingPayeeToMemo} + onChange={() => { + setFallbackMissingPayeeToMemo(state => !state); + parse( + filename, + getParseOptions('ofx', { + fallbackMissingPayeeToMemo: !fallbackMissingPayeeToMemo, + }), + ); + }} + > + Use Memo as a fallback for empty Payees + </CheckboxOption> + )} + {(isOfxFile(filetype) || isCamtFile(filetype)) && ( + <CheckboxOption + id="form_dont_reconcile" + checked={reconcile} + onChange={() => { + setReconcile(!reconcile); + }} + > + Merge with existing transactions + </CheckboxOption> + )} + + {/*Import Options */} + {(filetype === 'qif' || filetype === 'csv') && ( + <View style={{ marginTop: 10 }}> + <Stack + direction="row" + align="flex-start" + spacing={1} + style={{ marginTop: 5 }} > - Flip amount - </CheckboxOption> - {filetype === 'csv' && ( - <> + {/*Date Format */} + <View> + {(filetype === 'qif' || filetype === 'csv') && ( + <DateFormatSelect + transactions={transactions} + fieldMappings={fieldMappings} + parseDateFormat={parseDateFormat} + onChange={value => { + setParseDateFormat(value); + runImportPreview(); + }} + /> + )} + </View> + + {/* CSV Options */} + {filetype === 'csv' && ( + <View style={{ marginLeft: 10, gap: 5 }}> + <SectionLabel title="CSV OPTIONS" /> + <label + style={{ + display: 'flex', + flexDirection: 'row', + gap: 5, + alignItems: 'baseline', + }} + > + Delimiter: + <Select + options={[ + [',', ','], + [';', ';'], + ['|', '|'], + ['\t', 'tab'], + ]} + value={delimiter} + onChange={value => { + setDelimiter(value); + parse( + filename, + getParseOptions('csv', { + delimiter: value, + hasHeaderRow, + }), + ); + }} + style={{ width: 50 }} + /> + </label> + <CheckboxOption + id="form_has_header" + checked={hasHeaderRow} + onChange={() => { + setHasHeaderRow(!hasHeaderRow); + parse( + filename, + getParseOptions('csv', { + delimiter, + hasHeaderRow: !hasHeaderRow, + }), + ); + }} + > + File has header row + </CheckboxOption> + <CheckboxOption + id="clear_on_import" + checked={clearOnImport} + onChange={() => { + setClearOnImport(!clearOnImport); + }} + > + Clear transactions on import + </CheckboxOption> + <CheckboxOption + id="form_dont_reconcile" + checked={reconcile} + onChange={() => { + setReconcile(!reconcile); + }} + > + Merge with existing transactions + </CheckboxOption> + </View> + )} + + <View style={{ flex: 1 }} /> + + <View style={{ marginRight: 10, gap: 5 }}> + <SectionLabel title="AMOUNT OPTIONS" /> <CheckboxOption - id="form_split" - checked={splitMode} - disabled={inOutMode || flipAmount} + id="form_flip" + checked={flipAmount} + disabled={splitMode || inOutMode} onChange={() => { - onSplitMode(); + setFlipAmount(!flipAmount); runImportPreview(); }} > - Split amount into separate inflow/outflow columns + Flip amount </CheckboxOption> - <InOutOption - inOutMode={inOutMode} - outValue={outValue} - disabled={splitMode || flipAmount} + {filetype === 'csv' && ( + <> + <CheckboxOption + id="form_split" + checked={splitMode} + disabled={inOutMode || flipAmount} + onChange={() => { + onSplitMode(); + runImportPreview(); + }} + > + Split amount into separate inflow/outflow columns + </CheckboxOption> + <InOutOption + inOutMode={inOutMode} + outValue={outValue} + disabled={splitMode || flipAmount} + onToggle={() => { + setInOutMode(!inOutMode); + runImportPreview(); + }} + onChangeText={setOutValue} + /> + </> + )} + <MultiplierOption + multiplierEnabled={multiplierEnabled} + multiplierAmount={multiplierAmount} onToggle={() => { - setInOutMode(!inOutMode); + setMultiplierEnabled(!multiplierEnabled); + setMultiplierAmount(''); runImportPreview(); }} - onChangeText={setOutValue} + onChangeAmount={onMultiplierChange} /> - </> - )} - <MultiplierOption - multiplierEnabled={multiplierEnabled} - multiplierAmount={multiplierAmount} - onToggle={() => { - setMultiplierEnabled(!multiplierEnabled); - setMultiplierAmount(''); - runImportPreview(); + </View> + </Stack> + </View> + )} + + <View style={{ flexDirection: 'row', marginTop: 5 }}> + {/*Submit Button */} + <View + style={{ + alignSelf: 'flex-end', + flexDirection: 'row', + alignItems: 'center', + gap: '1em', + }} + > + <ButtonWithLoading + variant="primary" + isDisabled={ + transactions?.filter(trans => !trans.isMatchedTransaction) + .length === 0 + } + isLoading={loadingState === 'importing'} + onPress={() => { + onImport(); + close(); }} - onChangeAmount={onMultiplierChange} - /> + > + Import{' '} + { + transactions?.filter(trans => !trans.isMatchedTransaction) + .length + }{' '} + transactions + </ButtonWithLoading> </View> - </Stack> - </View> + </View> + </> )} - - <View style={{ flexDirection: 'row', marginTop: 5 }}> - {/*Submit Button */} - <View - style={{ - alignSelf: 'flex-end', - flexDirection: 'row', - alignItems: 'center', - gap: '1em', - }} - > - <ButtonWithLoading - variant="primary" - isDisabled={ - transactions?.filter(trans => !trans.isMatchedTransaction) - .length === 0 - } - isLoading={loadingState === 'importing'} - onPress={onImport} - > - Import{' '} - {transactions?.filter(trans => !trans.isMatchedTransaction).length}{' '} - transactions - </ButtonWithLoading> - </View> - </View> </Modal> ); } diff --git a/packages/desktop-client/src/components/modals/KeyboardShortcutModal.tsx b/packages/desktop-client/src/components/modals/KeyboardShortcutModal.tsx index 5a4a3a0b1..0860c39ad 100644 --- a/packages/desktop-client/src/components/modals/KeyboardShortcutModal.tsx +++ b/packages/desktop-client/src/components/modals/KeyboardShortcutModal.tsx @@ -3,14 +3,10 @@ import { useLocation } from 'react-router-dom'; import * as Platform from 'loot-core/src/client/platform'; import { type CSSProperties } from '../../style'; -import { Modal, type ModalProps } from '../common/Modal'; +import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal2'; import { Text } from '../common/Text'; import { View } from '../common/View'; -type KeyboardShortcutsModalProps = { - modalProps?: Partial<ModalProps>; -}; - type KeyIconProps = { shortcut: string; style?: CSSProperties; @@ -152,156 +148,191 @@ function Shortcut({ ); } -export function KeyboardShortcutModal({ - modalProps, -}: KeyboardShortcutsModalProps) { +export function KeyboardShortcutModal() { const location = useLocation(); const onBudget = location.pathname.startsWith('/budget'); const onAccounts = location.pathname.startsWith('/accounts'); const ctrl = Platform.OS === 'mac' ? '⌘' : 'Ctrl'; return ( - <Modal title="Keyboard Shortcuts" {...modalProps}> - <View - style={{ - flexDirection: 'row', - fontSize: 13, - }} - > - <View> - <Shortcut shortcut="?" description="Show this help dialog" /> - <Shortcut - shortcut="O" - description="Close the current budget and open another" - meta={ctrl} - /> - <Shortcut - shortcut="P" - description="Toggle the privacy filter" - meta={ctrl} - shift={true} + <Modal name="keyboard-shortcuts"> + {({ state: { close } }) => ( + <> + <ModalHeader + title="Keyboard Shortcuts" + rightContent={<ModalCloseButton onClick={close} />} /> - {onBudget && ( - <Shortcut - shortcut="0" - description="View current month" - style={{ - fontVariantNumeric: 'slashed-zero', - }} - /> - )} - {onAccounts && ( - <> - <Shortcut shortcut="Enter" description="Move down when editing" /> + <View + style={{ + flexDirection: 'row', + fontSize: 13, + }} + > + <View> + <Shortcut shortcut="?" description="Show this help dialog" /> <Shortcut - shortcut="Enter" - description="Move up when editing" - shift={true} - /> - <Shortcut - shortcut="I" - description="Import transactions" + shortcut="O" + description="Close the current budget and open another" meta={ctrl} /> - <Shortcut shortcut="B" description="Bank sync" meta={ctrl} /> - <GroupHeading group="Select a transaction, then" /> - <Shortcut - shortcut="J" - description="Move to the next transaction down" - /> - <Shortcut - shortcut="K" - description="Move to the next transaction up" - /> - <Shortcut - shortcut="↑" - description="Move to the next transaction down and scroll" - /> - <Shortcut - shortcut="↓" - description="Move to the next transaction up and scroll" - /> <Shortcut - shortcut="Space" - description="Toggle selection of current transaction" - /> - <Shortcut - shortcut="Space" - description="Toggle all transactions between current and most recently selected transaction" + shortcut="P" + description="Toggle the privacy filter" + meta={ctrl} shift={true} /> - </> - )} - </View> - <View - style={{ - marginRight: 15, - }} - > - <Shortcut - shortcut="Z" - description="Undo the last change" - meta={ctrl} - /> - <Shortcut - shortcut="Z" - description="Redo the last undone change" - shift={true} - meta={ctrl} - /> - {onBudget && ( - <> - <Shortcut shortcut="â†" description="View previous month" /> - <Shortcut shortcut="→" description="View next month" /> - </> - )} - {onAccounts && ( - <> + {onBudget && ( + <Shortcut + shortcut="0" + description="View current month" + style={{ + fontVariantNumeric: 'slashed-zero', + }} + /> + )} + {onAccounts && ( + <> + <Shortcut + shortcut="Enter" + description="Move down when editing" + /> + <Shortcut + shortcut="Enter" + description="Move up when editing" + shift={true} + /> + <Shortcut + shortcut="I" + description="Import transactions" + meta={ctrl} + /> + <Shortcut shortcut="B" description="Bank sync" meta={ctrl} /> + <GroupHeading group="Select a transaction, then" /> + <Shortcut + shortcut="J" + description="Move to the next transaction down" + /> + <Shortcut + shortcut="K" + description="Move to the next transaction up" + /> + <Shortcut + shortcut="↑" + description="Move to the next transaction down and scroll" + /> + <Shortcut + shortcut="↓" + description="Move to the next transaction up and scroll" + /> + <Shortcut + shortcut="Space" + description="Toggle selection of current transaction" + /> + <Shortcut + shortcut="Space" + description="Toggle all transactions between current and most recently selected transaction" + shift={true} + /> + </> + )} + </View> + <View + style={{ + marginRight: 15, + }} + > <Shortcut - shortcut="A" - description="Select all transactions" + shortcut="Z" + description="Undo the last change" meta={ctrl} /> - <Shortcut shortcut="Tab" description="Move right when editing" /> <Shortcut - shortcut="Tab" - description="Move left when editing" + shortcut="Z" + description="Redo the last undone change" shift={true} + meta={ctrl} /> - <Shortcut shortcut="T" description="Add a new transaction" /> - <Shortcut shortcut="F" description="Filter transactions" /> - <GroupHeading group="With transaction(s) selected" /> - <Shortcut - shortcut="F" - description="Filter to the selected transactions" - /> - <Shortcut - shortcut="D" - description="Delete selected transactions" - /> - <Shortcut - shortcut="A" - description="Set account for selected transactions" - /> - <Shortcut - shortcut="P" - description="Set payee for selected transactions" - /> - <Shortcut - shortcut="N" - description="Set notes for selected transactions" - /> - <Shortcut - shortcut="C" - description="Set category for selected transactions" - /> - <Shortcut - shortcut="L" - description="Toggle cleared for current transaction" - /> - </> - )} - </View> - </View> + {onAccounts && ( + <> + <Shortcut + shortcut="Enter" + description="Move up when editing" + shift={true} + /> + <Shortcut + shortcut="Tab" + description="Move left when editing" + shift={true} + /> + {onBudget && ( + <> + <Shortcut + shortcut="â†" + description="View previous month" + /> + <Shortcut shortcut="→" description="View next month" /> + </> + )} + {onAccounts && ( + <> + <Shortcut + shortcut="A" + description="Select all transactions" + meta={ctrl} + /> + <Shortcut + shortcut="Tab" + description="Move right when editing" + /> + <Shortcut + shortcut="Tab" + description="Move left when editing" + shift={true} + /> + <Shortcut + shortcut="T" + description="Add a new transaction" + /> + <Shortcut + shortcut="F" + description="Filter transactions" + /> + <GroupHeading group="With transaction(s) selected" /> + <Shortcut + shortcut="F" + description="Filter to the selected transactions" + /> + <Shortcut + shortcut="D" + description="Delete selected transactions" + /> + <Shortcut + shortcut="A" + description="Set account for selected transactions" + /> + <Shortcut + shortcut="P" + description="Set payee for selected transactions" + /> + <Shortcut + shortcut="N" + description="Set notes for selected transactions" + /> + <Shortcut + shortcut="C" + description="Set category for selected transactions" + /> + <Shortcut + shortcut="L" + description="Toggle cleared for current transaction" + /> + </> + )} + </> + )} + </View> + </View> + </> + )} </Modal> ); } diff --git a/packages/desktop-client/src/components/modals/LoadBackup.jsx b/packages/desktop-client/src/components/modals/LoadBackup.jsx index 9ed813034..21d750d07 100644 --- a/packages/desktop-client/src/components/modals/LoadBackup.jsx +++ b/packages/desktop-client/src/components/modals/LoadBackup.jsx @@ -1,12 +1,14 @@ import React, { Component, useState, useEffect } from 'react'; +import { useDispatch } from 'react-redux'; +import { loadBackup, makeBackup } from 'loot-core/client/actions'; import { send, listen, unlisten } from 'loot-core/src/platform/client/fetch'; import { useLocalPref } from '../../hooks/useLocalPref'; import { theme } from '../../style'; import { Block } from '../common/Block'; import { Button } from '../common/Button2'; -import { Modal } from '../common/Modal'; +import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal2'; import { Text } from '../common/Text'; import { View } from '../common/View'; import { Row, Cell } from '../table'; @@ -48,13 +50,8 @@ class BackupTable extends Component { } } -export function LoadBackup({ - budgetId, - watchUpdates, - backupDisabled, - actions, - modalProps, -}) { +export function LoadBackup({ budgetId, watchUpdates, backupDisabled }) { + const dispatch = useDispatch(); const [backups, setBackups] = useState([]); const [prefsBudgetId] = useLocalPref('id'); const budgetIdToLoad = budgetId || prefsBudgetId; @@ -74,66 +71,73 @@ export function LoadBackup({ const previousBackups = backups.filter(backup => !backup.isLatest); return ( - <Modal title="Load Backup" {...modalProps} style={{ flex: 0 }}> - {() => ( - <View style={{ marginBottom: 30 }}> - <View - style={{ - margin: 20, - marginTop: 0, - marginBottom: 15, - lineHeight: 1.5, - }} - > - {latestBackup ? ( - <Block> - <Block style={{ marginBottom: 10 }}> - <Text style={{ fontWeight: 600 }}> - You are currently working from a backup. - </Text>{' '} - You can load a different backup or revert to the original - version below. + <Modal name="load-backup" containerProps={{ style: { maxWidth: '30vw' } }}> + {({ state: { close } }) => ( + <> + <ModalHeader + title="Load Backup" + rightContent={<ModalCloseButton onClick={close} />} + /> + <View style={{ marginBottom: 30 }}> + <View + style={{ + margin: 20, + marginTop: 0, + marginBottom: 15, + lineHeight: 1.5, + }} + > + {latestBackup ? ( + <Block> + <Block style={{ marginBottom: 10 }}> + <Text style={{ fontWeight: 600 }}> + You are currently working from a backup. + </Text>{' '} + You can load a different backup or revert to the original + version below. + </Block> + <Button + variant="primary" + onPress={() => + dispatch(loadBackup(budgetIdToLoad, latestBackup.id)) + } + > + Revert to original version + </Button> </Block> - <Button - variant="primary" - onPress={() => - actions.loadBackup(budgetIdToLoad, latestBackup.id) - } - > - Revert to original version - </Button> + ) : ( + <View style={{ alignItems: 'flex-start' }}> + <Block style={{ marginBottom: 10 }}> + Select a backup to load. After loading a backup, you will + have a chance to revert to the current version in this + screen.{' '} + <Text style={{ fontWeight: 600 }}> + If you use a backup, you will have to setup all your + devices to sync from the new budget. + </Text> + </Block> + <Button + variant="primary" + isDisabled={backupDisabled} + onPress={() => dispatch(makeBackup())} + > + Backup now + </Button> + </View> + )} + </View> + {previousBackups.length === 0 ? ( + <Block style={{ color: theme.tableTextLight, marginLeft: 20 }}> + No backups available </Block> ) : ( - <View style={{ alignItems: 'flex-start' }}> - <Block style={{ marginBottom: 10 }}> - Select a backup to load. After loading a backup, you will have - a chance to revert to the current version in this screen.{' '} - <Text style={{ fontWeight: 600 }}> - If you use a backup, you will have to setup all your devices - to sync from the new budget. - </Text> - </Block> - <Button - variant="primary" - isDisabled={backupDisabled} - onPress={() => actions.makeBackup()} - > - Backup now - </Button> - </View> + <BackupTable + backups={previousBackups} + onSelect={id => dispatch(loadBackup(budgetIdToLoad, id))} + /> )} </View> - {previousBackups.length === 0 ? ( - <Block style={{ color: theme.tableTextLight, marginLeft: 20 }}> - No backups available - </Block> - ) : ( - <BackupTable - backups={previousBackups} - onSelect={id => actions.loadBackup(budgetIdToLoad, id)} - /> - )} - </View> + </> )} </Modal> ); diff --git a/packages/desktop-client/src/components/modals/ManageRulesModal.tsx b/packages/desktop-client/src/components/modals/ManageRulesModal.tsx index 9d1a69022..898c9992a 100644 --- a/packages/desktop-client/src/components/modals/ManageRulesModal.tsx +++ b/packages/desktop-client/src/components/modals/ManageRulesModal.tsx @@ -4,19 +4,14 @@ import { useLocation } from 'react-router-dom'; import { isNonProductionEnvironment } from 'loot-core/src/shared/environment'; -import { Modal } from '../common/Modal'; +import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal2'; import { ManageRules } from '../ManageRules'; -import { type CommonModalProps } from '../Modals'; type ManageRulesModalProps = { - modalProps: CommonModalProps; payeeId?: string; }; -export function ManageRulesModal({ - modalProps, - payeeId, -}: ManageRulesModalProps) { +export function ManageRulesModal({ payeeId }: ManageRulesModalProps) { const [loading, setLoading] = useState(true); const location = useLocation(); if (isNonProductionEnvironment()) { @@ -28,8 +23,16 @@ export function ManageRulesModal({ } return ( - <Modal title="Rules" loading={loading} {...modalProps}> - {() => <ManageRules isModal payeeId={payeeId} setLoading={setLoading} />} + <Modal name="manage-rules" isLoading={loading}> + {({ state: { close } }) => ( + <> + <ModalHeader + title="Rules" + rightContent={<ModalCloseButton onClick={close} />} + /> + <ManageRules isModal payeeId={payeeId} setLoading={setLoading} /> + </> + )} </Modal> ); } diff --git a/packages/desktop-client/src/components/modals/MergeUnusedPayees.jsx b/packages/desktop-client/src/components/modals/MergeUnusedPayees.jsx index fc1535a89..2a8256869 100644 --- a/packages/desktop-client/src/components/modals/MergeUnusedPayees.jsx +++ b/packages/desktop-client/src/components/modals/MergeUnusedPayees.jsx @@ -8,14 +8,14 @@ import { usePayees } from '../../hooks/usePayees'; import { theme } from '../../style'; import { Information } from '../alerts'; import { Button } from '../common/Button2'; -import { Modal, ModalButtons } from '../common/Modal'; +import { Modal, ModalButtons } from '../common/Modal2'; import { Paragraph } from '../common/Paragraph'; import { Text } from '../common/Text'; import { View } from '../common/View'; const highlightStyle = { color: theme.pageTextPositive }; -export function MergeUnusedPayees({ modalProps, payeeIds, targetPayeeId }) { +export function MergeUnusedPayees({ payeeIds, targetPayeeId }) { const allPayees = usePayees(); const modalStack = useSelector(state => state.modals.modalStack); const isEditingRule = !!modalStack.find(m => m.name === 'edit-rule'); @@ -63,8 +63,6 @@ export function MergeUnusedPayees({ modalProps, payeeIds, targetPayeeId }) { ruleId = id; } - modalProps.onClose(); - return ruleId; } @@ -78,13 +76,8 @@ export function MergeUnusedPayees({ modalProps, payeeIds, targetPayeeId }) { } return ( - <Modal - title="Merge payee?" - showHeader={false} - {...modalProps} - style={modalProps.style} - > - {() => ( + <Modal name="merge-unused-payees"> + {({ state: { close } }) => ( <View style={{ padding: 20, maxWidth: 500 }}> <View> <Paragraph style={{ marginBottom: 10, fontWeight: 500 }}> @@ -159,22 +152,25 @@ export function MergeUnusedPayees({ modalProps, payeeIds, targetPayeeId }) { <Button variant="primary" style={{ marginRight: 10 }} - onPress={onMerge} + onPress={() => { + onMerge(); + close(); + }} > Merge </Button> {!isEditingRule && ( <Button style={{ marginRight: 10 }} - onPress={onMergeAndCreateRule} + onPress={() => { + onMergeAndCreateRule(); + close(); + }} > Merge and edit rule </Button> )} - <Button - style={{ marginRight: 10 }} - onPress={() => modalProps.onBack()} - > + <Button style={{ marginRight: 10 }} onPress={close}> Do nothing </Button> </ModalButtons> diff --git a/packages/desktop-client/src/components/modals/NotesModal.tsx b/packages/desktop-client/src/components/modals/NotesModal.tsx index 04bd35823..1ef3e00ad 100644 --- a/packages/desktop-client/src/components/modals/NotesModal.tsx +++ b/packages/desktop-client/src/components/modals/NotesModal.tsx @@ -4,87 +4,86 @@ import React, { useEffect, useState } from 'react'; import { useNotes } from '../../hooks/useNotes'; import { SvgCheck } from '../../icons/v2'; import { Button } from '../common/Button2'; -import { Modal } from '../common/Modal'; +import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal2'; import { View } from '../common/View'; -import { type CommonModalProps } from '../Modals'; import { Notes } from '../Notes'; type NotesModalProps = { - modalProps: CommonModalProps; id: string; name: string; onSave: (id: string, notes: string) => void; }; -export function NotesModal({ modalProps, id, name, onSave }: NotesModalProps) { +export function NotesModal({ id, name, onSave }: NotesModalProps) { const originalNotes = useNotes(id); const [notes, setNotes] = useState(originalNotes); useEffect(() => setNotes(originalNotes), [originalNotes]); - function _onClose() { - modalProps?.onClose(); - } - function _onSave() { if (notes !== originalNotes) { onSave?.(id, notes); } - - _onClose(); } return ( <Modal - title={`Notes: ${name}`} - showHeader - focusAfterClose={false} - {...modalProps} - onClose={_onClose} - style={{ - height: '50vh', + name="notes" + containerProps={{ + style: { height: '50vh' }, }} > - <View - style={{ - flex: 1, - flexDirection: 'column', - }} - > - <Notes - notes={notes} - editable={true} - focused={true} - getStyle={() => ({ - borderRadius: 6, - flex: 1, - minWidth: 0, - })} - onChange={setNotes} - /> - <View - style={{ - flexDirection: 'column', - alignItems: 'center', - justifyItems: 'center', - width: '100%', - paddingTop: 10, - }} - > - <Button - variant="primary" + {({ state: { close } }) => ( + <> + <ModalHeader + title={`Notes: ${name}`} + rightContent={<ModalCloseButton onClick={close} />} + /> + <View style={{ - fontSize: 17, - fontWeight: 400, - width: '100%', + flex: 1, + flexDirection: 'column', }} - onPress={_onSave} > - <SvgCheck width={17} height={17} style={{ paddingRight: 5 }} /> - Save notes - </Button> - </View> - </View> + <Notes + notes={notes} + editable={true} + focused={true} + getStyle={() => ({ + borderRadius: 6, + flex: 1, + minWidth: 0, + })} + onChange={setNotes} + /> + <View + style={{ + flexDirection: 'column', + alignItems: 'center', + justifyItems: 'center', + width: '100%', + paddingTop: 10, + }} + > + <Button + variant="primary" + style={{ + fontSize: 17, + fontWeight: 400, + width: '100%', + }} + onPress={() => { + _onSave(); + close(); + }} + > + <SvgCheck width={17} height={17} style={{ paddingRight: 5 }} /> + Save notes + </Button> + </View> + </View> + </> + )} </Modal> ); } diff --git a/packages/desktop-client/src/components/modals/PayeeAutocompleteModal.tsx b/packages/desktop-client/src/components/modals/PayeeAutocompleteModal.tsx index 952361cac..9ab0dc1d1 100644 --- a/packages/desktop-client/src/components/modals/PayeeAutocompleteModal.tsx +++ b/packages/desktop-client/src/components/modals/PayeeAutocompleteModal.tsx @@ -6,17 +6,19 @@ import { usePayees } from '../../hooks/usePayees'; import { useResponsive } from '../../ResponsiveProvider'; import { theme } from '../../style'; import { PayeeAutocomplete } from '../autocomplete/PayeeAutocomplete'; -import { ModalCloseButton, Modal, ModalTitle } from '../common/Modal'; -import { type CommonModalProps } from '../Modals'; +import { + ModalCloseButton, + Modal, + ModalTitle, + ModalHeader, +} from '../common/Modal2'; type PayeeAutocompleteModalProps = { - modalProps: CommonModalProps; autocompleteProps: ComponentPropsWithoutRef<typeof PayeeAutocomplete>; onClose: () => void; }; export function PayeeAutocompleteModal({ - modalProps, autocompleteProps, onClose, }: PayeeAutocompleteModalProps) { @@ -24,11 +26,6 @@ export function PayeeAutocompleteModal({ const accounts = useAccounts() || []; const navigate = useNavigate(); - const _onClose = () => { - modalProps.onClose(); - onClose?.(); - }; - const { isNarrowWidth } = useResponsive(); const defaultAutocompleteProps = { containerProps: { style: { height: isNarrowWidth ? '90vh' : 275 } }, @@ -38,41 +35,49 @@ export function PayeeAutocompleteModal({ return ( <Modal - title={ - <ModalTitle - title="Payee" - getStyle={() => ({ color: theme.menuAutoCompleteText })} - /> - } + name="payee-autocomplete" noAnimation={!isNarrowWidth} - showHeader={isNarrowWidth} - focusAfterClose={false} - {...modalProps} - onClose={_onClose} - style={{ - height: isNarrowWidth ? '85vh' : 275, - backgroundColor: theme.menuAutoCompleteBackground, + onClose={onClose} + containerProps={{ + style: { + height: isNarrowWidth ? '85vh' : 275, + backgroundColor: theme.menuAutoCompleteBackground, + }, }} - CloseButton={props => ( - <ModalCloseButton - {...props} - style={{ color: theme.menuAutoCompleteText }} - /> - )} > - <PayeeAutocomplete - payees={payees} - accounts={accounts} - focused={true} - embedded={true} - closeOnBlur={false} - onClose={_onClose} - onManagePayees={onManagePayees} - showManagePayees={!isNarrowWidth} - showMakeTransfer={!isNarrowWidth} - {...defaultAutocompleteProps} - {...autocompleteProps} - /> + {({ state: { close } }) => ( + <> + {isNarrowWidth && ( + <ModalHeader + title={ + <ModalTitle + title="Payee" + getStyle={() => ({ color: theme.menuAutoCompleteText })} + /> + } + rightContent={ + <ModalCloseButton + onClick={close} + style={{ color: theme.menuAutoCompleteText }} + /> + } + /> + )} + <PayeeAutocomplete + payees={payees} + accounts={accounts} + focused={true} + embedded={true} + closeOnBlur={false} + onClose={close} + onManagePayees={onManagePayees} + showManagePayees={!isNarrowWidth} + showMakeTransfer={!isNarrowWidth} + {...defaultAutocompleteProps} + {...autocompleteProps} + /> + </> + )} </Modal> ); } diff --git a/packages/desktop-client/src/components/modals/ReportBalanceMenuModal.tsx b/packages/desktop-client/src/components/modals/ReportBalanceMenuModal.tsx index 9b6246534..01808eea1 100644 --- a/packages/desktop-client/src/components/modals/ReportBalanceMenuModal.tsx +++ b/packages/desktop-client/src/components/modals/ReportBalanceMenuModal.tsx @@ -9,19 +9,18 @@ import { DefaultCarryoverIndicator, } from '../budget/BalanceWithCarryover'; import { BalanceMenu } from '../budget/report/BalanceMenu'; -import { Modal, ModalTitle } from '../common/Modal'; +import { + Modal, + ModalCloseButton, + ModalHeader, + ModalTitle, +} from '../common/Modal2'; import { Text } from '../common/Text'; import { View } from '../common/View'; -import { type CommonModalProps } from '../Modals'; -type ReportBalanceMenuModalProps = ComponentPropsWithoutRef< - typeof BalanceMenu -> & { - modalProps: CommonModalProps; -}; +type ReportBalanceMenuModalProps = ComponentPropsWithoutRef<typeof BalanceMenu>; export function ReportBalanceMenuModal({ - modalProps, categoryId, onCarryover, }: ReportBalanceMenuModalProps) { @@ -39,55 +38,58 @@ export function ReportBalanceMenuModal({ } return ( - <Modal - title={<ModalTitle title={category.name} shrinkOnOverflow />} - showHeader - focusAfterClose={false} - {...modalProps} - > - <View - style={{ - justifyContent: 'center', - alignItems: 'center', - marginBottom: 20, - }} - > - <Text - style={{ - fontSize: 17, - fontWeight: 400, - }} - > - Balance - </Text> - <BalanceWithCarryover - disabled - style={{ - textAlign: 'center', - ...styles.veryLargeText, - }} - carryover={reportBudget.catCarryover(categoryId)} - balance={reportBudget.catBalance(categoryId)} - goal={reportBudget.catGoal(categoryId)} - budgeted={reportBudget.catBudgeted(categoryId)} - carryoverIndicator={({ style }) => - DefaultCarryoverIndicator({ - style: { - width: 15, - height: 15, - display: 'inline-flex', - position: 'relative', - ...style, - }, - }) - } - /> - </View> - <BalanceMenu - categoryId={categoryId} - getItemStyle={() => defaultMenuItemStyle} - onCarryover={onCarryover} - /> + <Modal name="report-balance-menu"> + {({ state: { close } }) => ( + <> + <ModalHeader + title={<ModalTitle title={category.name} shrinkOnOverflow />} + rightContent={<ModalCloseButton onClick={close} />} + /> + <View + style={{ + justifyContent: 'center', + alignItems: 'center', + marginBottom: 20, + }} + > + <Text + style={{ + fontSize: 17, + fontWeight: 400, + }} + > + Balance + </Text> + <BalanceWithCarryover + disabled + style={{ + textAlign: 'center', + ...styles.veryLargeText, + }} + carryover={reportBudget.catCarryover(categoryId)} + balance={reportBudget.catBalance(categoryId)} + goal={reportBudget.catGoal(categoryId)} + budgeted={reportBudget.catBudgeted(categoryId)} + carryoverIndicator={({ style }) => + DefaultCarryoverIndicator({ + style: { + width: 15, + height: 15, + display: 'inline-flex', + position: 'relative', + ...style, + }, + }) + } + /> + </View> + <BalanceMenu + categoryId={categoryId} + getItemStyle={() => defaultMenuItemStyle} + onCarryover={onCarryover} + /> + </> + )} </Modal> ); } diff --git a/packages/desktop-client/src/components/modals/ReportBudgetMenuModal.tsx b/packages/desktop-client/src/components/modals/ReportBudgetMenuModal.tsx index 13ac4bd66..b28976d63 100644 --- a/packages/desktop-client/src/components/modals/ReportBudgetMenuModal.tsx +++ b/packages/desktop-client/src/components/modals/ReportBudgetMenuModal.tsx @@ -10,23 +10,25 @@ import { amountToInteger, integerToAmount } from 'loot-core/shared/util'; import { useCategory } from '../../hooks/useCategory'; import { type CSSProperties, theme, styles } from '../../style'; import { BudgetMenu } from '../budget/report/BudgetMenu'; -import { Modal, ModalTitle } from '../common/Modal'; +import { + Modal, + ModalCloseButton, + ModalHeader, + ModalTitle, +} from '../common/Modal2'; import { Text } from '../common/Text'; import { View } from '../common/View'; import { FocusableAmountInput } from '../mobile/transactions/FocusableAmountInput'; -import { type CommonModalProps } from '../Modals'; import { useSheetValue } from '../spreadsheet/useSheetValue'; type ReportBudgetMenuModalProps = ComponentPropsWithoutRef< typeof BudgetMenu > & { - modalProps: CommonModalProps; categoryId: string; onUpdateBudget: (amount: number) => void; }; export function ReportBudgetMenuModal({ - modalProps, categoryId, onUpdateBudget, onCopyLastMonthAverage, @@ -57,51 +59,54 @@ export function ReportBudgetMenuModal({ } return ( - <Modal - title={<ModalTitle title={category.name} shrinkOnOverflow />} - showHeader - focusAfterClose={false} - {...modalProps} - > - <View - style={{ - justifyContent: 'center', - alignItems: 'center', - marginBottom: 20, - }} - > - <Text - style={{ - fontSize: 17, - fontWeight: 400, - }} - > - Budget - </Text> - <FocusableAmountInput - value={integerToAmount(budgeted || 0)} - focused={amountFocused} - onFocus={() => setAmountFocused(true)} - onBlur={() => setAmountFocused(false)} - onEnter={() => modalProps.onClose()} - zeroSign="+" - focusedStyle={{ - width: 'auto', - padding: '5px', - paddingLeft: '20px', - paddingRight: '20px', - minWidth: '100%', - }} - textStyle={{ ...styles.veryLargeText, textAlign: 'center' }} - onUpdateAmount={_onUpdateBudget} - /> - </View> - <BudgetMenu - getItemStyle={() => defaultMenuItemStyle} - onCopyLastMonthAverage={onCopyLastMonthAverage} - onSetMonthsAverage={onSetMonthsAverage} - onApplyBudgetTemplate={onApplyBudgetTemplate} - /> + <Modal name="report-budget-menu"> + {({ state: { close } }) => ( + <> + <ModalHeader + title={<ModalTitle title={category.name} shrinkOnOverflow />} + rightContent={<ModalCloseButton onClick={close} />} + /> + <View + style={{ + justifyContent: 'center', + alignItems: 'center', + marginBottom: 20, + }} + > + <Text + style={{ + fontSize: 17, + fontWeight: 400, + }} + > + Budget + </Text> + <FocusableAmountInput + value={integerToAmount(budgeted || 0)} + focused={amountFocused} + onFocus={() => setAmountFocused(true)} + onBlur={() => setAmountFocused(false)} + onEnter={close} + zeroSign="+" + focusedStyle={{ + width: 'auto', + padding: '5px', + paddingLeft: '20px', + paddingRight: '20px', + minWidth: '100%', + }} + textStyle={{ ...styles.veryLargeText, textAlign: 'center' }} + onUpdateAmount={_onUpdateBudget} + /> + </View> + <BudgetMenu + getItemStyle={() => defaultMenuItemStyle} + onCopyLastMonthAverage={onCopyLastMonthAverage} + onSetMonthsAverage={onSetMonthsAverage} + onApplyBudgetTemplate={onApplyBudgetTemplate} + /> + </> + )} </Modal> ); } diff --git a/packages/desktop-client/src/components/modals/ReportBudgetMonthMenuModal.tsx b/packages/desktop-client/src/components/modals/ReportBudgetMonthMenuModal.tsx index 72eee1de2..4c996dfc0 100644 --- a/packages/desktop-client/src/components/modals/ReportBudgetMonthMenuModal.tsx +++ b/packages/desktop-client/src/components/modals/ReportBudgetMonthMenuModal.tsx @@ -9,30 +9,23 @@ import { SvgNotesPaper } from '../../icons/v2'; import { type CSSProperties, styles, theme } from '../../style'; import { BudgetMonthMenu } from '../budget/report/budgetsummary/BudgetMonthMenu'; import { Button } from '../common/Button2'; -import { Modal, ModalTitle } from '../common/Modal'; +import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal2'; import { View } from '../common/View'; -import { type CommonModalProps } from '../Modals'; import { Notes } from '../Notes'; type ReportBudgetMonthMenuModalProps = { - modalProps: CommonModalProps; month: string; onBudgetAction: (month: string, action: string, arg?: unknown) => void; onEditNotes: (month: string) => void; }; export function ReportBudgetMonthMenuModal({ - modalProps, month, onBudgetAction, onEditNotes, }: ReportBudgetMonthMenuModalProps) { const originalNotes = useNotes(`budget-${month}`); - const onClose = () => { - modalProps.onClose(); - }; - const _onEditNotes = () => { onEditNotes?.(month); }; @@ -60,120 +53,127 @@ export function ReportBudgetMonthMenuModal({ return ( <Modal - title={<ModalTitle title={monthUtils.format(month, 'MMMM ‘yy')} />} - showHeader - focusAfterClose={false} - {...modalProps} - onClose={onClose} - style={{ - height: '50vh', + name="report-budget-month-menu" + containerProps={{ + style: { height: '50vh' }, }} > - <View - style={{ - flex: 1, - flexDirection: 'column', - }} - > - <View - style={{ - display: showMore ? 'none' : undefined, - overflowY: 'auto', - flex: 1, - }} - > - <Notes - notes={originalNotes?.length > 0 ? originalNotes : 'No notes'} - editable={false} - focused={false} - getStyle={() => ({ - borderRadius: 6, - ...((!originalNotes || originalNotes.length === 0) && { - justifySelf: 'center', - alignSelf: 'center', - color: theme.pageTextSubdued, - }), - })} + {({ state: { close } }) => ( + <> + <ModalHeader + title={monthUtils.format(month, 'MMMM ‘yy')} + rightContent={<ModalCloseButton onClick={close} />} /> - </View> - <View style={{ paddingTop: 10, gap: 5 }}> <View style={{ - display: showMore ? 'none' : undefined, - flexDirection: 'row', - flexWrap: 'wrap', - justifyContent: 'space-between', - alignContent: 'space-between', + flex: 1, + flexDirection: 'column', }} > - <Button style={buttonStyle} onPress={_onEditNotes}> - <SvgNotesPaper - width={20} - height={20} - style={{ paddingRight: 5 }} - /> - Edit notes - </Button> - </View> - <View> - <Button - variant="bare" - style={({ isPressed, isHovered }) => ({ - ...buttonStyle, - ...(isPressed || isHovered - ? { backgroundColor: 'transparent', color: buttonStyle.color } - : {}), - })} - onPress={onShowMore} + <View + style={{ + display: showMore ? 'none' : undefined, + overflowY: 'auto', + flex: 1, + }} > - {!showMore ? ( - <SvgCheveronUp - width={30} - height={30} - style={{ paddingRight: 5 }} - /> - ) : ( - <SvgCheveronDown - width={30} - height={30} - style={{ paddingRight: 5 }} - /> - )} - Actions - </Button> + <Notes + notes={originalNotes?.length > 0 ? originalNotes : 'No notes'} + editable={false} + focused={false} + getStyle={() => ({ + borderRadius: 6, + ...((!originalNotes || originalNotes.length === 0) && { + justifySelf: 'center', + alignSelf: 'center', + color: theme.pageTextSubdued, + }), + })} + /> + </View> + <View style={{ paddingTop: 10, gap: 5 }}> + <View + style={{ + display: showMore ? 'none' : undefined, + flexDirection: 'row', + flexWrap: 'wrap', + justifyContent: 'space-between', + alignContent: 'space-between', + }} + > + <Button style={buttonStyle} onPress={_onEditNotes}> + <SvgNotesPaper + width={20} + height={20} + style={{ paddingRight: 5 }} + /> + Edit notes + </Button> + </View> + <View> + <Button + variant="bare" + style={({ isPressed, isHovered }) => ({ + ...buttonStyle, + ...(isPressed || isHovered + ? { + backgroundColor: 'transparent', + color: buttonStyle.color, + } + : {}), + })} + onPress={onShowMore} + > + {!showMore ? ( + <SvgCheveronUp + width={30} + height={30} + style={{ paddingRight: 5 }} + /> + ) : ( + <SvgCheveronDown + width={30} + height={30} + style={{ paddingRight: 5 }} + /> + )} + Actions + </Button> + </View> + </View> + {showMore && ( + <BudgetMonthMenu + style={{ overflowY: 'auto', paddingTop: 10 }} + getItemStyle={() => defaultMenuItemStyle} + onCopyLastMonthBudget={() => { + onBudgetAction(month, 'copy-last'); + close(); + }} + onSetBudgetsToZero={() => { + onBudgetAction(month, 'set-zero'); + close(); + }} + onSetMonthsAverage={numberOfMonths => { + onBudgetAction(month, `set-${numberOfMonths}-avg`); + close(); + }} + onCheckTemplates={() => { + onBudgetAction(month, 'check-templates'); + close(); + }} + onApplyBudgetTemplates={() => { + onBudgetAction(month, 'apply-goal-template'); + close(); + }} + onOverwriteWithBudgetTemplates={() => { + onBudgetAction(month, 'overwrite-goal-template'); + close(); + }} + /> + )} </View> - </View> - {showMore && ( - <BudgetMonthMenu - style={{ overflowY: 'auto', paddingTop: 10 }} - getItemStyle={() => defaultMenuItemStyle} - onCopyLastMonthBudget={() => { - onBudgetAction(month, 'copy-last'); - onClose(); - }} - onSetBudgetsToZero={() => { - onBudgetAction(month, 'set-zero'); - onClose(); - }} - onSetMonthsAverage={numberOfMonths => { - onBudgetAction(month, `set-${numberOfMonths}-avg`); - onClose(); - }} - onCheckTemplates={() => { - onBudgetAction(month, 'check-templates'); - onClose(); - }} - onApplyBudgetTemplates={() => { - onBudgetAction(month, 'apply-goal-template'); - onClose(); - }} - onOverwriteWithBudgetTemplates={() => { - onBudgetAction(month, 'overwrite-goal-template'); - onClose(); - }} - /> - )} - </View> + </> + )} </Modal> ); } diff --git a/packages/desktop-client/src/components/modals/ReportBudgetSummaryModal.tsx b/packages/desktop-client/src/components/modals/ReportBudgetSummaryModal.tsx index 144da8fe0..7515dec21 100644 --- a/packages/desktop-client/src/components/modals/ReportBudgetSummaryModal.tsx +++ b/packages/desktop-client/src/components/modals/ReportBudgetSummaryModal.tsx @@ -7,40 +7,45 @@ import { styles } from '../../style'; import { ExpenseTotal } from '../budget/report/budgetsummary/ExpenseTotal'; import { IncomeTotal } from '../budget/report/budgetsummary/IncomeTotal'; import { Saved } from '../budget/report/budgetsummary/Saved'; -import { Modal } from '../common/Modal'; +import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal2'; import { Stack } from '../common/Stack'; -import { type CommonModalProps } from '../Modals'; import { NamespaceContext } from '../spreadsheet/NamespaceContext'; type ReportBudgetSummaryModalProps = { - modalProps: CommonModalProps; month: string; }; export function ReportBudgetSummaryModal({ month, - modalProps, }: ReportBudgetSummaryModalProps) { const currentMonth = monthUtils.currentMonth(); return ( - <Modal title="Budget Summary" {...modalProps}> - <NamespaceContext.Provider value={sheetForMonth(month)}> - <Stack - spacing={2} - style={{ - alignSelf: 'center', - backgroundColor: 'transparent', - borderRadius: 4, - }} - > - <IncomeTotal style={{ ...styles.mediumText }} /> - <ExpenseTotal style={{ ...styles.mediumText }} /> - </Stack> - <Saved - projected={month >= currentMonth} - style={{ ...styles.mediumText, marginTop: 20 }} - /> - </NamespaceContext.Provider> + <Modal name="report-budget-summary"> + {({ state: { close } }) => ( + <> + <ModalHeader + title="Budget Summary" + rightContent={<ModalCloseButton onClick={close} />} + /> + <NamespaceContext.Provider value={sheetForMonth(month)}> + <Stack + spacing={2} + style={{ + alignSelf: 'center', + backgroundColor: 'transparent', + borderRadius: 4, + }} + > + <IncomeTotal style={{ ...styles.mediumText }} /> + <ExpenseTotal style={{ ...styles.mediumText }} /> + </Stack> + <Saved + projected={month >= currentMonth} + style={{ ...styles.mediumText, marginTop: 20 }} + /> + </NamespaceContext.Provider> + </> + )} </Modal> ); } diff --git a/packages/desktop-client/src/components/modals/RolloverBalanceMenuModal.tsx b/packages/desktop-client/src/components/modals/RolloverBalanceMenuModal.tsx index 29a4ea856..c35073f26 100644 --- a/packages/desktop-client/src/components/modals/RolloverBalanceMenuModal.tsx +++ b/packages/desktop-client/src/components/modals/RolloverBalanceMenuModal.tsx @@ -9,19 +9,20 @@ import { DefaultCarryoverIndicator, } from '../budget/BalanceWithCarryover'; import { BalanceMenu } from '../budget/rollover/BalanceMenu'; -import { Modal, ModalTitle } from '../common/Modal'; +import { + Modal, + ModalCloseButton, + ModalHeader, + ModalTitle, +} from '../common/Modal2'; import { Text } from '../common/Text'; import { View } from '../common/View'; -import { type CommonModalProps } from '../Modals'; type RolloverBalanceMenuModalProps = ComponentPropsWithoutRef< typeof BalanceMenu -> & { - modalProps: CommonModalProps; -}; +>; export function RolloverBalanceMenuModal({ - modalProps, categoryId, onCarryover, onTransfer, @@ -41,57 +42,60 @@ export function RolloverBalanceMenuModal({ } return ( - <Modal - title={<ModalTitle title={category.name} shrinkOnOverflow />} - showHeader - focusAfterClose={false} - {...modalProps} - > - <View - style={{ - justifyContent: 'center', - alignItems: 'center', - marginBottom: 20, - }} - > - <Text - style={{ - fontSize: 17, - fontWeight: 400, - }} - > - Balance - </Text> - <BalanceWithCarryover - disabled - style={{ - textAlign: 'center', - ...styles.veryLargeText, - }} - carryover={rolloverBudget.catCarryover(categoryId)} - balance={rolloverBudget.catBalance(categoryId)} - goal={rolloverBudget.catGoal(categoryId)} - budgeted={rolloverBudget.catBudgeted(categoryId)} - carryoverIndicator={({ style }) => - DefaultCarryoverIndicator({ - style: { - width: 15, - height: 15, - display: 'inline-flex', - position: 'relative', - ...style, - }, - }) - } - /> - </View> - <BalanceMenu - categoryId={categoryId} - getItemStyle={() => defaultMenuItemStyle} - onCarryover={onCarryover} - onTransfer={onTransfer} - onCover={onCover} - /> + <Modal name="rollover-balance-menu"> + {({ state: { close } }) => ( + <> + <ModalHeader + title={<ModalTitle title={category.name} shrinkOnOverflow />} + rightContent={<ModalCloseButton onClick={close} />} + /> + <View + style={{ + justifyContent: 'center', + alignItems: 'center', + marginBottom: 20, + }} + > + <Text + style={{ + fontSize: 17, + fontWeight: 400, + }} + > + Balance + </Text> + <BalanceWithCarryover + disabled + style={{ + textAlign: 'center', + ...styles.veryLargeText, + }} + carryover={rolloverBudget.catCarryover(categoryId)} + balance={rolloverBudget.catBalance(categoryId)} + goal={rolloverBudget.catGoal(categoryId)} + budgeted={rolloverBudget.catBudgeted(categoryId)} + carryoverIndicator={({ style }) => + DefaultCarryoverIndicator({ + style: { + width: 15, + height: 15, + display: 'inline-flex', + position: 'relative', + ...style, + }, + }) + } + /> + </View> + <BalanceMenu + categoryId={categoryId} + getItemStyle={() => defaultMenuItemStyle} + onCarryover={onCarryover} + onTransfer={onTransfer} + onCover={onCover} + /> + </> + )} </Modal> ); } diff --git a/packages/desktop-client/src/components/modals/RolloverBudgetMenuModal.tsx b/packages/desktop-client/src/components/modals/RolloverBudgetMenuModal.tsx index d54587acc..5bd9c2440 100644 --- a/packages/desktop-client/src/components/modals/RolloverBudgetMenuModal.tsx +++ b/packages/desktop-client/src/components/modals/RolloverBudgetMenuModal.tsx @@ -10,23 +10,25 @@ import { amountToInteger, integerToAmount } from 'loot-core/shared/util'; import { useCategory } from '../../hooks/useCategory'; import { type CSSProperties, theme, styles } from '../../style'; import { BudgetMenu } from '../budget/rollover/BudgetMenu'; -import { Modal, ModalTitle } from '../common/Modal'; +import { + Modal, + ModalCloseButton, + ModalHeader, + ModalTitle, +} from '../common/Modal2'; import { Text } from '../common/Text'; import { View } from '../common/View'; import { FocusableAmountInput } from '../mobile/transactions/FocusableAmountInput'; -import { type CommonModalProps } from '../Modals'; import { useSheetValue } from '../spreadsheet/useSheetValue'; type RolloverBudgetMenuModalProps = ComponentPropsWithoutRef< typeof BudgetMenu > & { - modalProps: CommonModalProps; categoryId: string; onUpdateBudget: (amount: number) => void; }; export function RolloverBudgetMenuModal({ - modalProps, categoryId, onUpdateBudget, onCopyLastMonthAverage, @@ -57,51 +59,54 @@ export function RolloverBudgetMenuModal({ } return ( - <Modal - title={<ModalTitle title={category.name} shrinkOnOverflow />} - showHeader - focusAfterClose={false} - {...modalProps} - > - <View - style={{ - justifyContent: 'center', - alignItems: 'center', - marginBottom: 20, - }} - > - <Text - style={{ - fontSize: 17, - fontWeight: 400, - }} - > - Budget - </Text> - <FocusableAmountInput - value={integerToAmount(budgeted || 0)} - focused={amountFocused} - onFocus={() => setAmountFocused(true)} - onBlur={() => setAmountFocused(false)} - onEnter={() => modalProps.onClose()} - zeroSign="+" - focusedStyle={{ - width: 'auto', - padding: '5px', - paddingLeft: '20px', - paddingRight: '20px', - minWidth: '100%', - }} - textStyle={{ ...styles.veryLargeText, textAlign: 'center' }} - onUpdateAmount={_onUpdateBudget} - /> - </View> - <BudgetMenu - getItemStyle={() => defaultMenuItemStyle} - onCopyLastMonthAverage={onCopyLastMonthAverage} - onSetMonthsAverage={onSetMonthsAverage} - onApplyBudgetTemplate={onApplyBudgetTemplate} - /> + <Modal name="rollover-budget-menu"> + {({ state: { close } }) => ( + <> + <ModalHeader + title={<ModalTitle title={category.name} shrinkOnOverflow />} + rightContent={<ModalCloseButton onClick={close} />} + /> + <View + style={{ + justifyContent: 'center', + alignItems: 'center', + marginBottom: 20, + }} + > + <Text + style={{ + fontSize: 17, + fontWeight: 400, + }} + > + Budget + </Text> + <FocusableAmountInput + value={integerToAmount(budgeted || 0)} + focused={amountFocused} + onFocus={() => setAmountFocused(true)} + onBlur={() => setAmountFocused(false)} + onEnter={close} + zeroSign="+" + focusedStyle={{ + width: 'auto', + padding: '5px', + paddingLeft: '20px', + paddingRight: '20px', + minWidth: '100%', + }} + textStyle={{ ...styles.veryLargeText, textAlign: 'center' }} + onUpdateAmount={_onUpdateBudget} + /> + </View> + <BudgetMenu + getItemStyle={() => defaultMenuItemStyle} + onCopyLastMonthAverage={onCopyLastMonthAverage} + onSetMonthsAverage={onSetMonthsAverage} + onApplyBudgetTemplate={onApplyBudgetTemplate} + /> + </> + )} </Modal> ); } diff --git a/packages/desktop-client/src/components/modals/RolloverBudgetMonthMenuModal.tsx b/packages/desktop-client/src/components/modals/RolloverBudgetMonthMenuModal.tsx index 707024225..a34353e5d 100644 --- a/packages/desktop-client/src/components/modals/RolloverBudgetMonthMenuModal.tsx +++ b/packages/desktop-client/src/components/modals/RolloverBudgetMonthMenuModal.tsx @@ -9,30 +9,23 @@ import { SvgNotesPaper } from '../../icons/v2'; import { type CSSProperties, styles, theme } from '../../style'; import { BudgetMonthMenu } from '../budget/rollover/budgetsummary/BudgetMonthMenu'; import { Button } from '../common/Button2'; -import { Modal, ModalTitle } from '../common/Modal'; +import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal2'; import { View } from '../common/View'; -import { type CommonModalProps } from '../Modals'; import { Notes } from '../Notes'; type RolloverBudgetMonthMenuModalProps = { - modalProps: CommonModalProps; month: string; onBudgetAction: (month: string, action: string, arg?: unknown) => void; onEditNotes: (month: string) => void; }; export function RolloverBudgetMonthMenuModal({ - modalProps, month, onBudgetAction, onEditNotes, }: RolloverBudgetMonthMenuModalProps) { const originalNotes = useNotes(`budget-${month}`); - const onClose = () => { - modalProps.onClose(); - }; - const _onEditNotes = () => { onEditNotes?.(month); }; @@ -60,124 +53,131 @@ export function RolloverBudgetMonthMenuModal({ return ( <Modal - title={<ModalTitle title={monthUtils.format(month, 'MMMM ‘yy')} />} - showHeader - focusAfterClose={false} - {...modalProps} - onClose={onClose} - style={{ - height: '50vh', + name="rollover-budget-month-menu" + containerProps={{ + style: { height: '50vh' }, }} > - <View - style={{ - flex: 1, - flexDirection: 'column', - }} - > - <View - style={{ - display: showMore ? 'none' : undefined, - overflowY: 'auto', - flex: 1, - }} - > - <Notes - notes={originalNotes?.length > 0 ? originalNotes : 'No notes'} - editable={false} - focused={false} - getStyle={() => ({ - borderRadius: 6, - ...((!originalNotes || originalNotes.length === 0) && { - justifySelf: 'center', - alignSelf: 'center', - color: theme.pageTextSubdued, - }), - })} + {({ state: { close } }) => ( + <> + <ModalHeader + title={monthUtils.format(month, 'MMMM ‘yy')} + rightContent={<ModalCloseButton onClick={close} />} /> - </View> - <View style={{ paddingTop: 10, gap: 5 }}> <View style={{ - display: showMore ? 'none' : undefined, - flexDirection: 'row', - flexWrap: 'wrap', - justifyContent: 'space-between', - alignContent: 'space-between', + flex: 1, + flexDirection: 'column', }} > - <Button style={buttonStyle} onPress={_onEditNotes}> - <SvgNotesPaper - width={20} - height={20} - style={{ paddingRight: 5 }} - /> - Edit notes - </Button> - </View> - <View> - <Button - variant="bare" - style={({ isPressed, isHovered }) => ({ - ...buttonStyle, - ...(isPressed || isHovered - ? { backgroundColor: 'transparent', color: buttonStyle.color } - : {}), - })} - onPress={onShowMore} + <View + style={{ + display: showMore ? 'none' : undefined, + overflowY: 'auto', + flex: 1, + }} > - {!showMore ? ( - <SvgCheveronUp - width={30} - height={30} - style={{ paddingRight: 5 }} - /> - ) : ( - <SvgCheveronDown - width={30} - height={30} - style={{ paddingRight: 5 }} - /> - )} - Actions - </Button> + <Notes + notes={originalNotes?.length > 0 ? originalNotes : 'No notes'} + editable={false} + focused={false} + getStyle={() => ({ + borderRadius: 6, + ...((!originalNotes || originalNotes.length === 0) && { + justifySelf: 'center', + alignSelf: 'center', + color: theme.pageTextSubdued, + }), + })} + /> + </View> + <View style={{ paddingTop: 10, gap: 5 }}> + <View + style={{ + display: showMore ? 'none' : undefined, + flexDirection: 'row', + flexWrap: 'wrap', + justifyContent: 'space-between', + alignContent: 'space-between', + }} + > + <Button style={buttonStyle} onPress={_onEditNotes}> + <SvgNotesPaper + width={20} + height={20} + style={{ paddingRight: 5 }} + /> + Edit notes + </Button> + </View> + <View> + <Button + variant="bare" + style={({ isPressed, isHovered }) => ({ + ...buttonStyle, + ...(isPressed || isHovered + ? { + backgroundColor: 'transparent', + color: buttonStyle.color, + } + : {}), + })} + onPress={onShowMore} + > + {!showMore ? ( + <SvgCheveronUp + width={30} + height={30} + style={{ paddingRight: 5 }} + /> + ) : ( + <SvgCheveronDown + width={30} + height={30} + style={{ paddingRight: 5 }} + /> + )} + Actions + </Button> + </View> + </View> + {showMore && ( + <BudgetMonthMenu + style={{ overflowY: 'auto', paddingTop: 10 }} + getItemStyle={() => defaultMenuItemStyle} + onCopyLastMonthBudget={() => { + onBudgetAction(month, 'copy-last'); + close(); + }} + onSetBudgetsToZero={() => { + onBudgetAction(month, 'set-zero'); + close(); + }} + onSetMonthsAverage={numberOfMonths => { + onBudgetAction(month, `set-${numberOfMonths}-avg`); + close(); + }} + onCheckTemplates={() => { + onBudgetAction(month, 'check-templates'); + close(); + }} + onApplyBudgetTemplates={() => { + onBudgetAction(month, 'apply-goal-template'); + close(); + }} + onOverwriteWithBudgetTemplates={() => { + onBudgetAction(month, 'overwrite-goal-template'); + close(); + }} + onEndOfMonthCleanup={() => { + onBudgetAction(month, 'cleanup-goal-template'); + close(); + }} + /> + )} </View> - </View> - {showMore && ( - <BudgetMonthMenu - style={{ overflowY: 'auto', paddingTop: 10 }} - getItemStyle={() => defaultMenuItemStyle} - onCopyLastMonthBudget={() => { - onBudgetAction(month, 'copy-last'); - onClose(); - }} - onSetBudgetsToZero={() => { - onBudgetAction(month, 'set-zero'); - onClose(); - }} - onSetMonthsAverage={numberOfMonths => { - onBudgetAction(month, `set-${numberOfMonths}-avg`); - onClose(); - }} - onCheckTemplates={() => { - onBudgetAction(month, 'check-templates'); - onClose(); - }} - onApplyBudgetTemplates={() => { - onBudgetAction(month, 'apply-goal-template'); - onClose(); - }} - onOverwriteWithBudgetTemplates={() => { - onBudgetAction(month, 'overwrite-goal-template'); - onClose(); - }} - onEndOfMonthCleanup={() => { - onBudgetAction(month, 'cleanup-goal-template'); - onClose(); - }} - /> - )} - </View> + </> + )} </Modal> ); } diff --git a/packages/desktop-client/src/components/modals/RolloverBudgetSummaryModal.tsx b/packages/desktop-client/src/components/modals/RolloverBudgetSummaryModal.tsx index a2bba5ee9..cbde09f1b 100644 --- a/packages/desktop-client/src/components/modals/RolloverBudgetSummaryModal.tsx +++ b/packages/desktop-client/src/components/modals/RolloverBudgetSummaryModal.tsx @@ -8,13 +8,11 @@ import { format, sheetForMonth, prevMonth } from 'loot-core/src/shared/months'; import { styles } from '../../style'; import { ToBudgetAmount } from '../budget/rollover/budgetsummary/ToBudgetAmount'; import { TotalsList } from '../budget/rollover/budgetsummary/TotalsList'; -import { Modal } from '../common/Modal'; -import { type CommonModalProps } from '../Modals'; +import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal2'; import { NamespaceContext } from '../spreadsheet/NamespaceContext'; import { useSheetValue } from '../spreadsheet/useSheetValue'; type RolloverBudgetSummaryModalProps = { - modalProps: CommonModalProps; onBudgetAction: (month: string, action: string, arg?: unknown) => void; month: string; }; @@ -22,7 +20,6 @@ type RolloverBudgetSummaryModalProps = { export function RolloverBudgetSummaryModal({ month, onBudgetAction, - modalProps, }: RolloverBudgetSummaryModalProps) { const dispatch = useDispatch(); const prevMonthName = format(prevMonth(month), 'MMM'); @@ -79,42 +76,52 @@ export function RolloverBudgetSummaryModal({ const onResetHoldBuffer = () => { onBudgetAction(month, 'reset-hold'); - modalProps.onClose(); }; - const onClick = () => { + const onClick = ({ close }: { close: () => void }) => { dispatch( pushModal('rollover-summary-to-budget-menu', { month, onTransfer: openTransferAvailableModal, onCover: openCoverOverbudgetedModal, - onResetHoldBuffer, + onResetHoldBuffer: () => { + onResetHoldBuffer(); + close(); + }, onHoldBuffer, }), ); }; return ( - <Modal title="Budget Summary" {...modalProps}> - <NamespaceContext.Provider value={sheetForMonth(month)}> - <TotalsList - prevMonthName={prevMonthName} - style={{ - ...styles.mediumText, - }} - /> - <ToBudgetAmount - prevMonthName={prevMonthName} - style={{ - ...styles.mediumText, - marginTop: 15, - }} - amountStyle={{ - ...styles.underlinedText, - }} - onClick={onClick} - /> - </NamespaceContext.Provider> + <Modal name="rollover-budget-summary"> + {({ state: { close } }) => ( + <> + <ModalHeader + title="Budget Summary" + rightContent={<ModalCloseButton onClick={close} />} + /> + <NamespaceContext.Provider value={sheetForMonth(month)}> + <TotalsList + prevMonthName={prevMonthName} + style={{ + ...styles.mediumText, + }} + /> + <ToBudgetAmount + prevMonthName={prevMonthName} + style={{ + ...styles.mediumText, + marginTop: 15, + }} + amountStyle={{ + ...styles.underlinedText, + }} + onClick={() => onClick({ close })} + /> + </NamespaceContext.Provider> + </> + )} </Modal> ); } diff --git a/packages/desktop-client/src/components/modals/RolloverToBudgetMenuModal.tsx b/packages/desktop-client/src/components/modals/RolloverToBudgetMenuModal.tsx index c94e247be..d21d5dbc2 100644 --- a/packages/desktop-client/src/components/modals/RolloverToBudgetMenuModal.tsx +++ b/packages/desktop-client/src/components/modals/RolloverToBudgetMenuModal.tsx @@ -2,17 +2,13 @@ import React, { type ComponentPropsWithoutRef } from 'react'; import { type CSSProperties, theme, styles } from '../../style'; import { ToBudgetMenu } from '../budget/rollover/budgetsummary/ToBudgetMenu'; -import { Modal } from '../common/Modal'; -import { type CommonModalProps } from '../Modals'; +import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal2'; type RolloverToBudgetMenuModalProps = ComponentPropsWithoutRef< typeof ToBudgetMenu -> & { - modalProps: CommonModalProps; -}; +>; export function RolloverToBudgetMenuModal({ - modalProps, onTransfer, onCover, onHoldBuffer, @@ -26,14 +22,22 @@ export function RolloverToBudgetMenuModal({ }; return ( - <Modal showHeader focusAfterClose={false} {...modalProps}> - <ToBudgetMenu - getItemStyle={() => defaultMenuItemStyle} - onTransfer={onTransfer} - onCover={onCover} - onHoldBuffer={onHoldBuffer} - onResetHoldBuffer={onResetHoldBuffer} - /> + <Modal name="rollover-summary-to-budget-menu"> + {({ state: { close } }) => ( + <> + <ModalHeader + showLogo + rightContent={<ModalCloseButton onClick={close} />} + /> + <ToBudgetMenu + getItemStyle={() => defaultMenuItemStyle} + onTransfer={onTransfer} + onCover={onCover} + onHoldBuffer={onHoldBuffer} + onResetHoldBuffer={onResetHoldBuffer} + /> + </> + )} </Modal> ); } diff --git a/packages/desktop-client/src/components/modals/ScheduledTransactionMenuModal.tsx b/packages/desktop-client/src/components/modals/ScheduledTransactionMenuModal.tsx index 46c31083f..0436554a7 100644 --- a/packages/desktop-client/src/components/modals/ScheduledTransactionMenuModal.tsx +++ b/packages/desktop-client/src/components/modals/ScheduledTransactionMenuModal.tsx @@ -6,17 +6,18 @@ import { type Query } from 'loot-core/shared/query'; import { type CSSProperties, theme, styles } from '../../style'; import { Menu } from '../common/Menu'; -import { Modal, ModalTitle } from '../common/Modal'; +import { + Modal, + ModalCloseButton, + ModalHeader, + ModalTitle, +} from '../common/Modal2'; import { Text } from '../common/Text'; import { View } from '../common/View'; -import { type CommonModalProps } from '../Modals'; -type ScheduledTransactionMenuModalProps = ScheduledTransactionMenuProps & { - modalProps: CommonModalProps; -}; +type ScheduledTransactionMenuModalProps = ScheduledTransactionMenuProps; export function ScheduledTransactionMenuModal({ - modalProps, transactionId, onSkip, onPost, @@ -41,30 +42,35 @@ export function ScheduledTransactionMenuModal({ } return ( - <Modal - title={<ModalTitle title={schedule.name || ''} shrinkOnOverflow />} - showHeader - focusAfterClose={false} - {...modalProps} - > - <View - style={{ - justifyContent: 'center', - alignItems: 'center', - marginBottom: 20, - }} - > - <Text style={{ fontSize: 17, fontWeight: 400 }}>Scheduled date</Text> - <Text style={{ fontSize: 17, fontWeight: 700 }}> - {format(schedule.next_date, 'MMMM dd, yyyy')} - </Text> - </View> - <ScheduledTransactionMenu - transactionId={transactionId} - onPost={onPost} - onSkip={onSkip} - getItemStyle={() => defaultMenuItemStyle} - /> + <Modal name="scheduled-transaction-menu"> + {({ state: { close } }) => ( + <> + <ModalHeader + title={<ModalTitle title={schedule.name || ''} shrinkOnOverflow />} + rightContent={<ModalCloseButton onClick={close} />} + /> + <View + style={{ + justifyContent: 'center', + alignItems: 'center', + marginBottom: 20, + }} + > + <Text style={{ fontSize: 17, fontWeight: 400 }}> + Scheduled date + </Text> + <Text style={{ fontSize: 17, fontWeight: 700 }}> + {format(schedule.next_date, 'MMMM dd, yyyy')} + </Text> + </View> + <ScheduledTransactionMenu + transactionId={transactionId} + onPost={onPost} + onSkip={onSkip} + getItemStyle={() => defaultMenuItemStyle} + /> + </> + )} </Modal> ); } diff --git a/packages/desktop-client/src/components/modals/SelectLinkedAccounts.jsx b/packages/desktop-client/src/components/modals/SelectLinkedAccounts.jsx index 4dcc8696c..f246eaaa3 100644 --- a/packages/desktop-client/src/components/modals/SelectLinkedAccounts.jsx +++ b/packages/desktop-client/src/components/modals/SelectLinkedAccounts.jsx @@ -1,10 +1,17 @@ import React, { useState } from 'react'; +import { useDispatch } from 'react-redux'; + +import { + linkAccount, + linkAccountSimpleFin, + unlinkAccount, +} from 'loot-core/client/actions'; import { useAccounts } from '../../hooks/useAccounts'; import { theme } from '../../style'; import { Autocomplete } from '../autocomplete/Autocomplete'; import { Button } from '../common/Button2'; -import { Modal } from '../common/Modal'; +import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal2'; import { Text } from '../common/Text'; import { View } from '../common/View'; import { PrivacyFilter } from '../PrivacyFilter'; @@ -17,13 +24,12 @@ const addOffBudgetAccountOption = { }; export function SelectLinkedAccounts({ - modalProps, requisitionId, externalAccounts, - actions, syncSource, }) { externalAccounts.sort((a, b) => a.name.localeCompare(b.name)); + const dispatch = useDispatch(); const localAccounts = useAccounts().filter(a => a.closed === 0); const [chosenAccounts, setChosenAccounts] = useState(() => { return Object.fromEntries( @@ -41,7 +47,7 @@ export function SelectLinkedAccounts({ localAccounts .filter(acc => acc.account_id) .filter(acc => !chosenLocalAccountIds.includes(acc.id)) - .forEach(acc => actions.unlinkAccount(acc.id)); + .forEach(acc => dispatch(unlinkAccount(acc.id))); // Link new accounts Object.entries(chosenAccounts).forEach( @@ -59,29 +65,31 @@ export function SelectLinkedAccounts({ // Finally link the matched account if (syncSource === 'simpleFin') { - actions.linkAccountSimpleFin( - externalAccount, - chosenLocalAccountId !== addOnBudgetAccountOption.id && - chosenLocalAccountId !== addOffBudgetAccountOption.id - ? chosenLocalAccountId - : undefined, - offBudget, + dispatch( + linkAccountSimpleFin( + externalAccount, + chosenLocalAccountId !== addOnBudgetAccountOption.id && + chosenLocalAccountId !== addOffBudgetAccountOption.id + ? chosenLocalAccountId + : undefined, + offBudget, + ), ); } else { - actions.linkAccount( - requisitionId, - externalAccount, - chosenLocalAccountId !== addOnBudgetAccountOption.id && - chosenLocalAccountId !== addOffBudgetAccountOption.id - ? chosenLocalAccountId - : undefined, - offBudget, + dispatch( + linkAccount( + requisitionId, + externalAccount, + chosenLocalAccountId !== addOnBudgetAccountOption.id && + chosenLocalAccountId !== addOffBudgetAccountOption.id + ? chosenLocalAccountId + : undefined, + offBudget, + ), ); } }, ); - - actions.closeModal(); } const unlinkedAccounts = localAccounts.filter( @@ -103,9 +111,16 @@ export function SelectLinkedAccounts({ } return ( - <Modal title="Link Accounts" {...modalProps} style={{ width: 800 }}> - {() => ( + <Modal + name="select-linked-accounts" + containerProps={{ style: { width: 800 } }} + > + {({ state: { close } }) => ( <> + <ModalHeader + title="Link Accounts" + rightContent={<ModalCloseButton onClick={close} />} + /> <Text style={{ marginBottom: 10 }}> We found the following accounts. Select which ones you want to add: </Text> @@ -161,7 +176,10 @@ export function SelectLinkedAccounts({ > <Button variant="primary" - onPress={onNext} + onPress={() => { + onNext(); + close(); + }} isDisabled={!Object.keys(chosenAccounts).length} > Link accounts diff --git a/packages/desktop-client/src/components/modals/SimpleFinInitialise.tsx b/packages/desktop-client/src/components/modals/SimpleFinInitialise.tsx index 37d0a2d78..1392d38cc 100644 --- a/packages/desktop-client/src/components/modals/SimpleFinInitialise.tsx +++ b/packages/desktop-client/src/components/modals/SimpleFinInitialise.tsx @@ -7,19 +7,21 @@ import { Error } from '../alerts'; import { ButtonWithLoading } from '../common/Button2'; import { Input } from '../common/Input'; import { Link } from '../common/Link'; -import { Modal, ModalButtons } from '../common/Modal'; -import type { ModalProps } from '../common/Modal'; +import { + Modal, + ModalButtons, + ModalCloseButton, + ModalHeader, +} from '../common/Modal2'; import { Text } from '../common/Text'; import { View } from '../common/View'; import { FormField, FormLabel } from '../forms'; type SimpleFinInitialiseProps = { - modalProps?: Partial<ModalProps>; onSuccess: () => void; }; export const SimpleFinInitialise = ({ - modalProps, onSuccess, }: SimpleFinInitialiseProps) => { const [token, setToken] = useState(''); @@ -40,52 +42,62 @@ export const SimpleFinInitialise = ({ }); onSuccess(); - modalProps.onClose(); setIsLoading(false); }; return ( - <Modal title="Set-up SimpleFIN" size={{ width: 300 }} {...modalProps}> - <View style={{ display: 'flex', gap: 10 }}> - <Text> - In order to enable bank-sync via SimpleFIN (only for North American - banks) you will need to create a token. This can be done by creating - an account with{' '} - <Link - variant="external" - to="https://beta-bridge.simplefin.org/" - linkColor="purple" - > - SimpleFIN - </Link> - . - </Text> - - <FormField> - <FormLabel title="Token:" htmlFor="token-field" /> - <Input - id="token-field" - type="password" - value={token} - onChangeValue={value => { - setToken(value); - setIsValid(true); - }} + <Modal name="simplefin-init" containerProps={{ style: { width: 300 } }}> + {({ state: { close } }) => ( + <> + <ModalHeader + title="Set-up SimpleFIN" + rightContent={<ModalCloseButton onClick={close} />} /> - </FormField> + <View style={{ display: 'flex', gap: 10 }}> + <Text> + In order to enable bank-sync via SimpleFIN (only for North + American banks) you will need to create a token. This can be done + by creating an account with{' '} + <Link + variant="external" + to="https://beta-bridge.simplefin.org/" + linkColor="purple" + > + SimpleFIN + </Link> + . + </Text> + + <FormField> + <FormLabel title="Token:" htmlFor="token-field" /> + <Input + id="token-field" + type="password" + value={token} + onChangeValue={value => { + setToken(value); + setIsValid(true); + }} + /> + </FormField> - {!isValid && <Error>It is required to provide a token.</Error>} - </View> + {!isValid && <Error>It is required to provide a token.</Error>} + </View> - <ModalButtons> - <ButtonWithLoading - variant="primary" - isLoading={isLoading} - onPress={onSubmit} - > - Save and continue - </ButtonWithLoading> - </ModalButtons> + <ModalButtons> + <ButtonWithLoading + variant="primary" + isLoading={isLoading} + onPress={() => { + onSubmit(); + close(); + }} + > + Save and continue + </ButtonWithLoading> + </ModalButtons> + </> + )} </Modal> ); }; diff --git a/packages/desktop-client/src/components/modals/SingleInputModal.tsx b/packages/desktop-client/src/components/modals/SingleInputModal.tsx index 8ba91fcb7..162a53f18 100644 --- a/packages/desktop-client/src/components/modals/SingleInputModal.tsx +++ b/packages/desktop-client/src/components/modals/SingleInputModal.tsx @@ -1,19 +1,23 @@ // @ts-strict-ignore -import React, { type ComponentProps, useState, type FormEvent } from 'react'; +import React, { + useState, + type ComponentType, + type ComponentPropsWithoutRef, + type FormEvent, +} from 'react'; import { Form } from 'react-aria-components'; import { styles } from '../../style'; import { Button } from '../common/Button2'; import { FormError } from '../common/FormError'; import { InitialFocus } from '../common/InitialFocus'; -import { Modal } from '../common/Modal'; +import { Modal, ModalCloseButton, type ModalHeader } from '../common/Modal2'; import { View } from '../common/View'; import { InputField } from '../mobile/MobileForms'; -import { type CommonModalProps } from '../Modals'; type SingleInputModalProps = { - modalProps: Partial<CommonModalProps>; - title: ComponentProps<typeof Modal>['title']; + name: string; + Header: ComponentType<ComponentPropsWithoutRef<typeof ModalHeader>>; buttonText: string; onSubmit: (value: string) => void; onValidate?: (value: string) => string[]; @@ -21,8 +25,8 @@ type SingleInputModalProps = { }; export function SingleInputModal({ - modalProps, - title, + name, + Header, buttonText, onSubmit, onValidate, @@ -41,52 +45,61 @@ export function SingleInputModal({ } onSubmit?.(value); - modalProps.onClose(); }; return ( - <Modal title={title} {...modalProps}> - <Form onSubmit={_onSubmit}> - <View> - <InitialFocus> - <InputField - placeholder={inputPlaceholder} - defaultValue={value} - onChangeValue={setValue} - /> - </InitialFocus> - {errorMessage && ( - <FormError + <Modal name={name}> + {({ state: { close } }) => ( + <> + <Header rightContent={<ModalCloseButton onClick={close} />} /> + <Form + onSubmit={e => { + _onSubmit(e); + close(); + }} + > + <View> + <InitialFocus> + <InputField + placeholder={inputPlaceholder} + defaultValue={value} + onChangeValue={setValue} + /> + </InitialFocus> + {errorMessage && ( + <FormError + style={{ + paddingTop: 5, + marginLeft: styles.mobileEditingPadding, + marginRight: styles.mobileEditingPadding, + }} + > + * {errorMessage} + </FormError> + )} + </View> + <View style={{ - paddingTop: 5, - marginLeft: styles.mobileEditingPadding, - marginRight: styles.mobileEditingPadding, + justifyContent: 'center', + alignItems: 'center', + paddingTop: 10, }} > - * {errorMessage} - </FormError> - )} - </View> - <View - style={{ - justifyContent: 'center', - alignItems: 'center', - paddingTop: 10, - }} - > - <Button - type="submit" - variant="primary" - style={{ - height: styles.mobileMinHeight, - marginLeft: styles.mobileEditingPadding, - marginRight: styles.mobileEditingPadding, - }} - > - {buttonText} - </Button> - </View> - </Form> + <Button + type="submit" + variant="primary" + style={{ + height: styles.mobileMinHeight, + marginLeft: styles.mobileEditingPadding, + marginRight: styles.mobileEditingPadding, + }} + > + {buttonText} + </Button> + </View> + </Form> + </> + )} </Modal> ); } diff --git a/packages/desktop-client/src/components/modals/TransferModal.tsx b/packages/desktop-client/src/components/modals/TransferModal.tsx index 0361c0685..31b58ebd5 100644 --- a/packages/desktop-client/src/components/modals/TransferModal.tsx +++ b/packages/desktop-client/src/components/modals/TransferModal.tsx @@ -8,14 +8,12 @@ import { styles } from '../../style'; import { addToBeBudgetedGroup } from '../budget/util'; import { Button } from '../common/Button2'; import { InitialFocus } from '../common/InitialFocus'; -import { Modal } from '../common/Modal'; +import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal2'; import { View } from '../common/View'; import { FieldLabel, TapField } from '../mobile/MobileForms'; -import { type CommonModalProps } from '../Modals'; import { AmountInput } from '../util/AmountInput'; type TransferModalProps = { - modalProps: CommonModalProps; title: string; month: string; amount: number; @@ -24,7 +22,6 @@ type TransferModalProps = { }; export function TransferModal({ - modalProps, title, month, amount: initialAmount, @@ -62,65 +59,74 @@ export function TransferModal({ if (newAmount && categoryId) { onSubmit?.(newAmount, categoryId); } - - modalProps.onClose(); }; const toCategory = categories.find(c => c.id === toCategoryId); return ( - <Modal title={title} showHeader focusAfterClose={false} {...modalProps}> - <View> - <View> - <FieldLabel title="Transfer this amount:" /> - <InitialFocus> - <AmountInput - value={initialAmount} - autoDecimals={true} - style={{ - marginLeft: styles.mobileEditingPadding, - marginRight: styles.mobileEditingPadding, - }} - inputStyle={{ - height: styles.mobileMinHeight, - }} - onUpdate={setAmount} - onEnter={() => { - if (!toCategoryId) { - openCategoryModal(); - } - }} - /> - </InitialFocus> - </View> + <Modal name="transfer"> + {({ state: { close } }) => ( + <> + <ModalHeader + title={title} + rightContent={<ModalCloseButton onClick={close} />} + /> + <View> + <View> + <FieldLabel title="Transfer this amount:" /> + <InitialFocus> + <AmountInput + value={initialAmount} + autoDecimals={true} + style={{ + marginLeft: styles.mobileEditingPadding, + marginRight: styles.mobileEditingPadding, + }} + inputStyle={{ + height: styles.mobileMinHeight, + }} + onUpdate={setAmount} + onEnter={() => { + if (!toCategoryId) { + openCategoryModal(); + } + }} + /> + </InitialFocus> + </View> - <FieldLabel title="To:" /> - <TapField - tabIndex={0} - value={toCategory?.name} - onClick={openCategoryModal} - /> + <FieldLabel title="To:" /> + <TapField + tabIndex={0} + value={toCategory?.name} + onClick={openCategoryModal} + /> - <View - style={{ - justifyContent: 'center', - alignItems: 'center', - paddingTop: 10, - }} - > - <Button - variant="primary" - style={{ - height: styles.mobileMinHeight, - marginLeft: styles.mobileEditingPadding, - marginRight: styles.mobileEditingPadding, - }} - onPress={() => _onSubmit(amount, toCategoryId)} - > - Transfer - </Button> - </View> - </View> + <View + style={{ + justifyContent: 'center', + alignItems: 'center', + paddingTop: 10, + }} + > + <Button + variant="primary" + style={{ + height: styles.mobileMinHeight, + marginLeft: styles.mobileEditingPadding, + marginRight: styles.mobileEditingPadding, + }} + onPress={() => { + _onSubmit(amount, toCategoryId); + close(); + }} + > + Transfer + </Button> + </View> + </View> + </> + )} </Modal> ); } diff --git a/packages/desktop-client/src/components/schedules/DiscoverSchedules.tsx b/packages/desktop-client/src/components/schedules/DiscoverSchedules.tsx index e3238f741..745d2d6d5 100644 --- a/packages/desktop-client/src/components/schedules/DiscoverSchedules.tsx +++ b/packages/desktop-client/src/components/schedules/DiscoverSchedules.tsx @@ -7,7 +7,6 @@ import { q } from 'loot-core/src/shared/query'; import { getRecurringDescription } from 'loot-core/src/shared/schedules'; import type { DiscoverScheduleEntity } from 'loot-core/src/types/models'; -import type { BoundActions } from '../../hooks/useActions'; import { useDateFormat } from '../../hooks/useDateFormat'; import { useSelected, @@ -18,11 +17,10 @@ import { import { useSendPlatformRequest } from '../../hooks/useSendPlatformRequest'; import { theme } from '../../style'; import { ButtonWithLoading } from '../common/Button2'; -import { Modal } from '../common/Modal'; +import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal2'; import { Paragraph } from '../common/Paragraph'; import { Stack } from '../common/Stack'; import { View } from '../common/View'; -import { type CommonModalProps } from '../Modals'; import { Table, TableHeader, Row, Field, SelectCell } from '../table'; import { DisplayId } from '../util/DisplayId'; @@ -124,13 +122,7 @@ function DiscoverSchedulesTable({ ); } -export function DiscoverSchedules({ - modalProps, - actions, -}: { - modalProps: CommonModalProps; - actions: BoundActions; -}) { +export function DiscoverSchedules() { const { data, isLoading } = useSendPlatformRequest('schedule/discover'); const schedules = data || []; @@ -173,47 +165,57 @@ export function DiscoverSchedules({ } setCreating(false); - actions.popModal(); } return ( <Modal - title="Found schedules" - size={{ width: 850, height: 650 }} - {...modalProps} + name="schedules-discover" + containerProps={{ style: { width: 850, height: 650 } }} > - <Paragraph> - We found some possible schedules in your current transactions. Select - the ones you want to create. - </Paragraph> - <Paragraph> - If you expected a schedule here and don’t see it, it might be because - the payees of the transactions don’t match. Make sure you rename payees - on all transactions for a schedule to be the same payee. - </Paragraph> - - <SelectedProvider instance={selectedInst}> - <DiscoverSchedulesTable loading={isLoading} schedules={schedules} /> - </SelectedProvider> - - <Stack - direction="row" - align="center" - justify="flex-end" - style={{ - paddingTop: 20, - paddingBottom: 0, - }} - > - <ButtonWithLoading - variant="primary" - isLoading={creating} - isDisabled={selectedInst.items.size === 0} - onPress={onCreate} - > - Create schedules - </ButtonWithLoading> - </Stack> + {({ state: { close } }) => ( + <> + <ModalHeader + title="Found Schedules" + rightContent={<ModalCloseButton onClick={close} />} + /> + <Paragraph> + We found some possible schedules in your current transactions. + Select the ones you want to create. + </Paragraph> + <Paragraph> + If you expected a schedule here and don’t see it, it might be + because the payees of the transactions don’t match. Make sure you + rename payees on all transactions for a schedule to be the same + payee. + </Paragraph> + + <SelectedProvider instance={selectedInst}> + <DiscoverSchedulesTable loading={isLoading} schedules={schedules} /> + </SelectedProvider> + + <Stack + direction="row" + align="center" + justify="flex-end" + style={{ + paddingTop: 20, + paddingBottom: 0, + }} + > + <ButtonWithLoading + variant="primary" + isLoading={creating} + isDisabled={selectedInst.items.size === 0} + onPress={() => { + onCreate(); + close(); + }} + > + Create schedules + </ButtonWithLoading> + </Stack> + </> + )} </Modal> ); } diff --git a/packages/desktop-client/src/components/schedules/PostsOfflineNotification.jsx b/packages/desktop-client/src/components/schedules/PostsOfflineNotification.jsx index 429c5119d..b19c0dc9e 100644 --- a/packages/desktop-client/src/components/schedules/PostsOfflineNotification.jsx +++ b/packages/desktop-client/src/components/schedules/PostsOfflineNotification.jsx @@ -1,78 +1,96 @@ import React from 'react'; +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 { theme } from '../../style'; import { Button } from '../common/Button2'; -import { Modal } from '../common/Modal'; +import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal2'; import { Paragraph } from '../common/Paragraph'; import { Stack } from '../common/Stack'; import { Text } from '../common/Text'; import { DisplayId } from '../util/DisplayId'; -export function PostsOfflineNotification({ modalProps, actions }) { +export function PostsOfflineNotification() { const location = useLocation(); + const dispatch = useDispatch(); const payees = (location.state && location.state.payees) || []; const plural = payees.length > 1; async function onPost() { await send('schedule/force-run-service'); - actions.popModal(); + dispatch(popModal()); } return ( - <Modal title="Post transactions?" size="small" {...modalProps}> - <Paragraph> - {payees.length > 0 ? ( - <Text> - The {plural ? 'payees ' : 'payee '} - {payees.map((id, idx) => ( - <Text key={id}> - <Text style={{ color: theme.pageTextPositive }}> - <DisplayId id={id} type="payees" /> - </Text> - {idx === payees.length - 1 - ? ' ' - : idx === payees.length - 2 - ? ', and ' - : ', '} + <Modal name="schedule-posts-offline-notification"> + {({ state: { close } }) => ( + <> + <ModalHeader + title="Post transactions?" + rightContent={<ModalCloseButton onClick={close} />} + /> + <Paragraph> + {payees.length > 0 ? ( + <Text> + The {plural ? 'payees ' : 'payee '} + {payees.map((id, idx) => ( + <Text key={id}> + <Text style={{ color: theme.pageTextPositive }}> + <DisplayId id={id} type="payees" /> + </Text> + {idx === payees.length - 1 + ? ' ' + : idx === payees.length - 2 + ? ', and ' + : ', '} + </Text> + ))} </Text> - ))} - </Text> - ) : ( - <Text>There {plural ? 'are payees ' : 'is a payee '} that </Text> - )} + ) : ( + <Text>There {plural ? 'are payees ' : 'is a payee '} that </Text> + )} - <Text> - {plural ? 'have ' : 'has '} schedules that are due today. Usually we - automatically post transactions for these, but you are offline or - syncing failed. In order to avoid duplicate transactions, we let you - choose whether or not to create transactions for these schedules. - </Text> - </Paragraph> - <Paragraph> - Be aware that other devices may have already created these transactions. - If you have multiple devices, make sure you only do this on one device - or you will have duplicate transactions. - </Paragraph> - <Paragraph> - You can always manually post a transaction later for a due schedule by - selecting the schedule and clicking “Post transaction†in the action - menu. - </Paragraph> - <Stack - direction="row" - justify="flex-end" - style={{ marginTop: 20 }} - spacing={2} - > - <Button onPress={actions.popModal}>Decide later</Button> - <Button variant="primary" onPress={onPost}> - Post transactions - </Button> - </Stack> + <Text> + {plural ? 'have ' : 'has '} schedules that are due today. Usually + we automatically post transactions for these, but you are offline + or syncing failed. In order to avoid duplicate transactions, we + let you choose whether or not to create transactions for these + schedules. + </Text> + </Paragraph> + <Paragraph> + Be aware that other devices may have already created these + transactions. If you have multiple devices, make sure you only do + this on one device or you will have duplicate transactions. + </Paragraph> + <Paragraph> + You can always manually post a transaction later for a due schedule + by selecting the schedule and clicking “Post transaction†in the + action menu. + </Paragraph> + <Stack + direction="row" + justify="flex-end" + style={{ marginTop: 20 }} + spacing={2} + > + <Button onPress={close}>Decide later</Button> + <Button + variant="primary" + onPress={() => { + onPost(); + close(); + }} + > + Post transactions + </Button> + </Stack> + </> + )} </Modal> ); } diff --git a/packages/desktop-client/src/components/schedules/ScheduleDetails.jsx b/packages/desktop-client/src/components/schedules/ScheduleDetails.jsx index 12d4b9ca4..5c89c4fc2 100644 --- a/packages/desktop-client/src/components/schedules/ScheduleDetails.jsx +++ b/packages/desktop-client/src/components/schedules/ScheduleDetails.jsx @@ -16,7 +16,7 @@ import { theme } from '../../style'; import { AccountAutocomplete } from '../autocomplete/AccountAutocomplete'; import { PayeeAutocomplete } from '../autocomplete/PayeeAutocomplete'; import { Button } from '../common/Button2'; -import { Modal } from '../common/Modal'; +import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal2'; import { Stack } from '../common/Stack'; import { Text } from '../common/Text'; import { View } from '../common/View'; @@ -69,7 +69,7 @@ function updateScheduleConditions(schedule, fields) { }; } -export function ScheduleDetails({ modalProps, actions, id, transaction }) { +export function ScheduleDetails({ id, transaction }) { const adding = id == null; const fromTrans = transaction != null; const payees = getPayeesById(usePayees()); @@ -400,7 +400,6 @@ export function ScheduleDetails({ modalProps, actions, id, transaction }) { if (adding) { await onLinkTransactions([...selectedInst.items], res.data); } - actions.popModal(); } } @@ -448,342 +447,356 @@ export function ScheduleDetails({ modalProps, actions, id, transaction }) { // This is derived from the date const repeats = state.fields.date ? !!state.fields.date.frequency : false; return ( - <Modal - title={payee ? `Schedule: ${payee.name}` : 'Schedule'} - size="medium" - {...modalProps} - > - <Stack direction="row" style={{ marginTop: 10 }}> - <FormField style={{ flex: 1 }}> - <FormLabel title="Schedule Name" htmlFor="name-field" /> - <GenericInput - field="string" - type="string" - value={state.fields.name} - multi={false} - onChange={e => { - dispatch({ type: 'set-field', field: 'name', value: e }); - }} - /> - </FormField> - </Stack> - <Stack direction="row" style={{ marginTop: 20 }}> - <FormField style={{ flex: 1 }}> - <FormLabel title="Payee" id="payee-label" htmlFor="payee-field" /> - <PayeeAutocomplete - value={state.fields.payee} - labelProps={{ id: 'payee-label' }} - inputProps={{ id: 'payee-field', placeholder: '(none)' }} - onSelect={id => - dispatch({ type: 'set-field', field: 'payee', value: id }) - } - /> - </FormField> - - <FormField style={{ flex: 1 }}> - <FormLabel - title="Account" - id="account-label" - htmlFor="account-field" + <Modal name="schedule-edit"> + {({ state: { close } }) => ( + <> + <ModalHeader + title={payee ? `Schedule: ${payee.name}` : 'Schedule'} + rightContent={<ModalCloseButton onClick={close} />} /> - <AccountAutocomplete - includeClosedAccounts={false} - value={state.fields.account} - labelProps={{ id: 'account-label' }} - inputProps={{ id: 'account-field', placeholder: '(none)' }} - onSelect={id => - dispatch({ type: 'set-field', field: 'account', value: id }) - } - /> - </FormField> - - <FormField style={{ flex: 1 }}> - <Stack direction="row" align="center" style={{ marginBottom: 3 }}> - <FormLabel - title="Amount" - htmlFor="amount-field" - style={{ margin: 0, flex: 1 }} - /> - <OpSelect - ops={['isapprox', 'is', 'isbetween']} - value={state.fields.amountOp} - formatOp={op => { - switch (op) { - case 'is': - return 'is exactly'; - case 'isapprox': - return 'is approximately'; - case 'isbetween': - return 'is between'; - default: - throw new Error('Invalid op for select: ' + op); - } - }} - style={{ - padding: '0 10px', - color: theme.pageTextLight, - fontSize: 12, - }} - onChange={(_, op) => - dispatch({ type: 'set-field', field: 'amountOp', value: op }) - } - /> + <Stack direction="row" style={{ marginTop: 10 }}> + <FormField style={{ flex: 1 }}> + <FormLabel title="Schedule Name" htmlFor="name-field" /> + <GenericInput + field="string" + type="string" + value={state.fields.name} + multi={false} + onChange={e => { + dispatch({ type: 'set-field', field: 'name', value: e }); + }} + /> + </FormField> </Stack> - {state.fields.amountOp === 'isbetween' ? ( - <BetweenAmountInput - defaultValue={state.fields.amount} - onChange={value => - dispatch({ - type: 'set-field', - field: 'amount', - value, - }) - } - /> - ) : ( - <AmountInput - id="amount-field" - value={state.fields.amount} - onUpdate={value => - dispatch({ - type: 'set-field', - field: 'amount', - value, - }) - } - /> - )} - </FormField> - </Stack> - - <View style={{ marginTop: 20 }}> - <FormLabel title="Date" /> - </View> - - <Stack direction="row" align="flex-start" justify="space-between"> - <View style={{ width: '13.44rem' }}> - {repeats ? ( - <RecurringSchedulePicker - value={state.fields.date} - onChange={value => - dispatch({ type: 'set-field', field: 'date', value }) - } - /> - ) : ( - <DateSelect - value={state.fields.date} - onSelect={date => - dispatch({ type: 'set-field', field: 'date', value: date }) - } - dateFormat={dateFormat} - /> - )} - - {state.upcomingDates && ( - <View style={{ fontSize: 13, marginTop: 20 }}> - <Text style={{ color: theme.pageTextLight, fontWeight: 600 }}> - Upcoming dates - </Text> - <Stack - direction="column" - spacing={1} - style={{ marginTop: 10, color: theme.pageTextLight }} - > - {state.upcomingDates.map(date => ( - <View key={date}> - {monthUtils.format(date, `${dateFormat} EEEE`)} - </View> - ))} - </Stack> - </View> - )} - </View> - - <View - style={{ - marginTop: 5, - flexDirection: 'row', - alignItems: 'center', - userSelect: 'none', - }} - > - <Checkbox - id="form_repeats" - checked={repeats} - onChange={e => { - dispatch({ type: 'set-repeats', repeats: e.target.checked }); - }} - /> - <label htmlFor="form_repeats" style={{ userSelect: 'none' }}> - Repeats - </label> - </View> - - <Stack align="flex-end"> - <View - style={{ - marginTop: 5, - flexDirection: 'row', - alignItems: 'center', - userSelect: 'none', - justifyContent: 'flex-end', - }} - > - <Checkbox - id="form_posts_transaction" - checked={state.fields.posts_transaction} - onChange={e => { - dispatch({ - type: 'set-field', - field: 'posts_transaction', - value: e.target.checked, - }); - }} - /> - <label - htmlFor="form_posts_transaction" - style={{ userSelect: 'none' }} - > - Automatically add transaction - </label> - </View> + <Stack direction="row" style={{ marginTop: 20 }}> + <FormField style={{ flex: 1 }}> + <FormLabel title="Payee" id="payee-label" htmlFor="payee-field" /> + <PayeeAutocomplete + value={state.fields.payee} + labelProps={{ id: 'payee-label' }} + inputProps={{ id: 'payee-field', placeholder: '(none)' }} + onSelect={id => + dispatch({ type: 'set-field', field: 'payee', value: id }) + } + /> + </FormField> - <Text - style={{ - width: 350, - textAlign: 'right', - color: theme.pageTextLight, - marginTop: 10, - fontSize: 13, - lineHeight: '1.4em', - }} - > - If checked, the schedule will automatically create transactions for - you in the specified account - </Text> - - {!adding && state.schedule.rule && ( - <Stack direction="row" align="center" style={{ marginTop: 20 }}> - {state.isCustom && ( - <Text + <FormField style={{ flex: 1 }}> + <FormLabel + title="Account" + id="account-label" + htmlFor="account-field" + /> + <AccountAutocomplete + includeClosedAccounts={false} + value={state.fields.account} + labelProps={{ id: 'account-label' }} + inputProps={{ id: 'account-field', placeholder: '(none)' }} + onSelect={id => + dispatch({ type: 'set-field', field: 'account', value: id }) + } + /> + </FormField> + + <FormField style={{ flex: 1 }}> + <Stack direction="row" align="center" style={{ marginBottom: 3 }}> + <FormLabel + title="Amount" + htmlFor="amount-field" + style={{ margin: 0, flex: 1 }} + /> + <OpSelect + ops={['isapprox', 'is', 'isbetween']} + value={state.fields.amountOp} + formatOp={op => { + switch (op) { + case 'is': + return 'is exactly'; + case 'isapprox': + return 'is approximately'; + case 'isbetween': + return 'is between'; + default: + throw new Error('Invalid op for select: ' + op); + } + }} style={{ + padding: '0 10px', color: theme.pageTextLight, - fontSize: 13, - textAlign: 'right', - width: 350, + fontSize: 12, }} - > - This schedule has custom conditions and actions - </Text> + onChange={(_, op) => + dispatch({ + type: 'set-field', + field: 'amountOp', + value: op, + }) + } + /> + </Stack> + {state.fields.amountOp === 'isbetween' ? ( + <BetweenAmountInput + defaultValue={state.fields.amount} + onChange={value => + dispatch({ + type: 'set-field', + field: 'amount', + value, + }) + } + /> + ) : ( + <AmountInput + id="amount-field" + value={state.fields.amount} + onUpdate={value => + dispatch({ + type: 'set-field', + field: 'amount', + value, + }) + } + /> + )} + </FormField> + </Stack> + + <View style={{ marginTop: 20 }}> + <FormLabel title="Date" /> + </View> + + <Stack direction="row" align="flex-start" justify="space-between"> + <View style={{ width: '13.44rem' }}> + {repeats ? ( + <RecurringSchedulePicker + value={state.fields.date} + onChange={value => + dispatch({ type: 'set-field', field: 'date', value }) + } + /> + ) : ( + <DateSelect + value={state.fields.date} + onSelect={date => + dispatch({ type: 'set-field', field: 'date', value: date }) + } + dateFormat={dateFormat} + /> + )} + + {state.upcomingDates && ( + <View style={{ fontSize: 13, marginTop: 20 }}> + <Text style={{ color: theme.pageTextLight, fontWeight: 600 }}> + Upcoming dates + </Text> + <Stack + direction="column" + spacing={1} + style={{ marginTop: 10, color: theme.pageTextLight }} + > + {state.upcomingDates.map(date => ( + <View key={date}> + {monthUtils.format(date, `${dateFormat} EEEE`)} + </View> + ))} + </Stack> + </View> )} - <Button onPress={() => onEditRule()} isDisabled={adding}> - Edit as rule - </Button> - </Stack> - )} - </Stack> - </Stack> - - <View style={{ marginTop: 30, flex: 1 }}> - <SelectedProvider instance={selectedInst}> - {adding ? ( - <View style={{ flexDirection: 'row', padding: '5px 0' }}> - <Text style={{ color: theme.pageTextLight }}> - These transactions match this schedule: - </Text> - <View style={{ flex: 1 }} /> - <Text style={{ color: theme.pageTextLight }}> - Select transactions to link on save - </Text> </View> - ) : ( - <View style={{ flexDirection: 'row', alignItems: 'center' }}> - <Button - variant="bare" + + <View + style={{ + marginTop: 5, + flexDirection: 'row', + alignItems: 'center', + userSelect: 'none', + }} + > + <Checkbox + id="form_repeats" + checked={repeats} + onChange={e => { + dispatch({ type: 'set-repeats', repeats: e.target.checked }); + }} + /> + <label htmlFor="form_repeats" style={{ userSelect: 'none' }}> + Repeats + </label> + </View> + + <Stack align="flex-end"> + <View style={{ - color: - state.transactionsMode === 'linked' - ? theme.pageTextLink - : theme.pageTextSubdued, - marginRight: 10, - fontSize: 14, + marginTop: 5, + flexDirection: 'row', + alignItems: 'center', + userSelect: 'none', + justifyContent: 'flex-end', }} - onPress={() => onSwitchTransactions('linked')} > - Linked transactions - </Button>{' '} - <Button - variant="bare" + <Checkbox + id="form_posts_transaction" + checked={state.fields.posts_transaction} + onChange={e => { + dispatch({ + type: 'set-field', + field: 'posts_transaction', + value: e.target.checked, + }); + }} + /> + <label + htmlFor="form_posts_transaction" + style={{ userSelect: 'none' }} + > + Automatically add transaction + </label> + </View> + + <Text style={{ - color: - state.transactionsMode === 'matched' - ? theme.pageTextLink - : theme.pageTextSubdued, - fontSize: 14, + width: 350, + textAlign: 'right', + color: theme.pageTextLight, + marginTop: 10, + fontSize: 13, + lineHeight: '1.4em', }} - onPress={() => onSwitchTransactions('matched')} > - Find matching transactions - </Button> - <View style={{ flex: 1 }} /> - <SelectedItemsButton - name="transactions" - items={ - state.transactionsMode === 'linked' - ? [{ name: 'unlink', text: 'Unlink from schedule' }] - : [{ name: 'link', text: 'Link to schedule' }] + If checked, the schedule will automatically create transactions + for you in the specified account + </Text> + + {!adding && state.schedule.rule && ( + <Stack direction="row" align="center" style={{ marginTop: 20 }}> + {state.isCustom && ( + <Text + style={{ + color: theme.pageTextLight, + fontSize: 13, + textAlign: 'right', + width: 350, + }} + > + This schedule has custom conditions and actions + </Text> + )} + <Button onPress={() => onEditRule()} isDisabled={adding}> + Edit as rule + </Button> + </Stack> + )} + </Stack> + </Stack> + + <View style={{ marginTop: 30, flex: 1 }}> + <SelectedProvider instance={selectedInst}> + {adding ? ( + <View style={{ flexDirection: 'row', padding: '5px 0' }}> + <Text style={{ color: theme.pageTextLight }}> + These transactions match this schedule: + </Text> + <View style={{ flex: 1 }} /> + <Text style={{ color: theme.pageTextLight }}> + Select transactions to link on save + </Text> + </View> + ) : ( + <View style={{ flexDirection: 'row', alignItems: 'center' }}> + <Button + variant="bare" + style={{ + color: + state.transactionsMode === 'linked' + ? theme.pageTextLink + : theme.pageTextSubdued, + marginRight: 10, + fontSize: 14, + }} + onPress={() => onSwitchTransactions('linked')} + > + Linked transactions + </Button>{' '} + <Button + variant="bare" + style={{ + color: + state.transactionsMode === 'matched' + ? theme.pageTextLink + : theme.pageTextSubdued, + fontSize: 14, + }} + onPress={() => onSwitchTransactions('matched')} + > + Find matching transactions + </Button> + <View style={{ flex: 1 }} /> + <SelectedItemsButton + name="transactions" + items={ + state.transactionsMode === 'linked' + ? [{ name: 'unlink', text: 'Unlink from schedule' }] + : [{ name: 'link', text: 'Link to schedule' }] + } + onSelect={(name, ids) => { + switch (name) { + case 'link': + onLinkTransactions(ids); + break; + case 'unlink': + onUnlinkTransactions(ids); + break; + default: + } + }} + /> + </View> + )} + + <SimpleTransactionsTable + renderEmpty={ + <NoTransactionsMessage + error={state.error} + transactionsMode={state.transactionsMode} + /> } - onSelect={(name, ids) => { - switch (name) { - case 'link': - onLinkTransactions(ids); - break; - case 'unlink': - onUnlinkTransactions(ids); - break; - default: - } + transactions={state.transactions} + fields={['date', 'payee', 'amount']} + style={{ + border: '1px solid ' + theme.tableBorder, + borderRadius: 4, + overflow: 'hidden', + marginTop: 5, + maxHeight: 200, }} /> - </View> - )} + </SelectedProvider> + </View> - <SimpleTransactionsTable - renderEmpty={ - <NoTransactionsMessage - error={state.error} - transactionsMode={state.transactionsMode} - /> - } - transactions={state.transactions} - fields={['date', 'payee', 'amount']} - style={{ - border: '1px solid ' + theme.tableBorder, - borderRadius: 4, - overflow: 'hidden', - marginTop: 5, - maxHeight: 200, - }} - /> - </SelectedProvider> - </View> - - <Stack - direction="row" - justify="flex-end" - align="center" - style={{ marginTop: 20 }} - > - {state.error && ( - <Text style={{ color: theme.errorText }}>{state.error}</Text> - )} - <Button style={{ marginRight: 10 }} onPress={actions.popModal}> - Cancel - </Button> - <Button variant="primary" onPress={onSave}> - {adding ? 'Add' : 'Save'} - </Button> - </Stack> + <Stack + direction="row" + justify="flex-end" + align="center" + style={{ marginTop: 20 }} + > + {state.error && ( + <Text style={{ color: theme.errorText }}>{state.error}</Text> + )} + <Button style={{ marginRight: 10 }} onPress={close}> + Cancel + </Button> + <Button + variant="primary" + onPress={() => { + onSave(); + close(); + }} + > + {adding ? 'Add' : 'Save'} + </Button> + </Stack> + </> + )} </Modal> ); } diff --git a/packages/desktop-client/src/components/schedules/ScheduleLink.tsx b/packages/desktop-client/src/components/schedules/ScheduleLink.tsx index 7f989a784..666a040c4 100644 --- a/packages/desktop-client/src/components/schedules/ScheduleLink.tsx +++ b/packages/desktop-client/src/components/schedules/ScheduleLink.tsx @@ -8,25 +8,19 @@ import { send } from 'loot-core/src/platform/client/fetch'; import { type Query } from 'loot-core/src/shared/query'; import { type TransactionEntity } from 'loot-core/src/types/models'; -import { type BoundActions } from '../../hooks/useActions'; import { SvgAdd } from '../../icons/v0'; import { Button } from '../common/Button2'; -import { Modal } from '../common/Modal'; +import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal2'; import { Search } from '../common/Search'; import { Text } from '../common/Text'; import { View } from '../common/View'; -import { type CommonModalProps } from '../Modals'; import { ROW_HEIGHT, SchedulesTable } from './SchedulesTable'; export function ScheduleLink({ - modalProps, - actions, transactionIds: ids, getTransaction, }: { - actions: BoundActions; - modalProps?: CommonModalProps; transactionIds: string[]; getTransaction: (transactionId: string) => TransactionEntity; }) { @@ -50,11 +44,9 @@ export function ScheduleLink({ updated: ids.map(id => ({ id, schedule: scheduleId })), }); } - actions.popModal(); } async function onCreate() { - actions.popModal(); dispatch( pushModal('schedule-edit', { id: null, @@ -64,62 +56,83 @@ export function ScheduleLink({ } return ( - <Modal title="Link Schedule" size={{ width: 800 }} {...modalProps}> - <View - style={{ - flexDirection: 'row', - gap: 4, - marginBottom: 20, - alignItems: 'center', - }} - > - <Text> - Choose the schedule{' '} - {ids?.length > 1 - ? `these ${ids.length} transactions belong` - : `this transaction belongs`}{' '} - to: - </Text> - <Search - inputRef={searchInput} - isInModal - width={300} - placeholder="Filter schedules…" - value={filter} - onChange={setFilter} - /> - {ids.length === 1 && ( - <Button - variant="primary" - style={{ marginLeft: 15, padding: '4px 10px' }} - onPress={onCreate} + <Modal + name="schedule-link" + containerProps={{ + style: { + width: 800, + }, + }} + > + {({ state: { close } }) => ( + <> + <ModalHeader + title="Link Schedule" + rightContent={<ModalCloseButton onClick={close} />} + /> + <View + style={{ + flexDirection: 'row', + gap: 4, + marginBottom: 20, + alignItems: 'center', + }} > - <SvgAdd style={{ width: '20', padding: '3' }} /> - Create New - </Button> - )} - </View> + <Text> + Choose the schedule{' '} + {ids?.length > 1 + ? `these ${ids.length} transactions belong` + : `this transaction belongs`}{' '} + to: + </Text> + <Search + inputRef={searchInput} + isInModal + width={300} + placeholder="Filter schedules…" + value={filter} + onChange={setFilter} + /> + {ids.length === 1 && ( + <Button + variant="primary" + style={{ marginLeft: 15, padding: '4px 10px' }} + onPress={() => { + close(); + onCreate(); + }} + > + <SvgAdd style={{ width: '20', padding: '3' }} /> + Create New + </Button> + )} + </View> - <View - style={{ - flex: `1 1 ${ - (ROW_HEIGHT - 1) * (Math.max(schedules.length, 1) + 1) - }px`, - marginTop: 15, - maxHeight: '50vh', - }} - > - <SchedulesTable - allowCompleted={false} - filter={filter} - minimal={true} - onAction={() => {}} - onSelect={onSelect} - schedules={schedules} - statuses={statuses} - style={null} - /> - </View> + <View + style={{ + flex: `1 1 ${ + (ROW_HEIGHT - 1) * (Math.max(schedules.length, 1) + 1) + }px`, + marginTop: 15, + maxHeight: '50vh', + }} + > + <SchedulesTable + allowCompleted={false} + filter={filter} + minimal={true} + onAction={() => {}} + onSelect={id => { + onSelect(id); + close(); + }} + schedules={schedules} + statuses={statuses} + style={null} + /> + </View> + </> + )} </Modal> ); } diff --git a/packages/desktop-client/src/hooks/useModalState.ts b/packages/desktop-client/src/hooks/useModalState.ts new file mode 100644 index 000000000..732a69a96 --- /dev/null +++ b/packages/desktop-client/src/hooks/useModalState.ts @@ -0,0 +1,44 @@ +import { useCallback } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; + +import { popModal } from 'loot-core/client/actions'; +import { type State } from 'loot-core/client/state-types'; +import { type Modal } from 'loot-core/client/state-types/modals'; + +type ModalState = { + onClose: () => void; + modalStack: Modal[]; + activeModal?: string; + isActive: (name: string) => boolean; + isHidden: boolean; +}; + +export function useModalState(): ModalState { + const modalStack = useSelector((state: State) => state.modals.modalStack); + const isHidden = useSelector((state: State) => state.modals.isHidden); + const dispatch = useDispatch(); + + const popModalCallback = useCallback(() => { + dispatch(popModal()); + }, [dispatch]); + + const lastModal = modalStack[modalStack.length - 1]; + const isActive = useCallback( + (name: string) => { + if (name === lastModal?.name) { + return true; + } + + return false; + }, + [lastModal?.name], + ); + + return { + onClose: popModalCallback, + modalStack, + activeModal: lastModal?.name, + isActive, + isHidden, + }; +} diff --git a/packages/loot-core/src/client/actions/modals.ts b/packages/loot-core/src/client/actions/modals.ts index 879092f45..3d0a23cce 100644 --- a/packages/loot-core/src/client/actions/modals.ts +++ b/packages/loot-core/src/client/actions/modals.ts @@ -8,6 +8,7 @@ import type { ModalWithOptions, ModalType, FinanceModals, + Modal, } from '../state-types/modals'; export function pushModal<M extends keyof ModalWithOptions>( @@ -36,8 +37,7 @@ export function replaceModal<M extends ModalType>( name: M, options?: FinanceModals[M], ): ReplaceModalAction { - // @ts-expect-error TS is unable to determine that `name` and `options` match - const modal: M = { name, options }; + const modal: Modal = { name, options }; return { type: constants.REPLACE_MODAL, modal }; } diff --git a/packages/loot-core/src/client/state-types/modals.d.ts b/packages/loot-core/src/client/state-types/modals.d.ts index 3c6b4ab85..cf801cb2e 100644 --- a/packages/loot-core/src/client/state-types/modals.d.ts +++ b/packages/loot-core/src/client/state-types/modals.d.ts @@ -142,6 +142,7 @@ type FinanceModals = { onSave: (category: CategoryEntity) => void; onEditNotes: (id: string) => void; onDelete: (categoryId: string) => void; + onToggleVisibility: (categoryId: string) => void; onBudgetAction: (month: string, action: string, args?: unknown) => void; onClose?: () => void; }; @@ -167,6 +168,7 @@ type FinanceModals = { onAddCategory: (groupId: string, isIncome: boolean) => void; onEditNotes: (id: string) => void; onDelete: (groupId: string) => void; + onToggleVisibility: (groupId: string) => void; onClose?: () => void; }; notes: { @@ -292,3 +294,9 @@ export type ModalsState = { modalStack: Modal[]; isHidden: boolean; }; + +type Modal = { + name: string; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + options?: any; +}; diff --git a/upcoming-release-notes/2946.md b/upcoming-release-notes/2946.md new file mode 100644 index 000000000..f430704fb --- /dev/null +++ b/upcoming-release-notes/2946.md @@ -0,0 +1,6 @@ +--- +category: Maintenance +authors: [joel-jeremy] +--- + +Port finance modals to react-aria-components Modal. -- GitLab