Compare commits

...

68 Commits

Author SHA1 Message Date
Matiss Janis Aboltins
9fca85209f 🔖 (24.3.0) (#2413)
* 🔖 (24.3.0)

* Remove used release notes

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-03-03 17:05:39 +00:00
Neil
a9362cc6f9 BarGraph crash (#2411)
* Fix crash

* notes
2024-03-02 07:35:05 +00:00
Neil
40296dc876 Custom reports updates (#2386)
* Button changes and time filters

* rename on dashboard

* notes

* fix time filters

* Sort Categories

* Page title

* category sort order

* move button

* featureflag

* Highlight report name

* sankey fix

* VRT

* remove doubled element

* update

* fixes

* fixes

* create button fix and rename card fix

* Title change
2024-03-01 18:41:32 +00:00
Neil
ed1e0ceb30 Custom Reports AutoComplete (#2350)
* updated saved work

* merge fixes

* Disable CREATE TABLE

* notes

* turn on db table

* Fix TableGraph recall crash

* table format changes

* type fixes

* fixing some card displays

* merge fixes

* revert table change

* cardMenu width

* Add Saved Reports Autocomplete

* notes

* fix invalid values crash

* Title and auto-focus and esc

* notes

* fix filtering logic

* reload saved filters

* lint fix

* visual graph changes

* merge fixes

* fix

* review updates
2024-02-28 21:50:23 +00:00
Neil
55817b0e70 Add interval split and menu items to custom reports (#2389)
* Add interval split and menu items

* notes
2024-02-28 19:47:42 +00:00
youngcw
6d7d12138c Update csv amount parser. Fix #2374 (#2399)
* specify 2 decimal places in the csv amount parser

* note

* update tests

* cleanup
2024-02-28 11:39:23 -07:00
youngcw
52f1f79c01 fix #2363 broken create schedule from transaction (#2401)
* fix missing name

* note

* update how payees is created

* lint
2024-02-27 10:44:20 -07:00
shall0pass
991fc4f450 [Theme] Midnight updates (#2394)
* color updates

* release note
2024-02-26 14:21:54 -06:00
shall0pass
4a8c692d06 [Theme] Midnight updates (#2385)
* update colors

* remove comments
2024-02-21 13:50:00 -06:00
Neil
0609f47cc3 AutoComplete clean-up (#2349)
* AutoComplete clean-up

* notes

* lint fix

* fix tests

* review fix

* type
2024-02-21 18:59:05 +00:00
Julian Dominguez-Schatz
0d1e6f2ee7 Display rules with splits on rules page (#2368)
* Show splits on rules page

* Add `ActionExpression` for 'allocate'-type actions

* Add release notes

* Fix type errors
2024-02-21 11:47:23 -07:00
Neil
8568aebdbb Custom Reports: save reports (*new SQL table creation*) (#2335)
* updated saved work

* merge fixes

* Disable CREATE TABLE

* notes

* turn on db table

* Fix TableGraph recall crash

* table format changes

* type fixes

* fixing some card displays

* merge fixes

* revert table change

* cardMenu width

* notes

* fix filtering logic

* reload saved filters

* lint fix

* visual graph changes

* slice fixes

* typescript update
2024-02-21 18:12:26 +00:00
Julian Dominguez-Schatz
bfb7c1d213 Add plugin for offline PWA support (#2369)
* Add plugin for offline PWA support

* Add release notes

* Attempt to fix kcab fetch issue

* Fix type errors

* Cache more file types

* Empty commit to try to bump action

* Attempt to fix fonts
2024-02-20 18:23:27 -07:00
Joel Jeremy Marquez
e526555748 [Maintenance] Add tsconfig excludes (#2380)
* Add tsconfig excludes

* Release notes
2024-02-20 13:11:14 -08:00
DJ Mountney
45c4b262a2 Fix ability to rename budget in the UI (#2383) 2024-02-20 11:04:54 -08:00
youngcw
e1f805b9c9 schedule default amount (#2360)
* schedule default amount

* note

* vrt
2024-02-20 10:07:18 -07:00
DJ Mountney
d0c11cd3af Converts html special characters in ofx values to plaintext (#2364)
* Converts html special characters in ofx values to plaintext

- Apply during ofx/qfx import
2024-02-17 10:49:02 -08:00
shall0pass
0c5bce8baf [Enhancement] Theme: Midnight (#2312)
* midnight updates

* updates

* updates

* background one shade darker

* change highlights to match other accents

* release note

* link color

* variable spelling

* Upcoming pill color

* theme switch bug

* remove development, type error

* toggle background, disabled background+text

* account pillboxes and icons

* typecheck error
2024-02-16 09:43:24 -06:00
Matiss Janis Aboltins
e650e00cb8 ♻️ (eslint) enable some rules to enforce better code quality (#2357) 2024-02-16 07:34:55 +00:00
DHRUV RAMDEV
422996f8a7 fix: Margin when editing account on desktop (#2286)
* fix: Margin when editing account on desktop

* add release notes

* increase margin

* Update regression tests

* fix: Reduce padding in other areas

* update snapshots
2024-02-15 10:34:23 -08:00
Tristan Radisson
9cad57c607 #1383 Allow early post transaction of schedules (#2358)
* Allow early schedule post transaction

* Added release notes file
2024-02-15 07:33:00 -07:00
Neil
2a0f8335ed Convert SavedFilters to Typescript (#2320)
* move saved filters

* MenuButton

* fixes

* update

* FiltersStack

* notes

* merge fixes

* review fixes

* error catch
2024-02-13 11:16:54 -08:00
Joel Jeremy Marquez
08cbdab2a1 Hooks for frequently made operations (#2293)
* Hooks for frequently made operations

* Release notes

* Fix typecheck errors

* Remove useGlobalPrefs

* Add null checks

* Fix showCleared pref

* Add loaded flag for categories, accounts and payees state

* Refactor to reduce unnecessary states

* Fix eslint errors

* Fix hooks deps

* Add useEffect

* Fix typecheck error

* Set local and global pref hooks

* Fix lint error

* VRT

* Fix typecheck error

* Remove eager loading

* Fix typecheck error

* Fix typo

* Fix typecheck error

* Update useTheme

* Typecheck errors

* Typecheck error

* defaultValue

* Explicitly check undefined

* Remove useGlobalPref and useLocalPref defaults

* Fix default prefs

* Default value

* Fix lint error

* Set default theme

* Default date format in Account

* Update packages/desktop-client/src/style/theme.tsx

Co-authored-by: Matiss Janis Aboltins <matiss@mja.lv>

---------

Co-authored-by: Matiss Janis Aboltins <matiss@mja.lv>
2024-02-12 07:07:44 -08:00
Matiss Janis Aboltins
ec2de3b387 🔥 (remove victory dependency) (#2356) 2024-02-11 21:50:18 +00:00
Johannes Löthberg
65372e86a5 Remove category spending report (#2344)
It has been superseded by the new custom reports feature.

Signed-off-by: Johannes Löthberg <johannes@kyriasis.com>
2024-02-11 17:06:41 +00:00
Hinerangi Courtenay
53a61000a4 fix: Mobile menu blocks items #2040 (#2352)
* Fix Mobile menu blocks items (#2040)

* Only set bottom padding on pages the mobile nav bar is used (#2040)

* Add release note for PR
2024-02-10 18:13:13 -08:00
Neil
485902af6b Update Custom Report styles (#2345)
* updated saved work

* merge fixes

* Disable CREATE TABLE

* notes

* turn on db table

* Fix TableGraph recall crash

* table format changes

* type fixes

* fixing some card displays

* merge fixes

* revert table change

* Revert Changes

* notes

* merge fixes

* notes

* notes

* revert notes

* Area changes
2024-02-10 21:54:47 +00:00
Neil
5a67b7e822 Dynamic graph margins (#2346)
* dynamic graph margins

* notes
2024-02-08 16:31:54 -08:00
DJ Mountney
b994a6a74a Fix parse errors with OFX data with no transactions (#2342)
* Fix parse errors with OFX data with no transactions

- Return an empty array instead of an array of undefined
2024-02-08 13:15:21 -08:00
Matiss Janis Aboltins
b1b266e83c 🐛 (typescript) patching sendCatch type (#2343)
* 🐛 (typescript) patching sendCatch type

* Release notes
2024-02-08 20:53:05 +00:00
youngcw
58626c0026 update manifest (#2285)
* update manifest

* note

* note

* typo

* theme color

* add maskable icons

* fix icon color
2024-02-08 10:41:55 -07:00
DJ Mountney
45094daf2f Typescript: pass 2 at updating api-handlers to match server handlers (#2334)
* Typescript: pass 2 at updating api-handlers to match server handlers
2024-02-08 09:06:43 -08:00
Julian Dominguez-Schatz
2bb7b3c2ee Add rules with splits (#2059)
* Add split creation UI to rule creation modal

* Support applying splits when rules execute

* fix: deserialize transaction before running rules

According to how rules are run in other places in the app, we should be
supplying a "deserialized" (i.e., integer-for-amount and ISO date)
transaction rather than a "serialized" (amount-plus-formatted-date) one.
This fixes a crash in how split transactions are applied, as well as
date-based rules not applying correctly previously (any rule with a date
condition would never match on mobile).

* Add release notes

* Fix missing types pulled in from master

* PR feedback: use `getActions`

* PR feedback: use `flatMap`

* Fix action deletion

* Don't flicker upon split deletion

* Let users specify parent transaction actions (e.g. linking schedules)

* Support empty splits

* Revert adding `no-op` action type

* Support splits by percent

* Fix types

* Fix crash on transactions page when posting a transaction

The crash would probably have occurred in other places too with
auto-posting schedules :/

* Fix a bug where schedules wouldn't be marked as completed

This was because the query that we previously used didn't select parent
transactions, so no transaction was marked as being scheduled (since
only parent transactions have schedule IDs).

* Add feature flag

* Limit set actions within splits to fewer fields

* Fix merge conflict

* Don't run split rules if feature is disabled

* Fix percent-based splits not applying

* Fix crash when editing parent transaction amount

* Auto-format

* Attempt to fix failing tests

* More test/bug fixes

* Add an extra split at the end if there is a remaining amount

* Make sure split has correct values for dynamic remainder

* Remove extraneous console.log
2024-02-08 07:56:30 -07:00
Matiss Janis Aboltins
d6f610a326 added 'show/hide balance' button to cash flow report (#2322) 2024-02-08 08:12:36 +00:00
Joel Jeremy Marquez
8a8113a648 Update loot-core deps (#2280)
* Upgrade desktop-client depenencies

* yarn dedupe

* Update useSelectors

* Update loot-core deps

* yarn dedupe

* Move deps to devDependencies

* yarn dedupe
2024-02-07 18:40:59 -08:00
Joel Jeremy Marquez
029e2f09bf Update desktop client package versions (#2270)
* Upgrade desktop-client depenencies

* Release notes

* yarn dedupe

* yarn dedupe

* Fix typecheck error

* Update sass

* Update useSelectors
2024-02-06 17:43:09 -08:00
jaarasys-henria
86007e392f [Enhancement] Make transaction list sortable by cleared status (#1994) 2024-02-06 15:07:36 -07:00
Neil
4c4f2fd426 Custom Reports: add save reports menu (#2257)
* Add schema work

* notes

* merge fixes

* Add Reports Save Menu

* merge fixes

* updates

* notes

* updates

* updates

* save updates fix

* typecheck fixes

* merge fixes

* saveReport strict Typescript

* fix sidebar

* lint fix

* fixing functionality plus clean up

* clean up
2024-02-05 22:40:46 +00:00
Matiss Janis Aboltins
7a18827b1d allow running AQL against local database (#2326) 2024-02-05 19:16:02 +00:00
shall0pass
77fd65b2e7 [Bugfix] Cleanup tool: Add balance check (#2295)
* add balance check

* lint

* fill rollover categories after non-rollover

* allow partial fills of non-rollover

* update

* release note and youngcw suggestion

* warnings

* remove commented coded
2024-02-05 06:21:42 -06:00
Julian Dominguez-Schatz
30f03e8079 Save name fields on unfocus (#2327) 2024-02-04 16:50:36 +00:00
Matiss Janis Aboltins
6e16262b63 🔥 removing unused/dead code (#2328) 2024-02-04 16:05:11 +00:00
xentara1
29a515f3fe [Feature] Add ability to create schedules from existing transactions (#2222) 2024-02-03 19:00:27 +00:00
Julian Dominguez-Schatz
a5ab1a8fae Re-open autocomplete dropdown on change (#2325)
* Re-open autocomplete dropdown on change

* Add release notes with new PR ID
2024-02-03 11:16:15 -07:00
yoyotogblo
d37622162a Change lookback and look forward time range when fuzzy matching (#2300)
Change to 7 days prior and after for imported transactions when fuzzy matching
2024-02-03 11:08:45 -07:00
Neil
e3a8366dd7 Update and organize reports (#2274)
* Add schema work

* notes

* merge fixes

* Add Reports Save Menu

* merge fixes

* updates

* notes

* updates

* updates

* save updates fix

* typecheck fixes

* revert changes

* notes

* error fixes

* update

* fix

* merge fixes

* review changes

* reportChange and savedStatus

* Update packages/desktop-client/src/components/reports/SaveReport.tsx

Co-authored-by: DJ Mountney <david.mountney@twkie.net>

* merge fixes

---------

Co-authored-by: DJ Mountney <david.mountney@twkie.net>
2024-02-02 23:52:35 -08:00
Joel Jeremy Marquez
d5e49dde59 Update yarn to 4.0.2 (#2283)
* Update yarn

* Release notes
2024-02-02 18:11:07 -08:00
Joel Jeremy Marquez
f5258e6ebe Consider child transactions when fuzzy matching imported transactions (#2309)
* Consider child transaction when fuzzy matching

* Release notes
2024-02-02 18:03:57 -08:00
Joel Jeremy Marquez
f4d80fad92 [Maintenance] Remove modals.d.ts (#2298)
* Remove modals.d.ts

* Release notes

* Fix typecheck

* Fix lint error
2024-02-02 17:56:23 -08:00
DJ Mountney
3324dd5fa0 Fix docker start browser error (#2304)
* Fix docker start browser error

Don't launch browser when in docker
2024-02-02 17:18:27 -08:00
shall0pass
14509d15df [Bugfix] Dark Theme variable name misspelled (#2317)
* misspelling of variable name

* release note
2024-02-02 15:51:06 -06:00
Ifeoluwa Odubela
9b461c48c9 Bugfix: Add Primary Button hover background colors for light and dark theme #1971 (#2123)
* Bugfix: Add Primary Button hover background colors

* Add release notes

* Rename 1971.md to 2123.md

* Update release note 2123.md typo

* Update packages/desktop-client/src/style/themes/dark.ts

* Update packages/desktop-client/src/style/themes/light.ts

---------

Co-authored-by: Neil <55785687+carkom@users.noreply.github.com>
2024-02-02 21:26:13 +00:00
jaarasys-henria
55f2d126b3 [Maintenance] Pass HTTPS flag to dev container to enable HTTPS (#2316) 2024-02-02 21:14:45 +00:00
Matiss Janis Aboltins
6ae2047ab8 🔧 upgrade deprecated github actions (#2319) 2024-02-02 20:38:57 +00:00
Pedro Primor
9fdffcc8e9 Change month picker hover background color (#2121)
* Change month picker hover background color

* Add release notes

* Add updated screenshots from visual regression tests

* Revert "Add updated screenshots from visual regression tests"

This reverts commit d9d83b4789.

* Update failing visual regression tests screenshots

* Revert "Update failing visual regression tests screenshots"

This reverts commit aaef019191.
2024-02-02 13:11:16 -07:00
DHRUV RAMDEV
3daff4381f feat: Don't allow duplicate cat-groups in budget (#2262)
* feat: Don't allow duplicate cat-groups in budget

* Add release notes

* fix: error message

* pass group instead of name for accurate error message

* improve error message
2024-02-02 13:10:36 -07:00
Neil
5914469b11 Convert FiltersMenu to Typescript (part 1) (#2231)
* migration work

* notes

* typecheck

* typecheck fixes

* fixes

* merge fixes

* typecheck updates

* review fixes
2024-02-02 20:10:24 +00:00
youngcw
39e7f2598b fix budget header collapsed colors (#2313)
* fix budget header collapsed colors

* note
2024-02-02 13:09:53 -07:00
Neil
c8d326d24b Custom Reports - split out hidden categories from offbudget (#2302)
* Add Toggles

* budget table

* testing

* updates

* updates

* fixes

* updates

* fix Menu

* lint fixes

* fix keybindings

* revert budget menu changes

* notes

* remove default exports

* fixes

* disabled fix

* add style option

* lint fix

* remove css

* lint fixes

* color updates

* merge menu with togglemenu

* host

* menu fixes

* fix regression

* remove host

* adjustments

* work

* fix hidden filters

* merge fixes

* adjustments

* updates

* fix uncat table values

* fixes

* notes

* title change

* Adjust showHide selector
2024-02-02 20:09:27 +00:00
Matiss Janis Aboltins
d8639a2a71 🔖 (24.2.0) cleared transaction improvements; experimental simplefin bank-sync (#2311) 2024-02-02 19:20:53 +00:00
Matiss Janis Aboltins
734191424b 🐛 (goCardless) patch incomplete migration (#2308) 2024-02-01 16:40:12 +00:00
shall0pass
5d4fcfde00 [Enhancement] Goal Target with cleanup template (#2282)
* update goal target after montly cleanup

* release note
2024-01-31 13:42:05 -06:00
Joel Jeremy Marquez
54d7e5460a [Cleanup] useSingleActiveEditForm hook on mobile budget table (#2263)
* useSingleActiveEditForm on mobile budget table

* Release notes

* Remove unused variables
2024-01-30 14:37:06 -08:00
youngcw
43ebe9e0fd fix bad account sort order in demo (#2279)
* fix bad sort order in demo

* note

* add async back

* fix tests

* fix2

* fix3

* update vrt

* fix image
2024-01-26 09:12:54 -07:00
youngcw
515bdf5a74 update vrt instructions (#2287)
* update

* note

* revise
2024-01-26 09:06:12 -07:00
Ed
018714610a Updating link for GoCardless Bank Account Sync (#2276)
* Updating link for GoCardless Bank Account Sync

* Adding release notes

* Update URL to point to docs

* Fixing lint
2024-01-26 07:11:18 -08:00
Joel Jeremy Marquez
00ee165f8e Mobile Off Budget category label (#2284)
* Mobile Off Budget category label

* Release notes

* Fix error

* Fix release notes
2024-01-26 07:09:29 -08:00
Neil
68442ae9e6 Custom reports: hide "show ..." checkboxes in menu (#2174)
* Add Toggles

* budget table

* testing

* updates

* updates

* fixes

* updates

* fix Menu

* lint fixes

* fix keybindings

* revert budget menu changes

* notes

* remove default exports

* fixes

* disabled fix

* add style option

* lint fix

* remove css

* lint fixes

* color updates

* merge menu with togglemenu

* host

* menu fixes

* fix regression

* remove host

* adjustments

* onToggle

* vrt fix

* typecheck

* merge fixes

* colors

* lint fix
2024-01-24 21:49:12 +00:00
389 changed files with 8420 additions and 5886 deletions

View File

@@ -38,6 +38,7 @@ module.exports = {
extends: [
'react-app',
'plugin:react/recommended',
'plugin:react/jsx-runtime',
'plugin:prettier/recommended',
'plugin:@typescript-eslint/recommended',
'plugin:import/typescript',
@@ -57,7 +58,7 @@ module.exports = {
'@typescript-eslint/no-unused-vars': [
'warn',
{
varsIgnorePattern: '^_',
varsIgnorePattern: '^(_|React)',
ignoreRestSiblings: true,
},
],
@@ -90,15 +91,7 @@ module.exports = {
'react/prop-types': 'off',
// TODO: re-enable these rules
'react-hooks/exhaustive-deps': 'off',
'react/display-name': 'off',
'react/react-in-jsx-scope': 'off',
// 'react-hooks/exhaustive-deps': [
// 'warn',
// {
// additionalHooks: 'useLiveQuery',
// },
// ],
'no-var': 'warn',
'react/jsx-curly-brace-presence': 'warn',
@@ -277,6 +270,70 @@ module.exports = {
'import/no-default-export': 'off',
},
},
{
// TODO: fix the issues in these files
files: [
'./packages/desktop-client/src/components/accounts/Account.jsx',
'./packages/desktop-client/src/components/accounts/MobileAccount.jsx',
'./packages/desktop-client/src/components/accounts/MobileAccounts.jsx',
'./packages/desktop-client/src/components/App.tsx',
'./packages/desktop-client/src/components/budget/BudgetCategories.jsx',
'./packages/desktop-client/src/components/budget/BudgetSummaries.tsx',
'./packages/desktop-client/src/components/budget/DynamicBudgetTable.tsx',
'./packages/desktop-client/src/components/budget/index.tsx',
'./packages/desktop-client/src/components/budget/MobileBudget.tsx',
'./packages/desktop-client/src/components/budget/rollover/HoldTooltip.tsx',
'./packages/desktop-client/src/components/budget/rollover/TransferTooltip.tsx',
'./packages/desktop-client/src/components/common/Menu.tsx',
'./packages/desktop-client/src/components/FinancesApp.tsx',
'./packages/desktop-client/src/components/GlobalKeys.ts',
'./packages/desktop-client/src/components/KeyHandlers.tsx',
'./packages/desktop-client/src/components/LoggedInUser.tsx',
'./packages/desktop-client/src/components/manager/ManagementApp.jsx',
'./packages/desktop-client/src/components/manager/subscribe/common.tsx',
'./packages/desktop-client/src/components/ManageRules.tsx',
'./packages/desktop-client/src/components/mobile/MobileAmountInput.jsx',
'./packages/desktop-client/src/components/mobile/MobileNavTabs.tsx',
'./packages/desktop-client/src/components/Modals.tsx',
'./packages/desktop-client/src/components/modals/EditRule.jsx',
'./packages/desktop-client/src/components/modals/ImportTransactions.jsx',
'./packages/desktop-client/src/components/modals/MergeUnusedPayees.jsx',
'./packages/desktop-client/src/components/Notifications.tsx',
'./packages/desktop-client/src/components/payees/ManagePayees.jsx',
'./packages/desktop-client/src/components/payees/ManagePayeesWithData.jsx',
'./packages/desktop-client/src/components/payees/PayeeTable.tsx',
'./packages/desktop-client/src/components/reports/graphs/tableGraph/ReportTable.tsx',
'./packages/desktop-client/src/components/reports/graphs/tableGraph/ReportTableTotals.tsx',
'./packages/desktop-client/src/components/reports/reports/CashFlowCard.jsx',
'./packages/desktop-client/src/components/reports/reports/CustomReport.jsx',
'./packages/desktop-client/src/components/reports/reports/NetWorthCard.jsx',
'./packages/desktop-client/src/components/reports/SaveReportName.tsx',
'./packages/desktop-client/src/components/reports/useReport.ts',
'./packages/desktop-client/src/components/schedules/ScheduleDetails.jsx',
'./packages/desktop-client/src/components/schedules/SchedulesTable.tsx',
'./packages/desktop-client/src/components/select/DateSelect.tsx',
'./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: {
'react-hooks/exhaustive-deps': 'off',
},
},
],
settings: {
'import/resolver': {

View File

@@ -4,11 +4,11 @@ runs:
using: composite
steps:
- name: Install node
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
node-version: 18.16.0
- name: Cache
uses: actions/cache@v3
uses: actions/cache@v4
id: cache
with:
path: '**/node_modules'

View File

@@ -21,7 +21,7 @@ jobs:
api:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Set up environment
uses: ./.github/actions/setup
- name: Build API
@@ -29,7 +29,7 @@ jobs:
- name: Create package tgz
run: cd packages/api && yarn pack && mv package.tgz actual-api.tgz
- name: Upload Build
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: actual-api
path: packages/api/actual-api.tgz
@@ -37,7 +37,7 @@ jobs:
crdt:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Set up environment
uses: ./.github/actions/setup
- name: Build CRDT
@@ -45,7 +45,7 @@ jobs:
- name: Create package tgz
run: cd packages/crdt && yarn pack && mv package.tgz actual-crdt.tgz
- name: Upload Build
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: actual-crdt
path: packages/crdt/actual-crdt.tgz
@@ -53,18 +53,18 @@ jobs:
web:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Set up environment
uses: ./.github/actions/setup
- name: Build Web
run: ./bin/package-browser
- name: Upload Build
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: actual-web
path: packages/desktop-client/build
- name: Upload Build Stats
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: build-stats
path: packages/desktop-client/build-stats

View File

@@ -14,7 +14,7 @@ jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Set up environment
uses: ./.github/actions/setup
- name: Lint
@@ -22,7 +22,7 @@ jobs:
typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Set up environment
uses: ./.github/actions/setup
- name: Typecheck
@@ -30,7 +30,7 @@ jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Set up environment
uses: ./.github/actions/setup
- name: Test
@@ -40,8 +40,8 @@ jobs:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '19'
- name: Check migrations

View File

@@ -22,14 +22,14 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
uses: github/codeql-action/init@v3
with:
languages: javascript
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
uses: github/codeql-action/analyze@v3
with:
category: '/language:javascript'

View File

@@ -16,7 +16,7 @@ jobs:
outputs:
netlify_url: ${{ steps.netlify.outputs.url }}
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Set up environment
uses: ./.github/actions/setup
- name: Wait for Netlify build to finish
@@ -33,19 +33,20 @@ jobs:
container:
image: mcr.microsoft.com/playwright:v1.41.1-jammy
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Set up environment
uses: ./.github/actions/setup
- name: Run E2E Tests on Netlify URL
run: yarn e2e
env:
E2E_START_URL: ${{ needs.netlify.outputs.netlify_url }}
- uses: actions/upload-artifact@v3
- uses: actions/upload-artifact@v4
if: always()
with:
name: desktop-client-test-results
path: packages/desktop-client/test-results/
retention-days: 30
overwrite: true
vrt:
name: Visual regression
needs: netlify
@@ -53,16 +54,17 @@ jobs:
container:
image: mcr.microsoft.com/playwright:v1.41.1-jammy
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Set up environment
uses: ./.github/actions/setup
- name: Run VRT Tests on Netlify URL
run: yarn vrt
env:
E2E_START_URL: ${{ needs.netlify.outputs.netlify_url }}
- uses: actions/upload-artifact@v3
- uses: actions/upload-artifact@v4
if: always()
with:
name: desktop-client-test-results
path: packages/desktop-client/test-results/
retention-days: 30
overwrite: true

View File

@@ -26,7 +26,7 @@ jobs:
- macos-latest
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- if: ${{ startsWith(matrix.os, 'windows') }}
run: pip.exe install setuptools
- if: ${{ ! startsWith(matrix.os, 'windows') }}
@@ -41,7 +41,7 @@ jobs:
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
- name: Upload Build
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: actual-electron-${{ matrix.os }}
path: |

View File

@@ -24,7 +24,7 @@ jobs:
- macos-latest
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- if: ${{ startsWith(matrix.os, 'windows') }}
run: pip.exe install setuptools
- if: ${{ ! startsWith(matrix.os, 'windows') }}
@@ -34,7 +34,7 @@ jobs:
- name: Build Electron
run: ./bin/package-electron
- name: Upload Build
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: actual-electron-${{ matrix.os }}
path: |

View File

@@ -24,8 +24,8 @@ jobs:
runs-on: ubuntu-latest
steps:
# This is not a security concern because we have approved & merged the PR
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '19'
- name: Handle feature requests

View File

@@ -12,7 +12,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Check release notes
if: startsWith(github.head_ref, 'release/') == false
uses: actualbudget/actions/release-notes/check@main

View File

@@ -46,7 +46,7 @@ jobs:
echo "Build failed on PR branch or ${{github.base_ref}}"
exit 1
- name: Download build artifact from ${{github.base_ref}}
uses: dawidd6/action-download-artifact@v2
uses: dawidd6/action-download-artifact@v3
id: pr-build
with:
branch: ${{github.base_ref}}
@@ -55,7 +55,7 @@ jobs:
path: base
- name: Download build artifact from PR
uses: dawidd6/action-download-artifact@v2
uses: dawidd6/action-download-artifact@v3
with:
pr: ${{github.event.pull_request.number}}
workflow: build.yml

File diff suppressed because one or more lines are too long

View File

@@ -4,4 +4,4 @@ enableGlobalCache: false
nodeLinker: node-modules
yarnPath: .yarn/releases/yarn-4.0.1.cjs
yarnPath: .yarn/releases/yarn-4.0.2.cjs

View File

@@ -10,4 +10,4 @@ if [ ! -d "node_modules" ] || [ "$(ls -A node_modules)" = "" ]; then
yarn
fi
yarn start:browser
BROWSER=0 yarn start:browser

View File

@@ -8,6 +8,8 @@ services:
actual-development:
build: .
image: actual-development
environment:
- HTTPS
ports:
- '3001:3001'
volumes:

View File

@@ -52,17 +52,19 @@
"eslint-plugin-react": "7.32.2",
"eslint-plugin-rulesdir": "^0.2.2",
"node-jq": "^4.0.1",
"npm-run-all": "^4.1.3",
"npm-run-all": "^4.1.5",
"prettier": "3.2.4",
"react-refresh": "^0.14.0",
"source-map-support": "^0.5.21",
"typescript": "^5.0.2",
"typescript-strict-plugin": "^2.2.2-beta.2"
},
"resolutions": {
"rollup": "4.9.4"
},
"engines": {
"node": ">=18.0.0"
},
"packageManager": "yarn@4.0.1",
"packageManager": "yarn@4.0.2",
"browserslist": [
"electron 24.0",
"defaults"

View File

@@ -1,6 +1,6 @@
{
"name": "@actual-app/api",
"version": "6.4.0",
"version": "6.6.0",
"license": "MIT",
"description": "An API for Actual",
"engines": {
@@ -21,17 +21,17 @@
"clean": "rm -rf dist @types"
},
"dependencies": {
"better-sqlite3": "^9.2.2",
"better-sqlite3": "^9.3.0",
"compare-versions": "^6.1.0",
"node-fetch": "^3.3.2",
"uuid": "^9.0.0"
"uuid": "^9.0.1"
},
"devDependencies": {
"@swc/core": "^1.3.105",
"@swc/jest": "^0.2.31",
"@types/jest": "^27.5.0",
"@types/jest": "^27.5.2",
"@types/uuid": "^9.0.2",
"jest": "^27.0.0",
"jest": "^27.5.1",
"tsc-alias": "^1.8.8",
"typescript": "^5.0.2"
}

View File

@@ -17,14 +17,14 @@
"dependencies": {
"google-protobuf": "^3.12.0-rc.1",
"murmurhash": "^2.0.1",
"uuid": "^9.0.0"
"uuid": "^9.0.1"
},
"devDependencies": {
"@swc/core": "^1.3.105",
"@swc/jest": "^0.2.31",
"@types/jest": "^27.5.0",
"@types/jest": "^27.5.2",
"@types/uuid": "^9.0.2",
"jest": "^27.0.0",
"jest": "^27.5.1",
"ts-protoc-gen": "^0.15.0",
"typescript": "^5.0.2"
}

View File

@@ -32,12 +32,19 @@ Prerequisites:
#### Running against the local server
First start the dev server:
First start a dev instance:
```sh
HTTPS=true yarn start
```
or using the dev container:
```
HTTPS=true docker compose up --build
```
Note the network IP address and port the dev instance is listening on.
Next, navigate to the root of your project folder, run the standartised docker container, and launch the visual regression tests from within it.
```sh
@@ -47,11 +54,11 @@ docker run --rm --network host -v $(pwd):/work/ -w /work/ -it mcr.microsoft.com/
# If you receive an error such as "docker: invalid reference format", please instead use the following command:
docker run --rm --network host -v ${pwd}:/work/ -w /work/ -it mcr.microsoft.com/playwright:v1.41.1-jammy /bin/bash
# Run the VRT tests: important - they MUST be ran against a HTTPS server
E2E_START_URL=https://192.168.0.178:3001 yarn vrt
# Run the VRT tests: important - they MUST be ran against a HTTPS server. Use the ip and port noted earlier
E2E_START_URL=https://ip:port yarn vrt
# To update snapshots, use the following command:
E2E_START_URL=https://192.168.0.178:3001 yarn vrt --update-snapshots
E2E_START_URL=https://ip:port yarn vrt --update-snapshots
```
#### Running against a remote server

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 KiB

After

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 129 KiB

After

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 117 KiB

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 117 KiB

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

After

Width:  |  Height:  |  Size: 84 KiB

View File

@@ -49,7 +49,7 @@ test.describe('Mobile', () => {
test('opens the accounts page and asserts on balances', async () => {
const accountsPage = await navigation.goToAccountsPage();
const account = await accountsPage.getNthAccount(0);
const account = await accountsPage.getNthAccount(1);
await expect(account.name).toHaveText('Ally Savings');
await expect(account.balance).toHaveText('7,653.00');
@@ -58,7 +58,7 @@ test.describe('Mobile', () => {
test('opens individual account page and checks that filtering is working', async () => {
const accountsPage = await navigation.goToAccountsPage();
const accountPage = await accountsPage.openNthAccount(1);
const accountPage = await accountsPage.openNthAccount(0);
await expect(accountPage.heading).toHaveText('Bank of America');
expect(await accountPage.getBalance()).toBeGreaterThan(0);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 107 KiB

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 108 KiB

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 105 KiB

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 105 KiB

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 105 KiB

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 105 KiB

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 58 KiB

View File

@@ -1,26 +1,26 @@
{
"name": "@actual-app/web",
"version": "24.1.0",
"version": "24.3.0",
"license": "MIT",
"files": [
"build"
],
"devDependencies": {
"@juggle/resize-observer": "^3.1.2",
"@juggle/resize-observer": "^3.4.0",
"@playwright/test": "^1.41.1",
"@reach/listbox": "^0.18.0",
"@react-aria/focus": "^3.14.0",
"@react-aria/listbox": "^3.10.1",
"@react-aria/utils": "^3.19.0",
"@react-stately/collections": "^3.10.0",
"@react-stately/list": "^3.9.1",
"@react-aria/focus": "^3.16.0",
"@react-aria/listbox": "^3.11.3",
"@react-aria/utils": "^3.23.0",
"@react-stately/collections": "^3.10.4",
"@react-stately/list": "^3.10.2",
"@rollup/plugin-inject": "^5.0.5",
"@svgr/cli": "^8.0.1",
"@svgr/cli": "^8.1.0",
"@swc/core": "^1.3.105",
"@swc/helpers": "^0.5.3",
"@swc/plugin-react-remove-properties": "^1.5.108",
"@testing-library/react": "14.0.0",
"@testing-library/user-event": "14.4.3",
"@testing-library/react": "14.1.2",
"@testing-library/user-event": "14.5.2",
"@types/react": "^18.2.0",
"@types/react-dom": "^18.2.1",
"@types/react-modal": "^3.16.0",
@@ -32,14 +32,14 @@
"@vitejs/plugin-react-swc": "^3.5.0",
"chokidar": "^3.5.3",
"cross-env": "^7.0.3",
"date-fns": "^2.29.3",
"debounce": "^1.2.0",
"downshift": "7.6.0",
"focus-visible": "^4.1.1",
"date-fns": "^2.30.0",
"debounce": "^1.2.1",
"downshift": "7.6.2",
"focus-visible": "^4.1.5",
"glamor": "^2.20.40",
"hotkeys-js": "3.10.3",
"hotkeys-js": "^3.13.5",
"inter-ui": "^3.19.3",
"jest": "^27.0.0",
"jest": "^27.5.1",
"jest-watch-typeahead": "^2.2.2",
"mdast-util-newline-to-break": "^2.0.0",
"memoize-one": "^6.0.0",
@@ -48,27 +48,26 @@
"react-dnd": "^16.0.1",
"react-dnd-html5-backend": "^16.0.1",
"react-dom": "18.2.0",
"react-error-boundary": "^4.0.11",
"react-error-boundary": "^4.0.12",
"react-markdown": "^8.0.7",
"react-merge-refs": "^1.1.0",
"react-modal": "3.16.1",
"react-redux": "7.2.1",
"react-router-dom": "6.11.2",
"react-redux": "7.2.9",
"react-router-dom": "6.21.3",
"react-simple-pull-to-refresh": "^1.3.3",
"react-spring": "^9.7.1",
"react-virtualized-auto-sizer": "^1.0.2",
"recharts": "^2.8.0",
"redux": "^4.0.5",
"redux-thunk": "^2.3.0",
"react-spring": "^9.7.3",
"react-virtualized-auto-sizer": "^1.0.21",
"recharts": "^2.10.4",
"redux": "^4.2.1",
"redux-thunk": "^2.4.2",
"remark-gfm": "^3.0.1",
"rollup-plugin-visualizer": "^5.11.0",
"sass": "^1.63.6",
"rollup-plugin-visualizer": "^5.12.0",
"sass": "^1.70.0",
"swc-loader": "^0.2.3",
"terser-webpack-plugin": "^5.3.9",
"typescript": "^5.0.2",
"uuid": "^9.0.0",
"victory": "^36.6.8",
"terser-webpack-plugin": "^5.3.10",
"uuid": "^9.0.1",
"vite": "^5.0.12",
"vite-plugin-pwa": "^0.19.0",
"vite-tsconfig-paths": "^4.3.1",
"vitest": "^1.2.1",
"webpack-bundle-analyzer": "^4.10.1",

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

View File

@@ -1,20 +1,51 @@
{
"name": "Actual",
"short_name": "Actual",
"description": "A local-first personal finance tool",
"icons": [
{
"src": "/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
"type": "image/png",
"purpose": "any"
},
{
"src": "/android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
"type": "image/png",
"purpose": "any"
},
{
"src": "/maskable-192x192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "/maskable-512x512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
}
],
"theme_color": "#ffffff",
"screenshots": [
{
"src": "/screenshot_wide.png",
"form_factor": "wide",
"label": "Actual Budget Homepage",
"type": "image/png",
"sizes": "1280x720"
},
{
"src": "/screenshot_narrow.png",
"form_factor": "narrow",
"label": "Actual Budget Mobile Homepage",
"type": "image/png",
"sizes": "350x600"
}
],
"theme_color": "#8812E1",
"background_color": "#ffffff",
"display": "standalone",
"start_url": "."
"start_url": "./"
}

View File

@@ -8,13 +8,14 @@ import {
import { useSelector } from 'react-redux';
import * as Platform from 'loot-core/src/client/platform';
import { type State } from 'loot-core/src/client/state-types';
import {
init as initConnection,
send,
} from 'loot-core/src/platform/client/fetch';
import { type GlobalPrefs } from 'loot-core/src/types/prefs';
import { useActions } from '../hooks/useActions';
import { useLocalPref } from '../hooks/useLocalPref';
import { installPolyfills } from '../polyfills';
import { ResponsiveProvider } from '../ResponsiveProvider';
import { styles, hasHiddenScrollbars, ThemeStyle } from '../style';
@@ -31,26 +32,13 @@ import { UpdateNotification } from './UpdateNotification';
type AppInnerProps = {
budgetId: string;
cloudFileId: string;
loadingText: string;
loadBudget: (
id: string,
loadingText?: string,
options?: object,
) => Promise<void>;
closeBudget: () => Promise<void>;
loadGlobalPrefs: () => Promise<GlobalPrefs>;
};
function AppInner({
budgetId,
cloudFileId,
loadingText,
loadBudget,
closeBudget,
loadGlobalPrefs,
}: AppInnerProps) {
function AppInner({ budgetId, cloudFileId }: AppInnerProps) {
const [initializing, setInitializing] = useState(true);
const { showBoundary: showErrorBoundary } = useErrorBoundary();
const loadingText = useSelector((state: State) => state.app.loadingText);
const { loadBudget, closeBudget, loadGlobalPrefs } = useActions();
async function init() {
const socketName = await global.Actual.getServerSocket();
@@ -123,14 +111,9 @@ function ErrorFallback({ error }: FallbackProps) {
}
export function App() {
const budgetId = useSelector(
state => state.prefs.local && state.prefs.local.id,
);
const cloudFileId = useSelector(
state => state.prefs.local && state.prefs.local.cloudFileId,
);
const loadingText = useSelector(state => state.app.loadingText);
const { loadBudget, closeBudget, loadGlobalPrefs, sync } = useActions();
const [budgetId] = useLocalPref('id');
const [cloudFileId] = useLocalPref('cloudFileId');
const { sync } = useActions();
const [hiddenScrollbars, setHiddenScrollbars] = useState(
hasHiddenScrollbars(),
);
@@ -179,14 +162,7 @@ export function App() {
{process.env.REACT_APP_REVIEW_ID && !Platform.isPlaywright && (
<DevelopmentTopBar />
)}
<AppInner
budgetId={budgetId}
cloudFileId={cloudFileId}
loadingText={loadingText}
loadBudget={loadBudget}
closeBudget={closeBudget}
loadGlobalPrefs={loadGlobalPrefs}
/>
<AppInner budgetId={budgetId} cloudFileId={cloudFileId} />
</ErrorBoundary>
<ThemeStyle />
</View>

View File

@@ -2,6 +2,8 @@ import React from 'react';
import { useSelector } from 'react-redux';
import { useTransition, animated } from 'react-spring';
import { type State } from 'loot-core/src/client/state-types';
import { theme, styles } from '../style';
import { AnimatedRefresh } from './AnimatedRefresh';
@@ -9,7 +11,9 @@ import { Text } from './common/Text';
import { View } from './common/View';
export function BankSyncStatus() {
const accountsSyncing = useSelector(state => state.account.accountsSyncing);
const accountsSyncing = useSelector(
(state: State) => state.account.accountsSyncing,
);
const name = accountsSyncing
? accountsSyncing === '__all'

View File

@@ -2,6 +2,7 @@
import React, { type ReactElement, useEffect, useMemo } from 'react';
import { DndProvider } from 'react-dnd';
import { HTML5Backend as Backend } from 'react-dnd-html5-backend';
import { useSelector } from 'react-redux';
import {
Route,
Routes,
@@ -13,12 +14,12 @@ import {
import hotkeys from 'hotkeys-js';
import { AccountsProvider } from 'loot-core/src/client/data-hooks/accounts';
import { PayeesProvider } from 'loot-core/src/client/data-hooks/payees';
import { SpreadsheetProvider } from 'loot-core/src/client/SpreadsheetProvider';
import { type State } from 'loot-core/src/client/state-types';
import { checkForUpdateNotification } from 'loot-core/src/client/update-notification';
import * as undo from 'loot-core/src/platform/client/undo';
import { useAccounts } from '../hooks/useAccounts';
import { useActions } from '../hooks/useActions';
import { useNavigate } from '../hooks/useNavigate';
import { useResponsive } from '../ResponsiveProvider';
@@ -39,7 +40,8 @@ import { Reports } from './reports';
import { NarrowAlternate, WideComponent } from './responsive';
import { ScrollProvider } from './ScrollProvider';
import { Settings } from './settings';
import { FloatableSidebar, SidebarProvider } from './sidebar';
import { FloatableSidebar } from './sidebar';
import { SidebarProvider } from './sidebar/SidebarProvider';
import { Titlebar, TitlebarProvider } from './Titlebar';
import { TransactionEdit } from './transactions/MobileTransaction';
@@ -71,18 +73,19 @@ function WideNotSupported({ children, redirectTo = '/budget' }) {
return isNarrowWidth ? children : null;
}
function RouterBehaviors({ getAccounts }) {
function RouterBehaviors() {
const navigate = useNavigate();
const accounts = useAccounts();
const accountsLoaded = useSelector(
(state: State) => state.queries.accountsLoaded,
);
useEffect(() => {
// Get the accounts and check if any exist. If there are no
// accounts, we want to redirect the user to the All Accounts
// screen which will prompt them to add an account
getAccounts().then(accounts => {
if (accounts.length === 0) {
navigate('/accounts');
}
});
}, []);
// If there are no accounts, we want to redirect the user to
// the All Accounts screen which will prompt them to add an account
if (accountsLoaded && accounts.length === 0) {
navigate('/accounts');
}
}, [accountsLoaded, accounts]);
const location = useLocation();
const href = useHref(location);
@@ -116,7 +119,7 @@ function FinancesAppWithoutContext() {
return (
<BrowserRouter>
<RouterBehaviors getAccounts={actions.getAccounts} />
<RouterBehaviors />
<ExposeNavigate />
<View style={{ height: '100%' }}>
@@ -265,13 +268,9 @@ export function FinancesApp() {
<TitlebarProvider>
<SidebarProvider>
<BudgetMonthCountProvider>
<PayeesProvider>
<AccountsProvider>
<DndProvider backend={Backend}>
<ScrollProvider>{app}</ScrollProvider>
</DndProvider>
</AccountsProvider>
</PayeesProvider>
<DndProvider backend={Backend}>
<ScrollProvider>{app}</ScrollProvider>
</DndProvider>
</BudgetMonthCountProvider>
</SidebarProvider>
</TitlebarProvider>

View File

@@ -2,6 +2,8 @@
import React, { useState, useEffect } from 'react';
import { useSelector } from 'react-redux';
import { type State } from 'loot-core/src/client/state-types';
import { useActions } from '../hooks/useActions';
import { theme, styles, type CSSProperties } from '../style';
@@ -22,7 +24,7 @@ export function LoggedInUser({
style,
color,
}: LoggedInUserProps) {
const userData = useSelector(state => state.user.data);
const userData = useSelector((state: State) => state.user.data);
const { getUserData, signOut, closeBudget } = useActions();
const [loading, setLoading] = useState(true);
const [menuOpen, setMenuOpen] = useState(false);

View File

@@ -7,7 +7,7 @@ import React, {
type SetStateAction,
type Dispatch,
} from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useDispatch } from 'react-redux';
import { pushModal } from 'loot-core/src/client/actions/modals';
import { initiallyLoadPayees } from 'loot-core/src/client/actions/queries';
@@ -17,7 +17,9 @@ import { mapField, friendlyOp } from 'loot-core/src/shared/rules';
import { describeSchedule } from 'loot-core/src/shared/schedules';
import { type RuleEntity } from 'loot-core/src/types/models';
import { useAccounts } from '../hooks/useAccounts';
import { useCategories } from '../hooks/useCategories';
import { usePayees } from '../hooks/usePayees';
import { useSelected, SelectedProvider } from '../hooks/useSelected';
import { theme } from '../style';
@@ -103,11 +105,13 @@ function ManageRulesContent({
const { data: schedules } = SchedulesQuery.useQuery();
const { list: categories } = useCategories();
const state = useSelector(state => ({
payees: state.queries.payees,
accounts: state.queries.accounts,
const payees = usePayees();
const accounts = useAccounts();
const state = {
payees,
accounts,
schedules,
}));
};
const filterData = useMemo(
() => ({
...state,

View File

@@ -1,8 +1,6 @@
import React, { useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { savePrefs } from 'loot-core/src/client/actions';
import { useLocalPref } from '../hooks/useLocalPref';
import { useResponsive } from '../ResponsiveProvider';
import { theme, styles } from '../style';
@@ -14,27 +12,24 @@ import { Checkbox } from './forms';
const buttonStyle = { border: 0, fontSize: 15, padding: '10px 13px' };
export function MobileWebMessage() {
const hideMobileMessagePref = useSelector(state => {
return (state.prefs.local && state.prefs.local.hideMobileMessage) || true;
});
const [hideMobileMessage = true, setHideMobileMessagePref] =
useLocalPref('hideMobileMessage');
const { isNarrowWidth } = useResponsive();
const [show, setShow] = useState(
isNarrowWidth &&
!hideMobileMessagePref &&
!hideMobileMessage &&
!document.cookie.match(/hideMobileMessage=true/),
);
const [requestDontRemindMe, setRequestDontRemindMe] = useState(false);
const dispatch = useDispatch();
function onTry() {
setShow(false);
if (requestDontRemindMe) {
// remember the pref indefinitely
dispatch(savePrefs({ hideMobileMessage: true }));
setHideMobileMessagePref(true);
} else {
// Set a cookie for 5 minutes
const d = new Date();

View File

@@ -3,12 +3,12 @@ import React, { useEffect } from 'react';
import { useSelector } from 'react-redux';
import { useLocation } from 'react-router-dom';
import { type State } from 'loot-core/src/client/state-types';
import { type PopModalAction } from 'loot-core/src/client/state-types/modals';
import { send } from 'loot-core/src/platform/client/fetch';
import { useActions } from '../hooks/useActions';
import { useCategories } from '../hooks/useCategories';
import { useSyncServerStatus } from '../hooks/useSyncServerStatus';
import { type CommonModalProps } from '../types/modals';
import { CategoryGroupMenu } from './modals/CategoryGroupMenu';
import { CategoryMenu } from './modals/CategoryMenu';
@@ -40,14 +40,18 @@ import { PostsOfflineNotification } from './schedules/PostsOfflineNotification';
import { ScheduleDetails } from './schedules/ScheduleDetails';
import { ScheduleLink } from './schedules/ScheduleLink';
export type CommonModalProps = {
onClose: () => PopModalAction;
onBack: () => PopModalAction;
showBack: boolean;
isCurrent: boolean;
isHidden: boolean;
stackIndex: number;
};
export function Modals() {
const modalStack = useSelector(state => state.modals.modalStack);
const isHidden = useSelector(state => state.modals.isHidden);
const accounts = useSelector(state => state.queries.accounts);
const { grouped: categoryGroups, list: categories } = useCategories();
const budgetId = useSelector(
state => state.prefs.local && state.prefs.local.id,
);
const modalStack = useSelector((state: State) => state.modals.modalStack);
const isHidden = useSelector((state: State) => state.modals.isHidden);
const actions = useActions();
const location = useLocation();
@@ -97,8 +101,6 @@ export function Modals() {
account={options.account}
balance={options.balance}
canDelete={options.canDelete}
accounts={accounts.filter(acct => acct.closed === 0)}
categoryGroups={categoryGroups}
actions={actions}
/>
);
@@ -109,7 +111,6 @@ export function Modals() {
modalProps={modalProps}
externalAccounts={options.accounts}
requisitionId={options.requisitionId}
localAccounts={accounts.filter(acct => acct.closed === 0)}
actions={actions}
syncSource={options.syncSource}
/>
@@ -119,15 +120,8 @@ export function Modals() {
return (
<ConfirmCategoryDelete
modalProps={modalProps}
category={
'category' in options &&
categories.find(c => c.id === options.category)
}
group={
'group' in options &&
categoryGroups.find(g => g.id === options.group)
}
categoryGroups={categoryGroups}
category={options.category}
group={options.group}
onDelete={options.onDelete}
/>
);
@@ -145,7 +139,7 @@ export function Modals() {
return (
<LoadBackup
watchUpdates
budgetId={budgetId}
budgetId={options.budgetId}
modalProps={modalProps}
actions={actions}
backupDisabled={false}
@@ -301,6 +295,7 @@ export function Modals() {
modalProps={modalProps}
id={options?.id || null}
actions={actions}
transaction={options?.transaction || null}
/>
);
@@ -311,6 +306,8 @@ export function Modals() {
modalProps={modalProps}
actions={actions}
transactionIds={options?.transactionIds}
getTransaction={options?.getTransaction}
pushModal={options?.pushModal}
/>
);

View File

@@ -7,6 +7,7 @@ import React, {
} from 'react';
import { useSelector } from 'react-redux';
import { type State } from 'loot-core/src/client/state-types';
import type { NotificationWithId } from 'loot-core/src/client/state-types/notifications';
import { useActions } from '../hooks/useActions';
@@ -238,7 +239,9 @@ function Notification({
export function Notifications({ style }: { style?: CSSProperties }) {
const { removeNotification } = useActions();
const notifications = useSelector(state => state.notifications.notifications);
const notifications = useSelector(
(state: State) => state.notifications.notifications,
);
return (
<View
style={{

View File

@@ -7,8 +7,7 @@ import React, {
type ReactNode,
} from 'react';
import { usePrivacyMode } from 'loot-core/src/client/privacy';
import { usePrivacyMode } from '../hooks/usePrivacyMode';
import { useResponsive } from '../ResponsiveProvider';
import { View } from './common/View';

View File

@@ -2,7 +2,6 @@ import React, { useState } from 'react';
import type { Theme } from 'loot-core/src/types/prefs';
import { useActions } from '../hooks/useActions';
import { SvgMoonStars, SvgSun, SvgSystem } from '../icons/v2';
import { useResponsive } from '../ResponsiveProvider';
import { type CSSProperties, themeOptions, useTheme } from '../style';
@@ -16,8 +15,7 @@ type ThemeSelectorProps = {
};
export function ThemeSelector({ style }: ThemeSelectorProps) {
const theme = useTheme();
const { saveGlobalPrefs } = useActions();
const [theme, switchTheme] = useTheme();
const [menuOpen, setMenuOpen] = useState(false);
const { isNarrowWidth } = useResponsive();
@@ -26,17 +24,15 @@ export function ThemeSelector({ style }: ThemeSelectorProps) {
light: SvgSun,
dark: SvgMoonStars,
auto: SvgSystem,
midnight: SvgMoonStars,
} as const;
async function onMenuSelect(newTheme: string) {
function onMenuSelect(newTheme: Theme) {
setMenuOpen(false);
saveGlobalPrefs({
theme: newTheme as Theme,
});
switchTheme(newTheme);
}
const Icon = themeIcons?.[theme] || SvgSun;
const Icon = themeIcons[theme] || SvgSun;
return isNarrowWidth ? null : (
<Button

View File

@@ -6,7 +6,6 @@ import React, {
useContext,
type ReactNode,
} from 'react';
import { useSelector } from 'react-redux';
import { Routes, Route, useLocation } from 'react-router-dom';
import * as Platform from 'loot-core/src/client/platform';
@@ -16,6 +15,8 @@ import { type LocalPrefs } from 'loot-core/src/types/prefs';
import { useActions } from '../hooks/useActions';
import { useFeatureFlag } from '../hooks/useFeatureFlag';
import { useGlobalPref } from '../hooks/useGlobalPref';
import { useLocalPref } from '../hooks/useLocalPref';
import { useNavigate } from '../hooks/useNavigate';
import { SvgArrowLeft } from '../icons/v1';
import {
@@ -39,7 +40,7 @@ import { View } from './common/View';
import { KeyHandlers } from './KeyHandlers';
import { LoggedInUser } from './LoggedInUser';
import { useServerURL } from './ServerContext';
import { useSidebar } from './sidebar';
import { useSidebar } from './sidebar/SidebarProvider';
import { useSheetValue } from './spreadsheet/useSheetValue';
import { ThemeSelector } from './ThemeSelector';
import { Tooltip } from './tooltips';
@@ -118,10 +119,8 @@ type PrivacyButtonProps = {
};
function PrivacyButton({ style }: PrivacyButtonProps) {
const isPrivacyEnabled = useSelector(
state => state.prefs.local?.isPrivacyEnabled,
);
const { savePrefs } = useActions();
const [isPrivacyEnabled, setPrivacyEnabledPref] =
useLocalPref('isPrivacyEnabled');
const privacyIconStyle = { width: 15, height: 15 };
@@ -129,7 +128,7 @@ function PrivacyButton({ style }: PrivacyButtonProps) {
<Button
type="bare"
aria-label={`${isPrivacyEnabled ? 'Disable' : 'Enable'} privacy mode`}
onClick={() => savePrefs({ isPrivacyEnabled: !isPrivacyEnabled })}
onClick={() => setPrivacyEnabledPref(!isPrivacyEnabled)}
style={style}
>
{isPrivacyEnabled ? (
@@ -146,7 +145,7 @@ type SyncButtonProps = {
isMobile?: boolean;
};
function SyncButton({ style, isMobile = false }: SyncButtonProps) {
const cloudFileId = useSelector(state => state.prefs.local?.cloudFileId);
const [cloudFileId] = useLocalPref('cloudFileId');
const { sync } = useActions();
const [syncing, setSyncing] = useState(false);
@@ -286,9 +285,8 @@ function SyncButton({ style, isMobile = false }: SyncButtonProps) {
}
function BudgetTitlebar() {
const maxMonths = useSelector(state => state.prefs.global?.maxMonths);
const budgetType = useSelector(state => state.prefs.local?.budgetType);
const { saveGlobalPrefs } = useActions();
const [maxMonths, setMaxMonthsPref] = useGlobalPref('maxMonths');
const [budgetType] = useLocalPref('budgetType');
const { sendEvent } = useContext(TitlebarContext);
const [loading, setLoading] = useState(false);
@@ -317,7 +315,7 @@ function BudgetTitlebar() {
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
<MonthCountSelector
maxMonths={maxMonths || 1}
onChange={value => saveGlobalPrefs({ maxMonths: value })}
onChange={value => setMaxMonthsPref(value)}
/>
{reportBudgetEnabled && (
<View style={{ marginLeft: -5 }}>
@@ -390,9 +388,7 @@ export function Titlebar({ style }: TitlebarProps) {
const sidebar = useSidebar();
const { isNarrowWidth } = useResponsive();
const serverURL = useServerURL();
const floatingSidebar = useSelector(
state => state.prefs.global?.floatingSidebar,
);
const [floatingSidebar] = useGlobalPref('floatingSidebar');
return isNarrowWidth ? null : (
<View

View File

@@ -1,6 +1,8 @@
import React from 'react';
import { useSelector } from 'react-redux';
import { type State } from 'loot-core/src/client/state-types';
import { useActions } from '../hooks/useActions';
import { SvgClose } from '../icons/v1';
import { theme } from '../style';
@@ -11,9 +13,9 @@ import { Text } from './common/Text';
import { View } from './common/View';
export function UpdateNotification() {
const updateInfo = useSelector(state => state.app.updateInfo);
const updateInfo = useSelector((state: State) => state.app.updateInfo);
const showUpdateNotification = useSelector(
state => state.app.showUpdateNotification,
(state: State) => state.app.showUpdateNotification,
);
const { updateApp, setAppState } = useActions();

View File

@@ -26,7 +26,12 @@ import {
} from 'loot-core/src/shared/transactions';
import { applyChanges, groupById } from 'loot-core/src/shared/util';
import { useAccounts } from '../../hooks/useAccounts';
import { useCategories } from '../../hooks/useCategories';
import { useDateFormat } from '../../hooks/useDateFormat';
import { useFailedAccounts } from '../../hooks/useFailedAccounts';
import { useLocalPref } from '../../hooks/useLocalPref';
import { usePayees } from '../../hooks/usePayees';
import { SelectedProviderWithItems } from '../../hooks/useSelected';
import { styles, theme } from '../../style';
import { Button } from '../common/Button';
@@ -1199,6 +1204,7 @@ class AccountInternal extends PureComponent {
applySort = (field, ascDesc, prevField, prevAscDesc) => {
const filters = this.state.filters;
const isFiltered = filters.length > 0;
const sortField = getField(!field ? this.state.sort.field : field);
const sortAscDesc = !ascDesc ? this.state.sort.ascDesc : ascDesc;
const sortPrevField = getField(
@@ -1208,34 +1214,78 @@ class AccountInternal extends PureComponent {
? this.state.sort.prevAscDesc
: prevAscDesc;
if (!field) {
//no sort was made (called by applyFilters)
this.currentQuery = this.currentQuery.orderBy({
const sortCurrentQuery = function (that, sortField, sortAscDesc) {
if (sortField === 'cleared') {
that.currentQuery = that.currentQuery.orderBy({
reconciled: sortAscDesc,
});
}
that.currentQuery = that.currentQuery.orderBy({
[sortField]: sortAscDesc,
});
} else {
//sort called directly
if (filters.length > 0) {
//if filters already exist then apply them
this.applyFilters([...filters]);
this.currentQuery = this.currentQuery.orderBy({
[sortField]: sortAscDesc,
};
const sortRootQuery = function (that, sortField, sortAscDesc) {
if (sortField === 'cleared') {
that.currentQuery = that.rootQuery.orderBy({
reconciled: sortAscDesc,
});
that.currentQuery = that.currentQuery.orderBy({
cleared: sortAscDesc,
});
} else {
//no filters exist make new rootquery
this.currentQuery = this.rootQuery.orderBy({
that.currentQuery = that.rootQuery.orderBy({
[sortField]: sortAscDesc,
});
}
}
if (sortPrevField) {
//apply previos sort if it exists
this.currentQuery = this.currentQuery.orderBy({
};
// sort by previously used sort field, if any
const maybeSortByPreviousField = function (
that,
sortPrevField,
sortPrevAscDesc,
) {
if (!sortPrevField) {
return;
}
if (sortPrevField === 'cleared') {
that.currentQuery = that.currentQuery.orderBy({
reconciled: sortPrevAscDesc,
});
}
that.currentQuery = that.currentQuery.orderBy({
[sortPrevField]: sortPrevAscDesc,
});
};
switch (true) {
// called by applyFilters to sort an already filtered result
case !field:
sortCurrentQuery(this, sortField, sortAscDesc);
break;
// called directly from UI by sorting a column.
// active filters need to be applied before sorting
case isFiltered:
this.applyFilters([...filters]);
sortCurrentQuery(this, sortField, sortAscDesc);
break;
// called directly from UI by sorting a column.
// no active filters, start a new root query.
case !isFiltered:
sortRootQuery(this, sortField, sortAscDesc);
break;
default:
}
this.updateQuery(this.currentQuery, this.state.filters.length > 0);
maybeSortByPreviousField(this, sortPrevField, sortPrevAscDesc);
this.updateQuery(this.currentQuery, isFiltered);
};
onSort = (headerClicked, ascDesc) => {
@@ -1487,23 +1537,41 @@ export function Account() {
const location = useLocation();
const { grouped: categoryGroups } = useCategories();
const state = useSelector(state => ({
newTransactions: state.queries.newTransactions,
matchedTransactions: state.queries.matchedTransactions,
accounts: state.queries.accounts,
failedAccounts: state.account.failedAccounts,
dateFormat: state.prefs.local.dateFormat || 'MM/dd/yyyy',
hideFraction: state.prefs.local.hideFraction || false,
expandSplits: state.prefs.local['expand-splits'],
showBalances: params.id && state.prefs.local['show-balances-' + params.id],
showCleared: params.id && !state.prefs.local['hide-cleared-' + params.id],
showExtraBalances:
state.prefs.local['show-extra-balances-' + params.id || 'all-accounts'],
payees: state.queries.payees,
modalShowing: state.modals.modalStack.length > 0,
accountsSyncing: state.account.accountsSyncing,
lastUndoState: state.app.lastUndoState,
}));
const newTransactions = useSelector(state => state.queries.newTransactions);
const matchedTransactions = useSelector(
state => state.queries.matchedTransactions,
);
const accounts = useAccounts();
const payees = usePayees();
const failedAccounts = useFailedAccounts();
const dateFormat = useDateFormat() || 'MM/dd/yyyy';
const [hideFraction = false] = useLocalPref('hideFraction');
const [expandSplits] = useLocalPref('expand-splits');
const [showBalances] = useLocalPref(`show-balances-${params.id}`);
const [hideCleared] = useLocalPref(`hide-cleared-${params.id}`);
const [showExtraBalances] = useLocalPref(
`show-extra-balances-${params.id || 'all-accounts'}`,
);
const modalShowing = useSelector(state => state.modals.modalStack.length > 0);
const accountsSyncing = useSelector(state => state.account.accountsSyncing);
const lastUndoState = useSelector(state => state.app.lastUndoState);
const state = {
newTransactions,
matchedTransactions,
accounts,
failedAccounts,
dateFormat,
hideFraction,
expandSplits,
showBalances,
showCleared: !hideCleared,
showExtraBalances,
payees,
modalShowing,
accountsSyncing,
lastUndoState,
};
const dispatch = useDispatch();
const filtersList = useFilters();

View File

@@ -3,6 +3,7 @@ import { useSelector } from 'react-redux';
import { useParams } from 'react-router-dom';
import { authorizeBank } from '../../gocardless';
import { useAccounts } from '../../hooks/useAccounts';
import { useActions } from '../../hooks/useActions';
import { SvgExclamationOutline } from '../../icons/v1';
import { theme } from '../../style';
@@ -49,7 +50,7 @@ function getErrorMessage(type, code) {
}
export function AccountSyncCheck() {
const accounts = useSelector(state => state.queries.accounts);
const accounts = useAccounts();
const failedAccounts = useSelector(state => state.account.failedAccounts);
const { unlinkAccount, pushModal } = useActions();

View File

@@ -148,6 +148,8 @@ export function Balances({
opacity: selectedItems.size > 0 || showExtraBalances ? 1 : 0,
},
'&:hover svg': { opacity: 1 },
paddingTop: 1,
paddingBottom: 1,
}}
>
<CellValue

View File

@@ -1,5 +1,6 @@
import React, { useState, useRef } from 'react';
import { useLocalPref } from '../../hooks/useLocalPref';
import { useSyncServerStatus } from '../../hooks/useSyncServerStatus';
import { AnimatedLoading } from '../../icons/AnimatedLoading';
import { SvgAdd } from '../../icons/v1';
@@ -21,7 +22,7 @@ import { Search } from '../common/Search';
import { Stack } from '../common/Stack';
import { View } from '../common/View';
import { FilterButton } from '../filters/FiltersMenu';
import { FiltersStack } from '../filters/SavedFilters';
import { FiltersStack } from '../filters/FiltersStack';
import { KeyHandlers } from '../KeyHandlers';
import { NotesButton } from '../NotesButton';
import { SelectedTransactionsButton } from '../transactions/SelectedTransactions';
@@ -53,7 +54,6 @@ export function AccountHeader({
search,
filters,
conditionsOp,
savePrefs,
pushModal,
onSearch,
onAddTransaction,
@@ -83,11 +83,15 @@ export function AccountHeader({
const [menuOpen, setMenuOpen] = useState(false);
const searchInput = useRef(null);
const splitsExpanded = useSplitsExpanded();
const syncServerStatus = useSyncServerStatus();
const isUsingServer = syncServerStatus !== 'no-server';
const isServerOffline = syncServerStatus === 'offline';
const [_, setExpandSplitsPref] = useLocalPref('expand-splits');
let canSync = account && account.account_id;
let canSync = account && account.account_id && isUsingServer;
if (!account) {
// All accounts - check for any syncable account
canSync = !!accounts.find(account => !!account.account_id);
canSync = !!accounts.find(account => !!account.account_id) && isUsingServer;
}
function onToggleSplits() {
@@ -97,9 +101,7 @@ export function AccountHeader({
id: tableRef.current.getScrolledItem(),
});
savePrefs({
'expand-splits': !(splitsExpanded.state.mode === 'expand'),
});
setExpandSplitsPref(!(splitsExpanded.state.mode === 'expand'));
}
}
@@ -116,20 +118,25 @@ export function AccountHeader({
/>
<View style={{ ...styles.pageContent, paddingBottom: 10, flexShrink: 0 }}>
<View style={{ marginTop: 2, alignItems: 'flex-start' }}>
<View
style={{ marginTop: 2, marginBottom: 10, alignItems: 'flex-start' }}
>
<View>
{editingName ? (
<InitialFocus>
<Input
defaultValue={accountName}
onEnter={e => onSaveName(e.target.value)}
onBlur={() => onExposeName(false)}
onBlur={e => onSaveName(e.target.value)}
onEscape={() => onExposeName(false)}
style={{
fontSize: 25,
fontWeight: 500,
marginTop: -5,
marginBottom: -2,
marginLeft: -5,
paddingTop: 2,
paddingBottom: 2,
}}
/>
</InitialFocus>
@@ -153,7 +160,7 @@ export function AccountHeader({
fontSize: 25,
fontWeight: 500,
marginRight: 5,
marginBottom: 5,
marginBottom: -1,
}}
data-testid="account-name"
>
@@ -185,7 +192,7 @@ export function AccountHeader({
</View>
) : (
<View
style={{ fontSize: 25, fontWeight: 500, marginBottom: 5 }}
style={{ fontSize: 25, fontWeight: 500, marginBottom: -1 }}
data-testid="account-name"
>
{account && account.closed
@@ -210,7 +217,11 @@ export function AccountHeader({
style={{ marginTop: 12 }}
>
{((account && !account.closed) || canSync) && (
<Button type="bare" onClick={canSync ? onSync : onImport}>
<Button
type="bare"
onClick={canSync ? onSync : onImport}
disabled={canSync && isServerOffline}
>
{canSync ? (
<>
<AnimatedRefresh
@@ -222,7 +233,7 @@ export function AccountHeader({
}
style={{ marginRight: 4 }}
/>{' '}
Sync
{isServerOffline ? 'Sync offline' : 'Sync'}
</>
) : (
<>

View File

@@ -19,8 +19,13 @@ import {
ungroupTransactions,
} from 'loot-core/src/shared/transactions';
import { useAccounts } from '../../hooks/useAccounts';
import { useCategories } from '../../hooks/useCategories';
import { useDateFormat } from '../../hooks/useDateFormat';
import { useLocalPref } from '../../hooks/useLocalPref';
import { useLocalPrefs } from '../../hooks/useLocalPrefs';
import { useNavigate } from '../../hooks/useNavigate';
import { usePayees } from '../../hooks/usePayees';
import { useSetThemeColor } from '../../hooks/useSetThemeColor';
import { theme, styles } from '../../style';
import { Button } from '../common/Button';
@@ -72,19 +77,27 @@ function PreviewTransactions({ children }) {
let paged;
export function Account(props) {
const accounts = useSelector(state => state.queries.accounts);
const accounts = useAccounts();
const payees = usePayees();
const navigate = useNavigate();
const [transactions, setTransactions] = useState([]);
const [searchText, setSearchText] = useState('');
const [currentQuery, setCurrentQuery] = useState();
const state = useSelector(state => ({
payees: state.queries.payees,
newTransactions: state.queries.newTransactions,
prefs: state.prefs.local,
dateFormat: state.prefs.local.dateFormat || 'MM/dd/yyyy',
}));
const newTransactions = useSelector(state => state.queries.newTransactions);
const prefs = useLocalPrefs();
const dateFormat = useDateFormat() || 'MM/dd/yyyy';
const [_numberFormat] = useLocalPref('numberFormat');
const numberFormat = _numberFormat || 'comma-dot';
const [hideFraction = false] = useLocalPref('hideFraction');
const state = {
payees,
newTransactions,
prefs,
dateFormat,
};
const dispatch = useDispatch();
const actionCreators = useMemo(
@@ -134,11 +147,6 @@ export function Account(props) {
}
});
if (accounts.length === 0) {
await actionCreators.getAccounts();
}
await actionCreators.initiallyLoadPayees();
await fetchTransactions();
actionCreators.markAccountRead(accountId);
@@ -216,8 +224,6 @@ export function Account(props) {
const balance = queries.accountBalance(account);
const balanceCleared = queries.accountBalanceCleared(account);
const balanceUncleared = queries.accountBalanceUncleared(account);
const numberFormat = state.prefs.numberFormat || 'comma-dot';
const hideFraction = state.prefs.hideFraction || false;
return (
<SchedulesProvider

View File

@@ -199,11 +199,11 @@ export function AccountDetails({
</View>
<PullToRefresh onRefresh={onRefresh}>
<TransactionList
account={account}
transactions={allTransactions}
categories={categories}
accounts={accounts}
payees={payees}
showCategory={!account.offbudget}
isNew={isNewTransaction}
onLoadMore={onLoadMore}
onSelect={onSelectTransaction}

View File

@@ -1,10 +1,12 @@
import React, { useEffect, useState } from 'react';
import { useSelector } from 'react-redux';
import React, { useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { replaceModal, syncAndDownload } from 'loot-core/src/client/actions';
import * as queries from 'loot-core/src/client/queries';
import { useActions } from '../../hooks/useActions';
import { useAccounts } from '../../hooks/useAccounts';
import { useCategories } from '../../hooks/useCategories';
import { useLocalPref } from '../../hooks/useLocalPref';
import { useNavigate } from '../../hooks/useNavigate';
import { useSetThemeColor } from '../../hooks/useSetThemeColor';
import { SvgAdd } from '../../icons/v1';
@@ -13,6 +15,7 @@ import { Button } from '../common/Button';
import { Text } from '../common/Text';
import { TextOneLine } from '../common/TextOneLine';
import { View } from '../common/View';
import { ROW_HEIGHT as MOBILE_NAV_HEIGHT } from '../mobile/MobileNavTabs';
import { Page } from '../Page';
import { PullToRefresh } from '../responsive/PullToRefresh';
import { CellValue } from '../spreadsheet/CellValue';
@@ -175,7 +178,11 @@ function AccountList({
</Button>
}
padding={0}
style={{ flex: 1, backgroundColor: theme.mobilePageBackground }}
style={{
flex: 1,
backgroundColor: theme.mobilePageBackground,
paddingBottom: MOBILE_NAV_HEIGHT,
}}
>
{accounts.length === 0 && <EmptyMessage />}
<PullToRefresh onRefresh={onSync}>
@@ -216,26 +223,19 @@ function AccountList({
}
export function Accounts() {
const accounts = useSelector(state => state.queries.accounts);
const dispatch = useDispatch();
const accounts = useAccounts();
const newTransactions = useSelector(state => state.queries.newTransactions);
const updatedAccounts = useSelector(state => state.queries.updatedAccounts);
const numberFormat = useSelector(
state => state.prefs.local.numberFormat || 'comma-dot',
);
const hideFraction = useSelector(
state => state.prefs.local.hideFraction || false,
);
const [_numberFormat] = useLocalPref('numberFormat');
const numberFormat = _numberFormat || 'comma-dot';
const [hideFraction = false] = useLocalPref('hideFraction');
const { list: categories } = useCategories();
const { getAccounts, replaceModal, syncAndDownload } = useActions();
const transactions = useState({});
const navigate = useNavigate();
useEffect(() => {
(async () => getAccounts())();
}, []);
const onSelectAccount = id => {
navigate(`/accounts/${id}`);
};
@@ -244,6 +244,14 @@ export function Accounts() {
navigate(`/transaction/${transaction}`);
};
const onAddAccount = () => {
dispatch(replaceModal('add-account'));
};
const onSync = () => {
dispatch(syncAndDownload());
};
useSetThemeColor(theme.mobileViewTheme);
return (
@@ -260,10 +268,10 @@ export function Accounts() {
getBalanceQuery={queries.accountBalance}
getOnBudgetBalance={queries.budgetedAccountBalance}
getOffBudgetBalance={queries.offbudgetAccountBalance}
onAddAccount={() => replaceModal('add-account')}
onAddAccount={onAddAccount}
onSelectAccount={onSelectAccount}
onSelectTransaction={onSelectTransaction}
onSync={syncAndDownload}
onSync={onSync}
/>
</View>
);

View File

@@ -3,14 +3,15 @@ import React, { Fragment, type ComponentProps, type ReactNode } from 'react';
import { css } from 'glamor';
import { useCachedAccounts } from 'loot-core/src/client/data-hooks/accounts';
import { type AccountEntity } from 'loot-core/src/types/models';
import { useAccounts } from '../../hooks/useAccounts';
import { useResponsive } from '../../ResponsiveProvider';
import { type CSSProperties, theme } from '../../style';
import { View } from '../common/View';
import { Autocomplete } from './Autocomplete';
import { ItemHeader, type ItemHeaderProps } from './ItemHeader';
function AccountList({
items,
@@ -71,9 +72,7 @@ function AccountList({
type AccountAutoCompleteProps = {
embedded?: boolean;
includeClosedAccounts: boolean;
renderAccountItemGroupHeader?: (
props: AccountItemGroupHeaderProps,
) => ReactNode;
renderAccountItemGroupHeader?: (props: ItemHeaderProps) => ReactNode;
renderAccountItem?: (props: AccountItemProps) => ReactNode;
closeOnBlur?: boolean;
} & ComponentProps<typeof Autocomplete>;
@@ -86,7 +85,7 @@ export function AccountAutocomplete({
closeOnBlur,
...props
}: AccountAutoCompleteProps) {
let accounts = useCachedAccounts() || [];
let accounts = useAccounts() || [];
//remove closed accounts if needed
//then sort by closed, then offbudget
@@ -124,35 +123,10 @@ export function AccountAutocomplete({
);
}
type AccountItemGroupHeaderProps = {
title: string;
style?: CSSProperties;
};
export function AccountItemGroupHeader({
title,
style,
...props
}: AccountItemGroupHeaderProps) {
return (
<div
style={{
color: theme.menuAutoCompleteTextHeader,
padding: '4px 9px',
...style,
}}
data-testid={`${title}-account-item-group`}
{...props}
>
{title}
</div>
);
}
function defaultRenderAccountItemGroupHeader(
props: AccountItemGroupHeaderProps,
props: ItemHeaderProps,
): ReactNode {
return <AccountItemGroupHeader {...props} />;
return <ItemHeader {...props} type="account" />;
}
type AccountItemProps = {

View File

@@ -359,6 +359,7 @@ function SingleAutocomplete<T extends Item>({
setValue(value);
setIsChanged(true);
setIsOpen(true);
}}
onStateChange={changes => {
if (

View File

@@ -21,6 +21,7 @@ import { Text } from '../common/Text';
import { View } from '../common/View';
import { Autocomplete, defaultFilterSuggestion } from './Autocomplete';
import { ItemHeader, type ItemHeaderProps } from './ItemHeader';
export type CategoryListProps = {
items: Array<CategoryEntity & { group?: CategoryGroupEntity }>;
@@ -33,9 +34,7 @@ export type CategoryListProps = {
renderSplitTransactionButton?: (
props: SplitTransactionButtonProps,
) => ReactNode;
renderCategoryItemGroupHeader?: (
props: CategoryItemGroupHeaderProps,
) => ReactNode;
renderCategoryItemGroupHeader?: (props: ItemHeaderProps) => ReactNode;
renderCategoryItem?: (props: CategoryItemProps) => ReactNode;
};
function CategoryList({
@@ -103,9 +102,7 @@ type CategoryAutocompleteProps = ComponentProps<typeof Autocomplete> & {
renderSplitTransactionButton?: (
props: SplitTransactionButtonProps,
) => ReactNode;
renderCategoryItemGroupHeader?: (
props: CategoryItemGroupHeaderProps,
) => ReactNode;
renderCategoryItemGroupHeader?: (props: ItemHeaderProps) => ReactNode;
renderCategoryItem?: (props: CategoryItemProps) => ReactNode;
};
@@ -177,35 +174,8 @@ export function CategoryAutocomplete({
);
}
type CategoryItemGroupHeaderProps = {
title: string;
style?: CSSProperties;
};
export function CategoryItemGroupHeader({
title,
style,
...props
}: CategoryItemGroupHeaderProps) {
return (
<div
style={{
color: theme.menuAutoCompleteTextHeader,
padding: '4px 9px',
...style,
}}
data-testid={`${title}-category-item-group`}
{...props}
>
{title}
</div>
);
}
function defaultRenderCategoryItemGroupHeader(
props: CategoryItemGroupHeaderProps,
) {
return <CategoryItemGroupHeader {...props} />;
function defaultRenderCategoryItemGroupHeader(props: ItemHeaderProps) {
return <ItemHeader {...props} type="category" />;
}
type SplitTransactionButtonProps = {

View File

@@ -0,0 +1,34 @@
import React, { type ComponentProps } from 'react';
import { useFilters } from 'loot-core/src/client/data-hooks/filters';
import { type TransactionFilterEntity } from 'loot-core/types/models/transaction-filter';
import { Autocomplete } from './Autocomplete';
import { FilterList } from './FilterList';
export function FilterAutocomplete({
embedded,
...props
}: {
embedded?: boolean;
} & ComponentProps<typeof Autocomplete<TransactionFilterEntity>>) {
const filters = useFilters() || [];
return (
<Autocomplete
strict={true}
highlightFirst={true}
embedded={embedded}
suggestions={filters}
renderItems={(items, getItemProps, highlightedIndex) => (
<FilterList
items={items}
getItemProps={getItemProps}
highlightedIndex={highlightedIndex}
embedded={embedded}
/>
)}
{...props}
/>
);
}

View File

@@ -1,26 +1,21 @@
import React, { type ComponentProps } from 'react';
import { useFilters } from 'loot-core/src/client/data-hooks/filters';
import { type TransactionFilterEntity } from 'loot-core/src/types/models';
import { theme } from '../../style';
import { theme } from '../../style/theme';
import { View } from '../common/View';
import { Autocomplete } from './Autocomplete';
import { ItemHeader } from './ItemHeader';
type FilterListProps<T> = {
items: T[];
getItemProps: (arg: { item: T }) => ComponentProps<typeof View>;
highlightedIndex: number;
embedded?: boolean;
};
function FilterList<T extends { id: string; name: string }>({
export function FilterList<T extends { id: string; name: string }>({
items,
getItemProps,
highlightedIndex,
embedded,
}: FilterListProps<T>) {
}: {
items: T[];
getItemProps: (arg: { item: T }) => ComponentProps<typeof View>;
highlightedIndex: number;
embedded?: boolean;
}) {
return (
<View>
<View
@@ -30,6 +25,7 @@ function FilterList<T extends { id: string; name: string }>({
...(!embedded && { maxHeight: 175 }),
}}
>
<ItemHeader title="Saved Filters" type="filter" />
{items.map((item, idx) => {
return [
<div
@@ -55,32 +51,3 @@ function FilterList<T extends { id: string; name: string }>({
</View>
);
}
type SavedFilterAutocompleteProps = {
embedded?: boolean;
} & ComponentProps<typeof Autocomplete<TransactionFilterEntity>>;
export function SavedFilterAutocomplete({
embedded,
...props
}: SavedFilterAutocompleteProps) {
const filters = useFilters() || [];
return (
<Autocomplete
strict={true}
highlightFirst={true}
embedded={embedded}
suggestions={filters}
renderItems={(items, getItemProps, highlightedIndex) => (
<FilterList
items={items}
getItemProps={getItemProps}
highlightedIndex={highlightedIndex}
embedded={embedded}
/>
)}
{...props}
/>
);
}

View File

@@ -0,0 +1,26 @@
import React from 'react';
import { theme } from '../../style/theme';
import { type CSSProperties } from '../../style/types';
export type ItemHeaderProps = {
title: string;
style?: CSSProperties;
type?: string;
};
export function ItemHeader({ title, style, type, ...props }: ItemHeaderProps) {
return (
<div
style={{
color: theme.menuAutoCompleteTextHeader,
padding: '4px 9px',
...style,
}}
data-testid={`${title}-${type}-item-group`}
{...props}
>
{title}
</div>
);
}

View File

@@ -13,14 +13,14 @@ import { useDispatch } from 'react-redux';
import { css } from 'glamor';
import { createPayee } from 'loot-core/src/client/actions/queries';
import { useCachedAccounts } from 'loot-core/src/client/data-hooks/accounts';
import { useCachedPayees } from 'loot-core/src/client/data-hooks/payees';
import { getActivePayees } from 'loot-core/src/client/reducers/queries';
import {
type AccountEntity,
type PayeeEntity,
} from 'loot-core/src/types/models';
import { useAccounts } from '../../hooks/useAccounts';
import { usePayees } from '../../hooks/usePayees';
import { SvgAdd } from '../../icons/v1';
import { useResponsive } from '../../ResponsiveProvider';
import { type CSSProperties, theme } from '../../style';
@@ -32,6 +32,7 @@ import {
defaultFilterSuggestion,
AutocompleteFooter,
} from './Autocomplete';
import { ItemHeader, type ItemHeaderProps } from './ItemHeader';
function getPayeeSuggestions(payees, focusTransferPayees, accounts) {
let activePayees = accounts ? getActivePayees(payees, accounts) : payees;
@@ -163,7 +164,7 @@ type PayeeAutocompleteProps = {
onSelect?: (value: string) => void;
onManagePayees: () => void;
renderCreatePayeeButton?: (props: CreatePayeeButtonProps) => ReactNode;
renderPayeeItemGroupHeader?: (props: PayeeItemGroupHeaderProps) => ReactNode;
renderPayeeItemGroupHeader?: (props: ItemHeaderProps) => ReactNode;
renderPayeeItem?: (props: PayeeItemProps) => ReactNode;
accounts?: AccountEntity[];
payees?: PayeeEntity[];
@@ -187,12 +188,12 @@ export function PayeeAutocomplete({
payees,
...props
}: PayeeAutocompleteProps) {
const cachedPayees = useCachedPayees();
const retrievedPayees = usePayees();
if (!payees) {
payees = cachedPayees;
payees = retrievedPayees;
}
const cachedAccounts = useCachedAccounts();
const cachedAccounts = useAccounts();
if (!accounts) {
accounts = cachedAccounts;
}
@@ -422,35 +423,8 @@ function defaultRenderCreatePayeeButton(
return <CreatePayeeButton {...props} />;
}
type PayeeItemGroupHeaderProps = {
title: string;
style?: CSSProperties;
};
export function PayeeItemGroupHeader({
title,
style,
...props
}: PayeeItemGroupHeaderProps) {
return (
<div
style={{
color: theme.menuAutoCompleteTextHeader,
padding: '4px 9px',
...style,
}}
data-testid={`${title}-payee-item-group`}
{...props}
>
{title}
</div>
);
}
function defaultRenderPayeeItemGroupHeader(
props: PayeeItemGroupHeaderProps,
): ReactNode {
return <PayeeItemGroupHeader {...props} />;
function defaultRenderPayeeItemGroupHeader(props: ItemHeaderProps): ReactNode {
return <ItemHeader {...props} type="payee" />;
}
type PayeeItemProps = {

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