diff --git a/.github/workflows/docker-edge.yml b/.github/workflows/docker-edge.yml index 3fe1d545721..baf3d04bac5 100644 --- a/.github/workflows/docker-edge.yml +++ b/.github/workflows/docker-edge.yml @@ -33,6 +33,7 @@ env: jobs: build: + if: ${{ github.event.repository.fork == false }} name: Build Docker image runs-on: ubuntu-latest strategy: diff --git a/eslint.config.mjs b/eslint.config.mjs index 7f065e79b8e..1396a4fce40 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -790,20 +790,6 @@ export default [ 'packages/desktop-client/src/components/sidebar/Tools.tsx', 'packages/desktop-client/src/components/sort.tsx', 'packages/desktop-client/src/components/spreadsheet/useSheetValue.ts', - 'packages/desktop-client/src/components/table.tsx', - 'packages/desktop-client/src/components/Titlebar.tsx', - 'packages/desktop-client/src/components/transactions/MobileTransaction.jsx', - 'packages/desktop-client/src/components/transactions/SelectedTransactions.jsx', - 'packages/desktop-client/src/components/transactions/SimpleTransactionsTable.jsx', - 'packages/desktop-client/src/components/transactions/TransactionList.jsx', - 'packages/desktop-client/src/components/transactions/TransactionsTable.jsx', - 'packages/desktop-client/src/components/transactions/TransactionsTable.test.jsx', - 'packages/desktop-client/src/hooks/useAccounts.ts', - 'packages/desktop-client/src/hooks/useCategories.ts', - 'packages/desktop-client/src/hooks/usePayees.ts', - 'packages/desktop-client/src/hooks/useProperFocus.tsx', - 'packages/desktop-client/src/hooks/useSelected.tsx', - 'packages/loot-core/src/client/query-hooks.tsx', ], rules: { diff --git a/package.json b/package.json index 4d1d66729a4..bd8048a0fb6 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,9 @@ }, "scripts": { "start": "yarn start:browser", - "start:server": "yarn workspace actual-sync start", + "start:server": "yarn workspace @actual-app/sync-server start", + "start:server-monitor": "yarn workspace @actual-app/sync-server start-monitor", + "start:server-dev": "NODE_ENV=development BROWSER_OPEN=localhost:5006 yarn npm-run-all --parallel 'start:server-monitor' 'start'", "start:desktop": "yarn rebuild-electron && npm-run-all --parallel 'start:desktop-*'", "start:desktop-node": "yarn workspace loot-core watch:node", "start:desktop-client": "yarn workspace @actual-app/web watch", @@ -41,7 +43,7 @@ "rebuild-node": "yarn workspace loot-core rebuild", "lint": "eslint . --max-warnings 0", "lint:verbose": "DEBUG=eslint:cli-engine eslint . --max-warnings 0", - "install:server": "yarn workspaces focus actual-sync --production", + "install:server": "yarn workspaces focus @actual-app/sync-server --production", "typecheck": "yarn tsc && tsc-strict", "jq": "./node_modules/node-jq/bin/jq", "prepare": "husky" diff --git a/packages/component-library/src/styles.ts b/packages/component-library/src/styles.ts index c5ac0e22b56..ea21ddd0a9c 100644 --- a/packages/component-library/src/styles.ts +++ b/packages/component-library/src/styles.ts @@ -148,4 +148,10 @@ export const styles: Record = { lightScrollbar: null as CSSProperties | null, darkScrollbar: null as CSSProperties | null, scrollbarWidth: null as number | null, + editorPill: { + color: theme.pillText, + backgroundColor: theme.pillBackground, + borderRadius: 4, + padding: '3px 5px', + }, }; diff --git a/packages/desktop-client/e2e/accounts.mobile.test.ts-snapshots/Mobile-Accounts-opens-individual-account-page-and-checks-that-filtering-is-working-1-chromium-linux.png b/packages/desktop-client/e2e/accounts.mobile.test.ts-snapshots/Mobile-Accounts-opens-individual-account-page-and-checks-that-filtering-is-working-1-chromium-linux.png index cb84ec53392..1ad2d39d322 100644 Binary files a/packages/desktop-client/e2e/accounts.mobile.test.ts-snapshots/Mobile-Accounts-opens-individual-account-page-and-checks-that-filtering-is-working-1-chromium-linux.png and b/packages/desktop-client/e2e/accounts.mobile.test.ts-snapshots/Mobile-Accounts-opens-individual-account-page-and-checks-that-filtering-is-working-1-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/accounts.mobile.test.ts-snapshots/Mobile-Accounts-opens-individual-account-page-and-checks-that-filtering-is-working-2-chromium-linux.png b/packages/desktop-client/e2e/accounts.mobile.test.ts-snapshots/Mobile-Accounts-opens-individual-account-page-and-checks-that-filtering-is-working-2-chromium-linux.png index 2e33859da81..abf12e0eff4 100644 Binary files a/packages/desktop-client/e2e/accounts.mobile.test.ts-snapshots/Mobile-Accounts-opens-individual-account-page-and-checks-that-filtering-is-working-2-chromium-linux.png and b/packages/desktop-client/e2e/accounts.mobile.test.ts-snapshots/Mobile-Accounts-opens-individual-account-page-and-checks-that-filtering-is-working-2-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/accounts.mobile.test.ts-snapshots/Mobile-Accounts-opens-individual-account-page-and-checks-that-filtering-is-working-3-chromium-linux.png b/packages/desktop-client/e2e/accounts.mobile.test.ts-snapshots/Mobile-Accounts-opens-individual-account-page-and-checks-that-filtering-is-working-3-chromium-linux.png index 03cc182961c..59e18cee676 100644 Binary files a/packages/desktop-client/e2e/accounts.mobile.test.ts-snapshots/Mobile-Accounts-opens-individual-account-page-and-checks-that-filtering-is-working-3-chromium-linux.png and b/packages/desktop-client/e2e/accounts.mobile.test.ts-snapshots/Mobile-Accounts-opens-individual-account-page-and-checks-that-filtering-is-working-3-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-applies-budget-template-1-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-applies-budget-template-1-chromium-linux.png index 15255672a53..a09a8a2dce3 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-applies-budget-template-1-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-applies-budget-template-1-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-applies-budget-template-2-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-applies-budget-template-2-chromium-linux.png index 563cb12d538..4c31f8f3bdf 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-applies-budget-template-2-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-applies-budget-template-2-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-applies-budget-template-3-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-applies-budget-template-3-chromium-linux.png index f5330ca0bdc..c17afe1bf9b 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-applies-budget-template-3-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-applies-budget-template-3-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-checks-that-clicking--14404-in-the-page-header-opens-the-month-menu-modal-3-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-checks-that-clicking--14404-in-the-page-header-opens-the-month-menu-modal-3-chromium-linux.png index d5954385694..d2659616376 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-checks-that-clicking--14404-in-the-page-header-opens-the-month-menu-modal-3-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-checks-that-clicking--14404-in-the-page-header-opens-the-month-menu-modal-3-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.mobile.test.ts-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.ts-snapshots/Mobile-Budget-Envelope-checks-that-clicking--321fd-ed-amount-opens-the-budget-summary-menu-modal-2-chromium-linux.png index 4e6252fec76..e877ef6e32d 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-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.ts-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.ts-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.ts-snapshots/Mobile-Budget-Envelope-checks-that-clicking--4bb70-ed-amount-opens-the-budget-summary-menu-modal-1-chromium-linux.png index 65c60199b30..5a916e40e7c 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-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.ts-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.ts-snapshots/Mobile-Budget-Envelope-checks-that-clicking--589a6-the-page-header-shows-the-next-month-s-budget-2-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-checks-that-clicking--589a6-the-page-header-shows-the-next-month-s-budget-2-chromium-linux.png index 058a5667394..f234dfd9f50 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-checks-that-clicking--589a6-the-page-header-shows-the-next-month-s-budget-2-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-checks-that-clicking--589a6-the-page-header-shows-the-next-month-s-budget-2-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-checks-that-clicking--6ab37-roup-name-opens-the-category-group-menu-modal-1-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-checks-that-clicking--6ab37-roup-name-opens-the-category-group-menu-modal-1-chromium-linux.png index 488a1a1eef8..4e288662e02 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-checks-that-clicking--6ab37-roup-name-opens-the-category-group-menu-modal-1-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-checks-that-clicking--6ab37-roup-name-opens-the-category-group-menu-modal-1-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-checks-that-clicking--6dbdb-page-header-shows-the-previous-month-s-budget-1-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-checks-that-clicking--6dbdb-page-header-shows-the-previous-month-s-budget-1-chromium-linux.png index 96bba7fcc06..840c615edcb 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-checks-that-clicking--6dbdb-page-header-shows-the-previous-month-s-budget-1-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-checks-that-clicking--6dbdb-page-header-shows-the-previous-month-s-budget-1-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-checks-that-clicking--7bd8f-the-page-header-shows-the-next-month-s-budget-3-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-checks-that-clicking--7bd8f-the-page-header-shows-the-next-month-s-budget-3-chromium-linux.png index 988870579d7..ba95fa68fd9 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-checks-that-clicking--7bd8f-the-page-header-shows-the-next-month-s-budget-3-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-checks-that-clicking--7bd8f-the-page-header-shows-the-next-month-s-budget-3-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-checks-that-clicking--884ac-the-page-header-shows-the-next-month-s-budget-1-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-checks-that-clicking--884ac-the-page-header-shows-the-next-month-s-budget-1-chromium-linux.png index 460e95731b2..d9bcaf20707 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-checks-that-clicking--884ac-the-page-header-shows-the-next-month-s-budget-1-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-checks-that-clicking--884ac-the-page-header-shows-the-next-month-s-budget-1-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-checks-that-clicking--94a79-roup-name-opens-the-category-group-menu-modal-2-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-checks-that-clicking--94a79-roup-name-opens-the-category-group-menu-modal-2-chromium-linux.png index 864a20db262..a0c256d2f68 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-checks-that-clicking--94a79-roup-name-opens-the-category-group-menu-modal-2-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-checks-that-clicking--94a79-roup-name-opens-the-category-group-menu-modal-2-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.mobile.test.ts-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.ts-snapshots/Mobile-Budget-Envelope-checks-that-clicking--94a85-ed-amount-opens-the-budget-summary-menu-modal-3-chromium-linux.png index 55a7c2da4a5..fb15769e771 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-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.ts-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.ts-snapshots/Mobile-Budget-Envelope-checks-that-clicking--96ebb-page-header-shows-the-previous-month-s-budget-3-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-checks-that-clicking--96ebb-page-header-shows-the-previous-month-s-budget-3-chromium-linux.png index 0d7aac39903..3dceb48d7fd 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-checks-that-clicking--96ebb-page-header-shows-the-previous-month-s-budget-3-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-checks-that-clicking--96ebb-page-header-shows-the-previous-month-s-budget-3-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-checks-that-clicking--9e6aa-in-the-page-header-opens-the-budget-page-menu-1-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-checks-that-clicking--9e6aa-in-the-page-header-opens-the-budget-page-menu-1-chromium-linux.png index 8095e242ede..a290f3445b6 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-checks-that-clicking--9e6aa-in-the-page-header-opens-the-budget-page-menu-1-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-checks-that-clicking--9e6aa-in-the-page-header-opens-the-budget-page-menu-1-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-checks-that-clicking--bbde3-roup-name-opens-the-category-group-menu-modal-3-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-checks-that-clicking--bbde3-roup-name-opens-the-category-group-menu-modal-3-chromium-linux.png index fe047e927c7..a776161a5eb 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-checks-that-clicking--bbde3-roup-name-opens-the-category-group-menu-modal-3-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-checks-that-clicking--bbde3-roup-name-opens-the-category-group-menu-modal-3-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-checks-that-clicking--bed18-in-the-page-header-opens-the-month-menu-modal-1-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-checks-that-clicking--bed18-in-the-page-header-opens-the-month-menu-modal-1-chromium-linux.png index 702e3ee8ff2..10737edb3ae 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-checks-that-clicking--bed18-in-the-page-header-opens-the-month-menu-modal-1-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-checks-that-clicking--bed18-in-the-page-header-opens-the-month-menu-modal-1-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-checks-that-clicking--ceb3a-in-the-page-header-opens-the-month-menu-modal-2-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-checks-that-clicking--ceb3a-in-the-page-header-opens-the-month-menu-modal-2-chromium-linux.png index 41cced4df73..ce2728cbc87 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-checks-that-clicking--ceb3a-in-the-page-header-opens-the-month-menu-modal-2-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-checks-that-clicking--ceb3a-in-the-page-header-opens-the-month-menu-modal-2-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-checks-that-clicking--d270d-in-the-page-header-opens-the-budget-page-menu-2-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-checks-that-clicking--d270d-in-the-page-header-opens-the-budget-page-menu-2-chromium-linux.png index 36d8a1a9b35..eebcb154084 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-checks-that-clicking--d270d-in-the-page-header-opens-the-budget-page-menu-2-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-checks-that-clicking--d270d-in-the-page-header-opens-the-budget-page-menu-2-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-checks-that-clicking--d7184-page-header-shows-the-previous-month-s-budget-2-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-checks-that-clicking--d7184-page-header-shows-the-previous-month-s-budget-2-chromium-linux.png index b1807e1f4c5..1efe67bb6ac 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-checks-that-clicking--d7184-page-header-shows-the-previous-month-s-budget-2-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-checks-that-clicking--d7184-page-header-shows-the-previous-month-s-budget-2-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-checks-that-clicking--fdd57-in-the-page-header-opens-the-budget-page-menu-3-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-checks-that-clicking--fdd57-in-the-page-header-opens-the-budget-page-menu-3-chromium-linux.png index ac2b51b1a84..52e2f6f1224 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-checks-that-clicking--fdd57-in-the-page-header-opens-the-budget-page-menu-3-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-checks-that-clicking--fdd57-in-the-page-header-opens-the-budget-page-menu-3-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-checks-that-clicking-the-balance-cell-opens-the-balance-menu-modal-1-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-checks-that-clicking-the-balance-cell-opens-the-balance-menu-modal-1-chromium-linux.png index 9a4018ca1cc..965fbe10a63 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-checks-that-clicking-the-balance-cell-opens-the-balance-menu-modal-1-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-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.ts-snapshots/Mobile-Budget-Envelope-checks-that-clicking-the-balance-cell-opens-the-balance-menu-modal-2-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-checks-that-clicking-the-balance-cell-opens-the-balance-menu-modal-2-chromium-linux.png index 26354764c8e..b8ecd517062 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-checks-that-clicking-the-balance-cell-opens-the-balance-menu-modal-2-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-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.ts-snapshots/Mobile-Budget-Envelope-checks-that-clicking-the-balance-cell-opens-the-balance-menu-modal-3-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-checks-that-clicking-the-balance-cell-opens-the-balance-menu-modal-3-chromium-linux.png index 6aaaed2de0d..8432e50d7a1 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-checks-that-clicking-the-balance-cell-opens-the-balance-menu-modal-3-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-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.ts-snapshots/Mobile-Budget-Envelope-checks-that-clicking-the-budgeted-cell-opens-the-budget-menu-modal-1-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-checks-that-clicking-the-budgeted-cell-opens-the-budget-menu-modal-1-chromium-linux.png index e2cdde0d388..bd2ba94d3d4 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-checks-that-clicking-the-budgeted-cell-opens-the-budget-menu-modal-1-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-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.ts-snapshots/Mobile-Budget-Envelope-checks-that-clicking-the-budgeted-cell-opens-the-budget-menu-modal-2-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-checks-that-clicking-the-budgeted-cell-opens-the-budget-menu-modal-2-chromium-linux.png index e349b84c89a..637688a2133 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-checks-that-clicking-the-budgeted-cell-opens-the-budget-menu-modal-2-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-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.ts-snapshots/Mobile-Budget-Envelope-checks-that-clicking-the-budgeted-cell-opens-the-budget-menu-modal-3-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-checks-that-clicking-the-budgeted-cell-opens-the-budget-menu-modal-3-chromium-linux.png index 66c6fb6ea03..f445bf810f8 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-checks-that-clicking-the-budgeted-cell-opens-the-budget-menu-modal-3-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-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.ts-snapshots/Mobile-Budget-Envelope-checks-that-clicking-the-category-name-opens-the-category-menu-modal-1-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-checks-that-clicking-the-category-name-opens-the-category-menu-modal-1-chromium-linux.png index f6ee99e7e85..c1ff14681a0 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-checks-that-clicking-the-category-name-opens-the-category-menu-modal-1-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-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.ts-snapshots/Mobile-Budget-Envelope-checks-that-clicking-the-category-name-opens-the-category-menu-modal-2-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-checks-that-clicking-the-category-name-opens-the-category-menu-modal-2-chromium-linux.png index f43f528db36..ff93ade188a 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-checks-that-clicking-the-category-name-opens-the-category-menu-modal-2-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-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.ts-snapshots/Mobile-Budget-Envelope-checks-that-clicking-the-category-name-opens-the-category-menu-modal-3-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-checks-that-clicking-the-category-name-opens-the-category-menu-modal-3-chromium-linux.png index afab8bed374..18612390e0b 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-checks-that-clicking-the-category-name-opens-the-category-menu-modal-3-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-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.ts-snapshots/Mobile-Budget-Envelope-copies-last-month-s-budget-1-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-copies-last-month-s-budget-1-chromium-linux.png index 868fde3b913..d2fc215a77c 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-copies-last-month-s-budget-1-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-copies-last-month-s-budget-1-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-copies-last-month-s-budget-2-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-copies-last-month-s-budget-2-chromium-linux.png index 2734b1732e2..756810dac33 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-copies-last-month-s-budget-2-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-copies-last-month-s-budget-2-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-copies-last-month-s-budget-3-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-copies-last-month-s-budget-3-chromium-linux.png index 3d73f699537..50254cba2d7 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-copies-last-month-s-budget-3-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-copies-last-month-s-budget-3-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-loads-the-budget-page-with-budgeted-amounts-1-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-loads-the-budget-page-with-budgeted-amounts-1-chromium-linux.png index e8b9fcc0a78..6c3647f527b 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-loads-the-budget-page-with-budgeted-amounts-1-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-loads-the-budget-page-with-budgeted-amounts-1-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-loads-the-budget-page-with-budgeted-amounts-2-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-loads-the-budget-page-with-budgeted-amounts-2-chromium-linux.png index e035685b377..208274acea0 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-loads-the-budget-page-with-budgeted-amounts-2-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-loads-the-budget-page-with-budgeted-amounts-2-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-loads-the-budget-page-with-budgeted-amounts-3-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-loads-the-budget-page-with-budgeted-amounts-3-chromium-linux.png index 03e5347f49c..1efba4b14a0 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-loads-the-budget-page-with-budgeted-amounts-3-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-loads-the-budget-page-with-budgeted-amounts-3-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-set-budget-to-12-month-average-1-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-set-budget-to-12-month-average-1-chromium-linux.png index cfb7676153c..eb65be1c244 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-set-budget-to-12-month-average-1-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-set-budget-to-12-month-average-1-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-set-budget-to-12-month-average-2-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-set-budget-to-12-month-average-2-chromium-linux.png index 2d90739b65b..f09ea6eb707 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-set-budget-to-12-month-average-2-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-set-budget-to-12-month-average-2-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-set-budget-to-12-month-average-3-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-set-budget-to-12-month-average-3-chromium-linux.png index c1fe2134fc8..369837c5258 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-set-budget-to-12-month-average-3-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-set-budget-to-12-month-average-3-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-set-budget-to-3-month-average-1-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-set-budget-to-3-month-average-1-chromium-linux.png index 6c727d9aed0..688d04023ae 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-set-budget-to-3-month-average-1-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-set-budget-to-3-month-average-1-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-set-budget-to-3-month-average-2-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-set-budget-to-3-month-average-2-chromium-linux.png index 5779b5a4803..5679a056b32 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-set-budget-to-3-month-average-2-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-set-budget-to-3-month-average-2-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-set-budget-to-3-month-average-3-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-set-budget-to-3-month-average-3-chromium-linux.png index 726c57331af..684af02564a 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-set-budget-to-3-month-average-3-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-set-budget-to-3-month-average-3-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-set-budget-to-6-month-average-1-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-set-budget-to-6-month-average-1-chromium-linux.png index 6707abb9169..7532fff00ed 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-set-budget-to-6-month-average-1-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-set-budget-to-6-month-average-1-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-set-budget-to-6-month-average-2-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-set-budget-to-6-month-average-2-chromium-linux.png index b84640ca13c..a99d70c5e20 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-set-budget-to-6-month-average-2-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-set-budget-to-6-month-average-2-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-set-budget-to-6-month-average-3-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-set-budget-to-6-month-average-3-chromium-linux.png index 1445a7f3912..fa852315aa7 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-set-budget-to-6-month-average-3-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-set-budget-to-6-month-average-3-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-updates-the-budgeted-amount-1-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-updates-the-budgeted-amount-1-chromium-linux.png index 8d1410f1419..cdd4a4df820 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-updates-the-budgeted-amount-1-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-updates-the-budgeted-amount-1-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-updates-the-budgeted-amount-2-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-updates-the-budgeted-amount-2-chromium-linux.png index 5eee10c8c07..c7c991acab6 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-updates-the-budgeted-amount-2-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-updates-the-budgeted-amount-2-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-updates-the-budgeted-amount-3-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-updates-the-budgeted-amount-3-chromium-linux.png index 01cf9184a29..de2dadbce3f 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-updates-the-budgeted-amount-3-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Envelope-updates-the-budgeted-amount-3-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Tracking-applies-budget-template-1-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Tracking-applies-budget-template-1-chromium-linux.png index 74f34fcd050..97fa9a3c9ae 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Tracking-applies-budget-template-1-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Tracking-applies-budget-template-1-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Tracking-applies-budget-template-2-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Tracking-applies-budget-template-2-chromium-linux.png index ea66cde7373..f09a12bc770 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Tracking-applies-budget-template-2-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Tracking-applies-budget-template-2-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Tracking-applies-budget-template-3-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Tracking-applies-budget-template-3-chromium-linux.png index d02580c3f5f..a71d5ece56e 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Tracking-applies-budget-template-3-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Tracking-applies-budget-template-3-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.mobile.test.ts-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.ts-snapshots/Mobile-Budget-Tracking-checks-that-clicking--0ba04-nt-amount-opens-the-budget-summary-menu-modal-1-chromium-linux.png index 35aa12cd8cf..35962825af5 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-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.ts-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.ts-snapshots/Mobile-Budget-Tracking-checks-that-clicking--0dfe7-page-header-shows-the-previous-month-s-budget-1-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Tracking-checks-that-clicking--0dfe7-page-header-shows-the-previous-month-s-budget-1-chromium-linux.png index 73e12fc76ea..f08aae653bf 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Tracking-checks-that-clicking--0dfe7-page-header-shows-the-previous-month-s-budget-1-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Tracking-checks-that-clicking--0dfe7-page-header-shows-the-previous-month-s-budget-1-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.mobile.test.ts-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.ts-snapshots/Mobile-Budget-Tracking-checks-that-clicking--1ce6d-nt-amount-opens-the-budget-summary-menu-modal-2-chromium-linux.png index 5a748dfd7ee..7129a1c3a95 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-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.ts-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.ts-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.ts-snapshots/Mobile-Budget-Tracking-checks-that-clicking--42062-in-the-page-header-opens-the-month-menu-modal-2-chromium-linux.png index 8771e50c51a..60e086f309c 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-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.ts-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.ts-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.ts-snapshots/Mobile-Budget-Tracking-checks-that-clicking--49fb6-in-the-page-header-opens-the-month-menu-modal-3-chromium-linux.png index 73889255c41..5e0e887facc 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-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.ts-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.ts-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.ts-snapshots/Mobile-Budget-Tracking-checks-that-clicking--5f098-roup-name-opens-the-category-group-menu-modal-1-chromium-linux.png index c4d46a75fd6..c835fb4b9a9 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-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.ts-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.ts-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.ts-snapshots/Mobile-Budget-Tracking-checks-that-clicking--7c353-the-page-header-shows-the-next-month-s-budget-3-chromium-linux.png index 16b144855b5..2dc65951651 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-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.ts-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.ts-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.ts-snapshots/Mobile-Budget-Tracking-checks-that-clicking--929be-roup-name-opens-the-category-group-menu-modal-3-chromium-linux.png index 7bbb28410fb..9fa793e405d 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-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.ts-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.ts-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.ts-snapshots/Mobile-Budget-Tracking-checks-that-clicking--a3783-in-the-page-header-opens-the-budget-page-menu-1-chromium-linux.png index 1b22662391e..0b024176507 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-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.ts-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.ts-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.ts-snapshots/Mobile-Budget-Tracking-checks-that-clicking--a8b5e-in-the-page-header-opens-the-budget-page-menu-3-chromium-linux.png index cf602b52a12..c285e167145 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-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.ts-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.ts-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.ts-snapshots/Mobile-Budget-Tracking-checks-that-clicking--b1562-in-the-page-header-opens-the-month-menu-modal-1-chromium-linux.png index b16c2a13db2..dbf8c2f8e98 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-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.ts-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.ts-snapshots/Mobile-Budget-Tracking-checks-that-clicking--cfb69-page-header-shows-the-previous-month-s-budget-2-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Tracking-checks-that-clicking--cfb69-page-header-shows-the-previous-month-s-budget-2-chromium-linux.png index a7fc68c3c29..6c386b9c4e1 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Tracking-checks-that-clicking--cfb69-page-header-shows-the-previous-month-s-budget-2-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Tracking-checks-that-clicking--cfb69-page-header-shows-the-previous-month-s-budget-2-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.mobile.test.ts-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.ts-snapshots/Mobile-Budget-Tracking-checks-that-clicking--d5af6-the-page-header-shows-the-next-month-s-budget-1-chromium-linux.png index 74b81149829..c87750ded37 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-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.ts-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.ts-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.ts-snapshots/Mobile-Budget-Tracking-checks-that-clicking--dc927-roup-name-opens-the-category-group-menu-modal-2-chromium-linux.png index 2aeeadb6513..a7e4624b552 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-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.ts-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.ts-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.ts-snapshots/Mobile-Budget-Tracking-checks-that-clicking--f2198-the-page-header-shows-the-next-month-s-budget-2-chromium-linux.png index ccfa385f2f1..aa744a601bd 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-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.ts-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.ts-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.ts-snapshots/Mobile-Budget-Tracking-checks-that-clicking--f224f-nt-amount-opens-the-budget-summary-menu-modal-3-chromium-linux.png index 2bc864ccd16..5012ebed6af 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-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.ts-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.ts-snapshots/Mobile-Budget-Tracking-checks-that-clicking--f7fa3-page-header-shows-the-previous-month-s-budget-3-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Tracking-checks-that-clicking--f7fa3-page-header-shows-the-previous-month-s-budget-3-chromium-linux.png index bce1db2c68e..a7a4b942a7f 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Tracking-checks-that-clicking--f7fa3-page-header-shows-the-previous-month-s-budget-3-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Tracking-checks-that-clicking--f7fa3-page-header-shows-the-previous-month-s-budget-3-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.mobile.test.ts-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.ts-snapshots/Mobile-Budget-Tracking-checks-that-clicking--f8a19-in-the-page-header-opens-the-budget-page-menu-2-chromium-linux.png index ac5fb95ee8c..e0dc12c4fdb 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-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.ts-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.ts-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.ts-snapshots/Mobile-Budget-Tracking-checks-that-clicking-the-balance-cell-opens-the-balance-menu-modal-1-chromium-linux.png index 69b5304e0d0..a8367743e84 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-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.ts-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.ts-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.ts-snapshots/Mobile-Budget-Tracking-checks-that-clicking-the-balance-cell-opens-the-balance-menu-modal-2-chromium-linux.png index b58968edc91..98d654c05be 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-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.ts-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.ts-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.ts-snapshots/Mobile-Budget-Tracking-checks-that-clicking-the-balance-cell-opens-the-balance-menu-modal-3-chromium-linux.png index 5d5c9e88589..3ddd24ae4f2 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-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.ts-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.ts-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.ts-snapshots/Mobile-Budget-Tracking-checks-that-clicking-the-budgeted-cell-opens-the-budget-menu-modal-1-chromium-linux.png index d47bb949f4f..b56d301b2dc 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-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.ts-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.ts-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.ts-snapshots/Mobile-Budget-Tracking-checks-that-clicking-the-budgeted-cell-opens-the-budget-menu-modal-2-chromium-linux.png index 0fedc45eb04..b5220fd98be 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-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.ts-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.ts-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.ts-snapshots/Mobile-Budget-Tracking-checks-that-clicking-the-budgeted-cell-opens-the-budget-menu-modal-3-chromium-linux.png index 40d0b0f408a..edf317632d0 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-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.ts-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.ts-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.ts-snapshots/Mobile-Budget-Tracking-checks-that-clicking-the-category-name-opens-the-category-menu-modal-1-chromium-linux.png index 6c748af4cd7..717c11c0f7e 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-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.ts-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.ts-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.ts-snapshots/Mobile-Budget-Tracking-checks-that-clicking-the-category-name-opens-the-category-menu-modal-2-chromium-linux.png index 89b5a6cd2c9..c55e4caa729 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-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.ts-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.ts-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.ts-snapshots/Mobile-Budget-Tracking-checks-that-clicking-the-category-name-opens-the-category-menu-modal-3-chromium-linux.png index a23bf489f0e..89331cc0a6d 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-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.ts-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.ts-snapshots/Mobile-Budget-Tracking-copies-last-month-s-budget-1-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Tracking-copies-last-month-s-budget-1-chromium-linux.png index 7ccff58c183..3d8924bb61e 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Tracking-copies-last-month-s-budget-1-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Tracking-copies-last-month-s-budget-1-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Tracking-copies-last-month-s-budget-2-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Tracking-copies-last-month-s-budget-2-chromium-linux.png index 94d0d64dc2f..108ad326b72 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Tracking-copies-last-month-s-budget-2-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Tracking-copies-last-month-s-budget-2-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Tracking-copies-last-month-s-budget-3-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Tracking-copies-last-month-s-budget-3-chromium-linux.png index 18ff1d4e89e..1fa98576de2 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Tracking-copies-last-month-s-budget-3-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Tracking-copies-last-month-s-budget-3-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Tracking-loads-the-budget-page-with-budgeted-amounts-1-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Tracking-loads-the-budget-page-with-budgeted-amounts-1-chromium-linux.png index 9b379af8aa4..1e7f594b648 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Tracking-loads-the-budget-page-with-budgeted-amounts-1-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.ts-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.ts-snapshots/Mobile-Budget-Tracking-loads-the-budget-page-with-budgeted-amounts-2-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Tracking-loads-the-budget-page-with-budgeted-amounts-2-chromium-linux.png index 0ed4936a89c..27ac56d0afa 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Tracking-loads-the-budget-page-with-budgeted-amounts-2-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.ts-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.ts-snapshots/Mobile-Budget-Tracking-loads-the-budget-page-with-budgeted-amounts-3-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Tracking-loads-the-budget-page-with-budgeted-amounts-3-chromium-linux.png index cf697f2f37b..8e54cfd1843 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Tracking-loads-the-budget-page-with-budgeted-amounts-3-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.ts-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.ts-snapshots/Mobile-Budget-Tracking-set-budget-to-12-month-average-1-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Tracking-set-budget-to-12-month-average-1-chromium-linux.png index fb5f9a10041..6b0042f0228 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Tracking-set-budget-to-12-month-average-1-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Tracking-set-budget-to-12-month-average-1-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Tracking-set-budget-to-12-month-average-2-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Tracking-set-budget-to-12-month-average-2-chromium-linux.png index 48021d7b6fd..7970352a7d5 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Tracking-set-budget-to-12-month-average-2-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Tracking-set-budget-to-12-month-average-2-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Tracking-set-budget-to-12-month-average-3-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Tracking-set-budget-to-12-month-average-3-chromium-linux.png index 8aea9621129..68b8adc8421 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Tracking-set-budget-to-12-month-average-3-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Tracking-set-budget-to-12-month-average-3-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Tracking-set-budget-to-3-month-average-1-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Tracking-set-budget-to-3-month-average-1-chromium-linux.png index f0339368f86..a9046b593db 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Tracking-set-budget-to-3-month-average-1-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Tracking-set-budget-to-3-month-average-1-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Tracking-set-budget-to-3-month-average-2-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Tracking-set-budget-to-3-month-average-2-chromium-linux.png index 548c941757d..79ed5662f19 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Tracking-set-budget-to-3-month-average-2-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Tracking-set-budget-to-3-month-average-2-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Tracking-set-budget-to-3-month-average-3-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Tracking-set-budget-to-3-month-average-3-chromium-linux.png index c4df2d4cccd..54d41f5900b 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Tracking-set-budget-to-3-month-average-3-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Tracking-set-budget-to-3-month-average-3-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Tracking-set-budget-to-6-month-average-1-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Tracking-set-budget-to-6-month-average-1-chromium-linux.png index fdf1c3efc04..40e7a978f40 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Tracking-set-budget-to-6-month-average-1-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Tracking-set-budget-to-6-month-average-1-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Tracking-set-budget-to-6-month-average-2-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Tracking-set-budget-to-6-month-average-2-chromium-linux.png index 6abaecbf418..98a27ebe2a2 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Tracking-set-budget-to-6-month-average-2-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Tracking-set-budget-to-6-month-average-2-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Tracking-set-budget-to-6-month-average-3-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Tracking-set-budget-to-6-month-average-3-chromium-linux.png index a4795b81bd4..789f2fb7ca0 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Tracking-set-budget-to-6-month-average-3-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Tracking-set-budget-to-6-month-average-3-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Tracking-updates-the-budgeted-amount-1-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Tracking-updates-the-budgeted-amount-1-chromium-linux.png index 2e23c82f7e6..b02611e69eb 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Tracking-updates-the-budgeted-amount-1-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Tracking-updates-the-budgeted-amount-1-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Tracking-updates-the-budgeted-amount-2-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Tracking-updates-the-budgeted-amount-2-chromium-linux.png index 0be2162edd0..a2c7a8d91a5 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Tracking-updates-the-budgeted-amount-2-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Tracking-updates-the-budgeted-amount-2-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Tracking-updates-the-budgeted-amount-3-chromium-linux.png b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Tracking-updates-the-budgeted-amount-3-chromium-linux.png index a1e56532afe..5a3872f1c35 100644 Binary files a/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Tracking-updates-the-budgeted-amount-3-chromium-linux.png and b/packages/desktop-client/e2e/budget.mobile.test.ts-snapshots/Mobile-Budget-Tracking-updates-the-budgeted-amount-3-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/page-models/account-page.ts b/packages/desktop-client/e2e/page-models/account-page.ts index ebbee3f0068..32fd6072229 100644 --- a/packages/desktop-client/e2e/page-models/account-page.ts +++ b/packages/desktop-client/e2e/page-models/account-page.ts @@ -194,7 +194,8 @@ export class AccountPage { transaction: TransactionEntry, ) { if (transaction.debit) { - await transactionRow.getByTestId('debit').click(); + // double click to ensure the content is selected when adding split transactions + await transactionRow.getByTestId('debit').dblclick(); await this.page.keyboard.type(transaction.debit); await this.page.keyboard.press('Tab'); } diff --git a/packages/desktop-client/e2e/reports.test.ts-snapshots/Reports-loads-cash-flow-graph-and-checks-visuals-1-chromium-linux.png b/packages/desktop-client/e2e/reports.test.ts-snapshots/Reports-loads-cash-flow-graph-and-checks-visuals-1-chromium-linux.png index 85a97c2032f..85f9c02fe53 100644 Binary files a/packages/desktop-client/e2e/reports.test.ts-snapshots/Reports-loads-cash-flow-graph-and-checks-visuals-1-chromium-linux.png and b/packages/desktop-client/e2e/reports.test.ts-snapshots/Reports-loads-cash-flow-graph-and-checks-visuals-1-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/transactions.mobile.test.ts-snapshots/Mobile-Transactions-creates-a-transaction-via-footer-button-7-chromium-linux.png b/packages/desktop-client/e2e/transactions.mobile.test.ts-snapshots/Mobile-Transactions-creates-a-transaction-via-footer-button-7-chromium-linux.png index 5d6a84f5a7a..8d885268885 100644 Binary files a/packages/desktop-client/e2e/transactions.mobile.test.ts-snapshots/Mobile-Transactions-creates-a-transaction-via-footer-button-7-chromium-linux.png and b/packages/desktop-client/e2e/transactions.mobile.test.ts-snapshots/Mobile-Transactions-creates-a-transaction-via-footer-button-7-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/transactions.mobile.test.ts-snapshots/Mobile-Transactions-creates-a-transaction-via-footer-button-8-chromium-linux.png b/packages/desktop-client/e2e/transactions.mobile.test.ts-snapshots/Mobile-Transactions-creates-a-transaction-via-footer-button-8-chromium-linux.png index c8ed30e1d63..c56186c0171 100644 Binary files a/packages/desktop-client/e2e/transactions.mobile.test.ts-snapshots/Mobile-Transactions-creates-a-transaction-via-footer-button-8-chromium-linux.png and b/packages/desktop-client/e2e/transactions.mobile.test.ts-snapshots/Mobile-Transactions-creates-a-transaction-via-footer-button-8-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/transactions.mobile.test.ts-snapshots/Mobile-Transactions-creates-a-transaction-via-footer-button-9-chromium-linux.png b/packages/desktop-client/e2e/transactions.mobile.test.ts-snapshots/Mobile-Transactions-creates-a-transaction-via-footer-button-9-chromium-linux.png index d10b3d513df..5ea220ed44a 100644 Binary files a/packages/desktop-client/e2e/transactions.mobile.test.ts-snapshots/Mobile-Transactions-creates-a-transaction-via-footer-button-9-chromium-linux.png and b/packages/desktop-client/e2e/transactions.mobile.test.ts-snapshots/Mobile-Transactions-creates-a-transaction-via-footer-button-9-chromium-linux.png differ diff --git a/packages/desktop-client/package.json b/packages/desktop-client/package.json index 43336d5de03..5b537a79358 100644 --- a/packages/desktop-client/package.json +++ b/packages/desktop-client/package.json @@ -77,7 +77,7 @@ "vite": "^5.2.14", "vite-plugin-pwa": "^0.20.0", "vite-tsconfig-paths": "^4.3.2", - "vitest": "^1.6.0", + "vitest": "^1.6.1", "webpack-bundle-analyzer": "^4.10.1", "xml2js": "^0.6.2" }, diff --git a/packages/desktop-client/src/auth/ProtectedRoute.tsx b/packages/desktop-client/src/auth/ProtectedRoute.tsx index 0dad71a1c38..202980b9b21 100644 --- a/packages/desktop-client/src/auth/ProtectedRoute.tsx +++ b/packages/desktop-client/src/auth/ProtectedRoute.tsx @@ -1,8 +1,9 @@ import { useEffect, useState, type ReactElement } from 'react'; +import { View } from '@actual-app/components/view'; + import { type RemoteFile, type SyncedLocalFile } from 'loot-core/types/file'; -import { View } from '../components/common/View'; import { useMetadataPref } from '../hooks/useMetadataPref'; import { useSelector } from '../redux'; diff --git a/packages/desktop-client/src/components/AnimatedRefresh.tsx b/packages/desktop-client/src/components/AnimatedRefresh.tsx index b9273bf9556..d794c34f704 100644 --- a/packages/desktop-client/src/components/AnimatedRefresh.tsx +++ b/packages/desktop-client/src/components/AnimatedRefresh.tsx @@ -1,12 +1,11 @@ // @ts-strict-ignore import React, { type CSSProperties } from 'react'; +import { View } from '@actual-app/components/view'; import { keyframes } from '@emotion/css'; import { SvgRefresh } from '../icons/v1'; -import { View } from './common/View'; - const spin = keyframes({ '0%': { transform: 'rotateZ(0deg)' }, '100%': { transform: 'rotateZ(360deg)' }, diff --git a/packages/desktop-client/src/components/App.tsx b/packages/desktop-client/src/components/App.tsx index e6a09c936c2..50fead5553b 100644 --- a/packages/desktop-client/src/components/App.tsx +++ b/packages/desktop-client/src/components/App.tsx @@ -11,6 +11,9 @@ import { HotkeysProvider } from 'react-hotkeys-hook'; import { useTranslation } from 'react-i18next'; import { BrowserRouter } from 'react-router-dom'; +import { styles } from '@actual-app/components/styles'; +import { View } from '@actual-app/components/view'; + import { addNotification, closeBudget, @@ -25,14 +28,14 @@ import { init as initConnection, send } from 'loot-core/platform/client/fetch'; import { handleGlobalEvents } from '../global-events'; import { useMetadataPref } from '../hooks/useMetadataPref'; +import { setI18NextLanguage } from '../i18n'; import { installPolyfills } from '../polyfills'; import { useDispatch, useSelector, useStore } from '../redux'; -import { styles, hasHiddenScrollbars, ThemeStyle, useTheme } from '../style'; +import { hasHiddenScrollbars, ThemeStyle, useTheme } from '../style'; import { ExposeNavigate } from '../util/router-tools'; import { AppBackground } from './AppBackground'; import { BudgetMonthCountProvider } from './budget/BudgetMonthCountContext'; -import { View } from './common/View'; import { DevelopmentTopBar } from './DevelopmentTopBar'; import { FatalError } from './FatalError'; import { FinancesApp } from './FinancesApp'; @@ -64,6 +67,8 @@ function AppInner() { }; async function init() { + setI18NextLanguage(null); + const socketName = await maybeUpdate(() => global.Actual.getServerSocket(), ); diff --git a/packages/desktop-client/src/components/BankSyncStatus.tsx b/packages/desktop-client/src/components/BankSyncStatus.tsx index 843b62d3e3f..554a37c2748 100644 --- a/packages/desktop-client/src/components/BankSyncStatus.tsx +++ b/packages/desktop-client/src/components/BankSyncStatus.tsx @@ -2,12 +2,14 @@ import React from 'react'; import { Trans } from 'react-i18next'; import { useTransition, animated } from 'react-spring'; +import { styles } from '@actual-app/components/styles'; +import { Text } from '@actual-app/components/text'; +import { View } from '@actual-app/components/view'; + import { useSelector } from '../redux'; -import { theme, styles } from '../style'; +import { theme } from '../style'; import { AnimatedRefresh } from './AnimatedRefresh'; -import { Text } from './common/Text'; -import { View } from './common/View'; export function BankSyncStatus() { const accountsSyncing = useSelector(state => state.account.accountsSyncing); diff --git a/packages/desktop-client/src/components/DevelopmentTopBar.tsx b/packages/desktop-client/src/components/DevelopmentTopBar.tsx index c92375a402d..9fd121551e2 100644 --- a/packages/desktop-client/src/components/DevelopmentTopBar.tsx +++ b/packages/desktop-client/src/components/DevelopmentTopBar.tsx @@ -1,7 +1,8 @@ +import { View } from '@actual-app/components/view'; + import { theme } from '../style'; import { Link } from './common/Link'; -import { View } from './common/View'; export function DevelopmentTopBar() { return ( diff --git a/packages/desktop-client/src/components/EditablePageHeaderTitle.tsx b/packages/desktop-client/src/components/EditablePageHeaderTitle.tsx index 0552bb4bb33..2e883ddd89e 100644 --- a/packages/desktop-client/src/components/EditablePageHeaderTitle.tsx +++ b/packages/desktop-client/src/components/EditablePageHeaderTitle.tsx @@ -1,12 +1,13 @@ import React, { useCallback, useEffect, useState } from 'react'; +import { Button } from '@actual-app/components/button'; +import { InitialFocus } from '@actual-app/components/initial-focus'; +import { View } from '@actual-app/components/view'; + import { SvgPencil1 } from '../icons/v2'; import { theme } from '../style'; -import { Button } from './common/Button2'; -import { InitialFocus } from './common/InitialFocus'; import { Input } from './common/Input'; -import { View } from './common/View'; type EditablePageHeaderTitleProps = { title: string; diff --git a/packages/desktop-client/src/components/FinancesApp.tsx b/packages/desktop-client/src/components/FinancesApp.tsx index b56947979bd..156cc95cf02 100644 --- a/packages/desktop-client/src/components/FinancesApp.tsx +++ b/packages/desktop-client/src/components/FinancesApp.tsx @@ -9,6 +9,8 @@ import { useHref, } from 'react-router-dom'; +import { View } from '@actual-app/components/view'; + import { addNotification } from 'loot-core/client/actions'; import { sync } from 'loot-core/client/app/appSlice'; import * as undo from 'loot-core/platform/client/undo'; @@ -26,7 +28,6 @@ import { getIsOutdated, getLatestVersion } from '../util/versions'; import { UserAccessPage } from './admin/UserAccess/UserAccessPage'; import { BankSync } from './banksync'; import { BankSyncStatus } from './BankSyncStatus'; -import { View } from './common/View'; import { GlobalKeys } from './GlobalKeys'; import { ManageRulesPage } from './ManageRulesPage'; import { Category } from './mobile/budget/Category'; diff --git a/packages/desktop-client/src/components/FixedSizeList.tsx b/packages/desktop-client/src/components/FixedSizeList.tsx index 0baf528d125..7fe51ccca70 100644 --- a/packages/desktop-client/src/components/FixedSizeList.tsx +++ b/packages/desktop-client/src/components/FixedSizeList.tsx @@ -9,10 +9,9 @@ import { type CSSProperties, } from 'react'; +import { View } from '@actual-app/components/view'; import memoizeOne from 'memoize-one'; -import { View } from './common/View'; - const IS_SCROLLING_DEBOUNCE_INTERVAL = 150; const defaultItemKey: FixedSizeListProps['itemKey'] = (index: number) => index; diff --git a/packages/desktop-client/src/components/HelpMenu.tsx b/packages/desktop-client/src/components/HelpMenu.tsx index 40a4208ed41..8b28ccafac9 100644 --- a/packages/desktop-client/src/components/HelpMenu.tsx +++ b/packages/desktop-client/src/components/HelpMenu.tsx @@ -3,6 +3,10 @@ import { useHotkeys } from 'react-hotkeys-hook'; import { Trans, useTranslation } from 'react-i18next'; import { useLocation } from 'react-router-dom'; +import { Button } from '@actual-app/components/button'; +import { Menu } from '@actual-app/components/menu'; +import { Popover } from '@actual-app/components/popover'; +import { SpaceBetween } from '@actual-app/components/space-between'; import { useToggle } from 'usehooks-ts'; import { pushModal } from 'loot-core/client/actions/modals'; @@ -11,11 +15,6 @@ import { useFeatureFlag } from '../hooks/useFeatureFlag'; import { SvgHelp } from '../icons/v2/Help'; import { useDispatch } from '../redux'; -import { Button } from './common/Button2'; -import { Menu } from './common/Menu'; -import { Popover } from './common/Popover'; -import { SpaceBetween } from './common/SpaceBetween'; - const getPageDocs = (page: string) => { switch (page) { case '/budget': diff --git a/packages/desktop-client/src/components/LoggedInUser.tsx b/packages/desktop-client/src/components/LoggedInUser.tsx index 8ee81e32b7d..b8b849ab610 100644 --- a/packages/desktop-client/src/components/LoggedInUser.tsx +++ b/packages/desktop-client/src/components/LoggedInUser.tsx @@ -2,6 +2,13 @@ import React, { useState, useEffect, useRef, type CSSProperties } from 'react'; import { Trans, useTranslation } from 'react-i18next'; import { useLocation } from 'react-router-dom'; +import { Button } from '@actual-app/components/button'; +import { Menu } from '@actual-app/components/menu'; +import { Popover } from '@actual-app/components/popover'; +import { styles } from '@actual-app/components/styles'; +import { Text } from '@actual-app/components/text'; +import { View } from '@actual-app/components/view'; + import { closeBudget, getUserData, signOut } from 'loot-core/client/actions'; import { listen } from 'loot-core/platform/client/fetch'; import { type RemoteFile, type SyncedLocalFile } from 'loot-core/types/file'; @@ -12,13 +19,8 @@ import { Permissions } from '../auth/types'; import { useMetadataPref } from '../hooks/useMetadataPref'; import { useNavigate } from '../hooks/useNavigate'; import { useSelector, useDispatch } from '../redux'; -import { theme, styles } from '../style'; +import { theme } from '../style'; -import { Button } from './common/Button2'; -import { Menu } from './common/Menu'; -import { Popover } from './common/Popover'; -import { Text } from './common/Text'; -import { View } from './common/View'; import { PrivacyFilter } from './PrivacyFilter'; import { useMultiuserEnabled, useServerURL } from './ServerContext'; diff --git a/packages/desktop-client/src/components/ManageRules.tsx b/packages/desktop-client/src/components/ManageRules.tsx index 5963995aab1..7e8e9aeabeb 100644 --- a/packages/desktop-client/src/components/ManageRules.tsx +++ b/packages/desktop-client/src/components/ManageRules.tsx @@ -9,6 +9,11 @@ import React, { } from 'react'; import { useTranslation } from 'react-i18next'; +import { Button } from '@actual-app/components/button'; +import { Stack } from '@actual-app/components/stack'; +import { Text } from '@actual-app/components/text'; +import { View } from '@actual-app/components/view'; + import { pushModal } from 'loot-core/client/actions/modals'; import { useSchedules } from 'loot-core/client/data-hooks/schedules'; import { initiallyLoadPayees } from 'loot-core/client/queries/queriesSlice'; @@ -27,13 +32,9 @@ import { useSelected, SelectedProvider } from '../hooks/useSelected'; import { useDispatch } from '../redux'; import { theme } from '../style'; -import { Button } from './common/Button2'; import { Link } from './common/Link'; import { Search } from './common/Search'; import { SimpleTable } from './common/SimpleTable'; -import { Stack } from './common/Stack'; -import { Text } from './common/Text'; -import { View } from './common/View'; import { RulesHeader } from './rules/RulesHeader'; import { RulesList } from './rules/RulesList'; diff --git a/packages/desktop-client/src/components/Notes.tsx b/packages/desktop-client/src/components/Notes.tsx index 2f131a7f6a9..2fd4af9b8cb 100644 --- a/packages/desktop-client/src/components/Notes.tsx +++ b/packages/desktop-client/src/components/Notes.tsx @@ -3,13 +3,13 @@ import React, { useEffect, useRef } from 'react'; import { useTranslation } from 'react-i18next'; import ReactMarkdown from 'react-markdown'; +import { Text } from '@actual-app/components/text'; import { css } from '@emotion/css'; import remarkGfm from 'remark-gfm'; import { type CSSProperties, theme } from '../style'; import { remarkBreaks, sequentialNewlinesPlugin } from '../util/markdown'; -import { Text } from './common/Text'; import { useResponsive } from './responsive/ResponsiveProvider'; const remarkPlugins = [sequentialNewlinesPlugin, remarkGfm, remarkBreaks]; diff --git a/packages/desktop-client/src/components/NotesButton.tsx b/packages/desktop-client/src/components/NotesButton.tsx index 56624163a18..5bdc8390107 100644 --- a/packages/desktop-client/src/components/NotesButton.tsx +++ b/packages/desktop-client/src/components/NotesButton.tsx @@ -7,16 +7,17 @@ import React, { } from 'react'; import { useTranslation } from 'react-i18next'; +import { Button } from '@actual-app/components/button'; +import { Popover } from '@actual-app/components/popover'; +import { Tooltip } from '@actual-app/components/tooltip'; +import { View } from '@actual-app/components/view'; + import { send } from 'loot-core/platform/client/fetch'; import { useNotes } from '../hooks/useNotes'; import { SvgCustomNotesPaper } from '../icons/v2'; import { theme } from '../style'; -import { Button } from './common/Button2'; -import { Popover } from './common/Popover'; -import { Tooltip } from './common/Tooltip'; -import { View } from './common/View'; import { Notes } from './Notes'; type NotesButtonProps = { diff --git a/packages/desktop-client/src/components/Notifications.tsx b/packages/desktop-client/src/components/Notifications.tsx index 652ff2524e2..53524f4eafc 100644 --- a/packages/desktop-client/src/components/Notifications.tsx +++ b/packages/desktop-client/src/components/Notifications.tsx @@ -8,6 +8,11 @@ import React, { } from 'react'; import { useTranslation } from 'react-i18next'; +import { Button, ButtonWithLoading } from '@actual-app/components/button'; +import { Stack } from '@actual-app/components/stack'; +import { styles } from '@actual-app/components/styles'; +import { Text } from '@actual-app/components/text'; +import { View } from '@actual-app/components/view'; import { css } from '@emotion/css'; import { removeNotification } from 'loot-core/client/actions'; @@ -16,13 +21,9 @@ import type { NotificationWithId } from 'loot-core/client/state-types/notificati import { AnimatedLoading } from '../icons/AnimatedLoading'; import { SvgDelete } from '../icons/v0'; import { useSelector, useDispatch } from '../redux'; -import { styles, theme } from '../style'; +import { theme } from '../style'; -import { Button, ButtonWithLoading } from './common/Button2'; import { Link } from './common/Link'; -import { Stack } from './common/Stack'; -import { Text } from './common/Text'; -import { View } from './common/View'; import { useResponsive } from './responsive/ResponsiveProvider'; function compileMessage( diff --git a/packages/desktop-client/src/components/Page.tsx b/packages/desktop-client/src/components/Page.tsx index 6e750ce13d1..4683dab9a8e 100644 --- a/packages/desktop-client/src/components/Page.tsx +++ b/packages/desktop-client/src/components/Page.tsx @@ -1,9 +1,11 @@ import React, { type ReactNode, type CSSProperties } from 'react'; -import { theme, styles } from '../style'; +import { styles } from '@actual-app/components/styles'; +import { Text } from '@actual-app/components/text'; +import { View } from '@actual-app/components/view'; + +import { theme } from '../style'; -import { Text } from './common/Text'; -import { View } from './common/View'; import { useResponsive } from './responsive/ResponsiveProvider'; const HEADER_HEIGHT = 50; diff --git a/packages/desktop-client/src/components/PrivacyFilter.tsx b/packages/desktop-client/src/components/PrivacyFilter.tsx index 652b927ac2b..80998adc3bc 100644 --- a/packages/desktop-client/src/components/PrivacyFilter.tsx +++ b/packages/desktop-client/src/components/PrivacyFilter.tsx @@ -5,11 +5,11 @@ import React, { type ReactNode, } from 'react'; +import { View } from '@actual-app/components/view'; import { css } from '@emotion/css'; import { usePrivacyMode } from '../hooks/usePrivacyMode'; -import { View } from './common/View'; import { useResponsive } from './responsive/ResponsiveProvider'; type ConditionalPrivacyFilterProps = { diff --git a/packages/desktop-client/src/components/ThemeSelector.tsx b/packages/desktop-client/src/components/ThemeSelector.tsx index d14c0e1d04d..659158b1a35 100644 --- a/packages/desktop-client/src/components/ThemeSelector.tsx +++ b/packages/desktop-client/src/components/ThemeSelector.tsx @@ -1,14 +1,15 @@ import React, { useRef, useState, type CSSProperties } from 'react'; import { useTranslation } from 'react-i18next'; +import { Button } from '@actual-app/components/button'; +import { Menu } from '@actual-app/components/menu'; +import { Popover } from '@actual-app/components/popover'; + import type { Theme } from 'loot-core/types/prefs'; import { SvgMoonStars, SvgSun, SvgSystem } from '../icons/v2'; import { themeOptions, useTheme } from '../style'; -import { Button } from './common/Button2'; -import { Menu } from './common/Menu'; -import { Popover } from './common/Popover'; import { useResponsive } from './responsive/ResponsiveProvider'; type ThemeSelectorProps = { diff --git a/packages/desktop-client/src/components/Titlebar.tsx b/packages/desktop-client/src/components/Titlebar.tsx index 0fd74e7cbe4..ebddb911320 100644 --- a/packages/desktop-client/src/components/Titlebar.tsx +++ b/packages/desktop-client/src/components/Titlebar.tsx @@ -3,6 +3,11 @@ import { useHotkeys } from 'react-hotkeys-hook'; import { useTranslation } from 'react-i18next'; import { Routes, Route, useLocation } from 'react-router-dom'; +import { Button } from '@actual-app/components/button'; +import { SpaceBetween } from '@actual-app/components/space-between'; +import { styles } from '@actual-app/components/styles'; +import { Text } from '@actual-app/components/text'; +import { View } from '@actual-app/components/view'; import { css } from '@emotion/css'; import { sync } from 'loot-core/client/app/appSlice'; @@ -26,16 +31,12 @@ import { SvgViewShow, } from '../icons/v2'; import { useDispatch } from '../redux'; -import { theme, styles, type CSSProperties } from '../style'; +import { theme, type CSSProperties } from '../style'; import { AccountSyncCheck } from './accounts/AccountSyncCheck'; import { AnimatedRefresh } from './AnimatedRefresh'; import { MonthCountSelector } from './budget/MonthCountSelector'; -import { Button } from './common/Button2'; import { Link } from './common/Link'; -import { SpaceBetween } from './common/SpaceBetween'; -import { Text } from './common/Text'; -import { View } from './common/View'; import { HelpMenu } from './HelpMenu'; import { LoggedInUser } from './LoggedInUser'; import { useResponsive } from './responsive/ResponsiveProvider'; @@ -147,7 +148,7 @@ function SyncButton({ style, isMobile = false }: SyncButtonProps) { }); return unlisten; - }, []); + }, [cloudFileId]); const mobileColor = syncState === 'error' diff --git a/packages/desktop-client/src/components/UpdateNotification.tsx b/packages/desktop-client/src/components/UpdateNotification.tsx index 6d5fbba55d9..5b7301242f3 100644 --- a/packages/desktop-client/src/components/UpdateNotification.tsx +++ b/packages/desktop-client/src/components/UpdateNotification.tsx @@ -1,16 +1,17 @@ import React from 'react'; import { useTranslation } from 'react-i18next'; +import { Button } from '@actual-app/components/button'; +import { Text } from '@actual-app/components/text'; +import { View } from '@actual-app/components/view'; + import { setAppState, updateApp } from 'loot-core/client/app/appSlice'; import { SvgClose } from '../icons/v1'; import { useSelector, useDispatch } from '../redux'; import { theme } from '../style'; -import { Button } from './common/Button2'; import { Link } from './common/Link'; -import { Text } from './common/Text'; -import { View } from './common/View'; export function UpdateNotification() { const { t } = useTranslation(); diff --git a/packages/desktop-client/src/components/accounts/Account.tsx b/packages/desktop-client/src/components/accounts/Account.tsx index 0f5b199bb85..fb22b91d1d1 100644 --- a/packages/desktop-client/src/components/accounts/Account.tsx +++ b/packages/desktop-client/src/components/accounts/Account.tsx @@ -9,6 +9,10 @@ import React, { import { Trans } from 'react-i18next'; import { Navigate, useParams, useLocation } from 'react-router-dom'; +import { Button } from '@actual-app/components/button'; +import { styles } from '@actual-app/components/styles'; +import { Text } from '@actual-app/components/text'; +import { View } from '@actual-app/components/view'; import { debounce } from 'debounce'; import { t } from 'i18next'; import { v4 as uuidv4 } from 'uuid'; @@ -83,10 +87,7 @@ import { import { useSyncedPref } from '../../hooks/useSyncedPref'; import { useTransactionBatchActions } from '../../hooks/useTransactionBatchActions'; import { useSelector, useDispatch } from '../../redux'; -import { styles, theme } from '../../style'; -import { Button } from '../common/Button2'; -import { Text } from '../common/Text'; -import { View } from '../common/View'; +import { theme } from '../../style'; import { type SavedFilter } from '../filters/SavedFilterMenuButton'; import { TransactionList } from '../transactions/TransactionList'; import { validateAccountName } from '../util/accountValidation'; @@ -1880,7 +1881,7 @@ class AccountInternal extends PureComponent< fontStyle: 'italic', }} > - No transactions + No transactions ) : null } diff --git a/packages/desktop-client/src/components/accounts/AccountSyncCheck.tsx b/packages/desktop-client/src/components/accounts/AccountSyncCheck.tsx index f91425bbaec..4d3b07b3fc3 100644 --- a/packages/desktop-client/src/components/accounts/AccountSyncCheck.tsx +++ b/packages/desktop-client/src/components/accounts/AccountSyncCheck.tsx @@ -2,6 +2,10 @@ import React, { useCallback, useRef, useState } from 'react'; import { Trans, useTranslation } from 'react-i18next'; import { useParams } from 'react-router-dom'; +import { Button } from '@actual-app/components/button'; +import { Popover } from '@actual-app/components/popover'; +import { View } from '@actual-app/components/view'; + import { unlinkAccount } from 'loot-core/client/accounts/accountsSlice'; import { type AccountEntity } from 'loot-core/types/models'; @@ -11,10 +15,7 @@ import { useFailedAccounts } from '../../hooks/useFailedAccounts'; import { SvgExclamationOutline } from '../../icons/v1'; import { useDispatch } from '../../redux'; import { theme } from '../../style'; -import { Button } from '../common/Button2'; import { Link } from '../common/Link'; -import { Popover } from '../common/Popover'; -import { View } from '../common/View'; function useErrorMessage() { const { t } = useTranslation(); @@ -95,7 +96,7 @@ export function AccountSyncCheck() { setOpen(false); if (acc.account_id) { - authorizeBank(dispatch, { upgradingAccountId: acc.account_id }); + authorizeBank(dispatch); } }, [dispatch], diff --git a/packages/desktop-client/src/components/accounts/Balance.tsx b/packages/desktop-client/src/components/accounts/Balance.tsx index b0c37a5de40..136a0c0fc7b 100644 --- a/packages/desktop-client/src/components/accounts/Balance.tsx +++ b/packages/desktop-client/src/components/accounts/Balance.tsx @@ -1,6 +1,9 @@ import React, { useRef } from 'react'; import { useTranslation } from 'react-i18next'; +import { Button } from '@actual-app/components/button'; +import { Text } from '@actual-app/components/text'; +import { View } from '@actual-app/components/view'; import { useHover } from 'usehooks-ts'; import { useCachedSchedules } from 'loot-core/client/data-hooks/schedules'; @@ -12,9 +15,6 @@ import { type AccountEntity } from 'loot-core/types/models'; import { useSelectedItems } from '../../hooks/useSelected'; import { SvgArrowButtonRight1 } from '../../icons/v2'; import { theme } from '../../style'; -import { Button } from '../common/Button2'; -import { Text } from '../common/Text'; -import { View } from '../common/View'; import { PrivacyFilter } from '../PrivacyFilter'; import { type Binding } from '../spreadsheet'; import { CellValue, CellValueText } from '../spreadsheet/CellValue'; diff --git a/packages/desktop-client/src/components/accounts/Header.tsx b/packages/desktop-client/src/components/accounts/Header.tsx index 08e7147a416..59534cab9a1 100644 --- a/packages/desktop-client/src/components/accounts/Header.tsx +++ b/packages/desktop-client/src/components/accounts/Header.tsx @@ -8,6 +8,14 @@ import React, { import { useHotkeys } from 'react-hotkeys-hook'; import { Trans, useTranslation } from 'react-i18next'; +import { Button } from '@actual-app/components/button'; +import { InitialFocus } from '@actual-app/components/initial-focus'; +import { Menu } from '@actual-app/components/menu'; +import { Popover } from '@actual-app/components/popover'; +import { Stack } from '@actual-app/components/stack'; +import { styles } from '@actual-app/components/styles'; +import { View } from '@actual-app/components/view'; + import { type AccountEntity, type RuleConditionEntity, @@ -27,17 +35,11 @@ import { SvgLockClosed, SvgPencil1, } from '../../icons/v2'; -import { theme, styles } from '../../style'; +import { theme } from '../../style'; import { AnimatedRefresh } from '../AnimatedRefresh'; -import { Button } from '../common/Button2'; -import { InitialFocus } from '../common/InitialFocus'; import { Input } from '../common/Input'; -import { Menu } from '../common/Menu'; import { MenuButton } from '../common/MenuButton'; -import { Popover } from '../common/Popover'; import { Search } from '../common/Search'; -import { Stack } from '../common/Stack'; -import { View } from '../common/View'; import { FilterButton } from '../filters/FiltersMenu'; import { FiltersStack } from '../filters/FiltersStack'; import { type SavedFilter } from '../filters/SavedFilterMenuButton'; diff --git a/packages/desktop-client/src/components/accounts/Reconcile.tsx b/packages/desktop-client/src/components/accounts/Reconcile.tsx index b98eedbddd4..a05a943a58c 100644 --- a/packages/desktop-client/src/components/accounts/Reconcile.tsx +++ b/packages/desktop-client/src/components/accounts/Reconcile.tsx @@ -1,6 +1,12 @@ import React, { useState } from 'react'; import { Trans } from 'react-i18next'; +import { Button } from '@actual-app/components/button'; +import { InitialFocus } from '@actual-app/components/initial-focus'; +import { styles } from '@actual-app/components/styles'; +import { Text } from '@actual-app/components/text'; +import { View } from '@actual-app/components/view'; + import * as queries from 'loot-core/client/queries'; import { type Query } from 'loot-core/shared/query'; import { currencyToInteger } from 'loot-core/shared/util'; @@ -8,12 +14,8 @@ import { type AccountEntity } from 'loot-core/types/models'; import { type TransObjectLiteral } from 'loot-core/types/util'; import { SvgCheckCircle1 } from '../../icons/v2'; -import { styles, theme } from '../../style'; -import { Button } from '../common/Button2'; -import { InitialFocus } from '../common/InitialFocus'; +import { theme } from '../../style'; import { Input } from '../common/Input'; -import { Text } from '../common/Text'; -import { View } from '../common/View'; import { useFormat } from '../spreadsheet/useFormat'; import { useSheetValue } from '../spreadsheet/useSheetValue'; diff --git a/packages/desktop-client/src/components/admin/UserAccess/UserAccess.tsx b/packages/desktop-client/src/components/admin/UserAccess/UserAccess.tsx index 38290d3d939..64996f470a8 100644 --- a/packages/desktop-client/src/components/admin/UserAccess/UserAccess.tsx +++ b/packages/desktop-client/src/components/admin/UserAccess/UserAccess.tsx @@ -10,6 +10,10 @@ import React, { } from 'react'; import { Trans, useTranslation } from 'react-i18next'; +import { Button } from '@actual-app/components/button'; +import { Text } from '@actual-app/components/text'; +import { View } from '@actual-app/components/view'; + import { addNotification, pushModal } from 'loot-core/client/actions'; import { send } from 'loot-core/platform/client/fetch'; import * as undo from 'loot-core/platform/client/undo'; @@ -22,12 +26,9 @@ import { SvgLockOpen } from '../../../icons/v1'; import { SvgLockClosed } from '../../../icons/v2'; import { useDispatch } from '../../../redux'; import { theme } from '../../../style'; -import { Button } from '../../common/Button2'; import { Link } from '../../common/Link'; import { Search } from '../../common/Search'; import { SimpleTable } from '../../common/SimpleTable'; -import { Text } from '../../common/Text'; -import { View } from '../../common/View'; import { UserAccessHeader } from './UserAccessHeader'; import { UserAccessRow } from './UserAccessRow'; diff --git a/packages/desktop-client/src/components/admin/UserAccess/UserAccessRow.tsx b/packages/desktop-client/src/components/admin/UserAccess/UserAccessRow.tsx index ae9d5915c4e..88eca07c9a0 100644 --- a/packages/desktop-client/src/components/admin/UserAccess/UserAccessRow.tsx +++ b/packages/desktop-client/src/components/admin/UserAccess/UserAccessRow.tsx @@ -2,6 +2,8 @@ import React, { memo, useState } from 'react'; import { useTranslation } from 'react-i18next'; +import { View } from '@actual-app/components/view'; + import { addNotification, signOut } from 'loot-core/client/actions'; import { send } from 'loot-core/platform/client/fetch'; import { getUserAccessErrors } from 'loot-core/shared/errors'; @@ -10,7 +12,6 @@ import { type UserAvailable } from 'loot-core/types/models'; import { useMetadataPref } from '../../../hooks/useMetadataPref'; import { useDispatch } from '../../../redux'; import { theme } from '../../../style'; -import { View } from '../../common/View'; import { Checkbox } from '../../forms'; import { Row, Cell } from '../../table'; diff --git a/packages/desktop-client/src/components/admin/UserDirectory/UserDirectory.tsx b/packages/desktop-client/src/components/admin/UserDirectory/UserDirectory.tsx index 6fa61cd2b58..77341079403 100644 --- a/packages/desktop-client/src/components/admin/UserDirectory/UserDirectory.tsx +++ b/packages/desktop-client/src/components/admin/UserDirectory/UserDirectory.tsx @@ -10,6 +10,11 @@ import { } from 'react'; import { Trans, useTranslation } from 'react-i18next'; +import { Button } from '@actual-app/components/button'; +import { Stack } from '@actual-app/components/stack'; +import { Text } from '@actual-app/components/text'; +import { View } from '@actual-app/components/view'; + import { addNotification, signOut } from 'loot-core/client/actions'; import { pushModal } from 'loot-core/client/actions/modals'; import { send } from 'loot-core/platform/client/fetch'; @@ -22,13 +27,9 @@ import { import { SelectedProvider, useSelected } from '../../../hooks/useSelected'; import { useDispatch } from '../../../redux'; import { theme } from '../../../style'; -import { Button } from '../../common/Button2'; import { Link } from '../../common/Link'; import { Search } from '../../common/Search'; import { SimpleTable } from '../../common/SimpleTable'; -import { Stack } from '../../common/Stack'; -import { Text } from '../../common/Text'; -import { View } from '../../common/View'; import { UserDirectoryHeader } from './UserDirectoryHeader'; import { UserDirectoryRow } from './UserDirectoryRow'; @@ -41,37 +42,40 @@ type ManageUserDirectoryContentProps = { function useGetUserDirectoryErrors() { const { t } = useTranslation(); - function getUserDirectoryErrors(reason) { - switch (reason) { - case 'unauthorized': - return t('You are not logged in.'); - case 'token-expired': - return t('Login expired, please log in again.'); - case 'user-cant-be-empty': - return t( - 'Please enter a value for the username; the field cannot be empty.', - ); - case 'role-cant-be-empty': - return t('Select a role; the field cannot be empty.'); - case 'user-already-exists': - return t( - 'The username you entered already exists. Please choose a different username.', - ); - case 'not-all-deleted': - return t( - 'Not all users were deleted. Check if one of the selected users is the server owner.', - ); - case 'role-does-not-exists': - return t( - 'Selected role does not exists, possibly a bug? Visit https://actualbudget.org/contact/ for support.', - ); - default: - return t( - 'An internal error occurred, sorry! Visit https://actualbudget.org/contact/ for support. (ref: {{reason}})', - { reason }, - ); - } - } + const getUserDirectoryErrors = useCallback( + reason => { + switch (reason) { + case 'unauthorized': + return t('You are not logged in.'); + case 'token-expired': + return t('Login expired, please log in again.'); + case 'user-cant-be-empty': + return t( + 'Please enter a value for the username; the field cannot be empty.', + ); + case 'role-cant-be-empty': + return t('Select a role; the field cannot be empty.'); + case 'user-already-exists': + return t( + 'The username you entered already exists. Please choose a different username.', + ); + case 'not-all-deleted': + return t( + 'Not all users were deleted. Check if one of the selected users is the server owner.', + ); + case 'role-does-not-exists': + return t( + 'Selected role does not exist, possibly a bug? Visit https://actualbudget.org/contact/ for support.', + ); + default: + return t( + 'An internal error occurred, sorry! Visit https://actualbudget.org/contact/ for support. (ref: {{reason}})', + { reason }, + ); + } + }, + [t], + ); return { getUserDirectoryErrors }; } diff --git a/packages/desktop-client/src/components/admin/UserDirectory/UserDirectoryPage.tsx b/packages/desktop-client/src/components/admin/UserDirectory/UserDirectoryPage.tsx index ec8fd223f6e..58cf88afa01 100644 --- a/packages/desktop-client/src/components/admin/UserDirectory/UserDirectoryPage.tsx +++ b/packages/desktop-client/src/components/admin/UserDirectory/UserDirectoryPage.tsx @@ -1,9 +1,10 @@ import React, { type ReactNode } from 'react'; import { Trans, useTranslation } from 'react-i18next'; +import { Button } from '@actual-app/components/button'; +import { View } from '@actual-app/components/view'; + import { useNavigate } from '../../../hooks/useNavigate'; -import { Button } from '../../common/Button2'; -import { View } from '../../common/View'; import { Page } from '../../Page'; import { UserDirectory } from './UserDirectory'; diff --git a/packages/desktop-client/src/components/admin/UserDirectory/UserDirectoryRow.tsx b/packages/desktop-client/src/components/admin/UserDirectory/UserDirectoryRow.tsx index 391b9f33912..a26c7050c2f 100644 --- a/packages/desktop-client/src/components/admin/UserDirectory/UserDirectoryRow.tsx +++ b/packages/desktop-client/src/components/admin/UserDirectory/UserDirectoryRow.tsx @@ -2,12 +2,13 @@ import React, { memo } from 'react'; import { Trans } from 'react-i18next'; +import { Button } from '@actual-app/components/button'; +import { View } from '@actual-app/components/view'; + import { PossibleRoles, type UserEntity } from 'loot-core/types/models/user'; import { useSelectedDispatch } from '../../../hooks/useSelected'; import { theme } from '../../../style'; -import { Button } from '../../common/Button2'; -import { View } from '../../common/View'; import { Checkbox } from '../../forms'; import { SelectCell, Row, Cell } from '../../table'; diff --git a/packages/desktop-client/src/components/alerts.tsx b/packages/desktop-client/src/components/alerts.tsx index ee04560d1ff..7ab8b324004 100644 --- a/packages/desktop-client/src/components/alerts.tsx +++ b/packages/desktop-client/src/components/alerts.tsx @@ -5,11 +5,12 @@ import React, { type CSSProperties, } from 'react'; -import { SvgExclamationOutline, SvgInformationOutline } from '../icons/v1'; -import { styles, theme } from '../style'; +import { styles } from '@actual-app/components/styles'; +import { Text } from '@actual-app/components/text'; +import { View } from '@actual-app/components/view'; -import { Text } from './common/Text'; -import { View } from './common/View'; +import { SvgExclamationOutline, SvgInformationOutline } from '../icons/v1'; +import { theme } from '../style'; type AlertProps = { icon?: ComponentType<{ width?: number; style?: CSSProperties }>; diff --git a/packages/desktop-client/src/components/autocomplete/AccountAutocomplete.tsx b/packages/desktop-client/src/components/autocomplete/AccountAutocomplete.tsx index 967e2630705..a4786f9576b 100644 --- a/packages/desktop-client/src/components/autocomplete/AccountAutocomplete.tsx +++ b/packages/desktop-client/src/components/autocomplete/AccountAutocomplete.tsx @@ -8,14 +8,15 @@ import React, { } from 'react'; import { useTranslation } from 'react-i18next'; +import { styles } from '@actual-app/components/styles'; +import { TextOneLine } from '@actual-app/components/text-one-line'; +import { View } from '@actual-app/components/view'; import { css, cx } from '@emotion/css'; import { type AccountEntity } from 'loot-core/types/models'; import { useAccounts } from '../../hooks/useAccounts'; -import { theme, styles } from '../../style'; -import { TextOneLine } from '../common/TextOneLine'; -import { View } from '../common/View'; +import { theme } from '../../style'; import { useResponsive } from '../responsive/ResponsiveProvider'; import { Autocomplete } from './Autocomplete'; diff --git a/packages/desktop-client/src/components/autocomplete/Autocomplete.tsx b/packages/desktop-client/src/components/autocomplete/Autocomplete.tsx index 914cb0db7c3..c79698da2de 100644 --- a/packages/desktop-client/src/components/autocomplete/Autocomplete.tsx +++ b/packages/desktop-client/src/components/autocomplete/Autocomplete.tsx @@ -11,17 +11,18 @@ import React, { useState, } from 'react'; +import { Popover } from '@actual-app/components/popover'; +import { styles } from '@actual-app/components/styles'; +import { View } from '@actual-app/components/view'; import { css, cx } from '@emotion/css'; import Downshift, { type StateChangeTypes } from 'downshift'; import { getNormalisedString } from 'loot-core/shared/normalisation'; import { SvgRemove } from '../../icons/v2'; -import { styles, theme } from '../../style'; +import { theme } from '../../style'; import { Button } from '../common/Button'; import { Input } from '../common/Input'; -import { Popover } from '../common/Popover'; -import { View } from '../common/View'; import { useResponsive } from '../responsive/ResponsiveProvider'; type CommonAutocompleteProps = { diff --git a/packages/desktop-client/src/components/autocomplete/CategoryAutocomplete.tsx b/packages/desktop-client/src/components/autocomplete/CategoryAutocomplete.tsx index c3ba3d990b0..913a7bc19a9 100644 --- a/packages/desktop-client/src/components/autocomplete/CategoryAutocomplete.tsx +++ b/packages/desktop-client/src/components/autocomplete/CategoryAutocomplete.tsx @@ -12,6 +12,10 @@ import React, { } from 'react'; import { Trans, useTranslation } from 'react-i18next'; +import { styles } from '@actual-app/components/styles'; +import { Text } from '@actual-app/components/text'; +import { TextOneLine } from '@actual-app/components/text-one-line'; +import { View } from '@actual-app/components/view'; import { css, cx } from '@emotion/css'; import { trackingBudget, envelopeBudget } from 'loot-core/client/queries'; @@ -25,12 +29,9 @@ import { import { useCategories } from '../../hooks/useCategories'; import { useSyncedPref } from '../../hooks/useSyncedPref'; import { SvgSplit } from '../../icons/v0'; -import { theme, styles } from '../../style'; +import { theme } from '../../style'; import { useEnvelopeSheetValue } from '../budget/envelope/EnvelopeBudgetComponents'; import { makeAmountFullStyle } from '../budget/util'; -import { Text } from '../common/Text'; -import { TextOneLine } from '../common/TextOneLine'; -import { View } from '../common/View'; import { useResponsive } from '../responsive/ResponsiveProvider'; import { useSheetValue } from '../spreadsheet/useSheetValue'; diff --git a/packages/desktop-client/src/components/autocomplete/FilterList.tsx b/packages/desktop-client/src/components/autocomplete/FilterList.tsx index 1e4e0191b68..f5332b48f8a 100644 --- a/packages/desktop-client/src/components/autocomplete/FilterList.tsx +++ b/packages/desktop-client/src/components/autocomplete/FilterList.tsx @@ -1,8 +1,9 @@ import React, { type ComponentProps } from 'react'; import { useTranslation } from 'react-i18next'; +import { View } from '@actual-app/components/view'; + import { theme } from '../../style/theme'; -import { View } from '../common/View'; import { ItemHeader } from './ItemHeader'; diff --git a/packages/desktop-client/src/components/autocomplete/ItemHeader.tsx b/packages/desktop-client/src/components/autocomplete/ItemHeader.tsx index 1d9bf95c12d..18cbb9c9ed2 100644 --- a/packages/desktop-client/src/components/autocomplete/ItemHeader.tsx +++ b/packages/desktop-client/src/components/autocomplete/ItemHeader.tsx @@ -1,6 +1,8 @@ import React, { type CSSProperties } from 'react'; -import { styles, theme } from '../../style'; +import { styles } from '@actual-app/components/styles'; + +import { theme } from '../../style'; import { useResponsive } from '../responsive/ResponsiveProvider'; type ItemHeaderProps = { diff --git a/packages/desktop-client/src/components/autocomplete/PayeeAutocomplete.tsx b/packages/desktop-client/src/components/autocomplete/PayeeAutocomplete.tsx index f1113913a50..0b14526a9eb 100644 --- a/packages/desktop-client/src/components/autocomplete/PayeeAutocomplete.tsx +++ b/packages/desktop-client/src/components/autocomplete/PayeeAutocomplete.tsx @@ -13,6 +13,9 @@ import React, { } from 'react'; import { Trans, useTranslation } from 'react-i18next'; +import { styles } from '@actual-app/components/styles'; +import { TextOneLine } from '@actual-app/components/text-one-line'; +import { View } from '@actual-app/components/view'; import { css, cx } from '@emotion/css'; import { @@ -26,10 +29,8 @@ import { useAccounts } from '../../hooks/useAccounts'; import { useCommonPayees, usePayees } from '../../hooks/usePayees'; import { SvgAdd, SvgBookmark } from '../../icons/v1'; import { useDispatch } from '../../redux'; -import { theme, styles } from '../../style'; +import { theme } from '../../style'; import { Button } from '../common/Button'; -import { TextOneLine } from '../common/TextOneLine'; -import { View } from '../common/View'; import { useResponsive } from '../responsive/ResponsiveProvider'; import { diff --git a/packages/desktop-client/src/components/autocomplete/ReportList.tsx b/packages/desktop-client/src/components/autocomplete/ReportList.tsx index f4fb2cb553c..c1212931871 100644 --- a/packages/desktop-client/src/components/autocomplete/ReportList.tsx +++ b/packages/desktop-client/src/components/autocomplete/ReportList.tsx @@ -1,8 +1,9 @@ import React, { Fragment, type ComponentProps } from 'react'; import { useTranslation } from 'react-i18next'; +import { View } from '@actual-app/components/view'; + import { theme } from '../../style/theme'; -import { View } from '../common/View'; import { ItemHeader } from './ItemHeader'; diff --git a/packages/desktop-client/src/components/banksync/AccountRow.tsx b/packages/desktop-client/src/components/banksync/AccountRow.tsx index 4934b9f6741..e98d2242f92 100644 --- a/packages/desktop-client/src/components/banksync/AccountRow.tsx +++ b/packages/desktop-client/src/components/banksync/AccountRow.tsx @@ -1,12 +1,13 @@ import React, { memo } from 'react'; import { Trans } from 'react-i18next'; +import { Button } from '@actual-app/components/button'; + import { format } from 'loot-core/src/shared/months'; import { type AccountEntity } from 'loot-core/src/types/models'; import { useDateFormat } from '../../hooks/useDateFormat'; import { theme } from '../../style'; -import { Button } from '../common/Button2'; import { Row, Cell } from '../table'; const tsToString = (ts: string | null, dateFormat: string) => { diff --git a/packages/desktop-client/src/components/banksync/AccountsList.tsx b/packages/desktop-client/src/components/banksync/AccountsList.tsx index d1930bef993..4bff9360ac4 100644 --- a/packages/desktop-client/src/components/banksync/AccountsList.tsx +++ b/packages/desktop-client/src/components/banksync/AccountsList.tsx @@ -1,8 +1,8 @@ import React from 'react'; -import { type AccountEntity } from 'loot-core/src/types/models'; +import { View } from '@actual-app/components/view'; -import { View } from '../common/View'; +import { type AccountEntity } from 'loot-core/src/types/models'; import { AccountRow } from './AccountRow'; diff --git a/packages/desktop-client/src/components/banksync/EditSyncAccount.tsx b/packages/desktop-client/src/components/banksync/EditSyncAccount.tsx index 32426028998..b13efd362f1 100644 --- a/packages/desktop-client/src/components/banksync/EditSyncAccount.tsx +++ b/packages/desktop-client/src/components/banksync/EditSyncAccount.tsx @@ -1,6 +1,10 @@ import React, { useMemo, useState } from 'react'; import { Trans, useTranslation } from 'react-i18next'; +import { Button } from '@actual-app/components/button'; +import { Stack } from '@actual-app/components/stack'; +import { Text } from '@actual-app/components/text'; + import { useTransactions } from 'loot-core/client/data-hooks/transactions'; import { defaultMappings, @@ -15,10 +19,7 @@ import { } from 'loot-core/src/types/models'; import { useSyncedPref } from '../../hooks/useSyncedPref'; -import { Button } from '../common/Button2'; import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal'; -import { Stack } from '../common/Stack'; -import { Text } from '../common/Text'; import { CheckboxOption } from '../modals/ImportTransactionsModal/CheckboxOption'; import { FieldMapping } from './FieldMapping'; @@ -178,22 +179,13 @@ export function EditSyncAccount({ account }: EditSyncAccountProps) { Field mapping - {fields.length > 0 ? ( - - ) : ( - - - No transactions found with mappable fields, accounts must have - been synced at least once for this function to be available. - - - )} + Options diff --git a/packages/desktop-client/src/components/banksync/FieldMapping.tsx b/packages/desktop-client/src/components/banksync/FieldMapping.tsx index 4c73963e6a2..ddf0975cbcd 100644 --- a/packages/desktop-client/src/components/banksync/FieldMapping.tsx +++ b/packages/desktop-client/src/components/banksync/FieldMapping.tsx @@ -1,11 +1,12 @@ import React from 'react'; -import { useTranslation } from 'react-i18next'; +import { Trans, useTranslation } from 'react-i18next'; + +import { Text } from '@actual-app/components/text'; import { SvgRightArrow2 } from '../../icons/v0'; import { SvgEquals } from '../../icons/v1'; import { theme } from '../../style'; import { Select } from '../common/Select'; -import { Text } from '../common/Text'; import { Row, Cell, TableHeader } from '../table'; import { @@ -64,92 +65,103 @@ export function FieldMapping({ }} /> - - - - - - - {fields.map(field => { - return ( - + {fields.length === 0 ? ( + + + No transactions found with mappable fields, accounts must have been + synced at least once for this function to be available. + + + ) : ( + <> + - - - - - - [field, field])} + value={mapping.get(field.actualField)} + style={{ + width: 290, + }} + onChange={newValue => { + if (newValue) setMapping(field.actualField, newValue); + }} + /> + + + + + + f.field === mapping.get(field.actualField), + )?.example + } + width="flex" + style={{ paddingLeft: '10px', height: '100%', border: 0 }} + /> + + ); + })} + + )} ); } diff --git a/packages/desktop-client/src/components/banksync/index.tsx b/packages/desktop-client/src/components/banksync/index.tsx index e1615f47cee..4526920cad4 100644 --- a/packages/desktop-client/src/components/banksync/index.tsx +++ b/packages/desktop-client/src/components/banksync/index.tsx @@ -1,6 +1,9 @@ import { useMemo, useState, useCallback } from 'react'; import { Trans, useTranslation } from 'react-i18next'; +import { Text } from '@actual-app/components/text'; +import { View } from '@actual-app/components/view'; + import { pushModal } from 'loot-core/src/client/actions/modals'; import { type BankSyncProviders, @@ -10,8 +13,6 @@ import { import { useAccounts } from '../../hooks/useAccounts'; import { useGlobalPref } from '../../hooks/useGlobalPref'; import { useDispatch } from '../../redux'; -import { Text } from '../common/Text'; -import { View } from '../common/View'; import { MOBILE_NAV_HEIGHT } from '../mobile/MobileNavTabs'; import { Page } from '../Page'; import { useResponsive } from '../responsive/ResponsiveProvider'; diff --git a/packages/desktop-client/src/components/budget/BalanceWithCarryover.tsx b/packages/desktop-client/src/components/budget/BalanceWithCarryover.tsx index 320c29dc7be..45c0859c2c8 100644 --- a/packages/desktop-client/src/components/budget/BalanceWithCarryover.tsx +++ b/packages/desktop-client/src/components/budget/BalanceWithCarryover.tsx @@ -7,15 +7,16 @@ import React, { } from 'react'; import { useTranslation, Trans } from 'react-i18next'; +import { styles } from '@actual-app/components/styles'; +import { Tooltip } from '@actual-app/components/tooltip'; +import { View } from '@actual-app/components/view'; import { css } from '@emotion/css'; import { type TransObjectLiteral } from 'loot-core/types/util'; import { useFeatureFlag } from '../../hooks/useFeatureFlag'; import { SvgArrowThinRight } from '../../icons/v1'; -import { theme, styles } from '../../style'; -import { Tooltip } from '../common/Tooltip'; -import { View } from '../common/View'; +import { theme } from '../../style'; import { useResponsive } from '../responsive/ResponsiveProvider'; import { type Binding } from '../spreadsheet'; import { CellValue, CellValueText } from '../spreadsheet/CellValue'; @@ -186,10 +187,7 @@ export function BalanceWithCarryover({
{ { - type: - longGoalValue === 1 - ? t('Long', { context: 'noun' }) - : t('Template'), + type: longGoalValue === 1 ? t('Long') : t('Template'), } as TransObjectLiteral }
diff --git a/packages/desktop-client/src/components/budget/BudgetCategories.jsx b/packages/desktop-client/src/components/budget/BudgetCategories.jsx index 12efffd809b..b3a08e22782 100644 --- a/packages/desktop-client/src/components/budget/BudgetCategories.jsx +++ b/packages/desktop-client/src/components/budget/BudgetCategories.jsx @@ -1,8 +1,10 @@ import React, { memo, useState, useMemo } from 'react'; +import { styles } from '@actual-app/components/styles'; +import { View } from '@actual-app/components/view'; + import { useLocalPref } from '../../hooks/useLocalPref'; -import { theme, styles } from '../../style'; -import { View } from '../common/View'; +import { theme } from '../../style'; import { DropHighlightPosContext } from '../sort'; import { Row } from '../table'; diff --git a/packages/desktop-client/src/components/budget/BudgetPageHeader.tsx b/packages/desktop-client/src/components/budget/BudgetPageHeader.tsx index dfdb5844885..ef61f4c0401 100644 --- a/packages/desktop-client/src/components/budget/BudgetPageHeader.tsx +++ b/packages/desktop-client/src/components/budget/BudgetPageHeader.tsx @@ -1,7 +1,7 @@ // @ts-strict-ignore import React, { type ComponentProps, memo } from 'react'; -import { View } from '../common/View'; +import { View } from '@actual-app/components/view'; import { MonthPicker } from './MonthPicker'; import { getScrollbarWidth } from './util'; diff --git a/packages/desktop-client/src/components/budget/BudgetSummaries.tsx b/packages/desktop-client/src/components/budget/BudgetSummaries.tsx index 5308e8a36ac..8dffb677d65 100644 --- a/packages/desktop-client/src/components/budget/BudgetSummaries.tsx +++ b/packages/desktop-client/src/components/budget/BudgetSummaries.tsx @@ -7,12 +7,12 @@ import React, { } from 'react'; import { useSpring, animated } from 'react-spring'; +import { View } from '@actual-app/components/view'; import { css } from '@emotion/css'; import { addMonths, subMonths } from 'loot-core/shared/months'; import { useResizeObserver } from '../../hooks/useResizeObserver'; -import { View } from '../common/View'; import { type BudgetSummary as EnvelopeBudgetSummary } from './envelope/budgetsummary/BudgetSummary'; import { MonthsContext } from './MonthsContext'; diff --git a/packages/desktop-client/src/components/budget/BudgetTable.tsx b/packages/desktop-client/src/components/budget/BudgetTable.tsx index 66481fd64af..bcdf1719277 100644 --- a/packages/desktop-client/src/components/budget/BudgetTable.tsx +++ b/packages/desktop-client/src/components/budget/BudgetTable.tsx @@ -4,6 +4,9 @@ import React, { useState, } from 'react'; +import { styles } from '@actual-app/components/styles'; +import { View } from '@actual-app/components/view'; + import { type CategoryEntity, type CategoryGroupEntity, @@ -11,8 +14,7 @@ import { import { useCategories } from '../../hooks/useCategories'; import { useLocalPref } from '../../hooks/useLocalPref'; -import { theme, styles } from '../../style'; -import { View } from '../common/View'; +import { theme } from '../../style'; import { type DropPosition } from '../sort'; import { BudgetCategories } from './BudgetCategories'; diff --git a/packages/desktop-client/src/components/budget/BudgetTotals.tsx b/packages/desktop-client/src/components/budget/BudgetTotals.tsx index fa3e348b28f..59b9a2f4bc1 100644 --- a/packages/desktop-client/src/components/budget/BudgetTotals.tsx +++ b/packages/desktop-client/src/components/budget/BudgetTotals.tsx @@ -1,12 +1,14 @@ import React, { type ComponentProps, memo, useRef, useState } from 'react'; import { useTranslation, Trans } from 'react-i18next'; +import { Button } from '@actual-app/components/button'; +import { Menu } from '@actual-app/components/menu'; +import { Popover } from '@actual-app/components/popover'; +import { styles } from '@actual-app/components/styles'; +import { View } from '@actual-app/components/view'; + import { SvgDotsHorizontalTriple } from '../../icons/v1'; -import { theme, styles } from '../../style'; -import { Button } from '../common/Button2'; -import { Menu } from '../common/Menu'; -import { Popover } from '../common/Popover'; -import { View } from '../common/View'; +import { theme } from '../../style'; import { RenderMonths } from './RenderMonths'; import { getScrollbarWidth } from './util'; diff --git a/packages/desktop-client/src/components/budget/DynamicBudgetTable.tsx b/packages/desktop-client/src/components/budget/DynamicBudgetTable.tsx index 416c6be8d14..0875fda55dc 100644 --- a/packages/desktop-client/src/components/budget/DynamicBudgetTable.tsx +++ b/packages/desktop-client/src/components/budget/DynamicBudgetTable.tsx @@ -3,9 +3,9 @@ import React, { useEffect, type ComponentProps } from 'react'; import { useHotkeys } from 'react-hotkeys-hook'; import AutoSizer from 'react-virtualized-auto-sizer'; -import * as monthUtils from 'loot-core/shared/months'; +import { View } from '@actual-app/components/view'; -import { View } from '../common/View'; +import * as monthUtils from 'loot-core/shared/months'; import { useBudgetMonthCount } from './BudgetMonthCountContext'; import { BudgetPageHeader } from './BudgetPageHeader'; diff --git a/packages/desktop-client/src/components/budget/ExpenseCategory.tsx b/packages/desktop-client/src/components/budget/ExpenseCategory.tsx index f11721c2063..ed6877dee96 100644 --- a/packages/desktop-client/src/components/budget/ExpenseCategory.tsx +++ b/packages/desktop-client/src/components/budget/ExpenseCategory.tsx @@ -1,13 +1,14 @@ // @ts-strict-ignore import React, { type ComponentProps } from 'react'; +import { View } from '@actual-app/components/view'; + import { type CategoryGroupEntity, type CategoryEntity, } from 'loot-core/types/models'; import { theme } from '../../style'; -import { View } from '../common/View'; import { useDraggable, useDroppable, diff --git a/packages/desktop-client/src/components/budget/ExpenseGroup.tsx b/packages/desktop-client/src/components/budget/ExpenseGroup.tsx index 5fdbf16d848..61dad6f1e23 100644 --- a/packages/desktop-client/src/components/budget/ExpenseGroup.tsx +++ b/packages/desktop-client/src/components/budget/ExpenseGroup.tsx @@ -1,8 +1,9 @@ // @ts-strict-ignore import React, { type ComponentProps } from 'react'; +import { View } from '@actual-app/components/view'; + import { theme } from '../../style'; -import { View } from '../common/View'; import { useDraggable, useDroppable, diff --git a/packages/desktop-client/src/components/budget/IncomeHeader.tsx b/packages/desktop-client/src/components/budget/IncomeHeader.tsx index 193188fcf9d..3f3fbc1db3e 100644 --- a/packages/desktop-client/src/components/budget/IncomeHeader.tsx +++ b/packages/desktop-client/src/components/budget/IncomeHeader.tsx @@ -1,8 +1,8 @@ import React from 'react'; import { Trans } from 'react-i18next'; -import { Button } from '../common/Button2'; -import { View } from '../common/View'; +import { Button } from '@actual-app/components/button'; +import { View } from '@actual-app/components/view'; import { RenderMonths } from './RenderMonths'; diff --git a/packages/desktop-client/src/components/budget/MonthCountSelector.tsx b/packages/desktop-client/src/components/budget/MonthCountSelector.tsx index 22019451ae3..13a0163450a 100644 --- a/packages/desktop-client/src/components/budget/MonthCountSelector.tsx +++ b/packages/desktop-client/src/components/budget/MonthCountSelector.tsx @@ -1,9 +1,10 @@ import React from 'react'; import { useTranslation } from 'react-i18next'; +import { View } from '@actual-app/components/view'; + import { SvgCalendar } from '../../icons/v2'; import { theme } from '../../style'; -import { View } from '../common/View'; import { useBudgetMonthCount } from './BudgetMonthCountContext'; diff --git a/packages/desktop-client/src/components/budget/MonthPicker.tsx b/packages/desktop-client/src/components/budget/MonthPicker.tsx index d75c9725dc7..2085f52f7f2 100644 --- a/packages/desktop-client/src/components/budget/MonthPicker.tsx +++ b/packages/desktop-client/src/components/budget/MonthPicker.tsx @@ -2,13 +2,15 @@ import React, { type CSSProperties, useState } from 'react'; import { useTranslation } from 'react-i18next'; +import { styles } from '@actual-app/components/styles'; +import { View } from '@actual-app/components/view'; + import * as monthUtils from 'loot-core/shared/months'; import { useResizeObserver } from '../../hooks/useResizeObserver'; import { SvgCalendar } from '../../icons/v2'; -import { styles, theme } from '../../style'; +import { theme } from '../../style'; import { Link } from '../common/Link'; -import { View } from '../common/View'; import { type MonthBounds } from './MonthsContext'; diff --git a/packages/desktop-client/src/components/budget/RenderMonths.tsx b/packages/desktop-client/src/components/budget/RenderMonths.tsx index 4a80049c8ce..2b761c862f0 100644 --- a/packages/desktop-client/src/components/budget/RenderMonths.tsx +++ b/packages/desktop-client/src/components/budget/RenderMonths.tsx @@ -5,10 +5,11 @@ import React, { type ComponentType, } from 'react'; +import { View } from '@actual-app/components/view'; + import * as monthUtils from 'loot-core/shared/months'; import { theme } from '../../style'; -import { View } from '../common/View'; import { NamespaceContext } from '../spreadsheet/NamespaceContext'; import { MonthsContext } from './MonthsContext'; diff --git a/packages/desktop-client/src/components/budget/SidebarCategory.tsx b/packages/desktop-client/src/components/budget/SidebarCategory.tsx index 91cf34c7986..0b08646ce7f 100644 --- a/packages/desktop-client/src/components/budget/SidebarCategory.tsx +++ b/packages/desktop-client/src/components/budget/SidebarCategory.tsx @@ -2,21 +2,25 @@ import React, { type CSSProperties, type Ref, useRef } from 'react'; import { useTranslation } from 'react-i18next'; +import { Button } from '@actual-app/components/button'; +import { Menu } from '@actual-app/components/menu'; +import { Popover } from '@actual-app/components/popover'; +import { View } from '@actual-app/components/view'; + import { type CategoryGroupEntity, type CategoryEntity, } from 'loot-core/types/models'; import { useContextMenu } from '../../hooks/useContextMenu'; +import { useFeatureFlag } from '../../hooks/useFeatureFlag'; import { SvgCheveronDown } from '../../icons/v1'; import { theme } from '../../style'; -import { Button } from '../common/Button2'; -import { Menu } from '../common/Menu'; -import { Popover } from '../common/Popover'; -import { View } from '../common/View'; import { NotesButton } from '../NotesButton'; import { InputCell } from '../table'; +import { CategoryAutomationButton } from './goals/CategoryAutomationButton'; + type SidebarCategoryProps = { innerRef: Ref; category: CategoryEntity; @@ -24,6 +28,7 @@ type SidebarCategoryProps = { dragPreview?: boolean; dragging?: boolean; editing: boolean; + goalsShown?: boolean; style?: CSSProperties; borderColor?: string; isLast?: boolean; @@ -40,6 +45,7 @@ export function SidebarCategory({ dragPreview, dragging, editing, + goalsShown = false, style, isLast, onEditName, @@ -48,6 +54,7 @@ export function SidebarCategory({ onHideNewCategory, }: SidebarCategoryProps) { const { t } = useTranslation(); + const goalTemplatesUIEnabled = useFeatureFlag('goalTemplatesUIEnabled'); const temporary = category.id === 'new'; const { setMenuOpen, menuOpen, handleContextMenu, resetPosition, position } = @@ -117,17 +124,25 @@ export function SidebarCategory({ setMenuOpen(false); }} items={[ - { name: 'rename', text: 'Rename' }, + { name: 'rename', text: t('Rename') }, !categoryGroup?.hidden && { name: 'toggle-visibility', - text: category.hidden ? 'Show' : 'Hide', + text: category.hidden ? t('Show') : t('Hide'), }, - { name: 'delete', text: 'Delete' }, + { name: 'delete', text: t('Delete') }, ]} /> + {!goalsShown && goalTemplatesUIEnabled && ( + + + + )} , diff --git a/packages/desktop-client/src/components/budget/envelope/CoverMenu.tsx b/packages/desktop-client/src/components/budget/envelope/CoverMenu.tsx index cac3186bb11..bd2fe69b306 100644 --- a/packages/desktop-client/src/components/budget/envelope/CoverMenu.tsx +++ b/packages/desktop-client/src/components/budget/envelope/CoverMenu.tsx @@ -1,13 +1,14 @@ import React, { useMemo, useState } from 'react'; import { Trans, useTranslation } from 'react-i18next'; +import { Button } from '@actual-app/components/button'; +import { InitialFocus } from '@actual-app/components/initial-focus'; +import { View } from '@actual-app/components/view'; + import { type CategoryEntity } from 'loot-core/types/models'; import { useCategories } from '../../../hooks/useCategories'; import { CategoryAutocomplete } from '../../autocomplete/CategoryAutocomplete'; -import { Button } from '../../common/Button2'; -import { InitialFocus } from '../../common/InitialFocus'; -import { View } from '../../common/View'; import { addToBeBudgetedGroup, removeCategoriesFromGroups } from '../util'; type CoverMenuProps = { diff --git a/packages/desktop-client/src/components/budget/envelope/EnvelopeBudgetComponents.tsx b/packages/desktop-client/src/components/budget/envelope/EnvelopeBudgetComponents.tsx index 14489e8f77b..eee049d1353 100644 --- a/packages/desktop-client/src/components/budget/envelope/EnvelopeBudgetComponents.tsx +++ b/packages/desktop-client/src/components/budget/envelope/EnvelopeBudgetComponents.tsx @@ -6,6 +6,11 @@ import React, { } from 'react'; import { useTranslation, Trans } from 'react-i18next'; +import { Button } from '@actual-app/components/button'; +import { Popover } from '@actual-app/components/popover'; +import { styles } from '@actual-app/components/styles'; +import { Text } from '@actual-app/components/text'; +import { View } from '@actual-app/components/view'; import { css } from '@emotion/css'; import { envelopeBudget } from 'loot-core/client/queries'; @@ -16,11 +21,7 @@ import { integerToCurrency, amountToInteger } from 'loot-core/shared/util'; import { useContextMenu } from '../../../hooks/useContextMenu'; import { useUndo } from '../../../hooks/useUndo'; import { SvgCheveronDown } from '../../../icons/v1'; -import { styles, theme } from '../../../style'; -import { Button } from '../../common/Button2'; -import { Popover } from '../../common/Popover'; -import { Text } from '../../common/Text'; -import { View } from '../../common/View'; +import { theme } from '../../../style'; import { type Binding, type SheetFields } from '../../spreadsheet'; import { CellValue, CellValueText } from '../../spreadsheet/CellValue'; import { useSheetName } from '../../spreadsheet/useSheetName'; diff --git a/packages/desktop-client/src/components/budget/envelope/HoldMenu.tsx b/packages/desktop-client/src/components/budget/envelope/HoldMenu.tsx index d09f101c970..f0e96baa7b7 100644 --- a/packages/desktop-client/src/components/budget/envelope/HoldMenu.tsx +++ b/packages/desktop-client/src/components/budget/envelope/HoldMenu.tsx @@ -1,37 +1,26 @@ -import React, { - useState, - useContext, - useEffect, - type ChangeEvent, -} from 'react'; +import React, { useState, type ChangeEvent } from 'react'; import { Trans } from 'react-i18next'; -import { useSpreadsheet } from 'loot-core/client/SpreadsheetProvider'; +import { Button } from '@actual-app/components/button'; +import { InitialFocus } from '@actual-app/components/initial-focus'; +import { View } from '@actual-app/components/view'; + import { evalArithmetic } from 'loot-core/shared/arithmetic'; import { integerToCurrency, amountToInteger } from 'loot-core/shared/util'; -import { Button } from '../../common/Button2'; -import { InitialFocus } from '../../common/InitialFocus'; import { Input } from '../../common/Input'; -import { View } from '../../common/View'; -import { NamespaceContext } from '../../spreadsheet/NamespaceContext'; +import { useSheetValue } from '../../spreadsheet/useSheetValue'; type HoldMenuProps = { onSubmit: (amount: number) => void; onClose: () => void; }; export function HoldMenu({ onSubmit, onClose }: HoldMenuProps) { - const spreadsheet = useSpreadsheet(); - const sheetName = useContext(NamespaceContext); - const [amount, setAmount] = useState(null); - useEffect(() => { - (async () => { - const node = await spreadsheet.get(sheetName, 'to-budget'); - setAmount(integerToCurrency(Math.max(node.value as number, 0))); - })(); - }, []); + useSheetValue<'envelope-budget', 'to-budget'>('to-budget', ({ value }) => { + setAmount(integerToCurrency(Math.max(value || 0, 0))); + }); function submit(newAmount: string) { const parsedAmount = evalArithmetic(newAmount); diff --git a/packages/desktop-client/src/components/budget/envelope/TransferMenu.tsx b/packages/desktop-client/src/components/budget/envelope/TransferMenu.tsx index e5e893f25f1..41793ed9c12 100644 --- a/packages/desktop-client/src/components/budget/envelope/TransferMenu.tsx +++ b/packages/desktop-client/src/components/budget/envelope/TransferMenu.tsx @@ -1,16 +1,17 @@ import React, { useMemo, useState } from 'react'; import { Trans } from 'react-i18next'; +import { Button } from '@actual-app/components/button'; +import { InitialFocus } from '@actual-app/components/initial-focus'; +import { View } from '@actual-app/components/view'; + import { evalArithmetic } from 'loot-core/shared/arithmetic'; import { integerToCurrency, amountToInteger } from 'loot-core/shared/util'; import { type CategoryEntity } from 'loot-core/types/models'; import { useCategories } from '../../../hooks/useCategories'; import { CategoryAutocomplete } from '../../autocomplete/CategoryAutocomplete'; -import { Button } from '../../common/Button2'; -import { InitialFocus } from '../../common/InitialFocus'; import { Input } from '../../common/Input'; -import { View } from '../../common/View'; import { addToBeBudgetedGroup, removeCategoriesFromGroups } from '../util'; type TransferMenuProps = { diff --git a/packages/desktop-client/src/components/budget/envelope/budgetsummary/BudgetMonthMenu.tsx b/packages/desktop-client/src/components/budget/envelope/budgetsummary/BudgetMonthMenu.tsx index 2244bc4c6af..30016e9ce05 100644 --- a/packages/desktop-client/src/components/budget/envelope/budgetsummary/BudgetMonthMenu.tsx +++ b/packages/desktop-client/src/components/budget/envelope/budgetsummary/BudgetMonthMenu.tsx @@ -1,8 +1,9 @@ import React, { type ComponentPropsWithoutRef } from 'react'; import { useTranslation } from 'react-i18next'; +import { Menu } from '@actual-app/components/menu'; + import { useFeatureFlag } from '../../../../hooks/useFeatureFlag'; -import { Menu } from '../../../common/Menu'; type BudgetMonthMenuProps = Omit< ComponentPropsWithoutRef, diff --git a/packages/desktop-client/src/components/budget/envelope/budgetsummary/BudgetSummary.tsx b/packages/desktop-client/src/components/budget/envelope/budgetsummary/BudgetSummary.tsx index 21888cca50a..b8347ddc67d 100644 --- a/packages/desktop-client/src/components/budget/envelope/budgetsummary/BudgetSummary.tsx +++ b/packages/desktop-client/src/components/budget/envelope/budgetsummary/BudgetSummary.tsx @@ -1,6 +1,10 @@ import React, { useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; +import { Button } from '@actual-app/components/button'; +import { Popover } from '@actual-app/components/popover'; +import { styles } from '@actual-app/components/styles'; +import { View } from '@actual-app/components/view'; import { css } from '@emotion/css'; import * as monthUtils from 'loot-core/shared/months'; @@ -8,10 +12,7 @@ import * as monthUtils from 'loot-core/shared/months'; import { useUndo } from '../../../../hooks/useUndo'; import { SvgDotsHorizontalTriple } from '../../../../icons/v1'; import { SvgArrowButtonDown1, SvgArrowButtonUp1 } from '../../../../icons/v2'; -import { theme, styles } from '../../../../style'; -import { Button } from '../../../common/Button2'; -import { Popover } from '../../../common/Popover'; -import { View } from '../../../common/View'; +import { theme } from '../../../../style'; import { NotesButton } from '../../../NotesButton'; import { NamespaceContext } from '../../../spreadsheet/NamespaceContext'; import { useEnvelopeBudget } from '../EnvelopeBudgetContext'; diff --git a/packages/desktop-client/src/components/budget/envelope/budgetsummary/ToBudget.tsx b/packages/desktop-client/src/components/budget/envelope/budgetsummary/ToBudget.tsx index 42f32fac392..c3b788392de 100644 --- a/packages/desktop-client/src/components/budget/envelope/budgetsummary/ToBudget.tsx +++ b/packages/desktop-client/src/components/budget/envelope/budgetsummary/ToBudget.tsx @@ -5,11 +5,12 @@ import React, { useState, } from 'react'; +import { Popover } from '@actual-app/components/popover'; +import { View } from '@actual-app/components/view'; + import { envelopeBudget } from 'loot-core/client/queries'; import { useContextMenu } from '../../../../hooks/useContextMenu'; -import { Popover } from '../../../common/Popover'; -import { View } from '../../../common/View'; import { CoverMenu } from '../CoverMenu'; import { useEnvelopeSheetValue } from '../EnvelopeBudgetComponents'; import { HoldMenu } from '../HoldMenu'; diff --git a/packages/desktop-client/src/components/budget/envelope/budgetsummary/ToBudgetMenu.tsx b/packages/desktop-client/src/components/budget/envelope/budgetsummary/ToBudgetMenu.tsx index 3dd9b4a6857..3cb15da8dd2 100644 --- a/packages/desktop-client/src/components/budget/envelope/budgetsummary/ToBudgetMenu.tsx +++ b/packages/desktop-client/src/components/budget/envelope/budgetsummary/ToBudgetMenu.tsx @@ -1,9 +1,10 @@ import React, { type ComponentPropsWithoutRef } from 'react'; import { useTranslation } from 'react-i18next'; +import { Menu } from '@actual-app/components/menu'; + import { envelopeBudget } from 'loot-core/client/queries'; -import { Menu } from '../../../common/Menu'; import { useEnvelopeSheetValue } from '../EnvelopeBudgetComponents'; type ToBudgetMenuProps = Omit< diff --git a/packages/desktop-client/src/components/budget/envelope/budgetsummary/TotalsList.tsx b/packages/desktop-client/src/components/budget/envelope/budgetsummary/TotalsList.tsx index eae77e5eedc..07957345c64 100644 --- a/packages/desktop-client/src/components/budget/envelope/budgetsummary/TotalsList.tsx +++ b/packages/desktop-client/src/components/budget/envelope/budgetsummary/TotalsList.tsx @@ -1,4 +1,5 @@ import React, { type CSSProperties } from 'react'; +import { Trans } from 'react-i18next'; import { AlignedText } from '@actual-app/components/aligned-text'; import { Block } from '@actual-app/components/block'; @@ -120,10 +121,21 @@ export function TotalsList({ prevMonthName, style }: TotalsListProps) { - Available funds - Overspent in {prevMonthName} - Budgeted - For next month + + Available funds + + + + Overspent in {{ prevMonthName }} + + + + Budgeted + + + + For next month + ); diff --git a/packages/desktop-client/src/components/budget/goals/CategoryAutomationButton.tsx b/packages/desktop-client/src/components/budget/goals/CategoryAutomationButton.tsx new file mode 100644 index 00000000000..9aee1625b3b --- /dev/null +++ b/packages/desktop-client/src/components/budget/goals/CategoryAutomationButton.tsx @@ -0,0 +1,54 @@ +import React, { type CSSProperties } from 'react'; + +import { Button } from '@actual-app/components/button'; +import { theme } from '@actual-app/components/theme'; + +import { pushModal } from 'loot-core/client/actions'; +import { type Template } from 'loot-core/server/budget/types/templates'; + +import { useFeatureFlag } from '../../../hooks/useFeatureFlag'; +import { SvgChartPie } from '../../../icons/v1'; +import { useDispatch } from '../../../redux'; + +type CategoryAutomationButtonProps = { + width?: number; + height?: number; + defaultColor?: string; + style?: CSSProperties; +}; +export function CategoryAutomationButton({ + width = 12, + height = 12, + defaultColor = theme.buttonNormalText, + style, +}: CategoryAutomationButtonProps) { + const automations: Template[] = []; + const hasAutomations = !!automations.length; + + const dispatch = useDispatch(); + + const goalTemplatesEnabled = useFeatureFlag('goalTemplatesEnabled'); + const goalTemplatesUIEnabled = useFeatureFlag('goalTemplatesUIEnabled'); + + if (!goalTemplatesEnabled || !goalTemplatesUIEnabled) { + return null; + } + + return ( + + ); +} diff --git a/packages/desktop-client/src/components/budget/index.tsx b/packages/desktop-client/src/components/budget/index.tsx index 04778aa2937..e62b0de395c 100644 --- a/packages/desktop-client/src/components/budget/index.tsx +++ b/packages/desktop-client/src/components/budget/index.tsx @@ -2,6 +2,9 @@ import React, { memo, useMemo, useState, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; +import { styles } from '@actual-app/components/styles'; +import { View } from '@actual-app/components/view'; + import { addNotification, pushModal } from 'loot-core/client/actions'; import { applyBudgetAction, @@ -25,8 +28,6 @@ import { useLocalPref } from '../../hooks/useLocalPref'; import { useNavigate } from '../../hooks/useNavigate'; import { useSyncedPref } from '../../hooks/useSyncedPref'; import { useDispatch } from '../../redux'; -import { styles } from '../../style'; -import { View } from '../common/View'; import { NamespaceContext } from '../spreadsheet/NamespaceContext'; import { DynamicBudgetTable } from './DynamicBudgetTable'; diff --git a/packages/desktop-client/src/components/budget/tracking/BalanceMenu.tsx b/packages/desktop-client/src/components/budget/tracking/BalanceMenu.tsx index 22e3f5446bf..d5e39f3fce5 100644 --- a/packages/desktop-client/src/components/budget/tracking/BalanceMenu.tsx +++ b/packages/desktop-client/src/components/budget/tracking/BalanceMenu.tsx @@ -1,9 +1,9 @@ import React, { type ComponentPropsWithoutRef } from 'react'; import { useTranslation } from 'react-i18next'; -import { trackingBudget } from 'loot-core/client/queries'; +import { Menu } from '@actual-app/components/menu'; -import { Menu } from '../../common/Menu'; +import { trackingBudget } from 'loot-core/client/queries'; import { useTrackingSheetValue } from './TrackingBudgetComponents'; diff --git a/packages/desktop-client/src/components/budget/tracking/BudgetMenu.tsx b/packages/desktop-client/src/components/budget/tracking/BudgetMenu.tsx index b5f2f38c265..7144f409919 100644 --- a/packages/desktop-client/src/components/budget/tracking/BudgetMenu.tsx +++ b/packages/desktop-client/src/components/budget/tracking/BudgetMenu.tsx @@ -1,8 +1,9 @@ import React, { type ComponentPropsWithoutRef } from 'react'; import { useTranslation } from 'react-i18next'; +import { Menu } from '@actual-app/components/menu'; + import { useFeatureFlag } from '../../../hooks/useFeatureFlag'; -import { Menu } from '../../common/Menu'; type BudgetMenuProps = Omit< ComponentPropsWithoutRef, diff --git a/packages/desktop-client/src/components/budget/tracking/TrackingBudgetComponents.tsx b/packages/desktop-client/src/components/budget/tracking/TrackingBudgetComponents.tsx index 9bf9e9e536f..3afc6948e00 100644 --- a/packages/desktop-client/src/components/budget/tracking/TrackingBudgetComponents.tsx +++ b/packages/desktop-client/src/components/budget/tracking/TrackingBudgetComponents.tsx @@ -8,6 +8,11 @@ import React, { } from 'react'; import { Trans } from 'react-i18next'; +import { Button } from '@actual-app/components/button'; +import { Popover } from '@actual-app/components/popover'; +import { styles } from '@actual-app/components/styles'; +import { Text } from '@actual-app/components/text'; +import { View } from '@actual-app/components/view'; import { css } from '@emotion/css'; import { trackingBudget } from 'loot-core/client/queries'; @@ -17,11 +22,7 @@ import { integerToCurrency, amountToInteger } from 'loot-core/shared/util'; import { useUndo } from '../../../hooks/useUndo'; import { SvgCheveronDown } from '../../../icons/v1'; -import { styles, theme } from '../../../style'; -import { Button } from '../../common/Button2'; -import { Popover } from '../../common/Popover'; -import { Text } from '../../common/Text'; -import { View } from '../../common/View'; +import { theme } from '../../../style'; import { type Binding, type SheetFields } from '../../spreadsheet'; import { CellValue, CellValueText } from '../../spreadsheet/CellValue'; import { useSheetValue } from '../../spreadsheet/useSheetValue'; diff --git a/packages/desktop-client/src/components/budget/tracking/budgetsummary/BudgetMonthMenu.tsx b/packages/desktop-client/src/components/budget/tracking/budgetsummary/BudgetMonthMenu.tsx index d4b5e4c9cad..d1f941b21c4 100644 --- a/packages/desktop-client/src/components/budget/tracking/budgetsummary/BudgetMonthMenu.tsx +++ b/packages/desktop-client/src/components/budget/tracking/budgetsummary/BudgetMonthMenu.tsx @@ -1,8 +1,9 @@ import React, { type ComponentPropsWithoutRef } from 'react'; import { useTranslation } from 'react-i18next'; +import { Menu } from '@actual-app/components/menu'; + import { useFeatureFlag } from '../../../../hooks/useFeatureFlag'; -import { Menu } from '../../../common/Menu'; type BudgetMonthMenuProps = Omit< ComponentPropsWithoutRef, diff --git a/packages/desktop-client/src/components/budget/tracking/budgetsummary/BudgetSummary.tsx b/packages/desktop-client/src/components/budget/tracking/budgetsummary/BudgetSummary.tsx index 8df1ec15045..16eef91be3a 100644 --- a/packages/desktop-client/src/components/budget/tracking/budgetsummary/BudgetSummary.tsx +++ b/packages/desktop-client/src/components/budget/tracking/budgetsummary/BudgetSummary.tsx @@ -2,6 +2,11 @@ import React, { useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; +import { Button } from '@actual-app/components/button'; +import { Popover } from '@actual-app/components/popover'; +import { Stack } from '@actual-app/components/stack'; +import { styles } from '@actual-app/components/styles'; +import { View } from '@actual-app/components/view'; import { css } from '@emotion/css'; import * as monthUtils from 'loot-core/shared/months'; @@ -9,11 +14,7 @@ import * as monthUtils from 'loot-core/shared/months'; import { useUndo } from '../../../../hooks/useUndo'; import { SvgDotsHorizontalTriple } from '../../../../icons/v1'; import { SvgArrowButtonDown1, SvgArrowButtonUp1 } from '../../../../icons/v2'; -import { theme, styles } from '../../../../style'; -import { Button } from '../../../common/Button2'; -import { Popover } from '../../../common/Popover'; -import { Stack } from '../../../common/Stack'; -import { View } from '../../../common/View'; +import { theme } from '../../../../style'; import { NotesButton } from '../../../NotesButton'; import { NamespaceContext } from '../../../spreadsheet/NamespaceContext'; import { useTrackingBudget } from '../TrackingBudgetContext'; diff --git a/packages/desktop-client/src/components/budget/tracking/budgetsummary/BudgetTotal.tsx b/packages/desktop-client/src/components/budget/tracking/budgetsummary/BudgetTotal.tsx index 7e65d730e25..a5da04fa886 100644 --- a/packages/desktop-client/src/components/budget/tracking/budgetsummary/BudgetTotal.tsx +++ b/packages/desktop-client/src/components/budget/tracking/budgetsummary/BudgetTotal.tsx @@ -6,9 +6,11 @@ import React, { } from 'react'; import { Trans } from 'react-i18next'; -import { theme, styles } from '../../../../style'; -import { Text } from '../../../common/Text'; -import { View } from '../../../common/View'; +import { styles } from '@actual-app/components/styles'; +import { Text } from '@actual-app/components/text'; +import { View } from '@actual-app/components/view'; + +import { theme } from '../../../../style'; import { type SheetFields, type Binding } from '../../../spreadsheet'; import { CellValue, CellValueText } from '../../../spreadsheet/CellValue'; diff --git a/packages/desktop-client/src/components/budget/util.ts b/packages/desktop-client/src/components/budget/util.ts index 679d5e0c600..7d0bde3f547 100644 --- a/packages/desktop-client/src/components/budget/util.ts +++ b/packages/desktop-client/src/components/budget/util.ts @@ -1,6 +1,7 @@ // @ts-strict-ignore import { type CSSProperties } from 'react'; +import { styles } from '@actual-app/components/styles'; import { t } from 'i18next'; import { type useSpreadsheet } from 'loot-core/client/SpreadsheetProvider'; @@ -13,7 +14,7 @@ import { } from 'loot-core/types/models'; import { type SyncedPrefs } from 'loot-core/types/prefs'; -import { styles, theme } from '../../style'; +import { theme } from '../../style'; import { type DropPosition } from '../sort'; import { getValidMonthBounds } from './MonthsContext'; diff --git a/packages/desktop-client/src/components/common/Button2.ts b/packages/desktop-client/src/components/common/Button2.ts deleted file mode 100644 index 7871daccf4d..00000000000 --- a/packages/desktop-client/src/components/common/Button2.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { - Button as ActualComponentButton, - ButtonWithLoading as ActualComponentButtonWithLoading, -} from '@actual-app/components/button'; - -/** @deprecated please import Button from '@actual-app/components/button' */ -export const Button = ActualComponentButton; - -/** @deprecated please import ButtonWithLoading from '@actual-app/components/button' */ -export const ButtonWithLoading = ActualComponentButtonWithLoading; diff --git a/packages/desktop-client/src/components/common/Card.ts b/packages/desktop-client/src/components/common/Card.ts deleted file mode 100644 index ab9663033fa..00000000000 --- a/packages/desktop-client/src/components/common/Card.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { Card as ActualComponentCard } from '@actual-app/components/card'; - -/** @deprecated please import Card from '@actual-app/components/card' */ -export const Card = ActualComponentCard; diff --git a/packages/desktop-client/src/components/common/FormError.ts b/packages/desktop-client/src/components/common/FormError.ts deleted file mode 100644 index 81f37afb588..00000000000 --- a/packages/desktop-client/src/components/common/FormError.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { FormError as ActualComponentFormError } from '@actual-app/components/form-error'; - -/** @deprecated please import FormError from '@actual-app/components/form-error' */ -export const FormError = ActualComponentFormError; diff --git a/packages/desktop-client/src/components/common/InfoBubble.tsx b/packages/desktop-client/src/components/common/InfoBubble.tsx index d28879d80e0..69d7f271e35 100644 --- a/packages/desktop-client/src/components/common/InfoBubble.tsx +++ b/packages/desktop-client/src/components/common/InfoBubble.tsx @@ -1,12 +1,12 @@ import React, { useState } from 'react'; import { useLocation } from 'react-router-dom'; +import { Text } from '@actual-app/components/text'; +import { View } from '@actual-app/components/view'; + import { SvgInformationCircle } from '../../icons/v2'; import { theme } from '../../style'; -import { Text } from './Text'; -import { View } from './View'; - type InfoBubbleProps = { label: string; textWidth?: number; diff --git a/packages/desktop-client/src/components/common/InitialFocus.ts b/packages/desktop-client/src/components/common/InitialFocus.ts deleted file mode 100644 index 759add53a52..00000000000 --- a/packages/desktop-client/src/components/common/InitialFocus.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { InitialFocus as ActualComponentInitialFocus } from '@actual-app/components/initial-focus'; - -/** @deprecated please import InitialFocus from '@actual-app/components/initial-focus' */ -export const InitialFocus = ActualComponentInitialFocus; diff --git a/packages/desktop-client/src/components/common/InlineField.ts b/packages/desktop-client/src/components/common/InlineField.ts deleted file mode 100644 index 24019276be1..00000000000 --- a/packages/desktop-client/src/components/common/InlineField.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { InlineField as ActualComponentInlineField } from '@actual-app/components/inline-field'; - -/** @deprecated please import InlineField from '@actual-app/components/inline-field' */ -export const InlineField = ActualComponentInlineField; diff --git a/packages/desktop-client/src/components/common/Input.tsx b/packages/desktop-client/src/components/common/Input.tsx index a8173627ba1..158fb7251a5 100644 --- a/packages/desktop-client/src/components/common/Input.tsx +++ b/packages/desktop-client/src/components/common/Input.tsx @@ -5,11 +5,12 @@ import React, { useRef, } from 'react'; +import { styles } from '@actual-app/components/styles'; import { css, cx } from '@emotion/css'; import { useMergedRefs } from '../../hooks/useMergedRefs'; import { useProperFocus } from '../../hooks/useProperFocus'; -import { styles, theme, type CSSProperties } from '../../style'; +import { theme, type CSSProperties } from '../../style'; export const defaultInputStyle = { outline: 0, diff --git a/packages/desktop-client/src/components/common/InputWithContent.tsx b/packages/desktop-client/src/components/common/InputWithContent.tsx index 15f17394faa..6c60398bf83 100644 --- a/packages/desktop-client/src/components/common/InputWithContent.tsx +++ b/packages/desktop-client/src/components/common/InputWithContent.tsx @@ -1,9 +1,10 @@ import { useState, type ComponentProps, type ReactNode } from 'react'; +import { View } from '@actual-app/components/view'; + import { theme, type CSSProperties } from '../../style'; import { Input, defaultInputStyle } from './Input'; -import { View } from './View'; type InputWithContentProps = ComponentProps & { leftContent?: ReactNode; diff --git a/packages/desktop-client/src/components/common/Label.ts b/packages/desktop-client/src/components/common/Label.ts deleted file mode 100644 index c799cb06bbb..00000000000 --- a/packages/desktop-client/src/components/common/Label.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { Label as ActualComponentLabel } from '@actual-app/components/label'; - -/** @deprecated please import Label from '@actual-app/components/label' */ -export const Label = ActualComponentLabel; diff --git a/packages/desktop-client/src/components/common/Link.tsx b/packages/desktop-client/src/components/common/Link.tsx index 00225844537..d79c8ddc03a 100644 --- a/packages/desktop-client/src/components/common/Link.tsx +++ b/packages/desktop-client/src/components/common/Link.tsx @@ -5,15 +5,15 @@ import React, { } from 'react'; import { NavLink, useMatch } from 'react-router-dom'; +import { Button } from '@actual-app/components/button'; +import { styles } from '@actual-app/components/styles'; +import { Text } from '@actual-app/components/text'; import { css } from '@emotion/css'; import { useNavigate } from '../../hooks/useNavigate'; -import { type CSSProperties, styles } from '../../style'; +import { type CSSProperties } from '../../style'; import { theme } from '../../style/theme'; -import { Button } from './Button2'; -import { Text } from './Text'; - type TextLinkProps = { style?: CSSProperties; onClick?: MouseEventHandler; diff --git a/packages/desktop-client/src/components/common/Menu.ts b/packages/desktop-client/src/components/common/Menu.ts deleted file mode 100644 index d736353e625..00000000000 --- a/packages/desktop-client/src/components/common/Menu.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { - Menu as ActualComponentMenu, - type MenuItem as ActualComponentMenuItem, -} from '@actual-app/components/menu'; - -/** @deprecated please import Menu from '@actual-app/components/menu' */ -export const Menu = ActualComponentMenu; - -/** @deprecated please import MenuItem from '@actual-app/components/menu' */ -export type MenuItem = ActualComponentMenuItem; diff --git a/packages/desktop-client/src/components/common/MenuButton.tsx b/packages/desktop-client/src/components/common/MenuButton.tsx index bf55a955cc5..acb763cab44 100644 --- a/packages/desktop-client/src/components/common/MenuButton.tsx +++ b/packages/desktop-client/src/components/common/MenuButton.tsx @@ -1,8 +1,8 @@ import React, { type ComponentPropsWithoutRef, forwardRef } from 'react'; -import { SvgDotsHorizontalTriple } from '../../icons/v1'; +import { Button } from '@actual-app/components/button'; -import { Button } from './Button2'; +import { SvgDotsHorizontalTriple } from '../../icons/v1'; type MenuButtonProps = ComponentPropsWithoutRef; diff --git a/packages/desktop-client/src/components/common/Modal.tsx b/packages/desktop-client/src/components/common/Modal.tsx index 7a62a2a1d64..ec8751a9108 100644 --- a/packages/desktop-client/src/components/common/Modal.tsx +++ b/packages/desktop-client/src/components/common/Modal.tsx @@ -82,10 +82,6 @@ export const Modal = ({ position: 'fixed', inset: 0, zIndex: 3000, - overflowY: 'auto', - display: 'flex', - alignItems: 'center', - justifyContent: 'center', fontSize: 14, willChange: 'transform', // on mobile, we disable the blurred background for performance reasons @@ -100,69 +96,81 @@ export const Modal = ({ }} {...props} > - - {modalProps => ( - - + + {modalProps => ( + - - {typeof children === 'function' - ? children(modalProps) - : children} - - {isLoading && ( - - + + + {typeof children === 'function' + ? children(modalProps) + : children} - )} - - - )} - + {isLoading && ( + + + + )} + + + )} + + ); }; @@ -311,6 +319,7 @@ export function ModalHeader({ alignItems: 'center', position: 'relative', height: 60, + flex: 'none', }} > ; diff --git a/packages/desktop-client/src/components/common/Select.tsx b/packages/desktop-client/src/components/common/Select.tsx index f30dd3b3199..7f438eccc00 100644 --- a/packages/desktop-client/src/components/common/Select.tsx +++ b/packages/desktop-client/src/components/common/Select.tsx @@ -1,11 +1,11 @@ import { useRef, useState, type CSSProperties } from 'react'; -import { SvgExpandArrow } from '../../icons/v0'; +import { Button } from '@actual-app/components/button'; +import { Menu } from '@actual-app/components/menu'; +import { Popover } from '@actual-app/components/popover'; +import { View } from '@actual-app/components/view'; -import { Button } from './Button2'; -import { Menu } from './Menu'; -import { Popover } from './Popover'; -import { View } from './View'; +import { SvgExpandArrow } from '../../icons/v0'; function isValueOption( option: readonly [Value, string] | typeof Menu.line, diff --git a/packages/desktop-client/src/components/common/SimpleTable.tsx b/packages/desktop-client/src/components/common/SimpleTable.tsx index 6539d7655c0..fc059ba8385 100644 --- a/packages/desktop-client/src/components/common/SimpleTable.tsx +++ b/packages/desktop-client/src/components/common/SimpleTable.tsx @@ -6,7 +6,7 @@ import React, { type CSSProperties, } from 'react'; -import { View } from './View'; +import { View } from '@actual-app/components/view'; type SimpleTableProps = { loadMore?: () => void; diff --git a/packages/desktop-client/src/components/common/SpaceBetween.ts b/packages/desktop-client/src/components/common/SpaceBetween.ts deleted file mode 100644 index d272bf9a324..00000000000 --- a/packages/desktop-client/src/components/common/SpaceBetween.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { SpaceBetween as ActualComponentSpaceBetween } from '@actual-app/components/space-between'; - -/** @deprecated please import SpaceBetween from '@actual-app/components/space-between' */ -export const SpaceBetween = ActualComponentSpaceBetween; diff --git a/packages/desktop-client/src/components/common/Stack.ts b/packages/desktop-client/src/components/common/Stack.ts deleted file mode 100644 index e2a2f9b561f..00000000000 --- a/packages/desktop-client/src/components/common/Stack.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { Stack as ActualComponentStack } from '@actual-app/components/stack'; - -/** @deprecated please import Stack from '@actual-app/components/stack' */ -export const Stack = ActualComponentStack; diff --git a/packages/desktop-client/src/components/common/Text.ts b/packages/desktop-client/src/components/common/Text.ts deleted file mode 100644 index 74a1dcead77..00000000000 --- a/packages/desktop-client/src/components/common/Text.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { Text as ActualComponentText } from '@actual-app/components/text'; - -/** @deprecated please import Text from '@actual-app/components/text' */ -export const Text = ActualComponentText; diff --git a/packages/desktop-client/src/components/common/TextOneLine.ts b/packages/desktop-client/src/components/common/TextOneLine.ts deleted file mode 100644 index 3f09be34873..00000000000 --- a/packages/desktop-client/src/components/common/TextOneLine.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { TextOneLine as ActualComponentTextOneLine } from '@actual-app/components/text-one-line'; - -/** @deprecated please import TextOneLine from '@actual-app/components/text-one-line' */ -export const TextOneLine = ActualComponentTextOneLine; diff --git a/packages/desktop-client/src/components/common/Toggle.ts b/packages/desktop-client/src/components/common/Toggle.ts deleted file mode 100644 index 1e244e2c863..00000000000 --- a/packages/desktop-client/src/components/common/Toggle.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { Toggle as ActualComponentToggle } from '@actual-app/components/toggle'; - -/** @deprecated please import Toggle from '@actual-app/components/toggle' */ -export const Toggle = ActualComponentToggle; diff --git a/packages/desktop-client/src/components/common/Tooltip.ts b/packages/desktop-client/src/components/common/Tooltip.ts deleted file mode 100644 index 88c52e79957..00000000000 --- a/packages/desktop-client/src/components/common/Tooltip.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { Tooltip as ActualComponentTooltip } from '@actual-app/components/tooltip'; - -/** @deprecated please import Tooltip from '@actual-app/components/tooltip' */ -export const Tooltip = ActualComponentTooltip; diff --git a/packages/desktop-client/src/components/common/View.ts b/packages/desktop-client/src/components/common/View.ts deleted file mode 100644 index 1de101a9a21..00000000000 --- a/packages/desktop-client/src/components/common/View.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { View as ActualComponentView } from '@actual-app/components/view'; - -/** @deprecated please import View from '@actual-app/components/view' */ -export const View = ActualComponentView; diff --git a/packages/desktop-client/src/components/filters/AppliedFilters.tsx b/packages/desktop-client/src/components/filters/AppliedFilters.tsx index c1fa10aaf70..3f0ff1cb694 100644 --- a/packages/desktop-client/src/components/filters/AppliedFilters.tsx +++ b/packages/desktop-client/src/components/filters/AppliedFilters.tsx @@ -1,8 +1,8 @@ import React from 'react'; -import { type RuleConditionEntity } from 'loot-core/types/models'; +import { View } from '@actual-app/components/view'; -import { View } from '../common/View'; +import { type RuleConditionEntity } from 'loot-core/types/models'; import { ConditionsOpMenu } from './ConditionsOpMenu'; import { FilterExpression } from './FilterExpression'; diff --git a/packages/desktop-client/src/components/filters/CompactFiltersButton.tsx b/packages/desktop-client/src/components/filters/CompactFiltersButton.tsx index a9b1abe04fa..f94000ec147 100644 --- a/packages/desktop-client/src/components/filters/CompactFiltersButton.tsx +++ b/packages/desktop-client/src/components/filters/CompactFiltersButton.tsx @@ -1,7 +1,8 @@ import React from 'react'; +import { Button } from '@actual-app/components/button'; + import { SvgFilter } from '../../icons/v1'; -import { Button } from '../common/Button2'; export function CompactFiltersButton({ onPress }: { onPress: () => void }) { return ( diff --git a/packages/desktop-client/src/components/filters/ConditionsOpMenu.tsx b/packages/desktop-client/src/components/filters/ConditionsOpMenu.tsx index 41bf5be269b..e326c023dd8 100644 --- a/packages/desktop-client/src/components/filters/ConditionsOpMenu.tsx +++ b/packages/desktop-client/src/components/filters/ConditionsOpMenu.tsx @@ -1,10 +1,11 @@ import React from 'react'; +import { Text } from '@actual-app/components/text'; +import { View } from '@actual-app/components/view'; + import { type RuleConditionEntity } from 'loot-core/types/models'; import { theme } from '../../style'; -import { Text } from '../common/Text'; -import { View } from '../common/View'; import { FieldSelect } from '../modals/EditRuleModal'; export function ConditionsOpMenu({ diff --git a/packages/desktop-client/src/components/filters/FilterExpression.tsx b/packages/desktop-client/src/components/filters/FilterExpression.tsx index 47f2d9fd5c0..7906a6737d0 100644 --- a/packages/desktop-client/src/components/filters/FilterExpression.tsx +++ b/packages/desktop-client/src/components/filters/FilterExpression.tsx @@ -1,16 +1,17 @@ import React, { useRef, useState, type CSSProperties } from 'react'; import { useTranslation } from 'react-i18next'; +import { Button } from '@actual-app/components/button'; +import { Popover } from '@actual-app/components/popover'; +import { Text } from '@actual-app/components/text'; +import { View } from '@actual-app/components/view'; + import { mapField, friendlyOp } from 'loot-core/shared/rules'; import { integerToCurrency } from 'loot-core/shared/util'; import { type RuleConditionEntity } from 'loot-core/types/models'; import { SvgDelete } from '../../icons/v0'; import { theme } from '../../style'; -import { Button } from '../common/Button2'; -import { Popover } from '../common/Popover'; -import { Text } from '../common/Text'; -import { View } from '../common/View'; import { Value } from '../rules/Value'; import { FilterEditor } from './FiltersMenu'; diff --git a/packages/desktop-client/src/components/filters/FilterMenu.tsx b/packages/desktop-client/src/components/filters/FilterMenu.tsx index 94d389998c7..ea2e12dc7b7 100644 --- a/packages/desktop-client/src/components/filters/FilterMenu.tsx +++ b/packages/desktop-client/src/components/filters/FilterMenu.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { useTranslation } from 'react-i18next'; -import { Menu } from '../common/Menu'; +import { Menu } from '@actual-app/components/menu'; import { type SavedFilter } from './SavedFilterMenuButton'; diff --git a/packages/desktop-client/src/components/filters/FiltersButton.tsx b/packages/desktop-client/src/components/filters/FiltersButton.tsx index 1568986f0a8..91c78535c12 100644 --- a/packages/desktop-client/src/components/filters/FiltersButton.tsx +++ b/packages/desktop-client/src/components/filters/FiltersButton.tsx @@ -1,7 +1,8 @@ import React from 'react'; +import { Button } from '@actual-app/components/button'; + import { SvgFilter } from '../../icons/v1/Filter'; -import { Button } from '../common/Button2'; export function FiltersButton({ onPress }: { onPress: () => void }) { return ( diff --git a/packages/desktop-client/src/components/filters/FiltersMenu.jsx b/packages/desktop-client/src/components/filters/FiltersMenu.jsx index d0c80f27595..941bf949492 100644 --- a/packages/desktop-client/src/components/filters/FiltersMenu.jsx +++ b/packages/desktop-client/src/components/filters/FiltersMenu.jsx @@ -4,6 +4,14 @@ import { Form } from 'react-aria-components'; import { useHotkeys } from 'react-hotkeys-hook'; import { Trans, useTranslation } from 'react-i18next'; +import { Button } from '@actual-app/components/button'; +import { Menu } from '@actual-app/components/menu'; +import { Popover } from '@actual-app/components/popover'; +import { Stack } from '@actual-app/components/stack'; +import { styles } from '@actual-app/components/styles'; +import { Text } from '@actual-app/components/text'; +import { Tooltip } from '@actual-app/components/tooltip'; +import { View } from '@actual-app/components/view'; import { parse as parseDate, format as formatDate, @@ -24,15 +32,8 @@ import { import { titleFirst } from 'loot-core/shared/util'; import { useDateFormat } from '../../hooks/useDateFormat'; -import { styles, theme } from '../../style'; -import { Button } from '../common/Button2'; -import { Menu } from '../common/Menu'; -import { Popover } from '../common/Popover'; +import { theme } from '../../style'; import { Select } from '../common/Select'; -import { Stack } from '../common/Stack'; -import { Text } from '../common/Text'; -import { Tooltip } from '../common/Tooltip'; -import { View } from '../common/View'; import { GenericInput } from '../util/GenericInput'; import { CompactFiltersButton } from './CompactFiltersButton'; diff --git a/packages/desktop-client/src/components/filters/FiltersStack.tsx b/packages/desktop-client/src/components/filters/FiltersStack.tsx index f145c786864..2b021b86d4b 100644 --- a/packages/desktop-client/src/components/filters/FiltersStack.tsx +++ b/packages/desktop-client/src/components/filters/FiltersStack.tsx @@ -1,11 +1,11 @@ import React from 'react'; +import { Stack } from '@actual-app/components/stack'; +import { View } from '@actual-app/components/view'; + import { type TransactionFilterEntity } from 'loot-core/types/models'; import { type RuleConditionEntity } from 'loot-core/types/models/rule'; -import { Stack } from '../common/Stack'; -import { View } from '../common/View'; - import { AppliedFilters } from './AppliedFilters'; import { type SavedFilter, diff --git a/packages/desktop-client/src/components/filters/NameFilter.tsx b/packages/desktop-client/src/components/filters/NameFilter.tsx index 827b15fe860..336bf6be247 100644 --- a/packages/desktop-client/src/components/filters/NameFilter.tsx +++ b/packages/desktop-client/src/components/filters/NameFilter.tsx @@ -2,11 +2,12 @@ import React, { useRef, useEffect } from 'react'; import { Form } from 'react-aria-components'; import { useTranslation } from 'react-i18next'; +import { Button } from '@actual-app/components/button'; +import { Stack } from '@actual-app/components/stack'; +import { Text } from '@actual-app/components/text'; + import { theme } from '../../style'; -import { Button } from '../common/Button2'; import { Input } from '../common/Input'; -import { Stack } from '../common/Stack'; -import { Text } from '../common/Text'; import { FormField, FormLabel } from '../forms'; export function NameFilter({ diff --git a/packages/desktop-client/src/components/filters/OpButton.tsx b/packages/desktop-client/src/components/filters/OpButton.tsx index 0afac5deaf2..19a8a457532 100644 --- a/packages/desktop-client/src/components/filters/OpButton.tsx +++ b/packages/desktop-client/src/components/filters/OpButton.tsx @@ -1,11 +1,11 @@ import React, { type CSSProperties } from 'react'; +import { Button } from '@actual-app/components/button'; import { css } from '@emotion/css'; import { friendlyOp } from 'loot-core/shared/rules'; import { theme } from '../../style'; -import { Button } from '../common/Button2'; type OpButtonProps = { op: string; diff --git a/packages/desktop-client/src/components/filters/SavedFilterMenuButton.tsx b/packages/desktop-client/src/components/filters/SavedFilterMenuButton.tsx index b51be581bec..57e6633d430 100644 --- a/packages/desktop-client/src/components/filters/SavedFilterMenuButton.tsx +++ b/packages/desktop-client/src/components/filters/SavedFilterMenuButton.tsx @@ -1,15 +1,16 @@ import React, { useRef, useState } from 'react'; import { Trans, useTranslation } from 'react-i18next'; +import { Button } from '@actual-app/components/button'; +import { Popover } from '@actual-app/components/popover'; +import { Text } from '@actual-app/components/text'; +import { View } from '@actual-app/components/view'; + import { send, sendCatch } from 'loot-core/platform/client/fetch'; import { type TransactionFilterEntity } from 'loot-core/types/models'; import { type RuleConditionEntity } from 'loot-core/types/models/rule'; import { SvgExpandArrow } from '../../icons/v0'; -import { Button } from '../common/Button2'; -import { Popover } from '../common/Popover'; -import { Text } from '../common/Text'; -import { View } from '../common/View'; import { FilterMenu } from './FilterMenu'; import { NameFilter } from './NameFilter'; diff --git a/packages/desktop-client/src/components/forms.tsx b/packages/desktop-client/src/components/forms.tsx index 439acfae812..c86688e07bb 100644 --- a/packages/desktop-client/src/components/forms.tsx +++ b/packages/desktop-client/src/components/forms.tsx @@ -1,12 +1,11 @@ import React, { type ReactNode, type ComponentProps } from 'react'; +import { Text } from '@actual-app/components/text'; +import { View } from '@actual-app/components/view'; import { css } from '@emotion/css'; import { theme, type CSSProperties } from '../style'; -import { Text } from './common/Text'; -import { View } from './common/View'; - type SectionLabelProps = { title?: string; style?: CSSProperties; diff --git a/packages/desktop-client/src/components/gocardless/GoCardlessLink.tsx b/packages/desktop-client/src/components/gocardless/GoCardlessLink.tsx index 2a3312242e9..14e0a59b7df 100644 --- a/packages/desktop-client/src/components/gocardless/GoCardlessLink.tsx +++ b/packages/desktop-client/src/components/gocardless/GoCardlessLink.tsx @@ -1,9 +1,10 @@ import React from 'react'; import { Trans } from 'react-i18next'; +import { Paragraph } from '@actual-app/components/paragraph'; +import { View } from '@actual-app/components/view'; + import { Modal, ModalHeader } from '../common/Modal'; -import { Paragraph } from '../common/Paragraph'; -import { View } from '../common/View'; export function GoCardlessLink() { window.close(); diff --git a/packages/desktop-client/src/components/manager/BudgetList.tsx b/packages/desktop-client/src/components/manager/BudgetList.tsx index 5bcc4b123f4..0925434582a 100644 --- a/packages/desktop-client/src/components/manager/BudgetList.tsx +++ b/packages/desktop-client/src/components/manager/BudgetList.tsx @@ -7,6 +7,14 @@ import React, { } from 'react'; import { Trans, useTranslation } from 'react-i18next'; +import { Button } from '@actual-app/components/button'; +import { Menu } from '@actual-app/components/menu'; +import { Popover } from '@actual-app/components/popover'; +import { styles } from '@actual-app/components/styles'; +import { Text } from '@actual-app/components/text'; +import { Tooltip } from '@actual-app/components/tooltip'; +import { View } from '@actual-app/components/view'; + import { closeAndDownloadBudget, closeAndLoadBudget, @@ -43,14 +51,8 @@ import { } from '../../icons/v1'; import { SvgCloudUnknown, SvgKey, SvgRefreshArrow } from '../../icons/v2'; import { useSelector, useDispatch } from '../../redux'; -import { styles, theme } from '../../style'; +import { theme } from '../../style'; import { tokens } from '../../tokens'; -import { Button } from '../common/Button2'; -import { Menu } from '../common/Menu'; -import { Popover } from '../common/Popover'; -import { Text } from '../common/Text'; -import { Tooltip } from '../common/Tooltip'; -import { View } from '../common/View'; import { useResponsive } from '../responsive/ResponsiveProvider'; import { useMultiuserEnabled } from '../ServerContext'; diff --git a/packages/desktop-client/src/components/manager/ConfigServer.tsx b/packages/desktop-client/src/components/manager/ConfigServer.tsx index 44642b4c60e..3508aa10d47 100644 --- a/packages/desktop-client/src/components/manager/ConfigServer.tsx +++ b/packages/desktop-client/src/components/manager/ConfigServer.tsx @@ -2,6 +2,10 @@ import React, { useState, useEffect, useCallback } from 'react'; import { Trans, useTranslation } from 'react-i18next'; +import { Button, ButtonWithLoading } from '@actual-app/components/button'; +import { Text } from '@actual-app/components/text'; +import { View } from '@actual-app/components/view'; + import { createBudget, loggedIn, signOut } from 'loot-core/client/actions'; import { isNonProductionEnvironment, @@ -12,11 +16,8 @@ import { useGlobalPref } from '../../hooks/useGlobalPref'; import { useNavigate } from '../../hooks/useNavigate'; import { useDispatch } from '../../redux'; import { theme } from '../../style'; -import { Button, ButtonWithLoading } from '../common/Button2'; import { BigInput } from '../common/Input'; import { Link } from '../common/Link'; -import { Text } from '../common/Text'; -import { View } from '../common/View'; import { useServerURL, useSetServerURL } from '../ServerContext'; import { Title } from './subscribe/common'; diff --git a/packages/desktop-client/src/components/manager/ManagementApp.tsx b/packages/desktop-client/src/components/manager/ManagementApp.tsx index 12d2c79ffe0..8be567824ea 100644 --- a/packages/desktop-client/src/components/manager/ManagementApp.tsx +++ b/packages/desktop-client/src/components/manager/ManagementApp.tsx @@ -1,6 +1,9 @@ import React, { useEffect } from 'react'; import { Navigate, Route, Routes } from 'react-router-dom'; +import { Text } from '@actual-app/components/text'; +import { View } from '@actual-app/components/view'; + import { loggedIn } from 'loot-core/client/actions'; import { setAppState } from 'loot-core/client/app/appSlice'; @@ -15,8 +18,6 @@ import { UserDirectoryPage, } from '../admin/UserDirectory/UserDirectoryPage'; import { AppBackground } from '../AppBackground'; -import { Text } from '../common/Text'; -import { View } from '../common/View'; import { LoggedInUser } from '../LoggedInUser'; import { Notifications } from '../Notifications'; import { useResponsive } from '../responsive/ResponsiveProvider'; diff --git a/packages/desktop-client/src/components/manager/ServerURL.tsx b/packages/desktop-client/src/components/manager/ServerURL.tsx index d2d96bc7065..71b2c634b10 100644 --- a/packages/desktop-client/src/components/manager/ServerURL.tsx +++ b/packages/desktop-client/src/components/manager/ServerURL.tsx @@ -1,9 +1,10 @@ import React from 'react'; import { Trans } from 'react-i18next'; +import { Text } from '@actual-app/components/text'; +import { View } from '@actual-app/components/view'; + import { Link } from '../common/Link'; -import { Text } from '../common/Text'; -import { View } from '../common/View'; import { useServerURL } from '../ServerContext'; export function ServerURL() { diff --git a/packages/desktop-client/src/components/manager/WelcomeScreen.tsx b/packages/desktop-client/src/components/manager/WelcomeScreen.tsx index 7586402a78b..a8ff0fb6982 100644 --- a/packages/desktop-client/src/components/manager/WelcomeScreen.tsx +++ b/packages/desktop-client/src/components/manager/WelcomeScreen.tsx @@ -1,15 +1,17 @@ import React from 'react'; import { Trans, useTranslation } from 'react-i18next'; +import { Button } from '@actual-app/components/button'; +import { Paragraph } from '@actual-app/components/paragraph'; +import { styles } from '@actual-app/components/styles'; +import { Text } from '@actual-app/components/text'; +import { View } from '@actual-app/components/view'; + import { createBudget, pushModal } from 'loot-core/client/actions'; import { useDispatch } from '../../redux'; -import { styles, theme } from '../../style'; -import { Button } from '../common/Button2'; +import { theme } from '../../style'; import { Link } from '../common/Link'; -import { Paragraph } from '../common/Paragraph'; -import { Text } from '../common/Text'; -import { View } from '../common/View'; export function WelcomeScreen() { const { t } = useTranslation(); diff --git a/packages/desktop-client/src/components/manager/subscribe/Bootstrap.tsx b/packages/desktop-client/src/components/manager/subscribe/Bootstrap.tsx index 8b001f4f473..6687977c1ce 100644 --- a/packages/desktop-client/src/components/manager/subscribe/Bootstrap.tsx +++ b/packages/desktop-client/src/components/manager/subscribe/Bootstrap.tsx @@ -2,17 +2,18 @@ import React, { useState } from 'react'; import { Trans, useTranslation } from 'react-i18next'; +import { Button } from '@actual-app/components/button'; +import { Paragraph } from '@actual-app/components/paragraph'; +import { Text } from '@actual-app/components/text'; +import { View } from '@actual-app/components/view'; + import { createBudget } from 'loot-core/client/actions/budgets'; import { send } from 'loot-core/platform/client/fetch'; import { useNavigate } from '../../../hooks/useNavigate'; import { useDispatch } from '../../../redux'; import { theme } from '../../../style'; -import { Button } from '../../common/Button2'; import { Link } from '../../common/Link'; -import { Paragraph } from '../../common/Paragraph'; -import { Text } from '../../common/Text'; -import { View } from '../../common/View'; import { useRefreshLoginMethods } from '../../ServerContext'; import { useBootstrapped, Title } from './common'; diff --git a/packages/desktop-client/src/components/manager/subscribe/ChangePassword.tsx b/packages/desktop-client/src/components/manager/subscribe/ChangePassword.tsx index 0b4d6c0ee01..0d1c5eb7c68 100644 --- a/packages/desktop-client/src/components/manager/subscribe/ChangePassword.tsx +++ b/packages/desktop-client/src/components/manager/subscribe/ChangePassword.tsx @@ -2,13 +2,14 @@ import React, { useState } from 'react'; import { Trans, useTranslation } from 'react-i18next'; +import { Button } from '@actual-app/components/button'; +import { Text } from '@actual-app/components/text'; +import { View } from '@actual-app/components/view'; + import { send } from 'loot-core/platform/client/fetch'; import { useNavigate } from '../../../hooks/useNavigate'; import { theme } from '../../../style'; -import { Button } from '../../common/Button2'; -import { Text } from '../../common/Text'; -import { View } from '../../common/View'; import { Title } from './common'; import { ConfirmPasswordForm } from './ConfirmPasswordForm'; diff --git a/packages/desktop-client/src/components/manager/subscribe/ConfirmPasswordForm.tsx b/packages/desktop-client/src/components/manager/subscribe/ConfirmPasswordForm.tsx index cfb64077e44..7cd4b614c56 100644 --- a/packages/desktop-client/src/components/manager/subscribe/ConfirmPasswordForm.tsx +++ b/packages/desktop-client/src/components/manager/subscribe/ConfirmPasswordForm.tsx @@ -2,10 +2,11 @@ import React, { type ChangeEvent, type ReactNode, useState } from 'react'; import { Trans, useTranslation } from 'react-i18next'; +import { ButtonWithLoading } from '@actual-app/components/button'; +import { View } from '@actual-app/components/view'; + import { theme } from '../../../style'; -import { ButtonWithLoading } from '../../common/Button2'; import { BigInput } from '../../common/Input'; -import { View } from '../../common/View'; type ConfirmPasswordFormProps = { buttons: ReactNode; diff --git a/packages/desktop-client/src/components/manager/subscribe/Error.tsx b/packages/desktop-client/src/components/manager/subscribe/Error.tsx index 9168d9fbe02..623bb5e329b 100644 --- a/packages/desktop-client/src/components/manager/subscribe/Error.tsx +++ b/packages/desktop-client/src/components/manager/subscribe/Error.tsx @@ -3,11 +3,12 @@ import React from 'react'; import { Trans } from 'react-i18next'; import { useLocation } from 'react-router-dom'; +import { Button } from '@actual-app/components/button'; +import { Text } from '@actual-app/components/text'; +import { View } from '@actual-app/components/view'; + import { useNavigate } from '../../../hooks/useNavigate'; import { theme } from '../../../style'; -import { Button } from '../../common/Button2'; -import { Text } from '../../common/Text'; -import { View } from '../../common/View'; function getErrorMessage(reason) { switch (reason) { diff --git a/packages/desktop-client/src/components/manager/subscribe/Login.tsx b/packages/desktop-client/src/components/manager/subscribe/Login.tsx index 5c8ce27ab7b..df5de6965ab 100644 --- a/packages/desktop-client/src/components/manager/subscribe/Login.tsx +++ b/packages/desktop-client/src/components/manager/subscribe/Login.tsx @@ -3,6 +3,12 @@ import React, { useState, useEffect } from 'react'; import { Trans, useTranslation } from 'react-i18next'; import { useSearchParams } from 'react-router-dom'; +import { Button, ButtonWithLoading } from '@actual-app/components/button'; +import { Label } from '@actual-app/components/label'; +import { styles } from '@actual-app/components/styles'; +import { Text } from '@actual-app/components/text'; +import { View } from '@actual-app/components/view'; + import { loggedIn } from 'loot-core/client/actions/user'; import { send } from 'loot-core/platform/client/fetch'; import { isElectron } from 'loot-core/shared/environment'; @@ -11,14 +17,10 @@ import { type OpenIdConfig } from 'loot-core/types/models/openid'; import { useNavigate } from '../../../hooks/useNavigate'; import { AnimatedLoading } from '../../../icons/AnimatedLoading'; import { useDispatch } from '../../../redux'; -import { styles, theme } from '../../../style'; -import { Button, ButtonWithLoading } from '../../common/Button2'; +import { theme } from '../../../style'; import { BigInput } from '../../common/Input'; -import { Label } from '../../common/Label'; import { Link } from '../../common/Link'; import { Select } from '../../common/Select'; -import { Text } from '../../common/Text'; -import { View } from '../../common/View'; import { useResponsive } from '../../responsive/ResponsiveProvider'; import { useAvailableLoginMethods, useLoginMethod } from '../../ServerContext'; diff --git a/packages/desktop-client/src/components/manager/subscribe/OpenIdForm.tsx b/packages/desktop-client/src/components/manager/subscribe/OpenIdForm.tsx index 3a59f525b1f..ffb50ace57d 100644 --- a/packages/desktop-client/src/components/manager/subscribe/OpenIdForm.tsx +++ b/packages/desktop-client/src/components/manager/subscribe/OpenIdForm.tsx @@ -2,20 +2,22 @@ import { type ReactNode, useEffect, useState } from 'react'; import { Trans, useTranslation } from 'react-i18next'; import { useLocation, type Location } from 'react-router-dom'; +import { ButtonWithLoading } from '@actual-app/components/button'; +import { Menu } from '@actual-app/components/menu'; +import { Stack } from '@actual-app/components/stack'; +import { styles } from '@actual-app/components/styles'; +import { Text } from '@actual-app/components/text'; +import { View } from '@actual-app/components/view'; + import { addNotification } from 'loot-core/client/actions'; import { send } from 'loot-core/platform/client/fetch'; import { type Handlers } from 'loot-core/types/handlers'; import { type OpenIdConfig } from 'loot-core/types/models/openid'; -import { theme, styles } from '../../../style'; -import { ButtonWithLoading } from '../../common/Button2'; +import { theme } from '../../../style'; import { Input } from '../../common/Input'; import { Link } from '../../common/Link'; -import { Menu } from '../../common/Menu'; import { Select } from '../../common/Select'; -import { Stack } from '../../common/Stack'; -import { Text } from '../../common/Text'; -import { View } from '../../common/View'; import { FormField, FormLabel } from '../../forms'; import { useServerURL } from '../../ServerContext'; diff --git a/packages/desktop-client/src/components/mobile/FloatingActionBar.tsx b/packages/desktop-client/src/components/mobile/FloatingActionBar.tsx index dbee0a6cb85..fc8b1ad75b3 100644 --- a/packages/desktop-client/src/components/mobile/FloatingActionBar.tsx +++ b/packages/desktop-client/src/components/mobile/FloatingActionBar.tsx @@ -1,7 +1,8 @@ import { type PropsWithChildren, type CSSProperties } from 'react'; +import { View } from '@actual-app/components/view'; + import { theme } from '../../style'; -import { View } from '../common/View'; type FloatingActionBarProps = PropsWithChildren & { style: CSSProperties; diff --git a/packages/desktop-client/src/components/mobile/MobileBackButton.tsx b/packages/desktop-client/src/components/mobile/MobileBackButton.tsx index ab528289105..ac9cc7e0d02 100644 --- a/packages/desktop-client/src/components/mobile/MobileBackButton.tsx +++ b/packages/desktop-client/src/components/mobile/MobileBackButton.tsx @@ -1,11 +1,12 @@ import React, { type ComponentPropsWithoutRef } from 'react'; import { useTranslation } from 'react-i18next'; +import { Button } from '@actual-app/components/button'; +import { styles } from '@actual-app/components/styles'; +import { Text } from '@actual-app/components/text'; + import { useNavigate } from '../../hooks/useNavigate'; import { SvgCheveronLeft } from '../../icons/v1'; -import { styles } from '../../style'; -import { Button } from '../common/Button2'; -import { Text } from '../common/Text'; type MobileBackButtonProps = ComponentPropsWithoutRef; diff --git a/packages/desktop-client/src/components/mobile/MobileForms.tsx b/packages/desktop-client/src/components/mobile/MobileForms.tsx index 6f3035a4c0a..58ebcd0106e 100644 --- a/packages/desktop-client/src/components/mobile/MobileForms.tsx +++ b/packages/desktop-client/src/components/mobile/MobileForms.tsx @@ -6,14 +6,15 @@ import React, { type CSSProperties, } from 'react'; +import { styles } from '@actual-app/components/styles'; +import { Text } from '@actual-app/components/text'; +import { Toggle } from '@actual-app/components/toggle'; +import { View } from '@actual-app/components/view'; import { css } from '@emotion/css'; -import { theme, styles } from '../../style'; +import { theme } from '../../style'; import { Button } from '../common/Button'; import { Input } from '../common/Input'; -import { Text } from '../common/Text'; -import { Toggle } from '../common/Toggle'; -import { View } from '../common/View'; type FieldLabelProps = { title: string; diff --git a/packages/desktop-client/src/components/mobile/MobileNavTabs.tsx b/packages/desktop-client/src/components/mobile/MobileNavTabs.tsx index 1ca7c37492e..2d0498a4f2a 100644 --- a/packages/desktop-client/src/components/mobile/MobileNavTabs.tsx +++ b/packages/desktop-client/src/components/mobile/MobileNavTabs.tsx @@ -5,9 +5,12 @@ import React, { useCallback, useState, } from 'react'; +import { useTranslation } from 'react-i18next'; import { NavLink } from 'react-router-dom'; import { useSpring, animated, config } from 'react-spring'; +import { styles } from '@actual-app/components/styles'; +import { View } from '@actual-app/components/view'; import { useDrag } from '@use-gesture/react'; import { @@ -20,8 +23,7 @@ import { } from '../../icons/v1'; import { SvgReports } from '../../icons/v1/Reports'; import { SvgCalendar } from '../../icons/v2'; -import { theme, styles } from '../../style'; -import { View } from '../common/View'; +import { theme } from '../../style'; import { useResponsive } from '../responsive/ResponsiveProvider'; import { useScrollListener } from '../ScrollProvider'; @@ -36,6 +38,7 @@ const HIDDEN_Y = TOTAL_HEIGHT; export const MOBILE_NAV_HEIGHT = ROW_HEIGHT + PILL_HEIGHT; export function MobileNavTabs() { + const { t } = useTranslation(); const { isNarrowWidth } = useResponsive(); const [navbarState, setNavbarState] = useState<'default' | 'open' | 'hidden'>( 'default', @@ -89,49 +92,49 @@ export function MobileNavTabs() { const navTabs = [ { - name: 'Budget', + name: t('Budget'), path: '/budget', style: navTabStyle, Icon: SvgWallet, }, { - name: 'Transaction', + name: t('Transaction'), path: '/transactions/new', style: navTabStyle, Icon: SvgAdd, }, { - name: 'Accounts', + name: t('Accounts'), path: '/accounts', style: navTabStyle, Icon: SvgPiggyBank, }, { - name: 'Reports', + name: t('Reports'), path: '/reports', style: navTabStyle, Icon: SvgReports, }, { - name: 'Schedules (Soon)', + name: t('Schedules (Soon)'), path: '/schedules/soon', style: navTabStyle, Icon: SvgCalendar, }, { - name: 'Payees (Soon)', + name: t('Payees (Soon)'), path: '/payees/soon', style: navTabStyle, Icon: SvgStoreFront, }, { - name: 'Rules (Soon)', + name: t('Rules (Soon)'), path: '/rules/soon', style: navTabStyle, Icon: SvgTuning, }, { - name: 'Settings', + name: t('Settings'), path: '/settings', style: navTabStyle, Icon: SvgCog, diff --git a/packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx b/packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx index 6979ea5200e..3ac2b75af2b 100644 --- a/packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx +++ b/packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx @@ -5,6 +5,12 @@ import React, { useMemo, useState, } from 'react'; +import { useTranslation } from 'react-i18next'; + +import { Button } from '@actual-app/components/button'; +import { styles } from '@actual-app/components/styles'; +import { Text } from '@actual-app/components/text'; +import { View } from '@actual-app/components/view'; import { collapseModals, @@ -39,10 +45,7 @@ import { useDateFormat } from '../../../hooks/useDateFormat'; import { useFailedAccounts } from '../../../hooks/useFailedAccounts'; import { useNavigate } from '../../../hooks/useNavigate'; import { useSelector, useDispatch } from '../../../redux'; -import { styles, theme } from '../../../style'; -import { Button } from '../../common/Button2'; -import { Text } from '../../common/Text'; -import { View } from '../../common/View'; +import { theme } from '../../../style'; import { MobilePageHeader, Page } from '../../Page'; import { MobileBackButton } from '../MobileBackButton'; import { AddTransactionButton } from '../transactions/AddTransactionButton'; @@ -228,6 +231,7 @@ function TransactionListWithPreviews({ | 'uncategorized'; readonly accountName: AccountEntity['name'] | string; }) { + const { t } = useTranslation(); const baseTransactionsQuery = useCallback( () => queries.transactions(accountId).options({ splits: 'none' }).select('*'), @@ -338,7 +342,7 @@ function TransactionListWithPreviews({ balanceUncleared={balanceQueries.uncleared} isLoadingMore={isLoadingMore} onLoadMore={loadMoreTransactions} - searchPlaceholder={`Search ${accountName}`} + searchPlaceholder={t('Search {{accountName}}', { accountName })} onSearch={onSearch} onOpenTransaction={onOpenTransaction} onRefresh={onRefresh} diff --git a/packages/desktop-client/src/components/mobile/accounts/Accounts.tsx b/packages/desktop-client/src/components/mobile/accounts/Accounts.tsx index 11cb2fece99..ae0b3b4ed63 100644 --- a/packages/desktop-client/src/components/mobile/accounts/Accounts.tsx +++ b/packages/desktop-client/src/components/mobile/accounts/Accounts.tsx @@ -1,6 +1,11 @@ import React, { type CSSProperties, useCallback } from 'react'; import { useTranslation, Trans } from 'react-i18next'; +import { Button } from '@actual-app/components/button'; +import { styles } from '@actual-app/components/styles'; +import { Text } from '@actual-app/components/text'; +import { TextOneLine } from '@actual-app/components/text-one-line'; +import { View } from '@actual-app/components/view'; import { css } from '@emotion/css'; import { replaceModal } from 'loot-core/client/actions'; @@ -14,12 +19,8 @@ import { useNavigate } from '../../../hooks/useNavigate'; import { useSyncedPref } from '../../../hooks/useSyncedPref'; import { SvgAdd, SvgCheveronRight } from '../../../icons/v1'; import { useDispatch, useSelector } from '../../../redux'; -import { theme, styles } from '../../../style'; +import { theme } from '../../../style'; import { makeAmountFullStyle } from '../../budget/util'; -import { Button } from '../../common/Button2'; -import { Text } from '../../common/Text'; -import { TextOneLine } from '../../common/TextOneLine'; -import { View } from '../../common/View'; import { MobilePageHeader, Page } from '../../Page'; import { type Binding, type SheetFields } from '../../spreadsheet'; import { CellValue, CellValueText } from '../../spreadsheet/CellValue'; @@ -261,7 +262,7 @@ function AccountList({ {onBudgetAccounts.length > 0 && ( )} @@ -281,7 +282,7 @@ function AccountList({ {offBudgetAccounts.length > 0 && ( diff --git a/packages/desktop-client/src/components/mobile/budget/BudgetTable.jsx b/packages/desktop-client/src/components/mobile/budget/BudgetTable.jsx index 999a6de98cf..94055efbf08 100644 --- a/packages/desktop-client/src/components/mobile/budget/BudgetTable.jsx +++ b/packages/desktop-client/src/components/mobile/budget/BudgetTable.jsx @@ -1,6 +1,12 @@ import React, { memo, useCallback, useRef } from 'react'; import { useTranslation } from 'react-i18next'; +import { Button } from '@actual-app/components/button'; +import { Card } from '@actual-app/components/card'; +import { Label } from '@actual-app/components/label'; +import { styles } from '@actual-app/components/styles'; +import { Text } from '@actual-app/components/text'; +import { View } from '@actual-app/components/view'; import { css } from '@emotion/css'; import { AutoTextSize } from 'auto-text-size'; import memoizeOne from 'memoize-one'; @@ -29,17 +35,12 @@ import { SvgArrowThickRight, SvgCheveronRight, } from '../../../icons/v1'; -import { SvgViewShow } from '../../../icons/v2'; +import { SvgCalendar, SvgViewShow } from '../../../icons/v2'; import { useDispatch } from '../../../redux'; -import { theme, styles } from '../../../style'; +import { theme } from '../../../style'; import { BalanceWithCarryover } from '../../budget/BalanceWithCarryover'; import { makeAmountGrey, makeBalanceAmountStyle } from '../../budget/util'; -import { Button } from '../../common/Button2'; -import { Card } from '../../common/Card'; -import { Label } from '../../common/Label'; import { Link } from '../../common/Link'; -import { Text } from '../../common/Text'; -import { View } from '../../common/View'; import { MobilePageHeader, Page } from '../../Page'; import { PrivacyFilter } from '../../PrivacyFilter'; import { useResponsive } from '../../responsive/ResponsiveProvider'; @@ -487,7 +488,13 @@ const ExpenseCategory = memo(function ExpenseCategory({ }); dispatch(collapseModals(`${modalBudgetType}-balance-menu`)); showUndoNotification({ - message: `Covered ${category.name} overspending from ${categoriesById[fromCategoryId].name}.`, + message: t( + `Covered {{toCategoryName}} overspending from {{fromCategoryName}}.`, + { + toCategoryName: category.name, + fromCategoryName: categoriesById[fromCategoryId].name, + }, + ), }); }, }), @@ -501,6 +508,7 @@ const ExpenseCategory = memo(function ExpenseCategory({ month, onBudgetAction, showUndoNotification, + t, ]); const onOpenBalanceMenu = useCallback(() => { @@ -1627,6 +1635,7 @@ export function BudgetTable({ // editMode, onPrevMonth, onNextMonth, + onCurrentMonth, onSaveGroup, onDeleteGroup, onAddCategory, @@ -1693,6 +1702,16 @@ export function BudgetTable({ /> } + rightContent={ + + } /> } > diff --git a/packages/desktop-client/src/components/mobile/budget/CategoryTransactions.tsx b/packages/desktop-client/src/components/mobile/budget/CategoryTransactions.tsx index d196d053d16..9427fd9fd8b 100644 --- a/packages/desktop-client/src/components/mobile/budget/CategoryTransactions.tsx +++ b/packages/desktop-client/src/components/mobile/budget/CategoryTransactions.tsx @@ -1,5 +1,9 @@ import React, { useCallback, useEffect, useState } from 'react'; +import { TextOneLine } from '@actual-app/components/text-one-line'; +import { View } from '@actual-app/components/view'; + +import { SchedulesProvider } from 'loot-core/client/data-hooks/schedules'; import { useTransactions, useTransactionsSearch, @@ -17,8 +21,6 @@ import { import { useDateFormat } from '../../../hooks/useDateFormat'; import { useNavigate } from '../../../hooks/useNavigate'; import { useDispatch } from '../../../redux'; -import { TextOneLine } from '../../common/TextOneLine'; -import { View } from '../../common/View'; import { MobilePageHeader, Page } from '../../Page'; import { MobileBackButton } from '../MobileBackButton'; import { AddTransactionButton } from '../transactions/AddTransactionButton'; @@ -113,19 +115,21 @@ export function CategoryTransactions({ } padding={0} > - + + + ); } diff --git a/packages/desktop-client/src/components/mobile/budget/ListItem.tsx b/packages/desktop-client/src/components/mobile/budget/ListItem.tsx index f8c7c753335..b797cf23ef0 100644 --- a/packages/desktop-client/src/components/mobile/budget/ListItem.tsx +++ b/packages/desktop-client/src/components/mobile/budget/ListItem.tsx @@ -4,8 +4,9 @@ import React, { type CSSProperties, } from 'react'; +import { View } from '@actual-app/components/view'; + import { theme } from '../../../style'; -import { View } from '../../common/View'; const ROW_HEIGHT = 50; diff --git a/packages/desktop-client/src/components/mobile/budget/index.tsx b/packages/desktop-client/src/components/mobile/budget/index.tsx index b605b720d07..b9a95363cb7 100644 --- a/packages/desktop-client/src/components/mobile/budget/index.tsx +++ b/packages/desktop-client/src/components/mobile/budget/index.tsx @@ -1,6 +1,8 @@ // @ts-strict-ignore import React, { useCallback, useEffect, useState } from 'react'; +import { View } from '@actual-app/components/view'; + import { collapseModals, pushModal } from 'loot-core/client/actions'; import { sync } from 'loot-core/client/app/appSlice'; import { @@ -25,7 +27,6 @@ import { AnimatedLoading } from '../../../icons/AnimatedLoading'; import { useDispatch } from '../../../redux'; import { theme } from '../../../style'; import { prewarmMonth } from '../../budget/util'; -import { View } from '../../common/View'; import { NamespaceContext } from '../../spreadsheet/NamespaceContext'; import { SyncRefresh } from '../../SyncRefresh'; @@ -286,6 +287,12 @@ export function Budget() { setInitialized(true); }, [budgetType, setStartMonthPref, spreadsheet, startMonth]); + const onCurrentMonth = useCallback(async () => { + await prewarmMonth(budgetType, spreadsheet, currMonth); + setStartMonthPref(currMonth); + setInitialized(true); + }, [budgetType, setStartMonthPref, spreadsheet, currMonth]); + // const onOpenMonthActionMenu = () => { // const options = [ // 'Copy last month’s budget', @@ -500,6 +507,7 @@ export function Budget() { onShowBudgetSummary={onShowBudgetSummary} onPrevMonth={onPrevMonth} onNextMonth={onNextMonth} + onCurrentMonth={onCurrentMonth} onSaveGroup={onSaveGroup} onDeleteGroup={onDeleteGroup} onAddCategory={onOpenNewCategoryModal} diff --git a/packages/desktop-client/src/components/mobile/transactions/AddTransactionButton.tsx b/packages/desktop-client/src/components/mobile/transactions/AddTransactionButton.tsx index 2024205c3c0..372c3540381 100644 --- a/packages/desktop-client/src/components/mobile/transactions/AddTransactionButton.tsx +++ b/packages/desktop-client/src/components/mobile/transactions/AddTransactionButton.tsx @@ -1,9 +1,10 @@ import React from 'react'; import { useTranslation } from 'react-i18next'; +import { Button } from '@actual-app/components/button'; + import { useNavigate } from '../../../hooks/useNavigate'; import { SvgAdd } from '../../../icons/v1'; -import { Button } from '../../common/Button2'; type AddTransactionButtonProps = { to?: string; diff --git a/packages/desktop-client/src/components/mobile/transactions/FocusableAmountInput.tsx b/packages/desktop-client/src/components/mobile/transactions/FocusableAmountInput.tsx index cf3fcd18027..feee7554ce4 100644 --- a/packages/desktop-client/src/components/mobile/transactions/FocusableAmountInput.tsx +++ b/packages/desktop-client/src/components/mobile/transactions/FocusableAmountInput.tsx @@ -9,6 +9,9 @@ import React, { type CSSProperties, } from 'react'; +import { Button } from '@actual-app/components/button'; +import { Text } from '@actual-app/components/text'; +import { View } from '@actual-app/components/view'; import { css } from '@emotion/css'; import { @@ -21,9 +24,6 @@ import { useMergedRefs } from '../../../hooks/useMergedRefs'; import { useSyncedPref } from '../../../hooks/useSyncedPref'; import { theme } from '../../../style'; import { makeAmountFullStyle } from '../../budget/util'; -import { Button } from '../../common/Button2'; -import { Text } from '../../common/Text'; -import { View } from '../../common/View'; type AmountInputProps = { value: number; diff --git a/packages/desktop-client/src/components/mobile/transactions/TransactionEdit.jsx b/packages/desktop-client/src/components/mobile/transactions/TransactionEdit.jsx index 4a95a2d6a40..e0f15d20dbd 100644 --- a/packages/desktop-client/src/components/mobile/transactions/TransactionEdit.jsx +++ b/packages/desktop-client/src/components/mobile/transactions/TransactionEdit.jsx @@ -7,9 +7,13 @@ import React, { useMemo, useCallback, } from 'react'; -import { useTranslation } from 'react-i18next'; +import { Trans, useTranslation } from 'react-i18next'; import { useLocation, useParams } from 'react-router-dom'; +import { styles } from '@actual-app/components/styles'; +import { Text } from '@actual-app/components/text'; +import { Toggle } from '@actual-app/components/toggle'; +import { View } from '@actual-app/components/view'; import { format as formatDate, parse as parseDate, @@ -57,11 +61,8 @@ import { SvgSplit } from '../../../icons/v0'; import { SvgAdd, SvgPiggyBank, SvgTrash } from '../../../icons/v1'; import { SvgPencilWriteAlternate } from '../../../icons/v2'; import { useSelector, useDispatch } from '../../../redux'; -import { styles, theme } from '../../../style'; +import { theme } from '../../../style'; import { Button } from '../../common/Button'; -import { Text } from '../../common/Text'; -import { Toggle } from '../../common/Toggle'; -import { View } from '../../common/View'; import { MobilePageHeader, Page } from '../../Page'; import { AmountInput } from '../../util/AmountInput'; import { MobileBackButton } from '../MobileBackButton'; @@ -235,7 +236,7 @@ function Footer({ marginLeft: 6, }} > - Select account + Select account
) : isAdding ? ( @@ -253,7 +254,7 @@ function Footer({ marginLeft: 5, }} > - Add transaction + Add transaction ) : ( @@ -749,8 +750,8 @@ const TransactionEditInner = memo(function TransactionEditInner({ title={ transaction.payee == null ? isAdding - ? 'New Transaction' - : 'Transaction' + ? t('New Transaction') + : t('Transaction') : title } leftContent={} diff --git a/packages/desktop-client/src/components/mobile/transactions/TransactionList.tsx b/packages/desktop-client/src/components/mobile/transactions/TransactionList.tsx index bc983b2b246..2fe59e36af3 100644 --- a/packages/desktop-client/src/components/mobile/transactions/TransactionList.tsx +++ b/packages/desktop-client/src/components/mobile/transactions/TransactionList.tsx @@ -7,7 +7,7 @@ import React, { type CSSProperties, } from 'react'; import { ListBox, Section, Header, Collection } from 'react-aria-components'; -import { useTranslation } from 'react-i18next'; +import { Trans, useTranslation } from 'react-i18next'; import { Button } from '@actual-app/components/button'; import { Menu, type MenuItemObject } from '@actual-app/components/menu'; @@ -149,7 +149,9 @@ export function TransactionList({ backgroundColor: theme.mobilePageBackground, }} > - No transactions + + No transactions + )} items={sections} diff --git a/packages/desktop-client/src/components/mobile/transactions/TransactionListItem.tsx b/packages/desktop-client/src/components/mobile/transactions/TransactionListItem.tsx index 8d50fbae5b8..f2a0976ec9e 100644 --- a/packages/desktop-client/src/components/mobile/transactions/TransactionListItem.tsx +++ b/packages/desktop-client/src/components/mobile/transactions/TransactionListItem.tsx @@ -4,39 +4,68 @@ import React, { } from 'react'; import { mergeProps } from 'react-aria'; import { ListBoxItem } from 'react-aria-components'; +import { useTranslation } from 'react-i18next'; +import { Button } from '@actual-app/components/button'; +import { styles } from '@actual-app/components/styles'; +import { Text } from '@actual-app/components/text'; +import { TextOneLine } from '@actual-app/components/text-one-line'; +import { View } from '@actual-app/components/view'; import { PressResponder, usePress, useLongPress, } from '@react-aria/interactions'; -import { isPreviewId } from 'loot-core/shared/transactions'; -import { integerToCurrency } from 'loot-core/shared/util'; -import { type TransactionEntity } from 'loot-core/types/models'; +import { useCachedSchedules } from 'loot-core/client/data-hooks/schedules'; +import { isPreviewId } from 'loot-core/src/shared/transactions'; +import { integerToCurrency } from 'loot-core/src/shared/util'; +import { + type AccountEntity, + type TransactionEntity, +} from 'loot-core/types/models'; import { useAccount } from '../../../hooks/useAccount'; import { useCategories } from '../../../hooks/useCategories'; +import { useDisplayPayee } from '../../../hooks/useDisplayPayee'; import { usePayee } from '../../../hooks/usePayee'; -import { SvgSplit } from '../../../icons/v0'; +import { SvgLeftArrow2, SvgRightArrow2, SvgSplit } from '../../../icons/v0'; import { SvgArrowsSynchronize, + SvgCalendar, SvgCheckCircle1, SvgLockClosed, } from '../../../icons/v2'; import { useSelector } from '../../../redux'; -import { styles, theme } from '../../../style'; +import { theme } from '../../../style'; import { makeAmountFullStyle } from '../../budget/util'; -import { Button } from '../../common/Button2'; -import { Text } from '../../common/Text'; -import { TextOneLine } from '../../common/TextOneLine'; -import { View } from '../../common/View'; -import { getPrettyPayee } from '../utils'; import { lookupName, Status } from './TransactionEdit'; const ROW_HEIGHT = 60; +const getTextStyle = ({ + isPreview, +}: { + isPreview: boolean; +}): CSSProperties => ({ + ...styles.text, + fontSize: 14, + ...(isPreview + ? { + fontStyle: 'italic', + color: theme.pageTextLight, + } + : {}), +}); + +const getScheduleIconStyle = ({ isPreview }: { isPreview: boolean }) => ({ + width: 12, + height: 12, + marginRight: 5, + color: isPreview ? theme.pageTextLight : theme.menuItemText, +}); + type TransactionListItemProps = ComponentPropsWithoutRef< typeof ListBoxItem > & { @@ -49,11 +78,14 @@ export function TransactionListItem({ onLongPress, ...props }: TransactionListItemProps) { + const { t } = useTranslation(); const { list: categories } = useCategories(); const { value: transaction } = props; const payee = usePayee(transaction?.payee || ''); + const displayPayee = useDisplayPayee({ transaction }); + const account = useAccount(transaction?.account || ''); const transferAccount = useAccount(payee?.transfer_acct || ''); const isPreview = isPreviewId(transaction?.id || ''); @@ -90,7 +122,6 @@ export function TransactionListItem({ is_parent: isParent, is_child: isChild, notes, - schedule: scheduleId, forceUpcoming, } = transaction; @@ -98,11 +129,6 @@ export function TransactionListItem({ const isAdded = newTransactions.includes(id); const categoryName = lookupName(categories, categoryId); - const prettyPayee = getPrettyPayee({ - transaction, - payee, - transferAccount, - }); const specialCategory = account?.offbudget ? 'Off budget' : transferAccount && !transferAccount.offbudget @@ -112,17 +138,7 @@ export function TransactionListItem({ : null; const prettyCategory = specialCategory || categoryName; - - const textStyle: CSSProperties = { - ...styles.text, - fontSize: 14, - ...(isPreview - ? { - fontStyle: 'italic', - color: theme.pageTextLight, - } - : {}), - }; + const textStyle = getTextStyle({ isPreview }); return ( @@ -165,27 +181,23 @@ export function TransactionListItem({ > - {scheduleId && ( - - )} + - {prettyPayee || '(No payee)'} + {displayPayee || t('(No payee)')} {isPreview ? ( @@ -282,3 +294,39 @@ export function TransactionListItem({ ); } + +type PayeeIconsProps = { + transaction: TransactionEntity; + transferAccount?: AccountEntity; +}; + +function PayeeIcons({ transaction, transferAccount }: PayeeIconsProps) { + const { id, schedule: scheduleId } = transaction; + const { isLoading: isSchedulesLoading, schedules = [] } = + useCachedSchedules(); + const isPreview = isPreviewId(id); + const schedule = schedules.find(s => s.id === scheduleId); + const isScheduleRecurring = + schedule && schedule._date && !!schedule._date.frequency; + + if (isSchedulesLoading) { + return null; + } + + return ( + <> + {schedule && + (isScheduleRecurring ? ( + + ) : ( + + ))} + {transferAccount && + (transaction.amount > 0 ? ( + + ) : ( + + ))} + + ); +} diff --git a/packages/desktop-client/src/components/mobile/transactions/TransactionListWithBalances.tsx b/packages/desktop-client/src/components/mobile/transactions/TransactionListWithBalances.tsx index 34260f1f9ce..a8da911d1d0 100644 --- a/packages/desktop-client/src/components/mobile/transactions/TransactionListWithBalances.tsx +++ b/packages/desktop-client/src/components/mobile/transactions/TransactionListWithBalances.tsx @@ -1,14 +1,16 @@ import React, { type ComponentProps, useState } from 'react'; import { useTranslation } from 'react-i18next'; +import { Label } from '@actual-app/components/label'; +import { styles } from '@actual-app/components/styles'; +import { View } from '@actual-app/components/view'; + import { type TransactionEntity } from 'loot-core/types/models/transaction'; import { SelectedProvider, useSelected } from '../../../hooks/useSelected'; import { SvgSearchAlternate } from '../../../icons/v2'; -import { styles, theme } from '../../../style'; +import { theme } from '../../../style'; import { InputWithContent } from '../../common/InputWithContent'; -import { Label } from '../../common/Label'; -import { View } from '../../common/View'; import type { Binding, SheetNames, SheetFields } from '../../spreadsheet'; import { CellValue, CellValueText } from '../../spreadsheet/CellValue'; import { useSheetValue } from '../../spreadsheet/useSheetValue'; diff --git a/packages/desktop-client/src/components/modals/AccountAutocompleteModal.tsx b/packages/desktop-client/src/components/modals/AccountAutocompleteModal.tsx index 6e3b3170231..bfb9a0691aa 100644 --- a/packages/desktop-client/src/components/modals/AccountAutocompleteModal.tsx +++ b/packages/desktop-client/src/components/modals/AccountAutocompleteModal.tsx @@ -1,6 +1,8 @@ import React, { type ComponentPropsWithoutRef } from 'react'; import { useTranslation } from 'react-i18next'; +import { View } from '@actual-app/components/view'; + import { theme } from '../../style'; import { AccountAutocomplete } from '../autocomplete/AccountAutocomplete'; import { @@ -9,7 +11,6 @@ import { ModalTitle, ModalHeader, } from '../common/Modal'; -import { View } from '../common/View'; import { SectionLabel } from '../forms'; import { useResponsive } from '../responsive/ResponsiveProvider'; @@ -35,7 +36,9 @@ export function AccountAutocompleteModal({ onClose={onClose} containerProps={{ style: { - height: isNarrowWidth ? '85vh' : 275, + height: isNarrowWidth + ? 'calc(var(--visual-viewport-height) * 0.85)' + : 275, backgroundColor: theme.menuAutoCompleteBackground, }, }} diff --git a/packages/desktop-client/src/components/modals/AccountMenuModal.tsx b/packages/desktop-client/src/components/modals/AccountMenuModal.tsx index a7553c384c5..b36318ea864 100644 --- a/packages/desktop-client/src/components/modals/AccountMenuModal.tsx +++ b/packages/desktop-client/src/components/modals/AccountMenuModal.tsx @@ -7,6 +7,12 @@ import { } from 'react'; import { useTranslation } from 'react-i18next'; +import { Button } from '@actual-app/components/button'; +import { Menu } from '@actual-app/components/menu'; +import { Popover } from '@actual-app/components/popover'; +import { styles } from '@actual-app/components/styles'; +import { View } from '@actual-app/components/view'; + import { type AccountEntity } from 'loot-core/types/models'; import { useAccount } from '../../hooks/useAccount'; @@ -14,17 +20,13 @@ import { useAccounts } from '../../hooks/useAccounts'; import { useNotes } from '../../hooks/useNotes'; import { SvgClose, SvgDotsHorizontalTriple, SvgLockOpen } from '../../icons/v1'; import { SvgNotesPaper } from '../../icons/v2'; -import { styles, theme } from '../../style'; -import { Button } from '../common/Button2'; -import { Menu } from '../common/Menu'; +import { theme } from '../../style'; import { Modal, ModalCloseButton, ModalHeader, ModalTitle, } from '../common/Modal'; -import { Popover } from '../common/Popover'; -import { View } from '../common/View'; import { Notes } from '../Notes'; import { validateAccountName } from '../util/accountValidation'; diff --git a/packages/desktop-client/src/components/modals/BudgetListModal.tsx b/packages/desktop-client/src/components/modals/BudgetListModal.tsx index 1d1d5fc77f5..7e51f6b9bf6 100644 --- a/packages/desktop-client/src/components/modals/BudgetListModal.tsx +++ b/packages/desktop-client/src/components/modals/BudgetListModal.tsx @@ -1,11 +1,12 @@ import React from 'react'; import { useTranslation } from 'react-i18next'; +import { Text } from '@actual-app/components/text'; +import { View } from '@actual-app/components/view'; + import { useMetadataPref } from '../../hooks/useMetadataPref'; import { useSelector } from '../../redux'; import { Modal, ModalHeader, ModalCloseButton } from '../common/Modal'; -import { Text } from '../common/Text'; -import { View } from '../common/View'; import { BudgetList } from '../manager/BudgetList'; export function BudgetListModal() { diff --git a/packages/desktop-client/src/components/modals/BudgetPageMenuModal.tsx b/packages/desktop-client/src/components/modals/BudgetPageMenuModal.tsx index e96abcdc8e7..733076afdad 100644 --- a/packages/desktop-client/src/components/modals/BudgetPageMenuModal.tsx +++ b/packages/desktop-client/src/components/modals/BudgetPageMenuModal.tsx @@ -4,9 +4,11 @@ import React, { } from 'react'; import { useTranslation } from 'react-i18next'; +import { Menu } from '@actual-app/components/menu'; +import { styles } from '@actual-app/components/styles'; + import { useLocalPref } from '../../hooks/useLocalPref'; -import { theme, styles } from '../../style'; -import { Menu } from '../common/Menu'; +import { theme } from '../../style'; import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal'; type BudgetPageMenuModalProps = ComponentPropsWithoutRef; diff --git a/packages/desktop-client/src/components/modals/CategoryAutocompleteModal.tsx b/packages/desktop-client/src/components/modals/CategoryAutocompleteModal.tsx index 269e552ee6d..7ff32ed6708 100644 --- a/packages/desktop-client/src/components/modals/CategoryAutocompleteModal.tsx +++ b/packages/desktop-client/src/components/modals/CategoryAutocompleteModal.tsx @@ -1,6 +1,8 @@ import React, { type ComponentPropsWithoutRef } from 'react'; import { useTranslation } from 'react-i18next'; +import { View } from '@actual-app/components/view'; + import * as monthUtils from 'loot-core/shared/months'; import { theme } from '../../style'; @@ -11,7 +13,6 @@ import { ModalTitle, ModalHeader, } from '../common/Modal'; -import { View } from '../common/View'; import { SectionLabel } from '../forms'; import { useResponsive } from '../responsive/ResponsiveProvider'; import { NamespaceContext } from '../spreadsheet/NamespaceContext'; @@ -41,7 +42,9 @@ export function CategoryAutocompleteModal({ onClose={onClose} containerProps={{ style: { - height: isNarrowWidth ? '85vh' : 275, + height: isNarrowWidth + ? 'calc(var(--visual-viewport-height) * 0.85)' + : 275, backgroundColor: theme.menuAutoCompleteBackground, }, }} diff --git a/packages/desktop-client/src/components/modals/CategoryGroupMenuModal.tsx b/packages/desktop-client/src/components/modals/CategoryGroupMenuModal.tsx index bf22caa105b..f502a62f41c 100644 --- a/packages/desktop-client/src/components/modals/CategoryGroupMenuModal.tsx +++ b/packages/desktop-client/src/components/modals/CategoryGroupMenuModal.tsx @@ -7,23 +7,25 @@ import React, { } from 'react'; import { useTranslation } from 'react-i18next'; +import { Button } from '@actual-app/components/button'; +import { Menu } from '@actual-app/components/menu'; +import { Popover } from '@actual-app/components/popover'; +import { styles } from '@actual-app/components/styles'; +import { View } from '@actual-app/components/view'; + import { type CategoryGroupEntity } from 'loot-core/types/models'; import { useCategories } from '../../hooks/useCategories'; import { useNotes } from '../../hooks/useNotes'; import { SvgDotsHorizontalTriple, SvgAdd, SvgTrash } from '../../icons/v1'; import { SvgNotesPaper, SvgViewHide, SvgViewShow } from '../../icons/v2'; -import { styles, theme } from '../../style'; -import { Button } from '../common/Button2'; -import { Menu } from '../common/Menu'; +import { theme } from '../../style'; import { Modal, ModalCloseButton, ModalHeader, ModalTitle, } from '../common/Modal'; -import { Popover } from '../common/Popover'; -import { View } from '../common/View'; import { Notes } from '../Notes'; type CategoryGroupMenuModalProps = { diff --git a/packages/desktop-client/src/components/modals/CategoryMenuModal.tsx b/packages/desktop-client/src/components/modals/CategoryMenuModal.tsx index edec24657d4..2a238c8c5cd 100644 --- a/packages/desktop-client/src/components/modals/CategoryMenuModal.tsx +++ b/packages/desktop-client/src/components/modals/CategoryMenuModal.tsx @@ -2,6 +2,12 @@ import React, { useRef, useState, type CSSProperties } from 'react'; import { useTranslation } from 'react-i18next'; +import { Button } from '@actual-app/components/button'; +import { Menu } from '@actual-app/components/menu'; +import { Popover } from '@actual-app/components/popover'; +import { styles } from '@actual-app/components/styles'; +import { View } from '@actual-app/components/view'; + import { type CategoryEntity } from 'loot-core/types/models'; import { useCategory } from '../../hooks/useCategory'; @@ -9,17 +15,13 @@ import { useCategoryGroup } from '../../hooks/useCategoryGroup'; import { useNotes } from '../../hooks/useNotes'; import { SvgDotsHorizontalTriple, SvgTrash } from '../../icons/v1'; import { SvgNotesPaper, SvgViewHide, SvgViewShow } from '../../icons/v2'; -import { styles, theme } from '../../style'; -import { Button } from '../common/Button2'; -import { Menu } from '../common/Menu'; +import { theme } from '../../style'; import { Modal, ModalCloseButton, ModalHeader, ModalTitle, } from '../common/Modal'; -import { Popover } from '../common/Popover'; -import { View } from '../common/View'; import { Notes } from '../Notes'; type CategoryMenuModalProps = { @@ -114,7 +116,9 @@ export function CategoryMenuModal({ }} > 0 ? originalNotes : 'No notes'} + notes={ + originalNotes?.length > 0 ? originalNotes : t('No notes') + } editable={false} focused={false} getStyle={() => ({ @@ -197,14 +201,14 @@ function AdditionalCategoryMenu({ items={[ !categoryGroup?.hidden && { name: 'toggleVisibility', - text: category.hidden ? 'Show' : 'Hide', + text: category.hidden ? t('Show') : t('Hide'), icon: category.hidden ? SvgViewShow : SvgViewHide, iconSize: 16, }, !categoryGroup?.hidden && Menu.line, { name: 'delete', - text: 'Delete', + text: t('Delete'), icon: SvgTrash, iconSize: 15, }, diff --git a/packages/desktop-client/src/components/modals/CloseAccountModal.tsx b/packages/desktop-client/src/components/modals/CloseAccountModal.tsx index 1519db34919..cc698bb6361 100644 --- a/packages/desktop-client/src/components/modals/CloseAccountModal.tsx +++ b/packages/desktop-client/src/components/modals/CloseAccountModal.tsx @@ -3,6 +3,13 @@ import React, { type FormEvent, useState, type CSSProperties } from 'react'; import { Form } from 'react-aria-components'; import { useTranslation, Trans } from 'react-i18next'; +import { Button } from '@actual-app/components/button'; +import { FormError } from '@actual-app/components/form-error'; +import { Paragraph } from '@actual-app/components/paragraph'; +import { styles } from '@actual-app/components/styles'; +import { Text } from '@actual-app/components/text'; +import { View } from '@actual-app/components/view'; + import { pushModal } from 'loot-core/client/actions'; import { closeAccount } from 'loot-core/client/queries/queriesSlice'; import { integerToCurrency } from 'loot-core/shared/util'; @@ -12,16 +19,11 @@ import { type TransObjectLiteral } from 'loot-core/types/util'; import { useAccounts } from '../../hooks/useAccounts'; import { useCategories } from '../../hooks/useCategories'; import { useDispatch } from '../../redux'; -import { styles, theme } from '../../style'; +import { theme } from '../../style'; import { AccountAutocomplete } from '../autocomplete/AccountAutocomplete'; import { CategoryAutocomplete } from '../autocomplete/CategoryAutocomplete'; -import { Button } from '../common/Button2'; -import { FormError } from '../common/FormError'; import { Link } from '../common/Link'; import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal'; -import { Paragraph } from '../common/Paragraph'; -import { Text } from '../common/Text'; -import { View } from '../common/View'; import { useResponsive } from '../responsive/ResponsiveProvider'; function needsCategory( diff --git a/packages/desktop-client/src/components/modals/ConfirmTransactionDeleteModal.tsx b/packages/desktop-client/src/components/modals/ConfirmTransactionDeleteModal.tsx index 5b5315df73f..0acaea9cca8 100644 --- a/packages/desktop-client/src/components/modals/ConfirmTransactionDeleteModal.tsx +++ b/packages/desktop-client/src/components/modals/ConfirmTransactionDeleteModal.tsx @@ -1,12 +1,13 @@ import React from 'react'; import { useTranslation } from 'react-i18next'; -import { styles } from '../../style'; -import { Button } from '../common/Button2'; -import { InitialFocus } from '../common/InitialFocus'; +import { Button } from '@actual-app/components/button'; +import { InitialFocus } from '@actual-app/components/initial-focus'; +import { Paragraph } from '@actual-app/components/paragraph'; +import { styles } from '@actual-app/components/styles'; +import { View } from '@actual-app/components/view'; + import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal'; -import { Paragraph } from '../common/Paragraph'; -import { View } from '../common/View'; import { useResponsive } from '../responsive/ResponsiveProvider'; type ConfirmTransactionDeleteProps = { diff --git a/packages/desktop-client/src/components/modals/ConfirmUnlinkAccountModal.tsx b/packages/desktop-client/src/components/modals/ConfirmUnlinkAccountModal.tsx index d77fee203b0..a3ed1078ad1 100644 --- a/packages/desktop-client/src/components/modals/ConfirmUnlinkAccountModal.tsx +++ b/packages/desktop-client/src/components/modals/ConfirmUnlinkAccountModal.tsx @@ -1,11 +1,12 @@ import React from 'react'; import { useTranslation } from 'react-i18next'; -import { Button } from '../common/Button2'; -import { InitialFocus } from '../common/InitialFocus'; +import { Button } from '@actual-app/components/button'; +import { InitialFocus } from '@actual-app/components/initial-focus'; +import { Paragraph } from '@actual-app/components/paragraph'; +import { View } from '@actual-app/components/view'; + import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal'; -import { Paragraph } from '../common/Paragraph'; -import { View } from '../common/View'; type ConfirmUnlinkAccountProps = { accountName: string; diff --git a/packages/desktop-client/src/components/modals/CoverModal.tsx b/packages/desktop-client/src/components/modals/CoverModal.tsx index 31b0437ad99..0c0de7ddc40 100644 --- a/packages/desktop-client/src/components/modals/CoverModal.tsx +++ b/packages/desktop-client/src/components/modals/CoverModal.tsx @@ -1,19 +1,20 @@ import React, { useCallback, useMemo, useState } from 'react'; import { Trans, useTranslation } from 'react-i18next'; +import { Button } from '@actual-app/components/button'; +import { styles } from '@actual-app/components/styles'; +import { View } from '@actual-app/components/view'; + import { pushModal } from 'loot-core/client/actions'; import { type CategoryEntity } from 'loot-core/types/models'; import { useCategories } from '../../hooks/useCategories'; import { useDispatch } from '../../redux'; -import { styles } from '../../style'; import { addToBeBudgetedGroup, removeCategoriesFromGroups, } from '../budget/util'; -import { Button } from '../common/Button2'; import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal'; -import { View } from '../common/View'; import { FieldLabel, TapField } from '../mobile/MobileForms'; type CoverModalProps = { diff --git a/packages/desktop-client/src/components/modals/CreateAccountModal.tsx b/packages/desktop-client/src/components/modals/CreateAccountModal.tsx index 3c5c1a3fe5b..a753458409c 100644 --- a/packages/desktop-client/src/components/modals/CreateAccountModal.tsx +++ b/packages/desktop-client/src/components/modals/CreateAccountModal.tsx @@ -2,6 +2,14 @@ import React, { useEffect, useState } from 'react'; import { DialogTrigger } from 'react-aria-components'; import { Trans, useTranslation } from 'react-i18next'; +import { Button, ButtonWithLoading } from '@actual-app/components/button'; +import { InitialFocus } from '@actual-app/components/initial-focus'; +import { Menu } from '@actual-app/components/menu'; +import { Paragraph } from '@actual-app/components/paragraph'; +import { Popover } from '@actual-app/components/popover'; +import { Text } from '@actual-app/components/text'; +import { View } from '@actual-app/components/view'; + import { pushModal } from 'loot-core/client/actions'; import { send } from 'loot-core/platform/client/fetch'; @@ -15,15 +23,8 @@ import { SvgDotsHorizontalTriple } from '../../icons/v1'; import { useDispatch } from '../../redux'; import { theme } from '../../style'; import { Warning } from '../alerts'; -import { Button, ButtonWithLoading } from '../common/Button2'; -import { InitialFocus } from '../common/InitialFocus'; import { Link } from '../common/Link'; -import { Menu } from '../common/Menu'; import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal'; -import { Paragraph } from '../common/Paragraph'; -import { Popover } from '../common/Popover'; -import { Text } from '../common/Text'; -import { View } from '../common/View'; import { useMultiuserEnabled } from '../ServerContext'; type CreateAccountProps = { @@ -53,9 +54,7 @@ export function CreateAccountModal({ upgradingAccountId }: CreateAccountProps) { if (upgradingAccountId == null) { authorizeBank(dispatch); } else { - authorizeBank(dispatch, { - upgradingAccountId, - }); + authorizeBank(dispatch); } }; diff --git a/packages/desktop-client/src/components/modals/CreateEncryptionKeyModal.tsx b/packages/desktop-client/src/components/modals/CreateEncryptionKeyModal.tsx index cb7353de519..a2733b37a6d 100644 --- a/packages/desktop-client/src/components/modals/CreateEncryptionKeyModal.tsx +++ b/packages/desktop-client/src/components/modals/CreateEncryptionKeyModal.tsx @@ -3,6 +3,12 @@ import React, { useState } from 'react'; import { Form } from 'react-aria-components'; import { useTranslation, Trans } from 'react-i18next'; +import { ButtonWithLoading } from '@actual-app/components/button'; +import { InitialFocus } from '@actual-app/components/initial-focus'; +import { Paragraph } from '@actual-app/components/paragraph'; +import { styles } from '@actual-app/components/styles'; +import { Text } from '@actual-app/components/text'; +import { View } from '@actual-app/components/view'; import { css } from '@emotion/css'; import { loadAllFiles, loadGlobalPrefs } from 'loot-core/client/actions'; @@ -11,9 +17,7 @@ import { send } from 'loot-core/platform/client/fetch'; import { getCreateKeyError } from 'loot-core/shared/errors'; import { useDispatch } from '../../redux'; -import { styles, theme } from '../../style'; -import { ButtonWithLoading } from '../common/Button2'; -import { InitialFocus } from '../common/InitialFocus'; +import { theme } from '../../style'; import { Input } from '../common/Input'; import { Link } from '../common/Link'; import { @@ -22,9 +26,6 @@ import { ModalCloseButton, ModalHeader, } from '../common/Modal'; -import { Paragraph } from '../common/Paragraph'; -import { Text } from '../common/Text'; -import { View } from '../common/View'; import { useResponsive } from '../responsive/ResponsiveProvider'; type CreateEncryptionKeyModalProps = { diff --git a/packages/desktop-client/src/components/modals/CreateLocalAccountModal.tsx b/packages/desktop-client/src/components/modals/CreateLocalAccountModal.tsx index 29a43ce6410..7cf6ee47112 100644 --- a/packages/desktop-client/src/components/modals/CreateLocalAccountModal.tsx +++ b/packages/desktop-client/src/components/modals/CreateLocalAccountModal.tsx @@ -3,6 +3,13 @@ import { type FormEvent, useState } from 'react'; import { Form } from 'react-aria-components'; import { useTranslation, Trans } from 'react-i18next'; +import { Button } from '@actual-app/components/button'; +import { FormError } from '@actual-app/components/form-error'; +import { InitialFocus } from '@actual-app/components/initial-focus'; +import { InlineField } from '@actual-app/components/inline-field'; +import { Text } from '@actual-app/components/text'; +import { View } from '@actual-app/components/view'; + import { closeModal } from 'loot-core/client/actions'; import { createAccount } from 'loot-core/client/queries/queriesSlice'; import { toRelaxedNumber } from 'loot-core/shared/util'; @@ -11,10 +18,6 @@ import * as useAccounts from '../../hooks/useAccounts'; import { useNavigate } from '../../hooks/useNavigate'; import { useDispatch } from '../../redux'; import { theme } from '../../style'; -import { Button } from '../common/Button2'; -import { FormError } from '../common/FormError'; -import { InitialFocus } from '../common/InitialFocus'; -import { InlineField } from '../common/InlineField'; import { Input } from '../common/Input'; import { Link } from '../common/Link'; import { @@ -24,8 +27,6 @@ import { ModalHeader, ModalTitle, } from '../common/Modal'; -import { Text } from '../common/Text'; -import { View } from '../common/View'; import { Checkbox } from '../forms'; import { validateAccountName } from '../util/accountValidation'; @@ -85,7 +86,7 @@ export function CreateLocalAccountModal() { />
- + - + ([]); useEffect(() => { - send('access-get-available-users', defaultUserAccess.fileId).then( - (data: Awaited>) => { - if ('error' in data) { - setSetError(data.error); - } else { - setAvailableUsers( - data.map(user => [ - user.userId, - user.displayName - ? `${user.displayName} (${user.userName})` - : user.userName, - ]), - ); - } - }, - ); + send('access-get-available-users', defaultUserAccess.fileId).then(data => { + if ('error' in data) { + setSetError(data.error); + } else { + setAvailableUsers( + data.map(user => [ + user.userId, + user.displayName + ? `${user.displayName} (${user.userName})` + : user.userName, + ]), + ); + } + }); }, [defaultUserAccess.fileId]); async function onSave(close: () => void) { diff --git a/packages/desktop-client/src/components/modals/EditFieldModal.tsx b/packages/desktop-client/src/components/modals/EditFieldModal.tsx index 92503b70e89..526deca4309 100644 --- a/packages/desktop-client/src/components/modals/EditFieldModal.tsx +++ b/packages/desktop-client/src/components/modals/EditFieldModal.tsx @@ -6,6 +6,8 @@ import React, { } from 'react'; import { useTranslation } from 'react-i18next'; +import { Button } from '@actual-app/components/button'; +import { View } from '@actual-app/components/view'; import { parseISO, format as formatDate, parse as parseDate } from 'date-fns'; import { currentDay, dayFromDate } from 'loot-core/shared/months'; @@ -13,10 +15,8 @@ import { amountToInteger } from 'loot-core/shared/util'; import { useDateFormat } from '../../hooks/useDateFormat'; import { theme } from '../../style'; -import { Button } from '../common/Button2'; import { Input } from '../common/Input'; import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal'; -import { View } from '../common/View'; import { SectionLabel } from '../forms'; import { useResponsive } from '../responsive/ResponsiveProvider'; import { DateSelect } from '../select/DateSelect'; @@ -244,7 +244,9 @@ export function EditFieldModal({ onClose={onClose} containerProps={{ style: { - height: isNarrowWidth ? '85vh' : 275, + height: isNarrowWidth + ? 'calc(var(--visual-viewport-height) * 0.85)' + : 275, padding: '15px 10px', ...(minWidth && { minWidth }), backgroundColor: theme.menuAutoCompleteBackground, diff --git a/packages/desktop-client/src/components/modals/EditRuleModal.jsx b/packages/desktop-client/src/components/modals/EditRuleModal.jsx index 8dfb9b59473..1296aeb7bbb 100644 --- a/packages/desktop-client/src/components/modals/EditRuleModal.jsx +++ b/packages/desktop-client/src/components/modals/EditRuleModal.jsx @@ -1,6 +1,13 @@ import React, { useState, useEffect, useRef, useMemo } from 'react'; import { useTranslation, Trans } from 'react-i18next'; +import { Button } from '@actual-app/components/button'; +import { Menu } from '@actual-app/components/menu'; +import { Stack } from '@actual-app/components/stack'; +import { styles } from '@actual-app/components/styles'; +import { Text } from '@actual-app/components/text'; +import { Tooltip } from '@actual-app/components/tooltip'; +import { View } from '@actual-app/components/view'; import { css } from '@emotion/css'; import { v4 as uuid } from 'uuid'; @@ -35,15 +42,9 @@ import { useSelected, SelectedProvider } from '../../hooks/useSelected'; import { SvgDelete, SvgAdd, SvgSubtract } from '../../icons/v0'; import { SvgAlignLeft, SvgCode, SvgInformationOutline } from '../../icons/v1'; import { useDispatch } from '../../redux'; -import { styles, theme } from '../../style'; -import { Button } from '../common/Button2'; -import { Menu } from '../common/Menu'; +import { theme } from '../../style'; import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal'; import { Select } from '../common/Select'; -import { Stack } from '../common/Stack'; -import { Text } from '../common/Text'; -import { Tooltip } from '../common/Tooltip'; -import { View } from '../common/View'; import { StatusBadge } from '../schedules/StatusBadge'; import { SimpleTransactionsTable } from '../transactions/SimpleTransactionsTable'; import { BetweenAmountInput } from '../util/AmountInput'; @@ -201,14 +202,7 @@ function FieldError({ type }) { function Editor({ error, style, children }) { return ( - + {children} {error && } @@ -1008,12 +1002,6 @@ export function EditRuleModal({ defaultRule, onSave: originalOnSave }) { } } - const editorStyle = { - color: theme.pillText, - backgroundColor: theme.pillBackground, - borderRadius: 4, - }; - // Enable editing existing split rules even if the feature has since been disabled. const showSplitButton = actionSplits.length > 0; @@ -1104,7 +1092,7 @@ export function EditRuleModal({ defaultRule, onSave: originalOnSave }) { setConditions(conds)} /> @@ -1185,7 +1173,7 @@ export function EditRuleModal({ defaultRule, onSave: originalOnSave }) { 'append-notes', ]} action={action} - editorStyle={editorStyle} + editorStyle={styles.editorPill} onChange={(name, value) => { onChangeAction(action, name, value); }} diff --git a/packages/desktop-client/src/components/modals/EditUser.tsx b/packages/desktop-client/src/components/modals/EditUser.tsx index 0543fe98b40..081e14b8af2 100644 --- a/packages/desktop-client/src/components/modals/EditUser.tsx +++ b/packages/desktop-client/src/components/modals/EditUser.tsx @@ -1,19 +1,21 @@ import { useState } from 'react'; import { Trans, useTranslation } from 'react-i18next'; +import { Button } from '@actual-app/components/button'; +import { Stack } from '@actual-app/components/stack'; +import { styles } from '@actual-app/components/styles'; +import { Text } from '@actual-app/components/text'; +import { View } from '@actual-app/components/view'; + import { addNotification, popModal, signOut } from 'loot-core/client/actions'; import { send } from 'loot-core/platform/client/fetch'; import { PossibleRoles, type UserEntity } from 'loot-core/types/models/user'; import { useDispatch } from '../../redux'; -import { styles, theme } from '../../style'; -import { Button } from '../common/Button2'; +import { theme } from '../../style'; import { Input } from '../common/Input'; import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal'; import { Select } from '../common/Select'; -import { Stack } from '../common/Stack'; -import { Text } from '../common/Text'; -import { View } from '../common/View'; import { Checkbox, FormField, FormLabel } from '../forms'; type User = UserEntity; diff --git a/packages/desktop-client/src/components/modals/EnvelopeBalanceMenuModal.tsx b/packages/desktop-client/src/components/modals/EnvelopeBalanceMenuModal.tsx index 5dc42192978..6625973d8a0 100644 --- a/packages/desktop-client/src/components/modals/EnvelopeBalanceMenuModal.tsx +++ b/packages/desktop-client/src/components/modals/EnvelopeBalanceMenuModal.tsx @@ -4,10 +4,14 @@ import React, { } from 'react'; import { useTranslation } from 'react-i18next'; +import { styles } from '@actual-app/components/styles'; +import { Text } from '@actual-app/components/text'; +import { View } from '@actual-app/components/view'; + import { envelopeBudget } from 'loot-core/client/queries'; import { useCategory } from '../../hooks/useCategory'; -import { theme, styles } from '../../style'; +import { theme } from '../../style'; import { BalanceWithCarryover, CarryoverIndicator, @@ -19,8 +23,6 @@ import { ModalHeader, ModalTitle, } from '../common/Modal'; -import { Text } from '../common/Text'; -import { View } from '../common/View'; import { CellValueText } from '../spreadsheet/CellValue'; type EnvelopeBalanceMenuModalProps = ComponentPropsWithoutRef< diff --git a/packages/desktop-client/src/components/modals/EnvelopeBudgetMenuModal.tsx b/packages/desktop-client/src/components/modals/EnvelopeBudgetMenuModal.tsx index a7f1d9eb5a3..4d32cce5f0c 100644 --- a/packages/desktop-client/src/components/modals/EnvelopeBudgetMenuModal.tsx +++ b/packages/desktop-client/src/components/modals/EnvelopeBudgetMenuModal.tsx @@ -6,11 +6,15 @@ import React, { } from 'react'; import { useTranslation } from 'react-i18next'; +import { styles } from '@actual-app/components/styles'; +import { Text } from '@actual-app/components/text'; +import { View } from '@actual-app/components/view'; + import { envelopeBudget } from 'loot-core/client/queries'; import { amountToInteger, integerToAmount } from 'loot-core/shared/util'; import { useCategory } from '../../hooks/useCategory'; -import { theme, styles } from '../../style'; +import { theme } from '../../style'; import { BudgetMenu } from '../budget/envelope/BudgetMenu'; import { useEnvelopeSheetValue } from '../budget/envelope/EnvelopeBudgetComponents'; import { @@ -19,8 +23,6 @@ import { ModalHeader, ModalTitle, } from '../common/Modal'; -import { Text } from '../common/Text'; -import { View } from '../common/View'; import { FocusableAmountInput } from '../mobile/transactions/FocusableAmountInput'; type EnvelopeBudgetMenuModalProps = ComponentPropsWithoutRef< diff --git a/packages/desktop-client/src/components/modals/EnvelopeBudgetMonthMenuModal.tsx b/packages/desktop-client/src/components/modals/EnvelopeBudgetMonthMenuModal.tsx index 543b13784fa..932b58ee216 100644 --- a/packages/desktop-client/src/components/modals/EnvelopeBudgetMonthMenuModal.tsx +++ b/packages/desktop-client/src/components/modals/EnvelopeBudgetMonthMenuModal.tsx @@ -2,6 +2,9 @@ import React, { useState } from 'react'; import { useTranslation } from 'react-i18next'; +import { Button } from '@actual-app/components/button'; +import { styles } from '@actual-app/components/styles'; +import { View } from '@actual-app/components/view'; import { css } from '@emotion/css'; import * as monthUtils from 'loot-core/shared/months'; @@ -10,11 +13,9 @@ import { useNotes } from '../../hooks/useNotes'; import { useUndo } from '../../hooks/useUndo'; import { SvgCheveronDown, SvgCheveronUp } from '../../icons/v1'; import { SvgNotesPaper } from '../../icons/v2'; -import { styles, theme, type CSSProperties } from '../../style'; +import { theme, type CSSProperties } from '../../style'; import { BudgetMonthMenu } from '../budget/envelope/budgetsummary/BudgetMonthMenu'; -import { Button } from '../common/Button2'; import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal'; -import { View } from '../common/View'; import { Notes } from '../Notes'; type EnvelopeBudgetMonthMenuModalProps = { diff --git a/packages/desktop-client/src/components/modals/EnvelopeBudgetSummaryModal.tsx b/packages/desktop-client/src/components/modals/EnvelopeBudgetSummaryModal.tsx index 1acd608fe8c..f860b0950c8 100644 --- a/packages/desktop-client/src/components/modals/EnvelopeBudgetSummaryModal.tsx +++ b/packages/desktop-client/src/components/modals/EnvelopeBudgetSummaryModal.tsx @@ -1,6 +1,8 @@ import React from 'react'; import { useTranslation } from 'react-i18next'; +import { styles } from '@actual-app/components/styles'; + import { collapseModals, pushModal } from 'loot-core/client/actions'; import { envelopeBudget } from 'loot-core/client/queries'; import { format, sheetForMonth, prevMonth } from 'loot-core/shared/months'; @@ -9,7 +11,6 @@ import { groupById, integerToCurrency } from 'loot-core/shared/util'; import { useCategories } from '../../hooks/useCategories'; import { useUndo } from '../../hooks/useUndo'; import { useDispatch } from '../../redux'; -import { styles } from '../../style'; import { ToBudgetAmount } from '../budget/envelope/budgetsummary/ToBudgetAmount'; import { TotalsList } from '../budget/envelope/budgetsummary/TotalsList'; import { useEnvelopeSheetValue } from '../budget/envelope/EnvelopeBudgetComponents'; diff --git a/packages/desktop-client/src/components/modals/EnvelopeToBudgetMenuModal.tsx b/packages/desktop-client/src/components/modals/EnvelopeToBudgetMenuModal.tsx index 3466db52d69..43371ccaff6 100644 --- a/packages/desktop-client/src/components/modals/EnvelopeToBudgetMenuModal.tsx +++ b/packages/desktop-client/src/components/modals/EnvelopeToBudgetMenuModal.tsx @@ -3,7 +3,9 @@ import React, { type CSSProperties, } from 'react'; -import { theme, styles } from '../../style'; +import { styles } from '@actual-app/components/styles'; + +import { theme } from '../../style'; import { ToBudgetMenu } from '../budget/envelope/budgetsummary/ToBudgetMenu'; import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal'; diff --git a/packages/desktop-client/src/components/modals/FixEncryptionKeyModal.tsx b/packages/desktop-client/src/components/modals/FixEncryptionKeyModal.tsx index 91c3cd01094..904e00ba2b2 100644 --- a/packages/desktop-client/src/components/modals/FixEncryptionKeyModal.tsx +++ b/packages/desktop-client/src/components/modals/FixEncryptionKeyModal.tsx @@ -3,13 +3,18 @@ import React, { useState } from 'react'; import { Form } from 'react-aria-components'; import { useTranslation } from 'react-i18next'; +import { Button, ButtonWithLoading } from '@actual-app/components/button'; +import { InitialFocus } from '@actual-app/components/initial-focus'; +import { Paragraph } from '@actual-app/components/paragraph'; +import { styles } from '@actual-app/components/styles'; +import { Text } from '@actual-app/components/text'; +import { View } from '@actual-app/components/view'; + import { type FinanceModals } from 'loot-core/client/state-types/modals'; import { send } from 'loot-core/platform/client/fetch'; import { getTestKeyError } from 'loot-core/shared/errors'; -import { styles, theme } from '../../style'; -import { Button, ButtonWithLoading } from '../common/Button2'; -import { InitialFocus } from '../common/InitialFocus'; +import { theme } from '../../style'; import { Input } from '../common/Input'; import { Link } from '../common/Link'; import { @@ -18,9 +23,6 @@ import { ModalCloseButton, ModalHeader, } from '../common/Modal'; -import { Paragraph } from '../common/Paragraph'; -import { Text } from '../common/Text'; -import { View } from '../common/View'; import { useResponsive } from '../responsive/ResponsiveProvider'; type FixEncryptionKeyModalProps = { diff --git a/packages/desktop-client/src/components/modals/GoCardlessExternalMsgModal.tsx b/packages/desktop-client/src/components/modals/GoCardlessExternalMsgModal.tsx index c89f1a424f4..c477d1178c1 100644 --- a/packages/desktop-client/src/components/modals/GoCardlessExternalMsgModal.tsx +++ b/packages/desktop-client/src/components/modals/GoCardlessExternalMsgModal.tsx @@ -2,6 +2,10 @@ import React, { useEffect, useState, useRef } from 'react'; import { Trans, useTranslation } from 'react-i18next'; +import { Button } from '@actual-app/components/button'; +import { Paragraph } from '@actual-app/components/paragraph'; +import { View } from '@actual-app/components/view'; + import { pushModal } from 'loot-core/client/actions/modals'; import { sendCatch } from 'loot-core/platform/client/fetch'; import { @@ -15,11 +19,8 @@ import { useDispatch } from '../../redux'; import { theme } from '../../style'; import { Error, Warning } from '../alerts'; import { Autocomplete } from '../autocomplete/Autocomplete'; -import { Button } from '../common/Button2'; import { Link } from '../common/Link'; import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal'; -import { Paragraph } from '../common/Paragraph'; -import { View } from '../common/View'; import { FormField, FormLabel } from '../forms'; import { COUNTRY_OPTIONS } from '../util/countries'; diff --git a/packages/desktop-client/src/components/modals/GoCardlessInitialiseModal.tsx b/packages/desktop-client/src/components/modals/GoCardlessInitialiseModal.tsx index 39cac360de4..0bdd4761fd4 100644 --- a/packages/desktop-client/src/components/modals/GoCardlessInitialiseModal.tsx +++ b/packages/desktop-client/src/components/modals/GoCardlessInitialiseModal.tsx @@ -2,12 +2,15 @@ import React, { useState } from 'react'; import { useTranslation, Trans } from 'react-i18next'; +import { ButtonWithLoading } from '@actual-app/components/button'; +import { InitialFocus } from '@actual-app/components/initial-focus'; +import { Text } from '@actual-app/components/text'; +import { View } from '@actual-app/components/view'; + import { send } from 'loot-core/platform/client/fetch'; import { getSecretsError } from 'loot-core/shared/errors'; import { Error } from '../alerts'; -import { ButtonWithLoading } from '../common/Button2'; -import { InitialFocus } from '../common/InitialFocus'; import { Input } from '../common/Input'; import { Link } from '../common/Link'; import { @@ -16,8 +19,6 @@ import { ModalCloseButton, ModalHeader, } from '../common/Modal'; -import { Text } from '../common/Text'; -import { View } from '../common/View'; import { FormField, FormLabel } from '../forms'; type GoCardlessInitialiseProps = { diff --git a/packages/desktop-client/src/components/modals/GoalTemplateModal.tsx b/packages/desktop-client/src/components/modals/GoalTemplateModal.tsx index 12b06548ad0..6b50547378d 100644 --- a/packages/desktop-client/src/components/modals/GoalTemplateModal.tsx +++ b/packages/desktop-client/src/components/modals/GoalTemplateModal.tsx @@ -1,10 +1,11 @@ import { Trans, useTranslation } from 'react-i18next'; +import { Text } from '@actual-app/components/text'; +import { View } from '@actual-app/components/view'; + import { theme } from '../../style'; import { Link } from '../common/Link'; import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal'; -import { Text } from '../common/Text'; -import { View } from '../common/View'; import { TableHeader, Row, Field } from '../table'; export function GoalTemplateModal() { diff --git a/packages/desktop-client/src/components/modals/HoldBufferModal.tsx b/packages/desktop-client/src/components/modals/HoldBufferModal.tsx index 199411c00c5..c94b3a52c6e 100644 --- a/packages/desktop-client/src/components/modals/HoldBufferModal.tsx +++ b/packages/desktop-client/src/components/modals/HoldBufferModal.tsx @@ -1,14 +1,15 @@ import React, { useState } from 'react'; import { useTranslation } from 'react-i18next'; +import { Button } from '@actual-app/components/button'; +import { InitialFocus } from '@actual-app/components/initial-focus'; +import { styles } from '@actual-app/components/styles'; +import { View } from '@actual-app/components/view'; + import { envelopeBudget } from 'loot-core/client/queries'; -import { styles } from '../../style'; import { useEnvelopeSheetValue } from '../budget/envelope/EnvelopeBudgetComponents'; -import { Button } from '../common/Button2'; -import { InitialFocus } from '../common/InitialFocus'; import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal'; -import { View } from '../common/View'; import { FieldLabel } from '../mobile/MobileForms'; import { AmountInput } from '../util/AmountInput'; diff --git a/packages/desktop-client/src/components/modals/ImportTransactionsModal/CheckboxOption.tsx b/packages/desktop-client/src/components/modals/ImportTransactionsModal/CheckboxOption.tsx index 5af399a4396..b456f71d637 100644 --- a/packages/desktop-client/src/components/modals/ImportTransactionsModal/CheckboxOption.tsx +++ b/packages/desktop-client/src/components/modals/ImportTransactionsModal/CheckboxOption.tsx @@ -4,8 +4,9 @@ import React, { type ReactNode, } from 'react'; +import { View } from '@actual-app/components/view'; + import { theme } from '../../../style'; -import { View } from '../../common/View'; import { Checkbox } from '../../forms'; type CheckboxOptionProps = { diff --git a/packages/desktop-client/src/components/modals/ImportTransactionsModal/DateFormatSelect.tsx b/packages/desktop-client/src/components/modals/ImportTransactionsModal/DateFormatSelect.tsx index d743c71dedc..5e543568c3d 100644 --- a/packages/desktop-client/src/components/modals/ImportTransactionsModal/DateFormatSelect.tsx +++ b/packages/desktop-client/src/components/modals/ImportTransactionsModal/DateFormatSelect.tsx @@ -1,7 +1,8 @@ import React from 'react'; +import { View } from '@actual-app/components/view'; + import { Select } from '../../common/Select'; -import { View } from '../../common/View'; import { SectionLabel } from '../../forms'; import { diff --git a/packages/desktop-client/src/components/modals/ImportTransactionsModal/FieldMappings.tsx b/packages/desktop-client/src/components/modals/ImportTransactionsModal/FieldMappings.tsx index e4a0ab4e80a..dc92a26bfd5 100644 --- a/packages/desktop-client/src/components/modals/ImportTransactionsModal/FieldMappings.tsx +++ b/packages/desktop-client/src/components/modals/ImportTransactionsModal/FieldMappings.tsx @@ -1,7 +1,8 @@ import React from 'react'; -import { Stack } from '../../common/Stack'; -import { View } from '../../common/View'; +import { Stack } from '@actual-app/components/stack'; +import { View } from '@actual-app/components/view'; + import { SectionLabel } from '../../forms'; import { SelectField } from './SelectField'; diff --git a/packages/desktop-client/src/components/modals/ImportTransactionsModal/ImportTransactionsModal.jsx b/packages/desktop-client/src/components/modals/ImportTransactionsModal/ImportTransactionsModal.jsx index aaf616cb7d0..f44026d0bf4 100644 --- a/packages/desktop-client/src/components/modals/ImportTransactionsModal/ImportTransactionsModal.jsx +++ b/packages/desktop-client/src/components/modals/ImportTransactionsModal/ImportTransactionsModal.jsx @@ -1,6 +1,10 @@ import React, { useState, useEffect, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; +import { Button, ButtonWithLoading } from '@actual-app/components/button'; +import { Stack } from '@actual-app/components/stack'; +import { Text } from '@actual-app/components/text'; +import { View } from '@actual-app/components/view'; import deepEqual from 'deep-equal'; import { @@ -16,13 +20,9 @@ import { useDateFormat } from '../../../hooks/useDateFormat'; import { useSyncedPrefs } from '../../../hooks/useSyncedPrefs'; import { useDispatch } from '../../../redux'; import { theme } from '../../../style'; -import { Button, ButtonWithLoading } from '../../common/Button2'; import { Input } from '../../common/Input'; import { Modal, ModalCloseButton, ModalHeader } from '../../common/Modal'; import { Select } from '../../common/Select'; -import { Stack } from '../../common/Stack'; -import { Text } from '../../common/Text'; -import { View } from '../../common/View'; import { SectionLabel } from '../../forms'; import { TableHeader, TableWithNavigator } from '../../table'; @@ -233,7 +233,7 @@ export function ImportTransactionsModal({ options }) { const { amount } = parseAmountFields( trans, splitMode, - inOutMode, + isOfxFile(filetype) ? false : inOutMode, outValue, flipAmount, multiplierAmount, @@ -575,7 +575,7 @@ export function ImportTransactionsModal({ options }) { const { amount } = parseAmountFields( trans, splitMode, - inOutMode, + isOfxFile(filetype) ? false : inOutMode, outValue, flipAmount, multiplierAmount, diff --git a/packages/desktop-client/src/components/modals/ImportTransactionsModal/InOutOption.tsx b/packages/desktop-client/src/components/modals/ImportTransactionsModal/InOutOption.tsx index 80b565789ad..07f52985366 100644 --- a/packages/desktop-client/src/components/modals/ImportTransactionsModal/InOutOption.tsx +++ b/packages/desktop-client/src/components/modals/ImportTransactionsModal/InOutOption.tsx @@ -1,7 +1,8 @@ import React from 'react'; +import { View } from '@actual-app/components/view'; + import { Input } from '../../common/Input'; -import { View } from '../../common/View'; import { CheckboxOption } from './CheckboxOption'; diff --git a/packages/desktop-client/src/components/modals/ImportTransactionsModal/MultiplierOption.tsx b/packages/desktop-client/src/components/modals/ImportTransactionsModal/MultiplierOption.tsx index 4eb4fd23dca..4c48384f0bf 100644 --- a/packages/desktop-client/src/components/modals/ImportTransactionsModal/MultiplierOption.tsx +++ b/packages/desktop-client/src/components/modals/ImportTransactionsModal/MultiplierOption.tsx @@ -1,7 +1,8 @@ import React, { type ComponentProps } from 'react'; +import { View } from '@actual-app/components/view'; + import { Input } from '../../common/Input'; -import { View } from '../../common/View'; import { CheckboxOption } from './CheckboxOption'; diff --git a/packages/desktop-client/src/components/modals/ImportTransactionsModal/ParsedDate.tsx b/packages/desktop-client/src/components/modals/ImportTransactionsModal/ParsedDate.tsx index c32c9e09c69..7a6beded1b4 100644 --- a/packages/desktop-client/src/components/modals/ImportTransactionsModal/ParsedDate.tsx +++ b/packages/desktop-client/src/components/modals/ImportTransactionsModal/ParsedDate.tsx @@ -1,8 +1,9 @@ import React from 'react'; import { Trans } from 'react-i18next'; +import { Text } from '@actual-app/components/text'; + import { theme } from '../../../style'; -import { Text } from '../../common/Text'; import { formatDate, parseDate } from './utils'; diff --git a/packages/desktop-client/src/components/modals/ImportTransactionsModal/SubLabel.tsx b/packages/desktop-client/src/components/modals/ImportTransactionsModal/SubLabel.tsx index 7994c94fcf7..58a3490279c 100644 --- a/packages/desktop-client/src/components/modals/ImportTransactionsModal/SubLabel.tsx +++ b/packages/desktop-client/src/components/modals/ImportTransactionsModal/SubLabel.tsx @@ -1,7 +1,8 @@ import React from 'react'; +import { Text } from '@actual-app/components/text'; + import { theme } from '../../../style'; -import { Text } from '../../common/Text'; type SubLabelProps = { title: string; diff --git a/packages/desktop-client/src/components/modals/ImportTransactionsModal/Transaction.tsx b/packages/desktop-client/src/components/modals/ImportTransactionsModal/Transaction.tsx index 738ab9494ab..257c4833d8a 100644 --- a/packages/desktop-client/src/components/modals/ImportTransactionsModal/Transaction.tsx +++ b/packages/desktop-client/src/components/modals/ImportTransactionsModal/Transaction.tsx @@ -1,13 +1,15 @@ import React, { type ComponentProps, useMemo } from 'react'; +import { Stack } from '@actual-app/components/stack'; +import { styles } from '@actual-app/components/styles'; +import { Tooltip } from '@actual-app/components/tooltip'; +import { View } from '@actual-app/components/view'; + import { amountToCurrency } from 'loot-core/shared/util'; import { type CategoryEntity } from 'loot-core/types/models'; import { SvgDownAndRightArrow } from '../../../icons/v2'; -import { theme, styles } from '../../../style'; -import { Stack } from '../../common/Stack'; -import { Tooltip } from '../../common/Tooltip'; -import { View } from '../../common/View'; +import { theme } from '../../../style'; import { Checkbox } from '../../forms'; import { Row, Field } from '../../table'; diff --git a/packages/desktop-client/src/components/modals/KeyboardShortcutModal.tsx b/packages/desktop-client/src/components/modals/KeyboardShortcutModal.tsx index fb75b1a11cd..cbe97472759 100644 --- a/packages/desktop-client/src/components/modals/KeyboardShortcutModal.tsx +++ b/packages/desktop-client/src/components/modals/KeyboardShortcutModal.tsx @@ -2,11 +2,12 @@ import { type CSSProperties } from 'react'; import { useTranslation } from 'react-i18next'; import { useLocation } from 'react-router-dom'; +import { Text } from '@actual-app/components/text'; +import { View } from '@actual-app/components/view'; + import * as Platform from 'loot-core/client/platform'; import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal'; -import { Text } from '../common/Text'; -import { View } from '../common/View'; type KeyIconProps = { shortcut: string; diff --git a/packages/desktop-client/src/components/modals/MergeUnusedPayeesModal.tsx b/packages/desktop-client/src/components/modals/MergeUnusedPayeesModal.tsx index 9dbf9df1036..33b79a8f88a 100644 --- a/packages/desktop-client/src/components/modals/MergeUnusedPayeesModal.tsx +++ b/packages/desktop-client/src/components/modals/MergeUnusedPayeesModal.tsx @@ -1,6 +1,11 @@ import React, { useState, useRef, useEffect, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; +import { Button } from '@actual-app/components/button'; +import { Paragraph } from '@actual-app/components/paragraph'; +import { Text } from '@actual-app/components/text'; +import { View } from '@actual-app/components/view'; + import { replaceModal } from 'loot-core/client/actions/modals'; import { send } from 'loot-core/platform/client/fetch'; import { type PayeeEntity } from 'loot-core/types/models'; @@ -9,11 +14,7 @@ import { usePayees } from '../../hooks/usePayees'; import { useSelector, useDispatch } from '../../redux'; import { theme } from '../../style'; import { Information } from '../alerts'; -import { Button } from '../common/Button2'; import { Modal, ModalButtons } from '../common/Modal'; -import { Paragraph } from '../common/Paragraph'; -import { Text } from '../common/Text'; -import { View } from '../common/View'; const highlightStyle = { color: theme.pageTextPositive }; diff --git a/packages/desktop-client/src/components/modals/NotesModal.tsx b/packages/desktop-client/src/components/modals/NotesModal.tsx index 716828ccdb2..de105976b62 100644 --- a/packages/desktop-client/src/components/modals/NotesModal.tsx +++ b/packages/desktop-client/src/components/modals/NotesModal.tsx @@ -2,11 +2,12 @@ import React, { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; +import { Button } from '@actual-app/components/button'; +import { View } from '@actual-app/components/view'; + import { useNotes } from '../../hooks/useNotes'; import { SvgCheck } from '../../icons/v2'; -import { Button } from '../common/Button2'; import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal'; -import { View } from '../common/View'; import { Notes } from '../Notes'; type NotesModalProps = { diff --git a/packages/desktop-client/src/components/modals/OpenIDEnableModal.tsx b/packages/desktop-client/src/components/modals/OpenIDEnableModal.tsx index 18dbd7b6caf..de4729edd9a 100644 --- a/packages/desktop-client/src/components/modals/OpenIDEnableModal.tsx +++ b/packages/desktop-client/src/components/modals/OpenIDEnableModal.tsx @@ -1,6 +1,11 @@ import { useState } from 'react'; import { Trans, useTranslation } from 'react-i18next'; +import { Button } from '@actual-app/components/button'; +import { Label } from '@actual-app/components/label'; +import { styles } from '@actual-app/components/styles'; +import { View } from '@actual-app/components/view'; + import { closeBudget, popModal } from 'loot-core/client/actions'; import { send } from 'loot-core/platform/client/fetch'; import * as asyncStorage from 'loot-core/platform/server/asyncStorage'; @@ -8,12 +13,9 @@ import { getOpenIdErrors } from 'loot-core/shared/errors'; import { type OpenIdConfig } from 'loot-core/types/models/openid'; import { useDispatch } from '../../redux'; -import { theme, styles } from '../../style'; +import { theme } from '../../style'; import { Error } from '../alerts'; -import { Button } from '../common/Button2'; -import { Label } from '../common/Label'; import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal'; -import { View } from '../common/View'; import { OpenIdForm } from '../manager/subscribe/OpenIdForm'; import { useRefreshLoginMethods } from '../ServerContext'; diff --git a/packages/desktop-client/src/components/modals/OutOfSyncMigrationsModal.tsx b/packages/desktop-client/src/components/modals/OutOfSyncMigrationsModal.tsx index 2a904f26345..a8aa2c09895 100644 --- a/packages/desktop-client/src/components/modals/OutOfSyncMigrationsModal.tsx +++ b/packages/desktop-client/src/components/modals/OutOfSyncMigrationsModal.tsx @@ -1,15 +1,16 @@ import React from 'react'; import { Trans, useTranslation } from 'react-i18next'; +import { Button } from '@actual-app/components/button'; +import { Paragraph } from '@actual-app/components/paragraph'; +import { Text } from '@actual-app/components/text'; +import { View } from '@actual-app/components/view'; + import { closeBudget } from 'loot-core/client/actions'; import { useDispatch } from '../../redux'; -import { Button } from '../common/Button2'; import { Link } from '../common/Link'; import { Modal, ModalHeader, ModalTitle } from '../common/Modal'; -import { Paragraph } from '../common/Paragraph'; -import { Text } from '../common/Text'; -import { View } from '../common/View'; export function OutOfSyncMigrationsModal() { const dispatch = useDispatch(); diff --git a/packages/desktop-client/src/components/modals/PasswordEnableModal.tsx b/packages/desktop-client/src/components/modals/PasswordEnableModal.tsx index 067d127fcf1..1567a775fab 100644 --- a/packages/desktop-client/src/components/modals/PasswordEnableModal.tsx +++ b/packages/desktop-client/src/components/modals/PasswordEnableModal.tsx @@ -1,17 +1,19 @@ import { useState } from 'react'; import { Trans, useTranslation } from 'react-i18next'; +import { Button } from '@actual-app/components/button'; +import { Label } from '@actual-app/components/label'; +import { styles } from '@actual-app/components/styles'; +import { View } from '@actual-app/components/view'; + import { closeBudget, popModal } from 'loot-core/client/actions'; import { send } from 'loot-core/platform/client/fetch'; import * as asyncStorage from 'loot-core/platform/server/asyncStorage'; import { useDispatch } from '../../redux'; -import { theme, styles } from '../../style'; +import { theme } from '../../style'; import { Error as ErrorAlert } from '../alerts'; -import { Button } from '../common/Button2'; -import { Label } from '../common/Label'; import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal'; -import { View } from '../common/View'; import { FormField } from '../forms'; import { ConfirmOldPasswordForm, diff --git a/packages/desktop-client/src/components/modals/PayeeAutocompleteModal.tsx b/packages/desktop-client/src/components/modals/PayeeAutocompleteModal.tsx index 77a22652546..b2c85a98f4b 100644 --- a/packages/desktop-client/src/components/modals/PayeeAutocompleteModal.tsx +++ b/packages/desktop-client/src/components/modals/PayeeAutocompleteModal.tsx @@ -42,7 +42,9 @@ export function PayeeAutocompleteModal({ onClose={onClose} containerProps={{ style: { - height: isNarrowWidth ? '85vh' : 275, + height: isNarrowWidth + ? 'calc(var(--visual-viewport-height) * 0.85)' + : 275, backgroundColor: theme.menuAutoCompleteBackground, }, }} diff --git a/packages/desktop-client/src/components/modals/ScheduledTransactionMenuModal.tsx b/packages/desktop-client/src/components/modals/ScheduledTransactionMenuModal.tsx index 53efee0fa35..ec148781abe 100644 --- a/packages/desktop-client/src/components/modals/ScheduledTransactionMenuModal.tsx +++ b/packages/desktop-client/src/components/modals/ScheduledTransactionMenuModal.tsx @@ -5,6 +5,11 @@ import React, { } from 'react'; import { useTranslation } from 'react-i18next'; +import { Menu } from '@actual-app/components/menu'; +import { styles } from '@actual-app/components/styles'; +import { Text } from '@actual-app/components/text'; +import { View } from '@actual-app/components/view'; + import { useSchedules } from 'loot-core/client/data-hooks/schedules'; import { format } from 'loot-core/shared/months'; import { q } from 'loot-core/shared/query'; @@ -13,16 +18,13 @@ import { extractScheduleConds, } from 'loot-core/shared/schedules'; -import { theme, styles } from '../../style'; -import { Menu } from '../common/Menu'; +import { theme } from '../../style'; import { Modal, ModalCloseButton, ModalHeader, ModalTitle, } from '../common/Modal'; -import { Text } from '../common/Text'; -import { View } from '../common/View'; type ScheduledTransactionMenuModalProps = ScheduledTransactionMenuProps; diff --git a/packages/desktop-client/src/components/modals/SelectLinkedAccountsModal.jsx b/packages/desktop-client/src/components/modals/SelectLinkedAccountsModal.jsx index 10c3fd8777d..545c47caefb 100644 --- a/packages/desktop-client/src/components/modals/SelectLinkedAccountsModal.jsx +++ b/packages/desktop-client/src/components/modals/SelectLinkedAccountsModal.jsx @@ -1,6 +1,10 @@ import React, { useState } from 'react'; import { useTranslation, Trans } from 'react-i18next'; +import { Button } from '@actual-app/components/button'; +import { Text } from '@actual-app/components/text'; +import { View } from '@actual-app/components/view'; + import { linkAccount, linkAccountSimpleFin, @@ -12,10 +16,7 @@ import { useAccounts } from '../../hooks/useAccounts'; import { useDispatch } from '../../redux'; import { theme } from '../../style'; import { Autocomplete } from '../autocomplete/Autocomplete'; -import { Button } from '../common/Button2'; import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal'; -import { Text } from '../common/Text'; -import { View } from '../common/View'; import { PrivacyFilter } from '../PrivacyFilter'; import { TableHeader, Table, Row, Field } from '../table'; diff --git a/packages/desktop-client/src/components/modals/SimpleFinInitialiseModal.tsx b/packages/desktop-client/src/components/modals/SimpleFinInitialiseModal.tsx index d051d30f9be..c5460661436 100644 --- a/packages/desktop-client/src/components/modals/SimpleFinInitialiseModal.tsx +++ b/packages/desktop-client/src/components/modals/SimpleFinInitialiseModal.tsx @@ -2,11 +2,14 @@ import React, { useState } from 'react'; import { useTranslation, Trans } from 'react-i18next'; +import { ButtonWithLoading } from '@actual-app/components/button'; +import { Text } from '@actual-app/components/text'; +import { View } from '@actual-app/components/view'; + import { send } from 'loot-core/platform/client/fetch'; import { getSecretsError } from 'loot-core/shared/errors'; import { Error } from '../alerts'; -import { ButtonWithLoading } from '../common/Button2'; import { Input } from '../common/Input'; import { Link } from '../common/Link'; import { @@ -15,8 +18,6 @@ import { ModalCloseButton, ModalHeader, } from '../common/Modal'; -import { Text } from '../common/Text'; -import { View } from '../common/View'; import { FormField, FormLabel } from '../forms'; type SimpleFinInitialiseProps = { diff --git a/packages/desktop-client/src/components/modals/SingleInputModal.tsx b/packages/desktop-client/src/components/modals/SingleInputModal.tsx index c1d5a12ed68..e56fa50844b 100644 --- a/packages/desktop-client/src/components/modals/SingleInputModal.tsx +++ b/packages/desktop-client/src/components/modals/SingleInputModal.tsx @@ -7,12 +7,13 @@ import React, { } from 'react'; import { Form } from 'react-aria-components'; -import { styles } from '../../style'; -import { Button } from '../common/Button2'; -import { FormError } from '../common/FormError'; -import { InitialFocus } from '../common/InitialFocus'; +import { Button } from '@actual-app/components/button'; +import { FormError } from '@actual-app/components/form-error'; +import { InitialFocus } from '@actual-app/components/initial-focus'; +import { styles } from '@actual-app/components/styles'; +import { View } from '@actual-app/components/view'; + import { Modal, ModalCloseButton, type ModalHeader } from '../common/Modal'; -import { View } from '../common/View'; import { InputField } from '../mobile/MobileForms'; type SingleInputModalProps = { diff --git a/packages/desktop-client/src/components/modals/TrackingBalanceMenuModal.tsx b/packages/desktop-client/src/components/modals/TrackingBalanceMenuModal.tsx index 2a070152c53..9d20517b6a3 100644 --- a/packages/desktop-client/src/components/modals/TrackingBalanceMenuModal.tsx +++ b/packages/desktop-client/src/components/modals/TrackingBalanceMenuModal.tsx @@ -4,10 +4,14 @@ import React, { } from 'react'; import { useTranslation } from 'react-i18next'; +import { styles } from '@actual-app/components/styles'; +import { Text } from '@actual-app/components/text'; +import { View } from '@actual-app/components/view'; + import { trackingBudget } from 'loot-core/client/queries'; import { useCategory } from '../../hooks/useCategory'; -import { theme, styles } from '../../style'; +import { theme } from '../../style'; import { BalanceWithCarryover, CarryoverIndicator, @@ -19,8 +23,6 @@ import { ModalHeader, ModalTitle, } from '../common/Modal'; -import { Text } from '../common/Text'; -import { View } from '../common/View'; import { CellValueText } from '../spreadsheet/CellValue'; type TrackingBalanceMenuModalProps = ComponentPropsWithoutRef< diff --git a/packages/desktop-client/src/components/modals/TrackingBudgetMenuModal.tsx b/packages/desktop-client/src/components/modals/TrackingBudgetMenuModal.tsx index 33d8738caf7..abceb5c7d46 100644 --- a/packages/desktop-client/src/components/modals/TrackingBudgetMenuModal.tsx +++ b/packages/desktop-client/src/components/modals/TrackingBudgetMenuModal.tsx @@ -6,11 +6,15 @@ import React, { } from 'react'; import { useTranslation } from 'react-i18next'; +import { styles } from '@actual-app/components/styles'; +import { Text } from '@actual-app/components/text'; +import { View } from '@actual-app/components/view'; + import { trackingBudget } from 'loot-core/client/queries'; import { amountToInteger, integerToAmount } from 'loot-core/shared/util'; import { useCategory } from '../../hooks/useCategory'; -import { theme, styles } from '../../style'; +import { theme } from '../../style'; import { BudgetMenu } from '../budget/tracking/BudgetMenu'; import { useTrackingSheetValue } from '../budget/tracking/TrackingBudgetComponents'; import { @@ -19,8 +23,6 @@ import { ModalHeader, ModalTitle, } from '../common/Modal'; -import { Text } from '../common/Text'; -import { View } from '../common/View'; import { FocusableAmountInput } from '../mobile/transactions/FocusableAmountInput'; type TrackingBudgetMenuModalProps = ComponentPropsWithoutRef< diff --git a/packages/desktop-client/src/components/modals/TrackingBudgetMonthMenuModal.tsx b/packages/desktop-client/src/components/modals/TrackingBudgetMonthMenuModal.tsx index b2aeb325d65..f136a8ac430 100644 --- a/packages/desktop-client/src/components/modals/TrackingBudgetMonthMenuModal.tsx +++ b/packages/desktop-client/src/components/modals/TrackingBudgetMonthMenuModal.tsx @@ -2,6 +2,9 @@ import React, { useState } from 'react'; import { useTranslation } from 'react-i18next'; +import { Button } from '@actual-app/components/button'; +import { styles } from '@actual-app/components/styles'; +import { View } from '@actual-app/components/view'; import { css } from '@emotion/css'; import * as monthUtils from 'loot-core/shared/months'; @@ -10,11 +13,9 @@ import { useNotes } from '../../hooks/useNotes'; import { useUndo } from '../../hooks/useUndo'; import { SvgCheveronDown, SvgCheveronUp } from '../../icons/v1'; import { SvgNotesPaper } from '../../icons/v2'; -import { type CSSProperties, styles, theme } from '../../style'; +import { type CSSProperties, theme } from '../../style'; import { BudgetMonthMenu } from '../budget/tracking/budgetsummary/BudgetMonthMenu'; -import { Button } from '../common/Button2'; import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal'; -import { View } from '../common/View'; import { Notes } from '../Notes'; type TrackingBudgetMonthMenuModalProps = { diff --git a/packages/desktop-client/src/components/modals/TrackingBudgetSummaryModal.tsx b/packages/desktop-client/src/components/modals/TrackingBudgetSummaryModal.tsx index 52309e339fa..1c1b75237e7 100644 --- a/packages/desktop-client/src/components/modals/TrackingBudgetSummaryModal.tsx +++ b/packages/desktop-client/src/components/modals/TrackingBudgetSummaryModal.tsx @@ -1,15 +1,16 @@ import React from 'react'; import { useTranslation } from 'react-i18next'; +import { Stack } from '@actual-app/components/stack'; +import { styles } from '@actual-app/components/styles'; + import { sheetForMonth } from 'loot-core/shared/months'; import * as monthUtils from 'loot-core/shared/months'; -import { styles } from '../../style'; import { ExpenseTotal } from '../budget/tracking/budgetsummary/ExpenseTotal'; import { IncomeTotal } from '../budget/tracking/budgetsummary/IncomeTotal'; import { Saved } from '../budget/tracking/budgetsummary/Saved'; import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal'; -import { Stack } from '../common/Stack'; import { NamespaceContext } from '../spreadsheet/NamespaceContext'; type TrackingBudgetSummaryModalProps = { diff --git a/packages/desktop-client/src/components/modals/TransferModal.tsx b/packages/desktop-client/src/components/modals/TransferModal.tsx index 9cf1a7fa64b..8fdb02ba225 100644 --- a/packages/desktop-client/src/components/modals/TransferModal.tsx +++ b/packages/desktop-client/src/components/modals/TransferModal.tsx @@ -1,20 +1,21 @@ import React, { useMemo, useState } from 'react'; import { Trans, useTranslation } from 'react-i18next'; +import { Button } from '@actual-app/components/button'; +import { InitialFocus } from '@actual-app/components/initial-focus'; +import { styles } from '@actual-app/components/styles'; +import { View } from '@actual-app/components/view'; + import { pushModal } from 'loot-core/client/actions'; import { type CategoryEntity } from 'loot-core/types/models'; import { useCategories } from '../../hooks/useCategories'; import { useDispatch } from '../../redux'; -import { styles } from '../../style'; import { addToBeBudgetedGroup, removeCategoriesFromGroups, } from '../budget/util'; -import { Button } from '../common/Button2'; -import { InitialFocus } from '../common/InitialFocus'; import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal'; -import { View } from '../common/View'; import { FieldLabel, TapField } from '../mobile/MobileForms'; import { AmountInput } from '../util/AmountInput'; diff --git a/packages/desktop-client/src/components/modals/TransferOwnership.tsx b/packages/desktop-client/src/components/modals/TransferOwnership.tsx index 72c886ba457..b03f87c13b5 100644 --- a/packages/desktop-client/src/components/modals/TransferOwnership.tsx +++ b/packages/desktop-client/src/components/modals/TransferOwnership.tsx @@ -1,6 +1,12 @@ import { useEffect, useState } from 'react'; import { Trans, useTranslation } from 'react-i18next'; +import { Button } from '@actual-app/components/button'; +import { Stack } from '@actual-app/components/stack'; +import { styles } from '@actual-app/components/styles'; +import { Text } from '@actual-app/components/text'; +import { View } from '@actual-app/components/view'; + import { addNotification, closeAndLoadBudget, @@ -14,13 +20,9 @@ import { type Handlers } from 'loot-core/types/handlers'; import { useMetadataPref } from '../../hooks/useMetadataPref'; import { useDispatch, useSelector } from '../../redux'; -import { styles, theme } from '../../style'; -import { Button } from '../common/Button2'; +import { theme } from '../../style'; import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal'; import { Select } from '../common/Select'; -import { Stack } from '../common/Stack'; -import { Text } from '../common/Text'; -import { View } from '../common/View'; import { FormField, FormLabel } from '../forms'; type TransferOwnershipProps = { diff --git a/packages/desktop-client/src/components/modals/manager/ConfirmChangeDocumentDir.tsx b/packages/desktop-client/src/components/modals/manager/ConfirmChangeDocumentDir.tsx index 97f7b9aa22d..1ff4529be6b 100644 --- a/packages/desktop-client/src/components/modals/manager/ConfirmChangeDocumentDir.tsx +++ b/packages/desktop-client/src/components/modals/manager/ConfirmChangeDocumentDir.tsx @@ -1,16 +1,18 @@ import React, { useCallback, useState } from 'react'; import { Trans, useTranslation } from 'react-i18next'; +import { Button, ButtonWithLoading } from '@actual-app/components/button'; +import { styles } from '@actual-app/components/styles'; +import { Text } from '@actual-app/components/text'; +import { View } from '@actual-app/components/view'; + import { addNotification } from 'loot-core/client/actions'; import { useGlobalPref } from '../../../hooks/useGlobalPref'; import { useDispatch } from '../../../redux'; -import { theme, styles } from '../../../style'; +import { theme } from '../../../style'; import { Information } from '../../alerts'; -import { Button, ButtonWithLoading } from '../../common/Button2'; import { Modal, ModalCloseButton, ModalHeader } from '../../common/Modal'; -import { Text } from '../../common/Text'; -import { View } from '../../common/View'; import { Checkbox } from '../../forms'; function DirectoryDisplay({ directory }: { directory: string }) { diff --git a/packages/desktop-client/src/components/modals/manager/DeleteFileModal.tsx b/packages/desktop-client/src/components/modals/manager/DeleteFileModal.tsx index fb5ffda8d32..2f927f7b2a5 100644 --- a/packages/desktop-client/src/components/modals/manager/DeleteFileModal.tsx +++ b/packages/desktop-client/src/components/modals/manager/DeleteFileModal.tsx @@ -1,15 +1,16 @@ import React, { useState } from 'react'; import { Trans, useTranslation } from 'react-i18next'; +import { ButtonWithLoading } from '@actual-app/components/button'; +import { Text } from '@actual-app/components/text'; +import { View } from '@actual-app/components/view'; + import { deleteBudget } from 'loot-core/client/actions'; import { type File } from 'loot-core/types/file'; import { useDispatch } from '../../../redux'; import { theme } from '../../../style'; -import { ButtonWithLoading } from '../../common/Button2'; import { Modal, ModalCloseButton, ModalHeader } from '../../common/Modal'; -import { Text } from '../../common/Text'; -import { View } from '../../common/View'; type DeleteFileProps = { file: File; diff --git a/packages/desktop-client/src/components/modals/manager/DuplicateFileModal.tsx b/packages/desktop-client/src/components/modals/manager/DuplicateFileModal.tsx index 7c5afeb9307..4e030163233 100644 --- a/packages/desktop-client/src/components/modals/manager/DuplicateFileModal.tsx +++ b/packages/desktop-client/src/components/modals/manager/DuplicateFileModal.tsx @@ -1,6 +1,13 @@ import React, { useEffect, useState } from 'react'; import { Trans, useTranslation } from 'react-i18next'; +import { Button, ButtonWithLoading } from '@actual-app/components/button'; +import { FormError } from '@actual-app/components/form-error'; +import { InitialFocus } from '@actual-app/components/initial-focus'; +import { InlineField } from '@actual-app/components/inline-field'; +import { Text } from '@actual-app/components/text'; +import { View } from '@actual-app/components/view'; + import { addNotification, duplicateBudget, @@ -11,10 +18,6 @@ import { type File } from 'loot-core/types/file'; import { useDispatch } from '../../../redux'; 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, @@ -22,8 +25,6 @@ import { ModalCloseButton, ModalHeader, } from '../../common/Modal'; -import { Text } from '../../common/Text'; -import { View } from '../../common/View'; type DuplicateFileProps = { file: File; diff --git a/packages/desktop-client/src/components/modals/manager/FilesSettingsModal.tsx b/packages/desktop-client/src/components/modals/manager/FilesSettingsModal.tsx index 0e2a0b0d9b2..10cd1e939ae 100644 --- a/packages/desktop-client/src/components/modals/manager/FilesSettingsModal.tsx +++ b/packages/desktop-client/src/components/modals/manager/FilesSettingsModal.tsx @@ -1,16 +1,18 @@ import React, { useState } from 'react'; import { Trans, useTranslation } from 'react-i18next'; +import { Button } from '@actual-app/components/button'; +import { styles } from '@actual-app/components/styles'; +import { Text } from '@actual-app/components/text'; +import { View } from '@actual-app/components/view'; + import { loadAllFiles, pushModal } from 'loot-core/client/actions'; import { useGlobalPref } from '../../../hooks/useGlobalPref'; import { SvgPencil1 } from '../../../icons/v2'; import { useDispatch } from '../../../redux'; -import { theme, styles } from '../../../style'; -import { Button } from '../../common/Button2'; +import { theme } from '../../../style'; import { Modal, ModalCloseButton, ModalHeader } from '../../common/Modal'; -import { Text } from '../../common/Text'; -import { View } from '../../common/View'; function FileLocationSettings() { const [documentDir, _setDocumentDirPref] = useGlobalPref('documentDir'); diff --git a/packages/desktop-client/src/components/modals/manager/ImportModal.tsx b/packages/desktop-client/src/components/modals/manager/ImportModal.tsx index 13c1a6321b7..661a893025b 100644 --- a/packages/desktop-client/src/components/modals/manager/ImportModal.tsx +++ b/packages/desktop-client/src/components/modals/manager/ImportModal.tsx @@ -67,8 +67,10 @@ export function ImportModal() { )} - Select an app to import from, and we’ll guide you through the - process. + + Select an app to import from, and we’ll guide you through the + process. + {error ? ( - Add + Add )} @@ -1855,28 +1780,35 @@ function TransactionTableInner({ setScrollWidth(!width ? 0 : width); } + const { + onCloseAddTransaction: onCloseAddTransactionProp, + onNavigateToTransferAccount: onNavigateToTransferAccountProp, + onNavigateToSchedule: onNavigateToScheduleProp, + onNotesTagClick: onNotesTagClickProp, + } = props; + const onNavigateToTransferAccount = useCallback( accountId => { - props.onCloseAddTransaction(); - props.onNavigateToTransferAccount(accountId); + onCloseAddTransactionProp(); + onNavigateToTransferAccountProp(accountId); }, - [props.onCloseAddTransaction, props.onNavigateToTransferAccount], + [onCloseAddTransactionProp, onNavigateToTransferAccountProp], ); const onNavigateToSchedule = useCallback( scheduleId => { - props.onCloseAddTransaction(); - props.onNavigateToSchedule(scheduleId); + onCloseAddTransactionProp(); + onNavigateToScheduleProp(scheduleId); }, - [props.onCloseAddTransaction, props.onNavigateToSchedule], + [onCloseAddTransactionProp, onNavigateToScheduleProp], ); const onNotesTagClick = useCallback( noteTag => { - props.onCloseAddTransaction(); - props.onNotesTagClick(noteTag); + onCloseAddTransactionProp(); + onNotesTagClickProp(noteTag); }, - [props.onCloseAddTransaction, props.onNotesTagClick], + [onCloseAddTransactionProp, onNotesTagClickProp], ); useEffect(() => { @@ -2112,6 +2044,7 @@ export const TransactionTable = forwardRef((props, ref) => { const [newTransactions, setNewTransactions] = useState(null); const [prevIsAdding, setPrevIsAdding] = useState(false); const splitsExpanded = useSplitsExpanded(); + const splitsExpandedDispatch = splitsExpanded.dispatch; const prevSplitsExpanded = useRef(null); const tableRef = useRef(null); @@ -2190,6 +2123,8 @@ export const TransactionTable = forwardRef((props, ref) => { ); }, [props.transactions, props.payees, props.accounts]); + const hasPrevSplitsExpanded = prevSplitsExpanded.current; + useEffect(() => { // If it's anchored that means we've also disabled animations. To // reduce the chance for side effect collision, only do this if @@ -2198,7 +2133,7 @@ export const TransactionTable = forwardRef((props, ref) => { tableRef.current.unanchor(); tableRef.current.setRowAnimation(true); } - }, [prevSplitsExpanded.current]); + }, [hasPrevSplitsExpanded]); const newNavigator = useTableNavigator( newTransactions, @@ -2216,14 +2151,12 @@ export const TransactionTable = forwardRef((props, ref) => { const [_, forceRerender] = useState({}); const selectedItems = useSelectedItems(); - useLayoutEffect(() => { - latestState.current = { - newTransactions, - newNavigator, - tableNavigator, - transactions: props.transactions, - }; - }); + latestState.current = { + newTransactions, + newNavigator, + tableNavigator, + transactions: props.transactions, + }; // Derive new transactions from the `isAdding` prop if (prevIsAdding !== props.isAdding) { @@ -2238,32 +2171,30 @@ export const TransactionTable = forwardRef((props, ref) => { setPrevIsAdding(props.isAdding); } - useEffect(() => { - if (shouldAdd.current) { - if (newTransactions[0].account == null) { - dispatch( - addNotification({ - type: 'error', - message: 'Account is a required field', - }), - ); - newNavigator.onEdit('temp', 'account'); - } else { - const transactions = latestState.current.newTransactions; - const lastDate = transactions.length > 0 ? transactions[0].date : null; - setNewTransactions( - makeTemporaryTransactions( - props.currentAccountId, - props.currentCategoryId, - lastDate, - ), - ); - newNavigator.onEdit('temp', 'date'); - props.onAdd(transactions); - } - shouldAdd.current = false; + if (shouldAdd.current) { + if (newTransactions[0].account == null) { + dispatch( + addNotification({ + type: 'error', + message: 'Account is a required field', + }), + ); + newNavigator.onEdit('temp', 'account'); + } else { + const transactions = latestState.current.newTransactions; + const lastDate = transactions.length > 0 ? transactions[0].date : null; + setNewTransactions( + makeTemporaryTransactions( + props.currentAccountId, + props.currentCategoryId, + lastDate, + ), + ); + newNavigator.onEdit('temp', 'date'); + props.onAdd(transactions); } - }); + shouldAdd.current = false; + } useEffect(() => { if (savePending.current && afterSaveFunc.current) { @@ -2272,7 +2203,7 @@ export const TransactionTable = forwardRef((props, ref) => { } savePending.current = false; - }, [newTransactions, props.transactions]); + }, [newTransactions, props, props.transactions]); function getFieldsNewTransaction(item) { const fields = [ @@ -2409,7 +2340,20 @@ export const TransactionTable = forwardRef((props, ref) => { // effect we want to run. We have to wait for all updates to be // committed (the input could still be saving a value). forceRerender({}); - }, [props.onAdd, newNavigator.onEdit]); + }, []); + + const { + onSave: onSaveProp, + onApplyRules: onApplyRulesProp, + onBatchDelete, + onBatchDuplicate, + onBatchLinkSchedule, + onBatchUnlinkSchedule, + onCreateRule: onCreateRuleProp, + onScheduleAction: onScheduleActionProp, + onMakeAsNonSplitTransactions: onMakeAsNonSplitTransactionsProp, + onSplit: onSplitProp, + } = props; const onSave = useCallback( async (transaction, subtransactions = null, updatedFieldName = null) => { @@ -2420,8 +2364,8 @@ export const TransactionTable = forwardRef((props, ref) => { : transaction; if (isTemporaryId(transaction.id)) { - if (props.onApplyRules) { - groupedTransaction = await props.onApplyRules( + if (onApplyRulesProp) { + groupedTransaction = await onApplyRulesProp( groupedTransaction, updatedFieldName, ); @@ -2436,48 +2380,69 @@ export const TransactionTable = forwardRef((props, ref) => { ), ); } else { - props.onSave(groupedTransaction); + onSaveProp(groupedTransaction); } }, - [props.onSave], + [onSaveProp, onApplyRulesProp], ); - const onDelete = useCallback(id => { - const temporary = isTemporaryId(id); + const onDelete = useCallback( + id => { + const temporary = isTemporaryId(id); - if (temporary) { - const newTrans = latestState.current.newTransactions; + if (temporary) { + const newTrans = latestState.current.newTransactions; - if (id === newTrans[0].id) { - // You can never delete the parent new transaction - return; - } + if (id === newTrans[0].id) { + // You can never delete the parent new transaction + return; + } - setNewTransactions(deleteTransaction(newTrans, id).data); - } else { - props.onBatchDelete([id]); - } - }, []); + setNewTransactions(deleteTransaction(newTrans, id).data); + } else { + onBatchDelete([id]); + } + }, + [onBatchDelete], + ); - const onDuplicate = useCallback(id => { - props.onBatchDuplicate([id]); - }, []); + const onDuplicate = useCallback( + id => { + onBatchDuplicate([id]); + }, + [onBatchDuplicate], + ); - const onLinkSchedule = useCallback(id => { - props.onBatchLinkSchedule([id]); - }, []); - const onUnlinkSchedule = useCallback(id => { - props.onBatchUnlinkSchedule([id]); - }, []); - const onCreateRule = useCallback(id => { - props.onCreateRule([id]); - }, []); - const onScheduleAction = useCallback((action, id) => { - props.onScheduleAction(action, [id]); - }, []); - const onMakeAsNonSplitTransactions = useCallback(id => { - props.onMakeAsNonSplitTransactions([id]); - }, []); + const onLinkSchedule = useCallback( + id => { + onBatchLinkSchedule([id]); + }, + [onBatchLinkSchedule], + ); + const onUnlinkSchedule = useCallback( + id => { + onBatchUnlinkSchedule([id]); + }, + [onBatchUnlinkSchedule], + ); + const onCreateRule = useCallback( + id => { + onCreateRuleProp([id]); + }, + [onCreateRuleProp], + ); + const onScheduleAction = useCallback( + (action, id) => { + onScheduleActionProp(action, [id]); + }, + [onScheduleActionProp], + ); + const onMakeAsNonSplitTransactions = useCallback( + id => { + onMakeAsNonSplitTransactionsProp([id]); + }, + [onMakeAsNonSplitTransactionsProp], + ); const onSplit = useMemo(() => { return id => { @@ -2500,9 +2465,9 @@ export const TransactionTable = forwardRef((props, ref) => { } } else { const trans = latestState.current.transactions.find(t => t.id === id); - const newId = props.onSplit(id); + const newId = onSplitProp(id); - splitsExpanded.dispatch({ type: 'open-split', id: trans.id }); + splitsExpandedDispatch({ type: 'open-split', id: trans.id }); const { tableNavigator } = latestState.current; if (trans.amount === null) { @@ -2512,12 +2477,19 @@ export const TransactionTable = forwardRef((props, ref) => { } } }; - }, [props.onSplit, splitsExpanded.dispatch]); + }, [onSplitProp, splitsExpandedDispatch]); + + const { onAddSplit: onAddSplitProp } = props; const onAddSplit = useCallback( id => { + const { + tableNavigator, + newNavigator, + newTransactions: newTrans, + } = latestState.current; + if (isTemporaryId(id)) { - const newTrans = latestState.current.newTransactions; const { data, diff } = addSplitTransaction(newTrans, id); setNewTransactions(data); newNavigator.onEdit( @@ -2525,19 +2497,19 @@ export const TransactionTable = forwardRef((props, ref) => { latestState.current.newNavigator.focusedField, ); } else { - const newId = props.onAddSplit(id); + const newId = onAddSplitProp(id); tableNavigator.onEdit( newId, latestState.current.tableNavigator.focusedField, ); } }, - [props.onAddSplit], + [onAddSplitProp], ); const onDistributeRemainder = useCallback( async id => { - const { transactions, tableNavigator, newTransactions } = + const { transactions, newNavigator, tableNavigator, newTransactions } = latestState.current; const targetTransactions = isTemporaryId(id) @@ -2591,7 +2563,7 @@ export const TransactionTable = forwardRef((props, ref) => { }); } }, - [latestState], + [onSave], ); function onCloseAddTransaction() { @@ -2605,8 +2577,8 @@ export const TransactionTable = forwardRef((props, ref) => { } const onToggleSplit = useCallback( - id => splitsExpanded.dispatch({ type: 'toggle-split', id }), - [splitsExpanded.dispatch], + id => splitsExpandedDispatch({ type: 'toggle-split', id }), + [splitsExpandedDispatch], ); return ( diff --git a/packages/desktop-client/src/components/transactions/TransactionsTable.test.tsx b/packages/desktop-client/src/components/transactions/TransactionsTable.test.tsx index fff9b87b2aa..64aee992360 100644 --- a/packages/desktop-client/src/components/transactions/TransactionsTable.test.tsx +++ b/packages/desktop-client/src/components/transactions/TransactionsTable.test.tsx @@ -48,6 +48,10 @@ vi.mock('../../hooks/useFeatureFlag', () => ({ })); const accounts = [generateAccount('Bank of America')]; +vi.mock('../../hooks/useAccounts', () => ({ + useAccounts: () => accounts, +})); + const payees: PayeeEntity[] = [ { id: 'bob-id', @@ -65,6 +69,15 @@ const payees: PayeeEntity[] = [ name: 'This guy on the side of the road', }, ]; +vi.mock('../../hooks/usePayees', async importOriginal => { + // eslint-disable-next-line @typescript-eslint/consistent-type-imports + const actual = await importOriginal(); + return { + ...actual, + usePayees: () => payees, + }; +}); + const categoryGroups = generateCategoryGroups([ { name: 'Investments and Savings', @@ -79,6 +92,13 @@ const categoryGroups = generateCategoryGroups([ categories: [{ name: 'Big Projects' }, { name: 'Shed' }], }, ]); +vi.mock('../../hooks/useCategories', () => ({ + useCategories: () => ({ + list: categoryGroups.flatMap(g => g.categories), + grouped: categoryGroups, + }), +})); + const usualGroup = categoryGroups[1]; function generateTransactions( @@ -130,13 +150,14 @@ type LiveTransactionTableProps = { }; function LiveTransactionTable(props: LiveTransactionTableProps) { - const [transactions, setTransactions] = useState(props.transactions); + const { transactions: transactionsProp, onTransactionsChange } = props; + + const [transactions, setTransactions] = useState(transactionsProp); useEffect(() => { - if (transactions === props.transactions) return; - props.onTransactionsChange?.(transactions); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [transactions]); + if (transactions === transactionsProp) return; + onTransactionsChange?.(transactions); + }, [transactions, transactionsProp, onTransactionsChange]); const onSplit = (id: string) => { const { data, diff } = splitTransaction(transactions, id); @@ -212,6 +233,11 @@ function initBasicServer() { return { data: payees, dependencies: [] }; case 'accounts': return { data: accounts, dependencies: [] }; + case 'transactions': + return { + data: generateTransactions(5, [6]), + dependencies: [], + }; default: throw new Error(`queried unknown table: ${query.table}`); } diff --git a/packages/desktop-client/src/components/util/AmountInput.tsx b/packages/desktop-client/src/components/util/AmountInput.tsx index 833c0b32795..1a0e9f59069 100644 --- a/packages/desktop-client/src/components/util/AmountInput.tsx +++ b/packages/desktop-client/src/components/util/AmountInput.tsx @@ -10,6 +10,9 @@ import React, { } from 'react'; import { useTranslation } from 'react-i18next'; +import { Button } from '@actual-app/components/button'; +import { View } from '@actual-app/components/view'; + import { evalArithmetic } from 'loot-core/shared/arithmetic'; import { amountToInteger, appendDecimals } from 'loot-core/shared/util'; @@ -17,9 +20,7 @@ import { useMergedRefs } from '../../hooks/useMergedRefs'; import { useSyncedPref } from '../../hooks/useSyncedPref'; import { SvgAdd, SvgSubtract } from '../../icons/v1'; import { theme } from '../../style'; -import { Button } from '../common/Button2'; import { InputWithContent } from '../common/InputWithContent'; -import { View } from '../common/View'; import { useFormat } from '../spreadsheet/useFormat'; type AmountInputProps = { diff --git a/packages/desktop-client/src/components/util/DisplayId.tsx b/packages/desktop-client/src/components/util/DisplayId.tsx index 162744a1772..17ed79fb0a9 100644 --- a/packages/desktop-client/src/components/util/DisplayId.tsx +++ b/packages/desktop-client/src/components/util/DisplayId.tsx @@ -2,10 +2,11 @@ import React from 'react'; import { useTranslation } from 'react-i18next'; +import { Text } from '@actual-app/components/text'; + import { useAccount } from '../../hooks/useAccount'; import { usePayee } from '../../hooks/usePayee'; import { theme } from '../../style'; -import { Text } from '../common/Text'; type DisplayIdProps = { type: 'accounts' | 'payees'; diff --git a/packages/desktop-client/src/components/util/GenericInput.jsx b/packages/desktop-client/src/components/util/GenericInput.jsx index cf6f2bee112..d1331589250 100644 --- a/packages/desktop-client/src/components/util/GenericInput.jsx +++ b/packages/desktop-client/src/components/util/GenericInput.jsx @@ -1,5 +1,7 @@ import React from 'react'; +import { View } from '@actual-app/components/view'; + import { useReports } from 'loot-core/client/data-hooks/reports'; import { getMonthYearFormat } from 'loot-core/shared/months'; import { integerToAmount, amountToInteger } from 'loot-core/shared/util'; @@ -14,7 +16,6 @@ import { FilterAutocomplete } from '../autocomplete/FilterAutocomplete'; import { PayeeAutocomplete } from '../autocomplete/PayeeAutocomplete'; import { ReportAutocomplete } from '../autocomplete/ReportAutocomplete'; import { Input } from '../common/Input'; -import { View } from '../common/View'; import { Checkbox } from '../forms'; import { DateSelect } from '../select/DateSelect'; import { RecurringSchedulePicker } from '../select/RecurringSchedulePicker'; diff --git a/packages/desktop-client/src/gocardless.ts b/packages/desktop-client/src/gocardless.ts index 8d868ebb920..6cbb6b905dc 100644 --- a/packages/desktop-client/src/gocardless.ts +++ b/packages/desktop-client/src/gocardless.ts @@ -5,7 +5,6 @@ import { type GoCardlessToken } from 'loot-core/types/models'; function _authorize( dispatch: AppDispatch, - upgradingAccountId: string | undefined, { onSuccess, onClose, @@ -18,7 +17,6 @@ function _authorize( pushModal('gocardless-external-msg', { onMoveExternal: async ({ institutionId }) => { const resp = await send('gocardless-create-web-token', { - upgradingAccountId, institutionId, accessValidForDays: 90, }); @@ -28,7 +26,6 @@ function _authorize( window.Actual.openURLInBrowser(link); return send('gocardless-poll-web-token', { - upgradingAccountId, requisitionId, }); }, @@ -39,17 +36,13 @@ function _authorize( ); } -export async function authorizeBank( - dispatch: AppDispatch, - { upgradingAccountId }: { upgradingAccountId?: string } = {}, -) { - _authorize(dispatch, upgradingAccountId, { +export async function authorizeBank(dispatch: AppDispatch) { + _authorize(dispatch, { onSuccess: async data => { dispatch( pushModal('select-linked-accounts', { accounts: data.accounts, requisitionId: data.id, - upgradingAccountId, syncSource: 'goCardless', }), ); diff --git a/packages/desktop-client/src/hooks/useAccounts.ts b/packages/desktop-client/src/hooks/useAccounts.ts index 7fdda9a51f4..f7b71197153 100644 --- a/packages/desktop-client/src/hooks/useAccounts.ts +++ b/packages/desktop-client/src/hooks/useAccounts.ts @@ -4,15 +4,18 @@ import { getAccounts } from 'loot-core/client/queries/queriesSlice'; import { useSelector, useDispatch } from '../redux'; +import { useInitialMount } from './useInitialMount'; + export function useAccounts() { const dispatch = useDispatch(); const accountsLoaded = useSelector(state => state.queries.accountsLoaded); + const isInitialMount = useInitialMount(); useEffect(() => { - if (!accountsLoaded) { + if (isInitialMount && !accountsLoaded) { dispatch(getAccounts()); } - }, []); + }, [accountsLoaded, dispatch, isInitialMount]); return useSelector(state => state.queries.accounts); } diff --git a/packages/desktop-client/src/hooks/useCategories.ts b/packages/desktop-client/src/hooks/useCategories.ts index a6936b35865..b233d77852c 100644 --- a/packages/desktop-client/src/hooks/useCategories.ts +++ b/packages/desktop-client/src/hooks/useCategories.ts @@ -4,15 +4,18 @@ import { getCategories } from 'loot-core/client/queries/queriesSlice'; import { useSelector, useDispatch } from '../redux'; +import { useInitialMount } from './useInitialMount'; + export function useCategories() { const dispatch = useDispatch(); const categoriesLoaded = useSelector(state => state.queries.categoriesLoaded); + const isInitialMount = useInitialMount(); useEffect(() => { - if (!categoriesLoaded) { + if (isInitialMount && !categoriesLoaded) { dispatch(getCategories()); } - }, []); + }, [categoriesLoaded, dispatch, isInitialMount]); return useSelector(state => state.queries.categories); } diff --git a/packages/desktop-client/src/hooks/useDisplayPayee.ts b/packages/desktop-client/src/hooks/useDisplayPayee.ts new file mode 100644 index 00000000000..ab2609b523c --- /dev/null +++ b/packages/desktop-client/src/hooks/useDisplayPayee.ts @@ -0,0 +1,138 @@ +import { useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; + +import { useTransactions } from 'loot-core/client/data-hooks/transactions'; +import { q } from 'loot-core/shared/query'; +import { + type AccountEntity, + type PayeeEntity, + type TransactionEntity, +} from 'loot-core/types/models'; + +import { useAccounts } from './useAccounts'; +import { usePayee } from './usePayee'; +import { usePayees } from './usePayees'; + +type Counts = { + counts: Record; + maxCount: number; + mostCommonPayeeTransaction: TransactionEntity | null; +}; + +type UseDisplayPayeeProps = { + transaction?: TransactionEntity | undefined; +}; + +export function useDisplayPayee({ transaction }: UseDisplayPayeeProps) { + const { t } = useTranslation(); + const subtransactionsQuery = useMemo( + () => q('transactions').filter({ parent_id: transaction?.id }).select('*'), + [transaction?.id], + ); + const { transactions: subtransactions = [] } = useTransactions({ + query: subtransactionsQuery, + }); + + const accounts = useAccounts(); + const payees = usePayees(); + const payee = usePayee(transaction?.payee || ''); + + return useMemo(() => { + if (subtransactions.length === 0) { + return getPrettyPayee({ + t, + transaction, + payee, + transferAccount: accounts.find( + a => + a.id === + payees.find(p => p.id === transaction?.payee)?.transfer_acct, + ), + }); + } + + const { counts, mostCommonPayeeTransaction } = + subtransactions?.reduce( + ({ counts, ...result }, sub) => { + if (sub.payee) { + counts[sub.payee] = (counts[sub.payee] || 0) + 1; + if (counts[sub.payee] > result.maxCount) { + return { + counts, + maxCount: counts[sub.payee], + mostCommonPayeeTransaction: sub, + }; + } + } + return { counts, ...result }; + }, + { counts: {}, maxCount: 0, mostCommonPayeeTransaction: null } as Counts, + ) || {}; + + if (!mostCommonPayeeTransaction) { + return t('Split (no payee)'); + } + + const mostCommonPayee = payees.find( + p => p.id === mostCommonPayeeTransaction.payee, + ); + + if (!mostCommonPayee) { + return t('Split (no payee)'); + } + + const numDistinctPayees = Object.keys(counts).length; + + return getPrettyPayee({ + t, + transaction: mostCommonPayeeTransaction, + payee: mostCommonPayee, + transferAccount: accounts.find( + a => + a.id === + payees.find(p => p.id === mostCommonPayeeTransaction.payee) + ?.transfer_acct, + ), + numHiddenPayees: numDistinctPayees - 1, + }); + }, [subtransactions, payees, accounts, transaction, payee, t]); +} + +type GetPrettyPayeeProps = { + t: ReturnType['t']; + transaction?: TransactionEntity | undefined; + payee?: PayeeEntity | undefined; + transferAccount?: AccountEntity | undefined; + numHiddenPayees?: number | undefined; +}; + +function getPrettyPayee({ + t, + transaction, + payee, + transferAccount, + numHiddenPayees = 0, +}: GetPrettyPayeeProps) { + if (!transaction) { + return ''; + } + + const formatPayeeName = (payeeName: string) => + numHiddenPayees > 0 + ? `${payeeName} ${t('(+{{numHiddenPayees}} more)', { + numHiddenPayees, + })}` + : payeeName; + + const { payee: payeeId } = transaction; + + if (transferAccount) { + return formatPayeeName(transferAccount.name); + } else if (payee) { + return formatPayeeName(payee.name); + } else if (payeeId && payeeId.startsWith('new:')) { + return formatPayeeName(payeeId.slice('new:'.length)); + } + + return ''; +} diff --git a/packages/desktop-client/src/hooks/useFeatureFlag.ts b/packages/desktop-client/src/hooks/useFeatureFlag.ts index 1ad9821839a..5f29af5ac85 100644 --- a/packages/desktop-client/src/hooks/useFeatureFlag.ts +++ b/packages/desktop-client/src/hooks/useFeatureFlag.ts @@ -4,6 +4,7 @@ import { useSyncedPref } from './useSyncedPref'; const DEFAULT_FEATURE_FLAG_STATE: Record = { goalTemplatesEnabled: false, + goalTemplatesUIEnabled: false, actionTemplating: false, contextMenus: false, openidAuth: false, diff --git a/packages/desktop-client/src/hooks/usePayees.ts b/packages/desktop-client/src/hooks/usePayees.ts index 27d4660799f..925bae3102a 100644 --- a/packages/desktop-client/src/hooks/usePayees.ts +++ b/packages/desktop-client/src/hooks/usePayees.ts @@ -7,17 +7,21 @@ import { import { useSelector, useDispatch } from '../redux'; +import { useInitialMount } from './useInitialMount'; + export function useCommonPayees() { const dispatch = useDispatch(); const commonPayeesLoaded = useSelector( state => state.queries.commonPayeesLoaded, ); + const isInitialMount = useInitialMount(); + useEffect(() => { - if (!commonPayeesLoaded) { + if (isInitialMount && !commonPayeesLoaded) { dispatch(getCommonPayees()); } - }, []); + }, [commonPayeesLoaded, dispatch, isInitialMount]); return useSelector(state => state.queries.commonPayees); } @@ -26,11 +30,13 @@ export function usePayees() { const dispatch = useDispatch(); const payeesLoaded = useSelector(state => state.queries.payeesLoaded); + const isInitialMount = useInitialMount(); + useEffect(() => { - if (!payeesLoaded) { + if (isInitialMount && !payeesLoaded) { dispatch(getPayees()); } - }, []); + }, [dispatch, isInitialMount, payeesLoaded]); return useSelector(state => state.queries.payees); } diff --git a/packages/desktop-client/src/hooks/useProperFocus.tsx b/packages/desktop-client/src/hooks/useProperFocus.tsx index dba487bdce5..7c0fc5c7a72 100644 --- a/packages/desktop-client/src/hooks/useProperFocus.tsx +++ b/packages/desktop-client/src/hooks/useProperFocus.tsx @@ -98,5 +98,5 @@ export function useProperFocus( } prevShouldFocus.current = shouldFocus; - }, [shouldFocus]); + }, [context, ref, shouldFocus]); } diff --git a/packages/desktop-client/src/hooks/useSelected.tsx b/packages/desktop-client/src/hooks/useSelected.tsx index 7c999e6e752..d3cfe2cf9c2 100644 --- a/packages/desktop-client/src/hooks/useSelected.tsx +++ b/packages/desktop-client/src/hooks/useSelected.tsx @@ -204,7 +204,7 @@ export function useSelected( const prevState = undo.getUndoState('selectedItems'); undo.setUndoState('selectedItems', { name, items: state.selectedItems }); return () => undo.setUndoState('selectedItems', prevState); - }, [state.selectedItems]); + }, [name, state.selectedItems]); useEffect(() => { function onUndo({ messages, undoTag }: UndoState) { @@ -233,7 +233,7 @@ export function useSelected( } return listen('undo-event', onUndo); - }, []); + }, [name]); return { items: state.selectedItems, @@ -263,37 +263,35 @@ export function SelectedProvider({ fetchAllIds, children, }: SelectedProviderProps) { - const latestItems = useRef(null); - - useEffect(() => { - latestItems.current = instance.items; - }, [instance.items]); + const { items: instanceItems, dispatch: instanceDispatch } = instance; + const latestItems = useRef(instanceItems); + latestItems.current = instanceItems; const dispatch = useCallback( async (action: Actions) => { if (action.type === 'select-all') { if (latestItems.current && latestItems.current.size > 0) { - return instance.dispatch({ + return instanceDispatch({ type: 'select-none', isRangeSelect: action.isRangeSelect, }); } else { if (fetchAllIds) { - return instance.dispatch({ + return instanceDispatch({ type: 'select-all', ids: await fetchAllIds(), isRangeSelect: action.isRangeSelect, }); } - return instance.dispatch({ + return instanceDispatch({ type: 'select-all', isRangeSelect: action.isRangeSelect, }); } } - return instance.dispatch(action); + return instanceDispatch(action); }, - [instance.dispatch, fetchAllIds], + [instanceDispatch, fetchAllIds], ); return ( @@ -335,7 +333,7 @@ export function SelectedProviderWithItems({ useEffect(() => { registerDispatch?.(selected.dispatch); - }, [registerDispatch]); + }, [registerDispatch, selected.dispatch]); return ( instance={selected} fetchAllIds={fetchAllIds}> diff --git a/packages/desktop-client/src/style/styles.ts b/packages/desktop-client/src/style/styles.ts index 3d0643cfe39..5ea78501c78 100644 --- a/packages/desktop-client/src/style/styles.ts +++ b/packages/desktop-client/src/style/styles.ts @@ -1,13 +1,10 @@ // @ts-strict-ignore -import { styles as baseStyles } from '@actual-app/components/styles'; +import { styles } from '@actual-app/components/styles'; import * as Platform from 'loot-core/client/platform'; export { type CSSProperties } from '@actual-app/components/styles'; -/** @deprecated please import styles from '@actual-app/components/styles' */ -export const styles = baseStyles; - let hiddenScrollbars = false; // need both styles defined for primary and secondary colors diff --git a/packages/desktop-client/vite.config.mts b/packages/desktop-client/vite.config.mts index fd0b9acb1a4..69ddbb80003 100644 --- a/packages/desktop-client/vite.config.mts +++ b/packages/desktop-client/vite.config.mts @@ -103,6 +103,8 @@ export default defineConfig(async ({ mode }) => { ]; } + const browserOpen = env.BROWSER_OPEN ? `//${env.BROWSER_OPEN}` : true; + return { base: '/', envPrefix: 'REACT_APP_', @@ -139,7 +141,7 @@ export default defineConfig(async ({ mode }) => { ? ['chrome', 'firefox', 'edge', 'browser', 'browserPrivate'].includes( env.BROWSER, ) - : true, + : browserOpen, watch: { disableGlobbing: false, }, diff --git a/packages/desktop-electron/index.ts b/packages/desktop-electron/index.ts index 885d6f71dfc..cecd667e6fe 100644 --- a/packages/desktop-electron/index.ts +++ b/packages/desktop-electron/index.ts @@ -22,6 +22,8 @@ import { import { copy, exists, remove } from 'fs-extra'; import promiseRetry from 'promise-retry'; +import type { GlobalPrefsJson } from '../loot-core/src/types/prefs'; + import { getMenu } from './menu'; import { get as getWindowState, @@ -57,9 +59,36 @@ let serverProcess: UtilityProcess | null; let oAuthServer: ReturnType | null; +let queuedClientWinLogs: string[] = []; // logs that are queued up until the client window is ready + +const logMessage = (loglevel: 'info' | 'error', message: string) => { + // Electron main process logs + switch (loglevel) { + case 'info': + console.info(message); + break; + case 'error': + console.error(message); + break; + } + + if (!clientWin) { + // queue up the logs until the client window is ready + queuedClientWinLogs.push( + // eslint-disable-next-line rulesdir/typography + `console.${loglevel}('Actual Sync Server Log:', ${JSON.stringify(message)})`, + ); + } else { + // Send the queued up logs to the devtools console + clientWin.webContents.executeJavaScript( + `console.${loglevel}('Actual Sync Server Log:', ${JSON.stringify(message)})`, + ); + } +}; + const createOAuthServer = async () => { const port = 3010; - console.log(`OAuth server running on port: ${port}`); + logMessage('info', `OAuth server running on port: ${port}`); if (oAuthServer) { return { url: `http://localhost:${port}`, server: oAuthServer }; @@ -101,7 +130,7 @@ if (isDev) { } async function loadGlobalPrefs() { - let state: { [key: string]: unknown } | undefined = undefined; + let state: GlobalPrefsJson | undefined = undefined; try { state = JSON.parse( fs.readFileSync( @@ -110,7 +139,7 @@ async function loadGlobalPrefs() { ), ); } catch (e) { - console.info('Could not load global state - using defaults'); // This could be the first time running the app - no global-store.json + logMessage('info', 'Could not load global state - using defaults'); state = {}; } @@ -146,13 +175,13 @@ async function createBackgroundProcess() { ); serverProcess.stdout?.on('data', (chunk: Buffer) => { - // Send the Server console.log messages to the main browser window + // Send the Server log messages to the main browser window clientWin?.webContents.executeJavaScript(` console.info('Server Log:', ${JSON.stringify(chunk.toString('utf8'))})`); }); serverProcess.stderr?.on('data', (chunk: Buffer) => { - // Send the Server console.error messages out to the main browser window + // Send the Server log messages out to the main browser window clientWin?.webContents.executeJavaScript(` console.error('Server Log:', ${JSON.stringify(chunk.toString('utf8'))})`); }); @@ -170,7 +199,7 @@ async function createBackgroundProcess() { } break; default: - console.log('Unknown server message: ' + msg.type); + logMessage('info', 'Unknown server message: ' + msg.type); } }); } @@ -219,8 +248,9 @@ async function createWindow() { }); win.on('unresponsive', () => { - console.log( - 'browser window went unresponsive (maybe because of a modal though)', + logMessage( + 'info', + 'browser window went unresponsive (maybe because of a modal)', ); }); @@ -262,6 +292,13 @@ async function createWindow() { } clientWin = win; + + // Execute queued logs - displaying them in the client window + queuedClientWinLogs.map((log: string) => + win.webContents.executeJavaScript(log), + ); + + queuedClientWinLogs = []; } function isExternalUrl(url: string) { @@ -357,10 +394,10 @@ app.on('ready', async () => { // This is mainly to aid debugging Sentry errors - it will add a // breadcrumb powerMonitor.on('suspend', () => { - console.log('Suspending', new Date()); + logMessage('info', 'Suspending: ' + new Date()); }); - createBackgroundProcess(); + await createBackgroundProcess(); }); app.on('window-all-closed', () => { @@ -520,7 +557,10 @@ ipcMain.handle( }); await remove(currentBudgetDirectory); } catch (error) { - console.error('There was an error moving your directory', error); + logMessage( + 'error', + `There was an error moving your directory: ${error}`, + ); throw error; } }, diff --git a/packages/eslint-plugin-actual/package.json b/packages/eslint-plugin-actual/package.json index 44f374609e8..922dd892ce3 100644 --- a/packages/eslint-plugin-actual/package.json +++ b/packages/eslint-plugin-actual/package.json @@ -14,7 +14,7 @@ "eslint-plugin-eslint-plugin": "^5.5.1", "eslint-plugin-node": "^11.1.0", "eslint-vitest-rule-tester": "^0.7.1", - "vitest": "^1.6.0" + "vitest": "^1.6.1" }, "peerDependencies": { "eslint": ">=7" diff --git a/packages/loot-core/migrations/1736640000000__custom_report_sorting.sql b/packages/loot-core/migrations/1736640000000_custom_report_sorting.sql similarity index 100% rename from packages/loot-core/migrations/1736640000000__custom_report_sorting.sql rename to packages/loot-core/migrations/1736640000000_custom_report_sorting.sql diff --git a/packages/loot-core/migrations/1738491452000__sorting_rename.sql b/packages/loot-core/migrations/1738491452000_sorting_rename.sql similarity index 100% rename from packages/loot-core/migrations/1738491452000__sorting_rename.sql rename to packages/loot-core/migrations/1738491452000_sorting_rename.sql diff --git a/packages/loot-core/package.json b/packages/loot-core/package.json index bd09d1c25da..a4aa251e5d0 100644 --- a/packages/loot-core/package.json +++ b/packages/loot-core/package.json @@ -30,7 +30,7 @@ "date-fns": "^2.30.0", "deep-equal": "^2.2.3", "handlebars": "^4.7.8", - "lru-cache": "^5.1.1", + "lru-cache": "^11.0.2", "md5": "^2.3.0", "memoize-one": "^6.0.0", "mitt": "^3.0.1", diff --git a/packages/loot-core/src/client/SpreadsheetProvider.tsx b/packages/loot-core/src/client/SpreadsheetProvider.tsx index 6f8cfafbd78..7913acae576 100644 --- a/packages/loot-core/src/client/SpreadsheetProvider.tsx +++ b/packages/loot-core/src/client/SpreadsheetProvider.tsx @@ -1,32 +1,57 @@ -// @ts-strict-ignore -import React, { createContext, useEffect, useMemo, useContext } from 'react'; +import { + createContext, + useEffect, + useMemo, + useContext, + type ReactNode, +} from 'react'; -import LRU from 'lru-cache'; +import { LRUCache } from 'lru-cache'; import { listen, send } from '../platform/client/fetch'; +import { type Node } from '../server/spreadsheet/spreadsheet'; +import { type Query } from '../shared/query'; type SpreadsheetContextValue = ReturnType; -const SpreadsheetContext = createContext(undefined); +const SpreadsheetContext = createContext( + undefined, +); export function useSpreadsheet() { - return useContext(SpreadsheetContext); + const context = useContext(SpreadsheetContext); + if (!context) { + throw new Error('useSpreadsheet must be used within a SpreadsheetProvider'); + } + return context; } +// TODO: Make this generic and replace the Binding type in the desktop-client package. +type Binding = + | string + | { name: string; value?: unknown | null; query?: Query | undefined }; + +type CellCacheValue = { name: string; value: Node['value'] | null }; +type CellCache = { [name: string]: Promise | null }; +type CellObserverCallback = (node: CellCacheValue) => void; +type CellObservers = { [name: string]: CellObserverCallback[] }; + +const GLOBAL_SHEET_NAME = '__global'; + function makeSpreadsheet() { - const cellObservers = {}; - const LRUValueCache = new LRU({ max: 1200 }); - const cellCache = {}; + const cellObservers: CellObservers = {}; + const LRUValueCache = new LRUCache({ max: 1200 }); + const cellCache: CellCache = {}; let observersDisabled = false; class Spreadsheet { - observeCell(name, cb) { + observeCell(name: string, callback: CellObserverCallback): () => void { if (!cellObservers[name]) { cellObservers[name] = []; } - cellObservers[name].push(cb); + cellObservers[name].push(callback); return () => { - cellObservers[name] = cellObservers[name].filter(x => x !== cb); + cellObservers[name] = cellObservers[name].filter(cb => cb !== callback); if (cellObservers[name].length === 0) { cellCache[name] = null; @@ -34,23 +59,23 @@ function makeSpreadsheet() { }; } - disableObservers() { + disableObservers(): void { observersDisabled = true; } - enableObservers() { + enableObservers(): void { observersDisabled = false; } - prewarmCache(name, value) { + prewarmCache(name: string, value: CellCacheValue): void { LRUValueCache.set(name, value); } - listen() { - return listen('cells-changed', function (nodes) { + listen(): () => void { + return listen('cells-changed', event => { if (!observersDisabled) { // TODO: batch react so only renders once - nodes.forEach(node => { + event.forEach(node => { const observers = cellObservers[node.name]; if (observers) { observers.forEach(func => func(node)); @@ -62,7 +87,11 @@ function makeSpreadsheet() { }); } - bind(sheetName = '__global', binding, callback) { + bind( + sheetName: string = GLOBAL_SHEET_NAME, + binding: Binding, + callback: CellObserverCallback, + ): () => void { binding = typeof binding === 'string' ? { name: binding, value: null } : binding; @@ -77,7 +106,10 @@ function makeSpreadsheet() { // This is a display optimization to avoid flicker. The LRU cache // will keep a number of recent nodes in memory. if (LRUValueCache.has(resolvedName)) { - callback(LRUValueCache.get(resolvedName)); + const node = LRUValueCache.get(resolvedName); + if (node) { + callback(node); + } } if (cellCache[resolvedName] != null) { @@ -102,15 +134,15 @@ function makeSpreadsheet() { return cleanup; } - get(sheetName, name) { + get(sheetName: string, name: string) { return send('getCell', { sheetName, name }); } - getCellNames(sheetName) { + getCellNames(sheetName: string) { return send('getCellNamesInSheet', { sheetName }); } - createQuery(sheetName, name, query) { + createQuery(sheetName: string, name: string, query: Query) { return send('create-query', { sheetName, name, @@ -122,7 +154,11 @@ function makeSpreadsheet() { return new Spreadsheet(); } -export function SpreadsheetProvider({ children }) { +type SpreadsheetProviderProps = { + children: ReactNode; +}; + +export function SpreadsheetProvider({ children }: SpreadsheetProviderProps) { const spreadsheet = useMemo(() => makeSpreadsheet(), []); useEffect(() => { diff --git a/packages/loot-core/src/client/accounts/accountsSlice.ts b/packages/loot-core/src/client/accounts/accountsSlice.ts index f1ff845f4fd..0f032115b14 100644 --- a/packages/loot-core/src/client/accounts/accountsSlice.ts +++ b/packages/loot-core/src/client/accounts/accountsSlice.ts @@ -1,7 +1,13 @@ import { createSlice, type PayloadAction } from '@reduxjs/toolkit'; import { send } from '../../platform/client/fetch'; -import { type AccountEntity, type TransactionEntity } from '../../types/models'; +import { type SyncResponseWithErrors } from '../../server/accounts/app'; +import { + type SyncServerGoCardlessAccount, + type AccountEntity, + type TransactionEntity, + type SyncServerSimpleFinAccount, +} from '../../types/models'; import { addNotification } from '../actions'; import { getAccounts, @@ -80,9 +86,9 @@ export const unlinkAccount = createAppAsyncThunk( type LinkAccountPayload = { requisitionId: string; - account: unknown; - upgradingId?: AccountEntity['id']; - offBudget?: boolean; + account: SyncServerGoCardlessAccount; + upgradingId?: AccountEntity['id'] | undefined; + offBudget?: boolean | undefined; }; export const linkAccount = createAppAsyncThunk( @@ -103,9 +109,9 @@ export const linkAccount = createAppAsyncThunk( ); type LinkAccountSimpleFinPayload = { - externalAccount: unknown; - upgradingId?: AccountEntity['id']; - offBudget?: boolean; + externalAccount: SyncServerSimpleFinAccount; + upgradingId?: AccountEntity['id'] | undefined; + offBudget?: boolean | undefined; }; export const linkAccountSimpleFin = createAppAsyncThunk( @@ -124,22 +130,9 @@ export const linkAccountSimpleFin = createAppAsyncThunk( }, ); -type SyncResponse = { - errors: Array<{ - type: string; - category: string; - code: string; - message: string; - internal?: string; - }>; - newTransactions: Array; - matchedTransactions: Array; - updatedAccounts: Array; -}; - function handleSyncResponse( accountId: AccountEntity['id'], - res: SyncResponse, + res: SyncResponseWithErrors, dispatch: AppDispatch, resNewTransactions: Array, resMatchedTransactions: Array, @@ -153,7 +146,7 @@ function handleSyncResponse( if (error) { // We only want to mark the account as having problem if it // was a real syncing error. - if (error.type === 'SyncError') { + if ('type' in error && error.type === 'SyncError') { dispatch( markAccountFailed({ id: accountId, @@ -168,7 +161,7 @@ function handleSyncResponse( // Dispatch errors (if any) errors.forEach(error => { - if (error.type === 'SyncError') { + if ('type' in error && error.type === 'SyncError') { dispatch( addNotification({ type: 'error', @@ -180,7 +173,7 @@ function handleSyncResponse( addNotification({ type: 'error', message: error.message, - internal: error.internal, + internal: 'internal' in error ? error.internal : undefined, }), ); } diff --git a/packages/loot-core/src/client/data-hooks/schedules.tsx b/packages/loot-core/src/client/data-hooks/schedules.tsx index 372c96dc540..c122b818cdb 100644 --- a/packages/loot-core/src/client/data-hooks/schedules.tsx +++ b/packages/loot-core/src/client/data-hooks/schedules.tsx @@ -84,6 +84,7 @@ export function useSchedules({ setError(undefined); if (!query) { + console.error('No query provided to useSchedules'); return; } diff --git a/packages/loot-core/src/client/data-hooks/transactions.ts b/packages/loot-core/src/client/data-hooks/transactions.ts index 4711766c197..85386db6443 100644 --- a/packages/loot-core/src/client/data-hooks/transactions.ts +++ b/packages/loot-core/src/client/data-hooks/transactions.ts @@ -26,6 +26,10 @@ import { type PagedQuery, pagedQuery } from '../query-helpers'; import { type ScheduleStatuses, useCachedSchedules } from './schedules'; type UseTransactionsProps = { + /** + * The Query class is immutable so it is important to memoize the query object + * to prevent unnecessary re-renders i.e. `useMemo`, `useState`, etc. + */ query?: Query; options?: { pageCount?: number; 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 da866e12a7a..7fb3c44fdad 100644 --- a/packages/loot-core/src/client/state-types/modals.d.ts +++ b/packages/loot-core/src/client/state-types/modals.d.ts @@ -347,6 +347,7 @@ type FinanceModals = { }; 'keyboard-shortcuts': EmptyObject; 'goal-templates': EmptyObject; + 'category-automations-edit': EmptyObject; }; export type PushModalAction = { diff --git a/packages/loot-core/src/mocks/setup.ts b/packages/loot-core/src/mocks/setup.ts index 91834cde9df..64ea562bce8 100644 --- a/packages/loot-core/src/mocks/setup.ts +++ b/packages/loot-core/src/mocks/setup.ts @@ -3,7 +3,6 @@ import * as nativeFs from 'fs'; import * as fetchClient from '../platform/client/fetch'; import * as sqlite from '../platform/server/sqlite'; -import * as rules from '../server/accounts/transaction-rules'; import * as db from '../server/db'; import { enableGlobalMutations, @@ -12,6 +11,7 @@ import { import { setServer } from '../server/server-config'; import * as sheet from '../server/sheet'; import { setSyncingMode } from '../server/sync'; +import * as rules from '../server/transactions/transaction-rules'; import { updateVersion } from '../server/update'; import { resetTracer, tracer } from '../shared/test-helpers'; diff --git a/packages/loot-core/src/platform/client/fetch/index.browser.ts b/packages/loot-core/src/platform/client/fetch/index.browser.ts index 0a7f0d0569a..defc478021d 100644 --- a/packages/loot-core/src/platform/client/fetch/index.browser.ts +++ b/packages/loot-core/src/platform/client/fetch/index.browser.ts @@ -151,10 +151,9 @@ export const init: T.Init = async function (worker) { }; export const send: T.Send = function ( - name, - args, - { catchErrors = false } = {}, -) { + ...params: Parameters +): ReturnType { + const [name, args, { catchErrors = false } = {}] = params; return new Promise((resolve, reject) => { const id = uuidv4(); @@ -171,8 +170,7 @@ export const send: T.Send = function ( } else { globalWorker.postMessage(message); } - // eslint-disable-next-line @typescript-eslint/no-explicit-any - }) as any; + }); }; export const sendCatch: T.SendCatch = function (name, args) { diff --git a/packages/loot-core/src/platform/client/fetch/index.d.ts b/packages/loot-core/src/platform/client/fetch/index.d.ts index d6e4d4a31cd..dcb660ea902 100644 --- a/packages/loot-core/src/platform/client/fetch/index.d.ts +++ b/packages/loot-core/src/platform/client/fetch/index.d.ts @@ -7,25 +7,31 @@ export type Init = typeof init; export function send( name: K, - args?: Parameters[0], - options?: { catchErrors: true }, -): ReturnType< - | { data: Handlers[K] } - | { error: { type: 'APIError' | 'InternalError'; message: string } } + args: Parameters[0], + options: { catchErrors: true }, +): Promise< + | { data: Awaited>; error: undefined } + | { + data: undefined; + error: { type: 'APIError' | 'InternalError'; message: string }; + } >; export function send( name: K, args?: Parameters[0], options?: { catchErrors?: boolean }, -): ReturnType; +): Promise>>; export type Send = typeof send; export function sendCatch( name: K, args?: Parameters[0], -): ReturnType< - | { data: Handlers[K] } - | { error: { type: 'APIError' | 'InternalError'; message: string } } +): Promise< + | { data: Awaited>; error: undefined } + | { + data: undefined; + error: { type: 'APIError' | 'InternalError'; message: string }; + } >; export type SendCatch = typeof sendCatch; diff --git a/packages/loot-core/src/platform/client/fetch/index.web.ts b/packages/loot-core/src/platform/client/fetch/index.web.ts index c74ce488a6f..b28c1d680a6 100644 --- a/packages/loot-core/src/platform/client/fetch/index.web.ts +++ b/packages/loot-core/src/platform/client/fetch/index.web.ts @@ -79,10 +79,9 @@ export const init: T.Init = async function () { }; export const send: T.Send = function ( - name, - args, - { catchErrors = false } = {}, -) { + ...params: Parameters +): ReturnType { + const [name, args, { catchErrors = false } = {}] = params; return new Promise((resolve, reject) => { const id = uuidv4(); replyHandlers.set(id, { resolve, reject }); diff --git a/packages/loot-core/src/platform/server/asyncStorage/index.d.ts b/packages/loot-core/src/platform/server/asyncStorage/index.d.ts index 4a189f3a86e..88f7f415de6 100644 --- a/packages/loot-core/src/platform/server/asyncStorage/index.d.ts +++ b/packages/loot-core/src/platform/server/asyncStorage/index.d.ts @@ -1,20 +1,34 @@ +import { GlobalPrefs, GlobalPrefsJson } from '../../../types/prefs'; + export function init(opts?: { persist?: boolean }): void; export type Init = typeof init; -export function getItem(key: string): Promise; +type InferType = GlobalPrefs[K]; + +export function getItem( + key: K, +): Promise; export type GetItem = typeof getItem; -export function setItem(key: string, value: unknown): void; +export function setItem( + key: K, + value: GlobalPrefsJson[K], +): void; export type SetItem = typeof setItem; -export function removeItem(key: string): void; +export function removeItem(key: keyof GlobalPrefsJson): void; export type RemoveItem = typeof removeItem; -export function multiGet(keys: string[]): Promise<[string, string][]>; +export async function multiGet( + keys: K, +): Promise<{ [P in keyof K]: [K[P], GlobalPrefsJson[K[P]]] }>; export type MultiGet = typeof multiGet; -export function multiSet(keyValues: [string, unknown][]): void; +export function multiSet( + keyValues: Array<[K, GlobalPrefsJson[K]]>, +): void; + export type MultiSet = typeof multiSet; -export function multiRemove(keys: string[]): void; +export function multiRemove(keys: (keyof GlobalPrefsJson)[]): void; export type MultiRemove = typeof multiRemove; diff --git a/packages/loot-core/src/platform/server/asyncStorage/index.electron.ts b/packages/loot-core/src/platform/server/asyncStorage/index.electron.ts index bca8ddf73b5..d0cd503afff 100644 --- a/packages/loot-core/src/platform/server/asyncStorage/index.electron.ts +++ b/packages/loot-core/src/platform/server/asyncStorage/index.electron.ts @@ -2,12 +2,13 @@ import * as fs from 'fs'; import { join } from 'path'; +import { GlobalPrefsJson } from '../../../types/prefs'; import * as lootFs from '../fs'; import * as T from '.'; const getStorePath = () => join(lootFs.getDataDir(), 'global-store.json'); -let store; +let store: GlobalPrefsJson; let persisted = true; export const init: T.Init = function ({ persist = true } = {}) { @@ -55,15 +56,17 @@ export const removeItem: T.RemoveItem = function (key) { return _saveStore(); }; -export const multiGet: T.MultiGet = function (keys) { +export async function multiGet( + keys: K, +) { return new Promise(function (resolve) { return resolve( keys.map(function (key) { return [key, store[key]]; - }), + }) as { [P in keyof K]: [K[P], GlobalPrefsJson[K[P]]] }, ); }); -}; +} export const multiSet: T.MultiSet = function (keyValues) { keyValues.forEach(function ([key, value]) { diff --git a/packages/loot-core/src/platform/server/asyncStorage/index.testing.ts b/packages/loot-core/src/platform/server/asyncStorage/index.testing.ts index 5f96009b4d0..4de836e2040 100644 --- a/packages/loot-core/src/platform/server/asyncStorage/index.testing.ts +++ b/packages/loot-core/src/platform/server/asyncStorage/index.testing.ts @@ -1,7 +1,9 @@ // @ts-strict-ignore +import { GlobalPrefsJson } from '../../../types/prefs'; + import * as T from '.'; -const store = {}; +const store: GlobalPrefsJson = {}; export const init: T.Init = function () {}; @@ -19,15 +21,17 @@ export const removeItem: T.RemoveItem = function (key) { delete store[key]; }; -export const multiGet: T.MultiGet = function (keys) { +export async function multiGet( + keys: K, +) { return new Promise(function (resolve) { return resolve( keys.map(function (key) { return [key, store[key]]; - }), + }) as { [P in keyof K]: [K[P], GlobalPrefsJson[K[P]]] }, ); }); -}; +} export const multiSet: T.MultiSet = function (keyValues) { keyValues.forEach(function ([key, value]) { diff --git a/packages/loot-core/src/platform/server/asyncStorage/index.web.ts b/packages/loot-core/src/platform/server/asyncStorage/index.web.ts index c9bc80c7f7a..f09442e5cd8 100644 --- a/packages/loot-core/src/platform/server/asyncStorage/index.web.ts +++ b/packages/loot-core/src/platform/server/asyncStorage/index.web.ts @@ -1,4 +1,5 @@ // @ts-strict-ignore +import { GlobalPrefsJson } from '../../../types/prefs'; import { getDatabase } from '../indexeddb'; import * as T from '.'; @@ -53,7 +54,9 @@ export const removeItem: T.RemoveItem = async function (key) { }); }; -export const multiGet: T.MultiGet = async function (keys) { +export async function multiGet( + keys: K, +) { const db = await getDatabase(); const transaction = db.transaction(['asyncStorage'], 'readonly'); @@ -71,7 +74,7 @@ export const multiGet: T.MultiGet = async function (keys) { commit(transaction); return promise; -}; +} export const multiSet: T.MultiSet = async function (keyValues) { const db = await getDatabase(); diff --git a/packages/loot-core/src/platform/server/connection/index.web.ts b/packages/loot-core/src/platform/server/connection/index.web.ts index 6a17684c0e5..c34766d53a8 100644 --- a/packages/loot-core/src/platform/server/connection/index.web.ts +++ b/packages/loot-core/src/platform/server/connection/index.web.ts @@ -46,6 +46,7 @@ export const init: T.Init = function (serverChn, handlers) { if (msg.name === 'client-connected-to-backend') { // the client is indicating that it is connected to this backend. Stop attempting to connect + console.info('Backend: Client connected'); clearInterval(reconnectToClientInterval); return; } @@ -109,6 +110,7 @@ export const init: T.Init = function (serverChn, handlers) { let reconnectAttempts = 0; const reconnectToClientInterval = setInterval(() => { + console.info('Backend: Atempting to connect to client'); serverChannel.postMessage({ type: 'connect' }); reconnectAttempts++; if (reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) { diff --git a/packages/loot-core/src/platform/server/sqlite/unicodeLike.ts b/packages/loot-core/src/platform/server/sqlite/unicodeLike.ts index 33e9365fd9a..7e60269304f 100644 --- a/packages/loot-core/src/platform/server/sqlite/unicodeLike.ts +++ b/packages/loot-core/src/platform/server/sqlite/unicodeLike.ts @@ -1,7 +1,6 @@ -// @ts-strict-ignore -import LRU from 'lru-cache'; +import { LRUCache } from 'lru-cache'; -const likePatternCache = new LRU({ max: 500 }); +const likePatternCache = new LRUCache({ max: 500 }); export function unicodeLike( pattern: string | null, diff --git a/packages/loot-core/src/server/accounts/app.ts b/packages/loot-core/src/server/accounts/app.ts new file mode 100644 index 00000000000..c9a8ca3c377 --- /dev/null +++ b/packages/loot-core/src/server/accounts/app.ts @@ -0,0 +1,1038 @@ +import { v4 as uuidv4 } from 'uuid'; + +import { captureException } from '../../platform/exceptions'; +import * as asyncStorage from '../../platform/server/asyncStorage'; +import * as connection from '../../platform/server/connection'; +import { isNonProductionEnvironment } from '../../shared/environment'; +import { dayFromDate } from '../../shared/months'; +import * as monthUtils from '../../shared/months'; +import { amountToInteger } from '../../shared/util'; +import { + AccountEntity, + CategoryEntity, + SyncServerGoCardlessAccount, + PayeeEntity, + TransactionEntity, + SyncServerSimpleFinAccount, +} from '../../types/models'; +import { BankEntity } from '../../types/models/bank'; +import { createApp } from '../app'; +import * as db from '../db'; +import { + APIError, + BankSyncError, + PostError, + TransactionError, +} from '../errors'; +import { app as mainApp } from '../main-app'; +import { mutator } from '../mutators'; +import { get, post } from '../post'; +import { getServer } from '../server-config'; +import { batchMessages } from '../sync'; +import { undoable, withUndo } from '../undo'; + +import * as link from './link'; +import { getStartingBalancePayee } from './payees'; +import * as bankSync from './sync'; + +export type AccountHandlers = { + 'account-update': typeof updateAccount; + 'accounts-get': typeof getAccounts; + 'account-balance': typeof getAccountBalance; + 'account-properties': typeof getAccountProperties; + 'gocardless-accounts-link': typeof linkGoCardlessAccount; + 'simplefin-accounts-link': typeof linkSimpleFinAccount; + 'account-create': typeof createAccount; + 'account-close': typeof closeAccount; + 'account-reopen': typeof reopenAccount; + 'account-move': typeof moveAccount; + 'secret-set': typeof setSecret; + 'secret-check': typeof checkSecret; + 'gocardless-poll-web-token': typeof pollGoCardlessWebToken; + 'gocardless-poll-web-token-stop': typeof stopGoCardlessWebTokenPolling; + 'gocardless-status': typeof goCardlessStatus; + 'simplefin-status': typeof simpleFinStatus; + 'simplefin-accounts': typeof simpleFinAccounts; + 'gocardless-get-banks': typeof getGoCardlessBanks; + 'gocardless-create-web-token': typeof createGoCardlessWebToken; + 'accounts-bank-sync': typeof accountsBankSync; + 'simplefin-batch-sync': typeof simpleFinBatchSync; + 'transactions-import': typeof importTransactions; + 'account-unlink': typeof unlinkAccount; +}; + +async function updateAccount({ id, name }: Pick) { + await db.update('accounts', { id, name }); + return {}; +} + +async function getAccounts() { + return db.getAccounts(); +} + +async function getAccountBalance({ + id, + cutoff, +}: { + id: string; + cutoff: string | Date; +}) { + const { balance }: { balance: number } = await db.first( + 'SELECT sum(amount) as balance FROM transactions WHERE acct = ? AND isParent = 0 AND tombstone = 0 AND date <= ?', + [id, db.toDateRepr(dayFromDate(cutoff))], + ); + return balance ? balance : 0; +} + +async function getAccountProperties({ id }: { id: AccountEntity['id'] }) { + const { balance }: { balance: number } = await db.first( + 'SELECT sum(amount) as balance FROM transactions WHERE acct = ? AND isParent = 0 AND tombstone = 0', + [id], + ); + const { count }: { count: number } = await db.first( + 'SELECT count(id) as count FROM transactions WHERE acct = ? AND tombstone = 0', + [id], + ); + + return { balance: balance || 0, numTransactions: count }; +} + +async function linkGoCardlessAccount({ + requisitionId, + account, + upgradingId, + offBudget = false, +}: { + requisitionId: string; + account: SyncServerGoCardlessAccount; + upgradingId?: AccountEntity['id'] | undefined; + offBudget?: boolean | undefined; +}) { + let id; + const bank = await link.findOrCreateBank(account.institution, requisitionId); + + if (upgradingId) { + const accRow: AccountEntity = await db.first( + 'SELECT * FROM accounts WHERE id = ?', + [upgradingId], + ); + id = accRow.id; + await db.update('accounts', { + id, + account_id: account.account_id, + bank: bank.id, + account_sync_source: 'goCardless', + }); + } else { + id = uuidv4(); + await db.insertWithUUID('accounts', { + id, + account_id: account.account_id, + mask: account.mask, + name: account.name, + official_name: account.official_name, + bank: bank.id, + offbudget: offBudget ? 1 : 0, + account_sync_source: 'goCardless', + }); + await db.insertPayee({ + name: '', + transfer_acct: id, + }); + } + + await bankSync.syncAccount( + undefined, + undefined, + id, + account.account_id, + bank.bank_id, + ); + + connection.send('sync-event', { + type: 'success', + tables: ['transactions'], + }); + + return 'ok'; +} + +async function linkSimpleFinAccount({ + externalAccount, + upgradingId, + offBudget = false, +}: { + externalAccount: SyncServerSimpleFinAccount; + upgradingId?: AccountEntity['id'] | undefined; + offBudget?: boolean | undefined; +}) { + let id; + + const institution = { + name: externalAccount.institution ?? 'Unknown', + }; + + const bank = await link.findOrCreateBank( + institution, + externalAccount.orgDomain ?? externalAccount.orgId, + ); + + if (upgradingId) { + const accRow: AccountEntity = await db.first( + 'SELECT * FROM accounts WHERE id = ?', + [upgradingId], + ); + id = accRow.id; + await db.update('accounts', { + id, + account_id: externalAccount.account_id, + bank: bank.id, + account_sync_source: 'simpleFin', + }); + } else { + id = uuidv4(); + await db.insertWithUUID('accounts', { + id, + account_id: externalAccount.account_id, + name: externalAccount.name, + official_name: externalAccount.name, + bank: bank.id, + offbudget: offBudget ? 1 : 0, + account_sync_source: 'simpleFin', + }); + await db.insertPayee({ + name: '', + transfer_acct: id, + }); + } + + await bankSync.syncAccount( + undefined, + undefined, + id, + externalAccount.account_id, + bank.bank_id, + ); + + await connection.send('sync-event', { + type: 'success', + tables: ['transactions'], + }); + + return 'ok'; +} + +async function createAccount({ + name, + balance = 0, + offBudget = false, + closed = false, +}: { + name: string; + balance?: number | undefined; + offBudget?: boolean | undefined; + closed?: boolean | undefined; +}) { + const id: AccountEntity['id'] = await db.insertAccount({ + name, + offbudget: offBudget ? 1 : 0, + closed: closed ? 1 : 0, + }); + + await db.insertPayee({ + name: '', + transfer_acct: id, + }); + + if (balance != null && balance !== 0) { + const payee = await getStartingBalancePayee(); + + await db.insertTransaction({ + account: id, + amount: amountToInteger(balance), + category: offBudget ? null : payee.category, + payee: payee.id, + date: monthUtils.currentDay(), + cleared: true, + starting_balance_flag: true, + }); + } + + return id; +} + +async function closeAccount({ + id, + transferAccountId, + categoryId, + forced = false, +}: { + id: AccountEntity['id']; + transferAccountId?: AccountEntity['id'] | undefined; + categoryId?: CategoryEntity['id'] | undefined; + forced?: boolean | undefined; +}) { + // Unlink the account if it's linked. This makes sure to remove it from + // bank-sync providers. (This should not be undo-able, as it mutates the + // remote server and the user will have to link the account again) + await unlinkAccount({ id }); + + return withUndo(async () => { + const account: AccountEntity = await db.first( + 'SELECT * FROM accounts WHERE id = ? AND tombstone = 0', + [id], + ); + + // Do nothing if the account doesn't exist or it's already been + // closed + if (!account || account.closed === 1) { + return; + } + + const { balance, numTransactions } = await getAccountProperties({ id }); + + // If there are no transactions, we can simply delete the account + if (numTransactions === 0) { + await db.deleteAccount({ id }); + } else if (forced) { + const rows = await db.runQuery< + Pick + >( + 'SELECT id, transfer_id FROM v_transactions WHERE account = ?', + [id], + true, + ); + + const { id: payeeId }: Pick = await db.first( + 'SELECT id FROM payees WHERE transfer_acct = ?', + [id], + ); + + await batchMessages(async () => { + // TODO: what this should really do is send a special message that + // automatically marks the tombstone value for all transactions + // within an account... or something? This is problematic + // because another client could easily add new data that + // should be marked as deleted. + + rows.forEach(row => { + if (row.transfer_id) { + db.updateTransaction({ + id: row.transfer_id, + payee: null, + transfer_id: null, + }); + } + + db.deleteTransaction({ id: row.id }); + }); + + db.deleteAccount({ id }); + db.deleteTransferPayee({ id: payeeId }); + }); + } else { + if (balance !== 0 && transferAccountId == null) { + throw APIError('balance is non-zero: transferAccountId is required'); + } + + await db.update('accounts', { id, closed: 1 }); + + // If there is a balance we need to transfer it to the specified + // account (and possibly categorize it) + if (balance !== 0 && transferAccountId) { + const { id: payeeId }: Pick = await db.first( + 'SELECT id FROM payees WHERE transfer_acct = ?', + [transferAccountId], + ); + + await mainApp.handlers['transaction-add']({ + id: uuidv4(), + payee: payeeId, + amount: -balance, + account: id, + date: monthUtils.currentDay(), + notes: 'Closing account', + category: categoryId, + }); + } + } + }); +} + +async function reopenAccount({ id }: { id: AccountEntity['id'] }) { + await db.update('accounts', { id, closed: 0 }); +} + +async function moveAccount({ + id, + targetId, +}: { + id: AccountEntity['id']; + targetId: AccountEntity['id']; +}) { + await db.moveAccount(id, targetId); +} + +async function setSecret({ + name, + value, +}: { + name: string; + value: string | null; +}) { + const userToken = await asyncStorage.getItem('user-token'); + + if (!userToken) { + return { error: 'unauthorized' }; + } + + const serverConfig = getServer(); + if (!serverConfig) { + throw new Error('Failed to get server config.'); + } + + try { + return await post( + serverConfig.BASE_SERVER + '/secret', + { + name, + value, + }, + { + 'X-ACTUAL-TOKEN': userToken, + }, + ); + } catch (error) { + return { + error: 'failed', + reason: error instanceof PostError ? error.reason : undefined, + }; + } +} +async function checkSecret(name: string) { + const userToken = await asyncStorage.getItem('user-token'); + + if (!userToken) { + return { error: 'unauthorized' }; + } + + const serverConfig = getServer(); + if (!serverConfig) { + throw new Error('Failed to get server config.'); + } + + try { + return await get(serverConfig.BASE_SERVER + '/secret/' + name, { + 'X-ACTUAL-TOKEN': userToken, + }); + } catch (error) { + console.error(error); + return { error: 'failed' }; + } +} + +let stopPolling = false; + +async function pollGoCardlessWebToken({ + requisitionId, +}: { + requisitionId: string; +}) { + const userToken = await asyncStorage.getItem('user-token'); + if (!userToken) return { error: 'unknown' }; + + const startTime = Date.now(); + stopPolling = false; + + async function getData( + cb: (error: string | null, data?: unknown | undefined) => void, + ) { + if (stopPolling) { + return; + } + + if (Date.now() - startTime >= 1000 * 60 * 10) { + cb('timeout'); + return; + } + + const serverConfig = getServer(); + if (!serverConfig) { + throw new Error('Failed to get server config.'); + } + + const data = await post( + serverConfig.GOCARDLESS_SERVER + '/get-accounts', + { + requisitionId, + }, + { + 'X-ACTUAL-TOKEN': userToken, + }, + ); + + if (data) { + if (data.error) { + cb('unknown'); + } else { + cb(null, data); + } + } else { + setTimeout(() => getData(cb), 3000); + } + } + + return new Promise(resolve => { + getData((error, data) => { + if (error) { + resolve({ error }); + } else { + resolve({ data }); + } + }); + }); +} + +async function stopGoCardlessWebTokenPolling() { + stopPolling = true; + return 'ok'; +} + +async function goCardlessStatus() { + const userToken = await asyncStorage.getItem('user-token'); + + if (!userToken) { + return { error: 'unauthorized' }; + } + + const serverConfig = getServer(); + if (!serverConfig) { + throw new Error('Failed to get server config.'); + } + + return post( + serverConfig.GOCARDLESS_SERVER + '/status', + {}, + { + 'X-ACTUAL-TOKEN': userToken, + }, + ); +} + +async function simpleFinStatus() { + const userToken = await asyncStorage.getItem('user-token'); + + if (!userToken) { + return { error: 'unauthorized' }; + } + + const serverConfig = getServer(); + if (!serverConfig) { + throw new Error('Failed to get server config.'); + } + + return post( + serverConfig.SIMPLEFIN_SERVER + '/status', + {}, + { + 'X-ACTUAL-TOKEN': userToken, + }, + ); +} + +async function simpleFinAccounts() { + const userToken = await asyncStorage.getItem('user-token'); + + if (!userToken) { + return { error: 'unauthorized' }; + } + + const serverConfig = getServer(); + if (!serverConfig) { + throw new Error('Failed to get server config.'); + } + + try { + return await post( + serverConfig.SIMPLEFIN_SERVER + '/accounts', + {}, + { + 'X-ACTUAL-TOKEN': userToken, + }, + 60000, + ); + } catch (error) { + return { error_code: 'TIMED_OUT' }; + } +} + +async function getGoCardlessBanks(country: string) { + const userToken = await asyncStorage.getItem('user-token'); + + if (!userToken) { + return { error: 'unauthorized' }; + } + + const serverConfig = getServer(); + if (!serverConfig) { + throw new Error('Failed to get server config.'); + } + + return post( + serverConfig.GOCARDLESS_SERVER + '/get-banks', + { country, showDemo: isNonProductionEnvironment() }, + { + 'X-ACTUAL-TOKEN': userToken, + }, + ); +} + +async function createGoCardlessWebToken({ + institutionId, + accessValidForDays, +}: { + institutionId: string; + accessValidForDays: number; +}) { + const userToken = await asyncStorage.getItem('user-token'); + + if (!userToken) { + return { error: 'unauthorized' }; + } + + const serverConfig = getServer(); + if (!serverConfig) { + throw new Error('Failed to get server config.'); + } + + try { + return await post( + serverConfig.GOCARDLESS_SERVER + '/create-web-token', + { + institutionId, + accessValidForDays, + }, + { + 'X-ACTUAL-TOKEN': userToken, + }, + ); + } catch (error) { + console.error(error); + return { error: 'failed' }; + } +} + +type SyncResponse = { + newTransactions: Array; + matchedTransactions: Array; + updatedAccounts: Array; +}; + +function handleSyncResponse( + res: { + added: Array; + updated: Array; + }, + acct: db.DbAccount, +): SyncResponse { + const { added, updated } = res; + const newTransactions: Array = []; + const matchedTransactions: Array = []; + const updatedAccounts: Array = []; + + newTransactions.push(...added); + matchedTransactions.push(...updated); + + if (added.length > 0) { + updatedAccounts.push(acct.id); + } + + return { + newTransactions, + matchedTransactions, + updatedAccounts, + }; +} + +type SyncError = + | { + type: 'SyncError'; + accountId: AccountEntity['id']; + message: string; + category: string; + code: string; + } + | { + accountId: AccountEntity['id']; + message: string; + internal?: string; + }; + +function handleSyncError( + err: Error | PostError | BankSyncError, + acct: db.DbAccount, +): SyncError { + if (err instanceof BankSyncError) { + return { + type: 'SyncError', + accountId: acct.id, + message: 'Failed syncing account “' + acct.name + '.”', + category: err.category, + code: err.code, + }; + } + + if (err instanceof PostError && err.reason !== 'internal') { + return { + accountId: acct.id, + message: err.reason + ? err.reason + : `Account “${acct.name}” is not linked properly. Please link it again.`, + }; + } + + return { + accountId: acct.id, + message: + 'There was an internal error. Please get in touch https://actualbudget.org/contact for support.', + internal: err.stack, + }; +} + +export type SyncResponseWithErrors = SyncResponse & { + errors: SyncError[]; +}; + +async function accountsBankSync({ + ids = [], +}: { + ids: Array; +}): Promise { + const [[, userId], [, userKey]] = await asyncStorage.multiGet([ + 'user-id', + 'user-key', + ]); + + const accounts = await db.runQuery< + db.DbAccount & { bankId: db.DbBank['bank_id'] } + >( + ` + 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 + ${ids.length ? `AND a.id IN (${ids.map(() => '?').join(', ')})` : ''} + ORDER BY a.offbudget, a.sort_order + `, + ids, + true, + ); + + const errors: ReturnType[] = []; + const newTransactions: Array = []; + const matchedTransactions: Array = []; + const updatedAccounts: Array = []; + + for (const acct of accounts) { + if (acct.bankId && acct.account_id) { + try { + console.group('Bank Sync operation for account:', acct.name); + const syncResponse = await bankSync.syncAccount( + userId as string, + userKey as string, + acct.id, + acct.account_id, + acct.bankId, + ); + + const syncResponseData = handleSyncResponse(syncResponse, acct); + + newTransactions.push(...syncResponseData.newTransactions); + matchedTransactions.push(...syncResponseData.matchedTransactions); + updatedAccounts.push(...syncResponseData.updatedAccounts); + } catch (err) { + const error = err as Error; + errors.push(handleSyncError(error, acct)); + captureException({ + ...error, + message: 'Failed syncing account “' + acct.name + '.”', + } as Error); + } finally { + console.groupEnd(); + } + } + } + + if (updatedAccounts.length > 0) { + connection.send('sync-event', { + type: 'success', + tables: ['transactions'], + }); + } + + return { errors, newTransactions, matchedTransactions, updatedAccounts }; +} + +async function simpleFinBatchSync({ + ids = [], +}: { + ids: Array; +}): Promise< + Array<{ accountId: AccountEntity['id']; res: SyncResponseWithErrors }> +> { + const accounts = await db.runQuery< + db.DbAccount & { bankId: db.DbBank['bank_id'] } + >( + `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 + AND a.account_sync_source = 'simpleFin' + ${ids.length ? `AND a.id IN (${ids.map(() => '?').join(', ')})` : ''} + ORDER BY a.offbudget, a.sort_order`, + ids.length ? ids : [], + true, + ); + + const retVal: Array<{ + accountId: AccountEntity['id']; + res: { + errors: ReturnType[]; + newTransactions: Array; + matchedTransactions: Array; + updatedAccounts: Array; + }; + }> = []; + + console.group('Bank Sync operation for all SimpleFin accounts'); + try { + const syncResponses: Array<{ + accountId: AccountEntity['id']; + res: { + error_code: string; + error_type: string; + added: Array; + updated: Array; + }; + }> = await bankSync.simpleFinBatchSync( + accounts.map(a => ({ + id: a.id, + account_id: a.account_id || null, + })), + ); + for (const syncResponse of syncResponses) { + const account = accounts.find(a => a.id === syncResponse.accountId); + if (!account) { + console.error( + `Invalid account ID found in response: ${syncResponse.accountId}. Proceeding to the next account...`, + ); + continue; + } + + const errors: ReturnType[] = []; + const newTransactions: Array = []; + const matchedTransactions: Array = []; + const updatedAccounts: Array = []; + + if (syncResponse.res.error_code) { + errors.push( + handleSyncError( + { + type: 'BankSyncError', + reason: 'Failed syncing account “' + account.name + '.”', + category: syncResponse.res.error_type, + code: syncResponse.res.error_code, + } as BankSyncError, + account, + ), + ); + } else { + const syncResponseData = handleSyncResponse(syncResponse.res, account); + + newTransactions.push(...syncResponseData.newTransactions); + matchedTransactions.push(...syncResponseData.matchedTransactions); + updatedAccounts.push(...syncResponseData.updatedAccounts); + } + + retVal.push({ + accountId: syncResponse.accountId, + res: { errors, newTransactions, matchedTransactions, updatedAccounts }, + }); + } + } catch (err) { + const errors = []; + for (const account of accounts) { + retVal.push({ + accountId: account.id, + res: { + errors, + newTransactions: [], + matchedTransactions: [], + updatedAccounts: [], + }, + }); + const error = err as Error; + errors.push(handleSyncError(error, account)); + } + } + + if (retVal.some(a => a.res.updatedAccounts.length > 0)) { + connection.send('sync-event', { + type: 'success', + tables: ['transactions'], + }); + } + + console.groupEnd(); + + return retVal; +} + +type ImportTransactionsResult = bankSync.ReconcileTransactionsResult & { + errors: Array<{ + message: string; + }>; +}; + +async function importTransactions({ + accountId, + transactions, + isPreview, + opts, +}: { + accountId: AccountEntity['id']; + transactions: TransactionEntity[]; + isPreview: boolean; + opts?: { + defaultCleared: boolean; + }; +}): Promise { + if (typeof accountId !== 'string') { + throw APIError('transactions-import: accountId must be an id'); + } + + try { + const reconciled = await bankSync.reconcileTransactions( + accountId, + transactions, + false, + true, + isPreview, + opts?.defaultCleared, + ); + return { + errors: [], + added: reconciled.added, + updated: reconciled.updated, + updatedPreview: reconciled.updatedPreview, + }; + } catch (err) { + if (err instanceof TransactionError) { + return { + errors: [{ message: err.message }], + added: [], + updated: [], + updatedPreview: [], + }; + } + + throw err; + } +} + +async function unlinkAccount({ id }: { id: AccountEntity['id'] }) { + const { bank: bankId }: Pick = await db.first( + 'SELECT bank FROM accounts WHERE id = ?', + [id], + ); + + if (!bankId) { + return 'ok'; + } + + const accRow: AccountEntity = await db.first( + 'SELECT * FROM accounts WHERE id = ?', + [id], + ); + + const isGoCardless = accRow.account_sync_source === 'goCardless'; + + await db.updateAccount({ + id, + account_id: null, + bank: null, + balance_current: null, + balance_available: null, + balance_limit: null, + account_sync_source: null, + }); + + if (isGoCardless === false) { + return; + } + + const { count }: { count: number } = await db.first( + 'SELECT COUNT(*) as count FROM accounts WHERE bank = ?', + [bankId], + ); + + // No more accounts are associated with this bank. We can remove + // it from GoCardless. + const userToken = await asyncStorage.getItem('user-token'); + if (!userToken) { + return 'ok'; + } + + if (count === 0) { + const { bank_id: requisitionId }: Pick = + await db.first('SELECT bank_id FROM banks WHERE id = ?', [bankId]); + + const serverConfig = getServer(); + if (!serverConfig) { + throw new Error('Failed to get server config.'); + } + + try { + await post( + serverConfig.GOCARDLESS_SERVER + '/remove-account', + { + requisitionId, + }, + { + 'X-ACTUAL-TOKEN': userToken, + }, + ); + } catch (error) { + console.log({ error }); + } + } + + return 'ok'; +} + +export const app = createApp(); + +app.method('account-update', mutator(undoable(updateAccount))); +app.method('accounts-get', getAccounts); +app.method('account-balance', getAccountBalance); +app.method('account-properties', getAccountProperties); +app.method('gocardless-accounts-link', linkGoCardlessAccount); +app.method('simplefin-accounts-link', linkSimpleFinAccount); +app.method('account-create', mutator(undoable(createAccount))); +app.method('account-close', mutator(closeAccount)); +app.method('account-reopen', mutator(undoable(reopenAccount))); +app.method('account-move', mutator(undoable(moveAccount))); +app.method('secret-set', setSecret); +app.method('secret-check', checkSecret); +app.method('gocardless-poll-web-token', pollGoCardlessWebToken); +app.method('gocardless-poll-web-token-stop', stopGoCardlessWebTokenPolling); +app.method('gocardless-status', goCardlessStatus); +app.method('simplefin-status', simpleFinStatus); +app.method('simplefin-accounts', simpleFinAccounts); +app.method('gocardless-get-banks', getGoCardlessBanks); +app.method('gocardless-create-web-token', createGoCardlessWebToken); +app.method('accounts-bank-sync', accountsBankSync); +app.method('simplefin-batch-sync', simpleFinBatchSync); +app.method('transactions-import', mutator(undoable(importTransactions))); +app.method('account-unlink', mutator(unlinkAccount)); diff --git a/packages/loot-core/src/server/accounts/payees.ts b/packages/loot-core/src/server/accounts/payees.ts index 30eba38b5e2..bb8183f4e96 100644 --- a/packages/loot-core/src/server/accounts/payees.ts +++ b/packages/loot-core/src/server/accounts/payees.ts @@ -1,10 +1,11 @@ // @ts-strict-ignore +import { CategoryEntity, PayeeEntity } from '../../types/models'; import * as db from '../db'; export async function createPayee(description) { // Check to make sure no payee already exists with exactly the same // name - const row = await db.first( + const row: Pick = await db.first( `SELECT id FROM payees WHERE UNICODE_LOWER(name) = ? AND tombstone = 0`, [description.toLowerCase()], ); @@ -12,12 +13,12 @@ export async function createPayee(description) { if (row) { return row.id; } else { - return db.insertPayee({ name: description }); + return (await db.insertPayee({ name: description })) as PayeeEntity['id']; } } export async function getStartingBalancePayee() { - let category = await db.first(` + let category: CategoryEntity = await db.first(` SELECT * FROM categories WHERE is_income = 1 AND LOWER(name) = 'starting balances' AND diff --git a/packages/loot-core/src/server/accounts/sync.test.ts b/packages/loot-core/src/server/accounts/sync.test.ts index b4356875c32..f4514ca4e1f 100644 --- a/packages/loot-core/src/server/accounts/sync.test.ts +++ b/packages/loot-core/src/server/accounts/sync.test.ts @@ -4,9 +4,9 @@ import * as db from '../db'; import { loadMappings } from '../db/mappings'; import { post } from '../post'; import { getServer } from '../server-config'; +import { loadRules, insertRule } from '../transactions/transaction-rules'; import { reconcileTransactions, addTransactions } from './sync'; -import { loadRules, insertRule } from './transaction-rules'; jest.mock('../../shared/months', () => ({ ...jest.requireActual('../../shared/months'), diff --git a/packages/loot-core/src/server/accounts/sync.ts b/packages/loot-core/src/server/accounts/sync.ts index ac2c1dc0ccc..dfe4ab4f879 100644 --- a/packages/loot-core/src/server/accounts/sync.ts +++ b/packages/loot-core/src/server/accounts/sync.ts @@ -26,6 +26,8 @@ import { runMutator } from '../mutators'; import { post } from '../post'; import { getServer } from '../server-config'; import { batchMessages } from '../sync'; +import { batchUpdateTransactions } from '../transactions'; +import { runRules } from '../transactions/transaction-rules'; import { defaultMappings, mappingsFromString, @@ -33,8 +35,6 @@ import { import { getStartingBalancePayee } from './payees'; import { title } from './title'; -import { runRules } from './transaction-rules'; -import { batchUpdateTransactions } from './transactions'; function BankSyncError(type: string, code: string, details?: object) { return { type: 'BankSyncError', category: type, code, details }; @@ -423,6 +423,16 @@ async function createNewPayees(payeesToCreate, addsAndUpdates) { }); } +export type ReconcileTransactionsResult = { + added: string[]; + updated: string[]; + updatedPreview: Array<{ + transaction: TransactionEntity; + existing?: TransactionEntity; + ignored?: boolean; + }>; +}; + export async function reconcileTransactions( acctId, transactions, @@ -430,7 +440,7 @@ export async function reconcileTransactions( strictIdChecking = true, isPreview = false, defaultCleared = true, -) { +): Promise { console.log('Performing transaction reconciliation'); const updated = []; @@ -474,9 +484,20 @@ export async function reconcileTransactions( imported_payee: trans.imported_payee || null, notes: existing.notes || trans.notes || null, cleared: trans.cleared ?? existing.cleared, + raw_synced_data: + existing.raw_synced_data ?? trans.raw_synced_data ?? null, }; - if (hasFieldsChanged(existing, updates, Object.keys(updates))) { + const fieldsToMarkUpdated = Object.keys(updates).filter(k => { + // do not mark raw_synced_data if it's gone from falsy to falsy + if (!existing.raw_synced_data && !trans.raw_synced_data) { + return k !== 'raw_synced_data'; + } + + return true; + }); + + if (hasFieldsChanged(existing, updates, fieldsToMarkUpdated)) { updated.push({ id: existing.id, ...updates }); if (!existingPayeeMap.has(existing.payee)) { const payee = await db.getPayee(existing.payee); @@ -846,8 +867,8 @@ async function processBankSyncDownload( } export async function syncAccount( - userId: string, - userKey: string, + userId: string | undefined, + userKey: string | undefined, id: string, acctId: string, bankId: string, @@ -879,25 +900,22 @@ export async function syncAccount( return processBankSyncDownload(download, id, acctRow, newAccount); } -export async function SimpleFinBatchSync( - accounts: { - id: AccountEntity['id']; - accountId: AccountEntity['account_id']; - }[], +export async function simpleFinBatchSync( + accounts: Array>, ) { const startDates = await Promise.all( accounts.map(async a => getAccountSyncStartDate(a.id)), ); const res = await downloadSimpleFinTransactions( - accounts.map(a => a.accountId), + accounts.map(a => a.account_id), startDates, ); const promises = []; for (let i = 0; i < accounts.length; i++) { const account = accounts[i]; - const download = res[account.accountId]; + const download = res[account.account_id]; const acctRow = await db.select('accounts', account.id); const oldestTransaction = await getAccountOldestTransaction(account.id); diff --git a/packages/loot-core/src/server/api.ts b/packages/loot-core/src/server/api.ts index 6a39dd5b5e1..63e1ec7c14e 100644 --- a/packages/loot-core/src/server/api.ts +++ b/packages/loot-core/src/server/api.ts @@ -461,12 +461,14 @@ handlers['api/transactions-export'] = async function ({ transactions, categoryGroups, payees, + accounts, }) { checkFileOpen(); return handlers['transactions-export']({ transactions, categoryGroups, payees, + accounts, }); }; diff --git a/packages/loot-core/src/server/budget/base.ts b/packages/loot-core/src/server/budget/base.ts index bbb868ad0ed..f834ac02529 100644 --- a/packages/loot-core/src/server/budget/base.ts +++ b/packages/loot-core/src/server/budget/base.ts @@ -40,7 +40,7 @@ function createCategory(cat, sheetName, prevSheetName, start, end) { initialValue: 0, run: () => { // Making this sync is faster! - const rows = db.runQuery( + const rows = db.runQuery<{ amount: number }>( `SELECT SUM(amount) as amount FROM v_transactions_internal_alive t LEFT JOIN accounts a ON a.id = t.account WHERE t.date >= ${start} AND t.date <= ${end} @@ -71,7 +71,7 @@ function createCategoryGroup(group, sheetName) { function handleAccountChange(months, oldValue, newValue) { if (!oldValue || oldValue.offbudget !== newValue.offbudget) { - const rows = db.runQuery( + const rows = db.runQuery>( ` SELECT DISTINCT(category) as category FROM transactions WHERE acct = ? diff --git a/packages/loot-core/src/server/budget/categoryTemplate.ts b/packages/loot-core/src/server/budget/categoryTemplate.ts index 9dcd5180700..0436b3ba4a4 100644 --- a/packages/loot-core/src/server/budget/categoryTemplate.ts +++ b/packages/loot-core/src/server/budget/categoryTemplate.ts @@ -30,7 +30,12 @@ export class CategoryTemplate { // Class interface // set up the class and check all templates - static async init(templates: Template[], category: CategoryEntity, month) { + static async init( + templates: Template[], + category: CategoryEntity, + month, + budgeted: number, + ) { // get all the needed setup values const lastMonthSheet = monthUtils.sheetForMonth( monthUtils.subMonths(month, 1), @@ -47,6 +52,7 @@ export class CategoryTemplate { if (lastMonthBalance < 0 && !carryover) { fromLastMonth = 0; } else if (category.is_income) { + //for tracking budget fromLastMonth = 0; } else { fromLastMonth = lastMonthBalance; @@ -55,9 +61,23 @@ export class CategoryTemplate { await CategoryTemplate.checkByAndScheduleAndSpend(templates, month); await CategoryTemplate.checkPercentage(templates); // call the private constructor - return new CategoryTemplate(templates, category, month, fromLastMonth); + return new CategoryTemplate( + templates, + category, + month, + fromLastMonth, + budgeted, + ); } + getGoalOnly(): boolean { + // if there is only a goal + return ( + this.templates.length === 0 && + this.remainder.length === 0 && + this.goals.length > 0 + ); + } getPriorities(): number[] { return this.priorities; } @@ -224,16 +244,19 @@ export class CategoryTemplate { private limitAmount = 0; private limitCheck = false; private limitHold = false; + readonly previouslyBudgeted: number = 0; private constructor( templates: Template[], category: CategoryEntity, month: string, fromLastMonth: number, + budgeted: number, ) { this.category = category; this.month = month; this.fromLastMonth = fromLastMonth; + this.previouslyBudgeted = budgeted; // sort the template lines into regular template, goals, and remainder templates if (templates) { templates.forEach(t => { @@ -279,6 +302,7 @@ export class CategoryTemplate { private runGoal() { if (this.goals.length > 0) { + if (this.getGoalOnly()) this.toBudgetAmount = this.previouslyBudgeted; this.isLongGoal = true; this.goalAmount = amountToInteger(this.goals[0].amount); return; diff --git a/packages/loot-core/src/server/budget/goaltemplates.ts b/packages/loot-core/src/server/budget/goaltemplates.ts index 7a4b00a30c1..f137c6190a9 100644 --- a/packages/loot-core/src/server/budget/goaltemplates.ts +++ b/packages/loot-core/src/server/budget/goaltemplates.ts @@ -164,15 +164,22 @@ async function processTemplate( // gather needed priorities // gather remainder weights try { - const obj = await CategoryTemplate.init(templates, category, month); - availBudget += budgeted; + const obj = await CategoryTemplate.init( + templates, + category, + month, + budgeted, + ); + // don't use the funds that are not from templates + if (!obj.getGoalOnly()) { + availBudget += budgeted; + } availBudget += obj.getLimitExcess(); const p = obj.getPriorities(); p.forEach(pr => priorities.push(pr)); remainderWeight += obj.getRemainderWeight(); catObjects.push(obj); } catch (e) { - //console.log(`${categories[i].name}: ${e}`); errors.push(`${categories[i].name}: ${e.message}`); } @@ -183,7 +190,6 @@ async function processTemplate( goal: null, longGoal: null, }); - //await setGoal({ month, category: id, goal: null, long_goal: null }); } } diff --git a/packages/loot-core/src/server/budget/statements.ts b/packages/loot-core/src/server/budget/statements.ts index 43a1af7d940..13e4707a5f7 100644 --- a/packages/loot-core/src/server/budget/statements.ts +++ b/packages/loot-core/src/server/budget/statements.ts @@ -1,5 +1,5 @@ import * as db from '../db'; -import { Schedule } from '../db/types'; +import { DbSchedule } from '../db'; import { GOAL_PREFIX, TEMPLATE_PREFIX } from './template-notes'; @@ -41,7 +41,7 @@ export async function getCategoriesWithTemplateNotes(): Promise< ); } -export async function getActiveSchedules(): Promise { +export async function getActiveSchedules(): Promise { return await db.all( 'SELECT id, rule, active, completed, posts_transaction, tombstone, name from schedules WHERE name NOT NULL AND tombstone = 0', ); diff --git a/packages/loot-core/src/server/budget/template-notes.test.ts b/packages/loot-core/src/server/budget/template-notes.test.ts index 2621b03273d..81709dc63e4 100644 --- a/packages/loot-core/src/server/budget/template-notes.test.ts +++ b/packages/loot-core/src/server/budget/template-notes.test.ts @@ -1,5 +1,4 @@ import * as db from '../db'; -import { Schedule } from '../db/types'; import { CategoryWithTemplateNote, @@ -20,7 +19,7 @@ function mockGetTemplateNotesForCategories( ); } -function mockGetActiveSchedules(schedules: Schedule[]) { +function mockGetActiveSchedules(schedules: db.DbSchedule[]) { (getActiveSchedules as jest.Mock).mockResolvedValue(schedules); } @@ -277,7 +276,7 @@ describe('checkTemplates', () => { ); }); -function mockSchedules(): Schedule[] { +function mockSchedules(): db.DbSchedule[] { return [ { id: 'mock-schedule-1', diff --git a/packages/loot-core/src/server/budget/types/templates.d.ts b/packages/loot-core/src/server/budget/types/templates.d.ts index 73969f1554d..39b36f6e3df 100644 --- a/packages/loot-core/src/server/budget/types/templates.d.ts +++ b/packages/loot-core/src/server/budget/types/templates.d.ts @@ -60,7 +60,7 @@ interface AverageTemplate extends BaseTemplate { } interface GoalTemplate extends BaseTemplate { - type: 'simple'; + type: 'goal'; amount: number; } diff --git a/packages/loot-core/src/server/db/index.ts b/packages/loot-core/src/server/db/index.ts index dc324fca85f..40927d042e7 100644 --- a/packages/loot-core/src/server/db/index.ts +++ b/packages/loot-core/src/server/db/index.ts @@ -8,18 +8,14 @@ import { Timestamp, } from '@actual-app/crdt'; import { Database } from '@jlongster/sql.js'; -import LRU from 'lru-cache'; +import { LRUCache } from 'lru-cache'; import { v4 as uuidv4 } from 'uuid'; import * as fs from '../../platform/server/fs'; import * as sqlite from '../../platform/server/sqlite'; import * as monthUtils from '../../shared/months'; import { groupById } from '../../shared/util'; -import { - CategoryEntity, - CategoryGroupEntity, - PayeeEntity, -} from '../../types/models'; +import { CategoryEntity, CategoryGroupEntity } from '../../types/models'; import { schema, schemaConfig, @@ -37,6 +33,16 @@ import { import { sendMessages, batchMessages } from '../sync'; import { shoveSortOrders, SORT_INCREMENT } from './sort'; +import { + DbAccount, + DbCategory, + DbCategoryGroup, + DbPayee, + DbTransaction, + DbViewTransaction, +} from './types'; + +export * from './types'; export { toDateRepr, fromDateRepr } from '../models'; @@ -100,17 +106,24 @@ export function runQuery( sql: string, params?: Array, fetchAll?: false, -); -export function runQuery( +): { changes: unknown }; + +export function runQuery( sql: string, params: Array | undefined, fetchAll: true, -); -export function runQuery(sql, params, fetchAll) { - // const unrecord = perf.record('sqlite'); - const result = sqlite.runQuery(db, sql, params, fetchAll); - // unrecord(); - return result; +): T[]; + +export function runQuery( + sql: string, + params: (string | number)[], + fetchAll: boolean, +) { + if (fetchAll) { + return sqlite.runQuery(db, sql, params, true); + } else { + return sqlite.runQuery(db, sql, params, false); + } } export function execQuery(sql: string) { @@ -119,7 +132,7 @@ export function execQuery(sql: string) { // This manages an LRU cache of prepared query statements. This is // only needed in hot spots when you are running lots of queries. -let _queryCache = new LRU({ max: 100 }); +let _queryCache = new LRUCache({ max: 100 }); export function cache(sql: string) { const cached = _queryCache.get(sql); if (cached) { @@ -132,7 +145,7 @@ export function cache(sql: string) { } function resetQueryCache() { - _queryCache = new LRU({ max: 100 }); + _queryCache = new LRUCache({ max: 100 }); } export function transaction(fn: () => void) { @@ -147,19 +160,28 @@ export function asyncTransaction(fn: () => Promise) { // async. We return a promise here until we've audited all the code to // make sure nothing calls `.then` on this. export async function all(sql, params?: (string | number)[]) { - return runQuery(sql, params, true); + // TODO: In the next phase, we will make this function generic + // and pass the type of the return type to `runQuery`. + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return runQuery(sql, params, true) as any[]; } export async function first(sql, params?: (string | number)[]) { const arr = await runQuery(sql, params, true); - return arr.length === 0 ? null : arr[0]; + // TODO: In the next phase, we will make this function generic + // and pass the type of the return type to `runQuery`. + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return arr.length === 0 ? null : (arr[0] as any); } // The underlying sql system is now sync, but we can't update `first` yet // without auditing all uses of it export function firstSync(sql, params?: (string | number)[]) { const arr = runQuery(sql, params, true); - return arr.length === 0 ? null : arr[0]; + // TODO: In the next phase, we will make this function generic + // and pass the type of the return type to `runQuery`. + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return arr.length === 0 ? null : (arr[0] as any); } // This function is marked as async because `runQuery` is no longer @@ -175,7 +197,10 @@ export async function select(table, id) { [id], true, ); - return rows[0]; + // TODO: In the next phase, we will make this function generic + // and pass the type of the return type to `runQuery`. + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return rows[0] as any; } export async function update(table, params) { @@ -252,9 +277,12 @@ export async function deleteAll(table: string) { export async function selectWithSchema(table, sql, params) { const rows = await runQuery(sql, params, true); - return rows + const convertedRows = rows .map(row => convertFromSelect(schema, schemaConfig, table, row)) .filter(Boolean); + // TODO: Make convertFromSelect generic so we don't need this cast + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return convertedRows as any[]; } export async function selectFirstWithSchema(table, sql, params) { @@ -282,16 +310,18 @@ export function updateWithSchema(table, fields) { // Data-specific functions. Ideally this would be split up into // different files +// TODO: Fix return type. This should returns a DbCategory[]. export async function getCategories( - ids?: Array, + ids?: Array, ): Promise { const whereIn = ids ? `c.id IN (${toSqlQueryParameters(ids)}) AND` : ''; const query = `SELECT c.* FROM categories c WHERE ${whereIn} c.tombstone = 0 ORDER BY c.sort_order, c.id`; return ids ? await all(query, [...ids]) : await all(query); } +// TODO: Fix return type. This should returns a [DbCategoryGroup, ...DbCategory]. export async function getCategoriesGrouped( - ids?: Array, + ids?: Array, ): Promise> { const categoryGroupWhereIn = ids ? `cg.id IN (${toSqlQueryParameters(ids)}) AND` @@ -432,7 +462,11 @@ export function updateCategory(category) { return update('categories', category); } -export async function moveCategory(id, groupId, targetId?: string) { +export async function moveCategory( + id: DbCategory['id'], + groupId: DbCategoryGroup['id'], + targetId?: DbCategory['id'], +) { if (!groupId) { throw new Error('moveCategory: groupId is required'); } @@ -449,7 +483,10 @@ export async function moveCategory(id, groupId, targetId?: string) { await update('categories', { id, sort_order, cat_group: groupId }); } -export async function deleteCategory(category, transferId?: string) { +export async function deleteCategory( + category: Pick, + transferId?: DbCategory['id'], +) { if (transferId) { // We need to update all the deleted categories that currently // point to the one we're about to delete so they all are @@ -469,11 +506,11 @@ export async function deleteCategory(category, transferId?: string) { return delete_('categories', category.id); } -export async function getPayee(id) { +export async function getPayee(id: DbPayee['id']) { return first(`SELECT * FROM payees WHERE id = ?`, [id]); } -export async function getAccount(id) { +export async function getAccount(id: DbAccount['id']) { return first(`SELECT * FROM accounts WHERE id = ?`, [id]); } @@ -487,7 +524,7 @@ export async function insertPayee(payee) { return id; } -export async function deletePayee(payee) { +export async function deletePayee(payee: Pick) { const { transfer_acct } = await first('SELECT * FROM payees WHERE id = ?', [ payee.id, ]); @@ -506,7 +543,7 @@ export async function deletePayee(payee) { return delete_('payees', payee.id); } -export async function deleteTransferPayee(payee) { +export async function deleteTransferPayee(payee: Pick) { // This allows deleting transfer payees return delete_('payees', payee.id); } @@ -516,9 +553,12 @@ export function updatePayee(payee) { return update('payees', payee); } -export async function mergePayees(target: string, ids: string[]) { +export async function mergePayees( + target: DbPayee['id'], + ids: Array, +) { // Load in payees so we can check some stuff - const dbPayees: PayeeEntity[] = await all('SELECT * FROM payees'); + const dbPayees: DbPayee[] = await all('SELECT * FROM payees'); const payees = groupById(dbPayees); // Filter out any transfer payees @@ -613,7 +653,7 @@ export async function getOrphanedPayees() { return rows.map(row => row.id); } -export async function getPayeeByName(name) { +export async function getPayeeByName(name: DbPayee['name']) { return first( `SELECT * FROM payees WHERE UNICODE_LOWER(name) = ? AND tombstone = 0`, [name.toLowerCase()], @@ -632,7 +672,7 @@ export function getAccounts() { export async function insertAccount(account) { const accounts = await all( 'SELECT * FROM accounts WHERE offbudget = ? ORDER BY sort_order, name', - [account.offbudget != null ? account.offbudget : 0], + [account.offbudget ? 1 : 0], ); // Don't pass a target in, it will default to appending at the end @@ -651,7 +691,10 @@ export function deleteAccount(account) { return delete_('accounts', account.id); } -export async function moveAccount(id, targetId) { +export async function moveAccount( + id: DbAccount['id'], + targetId: DbAccount['id'], +) { const account = await first('SELECT * FROM accounts WHERE id = ?', [id]); let accounts; if (account.closed) { @@ -661,7 +704,7 @@ export async function moveAccount(id, targetId) { } else { accounts = await all( `SELECT id, sort_order FROM accounts WHERE tombstone = 0 AND offbudget = ? ORDER BY sort_order, name`, - [account.offbudget], + [account.offbudget ? 1 : 0], ); } @@ -674,7 +717,7 @@ export async function moveAccount(id, targetId) { }); } -export async function getTransaction(id) { +export async function getTransaction(id: DbViewTransaction['id']) { const rows = await selectWithSchema( 'transactions', 'SELECT * FROM v_transactions WHERE id = ?', @@ -683,7 +726,7 @@ export async function getTransaction(id) { return rows[0]; } -export async function getTransactions(accountId) { +export async function getTransactions(accountId: DbTransaction['acct']) { if (arguments.length > 1) { throw new Error( '`getTransactions` was given a second argument, it now only takes a single argument `accountId`', diff --git a/packages/loot-core/src/server/db/mappings.ts b/packages/loot-core/src/server/db/mappings.ts index 81b0c280db2..f7423cab5f0 100644 --- a/packages/loot-core/src/server/db/mappings.ts +++ b/packages/loot-core/src/server/db/mappings.ts @@ -22,14 +22,12 @@ let unlistenSync; export async function loadMappings() { // The mappings are separated into tables specific to the type of // data. But you know, we really could keep a global mapping table. - const categories = (await db.all('SELECT * FROM category_mapping')).map(r => [ - r.id, - r.transferId, - ]); - const payees = (await db.all('SELECT * FROM payee_mapping')).map(r => [ - r.id, - r.targetId, - ]); + const categories = (await db.all('SELECT * FROM category_mapping')).map( + r => [r.id, r.transferId] as const, + ); + const payees = (await db.all('SELECT * FROM payee_mapping')).map( + r => [r.id, r.targetId] as const, + ); // All ids are unique, so we can just keep a global table of mappings allMappings = new Map(categories.concat(payees)); diff --git a/packages/loot-core/src/server/db/types.d.ts b/packages/loot-core/src/server/db/types.d.ts deleted file mode 100644 index b6fdd94ec3a..00000000000 --- a/packages/loot-core/src/server/db/types.d.ts +++ /dev/null @@ -1,9 +0,0 @@ -export type Schedule = { - id: string; - rule: string; - active: number; - completed: number; - posts_transaction: number; - tombstone: number; - name: string | null; -}; diff --git a/packages/loot-core/src/server/db/types/index.ts b/packages/loot-core/src/server/db/types/index.ts new file mode 100644 index 00000000000..c6accb44f00 --- /dev/null +++ b/packages/loot-core/src/server/db/types/index.ts @@ -0,0 +1,315 @@ +// These are the types that exactly match the database schema. +// The `Entity` types e.g. `TransactionEntity`, `AccountEntity`, etc +// are specific to the AQL query framework and does not necessarily +// match the actual database schema. + +type JsonString = string; + +export type DbAccount = { + id: string; + name: string; + offbudget: 1 | 0; + closed: 1 | 0; + tombstone: 1 | 0; + sort_order: number; + account_id?: string | null; + balance_current?: number | null; + balance_available?: number | null; + balance_limit?: number | null; + mask?: string | null; + official_name?: string | null; + type?: string | null; + subtype?: string | null; + bank?: string | null; + account_sync_source?: 'simpleFin' | 'goCardless' | null; +}; + +export type DbBank = { + id: string; + bank_id: string; + name: string; + tombstone: 1 | 0; +}; + +export type DbCategory = { + id: string; + name: string; + is_income: 1 | 0; + cat_group: DbCategoryGroup['id']; + sort_order: number; + hidden: 1 | 0; + goal_def?: JsonString | null; + tombstone: 1 | 0; +}; + +export type DbCategoryGroup = { + id: string; + name: string; + is_income: 1 | 0; + sort_order: number; + hidden: 1 | 0; + tombstone: 1 | 0; +}; + +export type DbCategoryMapping = { + id: DbCategory['id']; + transferId: DbCategory['id']; +}; + +export type DbKvCache = { + key: string; + value: string; +}; + +export type DbKvCacheKey = { + id: number; + key: number; +}; + +export type DbClockMessage = { + id: string; + clock: string; +}; + +export type DbCrdtMessage = { + id: string; + timestamp: string; + dataset: string; + row: string; + column: string; + value: Uint8Array; +}; + +export type DbNote = { + id: string; + note: string; +}; + +export type DbPayeeMapping = { + id: DbPayee['id']; + targetId: DbPayee['id']; +}; + +export type DbPayee = { + id: string; + name: string; + transfer_acct?: DbAccount['id'] | null; + favorite: 1 | 0; + learn_categories: 1 | 0; + tombstone: 1 | 0; + // Unused in the codebase + category?: string | null; +}; + +export type DbRule = { + id: string; + stage: string; + conditions: JsonString; + actions: JsonString; + tombstone: 1 | 0; + conditions_op: string; +}; + +export type DbSchedule = { + id: string; + name: string; + rule: DbRule['id']; + active: 1 | 0; + completed: 1 | 0; + posts_transaction: 1 | 0; + tombstone: 1 | 0; +}; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +type DbScheduleJsonPath = { + schedule_id: DbSchedule['id']; + payee: string; + account: string; + amount: string; + date: string; +}; + +export type DbScheduleNextDate = { + id: string; + schedule_id: DbSchedule['id']; + local_next_date: number; + local_next_date_ts: number; + base_next_date: number; + base_next_date_ts: number; +}; + +// This is unused in the codebase. +// eslint-disable-next-line @typescript-eslint/no-unused-vars +type DbPendingTransaction = { + id: string; + acct: number; + amount: number; + description: string; + date: string; +}; + +export type DbTransaction = { + id: string; + isParent: 1 | 0; + isChild: 1 | 0; + date: number; + acct: DbAccount['id']; + amount: number; + sort_order: number; + parent_id?: DbTransaction['id'] | null; + category?: DbCategory['id'] | null; + description?: string | null; + notes?: string | null; + financial_id?: string | null; + error?: string | null; + imported_description?: string | null; + transferred_id?: DbTransaction['id'] | null; + schedule?: DbSchedule['id'] | null; + starting_balance_flag: 1 | 0; + tombstone: 1 | 0; + cleared: 1 | 0; + reconciled: 1 | 0; + // Unused in the codebase + pending?: 1 | 0 | null; + location?: string | null; + type?: string | null; +}; + +export type DbReflectBudget = { + id: string; + month: number; + category: string; + amount: number; + carryover: number; + goal: number; + long_goal: number; +}; + +export type DbZeroBudgetMonth = { + id: string; + buffered: number; +}; + +export type DbZeroBudget = { + id: string; + month: number; + category: string; + amount: number; + carryover: number; + goal: number; + long_goal: number; +}; + +export type DbTransactionFilter = { + id: string; + name: string; + conditions: JsonString; + conditions_op: string; + tombstone: 1 | 0; +}; + +export type DbPreference = { + id: string; + value: string; +}; + +export type DbCustomReport = { + id: string; + name: string; + start_date: string; + end_date: string; + date_static: number; + date_range: string; + mode: string; + group_by: string; + balance_type: string; + show_empty: 1 | 0; + show_offbudget: 1 | 0; + show_hidden: 1 | 0; + show_uncateogorized: 1 | 0; + selected_categories: string; + graph_type: string; + conditions: JsonString; + conditions_op: string; + metadata: JsonString; + interval: string; + color_scheme: string; + include_current: 1 | 0; + sort_by: string; + tombstone: 1 | 0; +}; + +export type DbDashboard = { + id: string; + type: string; + width: number; + height: number; + x: number; + y: number; + meta: JsonString; + tombstone: 1 | 0; +}; + +export type DbViewTransactionInternal = { + id: DbTransaction['id']; + is_parent: DbTransaction['isParent']; + is_child: DbTransaction['isChild']; + date: DbTransaction['date']; + account: DbAccount['id']; + amount: DbTransaction['amount']; + parent_id: DbTransaction['parent_id'] | null; + category: DbCategory['id'] | null; + payee: DbPayee['id'] | null; + notes: DbTransaction['notes'] | null; + imported_id: DbTransaction['financial_id'] | null; + error: DbTransaction['error'] | null; + imported_payee: DbTransaction['imported_description'] | null; + starting_balance_flag: DbTransaction['starting_balance_flag'] | null; + transfer_id: DbTransaction['transferred_id'] | null; + schedule: DbSchedule['id'] | null; + sort_order: DbTransaction['sort_order']; + cleared: DbTransaction['cleared']; + tombstone: DbTransaction['tombstone']; + reconciled: DbTransaction['reconciled']; +}; + +export type DbViewTransactionInternalAlive = DbViewTransactionInternal; +export type DbViewTransaction = DbViewTransactionInternalAlive; + +export type DbViewCategory = { + id: DbCategory['id']; + name: DbCategory['name']; + is_income: DbCategory['is_income']; + hidden: DbCategory['hidden']; + group: DbCategoryGroup['id']; + sort_order: DbCategory['sort_order']; + tombstone: DbCategory['tombstone']; +}; + +export type DbViewPayee = { + id: DbPayee['id']; + name: DbAccount['name'] | DbPayee['name']; + transfer_acct: DbPayee['transfer_acct']; + tombstone: DbPayee['tombstone']; +}; + +export type DbViewSchedule = { + id: DbSchedule['id']; + name: DbSchedule['name']; + rule: DbSchedule['rule']; + next_date: + | DbScheduleNextDate['local_next_date_ts'] + | DbScheduleNextDate['local_next_date'] + | DbScheduleNextDate['base_next_date']; + active: DbSchedule['active']; + completed: DbSchedule['completed']; + posts_transaction: DbSchedule['posts_transaction']; + tombstone: DbSchedule['tombstone']; + _payee: DbPayeeMapping['targetId']; + _account: DbAccount['id']; + _amount: number; + _amountOp: string; + _date: JsonString; + _conditions: JsonString; + _actions: JsonString; +}; diff --git a/packages/loot-core/src/server/errors.ts b/packages/loot-core/src/server/errors.ts index e218c13c15f..a8da01d7452 100644 --- a/packages/loot-core/src/server/errors.ts +++ b/packages/loot-core/src/server/errors.ts @@ -12,6 +12,21 @@ export class PostError extends Error { } } +export class BankSyncError extends Error { + reason: string; + category: string; + code: string; + type: 'BankSyncError'; + + constructor(reason: string, category: string, code: string) { + super('BankSyncError: ' + reason); + this.type = 'BankSyncError'; + this.reason = reason; + this.category = category; + this.code = code; + } +} + export class HTTPError extends Error { statusCode: number; responseBody: string; diff --git a/packages/loot-core/src/server/filters/app.ts b/packages/loot-core/src/server/filters/app.ts index c6b4ef69c96..8fc22399b12 100644 --- a/packages/loot-core/src/server/filters/app.ts +++ b/packages/loot-core/src/server/filters/app.ts @@ -1,11 +1,11 @@ // @ts-strict-ignore import { v4 as uuidv4 } from 'uuid'; -import { parseConditionsOrActions } from '../accounts/transaction-rules'; import { createApp } from '../app'; import * as db from '../db'; import { requiredFields } from '../models'; import { mutator } from '../mutators'; +import { parseConditionsOrActions } from '../transactions/transaction-rules'; import { undoable } from '../undo'; import { FiltersHandlers } from './types/handlers'; diff --git a/packages/loot-core/src/server/main.test.ts b/packages/loot-core/src/server/main.test.ts index ea4d8713007..39a16890d5f 100644 --- a/packages/loot-core/src/server/main.test.ts +++ b/packages/loot-core/src/server/main.test.ts @@ -1,5 +1,6 @@ // @ts-strict-ignore import { getClock, deserializeClock } from '@actual-app/crdt'; +import { v4 as uuidv4 } from 'uuid'; import { expectSnapshotWithDiffer } from '../mocks/util'; import * as connection from '../platform/server/connection'; @@ -175,6 +176,7 @@ describe('Budget', () => { // budgets for the earlier months await db.runQuery("INSERT INTO accounts (id, name) VALUES ('one', 'boa')"); await runHandler(handlers['transaction-add'], { + id: uuidv4(), date: '2016-05-06', amount: 50, account: 'one', diff --git a/packages/loot-core/src/server/main.ts b/packages/loot-core/src/server/main.ts index d57f94dcfed..e13cd0d35f3 100644 --- a/packages/loot-core/src/server/main.ts +++ b/packages/loot-core/src/server/main.ts @@ -12,22 +12,13 @@ import * as connection from '../platform/server/connection'; import * as fs from '../platform/server/fs'; import { logger } from '../platform/server/log'; import * as sqlite from '../platform/server/sqlite'; -import { isNonProductionEnvironment } from '../shared/environment'; import * as monthUtils from '../shared/months'; -import { dayFromDate } from '../shared/months'; -import { q, Query } from '../shared/query'; -import { amountToInteger, stringToInteger } from '../shared/util'; +import { q } from '../shared/query'; import { type Budget } from '../types/budget'; import { Handlers } from '../types/handlers'; import { OpenIdConfig } from '../types/models/openid'; -import { exportToCSV, exportQueryToCSV } from './accounts/export-to-csv'; -import * as link from './accounts/link'; -import { parseFile } from './accounts/parse-file'; -import { getStartingBalancePayee } from './accounts/payees'; -import * as bankSync from './accounts/sync'; -import * as rules from './accounts/transaction-rules'; -import { batchUpdateTransactions } from './accounts/transactions'; +import { app as accountsApp } from './accounts/app'; import { app as adminApp } from './admin/app'; import { installAPI } from './api'; import { runQuery as aqlQuery } from './aql'; @@ -45,7 +36,7 @@ import { app as dashboardApp } from './dashboard/app'; import * as db from './db'; import * as mappings from './db/mappings'; import * as encryption from './encryption'; -import { APIError, TransactionError, PostError } from './errors'; +import { APIError } from './errors'; import { app as filtersApp } from './filters/app'; import { handleBudgetImport } from './importers'; import { app } from './main-app'; @@ -73,6 +64,8 @@ import { } from './sync'; import * as syncMigrations from './sync/migrate'; import { app as toolsApp } from './tools/app'; +import { app as transactionsApp } from './transactions/app'; +import * as rules from './transactions/transaction-rules'; import { withUndo, clearUndo, undo, redo } from './undo'; import { updateVersion } from './update'; import { @@ -108,56 +101,6 @@ handlers['redo'] = mutator(function () { return redo(); }); -handlers['transactions-batch-update'] = mutator(async function ({ - added, - deleted, - updated, - learnCategories, -}) { - return withUndo(async () => { - const result = await batchUpdateTransactions({ - added, - updated, - deleted, - learnCategories, - }); - - return result; - }); -}); - -handlers['transaction-add'] = mutator(async function (transaction) { - await handlers['transactions-batch-update']({ added: [transaction] }); - return {}; -}); - -handlers['transaction-update'] = mutator(async function (transaction) { - await handlers['transactions-batch-update']({ updated: [transaction] }); - return {}; -}); - -handlers['transaction-delete'] = mutator(async function (transaction) { - await handlers['transactions-batch-update']({ deleted: [transaction] }); - return {}; -}); - -handlers['transactions-parse-file'] = async function ({ filepath, options }) { - return parseFile(filepath, options); -}; - -handlers['transactions-export'] = async function ({ - transactions, - accounts, - categoryGroups, - payees, -}) { - return exportToCSV(transactions, accounts, categoryGroups, payees); -}; - -handlers['transactions-export-query'] = async function ({ query: queryState }) { - return exportQueryToCSV(new Query(queryState)); -}; - handlers['get-categories'] = async function () { return { grouped: await db.getCategoriesGrouped(), @@ -165,17 +108,6 @@ handlers['get-categories'] = async function () { }; }; -handlers['get-earliest-transaction'] = async function () { - const { data } = await aqlQuery( - q('transactions') - .options({ splits: 'none' }) - .orderBy({ date: 'asc' }) - .select('*') - .limit(1), - ); - return data[0] || null; -}; - handlers['get-budget-bounds'] = async function () { return budget.createAllBudgets(); }; @@ -420,7 +352,7 @@ handlers['category-group-delete'] = mutator(async function ({ }); handlers['must-category-transfer'] = async function ({ id }) { - const res = await db.runQuery( + const res = await db.runQuery<{ count: number }>( `SELECT count(t.id) as count FROM transactions t LEFT JOIN category_mapping cm ON cm.id = t.category WHERE cm.transferId = ? AND t.tombstone = 0`, @@ -528,7 +460,10 @@ handlers['getCell'] = async function ({ sheetName, name }) { }; handlers['getCells'] = async function ({ names }) { - return names.map(name => ({ value: sheet.get()._getNode(name).value })); + return names.map(name => { + const node = sheet.get()._getNode(name); + return { name: node.name, value: node.value }; + }); }; handlers['getCellNamesInSheet'] = async function ({ sheetName }) { @@ -565,883 +500,6 @@ handlers['query'] = async function (query) { return aqlQuery(query); }; -handlers['account-update'] = mutator(async function ({ id, name }) { - return withUndo(async () => { - await db.update('accounts', { id, name }); - return {}; - }); -}); - -handlers['accounts-get'] = async function () { - return db.getAccounts(); -}; - -handlers['account-balance'] = async function ({ id, cutoff }) { - const { balance } = await db.first( - 'SELECT sum(amount) as balance FROM transactions WHERE acct = ? AND isParent = 0 AND tombstone = 0 AND date <= ?', - [id, db.toDateRepr(dayFromDate(cutoff))], - ); - return balance ? balance : 0; -}; - -handlers['account-properties'] = async function ({ id }) { - const { balance } = await db.first( - 'SELECT sum(amount) as balance FROM transactions WHERE acct = ? AND isParent = 0 AND tombstone = 0', - [id], - ); - const { count } = await db.first( - 'SELECT count(id) as count FROM transactions WHERE acct = ? AND tombstone = 0', - [id], - ); - - return { balance: balance || 0, numTransactions: count }; -}; - -handlers['gocardless-accounts-link'] = async function ({ - requisitionId, - account, - upgradingId, - offBudget, -}) { - let id; - const bank = await link.findOrCreateBank(account.institution, requisitionId); - - if (upgradingId) { - const accRow = await db.first('SELECT * FROM accounts WHERE id = ?', [ - upgradingId, - ]); - id = accRow.id; - await db.update('accounts', { - id, - account_id: account.account_id, - bank: bank.id, - account_sync_source: 'goCardless', - }); - } else { - id = uuidv4(); - await db.insertWithUUID('accounts', { - id, - account_id: account.account_id, - mask: account.mask, - name: account.name, - official_name: account.official_name, - bank: bank.id, - offbudget: offBudget ? 1 : 0, - account_sync_source: 'goCardless', - }); - await db.insertPayee({ - name: '', - transfer_acct: id, - }); - } - - await bankSync.syncAccount( - undefined, - undefined, - id, - account.account_id, - bank.bank_id, - ); - - connection.send('sync-event', { - type: 'success', - tables: ['transactions'], - }); - - return 'ok'; -}; - -handlers['simplefin-accounts-link'] = async function ({ - externalAccount, - upgradingId, - offBudget, -}) { - let id; - - const institution = { - name: externalAccount.institution ?? 'Unknown', - }; - - const bank = await link.findOrCreateBank( - institution, - externalAccount.orgDomain ?? externalAccount.orgId, - ); - - if (upgradingId) { - const accRow = await db.first('SELECT * FROM accounts WHERE id = ?', [ - upgradingId, - ]); - id = accRow.id; - await db.update('accounts', { - id, - account_id: externalAccount.account_id, - bank: bank.id, - account_sync_source: 'simpleFin', - }); - } else { - id = uuidv4(); - await db.insertWithUUID('accounts', { - id, - account_id: externalAccount.account_id, - name: externalAccount.name, - official_name: externalAccount.name, - bank: bank.id, - offbudget: offBudget ? 1 : 0, - account_sync_source: 'simpleFin', - }); - await db.insertPayee({ - name: '', - transfer_acct: id, - }); - } - - await bankSync.syncAccount( - undefined, - undefined, - id, - externalAccount.account_id, - bank.bank_id, - ); - - await connection.send('sync-event', { - type: 'success', - tables: ['transactions'], - }); - - return 'ok'; -}; - -handlers['account-create'] = mutator(async function ({ - name, - balance, - offBudget, - closed, -}) { - return withUndo(async () => { - const id = await db.insertAccount({ - name, - offbudget: offBudget ? 1 : 0, - closed: closed ? 1 : 0, - }); - - await db.insertPayee({ - name: '', - transfer_acct: id, - }); - - if (balance != null && balance !== 0) { - const payee = await getStartingBalancePayee(); - - await db.insertTransaction({ - account: id, - amount: amountToInteger(balance), - category: offBudget ? null : payee.category, - payee: payee.id, - date: monthUtils.currentDay(), - cleared: true, - starting_balance_flag: true, - }); - } - - return id; - }); -}); - -handlers['account-close'] = mutator(async function ({ - id, - transferAccountId, - categoryId, - forced, -}) { - // Unlink the account if it's linked. This makes sure to remove it from - // bank-sync providers. (This should not be undo-able, as it mutates the - // remote server and the user will have to link the account again) - await handlers['account-unlink']({ id }); - - return withUndo(async () => { - const account = await db.first( - 'SELECT * FROM accounts WHERE id = ? AND tombstone = 0', - [id], - ); - - // Do nothing if the account doesn't exist or it's already been - // closed - if (!account || account.closed === 1) { - return; - } - - const { balance, numTransactions } = await handlers['account-properties']({ - id, - }); - - // If there are no transactions, we can simply delete the account - if (numTransactions === 0) { - await db.deleteAccount({ id }); - } else if (forced) { - const rows = await db.runQuery( - 'SELECT id, transfer_id FROM v_transactions WHERE account = ?', - [id], - true, - ); - - const { id: payeeId } = await db.first( - 'SELECT id FROM payees WHERE transfer_acct = ?', - [id], - ); - - await batchMessages(async () => { - // TODO: what this should really do is send a special message that - // automatically marks the tombstone value for all transactions - // within an account... or something? This is problematic - // because another client could easily add new data that - // should be marked as deleted. - - rows.forEach(row => { - if (row.transfer_id) { - db.updateTransaction({ - id: row.transfer_id, - payee: null, - transfer_id: null, - }); - } - - db.deleteTransaction({ id: row.id }); - }); - - db.deleteAccount({ id }); - db.deleteTransferPayee({ id: payeeId }); - }); - } else { - if (balance !== 0 && transferAccountId == null) { - throw APIError('balance is non-zero: transferAccountId is required'); - } - - await db.update('accounts', { id, closed: 1 }); - - // If there is a balance we need to transfer it to the specified - // account (and possibly categorize it) - if (balance !== 0) { - const { id: payeeId } = await db.first( - 'SELECT id FROM payees WHERE transfer_acct = ?', - [transferAccountId], - ); - - await handlers['transaction-add']({ - id: uuidv4(), - payee: payeeId, - amount: -balance, - account: id, - date: monthUtils.currentDay(), - notes: 'Closing account', - category: categoryId || null, - }); - } - } - }); -}); - -handlers['account-reopen'] = mutator(async function ({ id }) { - return withUndo(async () => { - await db.update('accounts', { id, closed: 0 }); - }); -}); - -handlers['account-move'] = mutator(async function ({ id, targetId }) { - return withUndo(async () => { - await db.moveAccount(id, targetId); - }); -}); - -let stopPolling = false; - -handlers['secret-set'] = async function ({ name, value }) { - const userToken = await asyncStorage.getItem('user-token'); - - if (!userToken) { - return { error: 'unauthorized' }; - } - - try { - return await post( - getServer().BASE_SERVER + '/secret', - { - name, - value, - }, - { - 'X-ACTUAL-TOKEN': userToken, - }, - ); - } catch (error) { - return { error: 'failed', reason: error.reason }; - } -}; - -handlers['secret-check'] = async function (name) { - const userToken = await asyncStorage.getItem('user-token'); - - if (!userToken) { - return { error: 'unauthorized' }; - } - - try { - return await get(getServer().BASE_SERVER + '/secret/' + name, { - 'X-ACTUAL-TOKEN': userToken, - }); - } catch (error) { - console.error(error); - return { error: 'failed' }; - } -}; - -handlers['gocardless-poll-web-token'] = async function ({ - upgradingAccountId, - requisitionId, -}) { - const userToken = await asyncStorage.getItem('user-token'); - if (!userToken) return { error: 'unknown' }; - - const startTime = Date.now(); - stopPolling = false; - - async function getData(cb) { - if (stopPolling) { - return; - } - - if (Date.now() - startTime >= 1000 * 60 * 10) { - cb('timeout'); - return; - } - - const data = await post( - getServer().GOCARDLESS_SERVER + '/get-accounts', - { - upgradingAccountId, - requisitionId, - }, - { - 'X-ACTUAL-TOKEN': userToken, - }, - ); - - if (data) { - if (data.error) { - cb('unknown'); - } else { - cb(null, data); - } - } else { - setTimeout(() => getData(cb), 3000); - } - } - - return new Promise(resolve => { - getData((error, data) => { - if (error) { - resolve({ error }); - } else { - resolve({ data }); - } - }); - }); -}; - -handlers['gocardless-status'] = async function () { - const userToken = await asyncStorage.getItem('user-token'); - - if (!userToken) { - return { error: 'unauthorized' }; - } - - return post( - getServer().GOCARDLESS_SERVER + '/status', - {}, - { - 'X-ACTUAL-TOKEN': userToken, - }, - ); -}; - -handlers['simplefin-status'] = async function () { - const userToken = await asyncStorage.getItem('user-token'); - - if (!userToken) { - return { error: 'unauthorized' }; - } - - return post( - getServer().SIMPLEFIN_SERVER + '/status', - {}, - { - 'X-ACTUAL-TOKEN': userToken, - }, - ); -}; - -handlers['simplefin-accounts'] = async function () { - const userToken = await asyncStorage.getItem('user-token'); - - if (!userToken) { - return { error: 'unauthorized' }; - } - - try { - return await post( - getServer().SIMPLEFIN_SERVER + '/accounts', - {}, - { - 'X-ACTUAL-TOKEN': userToken, - }, - 60000, - ); - } catch (error) { - return { error_code: 'TIMED_OUT' }; - } -}; - -handlers['gocardless-get-banks'] = async function (country) { - const userToken = await asyncStorage.getItem('user-token'); - - if (!userToken) { - return { error: 'unauthorized' }; - } - - return post( - getServer().GOCARDLESS_SERVER + '/get-banks', - { country, showDemo: isNonProductionEnvironment() }, - { - 'X-ACTUAL-TOKEN': userToken, - }, - ); -}; - -handlers['gocardless-poll-web-token-stop'] = async function () { - stopPolling = true; - return 'ok'; -}; - -handlers['gocardless-create-web-token'] = async function ({ - upgradingAccountId, - institutionId, - accessValidForDays, -}) { - const userToken = await asyncStorage.getItem('user-token'); - - if (!userToken) { - return { error: 'unauthorized' }; - } - - try { - return await post( - getServer().GOCARDLESS_SERVER + '/create-web-token', - { - upgradingAccountId, - institutionId, - accessValidForDays, - }, - { - 'X-ACTUAL-TOKEN': userToken, - }, - ); - } catch (error) { - console.error(error); - return { error: 'failed' }; - } -}; - -async function handleSyncResponse( - res, - acct, - newTransactions, - matchedTransactions, - updatedAccounts, -) { - const { added, updated } = res; - - newTransactions.push(...added); - matchedTransactions.push(...updated); - - if (added.length > 0) { - updatedAccounts.push(acct.id); - } - - const ts = new Date().getTime().toString(); - const id = acct.id; - await db.runQuery(`UPDATE accounts SET last_sync = ? WHERE id = ?`, [ts, id]); -} - -function handleSyncError(err, acct) { - if (err.type === 'BankSyncError') { - return { - type: 'SyncError', - accountId: acct.id, - message: 'Failed syncing account “' + acct.name + '.”', - category: err.category, - code: err.code, - }; - } - - if (err instanceof PostError && err.reason !== 'internal') { - return { - accountId: acct.id, - message: err.reason - ? err.reason - : `Account “${acct.name}” is not linked properly. Please link it again.`, - }; - } - - return { - accountId: acct.id, - message: - 'There was an internal error. Please get in touch https://actualbudget.org/contact for support.', - internal: err.stack, - }; -} - -handlers['accounts-bank-sync'] = async function ({ ids = [] }) { - const [[, userId], [, userKey]] = await asyncStorage.multiGet([ - 'user-id', - 'user-key', - ]); - - const accounts = await db.runQuery( - ` - 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 - ${ids.length ? `AND a.id IN (${ids.map(() => '?').join(', ')})` : ''} - ORDER BY a.offbudget, a.sort_order - `, - ids, - true, - ); - - const errors = []; - const newTransactions = []; - const matchedTransactions = []; - const updatedAccounts = []; - - for (let i = 0; i < accounts.length; i++) { - const acct = accounts[i]; - if (acct.bankId) { - try { - console.group('Bank Sync operation for account:', acct.name); - const res = await bankSync.syncAccount( - userId, - userKey, - acct.id, - acct.account_id, - acct.bankId, - ); - - await handleSyncResponse( - res, - acct, - newTransactions, - matchedTransactions, - updatedAccounts, - ); - } catch (err) { - errors.push(handleSyncError(err, acct)); - err.message = 'Failed syncing account “' + acct.name + '.”'; - captureException(err); - } finally { - console.groupEnd(); - } - } - } - - if (updatedAccounts.length > 0) { - connection.send('sync-event', { - type: 'success', - tables: ['transactions'], - }); - } - - return { errors, newTransactions, matchedTransactions, updatedAccounts }; -}; - -handlers['simplefin-batch-sync'] = async function ({ ids = [] }) { - const accounts = await db.runQuery( - `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 - AND a.account_sync_source = 'simpleFin' - ${ids.length ? `AND a.id IN (${ids.map(() => '?').join(', ')})` : ''} - ORDER BY a.offbudget, a.sort_order`, - ids.length ? ids : [], - true, - ); - - const retVal = []; - - console.group('Bank Sync operation for all SimpleFin accounts'); - try { - const res = await bankSync.SimpleFinBatchSync( - accounts.map(a => ({ - id: a.id, - accountId: a.account_id, - })), - ); - for (const account of res) { - const errors = []; - const newTransactions = []; - const matchedTransactions = []; - const updatedAccounts = []; - - if (account.res.error_code) { - errors.push( - handleSyncError( - { - type: 'BankSyncError', - category: account.res.error_type, - code: account.res.error_code, - }, - accounts.find(a => a.id === account.accountId), - ), - ); - } else { - await handleSyncResponse( - account.res, - accounts.find(a => a.id === account.accountId), - newTransactions, - matchedTransactions, - updatedAccounts, - ); - } - - retVal.push({ - accountId: account.accountId, - res: { errors, newTransactions, matchedTransactions, updatedAccounts }, - }); - } - } catch (err) { - const errors = []; - for (const account of accounts) { - retVal.push({ - accountId: account.accountId, - res: { - errors, - newTransactions: [], - matchedTransactions: [], - updatedAccounts: [], - }, - }); - errors.push(handleSyncError(err, account)); - } - } - - if (retVal.some(a => a.res.updatedAccounts.length > 0)) { - connection.send('sync-event', { - type: 'success', - tables: ['transactions'], - }); - } - - console.groupEnd(); - - return retVal; -}; - -handlers['transactions-import'] = mutator(function ({ - accountId, - transactions, - isPreview, - opts, -}) { - return withUndo(async () => { - if (typeof accountId !== 'string') { - throw APIError('transactions-import: accountId must be an id'); - } - - try { - return await bankSync.reconcileTransactions( - accountId, - transactions, - false, - true, - isPreview, - opts?.defaultCleared, - ); - } catch (err) { - if (err instanceof TransactionError) { - return { - errors: [{ message: err.message }], - added: [], - updated: [], - updatedPreview: [], - }; - } - - throw err; - } - }); -}); - -handlers['account-unlink'] = mutator(async function ({ id }) { - const { bank: bankId } = await db.first( - 'SELECT bank FROM accounts WHERE id = ?', - [id], - ); - - if (!bankId) { - return 'ok'; - } - - const accRow = await db.first('SELECT * FROM accounts WHERE id = ?', [id]); - - const isGoCardless = accRow.account_sync_source === 'goCardless'; - - await db.updateAccount({ - id, - account_id: null, - bank: null, - balance_current: null, - balance_available: null, - balance_limit: null, - account_sync_source: null, - }); - - if (isGoCardless === false) { - return; - } - - const { count } = await db.first( - 'SELECT COUNT(*) as count FROM accounts WHERE bank = ?', - [bankId], - ); - - // No more accounts are associated with this bank. We can remove - // it from GoCardless. - const userToken = await asyncStorage.getItem('user-token'); - if (!userToken) { - return 'ok'; - } - - if (count === 0) { - const { bank_id: requisitionId } = await db.first( - 'SELECT bank_id FROM banks WHERE id = ?', - [bankId], - ); - try { - await post( - getServer().GOCARDLESS_SERVER + '/remove-account', - { - requisitionId, - }, - { - 'X-ACTUAL-TOKEN': userToken, - }, - ); - } catch (error) { - console.log({ error }); - } - } - - return 'ok'; -}); - -handlers['save-global-prefs'] = async function (prefs) { - if ('maxMonths' in prefs) { - await asyncStorage.setItem('max-months', '' + prefs.maxMonths); - } - if ('documentDir' in prefs) { - if (await fs.exists(prefs.documentDir)) { - await asyncStorage.setItem('document-dir', prefs.documentDir); - } - } - if ('floatingSidebar' in prefs) { - await asyncStorage.setItem('floating-sidebar', '' + prefs.floatingSidebar); - } - if ('language' in prefs) { - await asyncStorage.setItem('language', prefs.language); - } - if ('theme' in prefs) { - await asyncStorage.setItem('theme', prefs.theme); - } - if ('preferredDarkTheme' in prefs) { - await asyncStorage.setItem( - 'preferred-dark-theme', - prefs.preferredDarkTheme, - ); - } - if ('serverSelfSignedCert' in prefs) { - await asyncStorage.setItem( - 'server-self-signed-cert', - prefs.serverSelfSignedCert, - ); - } - return 'ok'; -}; - -handlers['load-global-prefs'] = async function () { - const [ - [, floatingSidebar], - [, maxMonths], - [, documentDir], - [, encryptKey], - [, language], - [, theme], - [, preferredDarkTheme], - [, serverSelfSignedCert], - ] = await asyncStorage.multiGet([ - 'floating-sidebar', - 'max-months', - 'document-dir', - 'encrypt-key', - 'language', - 'theme', - 'preferred-dark-theme', - 'server-self-signed-cert', - ]); - return { - floatingSidebar: floatingSidebar === 'true' ? true : false, - maxMonths: stringToInteger(maxMonths || ''), - documentDir: documentDir || getDefaultDocumentDir(), - keyId: encryptKey && JSON.parse(encryptKey).id, - language, - theme: - theme === 'light' || - theme === 'dark' || - theme === 'auto' || - theme === 'development' || - theme === 'midnight' - ? theme - : 'auto', - preferredDarkTheme: - preferredDarkTheme === 'dark' || preferredDarkTheme === 'midnight' - ? preferredDarkTheme - : 'dark', - serverSelfSignedCert: serverSelfSignedCert || undefined, - }; -}; - -handlers['save-prefs'] = async function (prefsToSet) { - const { cloudFileId } = prefs.getPrefs(); - - // Need to sync the budget name on the server as well - if (prefsToSet.budgetName && cloudFileId) { - const userToken = await asyncStorage.getItem('user-token'); - - await post(getServer().SYNC_SERVER + '/update-user-filename', { - token: userToken, - fileId: cloudFileId, - name: prefsToSet.budgetName, - }); - } - - await prefs.savePrefs(prefsToSet); - return 'ok'; -}; - -handlers['load-prefs'] = async function () { - return prefs.getPrefs(); -}; - handlers['sync-reset'] = async function () { return await resetSync(); }; @@ -2446,9 +1504,11 @@ app.combine( reportsApp, rulesApp, adminApp, + transactionsApp, + accountsApp, ); -function getDefaultDocumentDir() { +export function getDefaultDocumentDir() { if (Platform.isMobile) { // On mobile, unfortunately we need to be backwards compatible // with the old folder structure which does not store files inside diff --git a/packages/loot-core/src/server/post.ts b/packages/loot-core/src/server/post.ts index b521d87a147..90533596be7 100644 --- a/packages/loot-core/src/server/post.ts +++ b/packages/loot-core/src/server/post.ts @@ -4,7 +4,7 @@ import { fetch } from '../platform/server/fetch'; import { PostError } from './errors'; import * as Platform from './platform'; -function throwIfNot200(res, text) { +function throwIfNot200(res: Response, text: string) { if (res.status !== 200) { if (res.status === 500) { throw new PostError(res.status === 500 ? 'internal' : text); @@ -32,9 +32,14 @@ function throwIfNot200(res, text) { } } -export async function post(url, data, headers = {}, timeout = null) { - let text; - let res; +export async function post( + url: RequestInfo, + data: unknown, + headers = {}, + timeout: number | null = null, +) { + let text: string; + let res: Response; try { const controller = new AbortController(); @@ -57,14 +62,16 @@ export async function post(url, data, headers = {}, timeout = null) { throwIfNot200(res, text); + let responseData; + try { - res = JSON.parse(text); + responseData = JSON.parse(text); } catch (err) { // Something seriously went wrong. TODO handle errors throw new PostError('parse-json', { meta: text }); } - if (res.status !== 'ok') { + if (responseData.status !== 'ok') { console.log( 'API call failed: ' + url + @@ -74,10 +81,12 @@ export async function post(url, data, headers = {}, timeout = null) { JSON.stringify(res, null, 2), ); - throw new PostError(res.description || res.reason || 'unknown'); + throw new PostError( + responseData.description || responseData.reason || 'unknown', + ); } - return res.data; + return responseData.data; } export async function del(url, data, headers = {}, timeout = null) { diff --git a/packages/loot-core/src/server/preferences/app.ts b/packages/loot-core/src/server/preferences/app.ts index d1c73344e70..a174da7e797 100644 --- a/packages/loot-core/src/server/preferences/app.ts +++ b/packages/loot-core/src/server/preferences/app.ts @@ -1,24 +1,52 @@ -import { type SyncedPrefs } from '../../types/prefs'; +import * as asyncStorage from '../../platform/server/asyncStorage'; +import * as fs from '../../platform/server/fs'; +import { stringToInteger } from '../../shared/util'; +import { + GlobalPrefs, + MetadataPrefs, + type SyncedPrefs, +} from '../../types/prefs'; import { createApp } from '../app'; import * as db from '../db'; +import { getDefaultDocumentDir } from '../main'; import { mutator } from '../mutators'; +import { post } from '../post'; +import { + getPrefs as _getMetadataPrefs, + savePrefs as _saveMetadataPrefs, +} from '../prefs'; +import { getServer } from '../server-config'; import { undoable } from '../undo'; -import { PreferencesHandlers } from './types/handlers'; +export interface PreferencesHandlers { + 'preferences/save': typeof saveSyncedPrefs; + 'preferences/get': typeof getSyncedPrefs; + 'save-global-prefs': typeof saveGlobalPrefs; + 'load-global-prefs': typeof loadGlobalPrefs; + 'save-prefs': typeof saveMetadataPrefs; + 'load-prefs': typeof loadMetadataPrefs; +} export const app = createApp(); -const savePreferences = async ({ +app.method('preferences/save', mutator(undoable(saveSyncedPrefs))); +app.method('preferences/get', getSyncedPrefs); +app.method('save-global-prefs', saveGlobalPrefs); +app.method('load-global-prefs', loadGlobalPrefs); +app.method('save-prefs', saveMetadataPrefs); +app.method('load-prefs', loadMetadataPrefs); + +async function saveSyncedPrefs({ id, value, }: { id: keyof SyncedPrefs; value: string | undefined; -}) => { +}) { await db.update('preferences', { id, value }); -}; +} -const getPreferences = async (): Promise => { +async function getSyncedPrefs(): Promise { const prefs = (await db.all('SELECT id, value FROM preferences')) as Array<{ id: string; value: string; @@ -28,7 +56,106 @@ const getPreferences = async (): Promise => { carry[id as keyof SyncedPrefs] = value; return carry; }, {}); -}; +} + +async function saveGlobalPrefs(prefs: GlobalPrefs) { + if ('maxMonths' in prefs) { + await asyncStorage.setItem('max-months', '' + prefs.maxMonths); + } + if ('documentDir' in prefs) { + if (prefs.documentDir && (await fs.exists(prefs.documentDir))) { + await asyncStorage.setItem('document-dir', prefs.documentDir); + } + } + if ('floatingSidebar' in prefs) { + await asyncStorage.setItem('floating-sidebar', '' + prefs.floatingSidebar); + } + if ('language' in prefs) { + await asyncStorage.setItem('language', prefs.language); + } + if ('theme' in prefs) { + await asyncStorage.setItem('theme', prefs.theme); + } + if ('preferredDarkTheme' in prefs) { + await asyncStorage.setItem( + 'preferred-dark-theme', + prefs.preferredDarkTheme, + ); + } + if ('serverSelfSignedCert' in prefs) { + await asyncStorage.setItem( + 'server-self-signed-cert', + prefs.serverSelfSignedCert, + ); + } + return 'ok'; +} + +async function loadGlobalPrefs() { + const [ + [, floatingSidebar], + [, maxMonths], + [, documentDir], + [, encryptKey], + [, language], + [, theme], + [, preferredDarkTheme], + [, serverSelfSignedCert], + ] = await asyncStorage.multiGet([ + 'floating-sidebar', + 'max-months', + 'document-dir', + 'encrypt-key', + 'language', + 'theme', + 'preferred-dark-theme', + 'server-self-signed-cert', + ] as const); + return { + floatingSidebar: floatingSidebar === 'true', + maxMonths: stringToInteger(maxMonths || ''), + documentDir: documentDir || getDefaultDocumentDir(), + keyId: encryptKey && JSON.parse(encryptKey).id, + language, + theme: + theme === 'light' || + theme === 'dark' || + theme === 'auto' || + theme === 'development' || + theme === 'midnight' + ? theme + : 'auto', + preferredDarkTheme: + preferredDarkTheme === 'dark' || preferredDarkTheme === 'midnight' + ? preferredDarkTheme + : 'dark', + serverSelfSignedCert: serverSelfSignedCert || undefined, + }; +} + +async function saveMetadataPrefs(prefsToSet: MetadataPrefs) { + const { cloudFileId } = _getMetadataPrefs(); + + // Need to sync the budget name on the server as well + if (prefsToSet.budgetName && cloudFileId) { + const userToken = await asyncStorage.getItem('user-token'); + + const syncServer = getServer()?.SYNC_SERVER; + if (!syncServer) { + throw new Error('No sync server set'); + } + + await post(syncServer + '/update-user-filename', { + token: userToken, + fileId: cloudFileId, + name: prefsToSet.budgetName, + }); + } + + await _saveMetadataPrefs(prefsToSet); + return 'ok'; +} -app.method('preferences/save', mutator(undoable(savePreferences))); -app.method('preferences/get', getPreferences); +async function loadMetadataPrefs(): Promise { + return _getMetadataPrefs(); +} diff --git a/packages/loot-core/src/server/preferences/types/handlers.d.ts b/packages/loot-core/src/server/preferences/types/handlers.d.ts deleted file mode 100644 index 8af5516bd1f..00000000000 --- a/packages/loot-core/src/server/preferences/types/handlers.d.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { type SyncedPrefs } from '../../../types/prefs'; - -export interface PreferencesHandlers { - 'preferences/save': (arg: { - id: keyof SyncedPrefs; - value: string | undefined; - }) => Promise; - - 'preferences/get': () => Promise; -} diff --git a/packages/loot-core/src/server/rules/app.ts b/packages/loot-core/src/server/rules/app.ts index df8b60def1d..a13de653fc6 100644 --- a/packages/loot-core/src/server/rules/app.ts +++ b/packages/loot-core/src/server/rules/app.ts @@ -1,15 +1,16 @@ // @ts-strict-ignore import { type RuleEntity } from '../../types/models'; -import { Condition, Action, rankRules } from '../accounts/rules'; -import * as rules from '../accounts/transaction-rules'; import { createApp } from '../app'; import { RuleError } from '../errors'; import { mutator } from '../mutators'; import { batchMessages } from '../sync'; +import * as rules from '../transactions/transaction-rules'; import { undoable } from '../undo'; import { RulesHandlers } from './types/handlers'; +import { Condition, Action, rankRules } from '.'; + function validateRule(rule: Partial) { // Returns an array of errors, the array is the same link as the // passed-in `array`, or null if there are no errors diff --git a/packages/loot-core/src/server/accounts/rules.test.ts b/packages/loot-core/src/server/rules/index.test.ts similarity index 99% rename from packages/loot-core/src/server/accounts/rules.test.ts rename to packages/loot-core/src/server/rules/index.test.ts index 6935cb7eeb8..e0e5325f6eb 100644 --- a/packages/loot-core/src/server/accounts/rules.test.ts +++ b/packages/loot-core/src/server/rules/index.test.ts @@ -7,7 +7,7 @@ import { Action, Rule, RuleIndexer, -} from './rules'; +} from '.'; describe('Condition', () => { test('parses date formats correctly', () => { diff --git a/packages/loot-core/src/server/accounts/rules.ts b/packages/loot-core/src/server/rules/index.ts similarity index 100% rename from packages/loot-core/src/server/accounts/rules.ts rename to packages/loot-core/src/server/rules/index.ts diff --git a/packages/loot-core/src/server/rules/types/handlers.ts b/packages/loot-core/src/server/rules/types/handlers.ts index 766c45f2be5..18b76a7ce0c 100644 --- a/packages/loot-core/src/server/rules/types/handlers.ts +++ b/packages/loot-core/src/server/rules/types/handlers.ts @@ -1,10 +1,10 @@ // @ts-strict-ignore +import { type Action } from '..'; import { type RuleEntity, type TransactionEntity, type RuleActionEntity, } from '../../../types/models'; -import { type Action } from '../../accounts/rules'; type ValidationError = { conditionErrors: string[]; diff --git a/packages/loot-core/src/server/schedules/app.test.ts b/packages/loot-core/src/server/schedules/app.test.ts index 2aa6e47c1b1..a46ea12de06 100644 --- a/packages/loot-core/src/server/schedules/app.test.ts +++ b/packages/loot-core/src/server/schedules/app.test.ts @@ -3,9 +3,9 @@ import MockDate from 'mockdate'; import { q } from '../../shared/query'; import { getNextDate } from '../../shared/schedules'; -import { loadRules, updateRule } from '../accounts/transaction-rules'; import { runQuery as aqlQuery } from '../aql'; import { loadMappings } from '../db/mappings'; +import { loadRules, updateRule } from '../transactions/transaction-rules'; import { updateConditions, diff --git a/packages/loot-core/src/server/schedules/app.ts b/packages/loot-core/src/server/schedules/app.ts index 7016d6f627a..06c8e64999f 100644 --- a/packages/loot-core/src/server/schedules/app.ts +++ b/packages/loot-core/src/server/schedules/app.ts @@ -16,21 +16,21 @@ import { getStatus, recurConfigToRSchedule, } from '../../shared/schedules'; -import { Rule } from '../accounts/rules'; import { addTransactions } from '../accounts/sync'; -import { - getRules, - insertRule, - ruleModel, - updateRule, -} from '../accounts/transaction-rules'; import { createApp } from '../app'; import { runQuery as aqlQuery } from '../aql'; import * as db from '../db'; import { toDateRepr } from '../models'; import { mutator, runMutator } from '../mutators'; import * as prefs from '../prefs'; +import { Rule } from '../rules'; import { addSyncListener, batchMessages } from '../sync'; +import { + getRules, + insertRule, + ruleModel, + updateRule, +} from '../transactions/transaction-rules'; import { undoable } from '../undo'; import { Schedule as RSchedule } from '../util/rschedule'; diff --git a/packages/loot-core/src/server/schedules/find-schedules.ts b/packages/loot-core/src/server/schedules/find-schedules.ts index 8b45406138b..8d6df7354d0 100644 --- a/packages/loot-core/src/server/schedules/find-schedules.ts +++ b/packages/loot-core/src/server/schedules/find-schedules.ts @@ -7,10 +7,10 @@ import { q } from '../../shared/query'; import { getApproxNumberThreshold } from '../../shared/rules'; import { recurConfigToRSchedule } from '../../shared/schedules'; import { groupBy } from '../../shared/util'; -import { conditionsToAQL } from '../accounts/transaction-rules'; import { runQuery as aqlQuery } from '../aql'; import * as db from '../db'; import { fromDateRepr } from '../models'; +import { conditionsToAQL } from '../transactions/transaction-rules'; import { Schedule as RSchedule } from '../util/rschedule'; import { SchedulesHandlers } from './types/handlers'; diff --git a/packages/loot-core/src/server/sync/index.ts b/packages/loot-core/src/server/sync/index.ts index 7c4b03adb58..5888fb768a8 100644 --- a/packages/loot-core/src/server/sync/index.ts +++ b/packages/loot-core/src/server/sync/index.ts @@ -198,7 +198,7 @@ async function compareMessages(messages: Message[]): Promise { const { dataset, row, column, timestamp } = message; const timestampStr = timestamp.toString(); - const res = db.runQuery( + const res = db.runQuery>( db.cache( 'SELECT timestamp FROM messages_crdt WHERE dataset = ? AND row = ? AND column = ? AND timestamp >= ?', ), diff --git a/packages/loot-core/src/server/sync/migrate.test.ts b/packages/loot-core/src/server/sync/migrate.test.ts index 6959766b23f..7fec886fcdf 100644 --- a/packages/loot-core/src/server/sync/migrate.test.ts +++ b/packages/loot-core/src/server/sync/migrate.test.ts @@ -79,7 +79,11 @@ describe('sync migrations', () => { tracer.expectNow('applied', ['trans1/child1']); await tracer.expectWait('applied', ['trans1/child1']); - const transactions = db.runQuery('SELECT * FROM transactions', [], true); + const transactions = db.runQuery( + 'SELECT * FROM transactions', + [], + true, + ); expect(transactions.length).toBe(1); expect(transactions[0].parent_id).toBe('trans1'); diff --git a/packages/loot-core/src/server/tools/app.ts b/packages/loot-core/src/server/tools/app.ts index bc6b0321c34..60d23277309 100644 --- a/packages/loot-core/src/server/tools/app.ts +++ b/packages/loot-core/src/server/tools/app.ts @@ -1,10 +1,10 @@ // @ts-strict-ignore import { q } from '../../shared/query'; -import { batchUpdateTransactions } from '../accounts/transactions'; import { createApp } from '../app'; import { runQuery } from '../aql'; import * as db from '../db'; import { runMutator } from '../mutators'; +import { batchUpdateTransactions } from '../transactions'; import { ToolsHandlers } from './types/handlers'; @@ -52,7 +52,7 @@ app.method('tools/fix-split-transactions', async () => { `); await runMutator(async () => { - const updated = deletedRows.map(row => ({ id: row.id, tombstone: 1 })); + const updated = deletedRows.map(row => ({ id: row.id, tombstone: true })); await batchUpdateTransactions({ updated }); }); diff --git a/packages/loot-core/src/server/accounts/__snapshots__/transaction-rules.test.ts.snap b/packages/loot-core/src/server/transactions/__snapshots__/transaction-rules.test.ts.snap similarity index 100% rename from packages/loot-core/src/server/accounts/__snapshots__/transaction-rules.test.ts.snap rename to packages/loot-core/src/server/transactions/__snapshots__/transaction-rules.test.ts.snap diff --git a/packages/loot-core/src/server/accounts/__snapshots__/transfer.test.ts.snap b/packages/loot-core/src/server/transactions/__snapshots__/transfer.test.ts.snap similarity index 100% rename from packages/loot-core/src/server/accounts/__snapshots__/transfer.test.ts.snap rename to packages/loot-core/src/server/transactions/__snapshots__/transfer.test.ts.snap diff --git a/packages/loot-core/src/server/transactions/app.ts b/packages/loot-core/src/server/transactions/app.ts new file mode 100644 index 00000000000..d762be8f1ce --- /dev/null +++ b/packages/loot-core/src/server/transactions/app.ts @@ -0,0 +1,116 @@ +import { q, Query, QueryState } from '../../shared/query'; +import { + AccountEntity, + CategoryGroupEntity, + PayeeEntity, + TransactionEntity, +} from '../../types/models'; +import { createApp } from '../app'; +import { runQuery } from '../aql'; +import { mutator } from '../mutators'; +import { undoable } from '../undo'; + +import { exportQueryToCSV, exportToCSV } from './export/export-to-csv'; +import { parseFile, ParseFileOptions } from './import/parse-file'; + +import { batchUpdateTransactions } from '.'; + +export type TransactionHandlers = { + 'transactions-batch-update': typeof handleBatchUpdateTransactions; + 'transaction-add': typeof addTransaction; + 'transaction-update': typeof updateTransaction; + 'transaction-delete': typeof deleteTransaction; + 'transactions-parse-file': typeof parseTransactionsFile; + 'transactions-export': typeof exportTransactions; + 'transactions-export-query': typeof exportTransactionsQuery; + 'get-earliest-transaction': typeof getEarliestTransaction; +}; + +async function handleBatchUpdateTransactions({ + added, + deleted, + updated, + learnCategories, +}: Parameters[0]) { + const result = await batchUpdateTransactions({ + added, + updated, + deleted, + learnCategories, + }); + + return result; +} + +async function addTransaction(transaction: TransactionEntity) { + await handleBatchUpdateTransactions({ added: [transaction] }); + return {}; +} + +async function updateTransaction(transaction: TransactionEntity) { + await handleBatchUpdateTransactions({ updated: [transaction] }); + return {}; +} + +async function deleteTransaction(transaction: Pick) { + await handleBatchUpdateTransactions({ deleted: [transaction] }); + return {}; +} + +async function parseTransactionsFile({ + filepath, + options, +}: { + filepath: string; + options: ParseFileOptions; +}) { + return parseFile(filepath, options); +} + +async function exportTransactions({ + transactions, + accounts, + categoryGroups, + payees, +}: { + transactions: TransactionEntity[]; + accounts: AccountEntity[]; + categoryGroups: CategoryGroupEntity[]; + payees: PayeeEntity[]; +}) { + return exportToCSV(transactions, accounts, categoryGroups, payees); +} + +async function exportTransactionsQuery({ + query: queryState, +}: { + query: QueryState; +}) { + return exportQueryToCSV(new Query(queryState)); +} + +async function getEarliestTransaction() { + const { data } = await runQuery( + q('transactions') + .options({ splits: 'none' }) + .orderBy({ date: 'asc' }) + .select('*') + .limit(1), + ); + return data[0] || null; +} + +export const app = createApp(); + +app.method( + 'transactions-batch-update', + mutator(undoable(handleBatchUpdateTransactions)), +); + +app.method('transaction-add', mutator(addTransaction)); +app.method('transaction-update', mutator(updateTransaction)); +app.method('transaction-delete', mutator(deleteTransaction)); +app.method('transactions-parse-file', mutator(parseTransactionsFile)); +app.method('transactions-export', mutator(exportTransactions)); +app.method('transactions-export-query', mutator(exportTransactionsQuery)); +app.method('get-earliest-transaction', getEarliestTransaction); diff --git a/packages/loot-core/src/server/accounts/export-to-csv.ts b/packages/loot-core/src/server/transactions/export/export-to-csv.ts similarity index 97% rename from packages/loot-core/src/server/accounts/export-to-csv.ts rename to packages/loot-core/src/server/transactions/export/export-to-csv.ts index 7b8dda5af4a..7035069fd18 100644 --- a/packages/loot-core/src/server/accounts/export-to-csv.ts +++ b/packages/loot-core/src/server/transactions/export/export-to-csv.ts @@ -1,8 +1,8 @@ // @ts-strict-ignore import csvStringify from 'csv-stringify/lib/sync'; -import { integerToAmount } from '../../shared/util'; -import { runQuery as aqlQuery } from '../aql'; +import { integerToAmount } from '../../../shared/util'; +import { runQuery as aqlQuery } from '../../aql'; export async function exportToCSV( transactions, diff --git a/packages/loot-core/src/server/accounts/__snapshots__/parse-file.test.ts.snap b/packages/loot-core/src/server/transactions/import/__snapshots__/parse-file.test.ts.snap similarity index 100% rename from packages/loot-core/src/server/accounts/__snapshots__/parse-file.test.ts.snap rename to packages/loot-core/src/server/transactions/import/__snapshots__/parse-file.test.ts.snap diff --git a/packages/loot-core/src/server/accounts/ofx2json.ts b/packages/loot-core/src/server/transactions/import/ofx2json.ts similarity index 98% rename from packages/loot-core/src/server/accounts/ofx2json.ts rename to packages/loot-core/src/server/transactions/import/ofx2json.ts index b5ad38faa88..91fc8abeb55 100644 --- a/packages/loot-core/src/server/accounts/ofx2json.ts +++ b/packages/loot-core/src/server/transactions/import/ofx2json.ts @@ -1,7 +1,7 @@ // @ts-strict-ignore import { parseStringPromise } from 'xml2js'; -import { dayFromDate } from '../../shared/months'; +import { dayFromDate } from '../../../shared/months'; type OFXTransaction = { amount: string; diff --git a/packages/loot-core/src/server/accounts/parse-file.test.ts b/packages/loot-core/src/server/transactions/import/parse-file.test.ts similarity index 85% rename from packages/loot-core/src/server/accounts/parse-file.test.ts rename to packages/loot-core/src/server/transactions/import/parse-file.test.ts index 04cfc9f0892..9fbfff4df89 100644 --- a/packages/loot-core/src/server/accounts/parse-file.test.ts +++ b/packages/loot-core/src/server/transactions/import/parse-file.test.ts @@ -1,12 +1,12 @@ // @ts-strict-ignore import * as d from 'date-fns'; -import { amountToInteger } from '../../shared/util'; -import * as db from '../db'; -import * as prefs from '../prefs'; +import { amountToInteger } from '../../../shared/util'; +import { reconcileTransactions } from '../../accounts/sync'; +import * as db from '../../db'; +import * as prefs from '../../prefs'; import { parseFile } from './parse-file'; -import { reconcileTransactions } from './sync'; beforeEach(global.emptyDatabase()); @@ -65,7 +65,7 @@ describe('File import', () => { await db.insertAccount({ id: 'one', name: 'one' }); const { errors } = await importFileWithRealTime( 'one', - __dirname + '/../../mocks/files/data.qif', + __dirname + '/../../../mocks/files/data.qif', 'MM/dd/yy', ); expect(errors.length).toBe(0); @@ -78,7 +78,7 @@ describe('File import', () => { const { errors } = await importFileWithRealTime( 'one', - __dirname + '/../../mocks/files/data.ofx', + __dirname + '/../../../mocks/files/data.ofx', ); expect(errors.length).toBe(0); expect(await getTransactions('one')).toMatchSnapshot(); @@ -90,7 +90,7 @@ describe('File import', () => { const { errors } = await importFileWithRealTime( 'one', - __dirname + '/../../mocks/files/credit-card.ofx', + __dirname + '/../../../mocks/files/credit-card.ofx', ); expect(errors.length).toBe(0); expect(await getTransactions('one')).toMatchSnapshot(); @@ -102,7 +102,7 @@ describe('File import', () => { const { errors } = await importFileWithRealTime( 'one', - __dirname + '/../../mocks/files/data.qfx', + __dirname + '/../../../mocks/files/data.qfx', ); expect(errors.length).toBe(0); expect(await getTransactions('one')).toMatchSnapshot(); @@ -114,13 +114,13 @@ describe('File import', () => { let res = await importFileWithRealTime( 'one', - __dirname + '/../../mocks/files/best.data-ever$.QFX', + __dirname + '/../../../mocks/files/best.data-ever$.QFX', ); expect(res.errors.length).toBe(0); res = await importFileWithRealTime( 'one', - __dirname + '/../../mocks/files/big.data.QiF', + __dirname + '/../../../mocks/files/big.data.QiF', 'MM/dd/yy', ); expect(res.errors.length).toBe(0); @@ -136,7 +136,7 @@ describe('File import', () => { const { errors } = await importFileWithRealTime( 'one', - __dirname + '/../../mocks/files/8859-1.qfx', + __dirname + '/../../../mocks/files/8859-1.qfx', 'yyyy-MM-dd', ); expect(errors.length).toBe(0); @@ -149,7 +149,7 @@ describe('File import', () => { const { errors } = await importFileWithRealTime( 'one', - __dirname + '/../../mocks/files/html-vals.qfx', + __dirname + '/../../../mocks/files/html-vals.qfx', 'yyyy-MM-dd', ); expect(errors.length).toBe(0); @@ -162,7 +162,7 @@ describe('File import', () => { const { errors } = await importFileWithRealTime( 'one', - __dirname + '/../../mocks/files/camt/camt.053.xml', + __dirname + '/../../../mocks/files/camt/camt.053.xml', ); expect(errors.length).toBe(0); expect(await getTransactions('one')).toMatchSnapshot(); diff --git a/packages/loot-core/src/server/accounts/parse-file.ts b/packages/loot-core/src/server/transactions/import/parse-file.ts similarity index 96% rename from packages/loot-core/src/server/accounts/parse-file.ts rename to packages/loot-core/src/server/transactions/import/parse-file.ts index fd6178411cd..f551306018c 100644 --- a/packages/loot-core/src/server/accounts/parse-file.ts +++ b/packages/loot-core/src/server/transactions/import/parse-file.ts @@ -1,8 +1,8 @@ // @ts-strict-ignore import csv2json from 'csv-parse/lib/sync'; -import * as fs from '../../platform/server/fs'; -import { looselyParseAmount } from '../../shared/util'; +import * as fs from '../../../platform/server/fs'; +import { looselyParseAmount } from '../../../shared/util'; import { ofx2json } from './ofx2json'; import { qif2json } from './qif2json'; @@ -14,7 +14,7 @@ export type ParseFileResult = { transactions?: unknown[]; }; -type ParseFileOptions = { +export type ParseFileOptions = { hasHeaderRow?: boolean; delimiter?: string; fallbackMissingPayeeToMemo?: boolean; diff --git a/packages/loot-core/src/server/accounts/qif2json.ts b/packages/loot-core/src/server/transactions/import/qif2json.ts similarity index 100% rename from packages/loot-core/src/server/accounts/qif2json.ts rename to packages/loot-core/src/server/transactions/import/qif2json.ts diff --git a/packages/loot-core/src/server/accounts/xmlcamt2json.ts b/packages/loot-core/src/server/transactions/import/xmlcamt2json.ts similarity index 100% rename from packages/loot-core/src/server/accounts/xmlcamt2json.ts rename to packages/loot-core/src/server/transactions/import/xmlcamt2json.ts diff --git a/packages/loot-core/src/server/accounts/transactions.ts b/packages/loot-core/src/server/transactions/index.ts similarity index 99% rename from packages/loot-core/src/server/accounts/transactions.ts rename to packages/loot-core/src/server/transactions/index.ts index cacc9dbd4b3..e306d749131 100644 --- a/packages/loot-core/src/server/accounts/transactions.ts +++ b/packages/loot-core/src/server/transactions/index.ts @@ -1,4 +1,5 @@ // @ts-strict-ignore + import * as connection from '../../platform/server/connection'; import { Diff } from '../../shared/util'; import { PayeeEntity, TransactionEntity } from '../../types/models'; diff --git a/packages/loot-core/src/server/accounts/transaction-rules.test.ts b/packages/loot-core/src/server/transactions/transaction-rules.test.ts similarity index 100% rename from packages/loot-core/src/server/accounts/transaction-rules.test.ts rename to packages/loot-core/src/server/transactions/transaction-rules.test.ts diff --git a/packages/loot-core/src/server/accounts/transaction-rules.ts b/packages/loot-core/src/server/transactions/transaction-rules.ts similarity index 99% rename from packages/loot-core/src/server/accounts/transaction-rules.ts rename to packages/loot-core/src/server/transactions/transaction-rules.ts index 4e644f59524..40448d3ac40 100644 --- a/packages/loot-core/src/server/accounts/transaction-rules.ts +++ b/packages/loot-core/src/server/transactions/transaction-rules.ts @@ -1,4 +1,5 @@ // @ts-strict-ignore + import { currentDay, addDays, @@ -21,8 +22,6 @@ import { getPayee, getPayeeByName, insertPayee, getAccount } from '../db'; import { getMappings } from '../db/mappings'; import { RuleError } from '../errors'; import { requiredFields, toDateRepr } from '../models'; -import { batchMessages, addSyncListener } from '../sync'; - import { Condition, Action, @@ -32,15 +31,17 @@ import { migrateIds, iterateIds, execActions, -} from './rules'; -import { batchUpdateTransactions } from './transactions'; +} from '../rules'; +import { batchMessages, addSyncListener } from '../sync'; + +import { batchUpdateTransactions } from '.'; // TODO: Detect if it looks like the user is creating a rename rule // and prompt to create it in the pre phase instead // * We could also make the "create rule" button a dropdown that // provides different "templates" like "create renaming rule" -export { iterateIds } from './rules'; +export { iterateIds } from '../rules'; let allRules; let unlistenSync; diff --git a/packages/loot-core/src/server/accounts/transfer.test.ts b/packages/loot-core/src/server/transactions/transfer.test.ts similarity index 100% rename from packages/loot-core/src/server/accounts/transfer.test.ts rename to packages/loot-core/src/server/transactions/transfer.test.ts diff --git a/packages/loot-core/src/server/accounts/transfer.ts b/packages/loot-core/src/server/transactions/transfer.ts similarity index 97% rename from packages/loot-core/src/server/accounts/transfer.ts rename to packages/loot-core/src/server/transactions/transfer.ts index d180244dd9c..4538e4c26c8 100644 --- a/packages/loot-core/src/server/accounts/transfer.ts +++ b/packages/loot-core/src/server/transactions/transfer.ts @@ -7,11 +7,12 @@ async function getPayee(acct) { async function getTransferredAccount(transaction) { if (transaction.payee) { - const { transfer_acct } = await db.first( - 'SELECT id, transfer_acct FROM v_payees WHERE id = ?', + const result = await db.first( + 'SELECT transfer_acct FROM v_payees WHERE id = ?', [transaction.payee], ); - return transfer_acct; + + return result?.transfer_acct || null; } return null; } diff --git a/packages/loot-core/src/shared/schedules.ts b/packages/loot-core/src/shared/schedules.ts index 584f66fe4f5..d41370c2f28 100644 --- a/packages/loot-core/src/shared/schedules.ts +++ b/packages/loot-core/src/shared/schedules.ts @@ -2,7 +2,7 @@ import type { IRuleOptions } from '@rschedule/core'; import * as d from 'date-fns'; -import { Condition } from '../server/accounts/rules'; +import { Condition } from '../server/rules'; import * as monthUtils from './months'; import { q } from './query'; diff --git a/packages/loot-core/src/shared/util.ts b/packages/loot-core/src/shared/util.ts index 9c603384b57..9ce69619c28 100644 --- a/packages/loot-core/src/shared/util.ts +++ b/packages/loot-core/src/shared/util.ts @@ -210,7 +210,7 @@ export function appendDecimals( amountText: string, hideDecimals = false, ): string { - const { separator } = getNumberFormat(); + const { decimalSeparator: separator } = getNumberFormat(); let result = amountText; if (result.slice(-1) === separator) { result = result.slice(0, -1); @@ -283,47 +283,44 @@ export function getNumberFormat({ format?: NumberFormats; hideFraction: boolean; } = numberFormatConfig) { - let locale, regex, separator, separatorRegex; + let locale, thousandsSeparator, decimalSeparator; switch (format) { case 'space-comma': locale = 'en-SE'; - regex = /[^-0-9,.]/g; - separator = ','; - separatorRegex = /[,.]/g; + thousandsSeparator = '\xa0'; + decimalSeparator = ','; break; case 'dot-comma': locale = 'de-DE'; - regex = /[^-0-9,]/g; - separator = ','; + thousandsSeparator = '.'; + decimalSeparator = ','; break; case 'apostrophe-dot': locale = 'de-CH'; - regex = /[^-0-9,.]/g; - separator = '.'; - separatorRegex = /[,.]/g; + thousandsSeparator = '’'; + decimalSeparator = '.'; break; case 'comma-dot-in': locale = 'en-IN'; - regex = /[^-0-9.]/g; - separator = '.'; + thousandsSeparator = ','; + decimalSeparator = '.'; break; case 'comma-dot': default: locale = 'en-US'; - regex = /[^-0-9.]/g; - separator = '.'; + thousandsSeparator = ','; + decimalSeparator = '.'; } return { value: format, - separator, + thousandsSeparator, + decimalSeparator, formatter: new Intl.NumberFormat(locale, { minimumFractionDigits: hideFraction ? 0 : 2, maximumFractionDigits: hideFraction ? 0 : 2, }), - regex, - separatorRegex, }; } @@ -390,23 +387,25 @@ export function amountToCurrencyNoDecimal(amount: Amount): CurrencyAmount { }).formatter.format(amount); } -export function currencyToAmount( - currencyAmount: CurrencyAmount, -): Amount | null { - let amount; - if (getNumberFormat().separatorRegex) { - amount = parseFloat( - currencyAmount - .replace(getNumberFormat().regex, '') - .replace(getNumberFormat().separatorRegex, '.'), - ); +export function currencyToAmount(currencyAmount: string): Amount | null { + let integer, fraction; + + // match the last dot or comma in the string + const match = currencyAmount.match(/[,.](?=[^.,]*$)/); + + if ( + !match || + (match[0] === getNumberFormat().thousandsSeparator && + match.index + 4 === currencyAmount.length) + ) { + fraction = null; + integer = currencyAmount.replace(/\D/g, ''); } else { - amount = parseFloat( - currencyAmount - .replace(getNumberFormat().regex, '') - .replace(getNumberFormat().separator, '.'), - ); + integer = currencyAmount.slice(0, match.index).replace(/\D/g, ''); + fraction = currencyAmount.slice(match.index + 1); } + + const amount = parseFloat(integer + '.' + fraction); return isNaN(amount) ? null : amount; } diff --git a/packages/loot-core/src/types/api-handlers.d.ts b/packages/loot-core/src/types/api-handlers.d.ts index 37ca5336689..1d31dc097fb 100644 --- a/packages/loot-core/src/types/api-handlers.d.ts +++ b/packages/loot-core/src/types/api-handlers.d.ts @@ -1,6 +1,5 @@ import { ImportTransactionsOpts } from '@actual-app/api'; -import { type batchUpdateTransactions } from '../server/accounts/transactions'; import type { APIAccountEntity, APICategoryEntity, @@ -8,6 +7,7 @@ import type { APIFileEntity, APIPayeeEntity, } from '../server/api-models'; +import { type batchUpdateTransactions } from '../server/transactions'; import type { NewRuleEntity, RuleEntity, TransactionEntity } from './models'; import { type ServerHandlers } from './server-handlers'; @@ -76,6 +76,7 @@ export interface ApiHandlers { transactions; categoryGroups; payees; + accounts; }) => Promise; 'api/transactions-import': (arg: { diff --git a/packages/loot-core/src/types/handlers.d.ts b/packages/loot-core/src/types/handlers.d.ts index cf5b2c1bec8..24f666d08d5 100644 --- a/packages/loot-core/src/types/handlers.d.ts +++ b/packages/loot-core/src/types/handlers.d.ts @@ -1,13 +1,15 @@ +import type { AccountHandlers } from '../server/accounts/app'; import type { AdminHandlers } from '../server/admin/types/handlers'; import type { BudgetHandlers } from '../server/budget/types/handlers'; import type { DashboardHandlers } from '../server/dashboard/types/handlers'; import type { FiltersHandlers } from '../server/filters/types/handlers'; import type { NotesHandlers } from '../server/notes/types/handlers'; -import type { PreferencesHandlers } from '../server/preferences/types/handlers'; +import type { PreferencesHandlers } from '../server/preferences/app'; import type { ReportsHandlers } from '../server/reports/types/handlers'; import type { RulesHandlers } from '../server/rules/types/handlers'; import type { SchedulesHandlers } from '../server/schedules/types/handlers'; import type { ToolsHandlers } from '../server/tools/types/handlers'; +import type { TransactionHandlers } from '../server/transactions/app'; import type { ApiHandlers } from './api-handlers'; import type { ServerHandlers } from './server-handlers'; @@ -23,7 +25,9 @@ export interface Handlers ReportsHandlers, RulesHandlers, SchedulesHandlers, + TransactionHandlers, AdminHandlers, - ToolsHandlers {} + ToolsHandlers, + AccountHandlers {} export type HandlerFunctions = Handlers[keyof Handlers]; diff --git a/packages/loot-core/src/types/models/bank.d.ts b/packages/loot-core/src/types/models/bank.d.ts new file mode 100644 index 00000000000..6630e5670f3 --- /dev/null +++ b/packages/loot-core/src/types/models/bank.d.ts @@ -0,0 +1,6 @@ +export type BankEntity = { + id: string; + name: string; + bank_id: string; + tombstone: 0 | 1; +}; diff --git a/packages/loot-core/src/types/models/gocardless.d.ts b/packages/loot-core/src/types/models/gocardless.d.ts index b1003e88369..365c3314c97 100644 --- a/packages/loot-core/src/types/models/gocardless.d.ts +++ b/packages/loot-core/src/types/models/gocardless.d.ts @@ -73,3 +73,11 @@ export type GoCardlessTransaction = { valueDate?: string; valueDateTime?: string; }; + +export type SyncServerGoCardlessAccount = { + institution: string; + account_id: string; + mask: string; + name: string; + official_name: string; +}; diff --git a/packages/loot-core/src/types/models/simplefin.d.ts b/packages/loot-core/src/types/models/simplefin.d.ts index cd76cb7ba14..31ff188fb98 100644 --- a/packages/loot-core/src/types/models/simplefin.d.ts +++ b/packages/loot-core/src/types/models/simplefin.d.ts @@ -17,3 +17,11 @@ export type SimpleFinAccount = { export interface SimpleFinBatchSyncResponse { [accountId: AccountEntity['account_id']]: BankSyncResponse; } + +export type SyncServerSimpleFinAccount = { + account_id: string; + institution?: string; + orgDomain?: string; + orgId?: string; + name: string; +}; diff --git a/packages/loot-core/src/types/prefs.d.ts b/packages/loot-core/src/types/prefs.d.ts index d3c044ceb6b..fad8fc946d3 100644 --- a/packages/loot-core/src/types/prefs.d.ts +++ b/packages/loot-core/src/types/prefs.d.ts @@ -1,5 +1,6 @@ export type FeatureFlag = | 'goalTemplatesEnabled' + | 'goalTemplatesUIEnabled' | 'actionTemplating' | 'contextMenus' | 'openidAuth'; @@ -76,6 +77,8 @@ export type LocalPrefs = Partial<{ export type Theme = 'light' | 'dark' | 'auto' | 'midnight' | 'development'; export type DarkTheme = 'dark' | 'midnight'; + +// GlobalPrefs are the parsed global-store.json values export type GlobalPrefs = Partial<{ floatingSidebar: boolean; maxMonths: number; @@ -87,4 +90,24 @@ export type GlobalPrefs = Partial<{ serverSelfSignedCert: string; // Electron only }>; +// GlobalPrefsJson represents what's saved in the global-store.json file +export type GlobalPrefsJson = Partial<{ + 'user-id'?: string; + 'user-key'?: string; + 'encrypt-keys'?: string; + lastBudget?: string; + readOnly?: string; + 'server-url'?: string; + 'did-bootstrap'?: boolean; + 'user-token'?: string; + 'floating-sidebar'?: string; // "true" or "false" + 'max-months'?: string; // e.g. "2" or "3" + 'document-dir'?: GlobalPrefs['documentDir']; + 'encrypt-key'?: string; + language?: GlobalPrefs['language']; + theme?: GlobalPrefs['theme']; + 'preferred-dark-theme'?: GlobalPrefs['preferredDarkTheme']; + 'server-self-signed-cert'?: GlobalPrefs['serverSelfSignedCert']; +}>; + export type AuthMethods = 'password' | 'openid'; diff --git a/packages/loot-core/src/types/server-handlers.d.ts b/packages/loot-core/src/types/server-handlers.d.ts index 71dd1ec9cbe..26ff36f1906 100644 --- a/packages/loot-core/src/types/server-handlers.d.ts +++ b/packages/loot-core/src/types/server-handlers.d.ts @@ -1,59 +1,24 @@ -import { ImportTransactionsOpts } from '@actual-app/api'; - -import { ParseFileResult } from '../server/accounts/parse-file'; -import { batchUpdateTransactions } from '../server/accounts/transactions'; import { Backup } from '../server/backups'; import { RemoteFile } from '../server/cloud-storage'; import { Node as SpreadsheetNode } from '../server/spreadsheet/spreadsheet'; import { Message } from '../server/sync'; -import { QueryState } from '../shared/query'; import { Budget } from './budget'; import { - AccountEntity, CategoryEntity, CategoryGroupEntity, - GoCardlessToken, - GoCardlessInstitution, - SimpleFinAccount, RuleEntity, PayeeEntity, } from './models'; import { OpenIdConfig } from './models/openid'; -import { GlobalPrefs, MetadataPrefs } from './prefs'; // eslint-disable-next-line import/no-unresolved import { Query } from './query'; import { EmptyObject } from './util'; export interface ServerHandlers { - 'transaction-update': (transaction: { id: string }) => Promise; - undo: () => Promise; - redo: () => Promise; - 'transactions-batch-update': ( - ...arg: Parameters - ) => ReturnType; - - 'transaction-add': (transaction) => Promise; - - 'transaction-delete': (transaction) => Promise; - - 'transactions-parse-file': (arg: { - filepath: string; - options; - }) => Promise; - - 'transactions-export': (arg: { - transactions; - accounts?; - categoryGroups; - payees; - }) => Promise; - - 'transactions-export-query': (arg: { query: QueryState }) => Promise; - 'get-categories': () => Promise<{ grouped: Array; list: Array; @@ -130,144 +95,27 @@ export interface ServerHandlers { applySpecialCases?: boolean; }) => Promise<{ filters: unknown[] }>; - getCell: (arg: { - sheetName; - name; - }) => Promise; - - getCells: (arg: { names }) => Promise; - - getCellNamesInSheet: (arg: { sheetName }) => Promise; - - debugCell: (arg: { sheetName; name }) => Promise; - - 'create-query': (arg: { sheetName; name; query }) => Promise; - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - query: (query: Query) => Promise<{ data: any; dependencies: string[] }>; - - 'account-update': (arg: { id; name }) => Promise; - - 'accounts-get': () => Promise; - - 'account-properties': (arg: { - id; - }) => Promise<{ balance: number; numTransactions: number }>; - - 'gocardless-accounts-link': (arg: { - requisitionId; - account; - upgradingId; - offBudget; - }) => Promise<'ok'>; - - 'simplefin-accounts-link': (arg: { - externalAccount; - upgradingId; - offBudget; - }) => Promise<'ok'>; - - 'account-create': (arg: { - name: string; - balance?: number; - offBudget?: boolean; - closed?: 0 | 1; - }) => Promise; - - 'account-close': (arg: { - id; - transferAccountId?; - categoryId?; - forced?; - }) => Promise; - - 'account-reopen': (arg: { id }) => Promise; - - 'account-move': (arg: { id; targetId }) => Promise; - - 'secret-set': (arg: { - name: string; - value: string | null; - }) => Promise<{ error?: string; reason?: string }>; - 'secret-check': (arg: string) => Promise; - - 'gocardless-poll-web-token': (arg: { - upgradingAccountId?: string | undefined; - requisitionId: string; - }) => Promise< - { error: 'unknown' } | { error: 'timeout' } | { data: GoCardlessToken } - >; - - 'gocardless-status': () => Promise<{ configured: boolean }>; - - 'simplefin-status': () => Promise<{ configured: boolean }>; - - 'simplefin-accounts': () => Promise<{ - accounts?: SimpleFinAccount[]; - error_code?: string; - reason?: string; + getCell: (arg: { sheetName; name }) => Promise<{ + name: SpreadsheetNode['name']; + value: SpreadsheetNode['value']; }>; - 'simplefin-batch-sync': ({ ids }: { ids: string[] }) => Promise< - { - accountId: string; - res: { - errors; - newTransactions; - matchedTransactions; - updatedAccounts; - }; - }[] - >; - - 'gocardless-get-banks': (country: string) => Promise<{ - data: GoCardlessInstitution[]; - error?: { reason: string }; - }>; - - 'gocardless-poll-web-token-stop': () => Promise<'ok'>; - - 'gocardless-create-web-token': (arg: { - upgradingAccountId?: string | undefined; - institutionId: string; - accessValidForDays: number; + getCells: (arg: { + names; }) => Promise< - | { - requisitionId: string; - link: string; - } - | { error: 'unauthorized' } - | { error: 'failed' } + Array<{ name: SpreadsheetNode['name']; value?: SpreadsheetNode['value'] }> >; - 'accounts-bank-sync': (arg: { ids?: AccountEntity['id'][] }) => Promise<{ - errors; - newTransactions; - matchedTransactions; - updatedAccounts; - }>; - - 'transactions-import': (arg: { - accountId; - transactions; - isPreview; - opts?: ImportTransactionsOpts; - }) => Promise<{ - errors?: { message: string }[]; - added; - updated; - updatedPreview; - }>; - - 'account-unlink': (arg: { id }) => Promise<'ok'>; - - 'save-global-prefs': (prefs) => Promise<'ok'>; + getCellNamesInSheet: (arg: { + sheetName; + }) => Promise>; - 'load-global-prefs': () => Promise; + debugCell: (arg: { sheetName; name }) => Promise; - 'save-prefs': (prefsToSet) => Promise<'ok'>; + 'create-query': (arg: { sheetName; name; query }) => Promise<'ok'>; - 'load-prefs': () => Promise; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + query: (query: Query) => Promise<{ data: any; dependencies: string[] }>; 'sync-reset': () => Promise<{ error?: { reason: string; meta?: unknown } }>; diff --git a/packages/loot-core/webpack/webpack.browser.config.js b/packages/loot-core/webpack/webpack.browser.config.js index 096633ea009..0b9aba5e5c3 100644 --- a/packages/loot-core/webpack/webpack.browser.config.js +++ b/packages/loot-core/webpack/webpack.browser.config.js @@ -62,6 +62,12 @@ module.exports = { test: /\.pegjs$/, use: { loader: path.resolve(__dirname, '../peg-loader.js') }, }, + { + test: /\.m?js/, + resolve: { + fullySpecified: false, + }, + }, ], }, optimization: { diff --git a/packages/sync-server/docker/edge-alpine.Dockerfile b/packages/sync-server/docker/edge-alpine.Dockerfile index 070ae8f65ec..48a7fd69533 100644 --- a/packages/sync-server/docker/edge-alpine.Dockerfile +++ b/packages/sync-server/docker/edge-alpine.Dockerfile @@ -4,7 +4,7 @@ WORKDIR /app COPY .yarn ./.yarn COPY yarn.lock packages/sync-server/package.json .yarnrc.yml ./ RUN if [ "$(uname -m)" = "armv7l" ]; then yarn config set taskPoolConcurrency 2; yarn config set networkConcurrency 5; fi -RUN yarn workspaces focus actual-sync --production +RUN yarn workspaces focus @actual-app/sync-server --production RUN if [ "$(uname -m)" = "armv7l" ]; then npm install bcrypt better-sqlite3 --build-from-source; fi RUN mkdir /public diff --git a/packages/sync-server/docker/edge-ubuntu.Dockerfile b/packages/sync-server/docker/edge-ubuntu.Dockerfile index b0744a09548..18e009dad96 100644 --- a/packages/sync-server/docker/edge-ubuntu.Dockerfile +++ b/packages/sync-server/docker/edge-ubuntu.Dockerfile @@ -4,7 +4,7 @@ WORKDIR /app COPY .yarn ./.yarn COPY yarn.lock packages/sync-server/package.json .yarnrc.yml ./ RUN if [ "$(uname -m)" = "armv7l" ]; then yarn config set taskPoolConcurrency 2; yarn config set networkConcurrency 5; fi -RUN yarn workspaces focus actual-sync --production +RUN yarn workspaces focus @actual-app/sync-server --production RUN mkdir /public COPY artifacts.json /tmp/artifacts.json diff --git a/packages/sync-server/docker/stable-alpine.Dockerfile b/packages/sync-server/docker/stable-alpine.Dockerfile index 4fbf546ef5a..0194d5ba69e 100644 --- a/packages/sync-server/docker/stable-alpine.Dockerfile +++ b/packages/sync-server/docker/stable-alpine.Dockerfile @@ -4,7 +4,7 @@ WORKDIR /app COPY .yarn ./.yarn COPY yarn.lock packages/sync-server/package.json .yarnrc.yml ./ RUN if [ "$(uname -m)" = "armv7l" ]; then yarn config set taskPoolConcurrency 2; yarn config set networkConcurrency 5; fi -RUN yarn workspaces focus actual-sync --production +RUN yarn workspaces focus @actual-app/sync-server --production RUN if [ "$(uname -m)" = "armv7l" ]; then npm install bcrypt better-sqlite3 --build-from-source; fi FROM alpine:3.18 AS prod diff --git a/packages/sync-server/docker/stable-ubuntu.Dockerfile b/packages/sync-server/docker/stable-ubuntu.Dockerfile index a10d594476a..01834a5102b 100644 --- a/packages/sync-server/docker/stable-ubuntu.Dockerfile +++ b/packages/sync-server/docker/stable-ubuntu.Dockerfile @@ -4,7 +4,7 @@ WORKDIR /app COPY .yarn ./.yarn COPY yarn.lock packages/sync-server/package.json .yarnrc.yml ./ RUN if [ "$(uname -m)" = "armv7l" ]; then yarn config set taskPoolConcurrency 2; yarn config set networkConcurrency 5; fi -RUN yarn workspaces focus actual-sync --production +RUN yarn workspaces focus @actual-app/sync-server --production FROM node:18-bookworm-slim AS prod RUN apt-get update && apt-get install tini && apt-get clean -y && rm -rf /var/lib/apt/lists/* diff --git a/packages/sync-server/package.json b/packages/sync-server/package.json index e3532423b93..13ed1a6511e 100644 --- a/packages/sync-server/package.json +++ b/packages/sync-server/package.json @@ -1,11 +1,12 @@ { - "name": "actual-sync", + "name": "@actual-app/sync-server", "version": "25.2.1", "license": "MIT", "description": "actual syncing server", "type": "module", "scripts": { "start": "node app", + "start-monitor": "nodemon app", "lint": "eslint . --max-warnings 0", "lint:fix": "eslint . --fix", "build": "tsc", @@ -30,9 +31,9 @@ "cors": "^2.8.5", "date-fns": "^2.30.0", "debug": "^4.3.4", - "express": "4.20.0", + "express": "4.21.2", "express-actuator": "1.8.4", - "express-rate-limit": "^6.7.0", + "express-rate-limit": "^7.5.0", "express-response-size": "^0.0.3", "express-winston": "^4.2.0", "jws": "^4.0.0", @@ -47,8 +48,8 @@ "@types/bcrypt": "^5.0.2", "@types/better-sqlite3": "^7.6.12", "@types/cors": "^2.8.13", - "@types/express": "^4.17.17", - "@types/express-actuator": "^1.8.0", + "@types/express": "^5.0.0", + "@types/express-actuator": "^1.8.3", "@types/jest": "^29.2.3", "@types/node": "^17.0.45", "@types/supertest": "^2.0.12", @@ -57,7 +58,9 @@ "@typescript-eslint/parser": "^5.51.0", "eslint": "^8.33.0", "eslint-plugin-prettier": "^4.2.1", + "http-proxy-middleware": "^3.0.3", "jest": "^29.3.1", + "nodemon": "^3.1.9", "prettier": "^2.8.3", "supertest": "^6.3.1", "typescript": "^4.9.5" diff --git a/packages/sync-server/src/account-db.js b/packages/sync-server/src/account-db.js index 7d6fa1f27d4..b94d4131c61 100644 --- a/packages/sync-server/src/account-db.js +++ b/packages/sync-server/src/account-db.js @@ -27,11 +27,15 @@ export function needsBootstrap() { export function listLoginMethods() { const accountDb = getAccountDb(); const rows = accountDb.all('SELECT method, display_name, active FROM auth'); - return rows.map(r => ({ - method: r.method, - active: r.active, - displayName: r.display_name, - })); + return rows + .filter(f => + rows.length > 1 && config.enforceOpenId ? f.method === 'openid' : true, + ) + .map(r => ({ + method: r.method, + active: r.active, + displayName: r.display_name, + })); } export function getActiveLoginMethod() { diff --git a/packages/sync-server/src/app-gocardless/bank-factory.js b/packages/sync-server/src/app-gocardless/bank-factory.js index d0cf32cfb5b..00ac1b233cb 100644 --- a/packages/sync-server/src/app-gocardless/bank-factory.js +++ b/packages/sync-server/src/app-gocardless/bank-factory.js @@ -34,8 +34,10 @@ export const BANKS_WITH_LIMITED_HISTORY = [ 'BANCA_AIDEXA_AIDXITMM', 'BANCA_PATRIMONI_SENVITT1', 'BANCA_SELLA_SELBIT2B', + 'BANK_MILLENNIUM_BIGBPLPW', 'BANKINTER_BKBKESMM', 'BBVA_BBVAESMM', + 'BNP_PL_PPABPLPK', 'BRED_BREDFRPPXXX', 'CAIXA_GERAL_DEPOSITOS_CGDIPTPL', 'CAIXABANK_CAIXESBB', @@ -60,6 +62,7 @@ export const BANKS_WITH_LIMITED_HISTORY = [ 'LUMINOR_NDEALV2X', 'LUMINOR_RIKOEE22', 'LUMINOR_RIKOLV2X', + 'MBANK_RETAIL_BREXPLPW', 'MEDICINOSBANK_MDBALT22XXX', 'NORDEA_NDEADKKK', 'N26_NTSBDEB1', diff --git a/packages/sync-server/src/app.js b/packages/sync-server/src/app.js index 78bde184e5f..73c258bb705 100644 --- a/packages/sync-server/src/app.js +++ b/packages/sync-server/src/app.js @@ -24,14 +24,17 @@ process.on('unhandledRejection', reason => { app.disable('x-powered-by'); app.use(cors()); app.set('trust proxy', config.trustedProxies); -app.use( - rateLimit({ - windowMs: 60 * 1000, - max: 500, - legacyHeaders: false, - standardHeaders: true, - }), -); +if (process.env.NODE_ENV !== 'development') { + app.use( + rateLimit({ + windowMs: 60 * 1000, + max: 500, + legacyHeaders: false, + standardHeaders: true, + }), + ); +} + app.use(bodyParser.json({ limit: `${config.upload.fileSizeLimitMB}mb` })); app.use( bodyParser.raw({ @@ -67,9 +70,28 @@ app.use((req, res, next) => { res.set('Cross-Origin-Embedder-Policy', 'require-corp'); next(); }); -app.use(express.static(config.webRoot, { index: false })); +if (process.env.NODE_ENV === 'development') { + console.log( + 'Running in development mode - Proxying frontend routes to React Dev Server', + ); + + // Imported within Dev block to allow dev dependency in package.json (reduces package size in production) + const httpProxyMiddleware = await import('http-proxy-middleware'); -app.get('/*', (req, res) => res.sendFile(config.webRoot + '/index.html')); + app.use( + httpProxyMiddleware.createProxyMiddleware({ + target: 'http://localhost:3001', + changeOrigin: true, + ws: true, + logLevel: 'debug', + }), + ); +} else { + console.log('Running in production mode - Serving static React app'); + + app.use(express.static(config.webRoot, { index: false })); + app.get('/*', (req, res) => res.sendFile(config.webRoot + '/index.html')); +} function parseHTTPSConfig(value) { if (value.startsWith('-----BEGIN')) { diff --git a/packages/sync-server/src/config-types.ts b/packages/sync-server/src/config-types.ts index 464c2dc3fbc..957f3c8b38c 100644 --- a/packages/sync-server/src/config-types.ts +++ b/packages/sync-server/src/config-types.ts @@ -40,4 +40,5 @@ export interface Config { }; multiuser: boolean; token_expiration?: 'never' | 'openid-provider' | number; + enforceOpenId: boolean; } diff --git a/packages/sync-server/src/load-config.js b/packages/sync-server/src/load-config.js index d991d8e6e21..c6d8820c469 100644 --- a/packages/sync-server/src/load-config.js +++ b/packages/sync-server/src/load-config.js @@ -88,6 +88,7 @@ const defaultConfig = { projectRoot, multiuser: false, token_expiration: 'never', + enforceOpenId: false, }; /** @type {import('./config-types.js').Config} */ @@ -221,6 +222,17 @@ const finalConfig = { token_expiration: process.env.ACTUAL_TOKEN_EXPIRATION ? process.env.ACTUAL_TOKEN_EXPIRATION : config.token_expiration, + enforceOpenId: process.env.ACTUAL_OPENID_ENFORCE + ? (() => { + const value = process.env.ACTUAL_OPENID_ENFORCE.toLowerCase(); + if (!['true', 'false'].includes(value)) { + throw new Error( + 'ACTUAL_OPENID_ENFORCE must be either "true" or "false"', + ); + } + return value === 'true'; + })() + : config.enforceOpenId, }; debug(`using port ${finalConfig.port}`); debug(`using hostname ${finalConfig.hostname}`); diff --git a/sync-server.Dockerfile b/sync-server.Dockerfile index 9d83bf40526..eb17eeef7d0 100644 --- a/sync-server.Dockerfile +++ b/sync-server.Dockerfile @@ -3,7 +3,7 @@ RUN apt-get update && apt-get install -y openssl WORKDIR /app COPY .yarn ./.yarn COPY yarn.lock packages/sync-server/package.json .yarnrc.yml ./ -RUN yarn workspaces focus actual-sync --production +RUN yarn workspaces focus @actual-app/sync-server --production FROM node:18-bookworm-slim as prod RUN apt-get update && apt-get install tini && apt-get clean -y && rm -rf /var/lib/apt/lists/* diff --git a/upcoming-release-notes/4145.md b/upcoming-release-notes/4145.md new file mode 100644 index 00000000000..d8f8660e424 --- /dev/null +++ b/upcoming-release-notes/4145.md @@ -0,0 +1,6 @@ +--- +category: Maintenance +authors: [jfdoming] +--- + +Fix types of `send` function diff --git a/upcoming-release-notes/4213.md b/upcoming-release-notes/4213.md new file mode 100644 index 00000000000..74d4924cbd8 --- /dev/null +++ b/upcoming-release-notes/4213.md @@ -0,0 +1,6 @@ +--- +category: Enhancements +authors: [joel-jeremy] +--- + +useDisplayPayee hook to unify payee names in mobile and desktop. diff --git a/upcoming-release-notes/4221.md b/upcoming-release-notes/4221.md new file mode 100644 index 00000000000..a1e71ca1831 --- /dev/null +++ b/upcoming-release-notes/4221.md @@ -0,0 +1,6 @@ +--- +category: Maintenance +authors: [joel-jeremy] +--- + +Move transactions related server handlers from main.ts to server/transactions/app.ts diff --git a/upcoming-release-notes/4227.md b/upcoming-release-notes/4227.md new file mode 100644 index 00000000000..3ce168ffa10 --- /dev/null +++ b/upcoming-release-notes/4227.md @@ -0,0 +1,6 @@ +--- +category: Maintenance +authors: [joel-jeremy] +--- + +Move accounts related server handlers from main.ts to server/accounts/app.ts diff --git a/upcoming-release-notes/4247.md b/upcoming-release-notes/4247.md new file mode 100644 index 00000000000..ca01ecc5b5b --- /dev/null +++ b/upcoming-release-notes/4247.md @@ -0,0 +1,6 @@ +--- +category: Maintenance +authors: [joel-jeremy] +--- + +[TypeScript] Make `runQuery` generic to make it easy to type DB query results. \ No newline at end of file diff --git a/upcoming-release-notes/4258.md b/upcoming-release-notes/4258.md new file mode 100644 index 00000000000..f5e11b5edc5 --- /dev/null +++ b/upcoming-release-notes/4258.md @@ -0,0 +1,6 @@ +--- +category: Maintenance +authors: [joel-jeremy] +--- + +Fix react-hooks/exhaustive-deps error on useSelected.tsx diff --git a/upcoming-release-notes/4259.md b/upcoming-release-notes/4259.md new file mode 100644 index 00000000000..4eaa1c56838 --- /dev/null +++ b/upcoming-release-notes/4259.md @@ -0,0 +1,6 @@ +--- +category: Maintenance +authors: [joel-jeremy] +--- + +Fix react-hooks/exhaustive-deps error on useProperFocus.tsx diff --git a/upcoming-release-notes/4260.md b/upcoming-release-notes/4260.md new file mode 100644 index 00000000000..7b0484bf242 --- /dev/null +++ b/upcoming-release-notes/4260.md @@ -0,0 +1,6 @@ +--- +category: Maintenance +authors: [joel-jeremy] +--- + +Fix react-hooks/exhaustive-deps error on usePayees.ts diff --git a/upcoming-release-notes/4261.md b/upcoming-release-notes/4261.md new file mode 100644 index 00000000000..945197c8744 --- /dev/null +++ b/upcoming-release-notes/4261.md @@ -0,0 +1,6 @@ +--- +category: Maintenance +authors: [joel-jeremy] +--- + +Fix react-hooks/exhaustive-deps error on useCategories.ts diff --git a/upcoming-release-notes/4262.md b/upcoming-release-notes/4262.md new file mode 100644 index 00000000000..27e071d7e31 --- /dev/null +++ b/upcoming-release-notes/4262.md @@ -0,0 +1,6 @@ +--- +category: Maintenance +authors: [joel-jeremy] +--- + +Fix react-hooks/exhaustive-deps error on useAccounts.ts diff --git a/upcoming-release-notes/4268.md b/upcoming-release-notes/4268.md new file mode 100644 index 00000000000..cfd3c8b789b --- /dev/null +++ b/upcoming-release-notes/4268.md @@ -0,0 +1,6 @@ +--- +category: Maintenance +authors: [joel-jeremy] +--- + +Fix react-hooks/exhaustive-deps error on TransactionsTable.jsx diff --git a/upcoming-release-notes/4272.md b/upcoming-release-notes/4272.md new file mode 100644 index 00000000000..79c23d5065c --- /dev/null +++ b/upcoming-release-notes/4272.md @@ -0,0 +1,6 @@ +--- +category: Maintenance +authors: [joel-jeremy] +--- + +Fix react-hooks/exhaustive-deps error on TransactionList.jsx diff --git a/upcoming-release-notes/4273.md b/upcoming-release-notes/4273.md new file mode 100644 index 00000000000..179d7948c51 --- /dev/null +++ b/upcoming-release-notes/4273.md @@ -0,0 +1,6 @@ +--- +category: Maintenance +authors: [joel-jeremy] +--- + +Fix react-hooks/exhaustive-deps error on Titlebar.tsx diff --git a/upcoming-release-notes/4274.md b/upcoming-release-notes/4274.md new file mode 100644 index 00000000000..6c0a4e0d997 --- /dev/null +++ b/upcoming-release-notes/4274.md @@ -0,0 +1,6 @@ +--- +category: Maintenance +authors: [joel-jeremy] +--- + +Fix react-hooks/exhaustive-deps error on table.tsx diff --git a/upcoming-release-notes/4308.md b/upcoming-release-notes/4308.md new file mode 100644 index 00000000000..a94dab0b95a --- /dev/null +++ b/upcoming-release-notes/4308.md @@ -0,0 +1,6 @@ +--- +category: Features +authors: [jfdoming] +--- + +Foundations for the budget automations UI diff --git a/upcoming-release-notes/4343.md b/upcoming-release-notes/4343.md new file mode 100644 index 00000000000..6d1e85ae9fe --- /dev/null +++ b/upcoming-release-notes/4343.md @@ -0,0 +1,6 @@ +--- +category: Maintenance +authors: [matt-fidd] +--- + +Rename migrations to realign with the convention diff --git a/upcoming-release-notes/4369.md b/upcoming-release-notes/4369.md new file mode 100644 index 00000000000..d4fbd673ffc --- /dev/null +++ b/upcoming-release-notes/4369.md @@ -0,0 +1,6 @@ +--- +category: Maintenance +authors: [MikesGlitch] +--- + +Adding typescript type for the global-store.json setting file diff --git a/upcoming-release-notes/4370.md b/upcoming-release-notes/4370.md new file mode 100644 index 00000000000..f6b84764348 --- /dev/null +++ b/upcoming-release-notes/4370.md @@ -0,0 +1,6 @@ +--- +category: Maintenance +authors: [MikesGlitch] +--- + +Update package name of sync server to be consistent with the rest of the workspace diff --git a/upcoming-release-notes/4372.md b/upcoming-release-notes/4372.md new file mode 100644 index 00000000000..fe0bd325863 --- /dev/null +++ b/upcoming-release-notes/4372.md @@ -0,0 +1,6 @@ +--- +category: Enhancements +authors: [lelemm] +--- + +Sync server development mode with react fast refresh diff --git a/upcoming-release-notes/4380.md b/upcoming-release-notes/4380.md new file mode 100644 index 00000000000..e9af3ecd558 --- /dev/null +++ b/upcoming-release-notes/4380.md @@ -0,0 +1,6 @@ +--- +category: Enhancements +authors: [adastx] +--- + +Add button to go to current month in budget view on mobile diff --git a/upcoming-release-notes/4382.md b/upcoming-release-notes/4382.md new file mode 100644 index 00000000000..d16fc10044e --- /dev/null +++ b/upcoming-release-notes/4382.md @@ -0,0 +1,6 @@ +--- +category: Bugfix +authors: [langelgjm] +--- + +Ignore CSV inOutMode during OFX imports diff --git a/upcoming-release-notes/4383.md b/upcoming-release-notes/4383.md new file mode 100644 index 00000000000..8736e493857 --- /dev/null +++ b/upcoming-release-notes/4383.md @@ -0,0 +1,6 @@ +--- +category: Bugfix +authors: [AntoineTA] +--- + +Ensure decimal separator is recognized independantly of the configured number format. \ No newline at end of file diff --git a/upcoming-release-notes/4385.md b/upcoming-release-notes/4385.md new file mode 100644 index 00000000000..753e89eeab0 --- /dev/null +++ b/upcoming-release-notes/4385.md @@ -0,0 +1,6 @@ +--- +category: Maintenance +authors: [AlbertoCortina] +--- + +Remove deprecated components in favour of components from @actual-app library diff --git a/upcoming-release-notes/4388.md b/upcoming-release-notes/4388.md new file mode 100644 index 00000000000..4e5780b5752 --- /dev/null +++ b/upcoming-release-notes/4388.md @@ -0,0 +1,6 @@ +--- +category: Enhancements +authors: [michalgolab] +--- + +Add BANK_MILLENNIUM_BIGBPLPW, BNP_PL_PPABPLPK, MBANK_RETAIL_BREXPLPW to BANKS_WITH_LIMITED_HISTORY constant. diff --git a/upcoming-release-notes/4397.md b/upcoming-release-notes/4397.md new file mode 100644 index 00000000000..15200dde8b4 --- /dev/null +++ b/upcoming-release-notes/4397.md @@ -0,0 +1,6 @@ +--- +category: Bugfix +authors: [lelemm] +--- + +Fix for User directory page that was calling the api every frame diff --git a/upcoming-release-notes/4400.md b/upcoming-release-notes/4400.md new file mode 100644 index 00000000000..aca1e2b4435 --- /dev/null +++ b/upcoming-release-notes/4400.md @@ -0,0 +1,6 @@ +--- +category: Maintenance +authors: [MikesGlitch] +--- + +Fix new proxy middleware importing in production when only required in developement diff --git a/upcoming-release-notes/4403.md b/upcoming-release-notes/4403.md new file mode 100644 index 00000000000..b57e3f0e466 --- /dev/null +++ b/upcoming-release-notes/4403.md @@ -0,0 +1,6 @@ +--- +category: Enhancements +authors: [matt-fidd] +--- + +Update bank sync mapping data for existing transactions on sync diff --git a/upcoming-release-notes/4407.md b/upcoming-release-notes/4407.md new file mode 100644 index 00000000000..c86caba23a3 --- /dev/null +++ b/upcoming-release-notes/4407.md @@ -0,0 +1,6 @@ +--- +category: Maintenance +authors: [MikesGlitch] +--- + +Improving Electron logging to send logs created before startup to dev tools diff --git a/upcoming-release-notes/4408.md b/upcoming-release-notes/4408.md new file mode 100644 index 00000000000..7089479f72e --- /dev/null +++ b/upcoming-release-notes/4408.md @@ -0,0 +1,6 @@ +--- +category: Enhancements +authors: [lelemm] +--- + +Extending translations for components: Account, SidebarCategory, TotalsList, MobileNavTabs, AccountTransactions (Mobile), Accounts (Mobile), BudgetTable (Mobile), TransactionEdit (Mobile), TransactionList (Mobile), TransactionListItem (Mobile), CategoryMenuModal, CreateLocalAccountModal, ImportModal, ReportSideBar, CustomReport, Spending, Export, TransactionsTable diff --git a/upcoming-release-notes/4409.md b/upcoming-release-notes/4409.md new file mode 100644 index 00000000000..a06aae638a8 --- /dev/null +++ b/upcoming-release-notes/4409.md @@ -0,0 +1,6 @@ +--- +category: Maintenance +authors: [joel-jeremy] +--- + +[TypeScript] Add types for SpreadsheetProvider diff --git a/upcoming-release-notes/4410.md b/upcoming-release-notes/4410.md new file mode 100644 index 00000000000..b0d8594ebfa --- /dev/null +++ b/upcoming-release-notes/4410.md @@ -0,0 +1,6 @@ +--- +category: Maintenance +authors: [matt-fidd] +--- + +Bump vitest to 1.6.1 diff --git a/upcoming-release-notes/4411.md b/upcoming-release-notes/4411.md new file mode 100644 index 00000000000..53458d6e487 --- /dev/null +++ b/upcoming-release-notes/4411.md @@ -0,0 +1,6 @@ +--- +category: Maintenance +authors: [matt-fidd] +--- + +Bump express version diff --git a/upcoming-release-notes/4413.md b/upcoming-release-notes/4413.md new file mode 100644 index 00000000000..a0ab7fd769c --- /dev/null +++ b/upcoming-release-notes/4413.md @@ -0,0 +1,6 @@ +--- +category: Bugfix +authors: [matt-fidd] +--- + +Fix crash during bank sync when the payee is not found diff --git a/upcoming-release-notes/4415.md b/upcoming-release-notes/4415.md new file mode 100644 index 00000000000..c3bf832da19 --- /dev/null +++ b/upcoming-release-notes/4415.md @@ -0,0 +1,6 @@ +--- +category: Enhancements +authors: [lelemm] +--- + +Start the application with the browser's default language diff --git a/upcoming-release-notes/4417.md b/upcoming-release-notes/4417.md new file mode 100644 index 00000000000..275a004e6e1 --- /dev/null +++ b/upcoming-release-notes/4417.md @@ -0,0 +1,6 @@ +--- +category: Bugfix +authors: [lelemm] +--- + +Fix `On budget` / `Off budget` underline with translated languages diff --git a/upcoming-release-notes/4420.md b/upcoming-release-notes/4420.md new file mode 100644 index 00000000000..ab175def739 --- /dev/null +++ b/upcoming-release-notes/4420.md @@ -0,0 +1,6 @@ +--- +category: Maintenance +authors: [joel-jeremy] +--- + +Extract preferences related server handlers from main.ts to server/preferences/app.ts \ No newline at end of file diff --git a/upcoming-release-notes/4423.md b/upcoming-release-notes/4423.md new file mode 100644 index 00000000000..b1523fbb29f --- /dev/null +++ b/upcoming-release-notes/4423.md @@ -0,0 +1,6 @@ +--- +category: Enhancements +authors: [lelemm] +--- + +Added `ACTUAL_OPENID_ENFORCE=true` enviroment variable to enforce only OpenID auth. \ No newline at end of file diff --git a/upcoming-release-notes/4427.md b/upcoming-release-notes/4427.md new file mode 100644 index 00000000000..d12584af503 --- /dev/null +++ b/upcoming-release-notes/4427.md @@ -0,0 +1,6 @@ +--- +category: Enhancements +authors: [andrew--r] +--- + +Automatically adjust height of modals to fit the visible viewport when the keyboard is open on mobile diff --git a/upcoming-release-notes/4430.md b/upcoming-release-notes/4430.md new file mode 100644 index 00000000000..bae7d8561ec --- /dev/null +++ b/upcoming-release-notes/4430.md @@ -0,0 +1,6 @@ +--- +category: Maintenance +authors: [matt-fidd] +--- + +Prevent GitHub docker workflow from running on pushes to forks diff --git a/yarn.lock b/yarn.lock index 925228ba6b6..928e1e1471a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -79,6 +79,52 @@ __metadata: languageName: unknown linkType: soft +"@actual-app/sync-server@workspace:packages/sync-server": + version: 0.0.0-use.local + resolution: "@actual-app/sync-server@workspace:packages/sync-server" + dependencies: + "@actual-app/crdt": "npm:2.1.0" + "@actual-app/web": "npm:25.2.1" + "@babel/preset-typescript": "npm:^7.20.2" + "@types/bcrypt": "npm:^5.0.2" + "@types/better-sqlite3": "npm:^7.6.12" + "@types/cors": "npm:^2.8.13" + "@types/express": "npm:^5.0.0" + "@types/express-actuator": "npm:^1.8.3" + "@types/jest": "npm:^29.2.3" + "@types/node": "npm:^17.0.45" + "@types/supertest": "npm:^2.0.12" + "@types/uuid": "npm:^9.0.0" + "@typescript-eslint/eslint-plugin": "npm:^5.51.0" + "@typescript-eslint/parser": "npm:^5.51.0" + bcrypt: "npm:^5.1.1" + better-sqlite3: "npm:^11.7.0" + body-parser: "npm:^1.20.3" + cors: "npm:^2.8.5" + date-fns: "npm:^2.30.0" + debug: "npm:^4.3.4" + eslint: "npm:^8.33.0" + eslint-plugin-prettier: "npm:^4.2.1" + express: "npm:4.21.2" + express-actuator: "npm:1.8.4" + express-rate-limit: "npm:^7.5.0" + express-response-size: "npm:^0.0.3" + express-winston: "npm:^4.2.0" + http-proxy-middleware: "npm:^3.0.3" + jest: "npm:^29.3.1" + jws: "npm:^4.0.0" + migrate: "npm:^2.0.1" + nodemon: "npm:^3.1.9" + nordigen-node: "npm:^1.4.0" + openid-client: "npm:^5.4.2" + prettier: "npm:^2.8.3" + supertest: "npm:^6.3.1" + typescript: "npm:^4.9.5" + uuid: "npm:^9.0.0" + winston: "npm:^3.14.2" + languageName: unknown + linkType: soft + "@actual-app/web@npm:25.2.1": version: 25.2.1 resolution: "@actual-app/web@npm:25.2.1" @@ -161,7 +207,7 @@ __metadata: vite: "npm:^5.2.14" vite-plugin-pwa: "npm:^0.20.0" vite-tsconfig-paths: "npm:^4.3.2" - vitest: "npm:^1.6.0" + vitest: "npm:^1.6.1" webpack-bundle-analyzer: "npm:^4.10.1" xml2js: "npm:^0.6.2" languageName: unknown @@ -6211,7 +6257,7 @@ __metadata: languageName: node linkType: hard -"@types/express-actuator@npm:^1.8.0": +"@types/express-actuator@npm:^1.8.3": version: 1.8.3 resolution: "@types/express-actuator@npm:1.8.3" dependencies: @@ -6220,18 +6266,6 @@ __metadata: languageName: node linkType: hard -"@types/express-serve-static-core@npm:^4.17.33": - version: 4.19.6 - resolution: "@types/express-serve-static-core@npm:4.19.6" - dependencies: - "@types/node": "npm:*" - "@types/qs": "npm:*" - "@types/range-parser": "npm:*" - "@types/send": "npm:*" - checksum: 10/a2e00b6c5993f0dd63ada2239be81076fe0220314b9e9fde586e8946c9c09ce60f9a2dd0d74410ee2b5fd10af8c3e755a32bb3abf134533e2158142488995455 - languageName: node - linkType: hard - "@types/express-serve-static-core@npm:^5.0.0": version: 5.0.6 resolution: "@types/express-serve-static-core@npm:5.0.6" @@ -6244,7 +6278,7 @@ __metadata: languageName: node linkType: hard -"@types/express@npm:*": +"@types/express@npm:*, @types/express@npm:^5.0.0": version: 5.0.0 resolution: "@types/express@npm:5.0.0" dependencies: @@ -6256,18 +6290,6 @@ __metadata: languageName: node linkType: hard -"@types/express@npm:^4.17.17": - version: 4.17.21 - resolution: "@types/express@npm:4.17.21" - dependencies: - "@types/body-parser": "npm:*" - "@types/express-serve-static-core": "npm:^4.17.33" - "@types/qs": "npm:*" - "@types/serve-static": "npm:*" - checksum: 10/7a6d26cf6f43d3151caf4fec66ea11c9d23166e4f3102edfe45a94170654a54ea08cf3103d26b3928d7ebcc24162c90488e33986b7e3a5f8941225edd5eb18c7 - languageName: node - linkType: hard - "@types/fs-extra@npm:9.0.13, @types/fs-extra@npm:^9.0.11": version: 9.0.13 resolution: "@types/fs-extra@npm:9.0.13" @@ -6319,6 +6341,15 @@ __metadata: languageName: node linkType: hard +"@types/http-proxy@npm:^1.17.15": + version: 1.17.16 + resolution: "@types/http-proxy@npm:1.17.16" + dependencies: + "@types/node": "npm:*" + checksum: 10/a054ac8f5301acfcfdcec3a775f52dc371180bbe60037906534312f10cceb3799b4a16e46c56c22f9925d078e11dcda1723c38f1ddd124be8169a4cccca69c8c + languageName: node + linkType: hard + "@types/istanbul-lib-coverage@npm:*, @types/istanbul-lib-coverage@npm:^2.0.0, @types/istanbul-lib-coverage@npm:^2.0.1": version: 2.0.4 resolution: "@types/istanbul-lib-coverage@npm:2.0.4" @@ -7124,57 +7155,57 @@ __metadata: languageName: node linkType: hard -"@vitest/expect@npm:1.6.0": - version: 1.6.0 - resolution: "@vitest/expect@npm:1.6.0" +"@vitest/expect@npm:1.6.1": + version: 1.6.1 + resolution: "@vitest/expect@npm:1.6.1" dependencies: - "@vitest/spy": "npm:1.6.0" - "@vitest/utils": "npm:1.6.0" + "@vitest/spy": "npm:1.6.1" + "@vitest/utils": "npm:1.6.1" chai: "npm:^4.3.10" - checksum: 10/e82304a12e22b98c1ccea81e8f33c838561deb878588eac463164cc4f8fc0c401ace3a9e6758d9e3a6bcc01313e845e8478aaefb7548eaded04b8de12c1928f6 + checksum: 10/8aa366cc629bba4170eadebf092de9f64b46592fde9455b070cb7616dcba54f03d479e5844da0ddadecbc19a4f781a0b0d72ab2275cfccca54fd51398ac1b5d5 languageName: node linkType: hard -"@vitest/runner@npm:1.6.0": - version: 1.6.0 - resolution: "@vitest/runner@npm:1.6.0" +"@vitest/runner@npm:1.6.1": + version: 1.6.1 + resolution: "@vitest/runner@npm:1.6.1" dependencies: - "@vitest/utils": "npm:1.6.0" + "@vitest/utils": "npm:1.6.1" p-limit: "npm:^5.0.0" pathe: "npm:^1.1.1" - checksum: 10/d83a608be36dace77f91a9d15ab7753f9c5923281188a8d9cb5ccec770df9cc9ba80e5e1e3465328c7605977be0f0708610855abf5f4af037a4ede5f51a83e47 + checksum: 10/b3ee2cb7b80108c48505f71e291b7a70c819dc4c704c77d44beb722d641c5ef8e6f623e95a0259a3d0e8178d1b3559f426d03f13a3500420d1c2b8802e0128c4 languageName: node linkType: hard -"@vitest/snapshot@npm:1.6.0": - version: 1.6.0 - resolution: "@vitest/snapshot@npm:1.6.0" +"@vitest/snapshot@npm:1.6.1": + version: 1.6.1 + resolution: "@vitest/snapshot@npm:1.6.1" dependencies: magic-string: "npm:^0.30.5" pathe: "npm:^1.1.1" pretty-format: "npm:^29.7.0" - checksum: 10/0bfc26a48b45814604ff0f7276d73a047b79f3618e0b620ff54ea2de548e9603a9770963ba6ebb19f7ea1ed51001cbca58d74aa0271651d4f8e88c6233885eba + checksum: 10/f78876503ac850ac3f0a0766133cd020d83c1e665711d4e4370f5f408051b8da7a6294882c549b00a90f03c4ca25b7c41893514a7d5f9f336e6a47ad533b4cb1 languageName: node linkType: hard -"@vitest/spy@npm:1.6.0": - version: 1.6.0 - resolution: "@vitest/spy@npm:1.6.0" +"@vitest/spy@npm:1.6.1": + version: 1.6.1 + resolution: "@vitest/spy@npm:1.6.1" dependencies: tinyspy: "npm:^2.2.0" - checksum: 10/1c9698272a58aa47708bb8a1672d655fcec3285b02067cc3f70bfe76f4eda7a756eb379f8c945ccbe61677f5189aeb5ba93c2737a9d7db2de8c4e7bbdffcd372 + checksum: 10/55076c8dad8585c4d3923ec1e948e97746150d9d259a7b6045d8dd0e22babc631b22c31882c976c25b68cfbaf11d9d47fe0a77e68c3f1b8973b90c6b835becdb languageName: node linkType: hard -"@vitest/utils@npm:1.6.0": - version: 1.6.0 - resolution: "@vitest/utils@npm:1.6.0" +"@vitest/utils@npm:1.6.1": + version: 1.6.1 + resolution: "@vitest/utils@npm:1.6.1" dependencies: diff-sequences: "npm:^29.6.3" estree-walker: "npm:^3.0.3" loupe: "npm:^2.3.7" pretty-format: "npm:^29.7.0" - checksum: 10/5c5d7295ac13fcea1da039232bcc7c3fc6f070070fe12ba2ad152456af6e216e48a3ae169016cfcd5055706a00dc567b8f62e4a9b1914f069f52b8f0a3c25e60 + checksum: 10/2aa8718c5e0705f28a8e94ac00055a48789b1badda79d3578d21241557195816508677ecd5f41fe355edb204e6f817124f059c4806102e040cc8890d8691ae9a languageName: node linkType: hard @@ -7485,50 +7516,6 @@ __metadata: languageName: node linkType: hard -"actual-sync@workspace:packages/sync-server": - version: 0.0.0-use.local - resolution: "actual-sync@workspace:packages/sync-server" - dependencies: - "@actual-app/crdt": "npm:2.1.0" - "@actual-app/web": "npm:25.2.1" - "@babel/preset-typescript": "npm:^7.20.2" - "@types/bcrypt": "npm:^5.0.2" - "@types/better-sqlite3": "npm:^7.6.12" - "@types/cors": "npm:^2.8.13" - "@types/express": "npm:^4.17.17" - "@types/express-actuator": "npm:^1.8.0" - "@types/jest": "npm:^29.2.3" - "@types/node": "npm:^17.0.45" - "@types/supertest": "npm:^2.0.12" - "@types/uuid": "npm:^9.0.0" - "@typescript-eslint/eslint-plugin": "npm:^5.51.0" - "@typescript-eslint/parser": "npm:^5.51.0" - bcrypt: "npm:^5.1.1" - better-sqlite3: "npm:^11.7.0" - body-parser: "npm:^1.20.3" - cors: "npm:^2.8.5" - date-fns: "npm:^2.30.0" - debug: "npm:^4.3.4" - eslint: "npm:^8.33.0" - eslint-plugin-prettier: "npm:^4.2.1" - express: "npm:4.20.0" - express-actuator: "npm:1.8.4" - express-rate-limit: "npm:^6.7.0" - express-response-size: "npm:^0.0.3" - express-winston: "npm:^4.2.0" - jest: "npm:^29.3.1" - jws: "npm:^4.0.0" - migrate: "npm:^2.0.1" - nordigen-node: "npm:^1.4.0" - openid-client: "npm:^5.4.2" - prettier: "npm:^2.8.3" - supertest: "npm:^6.3.1" - typescript: "npm:^4.9.5" - uuid: "npm:^9.0.0" - winston: "npm:^3.14.2" - languageName: unknown - linkType: soft - "actual@workspace:.": version: 0.0.0-use.local resolution: "actual@workspace:." @@ -8935,6 +8922,25 @@ __metadata: languageName: node linkType: hard +"chokidar@npm:^3.5.2": + version: 3.6.0 + resolution: "chokidar@npm:3.6.0" + dependencies: + anymatch: "npm:~3.1.2" + braces: "npm:~3.0.2" + fsevents: "npm:~2.3.2" + glob-parent: "npm:~5.1.2" + is-binary-path: "npm:~2.1.0" + is-glob: "npm:~4.0.1" + normalize-path: "npm:~3.0.0" + readdirp: "npm:~3.6.0" + dependenciesMeta: + fsevents: + optional: true + checksum: 10/c327fb07704443f8d15f7b4a7ce93b2f0bc0e6cea07ec28a7570aa22cd51fcf0379df589403976ea956c369f25aa82d84561947e227cd925902e1751371658df + languageName: node + linkType: hard + "chownr@npm:^1.1.1": version: 1.1.4 resolution: "chownr@npm:1.1.4" @@ -9376,10 +9382,10 @@ __metadata: languageName: node linkType: hard -"cookie@npm:0.6.0": - version: 0.6.0 - resolution: "cookie@npm:0.6.0" - checksum: 10/c1f8f2ea7d443b9331680598b0ae4e6af18a618c37606d1bbdc75bec8361cce09fe93e727059a673f2ba24467131a9fb5a4eec76bb1b149c1b3e1ccb268dc583 +"cookie@npm:0.7.1": + version: 0.7.1 + resolution: "cookie@npm:0.7.1" + checksum: 10/aec6a6aa0781761bf55d60447d6be08861d381136a0fe94aa084fddd4f0300faa2b064df490c6798adfa1ebaef9e0af9b08a189c823e0811b8b313b3d9a03380 languageName: node linkType: hard @@ -9889,7 +9895,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:^4.3.7": +"debug@npm:^4, debug@npm:^4.3.6, debug@npm:^4.3.7": version: 4.4.0 resolution: "debug@npm:4.4.0" dependencies: @@ -11260,7 +11266,7 @@ __metadata: eslint-plugin-node: "npm:^11.1.0" eslint-vitest-rule-tester: "npm:^0.7.1" requireindex: "npm:^1.2.0" - vitest: "npm:^1.6.0" + vitest: "npm:^1.6.1" peerDependencies: eslint: ">=7" languageName: unknown @@ -11729,7 +11735,7 @@ __metadata: languageName: node linkType: hard -"eventemitter3@npm:^4.0.1": +"eventemitter3@npm:^4.0.0, eventemitter3@npm:^4.0.1": version: 4.0.7 resolution: "eventemitter3@npm:4.0.7" checksum: 10/8030029382404942c01d0037079f1b1bc8fed524b5849c237b80549b01e2fc49709e1d0c557fa65ca4498fc9e24cff1475ef7b855121fcc15f9d61f93e282346 @@ -11892,12 +11898,12 @@ __metadata: languageName: node linkType: hard -"express-rate-limit@npm:^6.7.0": - version: 6.11.2 - resolution: "express-rate-limit@npm:6.11.2" +"express-rate-limit@npm:^7.5.0": + version: 7.5.0 + resolution: "express-rate-limit@npm:7.5.0" peerDependencies: - express: ^4 || ^5 - checksum: 10/9b482cf91e030edcb88292831b2515208ddb9ec92330c54fb487c700fe8ac5000c7f5d2623ae4913b5a7fcce8e9ef65eb017e28edc96ace0ed111c16b996ccfc + express: ^4.11 || 5 || ^5.0.0-beta.1 + checksum: 10/eff34c83bf586789933a332a339b66649e2cca95c8e977d193aa8bead577d3182ac9f0e9c26f39389287539b8038890ff023f910b54ebb506a26a2ce135b92ca languageName: node linkType: hard @@ -11922,42 +11928,42 @@ __metadata: languageName: node linkType: hard -"express@npm:4.20.0": - version: 4.20.0 - resolution: "express@npm:4.20.0" +"express@npm:4.21.2": + version: 4.21.2 + resolution: "express@npm:4.21.2" dependencies: accepts: "npm:~1.3.8" array-flatten: "npm:1.1.1" body-parser: "npm:1.20.3" content-disposition: "npm:0.5.4" content-type: "npm:~1.0.4" - cookie: "npm:0.6.0" + cookie: "npm:0.7.1" cookie-signature: "npm:1.0.6" debug: "npm:2.6.9" depd: "npm:2.0.0" encodeurl: "npm:~2.0.0" escape-html: "npm:~1.0.3" etag: "npm:~1.8.1" - finalhandler: "npm:1.2.0" + finalhandler: "npm:1.3.1" fresh: "npm:0.5.2" http-errors: "npm:2.0.0" merge-descriptors: "npm:1.0.3" methods: "npm:~1.1.2" on-finished: "npm:2.4.1" parseurl: "npm:~1.3.3" - path-to-regexp: "npm:0.1.10" + path-to-regexp: "npm:0.1.12" proxy-addr: "npm:~2.0.7" - qs: "npm:6.11.0" + qs: "npm:6.13.0" range-parser: "npm:~1.2.1" safe-buffer: "npm:5.2.1" send: "npm:0.19.0" - serve-static: "npm:1.16.0" + serve-static: "npm:1.16.2" setprototypeof: "npm:1.2.0" statuses: "npm:2.0.1" type-is: "npm:~1.6.18" utils-merge: "npm:1.0.1" vary: "npm:~1.1.2" - checksum: 10/4131f566cf8f6d1611475d5ff5d0dbc5c628ad8b525aa2aa2b3da9a23a041efcce09ede10b8a31315b0258ac4e53208a009fd7669ee1eb385936a0d54adb3cde + checksum: 10/34571c442fc8c9f2c4b442d2faa10ea1175cf8559237fc6a278f5ce6254a8ffdbeb9a15d99f77c1a9f2926ab183e3b7ba560e3261f1ad4149799e3412ab66bd1 languageName: node linkType: hard @@ -12231,18 +12237,18 @@ __metadata: languageName: node linkType: hard -"finalhandler@npm:1.2.0": - version: 1.2.0 - resolution: "finalhandler@npm:1.2.0" +"finalhandler@npm:1.3.1": + version: 1.3.1 + resolution: "finalhandler@npm:1.3.1" dependencies: debug: "npm:2.6.9" - encodeurl: "npm:~1.0.2" + encodeurl: "npm:~2.0.0" escape-html: "npm:~1.0.3" on-finished: "npm:2.4.1" parseurl: "npm:~1.3.3" statuses: "npm:2.0.1" unpipe: "npm:~1.0.0" - checksum: 10/635718cb203c6d18e6b48dfbb6c54ccb08ea470e4f474ddcef38c47edcf3227feec316f886dd701235997d8af35240cae49856721ce18f539ad038665ebbf163 + checksum: 10/4babe72969b7373b5842bc9f75c3a641a4d0f8eb53af6b89fa714d4460ce03fb92b28de751d12ba415e96e7e02870c436d67412120555e2b382640535697305b languageName: node linkType: hard @@ -12315,7 +12321,7 @@ __metadata: languageName: node linkType: hard -"follow-redirects@npm:^1.15.6": +"follow-redirects@npm:^1.0.0, follow-redirects@npm:^1.15.6": version: 1.15.9 resolution: "follow-redirects@npm:1.15.9" peerDependenciesMeta: @@ -13277,6 +13283,31 @@ __metadata: languageName: node linkType: hard +"http-proxy-middleware@npm:^3.0.3": + version: 3.0.3 + resolution: "http-proxy-middleware@npm:3.0.3" + dependencies: + "@types/http-proxy": "npm:^1.17.15" + debug: "npm:^4.3.6" + http-proxy: "npm:^1.18.1" + is-glob: "npm:^4.0.3" + is-plain-object: "npm:^5.0.0" + micromatch: "npm:^4.0.8" + checksum: 10/32f58c29288ca63e109909fb998bd0f6f50eb15a98dec9487eac07dfc4f09d8507dbfa00b44442d868bafa904bd633c8bbd55686bb13b4d4af4f5c5b3bbca430 + languageName: node + linkType: hard + +"http-proxy@npm:^1.18.1": + version: 1.18.1 + resolution: "http-proxy@npm:1.18.1" + dependencies: + eventemitter3: "npm:^4.0.0" + follow-redirects: "npm:^1.0.0" + requires-port: "npm:^1.0.0" + checksum: 10/2489e98aba70adbfd8b9d41ed1ff43528be4598c88616c558b109a09eaffe4bb35e551b6c75ac42ed7d948bb7530a22a2be6ef4f0cecacb5927be139f4274594 + languageName: node + linkType: hard + "http2-wrapper@npm:^1.0.0-beta.5.2": version: 1.0.3 resolution: "http2-wrapper@npm:1.0.3" @@ -13423,6 +13454,13 @@ __metadata: languageName: node linkType: hard +"ignore-by-default@npm:^1.0.1": + version: 1.0.1 + resolution: "ignore-by-default@npm:1.0.1" + checksum: 10/441509147b3615e0365e407a3c18e189f78c07af08564176c680be1fabc94b6c789cad1342ad887175d4ecd5225de86f73d376cec8e06b42fd9b429505ffcf8a + languageName: node + linkType: hard + "ignore@npm:^5.1.1, ignore@npm:^5.2.0, ignore@npm:^5.3.1": version: 5.3.2 resolution: "ignore@npm:5.3.2" @@ -16024,7 +16062,7 @@ __metadata: i18next: "npm:^23.11.5" jest: "npm:^27.5.1" jsverify: "npm:^0.8.4" - lru-cache: "npm:^5.1.1" + lru-cache: "npm:^11.0.2" md5: "npm:^2.3.0" memfs: "npm:3.5.3" memoize-one: "npm:^6.0.0" @@ -16083,6 +16121,13 @@ __metadata: languageName: node linkType: hard +"lru-cache@npm:^11.0.2": + version: 11.0.2 + resolution: "lru-cache@npm:11.0.2" + checksum: 10/25fcb66e9d91eaf17227c6abfe526a7bed5903de74f93bfde380eb8a13410c5e8d3f14fe447293f3f322a7493adf6f9f015c6f1df7a235ff24ec30f366e1c058 + languageName: node + linkType: hard + "lru-cache@npm:^4.0.1": version: 4.1.5 resolution: "lru-cache@npm:4.1.5" @@ -16864,7 +16909,7 @@ __metadata: languageName: node linkType: hard -"micromatch@npm:^4.0.4, micromatch@npm:~4.0.7": +"micromatch@npm:^4.0.4, micromatch@npm:^4.0.8, micromatch@npm:~4.0.7": version: 4.0.8 resolution: "micromatch@npm:4.0.8" dependencies: @@ -17420,6 +17465,26 @@ __metadata: languageName: node linkType: hard +"nodemon@npm:^3.1.9": + version: 3.1.9 + resolution: "nodemon@npm:3.1.9" + dependencies: + chokidar: "npm:^3.5.2" + debug: "npm:^4" + ignore-by-default: "npm:^1.0.1" + minimatch: "npm:^3.1.2" + pstree.remy: "npm:^1.1.8" + semver: "npm:^7.5.3" + simple-update-notifier: "npm:^2.0.0" + supports-color: "npm:^5.5.0" + touch: "npm:^3.1.0" + undefsafe: "npm:^2.0.5" + bin: + nodemon: bin/nodemon.js + checksum: 10/7c01ddfa30815f4147006f5b7c015a1f75017118cf398ee8c4ba3ac904667f4555b91cca6b7b191e0f6ccf5072727aa20224a1456d5446f3f6053e15132068a2 + languageName: node + linkType: hard + "noms@npm:0.0.0": version: 0.0.0 resolution: "noms@npm:0.0.0" @@ -18095,10 +18160,10 @@ __metadata: languageName: node linkType: hard -"path-to-regexp@npm:0.1.10": - version: 0.1.10 - resolution: "path-to-regexp@npm:0.1.10" - checksum: 10/894e31f1b20e592732a87db61fff5b95c892a3fe430f9ab18455ebe69ee88ef86f8eb49912e261f9926fc53da9f93b46521523e33aefd9cb0a7b0d85d7096006 +"path-to-regexp@npm:0.1.12": + version: 0.1.12 + resolution: "path-to-regexp@npm:0.1.12" + checksum: 10/2e30f6a0144679c1f95c98e166b96e6acd1e72be9417830fefc8de7ac1992147eb9a4c7acaa59119fb1b3c34eec393b2129ef27e24b2054a3906fc4fb0d1398e languageName: node linkType: hard @@ -18542,6 +18607,13 @@ __metadata: languageName: node linkType: hard +"pstree.remy@npm:^1.1.8": + version: 1.1.8 + resolution: "pstree.remy@npm:1.1.8" + checksum: 10/ef13b1b5896b35f67dbd4fb7ba54bb2a5da1a5c317276cbad4bcad4159bf8f7b5e1748dc244bf36865f3d560d2fc952521581280a91468c9c2df166cc760c8c1 + languageName: node + linkType: hard + "pump@npm:^3.0.0": version: 3.0.0 resolution: "pump@npm:3.0.0" @@ -18566,15 +18638,6 @@ __metadata: languageName: node linkType: hard -"qs@npm:6.11.0": - version: 6.11.0 - resolution: "qs@npm:6.11.0" - dependencies: - side-channel: "npm:^1.0.4" - checksum: 10/5a3bfea3e2f359ede1bfa5d2f0dbe54001aa55e40e27dc3e60fab814362d83a9b30758db057c2011b6f53a2d4e4e5150194b5bac45372652aecb3e3c0d4b256e - languageName: node - linkType: hard - "qs@npm:6.13.0": version: 6.13.0 resolution: "qs@npm:6.13.0" @@ -19983,27 +20046,6 @@ __metadata: languageName: node linkType: hard -"send@npm:0.18.0": - version: 0.18.0 - resolution: "send@npm:0.18.0" - dependencies: - debug: "npm:2.6.9" - depd: "npm:2.0.0" - destroy: "npm:1.2.0" - encodeurl: "npm:~1.0.2" - escape-html: "npm:~1.0.3" - etag: "npm:~1.8.1" - fresh: "npm:0.5.2" - http-errors: "npm:2.0.0" - mime: "npm:1.6.0" - ms: "npm:2.1.3" - on-finished: "npm:2.4.1" - range-parser: "npm:~1.2.1" - statuses: "npm:2.0.1" - checksum: 10/ec66c0ad109680ad8141d507677cfd8b4e40b9559de23191871803ed241718e99026faa46c398dcfb9250676076573bd6bfe5d0ec347f88f4b7b8533d1d391cb - languageName: node - linkType: hard - "send@npm:0.19.0": version: 0.19.0 resolution: "send@npm:0.19.0" @@ -20043,15 +20085,15 @@ __metadata: languageName: node linkType: hard -"serve-static@npm:1.16.0": - version: 1.16.0 - resolution: "serve-static@npm:1.16.0" +"serve-static@npm:1.16.2": + version: 1.16.2 + resolution: "serve-static@npm:1.16.2" dependencies: - encodeurl: "npm:~1.0.2" + encodeurl: "npm:~2.0.0" escape-html: "npm:~1.0.3" parseurl: "npm:~1.3.3" - send: "npm:0.18.0" - checksum: 10/29a01f67e8c64a359d49dd0c46bc95bb4aa99781f97845dccbf0c8cd0284c5fd79ad7fb9433a36fac4b6c58b577d3eab314a379142412413b8b5cd73be3cd551 + send: "npm:0.19.0" + checksum: 10/7fa9d9c68090f6289976b34fc13c50ac8cd7f16ae6bce08d16459300f7fc61fbc2d7ebfa02884c073ec9d6ab9e7e704c89561882bbe338e99fcacb2912fde737 languageName: node linkType: hard @@ -20251,7 +20293,7 @@ __metadata: languageName: node linkType: hard -"simple-update-notifier@npm:2.0.0": +"simple-update-notifier@npm:2.0.0, simple-update-notifier@npm:^2.0.0": version: 2.0.0 resolution: "simple-update-notifier@npm:2.0.0" dependencies: @@ -20974,7 +21016,7 @@ __metadata: languageName: node linkType: hard -"supports-color@npm:^5.3.0": +"supports-color@npm:^5.3.0, supports-color@npm:^5.5.0": version: 5.5.0 resolution: "supports-color@npm:5.5.0" dependencies: @@ -21413,6 +21455,15 @@ __metadata: languageName: node linkType: hard +"touch@npm:^3.1.0": + version: 3.1.1 + resolution: "touch@npm:3.1.1" + bin: + nodetouch: bin/nodetouch.js + checksum: 10/853e763a1f4903302c5654ed353f84ad85baf757dac62c2d37ab67e0477cfd271e8c64771fcfad42310aff7c9d284ddb435ee5ca13ff36d0f3693fedd8e971d1 + languageName: node + linkType: hard + "tough-cookie@npm:^4.0.0": version: 4.1.4 resolution: "tough-cookie@npm:4.1.4" @@ -21907,6 +21958,13 @@ __metadata: languageName: node linkType: hard +"undefsafe@npm:^2.0.5": + version: 2.0.5 + resolution: "undefsafe@npm:2.0.5" + checksum: 10/f42ab3b5770fedd4ada175fc1b2eb775b78f609156f7c389106aafd231bfc210813ee49f54483d7191d7b76e483bc7f537b5d92d19ded27156baf57592eb02cc + languageName: node + linkType: hard + "underscore.string@npm:~3.3.4": version: 3.3.6 resolution: "underscore.string@npm:3.3.6" @@ -22456,9 +22514,9 @@ __metadata: languageName: node linkType: hard -"vite-node@npm:1.6.0": - version: 1.6.0 - resolution: "vite-node@npm:1.6.0" +"vite-node@npm:1.6.1": + version: 1.6.1 + resolution: "vite-node@npm:1.6.1" dependencies: cac: "npm:^6.7.14" debug: "npm:^4.3.4" @@ -22467,7 +22525,7 @@ __metadata: vite: "npm:^5.0.0" bin: vite-node: vite-node.mjs - checksum: 10/40230598c3c285cf65f407ac50b1c7753ab2dfa960de76ec1a95a0ce0ff963919d065c29ba538d9fb2fba3e0703a051d49d1ad6486001ba2f90616cc706ddc3d + checksum: 10/35f77a9efa38fae349e9c383780984deee185e0fdd107394ffe320586c9a896c59e9b098a9a9f96412adb293abf1a27671ca592b39013edadb9e0614aa817419 languageName: node linkType: hard @@ -22551,15 +22609,15 @@ __metadata: languageName: node linkType: hard -"vitest@npm:^1.6.0": - version: 1.6.0 - resolution: "vitest@npm:1.6.0" +"vitest@npm:^1.6.1": + version: 1.6.1 + resolution: "vitest@npm:1.6.1" dependencies: - "@vitest/expect": "npm:1.6.0" - "@vitest/runner": "npm:1.6.0" - "@vitest/snapshot": "npm:1.6.0" - "@vitest/spy": "npm:1.6.0" - "@vitest/utils": "npm:1.6.0" + "@vitest/expect": "npm:1.6.1" + "@vitest/runner": "npm:1.6.1" + "@vitest/snapshot": "npm:1.6.1" + "@vitest/spy": "npm:1.6.1" + "@vitest/utils": "npm:1.6.1" acorn-walk: "npm:^8.3.2" chai: "npm:^4.3.10" debug: "npm:^4.3.4" @@ -22573,13 +22631,13 @@ __metadata: tinybench: "npm:^2.5.1" tinypool: "npm:^0.8.3" vite: "npm:^5.0.0" - vite-node: "npm:1.6.0" + vite-node: "npm:1.6.1" why-is-node-running: "npm:^2.2.2" peerDependencies: "@edge-runtime/vm": "*" "@types/node": ^18.0.0 || >=20.0.0 - "@vitest/browser": 1.6.0 - "@vitest/ui": 1.6.0 + "@vitest/browser": 1.6.1 + "@vitest/ui": 1.6.1 happy-dom: "*" jsdom: "*" peerDependenciesMeta: @@ -22597,7 +22655,7 @@ __metadata: optional: true bin: vitest: vitest.mjs - checksum: 10/ad921a723ac9438636d37111f0b2ea5afd0ba4a7813fb75382b9f75574e10d533cf950573ebb9332a595ce197cb83593737a6b55a3b6e6eb00bddbcd0920a03e + checksum: 10/50d551be2cf6621d3844c42924595007befd73e10e9406e0fa08f1239e2c012d08f85b0a70d8656a11364a6a58930600c35a5ee00d8445071f0ab0afcacd085a languageName: node linkType: hard