diff --git a/packages/desktop-client/e2e/accounts.mobile.test.js-snapshots/Mobile-Accounts-opens-the-accounts-page-and-asserts-on-balances-1-chromium-linux.png b/packages/desktop-client/e2e/accounts.mobile.test.js-snapshots/Mobile-Accounts-opens-the-accounts-page-and-asserts-on-balances-1-chromium-linux.png index 568ed03a9dd..83b5875ce5b 100644 Binary files a/packages/desktop-client/e2e/accounts.mobile.test.js-snapshots/Mobile-Accounts-opens-the-accounts-page-and-asserts-on-balances-1-chromium-linux.png and b/packages/desktop-client/e2e/accounts.mobile.test.js-snapshots/Mobile-Accounts-opens-the-accounts-page-and-asserts-on-balances-1-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/accounts.mobile.test.js-snapshots/Mobile-Accounts-opens-the-accounts-page-and-asserts-on-balances-2-chromium-linux.png b/packages/desktop-client/e2e/accounts.mobile.test.js-snapshots/Mobile-Accounts-opens-the-accounts-page-and-asserts-on-balances-2-chromium-linux.png index 51bb6f2433d..fd439e6c7a8 100644 Binary files a/packages/desktop-client/e2e/accounts.mobile.test.js-snapshots/Mobile-Accounts-opens-the-accounts-page-and-asserts-on-balances-2-chromium-linux.png and b/packages/desktop-client/e2e/accounts.mobile.test.js-snapshots/Mobile-Accounts-opens-the-accounts-page-and-asserts-on-balances-2-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/accounts.mobile.test.js-snapshots/Mobile-Accounts-opens-the-accounts-page-and-asserts-on-balances-3-chromium-linux.png b/packages/desktop-client/e2e/accounts.mobile.test.js-snapshots/Mobile-Accounts-opens-the-accounts-page-and-asserts-on-balances-3-chromium-linux.png index 7577f513fc6..086e56d84a0 100644 Binary files a/packages/desktop-client/e2e/accounts.mobile.test.js-snapshots/Mobile-Accounts-opens-the-accounts-page-and-asserts-on-balances-3-chromium-linux.png and b/packages/desktop-client/e2e/accounts.mobile.test.js-snapshots/Mobile-Accounts-opens-the-accounts-page-and-asserts-on-balances-3-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/accounts.test.js b/packages/desktop-client/e2e/accounts.test.js index 5c9118172d6..dfe9cb47506 100644 --- a/packages/desktop-client/e2e/accounts.test.js +++ b/packages/desktop-client/e2e/accounts.test.js @@ -54,17 +54,17 @@ test.describe('Accounts', () => { await expect(page).toMatchThemeScreenshots(); }); - test.describe('Budgeted Accounts', () => { + test.describe('On Budget Accounts', () => { // Reset filters test.afterEach(async () => { await accountPage.removeFilter(0); }); test('creates a transfer from two existing transactions', async () => { - accountPage = await navigation.goToAccountPage('For budget'); + accountPage = await navigation.goToAccountPage('On budget'); await accountPage.waitFor(); - await expect(accountPage.accountName).toHaveText('Budgeted Accounts'); + await expect(accountPage.accountName).toHaveText('On Budget Accounts'); await accountPage.filterByNote('Test Acc Transfer'); diff --git a/packages/desktop-client/e2e/accounts.test.js-snapshots/Accounts-Import-Transactions-import-csv-file-twice-1-chromium-linux.png b/packages/desktop-client/e2e/accounts.test.js-snapshots/Accounts-Import-Transactions-import-csv-file-twice-1-chromium-linux.png index cc58f7f29ec..75f6d9468b1 100644 Binary files a/packages/desktop-client/e2e/accounts.test.js-snapshots/Accounts-Import-Transactions-import-csv-file-twice-1-chromium-linux.png and b/packages/desktop-client/e2e/accounts.test.js-snapshots/Accounts-Import-Transactions-import-csv-file-twice-1-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/accounts.test.js-snapshots/Accounts-Import-Transactions-import-csv-file-twice-2-chromium-linux.png b/packages/desktop-client/e2e/accounts.test.js-snapshots/Accounts-Import-Transactions-import-csv-file-twice-2-chromium-linux.png index 6e9e713d6ce..019872cae44 100644 Binary files a/packages/desktop-client/e2e/accounts.test.js-snapshots/Accounts-Import-Transactions-import-csv-file-twice-2-chromium-linux.png and b/packages/desktop-client/e2e/accounts.test.js-snapshots/Accounts-Import-Transactions-import-csv-file-twice-2-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/accounts.test.js-snapshots/Accounts-Import-Transactions-import-csv-file-twice-3-chromium-linux.png b/packages/desktop-client/e2e/accounts.test.js-snapshots/Accounts-Import-Transactions-import-csv-file-twice-3-chromium-linux.png index e7c52403655..2b339523965 100644 Binary files a/packages/desktop-client/e2e/accounts.test.js-snapshots/Accounts-Import-Transactions-import-csv-file-twice-3-chromium-linux.png and b/packages/desktop-client/e2e/accounts.test.js-snapshots/Accounts-Import-Transactions-import-csv-file-twice-3-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/accounts.test.js-snapshots/Accounts-Import-Transactions-imports-transactions-from-a-CSV-file-1-chromium-linux.png b/packages/desktop-client/e2e/accounts.test.js-snapshots/Accounts-Import-Transactions-imports-transactions-from-a-CSV-file-1-chromium-linux.png index 28fb5fc075a..93ad61b9bdb 100644 Binary files a/packages/desktop-client/e2e/accounts.test.js-snapshots/Accounts-Import-Transactions-imports-transactions-from-a-CSV-file-1-chromium-linux.png and b/packages/desktop-client/e2e/accounts.test.js-snapshots/Accounts-Import-Transactions-imports-transactions-from-a-CSV-file-1-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/accounts.test.js-snapshots/Accounts-Import-Transactions-imports-transactions-from-a-CSV-file-2-chromium-linux.png b/packages/desktop-client/e2e/accounts.test.js-snapshots/Accounts-Import-Transactions-imports-transactions-from-a-CSV-file-2-chromium-linux.png index d9b6c60afff..510fa9bf584 100644 Binary files a/packages/desktop-client/e2e/accounts.test.js-snapshots/Accounts-Import-Transactions-imports-transactions-from-a-CSV-file-2-chromium-linux.png and b/packages/desktop-client/e2e/accounts.test.js-snapshots/Accounts-Import-Transactions-imports-transactions-from-a-CSV-file-2-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/accounts.test.js-snapshots/Accounts-Import-Transactions-imports-transactions-from-a-CSV-file-3-chromium-linux.png b/packages/desktop-client/e2e/accounts.test.js-snapshots/Accounts-Import-Transactions-imports-transactions-from-a-CSV-file-3-chromium-linux.png index faed13bee17..42fa79651d9 100644 Binary files a/packages/desktop-client/e2e/accounts.test.js-snapshots/Accounts-Import-Transactions-imports-transactions-from-a-CSV-file-3-chromium-linux.png and b/packages/desktop-client/e2e/accounts.test.js-snapshots/Accounts-Import-Transactions-imports-transactions-from-a-CSV-file-3-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/accounts.test.js-snapshots/Accounts-closes-an-account-1-chromium-linux.png b/packages/desktop-client/e2e/accounts.test.js-snapshots/Accounts-closes-an-account-1-chromium-linux.png index e322b3a0110..9ace9002d92 100644 Binary files a/packages/desktop-client/e2e/accounts.test.js-snapshots/Accounts-closes-an-account-1-chromium-linux.png and b/packages/desktop-client/e2e/accounts.test.js-snapshots/Accounts-closes-an-account-1-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/accounts.test.js-snapshots/Accounts-closes-an-account-2-chromium-linux.png b/packages/desktop-client/e2e/accounts.test.js-snapshots/Accounts-closes-an-account-2-chromium-linux.png index c99575be0b1..485cd1f4285 100644 Binary files a/packages/desktop-client/e2e/accounts.test.js-snapshots/Accounts-closes-an-account-2-chromium-linux.png and b/packages/desktop-client/e2e/accounts.test.js-snapshots/Accounts-closes-an-account-2-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/accounts.test.js-snapshots/Accounts-closes-an-account-3-chromium-linux.png b/packages/desktop-client/e2e/accounts.test.js-snapshots/Accounts-closes-an-account-3-chromium-linux.png index 13ad4f0f9e7..f4ef788f1e9 100644 Binary files a/packages/desktop-client/e2e/accounts.test.js-snapshots/Accounts-closes-an-account-3-chromium-linux.png and b/packages/desktop-client/e2e/accounts.test.js-snapshots/Accounts-closes-an-account-3-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/accounts.test.js-snapshots/Accounts-closes-an-account-4-chromium-linux.png b/packages/desktop-client/e2e/accounts.test.js-snapshots/Accounts-closes-an-account-4-chromium-linux.png index bf4acbee5ba..c8bc05a7e86 100644 Binary files a/packages/desktop-client/e2e/accounts.test.js-snapshots/Accounts-closes-an-account-4-chromium-linux.png and b/packages/desktop-client/e2e/accounts.test.js-snapshots/Accounts-closes-an-account-4-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/accounts.test.js-snapshots/Accounts-closes-an-account-5-chromium-linux.png b/packages/desktop-client/e2e/accounts.test.js-snapshots/Accounts-closes-an-account-5-chromium-linux.png index d0d14d084d3..f545dced181 100644 Binary files a/packages/desktop-client/e2e/accounts.test.js-snapshots/Accounts-closes-an-account-5-chromium-linux.png and b/packages/desktop-client/e2e/accounts.test.js-snapshots/Accounts-closes-an-account-5-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/accounts.test.js-snapshots/Accounts-closes-an-account-6-chromium-linux.png b/packages/desktop-client/e2e/accounts.test.js-snapshots/Accounts-closes-an-account-6-chromium-linux.png index 00ed14f1d70..c9633902c80 100644 Binary files a/packages/desktop-client/e2e/accounts.test.js-snapshots/Accounts-closes-an-account-6-chromium-linux.png and b/packages/desktop-client/e2e/accounts.test.js-snapshots/Accounts-closes-an-account-6-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/accounts.test.js-snapshots/Accounts-creates-a-new-account-and-views-the-initial-balance-transaction-1-chromium-linux.png b/packages/desktop-client/e2e/accounts.test.js-snapshots/Accounts-creates-a-new-account-and-views-the-initial-balance-transaction-1-chromium-linux.png index ff0f29ec92a..4b09a36ab91 100644 Binary files a/packages/desktop-client/e2e/accounts.test.js-snapshots/Accounts-creates-a-new-account-and-views-the-initial-balance-transaction-1-chromium-linux.png and b/packages/desktop-client/e2e/accounts.test.js-snapshots/Accounts-creates-a-new-account-and-views-the-initial-balance-transaction-1-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/accounts.test.js-snapshots/Accounts-creates-a-new-account-and-views-the-initial-balance-transaction-2-chromium-linux.png b/packages/desktop-client/e2e/accounts.test.js-snapshots/Accounts-creates-a-new-account-and-views-the-initial-balance-transaction-2-chromium-linux.png index f98acb71ce2..71117da939c 100644 Binary files a/packages/desktop-client/e2e/accounts.test.js-snapshots/Accounts-creates-a-new-account-and-views-the-initial-balance-transaction-2-chromium-linux.png and b/packages/desktop-client/e2e/accounts.test.js-snapshots/Accounts-creates-a-new-account-and-views-the-initial-balance-transaction-2-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/accounts.test.js-snapshots/Accounts-creates-a-new-account-and-views-the-initial-balance-transaction-3-chromium-linux.png b/packages/desktop-client/e2e/accounts.test.js-snapshots/Accounts-creates-a-new-account-and-views-the-initial-balance-transaction-3-chromium-linux.png index b943624386b..9bf30abe716 100644 Binary files a/packages/desktop-client/e2e/accounts.test.js-snapshots/Accounts-creates-a-new-account-and-views-the-initial-balance-transaction-3-chromium-linux.png and b/packages/desktop-client/e2e/accounts.test.js-snapshots/Accounts-creates-a-new-account-and-views-the-initial-balance-transaction-3-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Envelope-checks-that-clicking--321fd-ed-amount-opens-the-budget-summary-menu-modal-2-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Envelope-checks-that-clicking--321fd-ed-amount-opens-the-budget-summary-menu-modal-2-chromium-linux.png index b31314a5707..4e6252fec76 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Envelope-checks-that-clicking--321fd-ed-amount-opens-the-budget-summary-menu-modal-2-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Envelope-checks-that-clicking--321fd-ed-amount-opens-the-budget-summary-menu-modal-2-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Envelope-checks-that-clicking--4bb70-ed-amount-opens-the-budget-summary-menu-modal-1-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Envelope-checks-that-clicking--4bb70-ed-amount-opens-the-budget-summary-menu-modal-1-chromium-linux.png index f400d85a126..65c60199b30 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Envelope-checks-that-clicking--4bb70-ed-amount-opens-the-budget-summary-menu-modal-1-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Envelope-checks-that-clicking--4bb70-ed-amount-opens-the-budget-summary-menu-modal-1-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Envelope-checks-that-clicking--94a85-ed-amount-opens-the-budget-summary-menu-modal-3-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Envelope-checks-that-clicking--94a85-ed-amount-opens-the-budget-summary-menu-modal-3-chromium-linux.png index 08b9252de66..55a7c2da4a5 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Envelope-checks-that-clicking--94a85-ed-amount-opens-the-budget-summary-menu-modal-3-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Envelope-checks-that-clicking--94a85-ed-amount-opens-the-budget-summary-menu-modal-3-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking--0ba04-nt-amount-opens-the-budget-summary-menu-modal-1-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking--0ba04-nt-amount-opens-the-budget-summary-menu-modal-1-chromium-linux.png index ce81ed35619..888b559f45d 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking--0ba04-nt-amount-opens-the-budget-summary-menu-modal-1-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking--0ba04-nt-amount-opens-the-budget-summary-menu-modal-1-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking--1ce6d-nt-amount-opens-the-budget-summary-menu-modal-2-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking--1ce6d-nt-amount-opens-the-budget-summary-menu-modal-2-chromium-linux.png index 99485f83c0f..938fece6a6f 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking--1ce6d-nt-amount-opens-the-budget-summary-menu-modal-2-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking--1ce6d-nt-amount-opens-the-budget-summary-menu-modal-2-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking--42062-in-the-page-header-opens-the-month-menu-modal-2-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking--42062-in-the-page-header-opens-the-month-menu-modal-2-chromium-linux.png index 6dac5c567f2..8771e50c51a 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking--42062-in-the-page-header-opens-the-month-menu-modal-2-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking--42062-in-the-page-header-opens-the-month-menu-modal-2-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking--49fb6-in-the-page-header-opens-the-month-menu-modal-3-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking--49fb6-in-the-page-header-opens-the-month-menu-modal-3-chromium-linux.png index 0f9398c3873..73889255c41 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking--49fb6-in-the-page-header-opens-the-month-menu-modal-3-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking--49fb6-in-the-page-header-opens-the-month-menu-modal-3-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking--5f098-roup-name-opens-the-category-group-menu-modal-1-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking--5f098-roup-name-opens-the-category-group-menu-modal-1-chromium-linux.png index b3dced3da41..c4d46a75fd6 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking--5f098-roup-name-opens-the-category-group-menu-modal-1-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking--5f098-roup-name-opens-the-category-group-menu-modal-1-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking--7c353-the-page-header-shows-the-next-month-s-budget-3-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking--7c353-the-page-header-shows-the-next-month-s-budget-3-chromium-linux.png index 64ccf3b3e34..16b144855b5 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking--7c353-the-page-header-shows-the-next-month-s-budget-3-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking--7c353-the-page-header-shows-the-next-month-s-budget-3-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking--929be-roup-name-opens-the-category-group-menu-modal-3-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking--929be-roup-name-opens-the-category-group-menu-modal-3-chromium-linux.png index 6d8cb23a464..7bbb28410fb 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking--929be-roup-name-opens-the-category-group-menu-modal-3-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking--929be-roup-name-opens-the-category-group-menu-modal-3-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking--a3783-in-the-page-header-opens-the-budget-page-menu-1-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking--a3783-in-the-page-header-opens-the-budget-page-menu-1-chromium-linux.png index 0eb679a919e..1b22662391e 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking--a3783-in-the-page-header-opens-the-budget-page-menu-1-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking--a3783-in-the-page-header-opens-the-budget-page-menu-1-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking--a8b5e-in-the-page-header-opens-the-budget-page-menu-3-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking--a8b5e-in-the-page-header-opens-the-budget-page-menu-3-chromium-linux.png index 70175ca8492..cf602b52a12 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking--a8b5e-in-the-page-header-opens-the-budget-page-menu-3-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking--a8b5e-in-the-page-header-opens-the-budget-page-menu-3-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking--b1562-in-the-page-header-opens-the-month-menu-modal-1-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking--b1562-in-the-page-header-opens-the-month-menu-modal-1-chromium-linux.png index 6443198c425..b16c2a13db2 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking--b1562-in-the-page-header-opens-the-month-menu-modal-1-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking--b1562-in-the-page-header-opens-the-month-menu-modal-1-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking--d5af6-the-page-header-shows-the-next-month-s-budget-1-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking--d5af6-the-page-header-shows-the-next-month-s-budget-1-chromium-linux.png index b044c08e50b..74b81149829 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking--d5af6-the-page-header-shows-the-next-month-s-budget-1-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking--d5af6-the-page-header-shows-the-next-month-s-budget-1-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking--dc927-roup-name-opens-the-category-group-menu-modal-2-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking--dc927-roup-name-opens-the-category-group-menu-modal-2-chromium-linux.png index d43e779a9ce..2aeeadb6513 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking--dc927-roup-name-opens-the-category-group-menu-modal-2-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking--dc927-roup-name-opens-the-category-group-menu-modal-2-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking--f2198-the-page-header-shows-the-next-month-s-budget-2-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking--f2198-the-page-header-shows-the-next-month-s-budget-2-chromium-linux.png index 3f2e3397c1a..ccfa385f2f1 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking--f2198-the-page-header-shows-the-next-month-s-budget-2-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking--f2198-the-page-header-shows-the-next-month-s-budget-2-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking--f224f-nt-amount-opens-the-budget-summary-menu-modal-3-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking--f224f-nt-amount-opens-the-budget-summary-menu-modal-3-chromium-linux.png index 6bb02c428c4..6c93af74f17 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking--f224f-nt-amount-opens-the-budget-summary-menu-modal-3-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking--f224f-nt-amount-opens-the-budget-summary-menu-modal-3-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking--f8a19-in-the-page-header-opens-the-budget-page-menu-2-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking--f8a19-in-the-page-header-opens-the-budget-page-menu-2-chromium-linux.png index 2660f55f70e..ac5fb95ee8c 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking--f8a19-in-the-page-header-opens-the-budget-page-menu-2-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking--f8a19-in-the-page-header-opens-the-budget-page-menu-2-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking-the-balance-cell-opens-the-balance-menu-modal-1-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking-the-balance-cell-opens-the-balance-menu-modal-1-chromium-linux.png index 839ef4511b3..69b5304e0d0 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking-the-balance-cell-opens-the-balance-menu-modal-1-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking-the-balance-cell-opens-the-balance-menu-modal-1-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking-the-balance-cell-opens-the-balance-menu-modal-2-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking-the-balance-cell-opens-the-balance-menu-modal-2-chromium-linux.png index 5817e79f764..b58968edc91 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking-the-balance-cell-opens-the-balance-menu-modal-2-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking-the-balance-cell-opens-the-balance-menu-modal-2-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking-the-balance-cell-opens-the-balance-menu-modal-3-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking-the-balance-cell-opens-the-balance-menu-modal-3-chromium-linux.png index 6c1c349ba81..5d5c9e88589 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking-the-balance-cell-opens-the-balance-menu-modal-3-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking-the-balance-cell-opens-the-balance-menu-modal-3-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking-the-budgeted-cell-opens-the-budget-menu-modal-1-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking-the-budgeted-cell-opens-the-budget-menu-modal-1-chromium-linux.png index 12415a1b4cf..d47bb949f4f 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking-the-budgeted-cell-opens-the-budget-menu-modal-1-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking-the-budgeted-cell-opens-the-budget-menu-modal-1-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking-the-budgeted-cell-opens-the-budget-menu-modal-2-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking-the-budgeted-cell-opens-the-budget-menu-modal-2-chromium-linux.png index 3036ffbf68f..0fedc45eb04 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking-the-budgeted-cell-opens-the-budget-menu-modal-2-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking-the-budgeted-cell-opens-the-budget-menu-modal-2-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking-the-budgeted-cell-opens-the-budget-menu-modal-3-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking-the-budgeted-cell-opens-the-budget-menu-modal-3-chromium-linux.png index 79274e17487..40d0b0f408a 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking-the-budgeted-cell-opens-the-budget-menu-modal-3-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking-the-budgeted-cell-opens-the-budget-menu-modal-3-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking-the-category-name-opens-the-category-menu-modal-1-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking-the-category-name-opens-the-category-menu-modal-1-chromium-linux.png index 5df130f15ee..6c748af4cd7 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking-the-category-name-opens-the-category-menu-modal-1-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking-the-category-name-opens-the-category-menu-modal-1-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking-the-category-name-opens-the-category-menu-modal-2-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking-the-category-name-opens-the-category-menu-modal-2-chromium-linux.png index 4195428bb4e..89b5a6cd2c9 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking-the-category-name-opens-the-category-menu-modal-2-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking-the-category-name-opens-the-category-menu-modal-2-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking-the-category-name-opens-the-category-menu-modal-3-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking-the-category-name-opens-the-category-menu-modal-3-chromium-linux.png index 76fe25abd17..a23bf489f0e 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking-the-category-name-opens-the-category-menu-modal-3-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-checks-that-clicking-the-category-name-opens-the-category-menu-modal-3-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-loads-the-budget-page-with-budgeted-amounts-1-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-loads-the-budget-page-with-budgeted-amounts-1-chromium-linux.png index bb0ac8bbf88..9b379af8aa4 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-loads-the-budget-page-with-budgeted-amounts-1-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-loads-the-budget-page-with-budgeted-amounts-1-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-loads-the-budget-page-with-budgeted-amounts-2-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-loads-the-budget-page-with-budgeted-amounts-2-chromium-linux.png index 631e8256296..0ed4936a89c 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-loads-the-budget-page-with-budgeted-amounts-2-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-loads-the-budget-page-with-budgeted-amounts-2-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-loads-the-budget-page-with-budgeted-amounts-3-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-loads-the-budget-page-with-budgeted-amounts-3-chromium-linux.png index b662506066c..cf697f2f37b 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-loads-the-budget-page-with-budgeted-amounts-3-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-loads-the-budget-page-with-budgeted-amounts-3-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-updates-the-budgeted-amount-1-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-updates-the-budgeted-amount-1-chromium-linux.png index a6c178b7a6c..bf758ff1e6b 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-updates-the-budgeted-amount-1-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-updates-the-budgeted-amount-1-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-updates-the-budgeted-amount-2-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-updates-the-budgeted-amount-2-chromium-linux.png index 38c4c7b2b1e..19d4e56003a 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-updates-the-budgeted-amount-2-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-updates-the-budgeted-amount-2-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-updates-the-budgeted-amount-3-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-updates-the-budgeted-amount-3-chromium-linux.png index 7fc874ece94..f9c38810b2e 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-updates-the-budgeted-amount-3-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Tracking-updates-the-budgeted-amount-3-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.test.js b/packages/desktop-client/e2e/budget.test.js index 3a2df2e45fc..bb256c3beca 100644 --- a/packages/desktop-client/e2e/budget.test.js +++ b/packages/desktop-client/e2e/budget.test.js @@ -27,12 +27,12 @@ test.describe('Budget', () => { test('renders the summary information: available funds, overspent, budgeted and for next month', async () => { const summary = budgetPage.budgetSummary.first(); - await expect(summary.getByText('Available Funds')).toBeVisible({ + await expect(summary.getByText('Available funds')).toBeVisible({ timeout: 10000, }); await expect(summary.getByText(/^Overspent in /)).toBeVisible(); await expect(summary.getByText('Budgeted')).toBeVisible(); - await expect(summary.getByText('For Next Month')).toBeVisible(); + await expect(summary.getByText('For next month')).toBeVisible(); await expect(page).toMatchThemeScreenshots(); }); diff --git a/packages/desktop-client/e2e/budget.test.js-snapshots/Budget-renders-the-summary-information-available-funds-overspent-budgeted-and-for-next-month-1-chromium-linux.png b/packages/desktop-client/e2e/budget.test.js-snapshots/Budget-renders-the-summary-information-available-funds-overspent-budgeted-and-for-next-month-1-chromium-linux.png index 8f6e6c82469..d325f3ee5f3 100644 Binary files a/packages/desktop-client/e2e/budget.test.js-snapshots/Budget-renders-the-summary-information-available-funds-overspent-budgeted-and-for-next-month-1-chromium-linux.png and b/packages/desktop-client/e2e/budget.test.js-snapshots/Budget-renders-the-summary-information-available-funds-overspent-budgeted-and-for-next-month-1-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.test.js-snapshots/Budget-renders-the-summary-information-available-funds-overspent-budgeted-and-for-next-month-2-chromium-linux.png b/packages/desktop-client/e2e/budget.test.js-snapshots/Budget-renders-the-summary-information-available-funds-overspent-budgeted-and-for-next-month-2-chromium-linux.png index e97f6f72826..20c9ddaa8eb 100644 Binary files a/packages/desktop-client/e2e/budget.test.js-snapshots/Budget-renders-the-summary-information-available-funds-overspent-budgeted-and-for-next-month-2-chromium-linux.png and b/packages/desktop-client/e2e/budget.test.js-snapshots/Budget-renders-the-summary-information-available-funds-overspent-budgeted-and-for-next-month-2-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.test.js-snapshots/Budget-renders-the-summary-information-available-funds-overspent-budgeted-and-for-next-month-3-chromium-linux.png b/packages/desktop-client/e2e/budget.test.js-snapshots/Budget-renders-the-summary-information-available-funds-overspent-budgeted-and-for-next-month-3-chromium-linux.png index b4b0b86bf94..fc7aa791584 100644 Binary files a/packages/desktop-client/e2e/budget.test.js-snapshots/Budget-renders-the-summary-information-available-funds-overspent-budgeted-and-for-next-month-3-chromium-linux.png and b/packages/desktop-client/e2e/budget.test.js-snapshots/Budget-renders-the-summary-information-available-funds-overspent-budgeted-and-for-next-month-3-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.test.js-snapshots/Budget-transfer-funds-to-another-category-1-chromium-linux.png b/packages/desktop-client/e2e/budget.test.js-snapshots/Budget-transfer-funds-to-another-category-1-chromium-linux.png index a1890d819e9..c29a9743f9e 100644 Binary files a/packages/desktop-client/e2e/budget.test.js-snapshots/Budget-transfer-funds-to-another-category-1-chromium-linux.png and b/packages/desktop-client/e2e/budget.test.js-snapshots/Budget-transfer-funds-to-another-category-1-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.test.js-snapshots/Budget-transfer-funds-to-another-category-2-chromium-linux.png b/packages/desktop-client/e2e/budget.test.js-snapshots/Budget-transfer-funds-to-another-category-2-chromium-linux.png index ee551b2033e..a5a5b01ec57 100644 Binary files a/packages/desktop-client/e2e/budget.test.js-snapshots/Budget-transfer-funds-to-another-category-2-chromium-linux.png and b/packages/desktop-client/e2e/budget.test.js-snapshots/Budget-transfer-funds-to-another-category-2-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.test.js-snapshots/Budget-transfer-funds-to-another-category-3-chromium-linux.png b/packages/desktop-client/e2e/budget.test.js-snapshots/Budget-transfer-funds-to-another-category-3-chromium-linux.png index a87364ce584..d20cceb4886 100644 Binary files a/packages/desktop-client/e2e/budget.test.js-snapshots/Budget-transfer-funds-to-another-category-3-chromium-linux.png and b/packages/desktop-client/e2e/budget.test.js-snapshots/Budget-transfer-funds-to-another-category-3-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/page-models/mobile-budget-page.js b/packages/desktop-client/e2e/page-models/mobile-budget-page.js index 7a3a63c0520..6e3689334fd 100644 --- a/packages/desktop-client/e2e/page-models/mobile-budget-page.js +++ b/packages/desktop-client/e2e/page-models/mobile-budget-page.js @@ -34,7 +34,7 @@ export class MobileBudgetPage { name: 'Saved', }); this.projectedSavingsButton = this.budgetTableHeader.getByRole('button', { - name: 'Projected Savings', + name: 'Projected savings', }); this.overspentButton = this.budgetTableHeader.getByRole('button', { name: 'Overspent', @@ -294,7 +294,7 @@ export class MobileBudgetPage { } throw new Error( - 'None of “Saved”, “Projected Savings”, or “Overspent” buttons could be located on the page', + 'None of “Saved”, “Projected savings”, or “Overspent” buttons could be located on the page', ); } diff --git a/packages/desktop-client/e2e/page-models/navigation.js b/packages/desktop-client/e2e/page-models/navigation.js index 248db6444b6..a8463d07678 100644 --- a/packages/desktop-client/e2e/page-models/navigation.js +++ b/packages/desktop-client/e2e/page-models/navigation.js @@ -66,7 +66,7 @@ export class Navigation { await this.page.getByLabel('Balance:').fill(String(data.balance)); if (data.offBudget) { - await this.page.getByLabel('Off-budget').click(); + await this.page.getByLabel('Off budget').click(); } await this.page diff --git a/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Switches-to-Area-Graph-and-checks-the-visuals-1-chromium-linux.png b/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Switches-to-Area-Graph-and-checks-the-visuals-1-chromium-linux.png index f38bfe3dd15..5f6dce83514 100644 Binary files a/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Switches-to-Area-Graph-and-checks-the-visuals-1-chromium-linux.png and b/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Switches-to-Area-Graph-and-checks-the-visuals-1-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Switches-to-Area-Graph-and-checks-the-visuals-2-chromium-linux.png b/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Switches-to-Area-Graph-and-checks-the-visuals-2-chromium-linux.png index 9f96c1f40a6..e7446c7b27c 100644 Binary files a/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Switches-to-Area-Graph-and-checks-the-visuals-2-chromium-linux.png and b/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Switches-to-Area-Graph-and-checks-the-visuals-2-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Switches-to-Area-Graph-and-checks-the-visuals-3-chromium-linux.png b/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Switches-to-Area-Graph-and-checks-the-visuals-3-chromium-linux.png index 51699728fb0..e537cb6e233 100644 Binary files a/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Switches-to-Area-Graph-and-checks-the-visuals-3-chromium-linux.png and b/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Switches-to-Area-Graph-and-checks-the-visuals-3-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Switches-to-Bar-Graph-and-checks-the-visuals-1-chromium-linux.png b/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Switches-to-Bar-Graph-and-checks-the-visuals-1-chromium-linux.png index 93fd2a30ca7..9210a0b8dfd 100644 Binary files a/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Switches-to-Bar-Graph-and-checks-the-visuals-1-chromium-linux.png and b/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Switches-to-Bar-Graph-and-checks-the-visuals-1-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Switches-to-Bar-Graph-and-checks-the-visuals-2-chromium-linux.png b/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Switches-to-Bar-Graph-and-checks-the-visuals-2-chromium-linux.png index c1c099ed815..2b9312d930f 100644 Binary files a/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Switches-to-Bar-Graph-and-checks-the-visuals-2-chromium-linux.png and b/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Switches-to-Bar-Graph-and-checks-the-visuals-2-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Switches-to-Bar-Graph-and-checks-the-visuals-3-chromium-linux.png b/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Switches-to-Bar-Graph-and-checks-the-visuals-3-chromium-linux.png index 21c27b4c172..bae67361093 100644 Binary files a/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Switches-to-Bar-Graph-and-checks-the-visuals-3-chromium-linux.png and b/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Switches-to-Bar-Graph-and-checks-the-visuals-3-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Switches-to-Data-Table-and-checks-the-visuals-1-chromium-linux.png b/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Switches-to-Data-Table-and-checks-the-visuals-1-chromium-linux.png index 19fcbbdc5b7..f95e2069d7c 100644 Binary files a/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Switches-to-Data-Table-and-checks-the-visuals-1-chromium-linux.png and b/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Switches-to-Data-Table-and-checks-the-visuals-1-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Switches-to-Data-Table-and-checks-the-visuals-2-chromium-linux.png b/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Switches-to-Data-Table-and-checks-the-visuals-2-chromium-linux.png index ff5c6a727de..515682792ff 100644 Binary files a/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Switches-to-Data-Table-and-checks-the-visuals-2-chromium-linux.png and b/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Switches-to-Data-Table-and-checks-the-visuals-2-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Switches-to-Data-Table-and-checks-the-visuals-3-chromium-linux.png b/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Switches-to-Data-Table-and-checks-the-visuals-3-chromium-linux.png index 8695b840426..f88ba16bd12 100644 Binary files a/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Switches-to-Data-Table-and-checks-the-visuals-3-chromium-linux.png and b/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Switches-to-Data-Table-and-checks-the-visuals-3-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Switches-to-Donut-Graph-and-checks-the-visuals-1-chromium-linux.png b/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Switches-to-Donut-Graph-and-checks-the-visuals-1-chromium-linux.png index 8b6fb7dfc07..de3ebd81cd0 100644 Binary files a/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Switches-to-Donut-Graph-and-checks-the-visuals-1-chromium-linux.png and b/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Switches-to-Donut-Graph-and-checks-the-visuals-1-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Switches-to-Donut-Graph-and-checks-the-visuals-2-chromium-linux.png b/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Switches-to-Donut-Graph-and-checks-the-visuals-2-chromium-linux.png index e821884d439..163556249ac 100644 Binary files a/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Switches-to-Donut-Graph-and-checks-the-visuals-2-chromium-linux.png and b/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Switches-to-Donut-Graph-and-checks-the-visuals-2-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Switches-to-Donut-Graph-and-checks-the-visuals-3-chromium-linux.png b/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Switches-to-Donut-Graph-and-checks-the-visuals-3-chromium-linux.png index 986929ddd5d..75342c0ebc7 100644 Binary files a/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Switches-to-Donut-Graph-and-checks-the-visuals-3-chromium-linux.png and b/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Switches-to-Donut-Graph-and-checks-the-visuals-3-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Switches-to-Line-Graph-and-checks-the-visuals-1-chromium-linux.png b/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Switches-to-Line-Graph-and-checks-the-visuals-1-chromium-linux.png index 585f647ab6d..952ce325b76 100644 Binary files a/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Switches-to-Line-Graph-and-checks-the-visuals-1-chromium-linux.png and b/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Switches-to-Line-Graph-and-checks-the-visuals-1-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Switches-to-Line-Graph-and-checks-the-visuals-2-chromium-linux.png b/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Switches-to-Line-Graph-and-checks-the-visuals-2-chromium-linux.png index 6f0f75b9498..43ee73625d3 100644 Binary files a/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Switches-to-Line-Graph-and-checks-the-visuals-2-chromium-linux.png and b/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Switches-to-Line-Graph-and-checks-the-visuals-2-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Switches-to-Line-Graph-and-checks-the-visuals-3-chromium-linux.png b/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Switches-to-Line-Graph-and-checks-the-visuals-3-chromium-linux.png index 31437f9115a..47841b69da4 100644 Binary files a/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Switches-to-Line-Graph-and-checks-the-visuals-3-chromium-linux.png and b/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Switches-to-Line-Graph-and-checks-the-visuals-3-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Validates-that-show-labels-button-shows-the-labels-1-chromium-linux.png b/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Validates-that-show-labels-button-shows-the-labels-1-chromium-linux.png index 4fcb7ed113c..ab4600cc271 100644 Binary files a/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Validates-that-show-labels-button-shows-the-labels-1-chromium-linux.png and b/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Validates-that-show-labels-button-shows-the-labels-1-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Validates-that-show-labels-button-shows-the-labels-2-chromium-linux.png b/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Validates-that-show-labels-button-shows-the-labels-2-chromium-linux.png index 8b26edfde87..be087cd2d72 100644 Binary files a/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Validates-that-show-labels-button-shows-the-labels-2-chromium-linux.png and b/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Validates-that-show-labels-button-shows-the-labels-2-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Validates-that-show-labels-button-shows-the-labels-3-chromium-linux.png b/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Validates-that-show-labels-button-shows-the-labels-3-chromium-linux.png index 29e02b64c07..1e773b48936 100644 Binary files a/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Validates-that-show-labels-button-shows-the-labels-3-chromium-linux.png and b/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Validates-that-show-labels-button-shows-the-labels-3-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Validates-that-show-legend-button-shows-the-legend-side-bar-1-chromium-linux.png b/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Validates-that-show-legend-button-shows-the-legend-side-bar-1-chromium-linux.png index 90de19a59b4..679d6cb70be 100644 Binary files a/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Validates-that-show-legend-button-shows-the-legend-side-bar-1-chromium-linux.png and b/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Validates-that-show-legend-button-shows-the-legend-side-bar-1-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Validates-that-show-legend-button-shows-the-legend-side-bar-2-chromium-linux.png b/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Validates-that-show-legend-button-shows-the-legend-side-bar-2-chromium-linux.png index 3008b23dd03..e7a5d21d406 100644 Binary files a/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Validates-that-show-legend-button-shows-the-legend-side-bar-2-chromium-linux.png and b/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Validates-that-show-legend-button-shows-the-legend-side-bar-2-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Validates-that-show-legend-button-shows-the-legend-side-bar-3-chromium-linux.png b/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Validates-that-show-legend-button-shows-the-legend-side-bar-3-chromium-linux.png index 033344627f2..21349cff419 100644 Binary files a/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Validates-that-show-legend-button-shows-the-legend-side-bar-3-chromium-linux.png and b/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Validates-that-show-legend-button-shows-the-legend-side-bar-3-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Validates-that-show-summary-button-shows-the-summary-1-chromium-linux.png b/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Validates-that-show-summary-button-shows-the-summary-1-chromium-linux.png index 50c6f491487..8d482a07055 100644 Binary files a/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Validates-that-show-summary-button-shows-the-summary-1-chromium-linux.png and b/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Validates-that-show-summary-button-shows-the-summary-1-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Validates-that-show-summary-button-shows-the-summary-2-chromium-linux.png b/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Validates-that-show-summary-button-shows-the-summary-2-chromium-linux.png index a80fcbd03cd..5c2ba0154b6 100644 Binary files a/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Validates-that-show-summary-button-shows-the-summary-2-chromium-linux.png and b/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Validates-that-show-summary-button-shows-the-summary-2-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Validates-that-show-summary-button-shows-the-summary-3-chromium-linux.png b/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Validates-that-show-summary-button-shows-the-summary-3-chromium-linux.png index 239e9ac4f13..f308f9746f7 100644 Binary files a/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Validates-that-show-summary-button-shows-the-summary-3-chromium-linux.png and b/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Validates-that-show-summary-button-shows-the-summary-3-chromium-linux.png differ 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 a2ece8e2033..00c1b8960ad 100644 Binary files a/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-loads-cash-flow-graph-and-checks-visuals-1-chromium-linux.png and b/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-loads-cash-flow-graph-and-checks-visuals-1-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-loads-cash-flow-graph-and-checks-visuals-2-chromium-linux.png b/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-loads-cash-flow-graph-and-checks-visuals-2-chromium-linux.png index 581cb57fcef..96bea65dc0f 100644 Binary files a/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-loads-cash-flow-graph-and-checks-visuals-2-chromium-linux.png and b/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-loads-cash-flow-graph-and-checks-visuals-2-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-loads-cash-flow-graph-and-checks-visuals-3-chromium-linux.png b/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-loads-cash-flow-graph-and-checks-visuals-3-chromium-linux.png index 7319444883a..32496ff33a6 100644 Binary files a/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-loads-cash-flow-graph-and-checks-visuals-3-chromium-linux.png and b/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-loads-cash-flow-graph-and-checks-visuals-3-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-loads-net-worth-and-cash-flow-reports-1-chromium-linux.png b/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-loads-net-worth-and-cash-flow-reports-1-chromium-linux.png index 846d1b279fb..4dcc0d90cdd 100644 Binary files a/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-loads-net-worth-and-cash-flow-reports-1-chromium-linux.png and b/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-loads-net-worth-and-cash-flow-reports-1-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-loads-net-worth-and-cash-flow-reports-2-chromium-linux.png b/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-loads-net-worth-and-cash-flow-reports-2-chromium-linux.png index 0ab2b3e9d33..be2a1b131f1 100644 Binary files a/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-loads-net-worth-and-cash-flow-reports-2-chromium-linux.png and b/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-loads-net-worth-and-cash-flow-reports-2-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-loads-net-worth-and-cash-flow-reports-3-chromium-linux.png b/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-loads-net-worth-and-cash-flow-reports-3-chromium-linux.png index b105e11b4ac..5ffcf45bbe5 100644 Binary files a/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-loads-net-worth-and-cash-flow-reports-3-chromium-linux.png and b/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-loads-net-worth-and-cash-flow-reports-3-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-loads-net-worth-graph-and-checks-visuals-1-chromium-linux.png b/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-loads-net-worth-graph-and-checks-visuals-1-chromium-linux.png index f6185ec8f3a..8f51e4c2606 100644 Binary files a/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-loads-net-worth-graph-and-checks-visuals-1-chromium-linux.png and b/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-loads-net-worth-graph-and-checks-visuals-1-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-loads-net-worth-graph-and-checks-visuals-2-chromium-linux.png b/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-loads-net-worth-graph-and-checks-visuals-2-chromium-linux.png index ea636130ffa..65acd67269a 100644 Binary files a/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-loads-net-worth-graph-and-checks-visuals-2-chromium-linux.png and b/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-loads-net-worth-graph-and-checks-visuals-2-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-loads-net-worth-graph-and-checks-visuals-3-chromium-linux.png b/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-loads-net-worth-graph-and-checks-visuals-3-chromium-linux.png index a821b995cd9..3c30f39ac56 100644 Binary files a/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-loads-net-worth-graph-and-checks-visuals-3-chromium-linux.png and b/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-loads-net-worth-graph-and-checks-visuals-3-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/rules.test.js-snapshots/Rules-checks-the-page-visuals-1-chromium-linux.png b/packages/desktop-client/e2e/rules.test.js-snapshots/Rules-checks-the-page-visuals-1-chromium-linux.png index 100632aedfd..3ad3435f8e2 100644 Binary files a/packages/desktop-client/e2e/rules.test.js-snapshots/Rules-checks-the-page-visuals-1-chromium-linux.png and b/packages/desktop-client/e2e/rules.test.js-snapshots/Rules-checks-the-page-visuals-1-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/rules.test.js-snapshots/Rules-checks-the-page-visuals-2-chromium-linux.png b/packages/desktop-client/e2e/rules.test.js-snapshots/Rules-checks-the-page-visuals-2-chromium-linux.png index ec85acc65d9..97bc032452d 100644 Binary files a/packages/desktop-client/e2e/rules.test.js-snapshots/Rules-checks-the-page-visuals-2-chromium-linux.png and b/packages/desktop-client/e2e/rules.test.js-snapshots/Rules-checks-the-page-visuals-2-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/rules.test.js-snapshots/Rules-checks-the-page-visuals-3-chromium-linux.png b/packages/desktop-client/e2e/rules.test.js-snapshots/Rules-checks-the-page-visuals-3-chromium-linux.png index b10f9430cb1..fbe67464526 100644 Binary files a/packages/desktop-client/e2e/rules.test.js-snapshots/Rules-checks-the-page-visuals-3-chromium-linux.png and b/packages/desktop-client/e2e/rules.test.js-snapshots/Rules-checks-the-page-visuals-3-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/rules.test.js-snapshots/Rules-creates-a-rule-and-makes-sure-it-is-applied-when-creating-a-transaction-1-chromium-linux.png b/packages/desktop-client/e2e/rules.test.js-snapshots/Rules-creates-a-rule-and-makes-sure-it-is-applied-when-creating-a-transaction-1-chromium-linux.png index ec4bbe864ba..6280bf3d0d4 100644 Binary files a/packages/desktop-client/e2e/rules.test.js-snapshots/Rules-creates-a-rule-and-makes-sure-it-is-applied-when-creating-a-transaction-1-chromium-linux.png and b/packages/desktop-client/e2e/rules.test.js-snapshots/Rules-creates-a-rule-and-makes-sure-it-is-applied-when-creating-a-transaction-1-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/rules.test.js-snapshots/Rules-creates-a-rule-and-makes-sure-it-is-applied-when-creating-a-transaction-2-chromium-linux.png b/packages/desktop-client/e2e/rules.test.js-snapshots/Rules-creates-a-rule-and-makes-sure-it-is-applied-when-creating-a-transaction-2-chromium-linux.png index 7e7d4403134..5c94335278d 100644 Binary files a/packages/desktop-client/e2e/rules.test.js-snapshots/Rules-creates-a-rule-and-makes-sure-it-is-applied-when-creating-a-transaction-2-chromium-linux.png and b/packages/desktop-client/e2e/rules.test.js-snapshots/Rules-creates-a-rule-and-makes-sure-it-is-applied-when-creating-a-transaction-2-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/rules.test.js-snapshots/Rules-creates-a-rule-and-makes-sure-it-is-applied-when-creating-a-transaction-3-chromium-linux.png b/packages/desktop-client/e2e/rules.test.js-snapshots/Rules-creates-a-rule-and-makes-sure-it-is-applied-when-creating-a-transaction-3-chromium-linux.png index 08f56d60e5e..0942ab6fc0f 100644 Binary files a/packages/desktop-client/e2e/rules.test.js-snapshots/Rules-creates-a-rule-and-makes-sure-it-is-applied-when-creating-a-transaction-3-chromium-linux.png and b/packages/desktop-client/e2e/rules.test.js-snapshots/Rules-creates-a-rule-and-makes-sure-it-is-applied-when-creating-a-transaction-3-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/rules.test.js-snapshots/Rules-creates-a-rule-and-makes-sure-it-is-applied-when-creating-a-transaction-4-chromium-linux.png b/packages/desktop-client/e2e/rules.test.js-snapshots/Rules-creates-a-rule-and-makes-sure-it-is-applied-when-creating-a-transaction-4-chromium-linux.png index d4f7c7c11c0..c1fbf802415 100644 Binary files a/packages/desktop-client/e2e/rules.test.js-snapshots/Rules-creates-a-rule-and-makes-sure-it-is-applied-when-creating-a-transaction-4-chromium-linux.png and b/packages/desktop-client/e2e/rules.test.js-snapshots/Rules-creates-a-rule-and-makes-sure-it-is-applied-when-creating-a-transaction-4-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/rules.test.js-snapshots/Rules-creates-a-rule-and-makes-sure-it-is-applied-when-creating-a-transaction-5-chromium-linux.png b/packages/desktop-client/e2e/rules.test.js-snapshots/Rules-creates-a-rule-and-makes-sure-it-is-applied-when-creating-a-transaction-5-chromium-linux.png index b38d80cfc76..332e4798508 100644 Binary files a/packages/desktop-client/e2e/rules.test.js-snapshots/Rules-creates-a-rule-and-makes-sure-it-is-applied-when-creating-a-transaction-5-chromium-linux.png and b/packages/desktop-client/e2e/rules.test.js-snapshots/Rules-creates-a-rule-and-makes-sure-it-is-applied-when-creating-a-transaction-5-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/rules.test.js-snapshots/Rules-creates-a-rule-and-makes-sure-it-is-applied-when-creating-a-transaction-6-chromium-linux.png b/packages/desktop-client/e2e/rules.test.js-snapshots/Rules-creates-a-rule-and-makes-sure-it-is-applied-when-creating-a-transaction-6-chromium-linux.png index ea6d25609d6..8ab0059a62a 100644 Binary files a/packages/desktop-client/e2e/rules.test.js-snapshots/Rules-creates-a-rule-and-makes-sure-it-is-applied-when-creating-a-transaction-6-chromium-linux.png and b/packages/desktop-client/e2e/rules.test.js-snapshots/Rules-creates-a-rule-and-makes-sure-it-is-applied-when-creating-a-transaction-6-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/rules.test.js-snapshots/Rules-creates-a-split-transaction-rule-and-makes-sure-it-is-applied-when-creating-a-transaction-1-chromium-linux.png b/packages/desktop-client/e2e/rules.test.js-snapshots/Rules-creates-a-split-transaction-rule-and-makes-sure-it-is-applied-when-creating-a-transaction-1-chromium-linux.png index d2af63c1497..cc0deca56ef 100644 Binary files a/packages/desktop-client/e2e/rules.test.js-snapshots/Rules-creates-a-split-transaction-rule-and-makes-sure-it-is-applied-when-creating-a-transaction-1-chromium-linux.png and b/packages/desktop-client/e2e/rules.test.js-snapshots/Rules-creates-a-split-transaction-rule-and-makes-sure-it-is-applied-when-creating-a-transaction-1-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/rules.test.js-snapshots/Rules-creates-a-split-transaction-rule-and-makes-sure-it-is-applied-when-creating-a-transaction-2-chromium-linux.png b/packages/desktop-client/e2e/rules.test.js-snapshots/Rules-creates-a-split-transaction-rule-and-makes-sure-it-is-applied-when-creating-a-transaction-2-chromium-linux.png index cb5ef2b7ec6..f152f3ae997 100644 Binary files a/packages/desktop-client/e2e/rules.test.js-snapshots/Rules-creates-a-split-transaction-rule-and-makes-sure-it-is-applied-when-creating-a-transaction-2-chromium-linux.png and b/packages/desktop-client/e2e/rules.test.js-snapshots/Rules-creates-a-split-transaction-rule-and-makes-sure-it-is-applied-when-creating-a-transaction-2-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/rules.test.js-snapshots/Rules-creates-a-split-transaction-rule-and-makes-sure-it-is-applied-when-creating-a-transaction-3-chromium-linux.png b/packages/desktop-client/e2e/rules.test.js-snapshots/Rules-creates-a-split-transaction-rule-and-makes-sure-it-is-applied-when-creating-a-transaction-3-chromium-linux.png index c6fc88b0b75..061b63de9aa 100644 Binary files a/packages/desktop-client/e2e/rules.test.js-snapshots/Rules-creates-a-split-transaction-rule-and-makes-sure-it-is-applied-when-creating-a-transaction-3-chromium-linux.png and b/packages/desktop-client/e2e/rules.test.js-snapshots/Rules-creates-a-split-transaction-rule-and-makes-sure-it-is-applied-when-creating-a-transaction-3-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/schedules.test.js-snapshots/Schedules-checks-the-page-visuals-1-chromium-linux.png b/packages/desktop-client/e2e/schedules.test.js-snapshots/Schedules-checks-the-page-visuals-1-chromium-linux.png index a802ba536e4..0c2c6cefc4e 100644 Binary files a/packages/desktop-client/e2e/schedules.test.js-snapshots/Schedules-checks-the-page-visuals-1-chromium-linux.png and b/packages/desktop-client/e2e/schedules.test.js-snapshots/Schedules-checks-the-page-visuals-1-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/schedules.test.js-snapshots/Schedules-checks-the-page-visuals-2-chromium-linux.png b/packages/desktop-client/e2e/schedules.test.js-snapshots/Schedules-checks-the-page-visuals-2-chromium-linux.png index d63beff8ce9..31b1bca4546 100644 Binary files a/packages/desktop-client/e2e/schedules.test.js-snapshots/Schedules-checks-the-page-visuals-2-chromium-linux.png and b/packages/desktop-client/e2e/schedules.test.js-snapshots/Schedules-checks-the-page-visuals-2-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/schedules.test.js-snapshots/Schedules-checks-the-page-visuals-3-chromium-linux.png b/packages/desktop-client/e2e/schedules.test.js-snapshots/Schedules-checks-the-page-visuals-3-chromium-linux.png index df2dc9c2c4c..ade6ced6832 100644 Binary files a/packages/desktop-client/e2e/schedules.test.js-snapshots/Schedules-checks-the-page-visuals-3-chromium-linux.png and b/packages/desktop-client/e2e/schedules.test.js-snapshots/Schedules-checks-the-page-visuals-3-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/schedules.test.js-snapshots/Schedules-creates-a-new-schedule-posts-the-transaction-and-later-completes-it-1-chromium-linux.png b/packages/desktop-client/e2e/schedules.test.js-snapshots/Schedules-creates-a-new-schedule-posts-the-transaction-and-later-completes-it-1-chromium-linux.png index 440f8c3b9f9..4ebe641c72d 100644 Binary files a/packages/desktop-client/e2e/schedules.test.js-snapshots/Schedules-creates-a-new-schedule-posts-the-transaction-and-later-completes-it-1-chromium-linux.png and b/packages/desktop-client/e2e/schedules.test.js-snapshots/Schedules-creates-a-new-schedule-posts-the-transaction-and-later-completes-it-1-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/schedules.test.js-snapshots/Schedules-creates-a-new-schedule-posts-the-transaction-and-later-completes-it-10-chromium-linux.png b/packages/desktop-client/e2e/schedules.test.js-snapshots/Schedules-creates-a-new-schedule-posts-the-transaction-and-later-completes-it-10-chromium-linux.png index d7fdcf4f754..4f1d5f19797 100644 Binary files a/packages/desktop-client/e2e/schedules.test.js-snapshots/Schedules-creates-a-new-schedule-posts-the-transaction-and-later-completes-it-10-chromium-linux.png and b/packages/desktop-client/e2e/schedules.test.js-snapshots/Schedules-creates-a-new-schedule-posts-the-transaction-and-later-completes-it-10-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/schedules.test.js-snapshots/Schedules-creates-a-new-schedule-posts-the-transaction-and-later-completes-it-11-chromium-linux.png b/packages/desktop-client/e2e/schedules.test.js-snapshots/Schedules-creates-a-new-schedule-posts-the-transaction-and-later-completes-it-11-chromium-linux.png index a70a6870cda..e3116aaead0 100644 Binary files a/packages/desktop-client/e2e/schedules.test.js-snapshots/Schedules-creates-a-new-schedule-posts-the-transaction-and-later-completes-it-11-chromium-linux.png and b/packages/desktop-client/e2e/schedules.test.js-snapshots/Schedules-creates-a-new-schedule-posts-the-transaction-and-later-completes-it-11-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/schedules.test.js-snapshots/Schedules-creates-a-new-schedule-posts-the-transaction-and-later-completes-it-12-chromium-linux.png b/packages/desktop-client/e2e/schedules.test.js-snapshots/Schedules-creates-a-new-schedule-posts-the-transaction-and-later-completes-it-12-chromium-linux.png index 11a107a25f3..927394477a1 100644 Binary files a/packages/desktop-client/e2e/schedules.test.js-snapshots/Schedules-creates-a-new-schedule-posts-the-transaction-and-later-completes-it-12-chromium-linux.png and b/packages/desktop-client/e2e/schedules.test.js-snapshots/Schedules-creates-a-new-schedule-posts-the-transaction-and-later-completes-it-12-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/schedules.test.js-snapshots/Schedules-creates-a-new-schedule-posts-the-transaction-and-later-completes-it-2-chromium-linux.png b/packages/desktop-client/e2e/schedules.test.js-snapshots/Schedules-creates-a-new-schedule-posts-the-transaction-and-later-completes-it-2-chromium-linux.png index 92dd17df133..a837d07eeb4 100644 Binary files a/packages/desktop-client/e2e/schedules.test.js-snapshots/Schedules-creates-a-new-schedule-posts-the-transaction-and-later-completes-it-2-chromium-linux.png and b/packages/desktop-client/e2e/schedules.test.js-snapshots/Schedules-creates-a-new-schedule-posts-the-transaction-and-later-completes-it-2-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/schedules.test.js-snapshots/Schedules-creates-a-new-schedule-posts-the-transaction-and-later-completes-it-3-chromium-linux.png b/packages/desktop-client/e2e/schedules.test.js-snapshots/Schedules-creates-a-new-schedule-posts-the-transaction-and-later-completes-it-3-chromium-linux.png index e1c80995e00..4fae2102048 100644 Binary files a/packages/desktop-client/e2e/schedules.test.js-snapshots/Schedules-creates-a-new-schedule-posts-the-transaction-and-later-completes-it-3-chromium-linux.png and b/packages/desktop-client/e2e/schedules.test.js-snapshots/Schedules-creates-a-new-schedule-posts-the-transaction-and-later-completes-it-3-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/schedules.test.js-snapshots/Schedules-creates-a-new-schedule-posts-the-transaction-and-later-completes-it-4-chromium-linux.png b/packages/desktop-client/e2e/schedules.test.js-snapshots/Schedules-creates-a-new-schedule-posts-the-transaction-and-later-completes-it-4-chromium-linux.png index eb14f152e87..e6b57d0f833 100644 Binary files a/packages/desktop-client/e2e/schedules.test.js-snapshots/Schedules-creates-a-new-schedule-posts-the-transaction-and-later-completes-it-4-chromium-linux.png and b/packages/desktop-client/e2e/schedules.test.js-snapshots/Schedules-creates-a-new-schedule-posts-the-transaction-and-later-completes-it-4-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/schedules.test.js-snapshots/Schedules-creates-a-new-schedule-posts-the-transaction-and-later-completes-it-5-chromium-linux.png b/packages/desktop-client/e2e/schedules.test.js-snapshots/Schedules-creates-a-new-schedule-posts-the-transaction-and-later-completes-it-5-chromium-linux.png index 6cee22f5a94..30e51041170 100644 Binary files a/packages/desktop-client/e2e/schedules.test.js-snapshots/Schedules-creates-a-new-schedule-posts-the-transaction-and-later-completes-it-5-chromium-linux.png and b/packages/desktop-client/e2e/schedules.test.js-snapshots/Schedules-creates-a-new-schedule-posts-the-transaction-and-later-completes-it-5-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/schedules.test.js-snapshots/Schedules-creates-a-new-schedule-posts-the-transaction-and-later-completes-it-6-chromium-linux.png b/packages/desktop-client/e2e/schedules.test.js-snapshots/Schedules-creates-a-new-schedule-posts-the-transaction-and-later-completes-it-6-chromium-linux.png index e2254859b7d..c9d5fee0948 100644 Binary files a/packages/desktop-client/e2e/schedules.test.js-snapshots/Schedules-creates-a-new-schedule-posts-the-transaction-and-later-completes-it-6-chromium-linux.png and b/packages/desktop-client/e2e/schedules.test.js-snapshots/Schedules-creates-a-new-schedule-posts-the-transaction-and-later-completes-it-6-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/schedules.test.js-snapshots/Schedules-creates-a-new-schedule-posts-the-transaction-and-later-completes-it-7-chromium-linux.png b/packages/desktop-client/e2e/schedules.test.js-snapshots/Schedules-creates-a-new-schedule-posts-the-transaction-and-later-completes-it-7-chromium-linux.png index 926b7d69301..1414d624b14 100644 Binary files a/packages/desktop-client/e2e/schedules.test.js-snapshots/Schedules-creates-a-new-schedule-posts-the-transaction-and-later-completes-it-7-chromium-linux.png and b/packages/desktop-client/e2e/schedules.test.js-snapshots/Schedules-creates-a-new-schedule-posts-the-transaction-and-later-completes-it-7-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/schedules.test.js-snapshots/Schedules-creates-a-new-schedule-posts-the-transaction-and-later-completes-it-8-chromium-linux.png b/packages/desktop-client/e2e/schedules.test.js-snapshots/Schedules-creates-a-new-schedule-posts-the-transaction-and-later-completes-it-8-chromium-linux.png index d933484319a..6a43aa588b9 100644 Binary files a/packages/desktop-client/e2e/schedules.test.js-snapshots/Schedules-creates-a-new-schedule-posts-the-transaction-and-later-completes-it-8-chromium-linux.png and b/packages/desktop-client/e2e/schedules.test.js-snapshots/Schedules-creates-a-new-schedule-posts-the-transaction-and-later-completes-it-8-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/schedules.test.js-snapshots/Schedules-creates-a-new-schedule-posts-the-transaction-and-later-completes-it-9-chromium-linux.png b/packages/desktop-client/e2e/schedules.test.js-snapshots/Schedules-creates-a-new-schedule-posts-the-transaction-and-later-completes-it-9-chromium-linux.png index 9ec263beb42..3aa8de5ec1d 100644 Binary files a/packages/desktop-client/e2e/schedules.test.js-snapshots/Schedules-creates-a-new-schedule-posts-the-transaction-and-later-completes-it-9-chromium-linux.png and b/packages/desktop-client/e2e/schedules.test.js-snapshots/Schedules-creates-a-new-schedule-posts-the-transaction-and-later-completes-it-9-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/settings.test.js-snapshots/Settings-checks-the-page-visuals-1-chromium-linux.png b/packages/desktop-client/e2e/settings.test.js-snapshots/Settings-checks-the-page-visuals-1-chromium-linux.png index 6edcc155e8c..f4ebfe318ad 100644 Binary files a/packages/desktop-client/e2e/settings.test.js-snapshots/Settings-checks-the-page-visuals-1-chromium-linux.png and b/packages/desktop-client/e2e/settings.test.js-snapshots/Settings-checks-the-page-visuals-1-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/settings.test.js-snapshots/Settings-checks-the-page-visuals-2-chromium-linux.png b/packages/desktop-client/e2e/settings.test.js-snapshots/Settings-checks-the-page-visuals-2-chromium-linux.png index 504c1144e80..d95e18307da 100644 Binary files a/packages/desktop-client/e2e/settings.test.js-snapshots/Settings-checks-the-page-visuals-2-chromium-linux.png and b/packages/desktop-client/e2e/settings.test.js-snapshots/Settings-checks-the-page-visuals-2-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/settings.test.js-snapshots/Settings-checks-the-page-visuals-3-chromium-linux.png b/packages/desktop-client/e2e/settings.test.js-snapshots/Settings-checks-the-page-visuals-3-chromium-linux.png index 2346fd83acb..8d85b9cf4c3 100644 Binary files a/packages/desktop-client/e2e/settings.test.js-snapshots/Settings-checks-the-page-visuals-3-chromium-linux.png and b/packages/desktop-client/e2e/settings.test.js-snapshots/Settings-checks-the-page-visuals-3-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-checks-the-page-visuals-1-chromium-linux.png b/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-checks-the-page-visuals-1-chromium-linux.png index e0b5ad7ba73..775c421f21e 100644 Binary files a/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-checks-the-page-visuals-1-chromium-linux.png and b/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-checks-the-page-visuals-1-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-checks-the-page-visuals-2-chromium-linux.png b/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-checks-the-page-visuals-2-chromium-linux.png index 1564f138dfe..be3da6d9975 100644 Binary files a/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-checks-the-page-visuals-2-chromium-linux.png and b/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-checks-the-page-visuals-2-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-checks-the-page-visuals-3-chromium-linux.png b/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-checks-the-page-visuals-3-chromium-linux.png index e4bf23f4cea..25c668e40c8 100644 Binary files a/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-checks-the-page-visuals-3-chromium-linux.png and b/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-checks-the-page-visuals-3-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-creates-a-split-test-transaction-1-chromium-linux.png b/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-creates-a-split-test-transaction-1-chromium-linux.png index f9dddffc720..504a8e3d15c 100644 Binary files a/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-creates-a-split-test-transaction-1-chromium-linux.png and b/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-creates-a-split-test-transaction-1-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-creates-a-split-test-transaction-2-chromium-linux.png b/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-creates-a-split-test-transaction-2-chromium-linux.png index f7ebc27b65b..3f8ecaeaf05 100644 Binary files a/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-creates-a-split-test-transaction-2-chromium-linux.png and b/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-creates-a-split-test-transaction-2-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-creates-a-split-test-transaction-3-chromium-linux.png b/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-creates-a-split-test-transaction-3-chromium-linux.png index 0c90ff92002..d80a3a0e021 100644 Binary files a/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-creates-a-split-test-transaction-3-chromium-linux.png and b/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-creates-a-split-test-transaction-3-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-creates-a-test-transaction-1-chromium-linux.png b/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-creates-a-test-transaction-1-chromium-linux.png index 32a731e7f71..f7a164b9efe 100644 Binary files a/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-creates-a-test-transaction-1-chromium-linux.png and b/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-creates-a-test-transaction-1-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-creates-a-test-transaction-2-chromium-linux.png b/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-creates-a-test-transaction-2-chromium-linux.png index 7847cdf91a7..77660b29aef 100644 Binary files a/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-creates-a-test-transaction-2-chromium-linux.png and b/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-creates-a-test-transaction-2-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-creates-a-test-transaction-3-chromium-linux.png b/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-creates-a-test-transaction-3-chromium-linux.png index f3f7c7287cb..7555f697792 100644 Binary files a/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-creates-a-test-transaction-3-chromium-linux.png and b/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-creates-a-test-transaction-3-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-creates-a-transfer-test-transaction-1-chromium-linux.png b/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-creates-a-transfer-test-transaction-1-chromium-linux.png index 47e323e93ee..1da49e54cb2 100644 Binary files a/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-creates-a-transfer-test-transaction-1-chromium-linux.png and b/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-creates-a-transfer-test-transaction-1-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-creates-a-transfer-test-transaction-2-chromium-linux.png b/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-creates-a-transfer-test-transaction-2-chromium-linux.png index d4da73c7c68..eb2ccd30916 100644 Binary files a/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-creates-a-transfer-test-transaction-2-chromium-linux.png and b/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-creates-a-transfer-test-transaction-2-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-creates-a-transfer-test-transaction-3-chromium-linux.png b/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-creates-a-transfer-test-transaction-3-chromium-linux.png index 86e303f6570..ff47298651c 100644 Binary files a/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-creates-a-transfer-test-transaction-3-chromium-linux.png and b/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-creates-a-transfer-test-transaction-3-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-creates-a-transfer-test-transaction-4-chromium-linux.png b/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-creates-a-transfer-test-transaction-4-chromium-linux.png index edda274f959..ea71505e978 100644 Binary files a/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-creates-a-transfer-test-transaction-4-chromium-linux.png and b/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-creates-a-transfer-test-transaction-4-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-creates-a-transfer-test-transaction-5-chromium-linux.png b/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-creates-a-transfer-test-transaction-5-chromium-linux.png index 0a866fb7c61..1f3556eb858 100644 Binary files a/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-creates-a-transfer-test-transaction-5-chromium-linux.png and b/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-creates-a-transfer-test-transaction-5-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-creates-a-transfer-test-transaction-6-chromium-linux.png b/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-creates-a-transfer-test-transaction-6-chromium-linux.png index 790d47d454e..23e7cdad42f 100644 Binary files a/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-creates-a-transfer-test-transaction-6-chromium-linux.png and b/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-creates-a-transfer-test-transaction-6-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-filters-transactions-by-category-7-chromium-linux.png b/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-filters-transactions-by-category-7-chromium-linux.png index d9cadc56156..b5f64d82e34 100644 Binary files a/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-filters-transactions-by-category-7-chromium-linux.png and b/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-filters-transactions-by-category-7-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-filters-transactions-by-category-8-chromium-linux.png b/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-filters-transactions-by-category-8-chromium-linux.png index d1f12b017ab..7f0366ddc05 100644 Binary files a/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-filters-transactions-by-category-8-chromium-linux.png and b/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-filters-transactions-by-category-8-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-filters-transactions-by-category-9-chromium-linux.png b/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-filters-transactions-by-category-9-chromium-linux.png index 2106111fbb4..ec3895063d3 100644 Binary files a/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-filters-transactions-by-category-9-chromium-linux.png and b/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-filters-transactions-by-category-9-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-filters-transactions-by-date-7-chromium-linux.png b/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-filters-transactions-by-date-7-chromium-linux.png index a0627091062..8e3c1155e89 100644 Binary files a/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-filters-transactions-by-date-7-chromium-linux.png and b/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-filters-transactions-by-date-7-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-filters-transactions-by-date-8-chromium-linux.png b/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-filters-transactions-by-date-8-chromium-linux.png index ff672703b3c..d12a6f8f4fd 100644 Binary files a/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-filters-transactions-by-date-8-chromium-linux.png and b/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-filters-transactions-by-date-8-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-filters-transactions-by-date-9-chromium-linux.png b/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-filters-transactions-by-date-9-chromium-linux.png index f099ce4a711..71526c8ebcb 100644 Binary files a/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-filters-transactions-by-date-9-chromium-linux.png and b/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-filters-transactions-by-date-9-chromium-linux.png differ diff --git a/packages/desktop-client/src/components/LoggedInUser.tsx b/packages/desktop-client/src/components/LoggedInUser.tsx index 8ad160f2294..bba3b2e885a 100644 --- a/packages/desktop-client/src/components/LoggedInUser.tsx +++ b/packages/desktop-client/src/components/LoggedInUser.tsx @@ -1,11 +1,11 @@ // @ts-strict-ignore import React, { useState, useEffect, useRef, type CSSProperties } from 'react'; import { Trans, useTranslation } from 'react-i18next'; -import { useSelector } from 'react-redux'; +import { useDispatch, useSelector } from 'react-redux'; +import { closeBudget, getUserData, signOut } from 'loot-core/client/actions'; import { type State } from 'loot-core/src/client/state-types'; -import { useActions } from '../hooks/useActions'; import { useNavigate } from '../hooks/useNavigate'; import { theme, styles } from '../style'; @@ -27,22 +27,28 @@ export function LoggedInUser({ color, }: LoggedInUserProps) { const { t } = useTranslation(); - + const dispatch = useDispatch(); + const navigate = useNavigate(); const userData = useSelector((state: State) => state.user.data); - const { getUserData, signOut, closeBudget } = useActions(); const [loading, setLoading] = useState(true); const [menuOpen, setMenuOpen] = useState(false); const serverUrl = useServerURL(); const triggerRef = useRef(null); useEffect(() => { - getUserData().then(() => setLoading(false)); + async function init() { + await dispatch(getUserData()); + } + + init().then(() => setLoading(false)); }, []); - const navigate = useNavigate(); + async function onCloseBudget() { + await dispatch(closeBudget()); + } async function onChangePassword() { - await closeBudget(); + await onCloseBudget(); navigate('/change-password'); } @@ -54,14 +60,14 @@ export function LoggedInUser({ onChangePassword(); break; case 'sign-in': - await closeBudget(); + await onCloseBudget(); navigate('/login'); break; case 'sign-out': - signOut(); + dispatch(signOut()); break; case 'config-server': - await closeBudget(); + await onCloseBudget(); navigate('/config-server'); break; default: diff --git a/packages/desktop-client/src/components/Modals.tsx b/packages/desktop-client/src/components/Modals.tsx index a52f6fef71d..58ba6f051f4 100644 --- a/packages/desktop-client/src/components/Modals.tsx +++ b/packages/desktop-client/src/components/Modals.tsx @@ -45,6 +45,7 @@ import { KeyboardShortcutModal } from './modals/KeyboardShortcutModal'; import { LoadBackupModal } from './modals/LoadBackupModal'; import { ConfirmChangeDocumentDirModal } from './modals/manager/ConfirmChangeDocumentDir'; import { DeleteFileModal } from './modals/manager/DeleteFileModal'; +import { DuplicateFileModal } from './modals/manager/DuplicateFileModal'; import { FilesSettingsModal } from './modals/manager/FilesSettingsModal'; import { ImportActualModal } from './modals/manager/ImportActualModal'; import { ImportModal } from './modals/manager/ImportModal'; @@ -586,6 +587,16 @@ export function Modals() { return ; case 'delete-budget': return ; + case 'duplicate-budget': + return ( + + ); case 'import': return ; case 'files-settings': diff --git a/packages/desktop-client/src/components/Titlebar.tsx b/packages/desktop-client/src/components/Titlebar.tsx index d430b8ceaaf..503d72a847f 100644 --- a/packages/desktop-client/src/components/Titlebar.tsx +++ b/packages/desktop-client/src/components/Titlebar.tsx @@ -1,10 +1,12 @@ import React, { useState, useEffect, type CSSProperties } from 'react'; import { useHotkeys } from 'react-hotkeys-hook'; +import { useDispatch } from 'react-redux'; import { Routes, Route, useLocation } from 'react-router-dom'; import { css } from '@emotion/css'; import { t } from 'i18next'; +import { sync } from 'loot-core/client/actions'; import * as Platform from 'loot-core/src/client/platform'; import * as queries from 'loot-core/src/client/queries'; import { listen } from 'loot-core/src/platform/client/fetch'; @@ -13,7 +15,6 @@ import { isElectron, } from 'loot-core/src/shared/environment'; -import { useActions } from '../hooks/useActions'; import { useGlobalPref } from '../hooks/useGlobalPref'; import { useMetadataPref } from '../hooks/useMetadataPref'; import { useNavigate } from '../hooks/useNavigate'; @@ -108,8 +109,7 @@ type SyncButtonProps = { }; function SyncButton({ style, isMobile = false }: SyncButtonProps) { const [cloudFileId] = useMetadataPref('cloudFileId'); - const { sync } = useActions(); - + const dispatch = useDispatch(); const [syncing, setSyncing] = useState(false); const [syncState, setSyncState] = useState< null | 'offline' | 'local' | 'disabled' | 'error' @@ -193,15 +193,17 @@ function SyncButton({ style, isMobile = false }: SyncButtonProps) { marginRight: 5, }; + const onSync = () => dispatch(sync()); + useHotkeys( 'ctrl+s, cmd+s, meta+s', - sync, + onSync, { enableOnFormTags: true, preventDefault: true, scopes: ['app'], }, - [sync], + [onSync], ); return ( @@ -223,7 +225,7 @@ function SyncButton({ style, isMobile = false }: SyncButtonProps) { '&[data-hovered]': hoveredStyle, '&[data-pressed]': activeStyle, })} - onPress={sync} + onPress={onSync} > {isMobile ? ( syncState === 'error' ? ( diff --git a/packages/desktop-client/src/components/UpdateNotification.tsx b/packages/desktop-client/src/components/UpdateNotification.tsx index acbb6ea2382..809ff797810 100644 --- a/packages/desktop-client/src/components/UpdateNotification.tsx +++ b/packages/desktop-client/src/components/UpdateNotification.tsx @@ -1,10 +1,10 @@ import React from 'react'; import { useTranslation } from 'react-i18next'; -import { useSelector } from 'react-redux'; +import { useSelector, useDispatch } from 'react-redux'; +import { setAppState, updateApp } from 'loot-core/client/actions'; import { type State } from 'loot-core/src/client/state-types'; -import { useActions } from '../hooks/useActions'; import { SvgClose } from '../icons/v1'; import { theme } from '../style'; @@ -20,7 +20,10 @@ export function UpdateNotification() { (state: State) => state.app.showUpdateNotification, ); - const { updateApp, setAppState } = useActions(); + const dispatch = useDispatch(); + const onRestart = () => { + dispatch(updateApp()); + }; if (updateInfo && showUpdateNotification) { const notes = updateInfo.releaseNotes; @@ -51,7 +54,7 @@ export function UpdateNotification() { { // Set a flag to never show an update notification again for this session - setAppState({ - updateInfo: null, - showUpdateNotification: false, - }); + dispatch( + setAppState({ + updateInfo: null, + showUpdateNotification: false, + }), + ); }} > void; @@ -841,8 +841,8 @@ class AccountInternal extends PureComponent< } if (!account) { - if (id === 'budgeted') { - return t('Budgeted Accounts'); + if (id === 'onbudget') { + return t('On Budget Accounts'); } else if (id === 'offbudget') { return t('Off Budget Accounts'); } else if (id === 'uncategorized') { @@ -1662,7 +1662,7 @@ class AccountInternal extends PureComponent< const isNameEditable = accountId && - accountId !== 'budgeted' && + accountId !== 'onbudget' && accountId !== 'offbudget' && accountId !== 'uncategorized'; @@ -1767,7 +1767,7 @@ class AccountInternal extends PureComponent< showAccount={ !accountId || accountId === 'offbudget' || - accountId === 'budgeted' || + accountId === 'onbudget' || accountId === 'uncategorized' } isAdding={this.state.isAdding} diff --git a/packages/desktop-client/src/components/autocomplete/AccountAutocomplete.tsx b/packages/desktop-client/src/components/autocomplete/AccountAutocomplete.tsx index e403042878c..3a50f424e23 100644 --- a/packages/desktop-client/src/components/autocomplete/AccountAutocomplete.tsx +++ b/packages/desktop-client/src/components/autocomplete/AccountAutocomplete.tsx @@ -68,8 +68,8 @@ function AccountList({ item.closed ? t('Closed Accounts') : item.offbudget - ? t('Off Budget') - : t('For Budget') + ? t('Off budget') + : t('On budget') }`; lastItem = item; diff --git a/packages/desktop-client/src/components/budget/BudgetTable.jsx b/packages/desktop-client/src/components/budget/BudgetTable.tsx similarity index 65% rename from packages/desktop-client/src/components/budget/BudgetTable.jsx rename to packages/desktop-client/src/components/budget/BudgetTable.tsx index 785457f2ab5..66481fd64af 100644 --- a/packages/desktop-client/src/components/budget/BudgetTable.jsx +++ b/packages/desktop-client/src/components/budget/BudgetTable.tsx @@ -1,14 +1,24 @@ -import React, { useState } from 'react'; +import React, { + type ComponentPropsWithoutRef, + type KeyboardEvent, + useState, +} from 'react'; + +import { + type CategoryEntity, + type CategoryGroupEntity, +} from 'loot-core/types/models'; import { useCategories } from '../../hooks/useCategories'; import { useLocalPref } from '../../hooks/useLocalPref'; import { theme, styles } from '../../style'; import { View } from '../common/View'; +import { type DropPosition } from '../sort'; import { BudgetCategories } from './BudgetCategories'; import { BudgetSummaries } from './BudgetSummaries'; import { BudgetTotals } from './BudgetTotals'; -import { MonthsProvider } from './MonthsContext'; +import { type MonthBounds, MonthsProvider } from './MonthsContext'; import { findSortDown, findSortUp, @@ -16,7 +26,39 @@ import { separateGroups, } from './util'; -export function BudgetTable(props) { +type BudgetTableProps = { + type: string; + prewarmStartMonth: string; + startMonth: string; + numMonths: number; + monthBounds: MonthBounds; + dataComponents: { + SummaryComponent: ComponentPropsWithoutRef< + typeof BudgetSummaries + >['SummaryComponent']; + BudgetTotalsComponent: ComponentPropsWithoutRef< + typeof BudgetTotals + >['MonthComponent']; + }; + onSaveCategory: (category: CategoryEntity) => void; + onDeleteCategory: (id: CategoryEntity['id']) => void; + onSaveGroup: (group: CategoryGroupEntity) => void; + onDeleteGroup: (id: CategoryGroupEntity['id']) => void; + onApplyBudgetTemplatesInGroup: (groupId: CategoryGroupEntity['id']) => void; + onReorderCategory: (params: { + id: CategoryEntity['id']; + groupId?: CategoryGroupEntity['id']; + targetId: CategoryEntity['id'] | null; + }) => void; + onReorderGroup: (params: { + id: CategoryGroupEntity['id']; + targetId: CategoryEntity['id'] | null; + }) => void; + onShowActivity: (id: CategoryEntity['id'], month?: string) => void; + onBudgetAction: (month: string, type: string, args: unknown) => void; +}; + +export function BudgetTable(props: BudgetTableProps) { const { type, prewarmStartMonth, @@ -35,23 +77,29 @@ export function BudgetTable(props) { onBudgetAction, } = props; - const { grouped: categoryGroups } = useCategories(); + const { grouped: categoryGroups = [] } = useCategories(); const [collapsedGroupIds = [], setCollapsedGroupIdsPref] = useLocalPref('budget.collapsed'); const [showHiddenCategories, setShowHiddenCategoriesPef] = useLocalPref( 'budget.showHiddenCategories', ); - const [editing, setEditing] = useState(null); + const [editing, setEditing] = useState<{ id: string; cell: string } | null>( + null, + ); - const onEditMonth = (id, month) => { + const onEditMonth = (id: string, month: string) => { setEditing(id ? { id, cell: month } : null); }; - const onEditName = id => { + const onEditName = (id: string) => { setEditing(id ? { id, cell: 'name' } : null); }; - const _onReorderCategory = (id, dropPos, targetId) => { + const _onReorderCategory = ( + id: string, + dropPos: DropPosition, + targetId: string, + ) => { const isGroup = !!categoryGroups.find(g => g.id === targetId); if (isGroup) { @@ -63,7 +111,7 @@ export function BudgetTable(props) { const group = categoryGroups.find(g => g.id === groupId); if (group) { - const { categories } = group; + const { categories = [] } = group; onReorderCategory({ id, groupId: group.id, @@ -77,7 +125,7 @@ export function BudgetTable(props) { let targetGroup; for (const group of categoryGroups) { - if (group.categories.find(cat => cat.id === targetId)) { + if (group.categories?.find(cat => cat.id === targetId)) { targetGroup = group; break; } @@ -85,13 +133,17 @@ export function BudgetTable(props) { onReorderCategory({ id, - groupId: targetGroup.id, - ...findSortDown(targetGroup.categories, dropPos, targetId), + groupId: targetGroup?.id, + ...findSortDown(targetGroup?.categories || [], dropPos, targetId), }); } }; - const _onReorderGroup = (id, dropPos, targetId) => { + const _onReorderGroup = ( + id: string, + dropPos: DropPosition, + targetId: string, + ) => { const [expenseGroups] = separateGroups(categoryGroups); // exclude Income group from sortable groups to fix off-by-one error onReorderGroup({ id, @@ -99,13 +151,21 @@ export function BudgetTable(props) { }); }; - const moveVertically = dir => { - const flattened = categoryGroups.reduce((all, group) => { - if (collapsedGroupIds.includes(group.id)) { - return all.concat({ id: group.id, isGroup: true }); - } - return all.concat([{ id: group.id, isGroup: true }, ...group.categories]); - }, []); + const moveVertically = (dir: 1 | -1) => { + const flattened = categoryGroups.reduce( + (all, group) => { + if (collapsedGroupIds.includes(group.id)) { + return all.concat({ id: group.id, isGroup: true }); + } + return all.concat([ + { id: group.id, isGroup: true }, + ...(group?.categories || []), + ]); + }, + [] as Array< + { id: CategoryGroupEntity['id']; isGroup: boolean } | CategoryEntity + >, + ); if (editing) { const idx = flattened.findIndex(item => item.id === editing.id); @@ -114,10 +174,13 @@ export function BudgetTable(props) { while (nextIdx >= 0 && nextIdx < flattened.length) { const next = flattened[nextIdx]; - if (next.isGroup) { + if ('isGroup' in next && next.isGroup) { nextIdx += dir; continue; - } else if (type === 'report' || !next.is_income) { + } else if ( + type === 'report' || + ('is_income' in next && !next.is_income) + ) { onEditMonth(next.id, editing.cell); return; } else { @@ -127,7 +190,7 @@ export function BudgetTable(props) { } }; - const onKeyDown = e => { + const onKeyDown = (e: KeyboardEvent) => { if (!editing) { return null; } @@ -138,7 +201,7 @@ export function BudgetTable(props) { } }; - const onCollapse = collapsedIds => { + const onCollapse = (collapsedIds: string[]) => { setCollapsedGroupIdsPref(collapsedIds); }; @@ -223,6 +286,7 @@ export function BudgetTable(props) { onKeyDown={onKeyDown} > ; +type DynamicBudgetTableProps = Omit< + ComponentProps, + 'numMonths' +> & { + maxMonths: number; + onMonthSelect: (month: string, numMonths: number) => void; +}; export const DynamicBudgetTable = (props: DynamicBudgetTableProps) => { return ( diff --git a/packages/desktop-client/src/components/budget/MonthPicker.tsx b/packages/desktop-client/src/components/budget/MonthPicker.tsx index 501bafd79ff..66f44423871 100644 --- a/packages/desktop-client/src/components/budget/MonthPicker.tsx +++ b/packages/desktop-client/src/components/budget/MonthPicker.tsx @@ -7,12 +7,12 @@ import { useResizeObserver } from '../../hooks/useResizeObserver'; import { styles, theme } from '../../style'; import { View } from '../common/View'; -import { type BoundsProps } from './MonthsContext'; +import { type MonthBounds } from './MonthsContext'; type MonthPickerProps = { startMonth: string; numDisplayed: number; - monthBounds: BoundsProps; + monthBounds: MonthBounds; style: CSSProperties; onSelect: (month: string) => void; }; diff --git a/packages/desktop-client/src/components/budget/MonthsContext.tsx b/packages/desktop-client/src/components/budget/MonthsContext.tsx index 2d5e377402a..dad9b264028 100644 --- a/packages/desktop-client/src/components/budget/MonthsContext.tsx +++ b/packages/desktop-client/src/components/budget/MonthsContext.tsx @@ -3,13 +3,13 @@ import React, { createContext, type ReactNode } from 'react'; import * as monthUtils from 'loot-core/src/shared/months'; -export type BoundsProps = { +export type MonthBounds = { start: string; end: string; }; export function getValidMonthBounds( - bounds: BoundsProps, + bounds: MonthBounds, startMonth: undefined | string, endMonth: string, ) { @@ -29,7 +29,7 @@ export const MonthsContext = createContext(null); type MonthsProviderProps = { startMonth: string | undefined; numMonths: number; - monthBounds: BoundsProps; + monthBounds: MonthBounds; type: string; children: ReactNode; }; diff --git a/packages/desktop-client/src/components/budget/SidebarCategory.tsx b/packages/desktop-client/src/components/budget/SidebarCategory.tsx index 4d3b53e7a04..bdd3f55a54d 100644 --- a/packages/desktop-client/src/components/budget/SidebarCategory.tsx +++ b/packages/desktop-client/src/components/budget/SidebarCategory.tsx @@ -1,5 +1,5 @@ // @ts-strict-ignore -import React, { type CSSProperties, type Ref, useRef, useState } from 'react'; +import React, { type CSSProperties, type Ref, useRef } from 'react'; import { useTranslation } from 'react-i18next'; import { @@ -7,7 +7,7 @@ import { type CategoryEntity, } from 'loot-core/src/types/models'; -import { useFeatureFlag } from '../../hooks/useFeatureFlag'; +import { useContextMenu } from '../../hooks/useContextMenu'; import { SvgCheveronDown } from '../../icons/v1'; import { theme } from '../../style'; import { Button } from '../common/Button2'; @@ -50,9 +50,9 @@ export function SidebarCategory({ const { t } = useTranslation(); const temporary = category.id === 'new'; - const [menuOpen, setMenuOpen] = useState(false); + const { setMenuOpen, menuOpen, handleContextMenu, resetPosition, position } = + useContextMenu(); const triggerRef = useRef(null); - const contextMenusEnabled = useFeatureFlag('contextMenus'); const displayed = ( { - if (!contextMenusEnabled) return; - e.preventDefault(); - setMenuOpen(true); - }} + onContextMenu={handleContextMenu} >
setMenuOpen(true)} + onPress={() => { + resetPosition(); + setMenuOpen(true); + }} > setMenuOpen(false)} - style={{ width: 200 }} + style={{ width: 200, margin: 1 }} isNonModal + {...position} > { diff --git a/packages/desktop-client/src/components/budget/SidebarGroup.tsx b/packages/desktop-client/src/components/budget/SidebarGroup.tsx index 20a81eba986..87e6eaad4d2 100644 --- a/packages/desktop-client/src/components/budget/SidebarGroup.tsx +++ b/packages/desktop-client/src/components/budget/SidebarGroup.tsx @@ -1,8 +1,9 @@ // @ts-strict-ignore -import React, { type CSSProperties, useRef, useState } from 'react'; +import React, { type CSSProperties, useRef } from 'react'; import { type ConnectDragSource } from 'react-dnd'; import { useTranslation } from 'react-i18next'; +import { useContextMenu } from '../../hooks/useContextMenu'; import { useFeatureFlag } from '../../hooks/useFeatureFlag'; import { SvgExpandArrow } from '../../icons/v0'; import { SvgCheveronDown } from '../../icons/v1'; @@ -58,9 +59,9 @@ export function SidebarGroup({ const isGoalTemplatesEnabled = useFeatureFlag('goalTemplatesEnabled'); const temporary = group.id === 'new'; - const [menuOpen, setMenuOpen] = useState(false); + const { setMenuOpen, menuOpen, handleContextMenu, resetPosition, position } = + useContextMenu(); const triggerRef = useRef(null); - const contextMenusEnabled = useFeatureFlag('contextMenus'); const displayed = ( { onToggleCollapse(group.id); }} - onContextMenu={e => { - if (!contextMenusEnabled) return; - e.preventDefault(); - setMenuOpen(true); - }} + onContextMenu={handleContextMenu} > {!dragPreview && ( setMenuOpen(true)} + onPress={() => { + resetPosition(); + setMenuOpen(true); + }} style={{ padding: 3 }} > @@ -122,8 +122,9 @@ export function SidebarGroup({ placement="bottom start" isOpen={menuOpen} onOpenChange={() => setMenuOpen(false)} - style={{ width: 200 }} + style={{ width: 200, margin: 1 }} isNonModal + {...position} > { diff --git a/packages/desktop-client/src/components/budget/envelope/EnvelopeBudgetComponents.tsx b/packages/desktop-client/src/components/budget/envelope/EnvelopeBudgetComponents.tsx index 78a42e0fd8d..0bb0491a2d8 100644 --- a/packages/desktop-client/src/components/budget/envelope/EnvelopeBudgetComponents.tsx +++ b/packages/desktop-client/src/components/budget/envelope/EnvelopeBudgetComponents.tsx @@ -3,7 +3,6 @@ import React, { type CSSProperties, memo, useRef, - useState, } from 'react'; import { useTranslation, Trans } from 'react-i18next'; @@ -14,7 +13,7 @@ import { evalArithmetic } from 'loot-core/src/shared/arithmetic'; import * as monthUtils from 'loot-core/src/shared/months'; import { integerToCurrency, amountToInteger } from 'loot-core/src/shared/util'; -import { useFeatureFlag } from '../../../hooks/useFeatureFlag'; +import { useContextMenu } from '../../../hooks/useContextMenu'; import { useUndo } from '../../../hooks/useUndo'; import { SvgCheveronDown } from '../../../icons/v1'; import { styles, theme } from '../../../style'; @@ -207,8 +206,20 @@ export const ExpenseCategoryMonth = memo(function ExpenseCategoryMonth({ const budgetMenuTriggerRef = useRef(null); const balanceMenuTriggerRef = useRef(null); - const [budgetMenuOpen, setBudgetMenuOpen] = useState(false); - const [balanceMenuOpen, setBalanceMenuOpen] = useState(false); + const { + setMenuOpen: setBudgetMenuOpen, + menuOpen: budgetMenuOpen, + handleContextMenu: handleBudgetContextMenu, + resetPosition: resetBudgetPosition, + position: budgetPosition, + } = useContextMenu(); + const { + setMenuOpen: setBalanceMenuOpen, + menuOpen: balanceMenuOpen, + handleContextMenu: handleBalanceContextMenu, + resetPosition: resetBalancePosition, + position: balancePosition, + } = useContextMenu(); const onMenuAction = (...args: Parameters) => { onBudgetAction(...args); @@ -216,7 +227,6 @@ export const ExpenseCategoryMonth = memo(function ExpenseCategoryMonth({ }; const { showUndoNotification } = useUndo(); - const contextMenusEnabled = useFeatureFlag('contextMenus'); return ( { - if (!contextMenusEnabled) return; if (editing) return; - e.preventDefault(); - setBudgetMenuOpen(true); + handleBudgetContextMenu(e); }} > {!editing && ( @@ -261,9 +270,11 @@ export const ExpenseCategoryMonth = memo(function ExpenseCategoryMonth({ }} >
diff --git a/packages/desktop-client/src/components/filters/FiltersMenu.jsx b/packages/desktop-client/src/components/filters/FiltersMenu.jsx index c6ec6b583d7..17ce8e7a451 100644 --- a/packages/desktop-client/src/components/filters/FiltersMenu.jsx +++ b/packages/desktop-client/src/components/filters/FiltersMenu.jsx @@ -117,8 +117,18 @@ function ConfigureField({ }} /> ) : ( - titleFirst(mapField(field)) + + {titleFirst(mapField(field))} + )} + @@ -222,6 +232,7 @@ function ConfigureField({ } value={value} multi={op === 'oneOf' || op === 'notOneOf'} + op={op} style={{ marginTop: 10 }} onChange={v => { dispatch({ type: 'set-value', value: v }); diff --git a/packages/desktop-client/src/components/filters/updateFilterReducer.ts b/packages/desktop-client/src/components/filters/updateFilterReducer.ts index 97721973312..3b8d92b67b2 100644 --- a/packages/desktop-client/src/components/filters/updateFilterReducer.ts +++ b/packages/desktop-client/src/components/filters/updateFilterReducer.ts @@ -19,7 +19,9 @@ export function updateFilterReducer( action.op === 'is' || action.op === 'doesNotContain' || action.op === 'isNot' || - action.op === 'hasTags') + action.op === 'hasTags' || + action.op === 'onBudget' || + action.op === 'offBudget') ) { // Clear out the value if switching between contains or // is/oneof for the id or string type diff --git a/packages/desktop-client/src/components/manager/BudgetList.tsx b/packages/desktop-client/src/components/manager/BudgetList.tsx index 72a3ebab0c0..12defd2e4dd 100644 --- a/packages/desktop-client/src/components/manager/BudgetList.tsx +++ b/packages/desktop-client/src/components/manager/BudgetList.tsx @@ -64,9 +64,11 @@ function getFileDescription(file: File, t: (key: string) => string) { function FileMenu({ onDelete, onClose, + onDuplicate, }: { onDelete: () => void; onClose: () => void; + onDuplicate?: () => void; }) { function onMenuSelect(type: string) { onClose(); @@ -75,18 +77,30 @@ function FileMenu({ case 'delete': onDelete(); break; + case 'duplicate': + if (onDuplicate) onDuplicate(); + break; default: } } const { t } = useTranslation(); - const items = [{ name: 'delete', text: t('Delete') }]; + const items = [ + ...(onDuplicate ? [{ name: 'duplicate', text: t('Duplicate') }] : []), + { name: 'delete', text: t('Delete') }, + ]; return ; } -function FileMenuButton({ onDelete }: { onDelete: () => void }) { +function FileMenuButton({ + onDelete, + onDuplicate, +}: { + onDelete: () => void; + onDuplicate?: () => void; +}) { const triggerRef = useRef(null); const [menuOpen, setMenuOpen] = useState(false); @@ -108,7 +122,11 @@ function FileMenuButton({ onDelete }: { onDelete: () => void }) { isOpen={menuOpen} onOpenChange={() => setMenuOpen(false)} > - setMenuOpen(false)} /> + setMenuOpen(false)} + onDuplicate={onDuplicate} + /> ); @@ -169,11 +187,13 @@ function FileItem({ quickSwitchMode, onSelect, onDelete, + onDuplicate, }: { file: File; quickSwitchMode: boolean; onSelect: (file: File) => void; onDelete: (file: File) => void; + onDuplicate: (file: File) => void; }) { const { t } = useTranslation(); @@ -239,7 +259,10 @@ function FileItem({ )} {!quickSwitchMode && ( - onDelete(file)} /> + onDelete(file)} + onDuplicate={'id' in file ? () => onDuplicate(file) : undefined} + /> )} @@ -252,11 +275,13 @@ function BudgetFiles({ quickSwitchMode, onSelect, onDelete, + onDuplicate, }: { files: File[]; quickSwitchMode: boolean; onSelect: (file: File) => void; onDelete: (file: File) => void; + onDuplicate: (file: File) => void; }) { function isLocalFile(file: File): file is LocalFile { return file.state === 'local'; @@ -292,6 +317,7 @@ function BudgetFiles({ quickSwitchMode={quickSwitchMode} onSelect={onSelect} onDelete={onDelete} + onDuplicate={onDuplicate} /> )) )} @@ -467,7 +493,19 @@ export function BudgetList({ showHeader = true, quickSwitchMode = false }) { files={files} quickSwitchMode={quickSwitchMode} onSelect={onSelect} - onDelete={file => dispatch(pushModal('delete-budget', { file }))} + onDelete={(file: File) => + dispatch(pushModal('delete-budget', { file })) + } + onDuplicate={(file: File) => { + if (file && 'id' in file) { + dispatch(pushModal('duplicate-budget', { file, managePage: true })); + } else { + console.error( + 'Attempted to duplicate a cloud file - only local files are supported. Cloud file:', + file, + ); + } + }} /> {!quickSwitchMode && ( - - - diff --git a/packages/desktop-client/src/components/mobile/accounts/Account.tsx b/packages/desktop-client/src/components/mobile/accounts/Account.tsx index 7940ad6222a..c8a3829764e 100644 --- a/packages/desktop-client/src/components/mobile/accounts/Account.tsx +++ b/packages/desktop-client/src/components/mobile/accounts/Account.tsx @@ -29,8 +29,8 @@ export function Account() { function accountNameFromId(id: string | undefined) { switch (id) { - case 'budgeted': - return 'Budgeted Accounts'; + case 'onbudget': + return 'On Budget Accounts'; case 'offbudget': return 'Off Budget Accounts'; case 'uncategorized': diff --git a/packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx b/packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx index d746aee4de2..3b6c6780312 100644 --- a/packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx +++ b/packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx @@ -53,7 +53,11 @@ export function AccountTransactions({ accountName, }: { readonly account?: AccountEntity; - readonly accountId?: string; + readonly accountId?: + | AccountEntity['id'] + | 'onbudget' + | 'offbudget' + | 'uncategorized'; readonly accountName: string; }) { const schedulesQuery = useMemo( @@ -218,7 +222,7 @@ function TransactionListWithPreviews({ readonly account?: AccountEntity; readonly accountId?: | AccountEntity['id'] - | 'budgeted' + | 'onbudget' | 'offbudget' | 'uncategorized'; readonly accountName: AccountEntity['name'] | string; @@ -236,6 +240,7 @@ function TransactionListWithPreviews({ transactions, isLoading, reload: reloadTransactions, + isLoadingMore, loadMore: loadMoreTransactions, } = useTransactions({ query: transactionsQuery, @@ -269,7 +274,7 @@ function TransactionListWithPreviews({ tables.includes('category_mapping') || tables.includes('payee_mapping') ) { - reloadTransactions?.(); + reloadTransactions(); } if (tables.includes('payees') || tables.includes('payee_mapping')) { @@ -326,6 +331,7 @@ function TransactionListWithPreviews({ balance={balanceQueries.balance} balanceCleared={balanceQueries.cleared} balanceUncleared={balanceQueries.uncleared} + isLoadingMore={isLoadingMore} onLoadMore={loadMoreTransactions} searchPlaceholder={`Search ${accountName}`} onSearch={onSearch} @@ -340,13 +346,13 @@ function queriesFromAccountId( entity: AccountEntity | undefined, ) { switch (id) { - case 'budgeted': + case 'onbudget': return { - balance: queries.budgetedAccountBalance(), + balance: queries.onBudgetAccountBalance(), }; case 'offbudget': return { - balance: queries.offbudgetAccountBalance(), + balance: queries.offBudgetAccountBalance(), }; case 'uncategorized': return { diff --git a/packages/desktop-client/src/components/mobile/accounts/Accounts.tsx b/packages/desktop-client/src/components/mobile/accounts/Accounts.tsx index f17806dc4a8..931234f5cbe 100644 --- a/packages/desktop-client/src/components/mobile/accounts/Accounts.tsx +++ b/packages/desktop-client/src/components/mobile/accounts/Accounts.tsx @@ -178,7 +178,7 @@ type AccountListProps = { accounts: AccountEntity[]; updatedAccounts: Array; getBalanceQuery: (account: AccountEntity) => Binding<'account', 'balance'>; - getOnBudgetBalance: () => Binding<'account', 'budgeted-accounts-balance'>; + getOnBudgetBalance: () => Binding<'account', 'onbudget-accounts-balance'>; getOffBudgetBalance: () => Binding<'account', 'offbudget-accounts-balance'>; onAddAccount: () => void; onSelectAccount: (id: string) => void; @@ -197,8 +197,8 @@ function AccountList({ }: AccountListProps) { const failedAccounts = useFailedAccounts(); const syncingAccountIds = useSelector(state => state.account.accountsSyncing); - const budgetedAccounts = accounts.filter(account => account.offbudget === 0); - const offbudgetAccounts = accounts.filter(account => account.offbudget === 1); + const onBudgetAccounts = accounts.filter(account => account.offbudget === 0); + const offBudgetAccounts = accounts.filter(account => account.offbudget === 1); return ( } - {budgetedAccounts.length > 0 && ( - + {onBudgetAccounts.length > 0 && ( + )} - {budgetedAccounts.map(acct => ( + {onBudgetAccounts.map(acct => ( ))} - {offbudgetAccounts.length > 0 && ( + {offBudgetAccounts.length > 0 && ( )} - {offbudgetAccounts.map(acct => ( + {offBudgetAccounts.map(acct => ( !account.closed)} updatedAccounts={updatedAccounts} getBalanceQuery={queries.accountBalance} - getOnBudgetBalance={queries.budgetedAccountBalance} - getOffBudgetBalance={queries.offbudgetAccountBalance} + getOnBudgetBalance={queries.onBudgetAccountBalance} + getOffBudgetBalance={queries.offBudgetAccountBalance} onAddAccount={onAddAccount} onSelectAccount={onSelectAccount} onSync={onSync} diff --git a/packages/desktop-client/src/components/mobile/budget/BudgetTable.jsx b/packages/desktop-client/src/components/mobile/budget/BudgetTable.jsx index 28ce9be58a5..1a54d3727fa 100644 --- a/packages/desktop-client/src/components/mobile/budget/BudgetTable.jsx +++ b/packages/desktop-client/src/components/mobile/budget/BudgetTable.jsx @@ -156,7 +156,7 @@ function Saved({ projected, onPress, show3Cols }) { minFontSizePx={6} maxFontSizePx={12} mode="oneline" - title="Projected Savings" + title="Projected savings" style={{ color: theme.formInputText, textAlign: 'left', diff --git a/packages/desktop-client/src/components/mobile/budget/CategoryTransactions.jsx b/packages/desktop-client/src/components/mobile/budget/CategoryTransactions.jsx index c03f1676451..aa0df7912c0 100644 --- a/packages/desktop-client/src/components/mobile/budget/CategoryTransactions.jsx +++ b/packages/desktop-client/src/components/mobile/budget/CategoryTransactions.jsx @@ -40,6 +40,7 @@ export function CategoryTransactions({ category, month }) { const { transactions, isLoading, + isLoadingMore, loadMore: loadMoreTransactions, reload: reloadTransactions, } = useTransactions({ @@ -56,7 +57,7 @@ export function CategoryTransactions({ category, month }) { tables.includes('category_mapping') || tables.includes('payee_mapping') ) { - reloadTransactions?.(); + reloadTransactions(); } if (tables.includes('payees') || tables.includes('payee_mapping')) { @@ -112,6 +113,7 @@ export function CategoryTransactions({ category, month }) { balanceUncleared={balanceUncleared} searchPlaceholder={`Search ${category.name}`} onSearch={onSearch} + isLoadingMore={isLoadingMore} onLoadMore={loadMoreTransactions} onOpenTransaction={onOpenTransaction} /> diff --git a/packages/desktop-client/src/components/mobile/transactions/TransactionEdit.jsx b/packages/desktop-client/src/components/mobile/transactions/TransactionEdit.jsx index 8005375a412..3ece052c251 100644 --- a/packages/desktop-client/src/components/mobile/transactions/TransactionEdit.jsx +++ b/packages/desktop-client/src/components/mobile/transactions/TransactionEdit.jsx @@ -517,7 +517,7 @@ const TransactionEditInner = memo(function TransactionEditInner({ const getCategory = useCallback( (trans, isOffBudget) => { if (isOffBudget) { - return 'Off Budget'; + return 'Off budget'; } else if (isBudgetTransfer(trans)) { return 'Transfer'; } else { diff --git a/packages/desktop-client/src/components/mobile/transactions/TransactionList.jsx b/packages/desktop-client/src/components/mobile/transactions/TransactionList.jsx index a22e236f9af..81f45869c6e 100644 --- a/packages/desktop-client/src/components/mobile/transactions/TransactionList.jsx +++ b/packages/desktop-client/src/components/mobile/transactions/TransactionList.jsx @@ -6,10 +6,9 @@ import React, { useState, } from 'react'; import { ListBox, Section, Header, Collection } from 'react-aria-components'; +import { useTranslation } from 'react-i18next'; import { useDispatch } from 'react-redux'; -import { t } from 'i18next'; - import { setNotificationInset } from 'loot-core/client/actions'; import { groupById, integerToCurrency } from 'loot-core/shared/util'; import * as monthUtils from 'loot-core/src/shared/months'; @@ -41,12 +40,32 @@ import { TransactionListItem } from './TransactionListItem'; const NOTIFICATION_BOTTOM_INSET = 75; +function Loading({ style, 'aria-label': ariaLabel }) { + return ( + + + + ); +} + export function TransactionList({ isLoading, transactions, onOpenTransaction, + isLoadingMore, onLoadMore, }) { + const { t } = useTranslation(); + const sections = useMemo(() => { // Group by date. We can assume transactions is ordered const sections = []; @@ -83,29 +102,19 @@ export function TransactionList({ ); useScrollListener(({ hasScrolledToEnd }) => { - if (hasScrolledToEnd('down', 5)) { + if (hasScrolledToEnd('down', 100)) { onLoadMore?.(); } }); if (isLoading) { - return ( - - - - ); + return ; } + return ( <> 0 ? 'multiple' : 'single'} selectedKeys={selectedTransactions} dependencies={[selectedTransactions]} @@ -159,6 +168,17 @@ export function TransactionList({ )} + + {isLoadingMore && ( + + )} + {selectedTransactions.size > 0 && ( )} diff --git a/packages/desktop-client/src/components/mobile/transactions/TransactionListItem.tsx b/packages/desktop-client/src/components/mobile/transactions/TransactionListItem.tsx index f46037d140a..db898ec9ef8 100644 --- a/packages/desktop-client/src/components/mobile/transactions/TransactionListItem.tsx +++ b/packages/desktop-client/src/components/mobile/transactions/TransactionListItem.tsx @@ -101,7 +101,7 @@ export function TransactionListItem({ transferAccount, }); const specialCategory = account?.offbudget - ? 'Off Budget' + ? 'Off budget' : transferAccount && !transferAccount.offbudget ? 'Transfer' : isParent diff --git a/packages/desktop-client/src/components/mobile/transactions/TransactionListWithBalances.jsx b/packages/desktop-client/src/components/mobile/transactions/TransactionListWithBalances.jsx index f884f927ac1..b86c131a901 100644 --- a/packages/desktop-client/src/components/mobile/transactions/TransactionListWithBalances.jsx +++ b/packages/desktop-client/src/components/mobile/transactions/TransactionListWithBalances.jsx @@ -65,6 +65,7 @@ export function TransactionListWithBalances({ balanceUncleared, searchPlaceholder = 'Search...', onSearch, + isLoadingMore, onLoadMore, onOpenTransaction, onRefresh, @@ -104,6 +105,7 @@ export function TransactionListWithBalances({ diff --git a/packages/desktop-client/src/components/modals/CloseAccountModal.tsx b/packages/desktop-client/src/components/modals/CloseAccountModal.tsx index d947b81c9c1..5e808a334d2 100644 --- a/packages/desktop-client/src/components/modals/CloseAccountModal.tsx +++ b/packages/desktop-client/src/components/modals/CloseAccountModal.tsx @@ -35,7 +35,7 @@ function needsCategory( const isOffBudget = acct && acct.offbudget === 1; // The user must select a category if transferring from a budgeted - // account to an off-budget account + // account to an off budget account return account.offbudget === 0 && isOffBudget; } @@ -183,8 +183,8 @@ export function CloseAccountModal({ {needsCategory(account, transferAccountId, accounts) && ( - Since you are transferring the balance from a budgeted - account to an off-budget account, this transaction must + Since you are transferring the balance from an on budget + account to an off budget account, this transaction must be categorized. Select a category: diff --git a/packages/desktop-client/src/components/modals/CreateAccountModal.tsx b/packages/desktop-client/src/components/modals/CreateAccountModal.tsx index 8f62089c913..6ba131bf6dd 100644 --- a/packages/desktop-client/src/components/modals/CreateAccountModal.tsx +++ b/packages/desktop-client/src/components/modals/CreateAccountModal.tsx @@ -1,5 +1,5 @@ -// @ts-strict-ignore -import React, { useEffect, useRef, useState } from 'react'; +import React, { useEffect, useState } from 'react'; +import { DialogTrigger } from 'react-aria-components'; import { useTranslation } from 'react-i18next'; import { useDispatch } from 'react-redux'; @@ -30,13 +30,12 @@ export function CreateAccountModal({ upgradingAccountId }: CreateAccountProps) { const { t } = useTranslation(); const syncServerStatus = useSyncServerStatus(); const dispatch = useDispatch(); - const [isGoCardlessSetupComplete, setIsGoCardlessSetupComplete] = - useState(null); - const [isSimpleFinSetupComplete, setIsSimpleFinSetupComplete] = - useState(null); - const [menuGoCardlessOpen, setGoCardlessMenuOpen] = useState(false); - const triggerRef = useRef(null); - const [menuSimplefinOpen, setSimplefinMenuOpen] = useState(false); + const [isGoCardlessSetupComplete, setIsGoCardlessSetupComplete] = useState< + boolean | null + >(null); + const [isSimpleFinSetupComplete, setIsSimpleFinSetupComplete] = useState< + boolean | null + >(null); const onConnectGoCardless = () => { if (!isGoCardlessSetupComplete) { @@ -139,7 +138,6 @@ export function CreateAccountModal({ upgradingAccountId }: CreateAccountProps) { value: null, }).then(() => { setIsGoCardlessSetupComplete(false); - setGoCardlessMenuOpen(false); }); }); }; @@ -154,7 +152,6 @@ export function CreateAccountModal({ upgradingAccountId }: CreateAccountProps) { value: null, }).then(() => { setIsSimpleFinSetupComplete(false); - setSimplefinMenuOpen(false); }); }); }; @@ -248,12 +245,10 @@ export function CreateAccountModal({ upgradingAccountId }: CreateAccountProps) { : t('Set up GoCardless for bank sync')} {isGoCardlessSetupComplete && ( - <> + - setGoCardlessMenuOpen(false)} - > + { if (item === 'reconfigure') { @@ -281,7 +272,7 @@ export function CreateAccountModal({ upgradingAccountId }: CreateAccountProps) { ]} /> - + )} @@ -317,24 +308,15 @@ export function CreateAccountModal({ upgradingAccountId }: CreateAccountProps) { : t('Set up SimpleFIN for bank sync')} {isSimpleFinSetupComplete && ( - <> - - setSimplefinMenuOpen(false)} - > + { if (item === 'reconfigure') { @@ -349,7 +331,7 @@ export function CreateAccountModal({ upgradingAccountId }: CreateAccountProps) { ]} /> - + )} diff --git a/packages/desktop-client/src/components/modals/CreateLocalAccountModal.tsx b/packages/desktop-client/src/components/modals/CreateLocalAccountModal.tsx index 594d3d11202..6fa7b7c4783 100644 --- a/packages/desktop-client/src/components/modals/CreateLocalAccountModal.tsx +++ b/packages/desktop-client/src/components/modals/CreateLocalAccountModal.tsx @@ -127,7 +127,7 @@ export function CreateLocalAccountModal() { verticalAlign: 'center', }} > - {t('Off-budget')} + {t('Off budget')}
onChange('value', v)} numberFormatType="currency" @@ -461,6 +462,7 @@ function ActionEditor({ action, editorStyle, onChange, onDelete, onAdd }) { { map[entry.transaction.trx_id] = entry; @@ -309,7 +309,7 @@ export function ImportTransactionsModal({ options }) { return next; }, []); }, - [accountId, categories.list, clearOnImport, importPreviewTransactions], + [accountId, categories.list, clearOnImport, dispatch], ); const parse = useCallback( @@ -320,8 +320,9 @@ export function ImportTransactionsModal({ options }) { setFilename(filename); setFileType(filetype); - const { errors, transactions: parsedTransactions = [] } = - await parseTransactions(filename, options); + const { errors, transactions: parsedTransactions = [] } = await dispatch( + parseTransactions(filename, options), + ); let index = 0; const transactions = parsedTransactions.map(trans => { @@ -399,11 +400,11 @@ export function ImportTransactionsModal({ options }) { }, [ accountId, + dispatch, getImportPreview, inOutMode, multiplierAmount, outValue, - parseTransactions, prefs, ], ); @@ -427,7 +428,6 @@ export function ImportTransactionsModal({ options }) { parse(options.filename, parseOptions); }, [ - parseTransactions, options.filename, delimiter, hasHeaderRow, @@ -653,13 +653,11 @@ export function ImportTransactionsModal({ options }) { }); } - const didChange = await importTransactions( - accountId, - finalTransactions, - reconcile, + const didChange = await dispatch( + importTransactions(accountId, finalTransactions, reconcile), ); if (didChange) { - await getPayees(); + await dispatch(getPayees()); } if (onImported) { diff --git a/packages/desktop-client/src/components/modals/SelectLinkedAccountsModal.jsx b/packages/desktop-client/src/components/modals/SelectLinkedAccountsModal.jsx index 90ec64cd633..bac4aefe5f4 100644 --- a/packages/desktop-client/src/components/modals/SelectLinkedAccountsModal.jsx +++ b/packages/desktop-client/src/components/modals/SelectLinkedAccountsModal.jsx @@ -23,7 +23,7 @@ import { TableHeader, Table, Row, Field } from '../table'; const addOnBudgetAccountOption = { id: 'new-on', name: 'Create new account' }; const addOffBudgetAccountOption = { id: 'new-off', - name: 'Create new account (off-budget)', + name: 'Create new account (off budget)', }; export function SelectLinkedAccountsModal({ diff --git a/packages/desktop-client/src/components/modals/manager/DuplicateFileModal.tsx b/packages/desktop-client/src/components/modals/manager/DuplicateFileModal.tsx new file mode 100644 index 00000000000..ed1d705521e --- /dev/null +++ b/packages/desktop-client/src/components/modals/manager/DuplicateFileModal.tsx @@ -0,0 +1,240 @@ +import React, { useEffect, useState } from 'react'; +import { Trans, useTranslation } from 'react-i18next'; +import { useDispatch } from 'react-redux'; + +import { + addNotification, + duplicateBudget, + uniqueBudgetName, + validateBudgetName, +} from 'loot-core/client/actions'; +import { type File } from 'loot-core/src/types/file'; + +import { theme } from '../../../style'; +import { Button, ButtonWithLoading } from '../../common/Button2'; +import { FormError } from '../../common/FormError'; +import { InitialFocus } from '../../common/InitialFocus'; +import { InlineField } from '../../common/InlineField'; +import { Input } from '../../common/Input'; +import { + Modal, + ModalButtons, + ModalCloseButton, + ModalHeader, +} from '../../common/Modal'; +import { Text } from '../../common/Text'; +import { View } from '../../common/View'; + +type DuplicateFileProps = { + file: File; + managePage?: boolean; + loadBudget?: 'none' | 'original' | 'copy'; + onComplete?: (event: { + status: 'success' | 'failed' | 'canceled'; + error?: object; + }) => void; +}; + +export function DuplicateFileModal({ + file, + managePage, + loadBudget = 'none', + onComplete, +}: DuplicateFileProps) { + const { t } = useTranslation(); + const fileEndingTranslation = t(' - copy'); + const [newName, setNewName] = useState(file.name + fileEndingTranslation); + const [nameError, setNameError] = useState(null); + + // If the state is "broken" that means it was created by another user. + const isCloudFile = 'cloudFileId' in file && file.state !== 'broken'; + const isLocalFile = 'id' in file; + const dispatch = useDispatch(); + + const [loadingState, setLoadingState] = useState<'cloud' | 'local' | null>( + null, + ); + + useEffect(() => { + (async () => { + setNewName(await uniqueBudgetName(file.name + fileEndingTranslation)); + })(); + }, [file.name, fileEndingTranslation]); + + const validateAndSetName = async (name: string) => { + const trimmedName = name.trim(); + const { valid, message } = await validateBudgetName(trimmedName); + if (valid) { + setNewName(trimmedName); + setNameError(null); + } else { + // The "Unknown error" should never happen, but this satifies type checking + setNameError(message ?? t('Unknown error with budget name')); + } + }; + + const handleDuplicate = async (sync: 'localOnly' | 'cloudSync') => { + const { valid, message } = await validateBudgetName(newName); + if (valid) { + setLoadingState(sync === 'cloudSync' ? 'cloud' : 'local'); + + try { + await dispatch( + duplicateBudget({ + id: 'id' in file ? file.id : undefined, + cloudId: + sync === 'cloudSync' && 'cloudFileId' in file + ? file.cloudFileId + : undefined, + oldName: file.name, + newName, + cloudSync: sync === 'cloudSync', + managePage, + loadBudget, + }), + ); + dispatch( + addNotification({ + type: 'message', + message: t('Duplicate file “{{newName}}” created.', { newName }), + }), + ); + if (onComplete) onComplete({ status: 'success' }); + } catch (e) { + const newError = new Error(t('Failed to duplicate budget')); + if (onComplete) onComplete({ status: 'failed', error: newError }); + else console.error('Failed to duplicate budget:', e); + dispatch( + addNotification({ + type: 'error', + message: t('Failed to duplicate budget file.'), + }), + ); + } finally { + setLoadingState(null); + } + } else { + const failError = new Error( + message ?? t('Unknown error with budget name'), + ); + if (onComplete) onComplete({ status: 'failed', error: failError }); + } + }; + + return ( + + {({ state: { close } }) => ( + + { + close(); + if (onComplete) onComplete({ status: 'canceled' }); + }} + /> + } + /> + + + + + setNewName(event.target.value)} + onBlur={event => validateAndSetName(event.target.value)} + style={{ flex: 1 }} + /> + + + {nameError && ( + + {nameError} + + )} + + {isLocalFile ? ( + isCloudFile && ( + + + Your budget is hosted on a server, making it accessible for + download on your devices. +
+ Would you like to duplicate this budget for all your devices + or keep it stored locally on this device? +
+
+ ) + ) : ( + + + Unable to duplicate a budget that is not located on your + device. +
+ Please download the budget from the server before duplicating. +
+
+ )} + + + {isLocalFile && isCloudFile && ( + handleDuplicate('cloudSync')} + > + Duplicate for all devices + + )} + {isLocalFile && ( + handleDuplicate('localOnly')} + > + Duplicate + {isCloudFile && locally} + + )} + +
+
+ )} +
+ ); +} diff --git a/packages/desktop-client/src/components/payees/PayeeTableRow.tsx b/packages/desktop-client/src/components/payees/PayeeTableRow.tsx index dce345a3da8..5392c1a435e 100644 --- a/packages/desktop-client/src/components/payees/PayeeTableRow.tsx +++ b/packages/desktop-client/src/components/payees/PayeeTableRow.tsx @@ -1,10 +1,10 @@ // @ts-strict-ignore -import { memo, useRef, useState, type CSSProperties } from 'react'; +import { memo, useRef, type CSSProperties } from 'react'; import { Trans, useTranslation } from 'react-i18next'; import { type PayeeEntity } from 'loot-core/src/types/models'; -import { useFeatureFlag } from '../../hooks/useFeatureFlag'; +import { useContextMenu } from '../../hooks/useContextMenu'; import { useSelectedDispatch } from '../../hooks/useSelected'; import { SvgArrowThinRight, SvgBookmark } from '../../icons/v1'; import { theme } from '../../style'; @@ -111,10 +111,8 @@ export const PayeeTableRow = memo( const { t } = useTranslation(); const triggerRef = useRef(null); - const [menuOpen, setMenuOpen] = useState(false); - const [crossOffset, setCrossOffset] = useState(0); - const [offset, setOffset] = useState(0); - const contextMenusEnabled = useFeatureFlag('contextMenus'); + const { setMenuOpen, menuOpen, handleContextMenu, position } = + useContextMenu(); return ( onHover && onHover(payee.id)} - onContextMenu={e => { - if (!contextMenusEnabled) return; - e.preventDefault(); - setMenuOpen(true); - const rect = e.currentTarget.getBoundingClientRect(); - setCrossOffset(e.clientX - rect.left); - setOffset(e.clientY - rect.bottom); - }} + onContextMenu={handleContextMenu} > setMenuOpen(false)} - crossOffset={crossOffset} - offset={offset} + {...position} style={{ width: 200, margin: 1 }} isNonModal > diff --git a/packages/desktop-client/src/components/reports/ReportOptions.ts b/packages/desktop-client/src/components/reports/ReportOptions.ts index d8f2ed7fce6..953771f1f65 100644 --- a/packages/desktop-client/src/components/reports/ReportOptions.ts +++ b/packages/desktop-client/src/components/reports/ReportOptions.ts @@ -257,7 +257,7 @@ const transferCategory: UncategorizedEntity = { }; const offBudgetCategory: UncategorizedEntity = { id: '', - name: t('Off Budget'), + name: t('Off budget'), uncategorized_id: 'off_budget', hidden: false, }; @@ -271,7 +271,7 @@ type UncategorizedGroupEntity = Pick< }; const uncategorizedGroup: UncategorizedGroupEntity = { - name: t('Uncategorized & Off Budget'), + name: t('Uncategorized & Off budget'), id: 'uncategorized', hidden: false, uncategorized_id: 'all', diff --git a/packages/desktop-client/src/components/rules/ConditionExpression.tsx b/packages/desktop-client/src/components/rules/ConditionExpression.tsx index 229868a338f..cc2d092bcc5 100644 --- a/packages/desktop-client/src/components/rules/ConditionExpression.tsx +++ b/packages/desktop-client/src/components/rules/ConditionExpression.tsx @@ -49,7 +49,11 @@ export function ConditionExpression({ {prefix && {prefix} } {mapField(field, options)}{' '} {friendlyOp(op)}{' '} - + {!['onbudget', 'offbudget'].includes( + (op as string)?.toLocaleLowerCase(), + ) && ( + + )} ); } diff --git a/packages/desktop-client/src/components/rules/RuleRow.tsx b/packages/desktop-client/src/components/rules/RuleRow.tsx index 2c59dcac267..10b67f30320 100644 --- a/packages/desktop-client/src/components/rules/RuleRow.tsx +++ b/packages/desktop-client/src/components/rules/RuleRow.tsx @@ -1,5 +1,5 @@ // @ts-strict-ignore -import React, { memo, useRef, useState } from 'react'; +import React, { memo, useRef } from 'react'; import { useTranslation } from 'react-i18next'; import { v4 as uuid } from 'uuid'; @@ -7,7 +7,7 @@ import { v4 as uuid } from 'uuid'; import { friendlyOp } from 'loot-core/src/shared/rules'; import { type RuleEntity } from 'loot-core/src/types/models'; -import { useFeatureFlag } from '../../hooks/useFeatureFlag'; +import { useContextMenu } from '../../hooks/useContextMenu'; import { useSelectedDispatch } from '../../hooks/useSelected'; import { SvgRightArrow2 } from '../../icons/v0'; import { styles, theme } from '../../style'; @@ -60,10 +60,8 @@ export const RuleRow = memo( const { t } = useTranslation(); const triggerRef = useRef(null); - const [menuOpen, setMenuOpen] = useState(false); - const [crossOffset, setCrossOffset] = useState(0); - const [offset, setOffset] = useState(0); - const contextMenusEnabled = useFeatureFlag('contextMenus'); + const { setMenuOpen, menuOpen, handleContextMenu, position } = + useContextMenu(); return ( onHover && onHover(rule.id)} onMouseLeave={() => onHover && onHover(null)} - onContextMenu={e => { - if (!contextMenusEnabled) return; - e.preventDefault(); - setMenuOpen(true); - const rect = triggerRef.current.getBoundingClientRect(); - setCrossOffset(e.clientX - rect.left); - setOffset(e.clientY - rect.bottom); - }} + onContextMenu={handleContextMenu} > setMenuOpen(false)} - crossOffset={crossOffset} - offset={offset} + {...position} style={{ width: 200, margin: 1 }} isNonModal > diff --git a/packages/desktop-client/src/components/schedules/SchedulesTable.tsx b/packages/desktop-client/src/components/schedules/SchedulesTable.tsx index 2d65f595667..e3a1782db6d 100644 --- a/packages/desktop-client/src/components/schedules/SchedulesTable.tsx +++ b/packages/desktop-client/src/components/schedules/SchedulesTable.tsx @@ -13,8 +13,8 @@ import { integerToCurrency } from 'loot-core/src/shared/util'; import { type ScheduleEntity } from 'loot-core/src/types/models'; import { useAccounts } from '../../hooks/useAccounts'; +import { useContextMenu } from '../../hooks/useContextMenu'; import { useDateFormat } from '../../hooks/useDateFormat'; -import { useFeatureFlag } from '../../hooks/useFeatureFlag'; import { usePayees } from '../../hooks/usePayees'; import { SvgDotsHorizontalTriple } from '../../icons/v1'; import { SvgCheck } from '../../icons/v2'; @@ -186,10 +186,14 @@ function ScheduleRow({ const rowRef = useRef(null); const buttonRef = useRef(null); - const [open, setOpen] = useState(false); - const [crossOffset, setCrossOffset] = useState(0); - const [offset, setOffset] = useState(0); - const contextMenusEnabled = useFeatureFlag('contextMenus'); + const { + setMenuOpen, + menuOpen, + handleContextMenu, + resetPosition, + position, + asContextMenu, + } = useContextMenu(); return ( { - if (!contextMenusEnabled) return; - if (minimal) return; - e.preventDefault(); - const rect = e.currentTarget.getBoundingClientRect(); - setCrossOffset(e.clientX - rect.left); - setOffset(e.clientY - rect.bottom); - setOpen('contextMenu'); - }} + onContextMenu={handleContextMenu} > {!minimal && ( setOpen(false)} + triggerRef={asContextMenu ? rowRef : buttonRef} + isOpen={menuOpen} + onOpenChange={() => setMenuOpen(false)} isNonModal placement="bottom start" - crossOffset={open === 'contextMenu' ? crossOffset : 0} - offset={open === 'contextMenu' ? offset : 0} + {...position} style={{ margin: 1 }} > { onAction(action, id); - setOpen(false); + resetPosition(); + setMenuOpen(false); }} /> @@ -276,7 +272,8 @@ function ScheduleRow({ variant="bare" aria-label={t('Menu')} onPress={() => { - setOpen('button'); + resetPosition(); + setMenuOpen(true); }} > {t('Learn more…')} diff --git a/packages/desktop-client/src/components/settings/Reset.tsx b/packages/desktop-client/src/components/settings/Reset.tsx index 193bb2565e6..0a9128d918f 100644 --- a/packages/desktop-client/src/components/settings/Reset.tsx +++ b/packages/desktop-client/src/components/settings/Reset.tsx @@ -1,10 +1,11 @@ import React, { useState } from 'react'; +import { useDispatch } from 'react-redux'; import { t } from 'i18next'; +import { resetSync } from 'loot-core/client/actions'; import { send } from 'loot-core/src/platform/client/fetch'; -import { useActions } from '../../hooks/useActions'; import { useMetadataPref } from '../../hooks/useMetadataPref'; import { ButtonWithLoading } from '../common/Button2'; import { Text } from '../common/Text'; @@ -41,13 +42,13 @@ export function ResetCache() { export function ResetSync() { const [groupId] = useMetadataPref('groupId'); const isEnabled = !!groupId; - const { resetSync } = useActions(); + const dispatch = useDispatch(); const [resetting, setResetting] = useState(false); async function onResetSync() { setResetting(true); - await resetSync(); + await dispatch(resetSync()); setResetting(false); } diff --git a/packages/desktop-client/src/components/settings/index.tsx b/packages/desktop-client/src/components/settings/index.tsx index 4a7852e12b0..6e93d81a702 100644 --- a/packages/desktop-client/src/components/settings/index.tsx +++ b/packages/desktop-client/src/components/settings/index.tsx @@ -1,12 +1,13 @@ import React, { type ReactNode, useEffect } from 'react'; +import { useDispatch } from 'react-redux'; import { css } from '@emotion/css'; import { t } from 'i18next'; +import { closeBudget, loadPrefs } from 'loot-core/client/actions'; import { isElectron } from 'loot-core/shared/environment'; import { listen } from 'loot-core/src/platform/client/fetch'; -import { useActions } from '../../hooks/useActions'; import { useGlobalPref } from '../../hooks/useGlobalPref'; import { useIsOutdated, useLatestVersion } from '../../hooks/useLatestVersion'; import { useMetadataPref } from '../../hooks/useMetadataPref'; @@ -126,17 +127,20 @@ function AdvancedAbout() { export function Settings() { const [floatingSidebar] = useGlobalPref('floatingSidebar'); const [budgetName] = useMetadataPref('budgetName'); + const dispatch = useDispatch(); - const { loadPrefs, closeBudget } = useActions(); + const onCloseBudget = () => { + dispatch(closeBudget()); + }; useEffect(() => { const unlisten = listen('prefs-updated', () => { - loadPrefs(); + dispatch(loadPrefs()); }); - loadPrefs(); + dispatch(loadPrefs()); return () => unlisten(); - }, [loadPrefs]); + }, [dispatch]); const { isNarrowWidth } = useResponsive(); @@ -169,7 +173,7 @@ export function Settings() { style={{ color: theme.buttonNormalDisabledText }} /> - + )} diff --git a/packages/desktop-client/src/components/sidebar/Account.tsx b/packages/desktop-client/src/components/sidebar/Account.tsx index 62985660a4f..eebfd7c98c6 100644 --- a/packages/desktop-client/src/components/sidebar/Account.tsx +++ b/packages/desktop-client/src/components/sidebar/Account.tsx @@ -153,7 +153,7 @@ export function Account>({ state.account.accountsSyncing, @@ -100,11 +100,11 @@ export function Accounts() { style={{ fontWeight, marginTop: 15 }} /> - {budgetedAccounts.length > 0 && ( + {onBudgetAccounts.length > 0 && ( )} - {budgetedAccounts.map((account, i) => ( + {onBudgetAccounts.map((account, i) => ( ['value'] { const { sheetName, fullSheetName } = useSheetName(binding); - const bindingObj = - typeof binding === 'string' - ? { name: binding, value: null, query: undefined } - : binding; + const bindingObj = useMemo( + () => + typeof binding === 'string' + ? { name: binding, value: null, query: undefined } + : binding, + [binding], + ); const spreadsheet = useSpreadsheet(); const [result, setResult] = useState>({ name: fullSheetName, - value: bindingObj.value === undefined ? null : bindingObj.value, + value: bindingObj.value ? bindingObj.value : null, query: bindingObj.query, }); const latestOnChange = useRef(onChange); @@ -48,15 +51,16 @@ export function useSheetValue< latestValue.current = result.value; useLayoutEffect(() => { - if (bindingObj.query) { - spreadsheet.createQuery(sheetName, bindingObj.name, bindingObj.query); - } + let isMounted = true; - return spreadsheet.bind( + const unbind = spreadsheet.bind( sheetName, - binding, - null, + bindingObj, (newResult: SheetValueResult) => { + if (!isMounted) { + return; + } + if (latestOnChange.current) { latestOnChange.current(newResult); } @@ -66,7 +70,17 @@ export function useSheetValue< } }, ); - }, [sheetName, bindingObj.name, JSON.stringify(bindingObj.query)]); + + return () => { + isMounted = false; + unbind(); + }; + }, [ + spreadsheet, + sheetName, + bindingObj.name, + bindingObj.query?.serializeAsString(), + ]); return result.value; } diff --git a/packages/desktop-client/src/components/transactions/TransactionsTable.jsx b/packages/desktop-client/src/components/transactions/TransactionsTable.jsx index 60f581b60c0..655e853ccf4 100644 --- a/packages/desktop-client/src/components/transactions/TransactionsTable.jsx +++ b/packages/desktop-client/src/components/transactions/TransactionsTable.jsx @@ -47,7 +47,7 @@ import { titleFirst, } from 'loot-core/src/shared/util'; -import { useFeatureFlag } from '../../hooks/useFeatureFlag'; +import { useContextMenu } from '../../hooks/useContextMenu'; import { useMergedRefs } from '../../hooks/useMergedRefs'; import { usePrevious } from '../../hooks/usePrevious'; import { useProperFocus } from '../../hooks/useProperFocus'; @@ -1048,10 +1048,8 @@ const Transaction = memo(function Transaction({ }, 1); }, [splitError, allTransactions]); - const [menuOpen, setMenuOpen] = useState(false); - const [crossOffset, setCrossOffset] = useState(0); - const [offset, setOffset] = useState(0); - const contextMenusEnabled = useFeatureFlag('contextMenus'); + const { setMenuOpen, menuOpen, handleContextMenu, position } = + useContextMenu(); return ( { - if (!contextMenusEnabled) return; - if (transaction.id === 'temp') return; - e.preventDefault(); - const rect = e.currentTarget.getBoundingClientRect(); - setCrossOffset(e.clientX - rect.left); - setOffset(e.clientY - rect.bottom); - setMenuOpen(true); - }} + onContextMenu={handleContextMenu} > setMenuOpen(false)} - crossOffset={crossOffset} - offset={offset} + {...position} style={{ width: 200, margin: 1 }} isNonModal > @@ -1420,7 +1409,7 @@ const Transaction = memo(function Transaction({ ) : isBudgetTransfer || isOffBudget ? ( - ); + switch (op) { + case 'onBudget': + case 'offBudget': + content = null; + break; + default: + content = ( + + ); + break; + } break; case 'category': diff --git a/packages/desktop-client/src/hooks/useContextMenu.ts b/packages/desktop-client/src/hooks/useContextMenu.ts new file mode 100644 index 00000000000..69bbd0bd8ce --- /dev/null +++ b/packages/desktop-client/src/hooks/useContextMenu.ts @@ -0,0 +1,40 @@ +import { type MouseEventHandler, useState } from 'react'; + +import { useFeatureFlag } from './useFeatureFlag'; + +export function useContextMenu() { + const [menuOpen, setMenuOpen] = useState(false); + const [asContextMenu, setAsContextMenu] = useState(false); + const [position, setPosition] = useState({ crossOffset: 0, offset: 0 }); + const contextMenusEnabled = useFeatureFlag('contextMenus'); + + const handleContextMenu: MouseEventHandler = e => { + if (!contextMenusEnabled) return; + + e.preventDefault(); + setAsContextMenu(true); + + const rect = e.currentTarget.getBoundingClientRect(); + setPosition({ + crossOffset: e.clientX - rect.left, + offset: e.clientY - rect.bottom, + }); + setMenuOpen(true); + }; + + const resetPosition = (crossOffset = 0, offset = 0) => { + setPosition({ crossOffset, offset }); + }; + + return { + menuOpen, + setMenuOpen: (open: boolean) => { + setMenuOpen(open); + setAsContextMenu(false); + }, + position, + handleContextMenu, + resetPosition, + asContextMenu, + }; +} diff --git a/packages/desktop-client/src/hooks/useBudgetedAccounts.ts b/packages/desktop-client/src/hooks/useOnBudgetAccounts.ts similarity index 86% rename from packages/desktop-client/src/hooks/useBudgetedAccounts.ts rename to packages/desktop-client/src/hooks/useOnBudgetAccounts.ts index dbd8e1f53db..31fd6cf10c0 100644 --- a/packages/desktop-client/src/hooks/useBudgetedAccounts.ts +++ b/packages/desktop-client/src/hooks/useOnBudgetAccounts.ts @@ -2,7 +2,7 @@ import { useMemo } from 'react'; import { useAccounts } from './useAccounts'; -export function useBudgetedAccounts() { +export function useOnBudgetAccounts() { const accounts = useAccounts(); return useMemo( () => diff --git a/packages/loot-core/src/client/SpreadsheetProvider.tsx b/packages/loot-core/src/client/SpreadsheetProvider.tsx index 7c53ca515d4..6f8cfafbd78 100644 --- a/packages/loot-core/src/client/SpreadsheetProvider.tsx +++ b/packages/loot-core/src/client/SpreadsheetProvider.tsx @@ -62,22 +62,26 @@ function makeSpreadsheet() { }); } - bind(sheetName = '__global', binding, fields, cb) { + bind(sheetName = '__global', binding, callback) { binding = typeof binding === 'string' ? { name: binding, value: null } : binding; + if (binding.query) { + this.createQuery(sheetName, binding.name, binding.query); + } + const resolvedName = `${sheetName}!${binding.name}`; - const cleanup = this.observeCell(resolvedName, cb); + const cleanup = this.observeCell(resolvedName, callback); // Always synchronously call with the existing value if it has one. // This is a display optimization to avoid flicker. The LRU cache // will keep a number of recent nodes in memory. if (LRUValueCache.has(resolvedName)) { - cb(LRUValueCache.get(resolvedName)); + callback(LRUValueCache.get(resolvedName)); } if (cellCache[resolvedName] != null) { - cellCache[resolvedName].then(cb); + cellCache[resolvedName].then(callback); } else { const req = this.get(sheetName, binding.name); cellCache[resolvedName] = req; @@ -90,7 +94,7 @@ function makeSpreadsheet() { // with an old value depending on the order of messages) if (cellCache[resolvedName] === req) { LRUValueCache.set(resolvedName, result); - cb(result); + callback(result); } }); } diff --git a/packages/loot-core/src/client/actions/budgets.ts b/packages/loot-core/src/client/actions/budgets.ts index 5ce240a64b8..8e0a5bfebd1 100644 --- a/packages/loot-core/src/client/actions/budgets.ts +++ b/packages/loot-core/src/client/actions/budgets.ts @@ -148,6 +148,73 @@ export function createBudget({ testMode = false, demoMode = false } = {}) { }; } +export function validateBudgetName(name: string): { + valid: boolean; + message?: string; +} { + return send('validate-budget-name', { name }); +} + +export function uniqueBudgetName(name: string): string { + return send('unique-budget-name', { name }); +} + +export function duplicateBudget({ + id, + cloudId, + oldName, + newName, + managePage, + loadBudget = 'none', + cloudSync, +}: { + id?: string; + cloudId?: string; + oldName: string; + newName: string; + managePage?: boolean; + loadBudget: 'none' | 'original' | 'copy'; + /** + * cloudSync is used to determine if the duplicate budget + * should be synced to the server + */ + cloudSync?: boolean; +}) { + return async (dispatch: Dispatch) => { + try { + dispatch( + setAppState({ + loadingText: t('Duplicating: {{oldName}} -- to: {{newName}}', { + oldName, + newName, + }), + }), + ); + + await send('duplicate-budget', { + id, + cloudId, + newName, + cloudSync, + open: loadBudget, + }); + + dispatch(closeModal()); + + if (managePage) { + await dispatch(loadAllFiles()); + } + } catch (error) { + console.error('Error duplicating budget:', error); + throw error instanceof Error + ? error + : new Error('Error duplicating budget: ' + String(error)); + } finally { + dispatch(setAppState({ loadingText: null })); + } + }; +} + export function importBudget( filepath: string, type: Parameters[0]['type'], diff --git a/packages/loot-core/src/client/data-hooks/schedules.tsx b/packages/loot-core/src/client/data-hooks/schedules.tsx index 6207b6304a8..03b95b42b30 100644 --- a/packages/loot-core/src/client/data-hooks/schedules.tsx +++ b/packages/loot-core/src/client/data-hooks/schedules.tsx @@ -161,7 +161,7 @@ export function useCachedSchedules() { } export function accountSchedulesQuery( - accountId?: AccountEntity['id'] | 'budgeted' | 'offbudget' | 'uncategorized', + accountId?: AccountEntity['id'] | 'onbudget' | 'offbudget' | 'uncategorized', ) { const filterByAccount = accountFilter(accountId, '_account'); const filterByPayee = accountFilter(accountId, '_payee.transfer_acct'); diff --git a/packages/loot-core/src/client/data-hooks/transactions.ts b/packages/loot-core/src/client/data-hooks/transactions.ts index 60ec93e1f2d..096fe2836ea 100644 --- a/packages/loot-core/src/client/data-hooks/transactions.ts +++ b/packages/loot-core/src/client/data-hooks/transactions.ts @@ -1,4 +1,4 @@ -import { useEffect, useRef, useState, useMemo } from 'react'; +import { useEffect, useRef, useState, useMemo, useCallback } from 'react'; import debounce from 'lodash/debounce'; @@ -24,10 +24,11 @@ type UseTransactionsProps = { type UseTransactionsResult = { transactions: ReadonlyArray; - isLoading?: boolean; + isLoading: boolean; error?: Error; - reload?: () => void; - loadMore?: () => void; + reload: () => void; + loadMore: () => void; + isLoadingMore: boolean; }; export function useTransactions({ @@ -35,6 +36,7 @@ export function useTransactions({ options = { pageCount: 50 }, }: UseTransactionsProps): UseTransactionsResult { const [isLoading, setIsLoading] = useState(true); + const [isLoadingMore, setIsLoadingMore] = useState(false); const [error, setError] = useState(undefined); const [transactions, setTransactions] = useState< ReadonlyArray @@ -88,12 +90,32 @@ export function useTransactions({ }; }, [query]); + const loadMore = useCallback(async () => { + if (!pagedQueryRef.current) { + return; + } + + setIsLoadingMore(true); + + await pagedQueryRef.current + .fetchNext() + .catch(setError) + .finally(() => { + setIsLoadingMore(false); + }); + }, []); + + const reload = useCallback(() => { + pagedQueryRef.current?.run(); + }, []); + return { transactions, isLoading, error, - reload: pagedQueryRef.current?.run, - loadMore: pagedQueryRef.current?.fetchNext, + reload, + loadMore, + isLoadingMore, }; } diff --git a/packages/loot-core/src/client/queries.ts b/packages/loot-core/src/client/queries.ts index c36659a1378..a8b1ff9b368 100644 --- a/packages/loot-core/src/client/queries.ts +++ b/packages/loot-core/src/client/queries.ts @@ -29,11 +29,11 @@ const envelopeParametrizedField = parametrizedField<'envelope-budget'>(); const trackingParametrizedField = parametrizedField<'tracking-budget'>(); export function accountFilter( - accountId?: AccountEntity['id'] | 'budgeted' | 'offbudget' | 'uncategorized', + accountId?: AccountEntity['id'] | 'onbudget' | 'offbudget' | 'uncategorized', field = 'account', ) { if (accountId) { - if (accountId === 'budgeted') { + if (accountId === 'onbudget') { return { $and: [ { [`${field}.offbudget`]: false }, @@ -68,7 +68,7 @@ export function accountFilter( } export function transactions( - accountId?: AccountEntity['id'] | 'budgeted' | 'offbudget' | 'uncategorized', + accountId?: AccountEntity['id'] | 'onbudget' | 'offbudget' | 'uncategorized', ) { let query = q('transactions').options({ splits: 'grouped' }); @@ -159,16 +159,16 @@ export function allAccountBalance() { } satisfies Binding<'account', 'accounts-balance'>; } -export function budgetedAccountBalance() { +export function onBudgetAccountBalance() { return { - name: `budgeted-accounts-balance`, + name: `onbudget-accounts-balance`, query: q('transactions') .filter({ 'account.offbudget': false, 'account.closed': false }) .calculate({ $sum: '$amount' }), - } satisfies Binding<'account', 'budgeted-accounts-balance'>; + } satisfies Binding<'account', 'onbudget-accounts-balance'>; } -export function offbudgetAccountBalance() { +export function offBudgetAccountBalance() { return { name: `offbudget-accounts-balance`, query: q('transactions') 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 11450cf4775..9a415988fcf 100644 --- a/packages/loot-core/src/client/state-types/modals.d.ts +++ b/packages/loot-core/src/client/state-types/modals.d.ts @@ -78,6 +78,37 @@ type FinanceModals = { 'delete-budget': { file: File }; + 'duplicate-budget': { + /** The budget file to be duplicated */ + file: File; + /** + * Indicates whether the duplication is initiated from the budget + * management page. This may affect the behavior or UI of the + * duplication process. + */ + managePage?: boolean; + /** + * loadBudget indicates whether to open the 'original' budget, the + * new duplicated 'copy' budget, or no budget ('none'). If 'none' + * duplicate-budget stays on the same page. + */ + loadBudget?: 'none' | 'original' | 'copy'; + /** + * onComplete is called when the DuplicateFileModal is closed. + * @param event the event object will pass back the status of the + * duplicate process. + * 'success' if the budget was duplicated. + * 'failed' if the budget could not be duplicated. This will also + * pass an error on the event object. + * 'canceled' if the DuplicateFileModal was canceled. + * @returns + */ + onComplete?: (event: { + status: 'success' | 'failed' | 'canceled'; + error?: Error; + }) => void; + }; + import: null; 'import-ynab4': null; diff --git a/packages/loot-core/src/mocks/spreadsheet.ts b/packages/loot-core/src/mocks/spreadsheet.ts index cdd02751c8a..87e4456a930 100644 --- a/packages/loot-core/src/mocks/spreadsheet.ts +++ b/packages/loot-core/src/mocks/spreadsheet.ts @@ -23,7 +23,7 @@ export function makeSpreadsheet() { this._getNode(sheetName, name).value = value; }, - bind(sheetName, binding, fields, cb) { + bind(sheetName, binding, cb) { const { name } = binding; const resolvedName = `${sheetName}!${name}`; if (!this.observers[resolvedName]) { diff --git a/packages/loot-core/src/platform/server/fs/index.web.ts b/packages/loot-core/src/platform/server/fs/index.web.ts index 06eebb13ade..0d299888528 100644 --- a/packages/loot-core/src/platform/server/fs/index.web.ts +++ b/packages/loot-core/src/platform/server/fs/index.web.ts @@ -19,11 +19,11 @@ export { join }; export { getDocumentDir, getBudgetDir, _setDocumentDir } from './shared'; export const getDataDir = () => process.env.ACTUAL_DATA_DIR; -export const pathToId = function (filepath) { +export const pathToId = function (filepath: string): string { return filepath.replace(/^\//, '').replace(/\//g, '-'); }; -function _exists(filepath) { +function _exists(filepath: string): boolean { try { FS.readlink(filepath); return true; @@ -47,7 +47,7 @@ function _mkdirRecursively(dir) { } } -function _createFile(filepath) { +function _createFile(filepath: string) { // This can create the file. Check if it exists, if not create a // symlink if it's a sqlite file. Otherwise store in idb @@ -67,7 +67,7 @@ function _createFile(filepath) { return filepath; } -async function _readFile(filepath, opts?: { encoding?: string }) { +async function _readFile(filepath: string, opts?: { encoding?: string }) { // We persist stuff in /documents, but don't need to handle sqlite // file specifically because those are symlinked to a separate // filesystem and will be handled in the BlockedFS @@ -88,7 +88,7 @@ async function _readFile(filepath, opts?: { encoding?: string }) { throw new Error('File does not exist: ' + filepath); } - if (opts.encoding === 'utf8' && ArrayBuffer.isView(item.contents)) { + if (opts?.encoding === 'utf8' && ArrayBuffer.isView(item.contents)) { return String.fromCharCode.apply( null, new Uint16Array(item.contents.buffer), @@ -101,7 +101,7 @@ async function _readFile(filepath, opts?: { encoding?: string }) { } } -function resolveLink(path) { +function resolveLink(path: string): string { try { const { node } = FS.lookupPath(path, { follow: false }); return node.link ? FS.readlink(path) : path; @@ -110,7 +110,7 @@ function resolveLink(path) { } } -async function _writeFile(filepath, contents) { +async function _writeFile(filepath: string, contents): Promise { if (contents instanceof ArrayBuffer) { contents = new Uint8Array(contents); } else if (ArrayBuffer.isView(contents)) { @@ -146,9 +146,53 @@ async function _writeFile(filepath, contents) { } else { FS.writeFile(resolveLink(filepath), contents); } + return true; } -async function _removeFile(filepath) { +async function _copySqlFile( + frompath: string, + topath: string, +): Promise { + _createFile(topath); + + const { store } = await idb.getStore(await idb.getDatabase(), 'files'); + await idb.set(store, { filepath: topath, contents: '' }); + const fromitem = await idb.get(store, frompath); + const fromDbPath = pathToId(fromitem.filepath); + const toDbPath = pathToId(topath); + + const fromfile = BFS.backend.createFile(fromDbPath); + const tofile = BFS.backend.createFile(toDbPath); + + try { + fromfile.open(); + tofile.open(); + const fileSize = fromfile.meta.size; + const blockSize = fromfile.meta.blockSize; + + const buffer = new ArrayBuffer(blockSize); + const bufferView = new Uint8Array(buffer); + + for (let i = 0; i < fileSize; i += blockSize) { + const bytesToRead = Math.min(blockSize, fileSize - i); + fromfile.read(bufferView, 0, bytesToRead, i); + tofile.write(bufferView, 0, bytesToRead, i); + } + } catch (error) { + tofile.close(); + fromfile.close(); + _removeFile(toDbPath); + console.error('Failed to copy database file', error); + return false; + } finally { + tofile.close(); + fromfile.close(); + } + + return true; +} + +async function _removeFile(filepath: string) { if (!NO_PERSIST && filepath.startsWith('/documents')) { const isDb = filepath.endsWith('.sqlite'); @@ -272,22 +316,39 @@ export const size = async function (filepath) { return attrs.size; }; -export const copyFile = async function (frompath, topath) { - // TODO: This reads the whole file into memory, but that's probably - // not a problem. This could be optimized - const contents = await _readFile(frompath); - return _writeFile(topath, contents); +export const copyFile = async function ( + frompath: string, + topath: string, +): Promise { + let result = false; + try { + const contents = await _readFile(frompath); + result = await _writeFile(topath, contents); + } catch (error) { + if (frompath.endsWith('.sqlite') || topath.endsWith('.sqlite')) { + try { + result = await _copySqlFile(frompath, topath); + } catch (secondError) { + throw new Error( + `Failed to copy SQL file from ${frompath} to ${topath}: ${secondError.message}`, + ); + } + } else { + throw error; + } + } + return result; }; -export const readFile = async function (filepath, encoding = 'utf8') { +export const readFile = async function (filepath: string, encoding = 'utf8') { return _readFile(filepath, { encoding }); }; -export const writeFile = async function (filepath, contents) { +export const writeFile = async function (filepath: string, contents) { return _writeFile(filepath, contents); }; -export const removeFile = async function (filepath) { +export const removeFile = async function (filepath: string) { return _removeFile(filepath); }; diff --git a/packages/loot-core/src/server/accounts/rules.ts b/packages/loot-core/src/server/accounts/rules.ts index 20f9ea2d219..449590370fb 100644 --- a/packages/loot-core/src/server/accounts/rules.ts +++ b/packages/loot-core/src/server/accounts/rules.ts @@ -201,6 +201,8 @@ const CONDITION_TYPES = { 'doesNotContain', 'notOneOf', 'and', + 'onBudget', + 'offBudget', ], nullable: true, parse(op, value, fieldName) { @@ -518,6 +520,21 @@ export class Condition { console.log('invalid regexp in matches condition', e); return false; } + + case 'onBudget': + if (!object._account) { + return false; + } + + return object._account.offbudget === 0; + + case 'offBudget': + if (!object._account) { + return false; + } + + return object._account.offbudget === 1; + default: } @@ -948,6 +965,8 @@ const OP_SCORES: Record = { doesNotContain: 0, matches: 0, hasTags: 0, + onBudget: 0, + offBudget: 0, }; function computeScore(rule: Rule): number { diff --git a/packages/loot-core/src/server/accounts/sync.ts b/packages/loot-core/src/server/accounts/sync.ts index aedf1e4f80f..42ff79b628b 100644 --- a/packages/loot-core/src/server/accounts/sync.ts +++ b/packages/loot-core/src/server/accounts/sync.ts @@ -523,6 +523,9 @@ export async function matchTransactions( ); // The first pass runs the rules, and preps data for fuzzy matching + const accounts: AccountEntity[] = await db.getAccounts(); + const accountsMap = new Map(accounts.map(account => [account.id, account])); + const transactionsStep1 = []; for (const { payee_name, @@ -530,7 +533,7 @@ export async function matchTransactions( subtransactions, } of normalized) { // Run the rules - const trans = await runRules(originalTrans); + const trans = await runRules(originalTrans, accountsMap); let match = null; let fuzzyDataset = null; @@ -673,9 +676,12 @@ export async function addTransactions( { rawPayeeName: true }, ); + const accounts: AccountEntity[] = await db.getAccounts(); + const accountsMap = new Map(accounts.map(account => [account.id, account])); + for (const { trans: originalTrans, subtransactions } of normalized) { // Run the rules - const trans = await runRules(originalTrans); + const trans = await runRules(originalTrans, accountsMap); const finalTransaction = { id: uuidv4(), diff --git a/packages/loot-core/src/server/accounts/transaction-rules.ts b/packages/loot-core/src/server/accounts/transaction-rules.ts index 2c2569a5fc8..8a8d52c72bd 100644 --- a/packages/loot-core/src/server/accounts/transaction-rules.ts +++ b/packages/loot-core/src/server/accounts/transaction-rules.ts @@ -13,10 +13,11 @@ import { type TransactionEntity, type RuleActionEntity, type RuleEntity, + AccountEntity, } from '../../types/models'; import { schemaConfig } from '../aql'; import * as db from '../db'; -import { getPayee, getPayeeByName, insertPayee } from '../db'; +import { getPayee, getPayeeByName, insertPayee, getAccount } from '../db'; import { getMappings } from '../db/mappings'; import { RuleError } from '../errors'; import { requiredFields, toDateRepr } from '../models'; @@ -274,8 +275,20 @@ function onApplySync(oldValues, newValues) { } // Runner -export async function runRules(trans) { - let finalTrans = await prepareTransactionForRules({ ...trans }); +export async function runRules( + trans, + accounts: Map | null = null, +) { + let accountsMap = null; + if (accounts === null) { + accountsMap = new Map( + (await db.getAccounts()).map(account => [account.id, account]), + ); + } else { + accountsMap = accounts; + } + + let finalTrans = await prepareTransactionForRules({ ...trans }, accountsMap); const rules = rankRules( fastSetMerge( @@ -291,7 +304,11 @@ export async function runRules(trans) { return await finalizeTransactionForRules(finalTrans); } -function conditionSpecialCases(cond: Condition): Condition { +function conditionSpecialCases(cond: Condition | null): Condition | null { + if (!cond) { + return cond; + } + //special cases that require multiple conditions if (cond.op === 'is' && cond.field === 'category' && cond.value === null) { return new Condition( @@ -555,6 +572,12 @@ export function conditionsToAQL(conditions, { recurDateBounds = 100 } = {}) { return { $and: getValue(value).map(subExpr => mapConditionToActualQL(subExpr)), }; + + case 'onBudget': + return { 'account.offbudget': false }; + case 'offBudget': + return { 'account.offbudget': true }; + default: throw new Error('Unhandled operator: ' + op); } @@ -604,8 +627,14 @@ export async function applyActions( return null; } + const accounts: AccountEntity[] = await db.getAccounts(); const transactionsForRules = await Promise.all( - transactions.map(prepareTransactionForRules), + transactions.map(transactions => + prepareTransactionForRules( + transactions, + new Map(accounts.map(account => [account.id, account])), + ), + ), ); const updated = transactionsForRules.flatMap(trans => { @@ -836,10 +865,12 @@ export async function updateCategoryRules(transactions) { export type TransactionForRules = TransactionEntity & { payee_name?: string; + _account?: AccountEntity; }; export async function prepareTransactionForRules( trans: TransactionEntity, + accounts: Map | null = null, ): Promise { const r: TransactionForRules = { ...trans }; if (trans.payee) { @@ -849,6 +880,14 @@ export async function prepareTransactionForRules( } } + if (trans.account) { + if (accounts !== null && accounts.has(trans.account)) { + r._account = accounts.get(trans.account); + } else { + r._account = await getAccount(trans.account); + } + } + return r; } diff --git a/packages/loot-core/src/server/accounts/transfer.ts b/packages/loot-core/src/server/accounts/transfer.ts index f9c81048378..fa8969667e0 100644 --- a/packages/loot-core/src/server/accounts/transfer.ts +++ b/packages/loot-core/src/server/accounts/transfer.ts @@ -26,7 +26,7 @@ async function clearCategory(transaction, transferAcct) { [transferAcct], ); - // If the transfer is between two on-budget or two off-budget accounts, + // If the transfer is between two on budget or two off budget accounts, // we should clear the category, because the category is not relevant if (fromOffBudget === toOffBudget) { await db.updateTransaction({ id: transaction.id, category: null }); diff --git a/packages/loot-core/src/server/db/index.ts b/packages/loot-core/src/server/db/index.ts index 0893c2aadc8..dc324fca85f 100644 --- a/packages/loot-core/src/server/db/index.ts +++ b/packages/loot-core/src/server/db/index.ts @@ -473,6 +473,10 @@ export async function getPayee(id) { return first(`SELECT * FROM payees WHERE id = ?`, [id]); } +export async function getAccount(id) { + return first(`SELECT * FROM accounts WHERE id = ?`, [id]); +} + export async function insertPayee(payee) { payee = payeeModel.validate(payee); let id; diff --git a/packages/loot-core/src/server/main.ts b/packages/loot-core/src/server/main.ts index aadde848273..89a29484f7a 100644 --- a/packages/loot-core/src/server/main.ts +++ b/packages/loot-core/src/server/main.ts @@ -73,7 +73,11 @@ import * as syncMigrations from './sync/migrate'; import { app as toolsApp } from './tools/app'; import { withUndo, clearUndo, undo, redo } from './undo'; import { updateVersion } from './update'; -import { uniqueFileName, idFromFileName } from './util/budget-name'; +import { + uniqueBudgetName, + idFromBudgetName, + validateBudgetName, +} from './util/budget-name'; const DEMO_BUDGET_ID = '_demo-budget'; const TEST_BUDGET_ID = '_test-budget'; @@ -512,22 +516,8 @@ handlers['make-filters-from-conditions'] = async function ({ conditions }) { }; handlers['getCell'] = async function ({ sheetName, name }) { - // Fields is no longer used - hardcode - const fields = ['name', 'value']; const node = sheet.get()._getNode(resolveName(sheetName, name)); - if (fields) { - const res = {}; - fields.forEach(field => { - if (field === 'run') { - res[field] = node._run ? node._run.toString() : null; - } else { - res[field] = node[field]; - } - }); - return res; - } else { - return node; - } + return { name: node.name, value: node.value }; }; handlers['getCells'] = async function ({ names }) { @@ -1107,7 +1097,7 @@ handlers['accounts-bank-sync'] = async function ({ ids = [] }) { const accounts = await db.runQuery( ` - SELECT a.*, b.bank_id as bankId + SELECT a.*, b.bank_id as bankId FROM accounts a LEFT JOIN banks b ON a.bank = b.id WHERE a.tombstone = 0 AND a.closed = 0 @@ -1710,6 +1700,14 @@ handlers['sync'] = async function () { return fullSync(); }; +handlers['validate-budget-name'] = async function ({ name }) { + return validateBudgetName(name); +}; + +handlers['unique-budget-name'] = async function ({ name }) { + return uniqueBudgetName(name); +}; + handlers['get-budgets'] = async function () { const paths = await fs.listDir(fs.getDocumentDir()); const budgets = ( @@ -1879,7 +1877,7 @@ handlers['close-budget'] = async function () { } prefs.unloadPrefs(); - stopBackupService(); + await stopBackupService(); return 'ok'; }; @@ -1892,13 +1890,102 @@ handlers['delete-budget'] = async function ({ id, cloudFileId }) { // If a local file exists, you can delete it by passing its local id if (id) { - const budgetDir = fs.getBudgetDir(id); - await fs.removeDirRecursively(budgetDir); + // opening and then closing the database is a hack to be able to delete + // the budget file if it hasn't been opened yet. This needs a better + // way, but works for now. + try { + await db.openDatabase(id); + await db.closeDatabase(); + const budgetDir = fs.getBudgetDir(id); + await fs.removeDirRecursively(budgetDir); + } catch (e) { + return 'fail'; + } } return 'ok'; }; +handlers['duplicate-budget'] = async function ({ + id, + newName, + cloudSync, + open, +}): Promise { + if (!id) throw new Error('Unable to duplicate a budget that is not local.'); + + const { valid, message } = await validateBudgetName(newName); + if (!valid) throw new Error(message); + + const budgetDir = fs.getBudgetDir(id); + + const newId = await idFromBudgetName(newName); + + // copy metadata from current budget + // replace id with new budget id and budgetName with new budget name + const metadataText = await fs.readFile(fs.join(budgetDir, 'metadata.json')); + const metadata = JSON.parse(metadataText); + metadata.id = newId; + metadata.budgetName = newName; + [ + 'cloudFileId', + 'groupId', + 'lastUploaded', + 'encryptKeyId', + 'lastSyncedTimestamp', + ].forEach(item => { + if (metadata[item]) delete metadata[item]; + }); + + try { + const newBudgetDir = fs.getBudgetDir(newId); + await fs.mkdir(newBudgetDir); + + // write metadata for new budget + await fs.writeFile( + fs.join(newBudgetDir, 'metadata.json'), + JSON.stringify(metadata), + ); + + await fs.copyFile( + fs.join(budgetDir, 'db.sqlite'), + fs.join(newBudgetDir, 'db.sqlite'), + ); + } catch (error) { + // Clean up any partially created files + try { + const newBudgetDir = fs.getBudgetDir(newId); + if (await fs.exists(newBudgetDir)) { + await fs.removeDirRecursively(newBudgetDir); + } + } catch {} // Ignore cleanup errors + throw new Error(`Failed to duplicate budget: ${error.message}`); + } + + // load in and validate + const { error } = await loadBudget(newId); + if (error) { + console.log('Error duplicating budget: ' + error); + return error; + } + + if (cloudSync) { + try { + await cloudStorage.upload(); + } catch (error) { + console.warn('Failed to sync duplicated budget to cloud:', error); + // Ignore any errors uploading. If they are offline they should + // still be able to create files. + } + } + + handlers['close-budget'](); + if (open === 'original') await loadBudget(id); + if (open === 'copy') await loadBudget(newId); + + return newId; +}; + handlers['create-budget'] = async function ({ budgetName, avoidUpload, @@ -1921,13 +2008,10 @@ handlers['create-budget'] = async function ({ } else { // Generate budget name if not given if (!budgetName) { - // Unfortunately we need to load all of the existing files first - // so we can detect conflicting names. - const files = await handlers['get-budgets'](); - budgetName = await uniqueFileName(files); + budgetName = await uniqueBudgetName(); } - id = await idFromFileName(budgetName); + id = await idFromBudgetName(budgetName); } const budgetDir = fs.getBudgetDir(id); @@ -1993,8 +2077,8 @@ handlers['export-budget'] = async function () { } }; -async function loadBudget(id) { - let dir; +async function loadBudget(id: string) { + let dir: string; try { dir = fs.getBudgetDir(id); } catch (e) { @@ -2071,7 +2155,7 @@ async function loadBudget(id) { !Platform.isMobile && process.env.NODE_ENV !== 'test' ) { - startBackupService(id); + await startBackupService(id); } try { diff --git a/packages/loot-core/src/server/util/budget-name.ts b/packages/loot-core/src/server/util/budget-name.ts index 3c94888f0da..dfe492e5c51 100644 --- a/packages/loot-core/src/server/util/budget-name.ts +++ b/packages/loot-core/src/server/util/budget-name.ts @@ -1,16 +1,18 @@ -// @ts-strict-ignore import { v4 as uuidv4 } from 'uuid'; import * as fs from '../../platform/server/fs'; +import { handlers } from '../main'; -export async function uniqueFileName(existingFiles) { - const initialName = 'My Finances'; +export async function uniqueBudgetName( + initialName: string = 'My Finances', +): Promise { + const budgets = await handlers['get-budgets'](); let idx = 1; // If there is a conflict, keep appending an index until there is no // conflict and we have a unique name let newName = initialName; - while (existingFiles.find(file => file.name === newName)) { + while (budgets.find(file => file.name === newName)) { newName = `${initialName} ${idx}`; idx++; } @@ -18,7 +20,25 @@ export async function uniqueFileName(existingFiles) { return newName; } -export async function idFromFileName(name) { +export async function validateBudgetName( + name: string, +): Promise<{ valid: boolean; message?: string }> { + const trimmedName = name.trim(); + const uniqueName = await uniqueBudgetName(trimmedName); + let message: string | null = null; + + if (trimmedName === '') message = 'Budget name cannot be blank'; + if (trimmedName.length > 100) { + message = 'Budget name is too long (max length 100)'; + } + if (uniqueName !== trimmedName) { + message = `“${name}” already exists, try “${uniqueName}” instead`; + } + + return message ? { valid: false, message } : { valid: true }; +} + +export async function idFromBudgetName(name: string): Promise { let id = name.replace(/( |[^A-Za-z0-9])/g, '-') + '-' + uuidv4().slice(0, 7); // Make sure the id is unique. There's a chance one could already diff --git a/packages/loot-core/src/shared/query.ts b/packages/loot-core/src/shared/query.ts index 4b38e62ac8c..b53ad149867 100644 --- a/packages/loot-core/src/shared/query.ts +++ b/packages/loot-core/src/shared/query.ts @@ -5,18 +5,18 @@ type ObjectExpression = { }; export type QueryState = { - table: string; - tableOptions: Record; - filterExpressions: Array; - selectExpressions: Array; - groupExpressions: Array; - orderExpressions: Array; - calculation: boolean; - rawMode: boolean; - withDead: boolean; - validateRefs: boolean; - limit: number | null; - offset: number | null; + get table(): string; + get tableOptions(): Readonly>; + get filterExpressions(): ReadonlyArray; + get selectExpressions(): ReadonlyArray; + get groupExpressions(): ReadonlyArray; + get orderExpressions(): ReadonlyArray; + get calculation(): boolean; + get rawMode(): boolean; + get withDead(): boolean; + get validateRefs(): boolean; + get limit(): number | null; + get offset(): number | null; }; export class Query { @@ -76,15 +76,19 @@ export class Query { exprs = [exprs]; } - const query = new Query({ ...this.state, selectExpressions: exprs }); - query.state.calculation = false; - return query; + return new Query({ + ...this.state, + selectExpressions: exprs, + calculation: false, + }); } calculate(expr: ObjectExpression | string) { - const query = this.select({ result: expr }); - query.state.calculation = true; - return query; + return new Query({ + ...this.state, + selectExpressions: [{ result: expr }], + calculation: true, + }); } groupBy(exprs: ObjectExpression | string | Array) { @@ -140,6 +144,10 @@ export class Query { serialize() { return this.state; } + + serializeAsString() { + return JSON.stringify(this.serialize()); + } } export function getPrimaryOrderBy( diff --git a/packages/loot-core/src/shared/rules.ts b/packages/loot-core/src/shared/rules.ts index cbfe591964f..82c837fe177 100644 --- a/packages/loot-core/src/shared/rules.ts +++ b/packages/loot-core/src/shared/rules.ts @@ -21,6 +21,8 @@ const TYPE_INFO = { 'isNot', 'doesNotContain', 'notOneOf', + 'onBudget', + 'offBudget', ], nullable: true, }, @@ -65,12 +67,16 @@ const FIELD_INFO = { type: 'string', disallowedOps: new Set(['hasTags']), }, - payee: { type: 'id' }, + payee: { type: 'id', disallowedOps: new Set(['onBudget', 'offBudget']) }, payee_name: { type: 'string' }, date: { type: 'date' }, notes: { type: 'string' }, amount: { type: 'number' }, - category: { type: 'id', internalOps: new Set(['and']) }, + category: { + type: 'id', + disallowedOps: new Set(['onBudget', 'offBudget']), + internalOps: new Set(['and']), + }, account: { type: 'id' }, cleared: { type: 'boolean' }, reconciled: { type: 'boolean' }, @@ -199,6 +205,10 @@ export function friendlyOp(op, type?) { return t('and'); case 'or': return 'or'; + case 'onBudget': + return 'is on budget'; + case 'offBudget': + return 'is off budget'; default: return ''; } diff --git a/packages/loot-core/src/types/models/rule.d.ts b/packages/loot-core/src/types/models/rule.d.ts index 5cbbe7d3697..8d85f2c6634 100644 --- a/packages/loot-core/src/types/models/rule.d.ts +++ b/packages/loot-core/src/types/models/rule.d.ts @@ -27,7 +27,9 @@ export type RuleConditionOp = | 'doesNotContain' | 'hasTags' | 'and' - | 'matches'; + | 'matches' + | 'onBudget' + | 'offBudget'; type FieldValueTypes = { account: string; @@ -76,6 +78,8 @@ export type RuleConditionEntity = | 'contains' | 'doesNotContain' | 'matches' + | 'onBudget' + | 'offBudget' > | BaseConditionEntity< 'category', diff --git a/packages/loot-core/src/types/server-handlers.d.ts b/packages/loot-core/src/types/server-handlers.d.ts index 4afbc72a3e8..92b872e54f5 100644 --- a/packages/loot-core/src/types/server-handlers.d.ts +++ b/packages/loot-core/src/types/server-handlers.d.ts @@ -178,7 +178,7 @@ export interface ServerHandlers { 'account-move': (arg: { id; targetId }) => Promise; - 'secret-set': (arg: { name: string; value: string }) => Promise; + 'secret-set': (arg: { name: string; value: string | null }) => Promise; 'secret-check': (arg: string) => Promise; 'gocardless-poll-web-token': (arg: { @@ -304,6 +304,12 @@ export interface ServerHandlers { | { messages: Message[] } >; + 'validate-budget-name': (arg: { + name: string; + }) => Promise<{ valid: boolean; message?: string }>; + + 'unique-budget-name': (arg: { name: string }) => Promise; + 'get-budgets': () => Promise; 'get-remote-files': () => Promise; @@ -327,7 +333,24 @@ export interface ServerHandlers { 'delete-budget': (arg: { id?: string; cloudFileId?: string; - }) => Promise<'ok'>; + }) => Promise<'ok' | 'fail'>; + + /** + * Duplicates a budget file. + * @param {Object} arg - The arguments for duplicating a budget. + * @param {string} [arg.id] - The ID of the local budget to duplicate. + * @param {string} [arg.cloudId] - The ID of the cloud-synced budget to duplicate. + * @param {string} arg.newName - The name for the duplicated budget. + * @param {boolean} [arg.cloudSync] - Whether to sync the duplicated budget to the cloud. + * @returns {Promise} The ID of the newly created budget. + */ + 'duplicate-budget': (arg: { + id?: string; + cloudId?: string; + newName: string; + cloudSync?: boolean; + open: 'none' | 'original' | 'copy'; + }) => Promise; 'create-budget': (arg: { budgetName?; diff --git a/upcoming-release-notes/3775.md b/upcoming-release-notes/3775.md new file mode 100644 index 00000000000..dc711646600 --- /dev/null +++ b/upcoming-release-notes/3775.md @@ -0,0 +1,6 @@ +--- +category: Enhancements +authors: [UnderKoen] +--- + +Position context menus on the to budget page to the cursor, and make popovers non selectable. diff --git a/upcoming-release-notes/3847.md b/upcoming-release-notes/3847.md new file mode 100644 index 00000000000..785e81f7abe --- /dev/null +++ b/upcoming-release-notes/3847.md @@ -0,0 +1,6 @@ +--- +category: Features +authors: [tlesicka] +--- + +Added ability to duplicate budgets. diff --git a/upcoming-release-notes/3879.md b/upcoming-release-notes/3879.md new file mode 100644 index 00000000000..89887d8dedf --- /dev/null +++ b/upcoming-release-notes/3879.md @@ -0,0 +1,6 @@ +--- +category: Maintenance +authors: [joel-jeremy] +--- + +Optimize useSheetValue hook diff --git a/upcoming-release-notes/3891.md b/upcoming-release-notes/3891.md new file mode 100644 index 00000000000..034344b1d13 --- /dev/null +++ b/upcoming-release-notes/3891.md @@ -0,0 +1,6 @@ +--- +category: Enhancements +authors: [lelemm] +--- + +Filter accounts when on budget or off budget diff --git a/upcoming-release-notes/3899.md b/upcoming-release-notes/3899.md new file mode 100644 index 00000000000..f330782869e --- /dev/null +++ b/upcoming-release-notes/3899.md @@ -0,0 +1,6 @@ +--- +category: Maintenance +authors: [joel-jeremy] +--- + +Convert BudgetTable.jsx to TypeScript diff --git a/upcoming-release-notes/3900.md b/upcoming-release-notes/3900.md new file mode 100644 index 00000000000..6482218bc0c --- /dev/null +++ b/upcoming-release-notes/3900.md @@ -0,0 +1,6 @@ +--- +category: Enhancements +authors: [joel-jeremy] +--- + +Add loading indicator when loading more transactions in mobile transaction list. diff --git a/upcoming-release-notes/3903.md b/upcoming-release-notes/3903.md new file mode 100644 index 00000000000..14f2456f37c --- /dev/null +++ b/upcoming-release-notes/3903.md @@ -0,0 +1,6 @@ +--- +category: Maintenance +authors: [joel-jeremy] +--- + +Use consistent terms for on budget accounts i.e. `For budget`/`Budgeted` --> `On budget`. diff --git a/upcoming-release-notes/3911.md b/upcoming-release-notes/3911.md new file mode 100644 index 00000000000..01f7348949c --- /dev/null +++ b/upcoming-release-notes/3911.md @@ -0,0 +1,6 @@ +--- +category: Maintenance +authors: [joel-jeremy] +--- + +Remove usage of useActions hook diff --git a/upcoming-release-notes/3942.md b/upcoming-release-notes/3942.md new file mode 100644 index 00000000000..c66d2b9326b --- /dev/null +++ b/upcoming-release-notes/3942.md @@ -0,0 +1,6 @@ +--- +category: Bugfix +authors: [MatissJanis] +--- + +Fix misaligned gocardless credential popover. diff --git a/upcoming-release-notes/3943.md b/upcoming-release-notes/3943.md new file mode 100644 index 00000000000..988e0ac840c --- /dev/null +++ b/upcoming-release-notes/3943.md @@ -0,0 +1,6 @@ +--- +category: Bugfix +authors: [MatissJanis] +--- + +Fix rule creation throwing error for "notes contains (nothing)" condition. diff --git a/upcoming-release-notes/3944.md b/upcoming-release-notes/3944.md new file mode 100644 index 00000000000..5d73da1eeec --- /dev/null +++ b/upcoming-release-notes/3944.md @@ -0,0 +1,6 @@ +--- +category: Bugfix +authors: [adamhl8] +--- + +Fix tracking budget docs link in settings.