Compare commits

...

189 Commits

Author SHA1 Message Date
Matiss Janis Aboltins
44375e72ad 🔖 (24.9.0) (#3348) 2024-09-03 18:02:45 +01:00
Michael Clark
6454c10e63 🐛 Fix tooltip when using touch devices (#3342)
* fix tooltip when using touch devices

* release notes
2024-09-02 17:23:22 +01:00
Matiss Janis Aboltins
2a9546ced1 🐛 fix reconciliation closing on enter click (#3338) 2024-09-01 20:08:19 +01:00
Matiss Janis Aboltins
8926ff69b1 🐛 fix long payee name overflow (#3340) 2024-09-01 18:26:57 +01:00
Matiss Janis Aboltins
340169bfb6 🐛 fix schedules modal closing when selecting link transactions (#3337) 2024-09-01 18:26:41 +01:00
Tim
3a905d3f9a [WIP] fix toggleSpentColumn being called on every render (#3333)
* fix toggleSpentColumn being called on every render

* release notes
2024-08-31 20:10:02 +01:00
Ryan Bianchi
7738ea0c00 Fix suggested payee issues #3317 #3316 (#3318) 2024-08-30 19:04:50 +01:00
Michael Clark
8e077e0282 :electron: Desktop app to work with self signed certificates (#3308)
* solves the problem but creates a vulnerability

* sake...

* working but need to specify rootca.pem

* works

* being flexible on the cert names, as long as its a crt or pem

* remove console logs

* initial setup for adding cert

* caps

* comments

* fix ts strict

* rewrote it

* release notes

* remove unneeded

* https no polyfill

* removing the cert reference if it is not found

* moving full stop
2024-08-28 21:39:14 +01:00
Matiss Janis Aboltins
ae608f0cb8 🐛 (dashboards) add back spending report if dashboards feature is not enabled (#3323) 2024-08-28 21:02:47 +01:00
Matiss Janis Aboltins
f1c0d0b8a6 🐛 fix 's' hotkey not working in transaction table (#3324) 2024-08-28 20:21:24 +01:00
Matt Fiddaman
d9adb750d4 🧹 optimise GoCardless bank sync to use fewer api calls (#3279)
* optimise

* lint

* release note

* psybers feedback
2024-08-26 11:03:57 +01:00
lelemm
1750cd9081 Filter fix when alternating all <-> any (#3278) 2024-08-24 09:49:36 +01:00
lelemm
7769d0303e "has tags" filter (#3290)
* new tag filter

* fixes

* release notes

* fixes for the rules modal

* more fixes

* linter

* visual regression fixes

* review suggestions

* missing this change
2024-08-23 08:21:09 -07:00
Michael Clark
9108b63355 :electron: Notarize the mac desktop app (#3300)
* adding notarization stuff back in

* win csc settings so win build doesnt try to sign with mac stuff

* windows doesnt need to know about mac build

* teamid env var instead of config val as per docs

* for testing purposes only

* probably wont work

* yet, didnt work

* try this

* update notarize

* removing test code

* add release notes
2024-08-22 17:43:51 +01:00
Robert Dyer
1b70e59bde Apply regular expression conditions to imported transactions. (#3287) 2024-08-22 17:25:16 +01:00
Robert Dyer
b48d256ec4 Translation: desktop-client/components/sidebar (#3302) 2024-08-22 17:23:23 +01:00
Robert Dyer
9c0e6a307b Translation: desktop-client/components/reports/graphs (#3299) 2024-08-21 20:41:25 +01:00
Michael Clark
3e5ce72e27 :electron: Packaging different architectures and installers for windows (#3185)
* packaging different architectures for windows

* appx for the windows store app hosting

* remove unneeded applicationid

* adding windows store assets

* adding images

* appx added to artifacts

* add appx to release

* remove override build params

* being specific about the mac build - default is dmg

* are these all needed.. Seems so

* removing appx from the release notes as its only for the windows store

* moving appxs to a different artifact for smaller download

* Update electron-pr.yml

* update version

* update release process to remove actual-windows.exe because it can possible be the wrong arch
2024-08-20 12:01:36 -07:00
Matiss Janis Aboltins
b347f03fbb (dashboards) ability to rename all widgets (#3284) 2024-08-20 17:25:18 +01:00
Matiss Janis Aboltins
f3660c166f ⬆️ upgrade typescript, eslint, prettier (#3289) 2024-08-20 17:18:54 +01:00
Robert Dyer
aaf96bbc2c Better debug logs for bank sync errors. (#3296)
* Better debug logs for bank sync errors.

* add release note

* force CI update
2024-08-20 07:44:35 -07:00
Crazypkr1099
6d84b0e371 Fix wrong month on spending card (#3295)
* Create 2881.md

* Fix spending card reading wrong month

* Revert "Create 2881.md"

This reverts commit de26f690e8.

* Create 3295.md
2024-08-20 07:38:01 -07:00
Matiss Janis Aboltins
db4b504e53 typescript: migrate cards from jsx to tsx (#3285) 2024-08-19 18:07:20 +01:00
Matiss Janis Aboltins
d6afc85a8c adding feedback links besides the feature flags (#3283) 2024-08-18 11:17:42 +01:00
Julian Dominguez-Schatz
ee21155d1a Display splits in previews (#2923)
* Display splits in mobile previews

* Display splits in desktop previews

* UI fix: hide checkboxes on preview child transactions

* UI fix: show notes on preview transactions

* Add release notes

* Update vrt for mobile padding fix

* Fix status display

* Collapse split previews by default

* PR feedback: fix issues with split payee

* Update new VRT with spacing from this PR
2024-08-17 16:30:27 -04:00
Robert Dyer
65a7c58441 Translation: desktop-client/components/budget/report (#3280) 2024-08-17 18:33:03 +01:00
Matiss Janis Aboltins
51ec600de2 customizable reports homepage - drag-able and resizable widgets (#3231) 2024-08-17 17:53:35 +01:00
Robert Dyer
af5fd5b3ef Translation: desktop-client/components/autocomplete (#3275) 2024-08-17 11:44:37 +01:00
wdpk
eccdc52342 feat: [localization] Update README to mention weblate (#3271)
* weblate mention in readme

part of terms for libre plan

* Create 3271.md

* add link to internationalization section of documentation
2024-08-16 19:47:54 +01:00
Robert Dyer
4c192d7e1e Translation: desktop-client/components/filters (#3270)
* Translation: desktop-client/components/filters

* add release note

* fix linter

* add missing t definition
2024-08-16 13:35:57 +01:00
Matt Fiddaman
f715ceafc9 hide target category from cover overspending list (#3115)
* hide target category from cover overspending list

* release note

* suggestions from joel-jeremy

* useMemo and better types

* add change to mobile

* fix build

* fix mobile
2024-08-15 14:40:20 +01:00
Robert Dyer
af73dcd722 Add rule actions to prepend/append to transaction notes. (#3215)
* Add rule action to append to transaction notes.

* add release note

* support prepending

* fix linter

* update release note

* fix typecheck error

* update VRT test code

* revising VRT code

* select by row

* fix missing delete button

* fix VRT tests

* fix linter

* empty commit for CI

* avoid 'undefined' appearing in notes

* fix linter
2024-08-15 14:36:44 +01:00
Robert Dyer
5e3485a8e2 Automatically focus inputs, or the primary button, in modals. (#2974)
* Automatically focus inputs, or the primary button, in modals.

* Set focus on more modals.

* focus mobile transaction edits

* add release note

* fix linter

* fix linter
2024-08-15 14:36:09 +01:00
Julian Dominguez-Schatz
1458dbc307 Support type-checking on spreadsheet fields (part 3—last part) (#3097)
* Add report budget typing

* Remove default `any` types

* Add release notes

* Fix lint

* Attempt to fix unrelated test error

* fix: changed type name

* More correct types

* fix types
2024-08-15 08:32:41 -04:00
Stefan Wilkes
9ac77af077 Added configuration to CSV importer that allows to skip lines (#3234)
* Added configuration to CSV importer that allows to skip lines

* Fixed several type / parser check when import is executed with a different CSV structure on accounts with transactions

* Fixed linter warning

* Reverted changes on sync.ts as initial error is not occuring anymore.
This will also fix the tests again

---------

Co-authored-by: youngcw <calebyoung94@gmail.com>
2024-08-14 22:16:33 +01:00
Neil
3e07d18acd Update ModeButton.tsx (#3257)
* Update ModeButton.tsx

* Update upcoming-release-notes

* Update 3257.md

* Update ModeButton.tsx
2024-08-14 21:45:59 +01:00
Matiss Janis Aboltins
fa6cc26416 ♻️ cleanup iterableTopologicalSort feature flag (#3262) 2024-08-14 19:42:04 +01:00
Robert Dyer
a1ca871b24 Allow escaping tags with double ##. (#3246)
* Allow escaping tags with double ##.

* add release note

* convert '#' to '##' in bank sync-generated notes
2024-08-14 18:40:06 +01:00
Julian Dominguez-Schatz
d9066a49c4 Remove some any types from the API (#3238)
* Remove some `any` types from the API

* Add release notes

* No backwards-incompatible changes

* Update tests to reflect API changes

* PR feedback: standardize on id for deletes

* PR feedback: restore partial updates
2024-08-14 13:28:09 -04:00
Julian Dominguez-Schatz
63ad6dadf2 fix(mobile): show category for on-to-off-budget transfers (#3258)
* fix(mobile): show category for on-to-off-budget transfers

* Add release notes
2024-08-14 13:27:34 -04:00
Michael Clark
89b096aa65 :electron: Fix export on mac (#3250)
* fix export on mac

* add release notes
2024-08-14 13:38:57 +01:00
Eirik Reksten
ee0156d35d 3211: Fix broken transaction import on new accounts (#3251)
* 3211: Added check for string before matching any object.

* Release notes for 3251.

* Fix release notes authors.
2024-08-14 10:13:07 +01:00
Alex Walker
9c17d55e0d Extract out note template logic from goaltemplates.ts, refactor and add unit tests (#3221)
Signed-off-by: Alex Walker <walker.c.alex@gmail.com>
2024-08-13 06:15:51 -07:00
Julian Dominguez-Schatz
411a6791b2 Fix transfer category in temporary transactions (#3239)
* Fix transfer category in temporary transactions

* Add visual regression tests to prevent this issue in the future

* Add release notes
2024-08-12 21:33:21 -04:00
Michael Clark
6f3af7b609 :electron: Fix "Unknown problem opening <local actual file>" on Electron (#3220)
* add electron logging to main browser process console

* add logging

* removing old way

* release notes

* adding some logs in to test mac build

* repent satan

* i caste yeee oooott sinner

* derp

* hmmm<

* forcing nodegyprebuild

* not like this.... Not like this... 😢

* hmm

* dunno

* will it recognise it if i link it manually.. 👀

* give up

* rebuild

* merge asars fasle

* update package

* manually do it ffs work damnit

* remove the cmd

* dont rebuild cause i build it manually

* dafuq is this, two bettersqlite modules installed huhhhhh

* test

* does this work?

* bloody hell

* couple more logs

* test this out

* arch in name

* adding the rebuild step back into first build

* try rebuild before pack - so we know what arch we need

* having a laugh

* tidying up

* release notes

* move package up a bit

* exit process if no electron verison
2024-08-12 22:50:48 +01:00
Joel Jeremy Marquez
43ff1c033e [React Aria Button] Reports page buttons (#3159)
* More components to use react aria Button

* vrt

* Fix account menu test

* vrt

* vrt

* VRT

* VRT

* React Aria Button - Reports page

* Release notes

* Fix typecheck error

* Fix typecheck error + VRT (Create new custom report button got slightly bigger)

* Fix payee icon color
2024-08-12 13:53:58 -07:00
Joel Jeremy Marquez
09c44d351d [Mobile] Long press transaction to reveal floating action bar with bulk actions (#2892)
* Mobile transaction long press

* Floating action bar

* Styling

* Add functionality

* Fix typecheck error

* Release notes

* Undo notifications

* Fix schedules and update transaction delete confirmation message

* Use react-aria useLongPress

* Bulk edit amount display

* Themes

* Do not clear on batch update

* useUndo hook

* Fix typecheck error

* Update useUndo

* Fix typecheck error

* Handle batch deleted transactions

* useMemo

* Make onClearSelectedTransactions mandatory

* Extract FloatingActionBar to a separate component

* Require onAddSelectedTransaction and onClearSelectedTransactions if there are  any selectedTransactions

* Fix schedule link

* Undo notification timeout

* Use useSelected

* Fix typecheck error

* Category transactions batch updates

* Remove undo notification title

* Fix types

* Fix notes undo notification

* Move SelectedProvider to TransactionListWithBalances

* Remove NewPayeeEntity

* Disable support for amount batch edit for now

* Fix lint error

* Notification inset + reuse useTransactionBatchActions

* Always show notification close button regardless if sticky or not

* Allow clicking action bar when notifications are present

* Fix typecheck error

* Remove inset on addNotification calls

* Use PressResponder

* Fix mobile transaction border

* VRT

* VRT

* VRT

* VRT
2024-08-12 13:53:14 -07:00
Julian Wachholz
a22160579d fix: i18n: Use 'en' as default language (#3242)
* fix: i18n: Use 'en' as default language

Using 'cimode' as default language code will return the
translation keys verbatim without doing interpolation.
This is unwanted unless working directly on localization.

Fixes #3240

* Add release note
2024-08-12 10:54:48 +01:00
Matiss Janis Aboltins
81df2ce7fd ♻️ (prefs) initial type implementation for synced/local/metadata prefs (#3236) 2024-08-12 08:12:14 +01:00
Jordan Lees
119d0b339d Fix off-by-one error when placing category into 2nd-to-last place (#3241)
* Fix off-by-one error when placing category into 2nd-to-last place

Signed-off-by: JL102 <jordanlees@mailbox.org>

* Add changelog file

Signed-off-by: JL102 <jordanlees@mailbox.org>

---------

Signed-off-by: JL102 <jordanlees@mailbox.org>
2024-08-11 22:49:17 -07:00
Julian Dominguez-Schatz
d1362c3d74 Correct type for to-budget field (#3237)
* Correct type for `to-budget` field

* Add release notes

* Don't enable `strictNullChecks` for now
2024-08-11 16:52:35 -04:00
Julian Wachholz
8142dd1ec9 feat: introduce i18n framework (#3036)
* feat: introduce i18n framework

* Incorporate review feedback

* Patch demo

* remove unnecessary arguments

* Consistently use t() function

* Fix typing issue

* Fix e2e tests

---------

Co-authored-by: Matiss Janis Aboltins <matiss@mja.lv>
2024-08-11 15:37:33 -04:00
pmoon
2afd6967b4 Fix reconcileTransactions Invokation To Use strictIdChecking (#3232) 2024-08-11 18:37:20 +01:00
Robert Dyer
fe922ec22e Highlight current month in budgets. (#3111) 2024-08-10 20:00:46 +01:00
pmoon
30a70f5627 fix(#2562): Prevent transaction deduplication for imported transactions (#2991)
* fix(#2562): Prevent transaction deduplication for imported transactions (#2770)

* fix(#2562): Prevent transaction deduplication for imported transactions

* chore(): eslint fixes

* chore(): Add release note file

* fix(#2562): Allow transaction deduplication if transaction being imported is null

* chore: Rename release note, add strazto as author

* test(loot-core): Add test case for new logic

* docs(release-notes.loot-core): Add pmoon00 as author

* test(loot-core): Update test case to not be affected by unrelated bug

* test(loot-core): fix linter

---------

Co-authored-by: Mohamed El Mahdali <mohamed.elmahdali.developer@gmail.com>
Co-authored-by: Matiss Janis Aboltins <matiss@mja.lv>

* Add Handling For goCardless Fuzzy Search

* Rename Release Note File

* Rename Release Notes File

* Fix UseFuzzySearchV2 After Merge Conflict

* Update Fuzzy Search Query To Include New Columns

* Update useFuzzyMatchV2 Variable To useStrictIdChecking

* Update useStrictIdChecking To Only Be Used If It's Not Syncing From External Sources

---------

Co-authored-by: Matthew Strasiotto <39424834+strazto@users.noreply.github.com>
Co-authored-by: Mohamed El Mahdali <mohamed.elmahdali.developer@gmail.com>
Co-authored-by: Matiss Janis Aboltins <matiss@mja.lv>
2024-08-09 01:44:37 +01:00
Robert Dyer
65c5f2c559 Filter by account when linking schedules (#3188)
* Add "S" hotkey for viewing/linking schedules.

* Default to filtering by account name when linking a schedule to a transaction.

* Work on 'all accounts' page.

* Update help modal

* add release note
2024-08-08 13:16:19 -07:00
Michael Clark
1abca7619d :electron: Making Electron server logs visible in devtools (#3219)
* add electron logging to main browser process console

* add logging

* removing old way

* release notes
2024-08-08 20:45:57 +01:00
Michael Clark
6a85f84565 :electron: Fix gocardless "Linking account" integration (#3212)
* fix gocardless call - forked process didnt know its origin

* add release notes

* mamma mia

* remove old change
2024-08-07 20:33:23 +01:00
Julian Dominguez-Schatz
65329398fd Support type-checking on spreadsheet fields (part 2) (#3095)
* Add rollover budget typing

* Fix lint

* Add release notes

* Fix strict typechecking
2024-08-07 07:55:54 -04:00
Matiss Janis Aboltins
a2e434a1fb ♻️ (reports) improve useReports data fetching hook to return loading state (#3198) 2024-08-06 20:47:43 +01:00
Neil
d2bbe6a98e Spending Report: Mobile UI (#3209)
* SpendingMobile UI

* notes
2024-08-06 20:36:45 +01:00
Matiss Janis Aboltins
2c1967d788 ♻️ (reports) add 'showTooltip' prop to various graphs (#3200) 2024-08-06 20:31:35 +01:00
Spencer Murray
798aee78c3 Identify Payee and Notes fields by name if they exist in CSV import (#3203)
* Use csv fields with name payee/notes as defaults for Payee/Notes in getInitialMappings

* Add release notes

* Run lint

* Use or instead of nullish coalesce to match other fields
2024-08-06 15:41:48 +01:00
Matt Fiddaman
2807c98c2c fix "unkown" typo in error message (#3205) 2024-08-06 10:09:21 +01:00
Joel Jeremy Marquez
5e9b976676 [React Aria Button] - Migrate desktop and mobile budget page buttons (#3156)
* More components to use react aria Button

* Release notes

* vrt

* Fix typecheck error

* Fix account menu test

* Fix typecheck error

* Fix typecheck error

* Remove unnecessary aria-labels

* Fix payee icons and category notes

* vrt

* vrt

* Fix notes button

* Fix typecheck error

* Fix lint error

* VRT

* Remove default :focus on Button2

* Add Button2 defaultClassName

* Update className

* Fix typecheck error

* Cleanup

* VRT

* Fix typecheck error

* Fix typecheck error

* Fix typecheck error

* react-aria Button for budget pages

* Release notes
2024-08-05 20:45:30 -06:00
Joel Jeremy Marquez
44ce976ffa [React Aria Button] - Migrate sidebar, notifications, transactions, recurring schedule picker buttons (#2984)
* More components to use react aria Button

* Release notes

* vrt

* Fix typecheck error

* Fix account menu test

* Fix typecheck error

* Fix typecheck error

* Remove unnecessary aria-labels

* Fix payee icons and category notes

* vrt

* vrt

* Fix notes button

* Fix typecheck error

* Fix lint error

* VRT

* Remove default :focus on Button2

* Add Button2 defaultClassName

* Update className

* Fix typecheck error

* Cleanup

* VRT

* Fix typecheck error

* Fix typecheck error

* Fix typecheck error
2024-08-05 20:44:51 -06:00
Robert Dyer
5ba80fcbdc Fix mobile status indicators cutting off. (#3206) 2024-08-06 02:10:10 +01:00
Matt Fiddaman
7b77f60458 Adds a tooltip to the transaction tables to show the imported payee (#3018) 2024-08-05 20:24:49 +01:00
Alex Walker
81f59ff776 Add unit tests for goal template types (#3183)
* Add unit tests for each goal type

* Add test for goals schedule template

* Update release notes
2024-08-05 11:58:49 +01:00
Matiss Janis Aboltins
63d9547e7c ♻️ (reports) unify selectedCategories and conditions (#3178) 2024-08-04 20:09:54 +01:00
Neil
d18fd36ae1 Spending Report: UI Adjustments (#3166)
* adjust UI

* notes

* revert cashflow change

* merge fixes

* remove bold, adjust style code
2024-08-04 20:06:48 +01:00
rodriguestiago0
2b1ba88983 Add Reset Hold and Hold For Next Month api (#3140) 2024-08-04 19:56:11 +01:00
youngcw
8be867f884 Fix a few number parsing issues (#3044)
* revert built in number parse

* note

* tests

* remove trivial test

* more tests

---------

Co-authored-by: Julian Dominguez-Schatz <julian.dominguezschatz@gmail.com>
2024-08-04 10:21:53 -07:00
Tim Quelch
cafe480ba4 Update monthly spending report option name (#3181)
* Update monthly spending report name

* Add change note

---------

Co-authored-by: DJ Mountney <david@twkie.net>
2024-08-03 11:33:22 -07:00
alcroito
6472c70960 Shorten hidden category names imported from YNAB4 (#3122)
Imported hidden categories had the parent category entity id (UUID)
appended to the end of the name, making the category name quite long
and somewhat hard to read.

Remove the entity id from the end, and replace the apostrophe that
divides the master category from the sub category with a forward slash.

This shortens the name, ensuring a better chance that the full
category name fits into the default table cell width.

Co-authored-by: DJ Mountney <david@twkie.net>
2024-08-03 11:18:33 -07:00
Sebastian Civarolo
56c5a533e7 Fix false positives for duplicate filters error when saving a new filter (#2970)
* update conditionExists function to compare filter options

* rename method

* array.some instead of for loop

* release note

* refactor the condition search
2024-08-03 11:09:25 -07:00
Matiss Janis Aboltins
7e3ff1ad03 (vrt) improving stability - rules test (#3186) 2024-08-03 19:06:48 +01:00
Julian Dominguez-Schatz
e0d7233b40 Support type-checking on spreadsheet fields (part 1) (#3093)
* Correct table usage of `onBlur`

* Add basic spreadsheet typing structure

* Move to different module

* Add account typing

* Add release notes

* Fix lint

* Remove unneeded diff

* PR feedback
2024-08-03 11:06:47 -04:00
Julian Dominguez-Schatz
1b4c4319e1 Disable typography linter in tests (#3114)
* Disable typography linter in tests

* Add release notes

* Remove unused type ignore
2024-08-03 11:05:30 -04:00
Matiss Janis Aboltins
14f29941b0 ♻️ (typescript) make category and rule types stricter (#3180) 2024-08-03 15:24:01 +01:00
Matiss Janis Aboltins
4389329bfa 🔖 (24.8.0) (#3179) 2024-08-03 14:30:33 +01:00
Matiss Janis Aboltins
3a38c32b4c 🐛 (modals) allow content to be vertically scrollable (#3161)
* 🐛 (modals) allow content to be vertically scrollable

Closes #3079

* Feedback: overfloww auto

* fix modal scrollbar style

* dont need to spread

* lint 😞

* Update 3161.md

* adding width 100% to inputwithcontent

---------

Co-authored-by: Michael Clark <5285928+MikesGlitch@users.noreply.github.com>
2024-08-02 19:16:32 +01:00
Yusef Ouda
c3c6acd37c Fix budget type toggle not working the first time (#3169)
* use default 'rollover' for budgetType localPref

* release notes

---------

Co-authored-by: Yusef Ouda <5180063+YusefOuda@users.noreply.github.com>
2024-08-01 17:59:04 +01:00
wnklmnn
8de0f6a72a Set minWidth on <input /> to allow it to shrink. (#3168)
* Set minWidth on <input /> to allow it to shrink.
Fixes #3126

* fix formatting

* add release-notes markdown
2024-07-31 18:31:49 -04:00
Neil
2799dbee3e Spending Report: Add Last Month (#3132)
* AddLastMonth

* notes

* Add data for last month a year ago

* Adjustments for save button

* spending card fix

* notes

* fix typecheck

* fix averages
2024-07-31 21:35:23 +01:00
Matt Fiddaman
58eeee825e add goal tooltip to balance in budget table (#3123)
* add goal tooltip to balance in budget table

* release note

* fix long goals over multiple months
2024-07-29 23:49:25 +01:00
Matiss Janis Aboltins
6653dca776 🐛 (modals) remove focus outline (#3160)
Closes #3141
2024-07-29 22:00:27 +01:00
Joel Jeremy Marquez
77ba15f54c Fix local playwright reporter config (#3158)
* Fix local playwright reporter config

* Release notes
2024-07-29 12:26:51 -07:00
Matiss Janis Aboltins
653a0ab104 🐛 (rules) fix error handling (#3149) 2024-07-29 18:35:49 +01:00
Joel Jeremy Marquez
2c26fa51a3 Add Modal2 backdrop (#3147)
* Add Modal2 backdrop

* Release notes

* VRT

* Use HTML reported when running tests locally
2024-07-29 00:41:44 -07:00
Michael Clark
dff9911a15 [Fix] "Enter" key should save on "Cover Overspending" popup (#3153)
* allow autocomplete events to fire when dropdown is closed

* release notes

* simpler solution

* release notes
2024-07-28 21:47:13 +01:00
Michael Clark
3d5818f017 :electron: Fix electron "Data Dir" picker on Settings page (#3133)
* fix electron file location picker

* add release notes

* update message and style
2024-07-28 21:32:20 +01:00
Matiss Janis Aboltins
efd294dcef 🐛 fix plain-text link in simplefin error (#3151) 2024-07-28 14:57:24 +01:00
Matiss Janis Aboltins
0eb62a09bc 🔧 improve unit test stability while using uuid (#3144) 2024-07-27 21:24:19 +01:00
Joel Jeremy Marquez
73d52fa0d0 Fix Button2 isDisabled prop (#3146)
* Fix Button2 isDisabled prop

* Release notes

* Update 3146.md
2024-07-27 13:06:15 -07:00
Matiss Janis Aboltins
5b0cc63f73 🐛 add missing underline to links (#3143)
* 🐛 add missing underline to links

* Update accounts screenshots
2024-07-27 16:44:14 +01:00
Matiss Janis Aboltins
26a591f07f 🐛 add missing hover cursor to new button component (#3142) 2024-07-27 16:41:59 +01:00
Robert Dyer
fe8851c797 Fix running balances thick header. (#3082)
* Update running balances width to display large numbers.

* add release note

* update width

* update width

* Fix running balances thick header.

* add release note

* fix alignment

* fix lint

* dont make header clickable

* refactor so HeaderCell can be unclickable
2024-07-26 12:07:10 -07:00
youngcw
511f677ae4 [Goals]: Add a long term goal template (#3012)
* update the parser

* can set goal value, but there are errors and it still needs to look for a balance and not month amount

* fix apply budget

* un change

* note

* lint

* working processing, need to set colors based on month somehow

* add long goal option to the gui and db

* note, lint

* fix cleanup

* fix

* make mobile work, lint

* fix bindings

* more proper

* lint

* fix single category run

* don't unset goal values if they don't have a template too

* lint

* more lint

* fix check when no template exists

* rearrange to get around the issue of inconsistent colors

* lint

* typecheck

* add field to aql schema

* fixes

* cleanup

* migration date
2024-07-26 09:04:44 -07:00
Joel Jeremy Marquez
1cef0d11ee Fix menus autoclosed when clicked element on top of the menu item (#3131)
* Fix menus autoclosed when clicked element on top of the menu item

* Release notes
2024-07-25 13:50:23 -07:00
Neil
536cabb75b Spending Report: card fix (#3135)
* spending card fix

* notes
2024-07-25 18:07:43 +01:00
Neil
cceda03905 Spending Report: add save button (#3112)
* add save button

* notes

* rerun checks

* add time

* tooltips

* change entity to string

* lint fix

* adjust defaults

* lint
2024-07-25 09:35:44 +01:00
Neil
982f555a21 Custom Reports: updateReport db Schema fix (#3127)
* updateReport db Schema fix

* notes

* error
2024-07-24 19:20:41 +01:00
Fran González
fe70ecb635 Fixes #3089: Dismiss pop-over on budget action (#3092)
* Fix no dismissal on budget action

* Add release notes file

* Correct tag

* Remove unnecessary callback

* Linting

* Apply same strategy on reportcomponents

* Rename to onMenuAction
2024-07-23 10:38:30 -07:00
Neil
5c0bee6031 Custom Reports: PlusOne (#3117)
* PlusOne

* notes

* add daterange filters
2024-07-22 19:02:21 +01:00
Bruno Ribeiro
4439bb6abe Enhance Autocomplete sorting, Payees tab filter, and Schedules tab filter to ignore characters with accents / diacritics (#3045)
* Autocomplete sort now ignores diacritics

* Payees tab now takes into account diacritics

* Category Autocomplete now ignores diacritics

* Schedules filter now ignore diacritics

* Added release note

* Fixed type error

* Added normalisation to manage rules filter

* Added normalisation to $like operator

* Added normalisation function to loot-core

* Fixed type error & added normalisation to notlike

* Fixed unit tests

* Changed normalise to use loot-core/shared on desktop

* Linting fix
2024-07-22 16:13:25 +01:00
Reece
b432204b4b modify fly.io url to point to correct documentation (#3113)
* modify fly.io url to point to correct documentation

* Create 3113.md
2024-07-21 15:27:13 -04:00
Matiss Janis Aboltins
9a85a72089 🔧 ping WIP PRs that have been inactive for a week (#3107)
* 🔧 ping WIP PRs that have been inactive for a week

* Bump the stale action version
2024-07-20 21:17:00 +01:00
Julian Dominguez-Schatz
a970a78932 Include more information in payee of split parent (#3049)
* Use dicts to look up common information

* Show abbreviated payees in split payee section

* Update vrt

* fix: missing transfer icon

* Add release notes

* fix: update vrt again

* bugfix: failing edge cases

* fix: stale test

* fix: stale test, p2 (+ vrt)
2024-07-20 11:43:24 -07:00
Joel Jeremy Marquez
ed65805d53 Port finance modals to use new Modal component based on React Aria Modal (#2946)
* React Aria Modal POC

* Fix imports

* Use composition

* Fix typecheck and lint errors

* VRT

* Fix schedule details modal header

* Fix typecheck error + VRT

* Fix typecheck error

* Release notes

* Update release note

* Fix props

* Update modal props

* useModalState hook

* VRT

* VRT

* Fix typecheck error

* Fix modal close

* VRT

* Fix test

* Fix typecheck error
2024-07-20 09:03:58 -07:00
Matiss Janis Aboltins
88ae7e9375 📝 update some information in the README (#3106) 2024-07-20 17:03:25 +01:00
Matiss Janis Aboltins
0135a4d1b9 🔥 (preferences) remove unused user prefs (#3104) 2024-07-20 09:53:54 +01:00
Matiss Janis Aboltins
4af2c4f214 ⬆️ upgrade yarn to 4.3.1 (#3105) 2024-07-20 09:53:42 +01:00
Ryan Bianchi
89a8f102dc Recently used and favorite payees (#2814)
* add idea of common payee, a top 10 frequently used payee

* add button in payee to mark as favorite

* cleanup

* minor fixes

* add release notes and make favorite optional

* fix TransactionsTable test

* lint and release notes

* rename section, resort list to ensure both are sorted

* don't show common, move bookmarked to menu

* add a limit on adding common payees

* linting

* reduce to 5 commonly used payees by default

* linting

* more linting

* update migrate timestamp

* more linting

* fix api name, bump migrate timestamp

* Add star to payee dropdown and rename section to 'Suggested Payees'

---------

Co-authored-by: youngcw <calebyoung94@gmail.com>
2024-07-19 19:44:29 -06:00
Sreetam Das
d032fce7ea fix: number format config not being respected for graphs (#2832)
* Add computed padding for handling clipped Net worth amounts

* Add comment, early handle 5 character case

* Add release note

* Use currency decimal preference for Net Worth graph Y-axis ticks

* use number format preference for `BarLineGraph` ticks

* use number format preference for checking `NetWorthGraph` tick overflow

* prevent `numberFormatConfig` overwrite

`getNumberFormat` uses `numberFormatConfig` as the default argument;
passing just `{ hideFraction: true }` caused `numberFormatConfig.format`
to be ignored

* add release note

* use `amountToCurrencyNoDecimal` instead for `{BarLine,NetWorth}Graph` ticks
2024-07-18 13:02:01 -06:00
Joel Jeremy Marquez
2fdc7fef32 Add callouts to starting fresh and migration documentations in README (#3101)
* Add callouts to starting fresh and migration documentations

* README

* Update README
2024-07-18 09:24:21 -07:00
Michael Clark
1e41d695c5 :electron: Added keyboard shortcuts reference to Help menu (#3100)
* adding keyboard shortcuts reference to help menu

* add release notes
2024-07-18 09:13:50 -06:00
wdpk
12f91f7d86 update split transaction handling for csv export (#2973)
* Split transaction handling for csv export (#2973)

* refactor, zero-amt parents and add split_amount

change how child/parents are counted to remove dependence on `sort_order` being negative. for export, give parent transactions a 0 in the `amount` column, but add a new column with their `split_amount` for other programs to possibly import

---------

Co-authored-by: Zeus\Herb <herb@win.dows>
Co-authored-by: DJ Mountney <david.mountney@twkie.net>
2024-07-18 07:57:36 -07:00
Michael Clark
f75d0f8099 :electron: security.js and preload.js ➡️ .ts (#3066) 2024-07-17 22:31:09 +01:00
Robert Dyer
07bbe00059 Add additional hotkeys (#3061)
* Add account page hotkeys

* add release note

* fix linter

* change shortcut

* change hotkey

* fix lint

* add budget shortcuts

* update help page

* fix linter

* add privacy filter hotkey p

* update help modal

* fix deps

* slash the zero

* bound the month picker

* change privacy shortcut to ctrl+p

* remap keys to ctrl

* add select all hotkey

* fix linter

* change add hotkey to T

* update help modal

* resize help modal

* fix linter

* shrink modal size more

* change budget reset behavior

* change privacy to shift+ctrl+p

* move shift to front
2024-07-17 14:41:50 -06:00
Simon Schmidt
be0d363576 CAMT.053: Handle missing ValDt on entries with Sts Book (#3086)
* Replicate seen-in-the-wild camt.053 example

* Handle missing ValDt in camt

* Release notes for #3086
2024-07-17 14:41:06 -06:00
Joel Jeremy Marquez
c2e648c9d5 React Aria Button on Modals (#2918)
* React Aria Button on modals

* Release notes

* Remove tabIndex

* Remove aria-label
2024-07-17 13:38:41 -07:00
Joel Jeremy Marquez
33049a77e7 Fix rules not being applied in mobile transaction entry (#3073)
* Fix rules in mobile transaction entry

* Release notes

* Remove alert
2024-07-17 13:38:19 -07:00
Joel Jeremy Marquez
89241623f3 React Aria Button on Management App (#2916)
* React Aria Button on management app

* Release notes

* Update release notes

* Fix typecheck error

* Remove aria labels

* Apply suggestions from code review

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

* Remove aria-labels

* Remove aria-label

---------

Co-authored-by: Matiss Janis Aboltins <matiss@mja.lv>
2024-07-17 12:20:12 -07:00
Robert Dyer
8434e8f5ce Fix "?" crashing on budget selection page. (#3084)
* Fix ? crashing on load budget page.

* add release note
2024-07-17 11:48:28 -06:00
Robert Dyer
9b99debacc Update running balances width to display large numbers. (#3080)
* Update running balances width to display large numbers.

* add release note

* update width

* update width
2024-07-17 10:20:22 -06:00
Austin Pearce
a23ec33591 Add autocapitalize (#3056) 2024-07-16 17:57:01 -07:00
Matiss Janis Aboltins
aaea04fc00 🔧 add lint-staged and husky to auto-patch formatting issues (#3058) 2024-07-16 19:10:57 +01:00
Austin Pearce
b4f0087eef increase mobile header label font weight (#3062) 2024-07-16 00:07:21 -07:00
Matiss Janis Aboltins
f81c452ba5 ♻️ (tooltip) refactoring to react-aria (vol.10) (#2828) 2024-07-15 18:18:40 +01:00
Robert Dyer
7072674111 Add help modal for keyboard shortcuts. (#3033)
* Add help modal for keyboard shortcuts.

* add release note

* fix linter

* fix typecheck

* fix linter

* use component syntax for GroupHeading

* use component syntax for Shortcut

* fix linter

* use component syntax for KeyIcon

* refactor to support different dialogs

* show different help based on current page

* fix linter

* reword help

* capitalize letters

* show cmd on mac

* stop event propagation

* dont show if a modal is already open

* remove unused import

* rename modal

* move where location check happens

* dont stop event

* allow typing '?' in inputs

* better filter

* extract function

* fix linter

* dont show if filter popover is visible

* fix linter

* fix wrong shortcut, support SHIFT

* fix linter

* fix conditional
2024-07-15 08:06:09 -07:00
Michael Clark
16e887c917 :electron: Build electron for Mac Universal arch (#3015) 2024-07-13 22:03:57 +01:00
Tom Crasset
572033debe migrate BudgetList to typescript (#3026) 2024-07-13 18:02:01 +01:00
Michael Clark
b85f9102ce :electron: Convert window-state.js ➡️ window-state.ts (#3027) 2024-07-13 13:05:42 +01:00
Yusef Ouda
942aebedd0 fixes alignment of notifications on mobile to be centered (#3046) 2024-07-13 12:45:51 +01:00
Chris Tozlowski
32d830440a Fix the position of the separator in the operator menu when editing a rule (#3037)
* Fix line separator position in operations menu

* release note
2024-07-12 08:20:44 -07:00
youngcw
4575616961 [Goals]: don't reset goals when using "apply template" (#3011)
* fix apply budget

* un change

* note

* lint
2024-07-12 07:05:05 -07:00
DJ Mountney
4a0e2ea306 Remove Trafico workflow in favour of our new GitHub bot (#3023)
* Remove Trafico workflow in favour of our new GitHub bot

* Add release note
2024-07-11 07:30:12 -07:00
Robert Dyer
14ec9a9089 Dim hidden income category rows (#3032)
* Dim hidden income category rows

* add release note

* fix linter
2024-07-11 05:47:12 -07:00
Matt Fiddaman
e91b4070aa Add mergePayees method to the API (#3028) 2024-07-11 10:01:41 +01:00
Robert Dyer
6dd34b0c63 Perform bank sync in same order as accounts shown in sidebar. (#3029) 2024-07-11 09:32:57 +01:00
Robert Dyer
ab4639f48f API: add getBudgets() methods to list all budgets in the local cache or remote server. (#2928) 2024-07-11 09:27:32 +01:00
Austin Pearce
aa3cbd881b Fix alignment of reports` (#3007) 2024-07-10 20:17:48 -07:00
Robert Dyer
8681c9c3e6 Fix editing transactions on mobile not going back. (#2968) 2024-07-10 22:00:50 +01:00
Matiss Janis Aboltins
9ec9aef632 (budget-type) moving the selector to settings page (#3017)
*  (budget-type) moving the selector to settings page

* Feedback: move the block down
2024-07-10 21:58:20 +01:00
Robert Dyer
3be7dd753d Add getAccountBalance() API. (#2930) 2024-07-10 21:52:21 +01:00
Robert Dyer
259e84cea5 Expose bank sync account data in AQL. (#3022)
* Expose bank sync account data in AQL.

* add release note
2024-07-10 07:05:20 -07:00
Austin Pearce
f9014f0e19 Fix mobile payee creation (#3019) 2024-07-09 16:06:25 -07:00
Julian Wachholz
e59f5c9af8 Add apostrophe-dot number format (#2982) 2024-07-09 19:14:38 +01:00
Matt Fiddaman
771c01c8b4 Move bank sync payee name normalisation from actual to actual-server (#2721) 2024-07-09 19:05:15 +01:00
Michael Clark
9f72b43826 :electron: Remove unneded files (#3014) 2024-07-09 18:08:23 +01:00
Julian Wachholz
ec3475d834 Fix number formatting with non-breaking space (#2981) 2024-07-09 18:02:40 +01:00
Wizmaster
5ea9c587a8 Explicitly ask when reconciling transactions on manual import (#2717)
- Added import preview in transaction import list
- Added checkboxes to selectively prevent merging transactions

Co-authored-by: youngcw <calebyoung94@gmail.com>
2024-07-09 06:39:08 -07:00
Matiss Janis Aboltins
1e38055376 🐛 (popover) fix date popover closing when editing a filter (#3009) 2024-07-08 19:57:00 +01:00
Julian Dominguez-Schatz
0ee9126820 Disable interactivity on preview status icons (#2924)
* Disable interactivity on preview statuses

These have no click action but have a focus effect of a purple circle
(residual from the "Cleared" checkbox styling) that looks a bit glitchy.

* Add release notes

* Exclude status field from keyboard navigation
2024-07-08 10:39:00 -07:00
Joel Jeremy Marquez
9e455e4c1e Fix cover modal title (#3008)
* Fix cover modal title

* Release notes
2024-07-08 09:59:36 -07:00
Yusef Ouda
d77b54f27b reorders 'Rename' above 'Hide' in menu popovers, adds debounce to sidebar animation (#3001)
* reorders 'Rename' above 'Hide' in menu popovers

* release notes

* adds debounce to sidebar animation

* bump debounce time

* release notes

* release notes

* Update debounce import

* Update index.tsx

* Update index.tsx

* Update index.tsx

* Update index.tsx

* Update index.tsx

* Update index.tsx

* Update index.tsx

* Update index.tsx

* removes event listener on titlebar, changes margins
2024-07-08 09:51:28 -07:00
Sreetam Das
ff36d1efbe Add computed padding for handling clipped large Net worth amounts (#2818)
* Add computed padding for handling clipped Net worth amounts

* Add comment, early handle 5 character case

* Add release note

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

Co-authored-by: Robert Dyer <rdyer@unl.edu>

* Update vrt snapshots

* Fix NetWorthGraph cutoff when `compact` is true

This happens in case of `ReportCard`

* Update VRT snapshots to revert to original

* Revert snapshots to original

* vrt

---------

Co-authored-by: Robert Dyer <rdyer@unl.edu>
Co-authored-by: youngcw <calebyoung94@gmail.com>
2024-07-08 08:13:47 -07:00
youngcw
cbbbaf65cf remove version from electron build names (#3000)
* remove version from electron build names

* note

* fix
2024-07-07 14:54:14 -07:00
Yusef Ouda
f129b07dc9 Adds ability to resize sidebar (#2993)
* Adds ability to resize sidebar

* Adds release notes

* Changes to feature

* lint

* change translateX to use % for both states

* vrt

* set max sidebar width, cleanup

* set min and max widths

* min width to 200px

* changes resizable sidebar to use re-resizable instead off css resize

* vrt

* vrt
2024-07-07 14:10:41 -07:00
Joel Jeremy Marquez
f1caf21deb Assign schedule to both transactions if schedule is a transfer (#2990)
* Assign schedule to both transactions if schedule is a transfer

* Release notes

* Migration for old scheduled transfer transactions
2024-07-07 09:29:27 -07:00
Michael Clark
a28ea6be8f :electron: server.js ➡️ server.ts (#2995) 2024-07-07 13:31:26 +01:00
Michael Clark
f36c5e002b :electron: Remove "About" screen and broken updater (#2983) 2024-07-05 21:35:52 +01:00
Michael Clark
803289ee1f :electron: Electron menu.js ➡️ menu.ts (#2978) 2024-07-04 20:12:22 +01:00
Joel Jeremy Marquez
76cdad4fe6 React Aria Button on Accounts and Payees page (#2914)
* React Aria Button on payees and accounts page

* Release notes

* Fix Reconcile

* VRT

* VRT

* Fix balance hover

* VRT

* Update packages/desktop-client/src/components/accounts/Balance.jsx

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

* Fix lint

---------

Co-authored-by: Matiss Janis Aboltins <matiss@mja.lv>
2024-07-04 11:56:14 -07:00
Julian Wachholz
d03b30bc00 Add PWA shortcuts (#2980)
* Add PWA shortcuts

* Add release note
2024-07-04 08:27:57 -07:00
dymanoid
710d9ab8ac fix: use Unicode-aware SQLite LIKE filtering (#2903)
* fix: use Unicode-aware SQLite filtering

* Add release notes, fix type check

* Fix code styling
2024-07-04 08:24:58 -07:00
DJ Mountney
d008944022 Remove the trafico pr review triggers (#2942)
* Remove the trafico pr review triggers

- We loose triggers for Approved and Changes Requested
- They weren't working on forks anyways

* Add release note
2024-07-04 07:34:38 -07:00
Robert Dyer
f18bce6094 Fix warning modal not showing a second time. (#2956)
* Fix warning modal not showing a second time.

* add release note
2024-07-03 18:13:57 -07:00
Michael Clark
31eb00a155 :electron: Electron package Typescript starting point (#2880) 2024-07-03 21:28:04 +01:00
Joel Jeremy Marquez
a67c969189 React Aria Button on Settings and Rules Page (#2913)
* React Aria Button on rules and settings page

* Release notes

* VRT
2024-07-03 13:04:26 -07:00
dymanoid
58e6c6f23a Fix rollover arrow display for mobile and desktop (#2943)
* Fix rollover arrow display for mobile and desktop

* Add release notes

* Implement review suggestions

* Fix rollover indicator without goals
2024-07-03 11:00:03 -07:00
Matiss Janis Aboltins
f046d75b75 ♻️ refactoring Select component to use existing Menu (#2905) 2024-07-03 18:25:41 +01:00
Joel Jeremy Marquez
30bcfedc86 React Aria Button as base of Button component (#2904)
* React Aria Button as base of Button component

* Release notes

* AmountInput sign button

* Fix tests

* Comment

* Fix disabled/pressed style

* Update react-aria-components version

* yarn.lock

* Apply defaultStyle

* Fix button props type
2024-07-03 09:33:57 -07:00
Matiss Janis Aboltins
866b4d6cd4 🐛 (bank-sync) fix account highlight dissapearing (#2898) 2024-07-03 17:15:36 +01:00
Julian Dominguez-Schatz
a42938fa64 Reapply rules to split transactions when the parent changes (#2834)
* Reapply rules to split transactions when the parent changes

Concretely, this enables the "standard" workflow for
split-transaction-entry on desktop, where you enter the payee first, and
then edit the amount afterwards (and expect splits in a rule to apply when
you edit the amount).

* Add release notes

* Fix bug in first field below parent transaction

* Update vrt
2024-07-02 21:31:14 -07:00
Michael Clark
e02b0f9bc7 :electron: Fix backup time format in electron (#2960)
* fix backup time in electron

* release notes

* bugfix not maintenance
2024-07-02 14:16:16 -07:00
Matiss Janis Aboltins
049a41f366 🔖 (24.7.0) custom reports, splits in rules, tags and more (#2955) 2024-07-02 21:29:31 +01:00
Matiss Janis Aboltins
7f30680fb3 Revert "🐛 fix the app randomly navigating back (closing) (#2772)" (#2966)
This reverts commit 247e3e8d93.
2024-07-02 21:23:33 +01:00
Robert Dyer
2d4256b239 Fix wording of split rule. (#2927)
* Fix wording of slit rule.

* add release note

* fix typo
2024-06-27 13:08:30 -07:00
Matiss Janis Aboltins
247e3e8d93 🐛 fix the app randomly navigating back (closing) (#2772) 2024-06-27 19:00:17 +01:00
Joel Jeremy Marquez
5951b92668 Group and ungroup split transactions (#2805)
* Group and ungroup split transactions

* Release note

* Fix release note category

* Do not allow on reconciled transactions

* Add account validation, fix undo behavior, set split payee

* Fix lint errors

* Allow extracting some child transactions

* Disabled split/unsplit selected items menu

* Fix lint error

* Fix typecheck error

* Special Split payee

* "Split" payee on parent transaction

* Show manage payees on payee autocomplete modal

* Fix typecheck error + cleanup

* Fix typecheck error + cleanup

* VRT

* Fix tests

* VRT

* Only show split/unsplit when applicable
2024-06-26 12:39:36 -07:00
youngcw
a9ee670eb4 change the mobile budget balance colors to be the same as desktop (#2940)
* change the mobile budget colors to be the same as desktop

* fix

* some lint and note

* actual note

* fix mad lint

* another lint
2024-06-26 10:03:37 -07:00
lelemm
3990aaf38f Fix: Transaction table constantly resizing (#2941) 2024-06-26 17:27:47 +01:00
Michael Clark
48f5880f1d :electron: Fix "Export" function on desktop app (#2925)
* fix electron export file

* add release notes
2024-06-25 09:38:39 -07:00
Neil
3332f58376 Custom Reports: Adjust Net values (#2871)
* Add Net value

* notes

* fix

* revert changes

* balanceTypeOpType

* lint fix

* add net numbers

* bar fix

* nit fixes and fix clicks

* remove abs
2024-06-24 20:08:59 +01:00
Michael Clark
46ea8fbf72 :electron: Fix regex filters on electron app (#2929)
* fix regex on desktop app

* add release notes
2024-06-24 08:08:15 -07:00
Matiss Janis Aboltins
6a21f8e3de Revert "fix(#2562): Prevent transaction deduplication for imported transactio…" (#2910) 2024-06-22 21:32:51 +01:00
Joel Jeremy Marquez
f02ca4e3d2 Format transaction notes as clickable tags (#2670)
* Format notes as clickable tags

* Release notes

* Fix tests - extract the handler to higher level component

* Update colors

* Fix filtering

* Rename variables

* Remove font weight

* Cleanup style

* Append note tag filters

* Fix overlapping UI

* Revert pill colors

* Rename prop

* Rename notes

* Delete filter margin

* Fix typecheck error

* VRT + typecheck fix

* Add matches op in rules + use it to match tags

* Fix database types

* Fix typecheck error

* Fix typecheck

* Move create_function call

* VRT

* Update tag regex

* Escape regex input

* Update tag regex

* Use onApplyFilter

* Update tag formatting

* Fix tag formatting

* Update regex

* VRT

* Update packages/desktop-client/src/components/modals/EditRule.jsx

Co-authored-by: Robert Dyer <rdyer@unl.edu>

* VRT

* Fix error

* Fix filtered balance

* VRT

---------

Co-authored-by: Robert Dyer <rdyer@unl.edu>
2024-06-21 10:47:21 -07:00
636 changed files with 24188 additions and 15122 deletions

View File

@@ -1,4 +1,3 @@
/* eslint-disable rulesdir/typography */
const path = require('path');
const rulesDirPlugin = require('eslint-plugin-rulesdir');
@@ -34,9 +33,23 @@ const restrictedImportColors = [
];
module.exports = {
plugins: ['prettier', 'import', 'rulesdir', '@typescript-eslint'],
root: true,
env: {
browser: true,
commonjs: true,
es6: true,
jest: true,
node: true,
},
plugins: [
'prettier',
'import',
'rulesdir',
'@typescript-eslint',
'jsx-a11y',
'react-hooks',
],
extends: [
'react-app',
'plugin:react/recommended',
'plugin:react/jsx-runtime',
'plugin:prettier/recommended',
@@ -51,6 +64,184 @@ module.exports = {
vi: true,
},
rules: {
// http://eslint.org/docs/rules/
'array-callback-return': 'warn',
'default-case': ['warn', { commentPattern: '^no default$' }],
'dot-location': ['warn', 'property'],
eqeqeq: ['warn', 'smart'],
'new-parens': 'warn',
'no-array-constructor': 'warn',
'no-caller': 'warn',
'no-cond-assign': ['warn', 'except-parens'],
'no-const-assign': 'warn',
'no-control-regex': 'warn',
'no-delete-var': 'warn',
'no-dupe-args': 'warn',
'no-dupe-class-members': 'warn',
'no-dupe-keys': 'warn',
'no-duplicate-case': 'warn',
'no-empty-character-class': 'warn',
'no-empty-pattern': 'warn',
'no-eval': 'warn',
'no-ex-assign': 'warn',
'no-extend-native': 'warn',
'no-extra-bind': 'warn',
'no-extra-label': 'warn',
'no-fallthrough': 'warn',
'no-func-assign': 'warn',
'no-implied-eval': 'warn',
'no-invalid-regexp': 'warn',
'no-iterator': 'warn',
'no-label-var': 'warn',
'no-labels': ['warn', { allowLoop: true, allowSwitch: false }],
'no-lone-blocks': 'warn',
'no-mixed-operators': [
'warn',
{
groups: [
['&', '|', '^', '~', '<<', '>>', '>>>'],
['==', '!=', '===', '!==', '>', '>=', '<', '<='],
['&&', '||'],
['in', 'instanceof'],
],
allowSamePrecedence: false,
},
],
'no-multi-str': 'warn',
'no-global-assign': 'warn',
'no-unsafe-negation': 'warn',
'no-new-func': 'warn',
'no-new-object': 'warn',
'no-new-symbol': 'warn',
'no-new-wrappers': 'warn',
'no-obj-calls': 'warn',
'no-octal': 'warn',
'no-octal-escape': 'warn',
'no-redeclare': 'warn',
'no-regex-spaces': 'warn',
'no-script-url': 'warn',
'no-self-assign': 'warn',
'no-self-compare': 'warn',
'no-sequences': 'warn',
'no-shadow-restricted-names': 'warn',
'no-sparse-arrays': 'warn',
'no-template-curly-in-string': 'warn',
'no-this-before-super': 'warn',
'no-throw-literal': 'warn',
'no-undef': 'error',
'no-unreachable': 'warn',
'no-unused-expressions': [
'error',
{
allowShortCircuit: true,
allowTernary: true,
allowTaggedTemplates: true,
},
],
'no-unused-labels': 'warn',
'no-use-before-define': [
'warn',
{
functions: false,
classes: false,
variables: false,
},
],
'no-useless-computed-key': 'warn',
'no-useless-concat': 'warn',
'no-useless-constructor': 'warn',
'no-useless-escape': 'warn',
'no-useless-rename': [
'warn',
{
ignoreDestructuring: false,
ignoreImport: false,
ignoreExport: false,
},
],
'no-with': 'warn',
'no-whitespace-before-property': 'warn',
'react-hooks/exhaustive-deps': 'warn',
'require-yield': 'warn',
'rest-spread-spacing': ['warn', 'never'],
strict: ['warn', 'never'],
'unicode-bom': ['warn', 'never'],
'use-isnan': 'warn',
'valid-typeof': 'warn',
'no-restricted-properties': [
'error',
{
object: 'require',
property: 'ensure',
message:
'Please use import() instead. More info: https://facebook.github.io/create-react-app/docs/code-splitting',
},
{
object: 'System',
property: 'import',
message:
'Please use import() instead. More info: https://facebook.github.io/create-react-app/docs/code-splitting',
},
],
'getter-return': 'warn',
// https://github.com/benmosher/eslint-plugin-import/tree/master/docs/rules
'import/first': 'error',
'import/no-amd': 'error',
'import/no-anonymous-default-export': 'warn',
'import/no-webpack-loader-syntax': 'error',
// https://github.com/yannickcr/eslint-plugin-react/tree/master/docs/rules
'react/forbid-foreign-prop-types': ['warn', { allowInPropTypes: true }],
'react/jsx-no-comment-textnodes': 'warn',
'react/jsx-no-duplicate-props': 'warn',
'react/jsx-no-target-blank': 'warn',
'react/jsx-no-undef': 'error',
'react/jsx-pascal-case': [
'warn',
{
allowAllCaps: true,
ignore: [],
},
],
'react/no-danger-with-children': 'warn',
// Disabled because of undesirable warnings
// See https://github.com/facebook/create-react-app/issues/5204 for
// blockers until its re-enabled
// 'react/no-deprecated': 'warn',
'react/no-direct-mutation-state': 'warn',
'react/no-is-mounted': 'warn',
'react/no-typos': 'error',
'react/require-render-return': 'error',
'react/style-prop-object': 'warn',
// https://github.com/evcohen/eslint-plugin-jsx-a11y/tree/master/docs/rules
'jsx-a11y/alt-text': 'warn',
'jsx-a11y/anchor-has-content': 'warn',
'jsx-a11y/anchor-is-valid': [
'warn',
{
aspects: ['noHref', 'invalidHref'],
},
],
'jsx-a11y/aria-activedescendant-has-tabindex': 'warn',
'jsx-a11y/aria-props': 'warn',
'jsx-a11y/aria-proptypes': 'warn',
'jsx-a11y/aria-role': ['warn', { ignoreNonDOM: true }],
'jsx-a11y/aria-unsupported-elements': 'warn',
'jsx-a11y/heading-has-content': 'warn',
'jsx-a11y/iframe-has-title': 'warn',
'jsx-a11y/img-redundant-alt': 'warn',
'jsx-a11y/no-access-key': 'warn',
'jsx-a11y/no-distracting-elements': 'warn',
'jsx-a11y/no-redundant-roles': 'warn',
'jsx-a11y/role-has-required-aria-props': 'warn',
'jsx-a11y/role-supports-aria-props': 'warn',
'jsx-a11y/scope': 'warn',
// https://github.com/facebook/react/tree/main/packages/eslint-plugin-react-hooks
'react-hooks/rules-of-hooks': 'error',
'prettier/prettier': 'warn',
// Note: base rule explicitly disabled in favor of the TS one
@@ -60,6 +251,7 @@ module.exports = {
{
varsIgnorePattern: '^(_|React)',
ignoreRestSiblings: true,
caughtErrors: 'none',
},
],
@@ -166,9 +358,63 @@ module.exports = {
'prefer-const': 'warn',
'prefer-spread': 'off',
'@typescript-eslint/no-empty-function': 'off',
'@typescript-eslint/no-require-imports': 'off',
'import/no-default-export': 'warn',
},
overrides: [
{
files: ['**/*.ts?(x)'],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 2018,
sourceType: 'module',
ecmaFeatures: {
jsx: true,
},
// typescript-eslint specific options
warnOnUnsupportedTypeScriptVersion: true,
},
plugins: ['@typescript-eslint'],
// If adding a typescript-eslint version of an existing ESLint rule,
// make sure to disable the ESLint rule here.
rules: {
// TypeScript's `noFallthroughCasesInSwitch` option is more robust (#6906)
'default-case': 'off',
// 'tsc' already handles this (https://github.com/typescript-eslint/typescript-eslint/issues/291)
'no-dupe-class-members': 'off',
// 'tsc' already handles this (https://github.com/typescript-eslint/typescript-eslint/issues/477)
'no-undef': 'off',
// Add TypeScript specific rules (and turn off ESLint equivalents)
'@typescript-eslint/consistent-type-assertions': 'warn',
'no-array-constructor': 'off',
'@typescript-eslint/no-array-constructor': 'warn',
'no-redeclare': 'off',
'@typescript-eslint/no-redeclare': 'warn',
'no-use-before-define': 'off',
'@typescript-eslint/no-use-before-define': [
'warn',
{
functions: false,
classes: false,
variables: false,
typedefs: false,
},
],
'no-unused-expressions': 'off',
'@typescript-eslint/no-unused-expressions': [
'error',
{
allowShortCircuit: true,
allowTernary: true,
allowTaggedTemplates: true,
},
],
'no-useless-constructor': 'off',
'@typescript-eslint/no-useless-constructor': 'warn',
},
},
{
files: ['.eslintrc.js', './**/.eslintrc.js'],
parserOptions: { project: null },
@@ -189,7 +435,7 @@ module.exports = {
'warn',
{ prefer: 'type-imports', fixStyle: 'inline-type-imports' },
],
'@typescript-eslint/ban-types': [
'@typescript-eslint/no-restricted-types': [
'warn',
{
types: {
@@ -197,7 +443,6 @@ module.exports = {
FunctionComponent: { message: ruleFCMsg },
FC: { message: ruleFCMsg },
},
extendDefaults: true,
},
],
},
@@ -332,8 +577,23 @@ module.exports = {
'react-hooks/exhaustive-deps': 'off',
},
},
{
files: [
'.eslintrc.js',
'*.test.js',
'*.test.ts',
'*.test.jsx',
'*.test.tsx',
],
rules: {
'rulesdir/typography': 'off',
},
},
],
settings: {
react: {
version: 'detect',
},
'import/resolver': {
typescript: {
alwaysTryTypes: true,

View File

@@ -48,13 +48,17 @@ jobs:
sudo flatpak install org.electronjs.Electron2.BaseApp/x86_64/23.08 -y
- name: Set up environment
uses: ./.github/actions/setup
- name: Build Electron
- name: Build Electron for Mac
if: ${{ startsWith(matrix.os, 'macos') }}
run: ./bin/package-electron
env:
# CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
# CSC_LINK: ${{ secrets.CSC_LINK }}
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
CSC_LINK: ${{ secrets.CSC_LINK }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
- name: Build Electron
if: ${{ ! startsWith(matrix.os, 'macos') }}
run: ./bin/package-electron
- name: Upload Build
uses: actions/upload-artifact@v4
with:
@@ -62,13 +66,22 @@ jobs:
path: |
packages/desktop-electron/dist/*.dmg
packages/desktop-electron/dist/*.exe
!packages/desktop-electron/dist/Actual-windows.exe
packages/desktop-electron/dist/*.AppImage
packages/desktop-electron/dist/*.flatpak
- name: Upload Windows Store Build
if: ${{ startsWith(matrix.os, 'windows') }}
uses: actions/upload-artifact@v4
with:
name: actual-electron-${{ matrix.os }}-appx
path: |
packages/desktop-electron/dist/*.appx
- name: Add to Release
uses: softprops/action-gh-release@v2
with:
files: |
packages/desktop-electron/dist/*.dmg
packages/desktop-electron/dist/*.exe
!packages/desktop-electron/dist/Actual-windows.exe
packages/desktop-electron/dist/*.AppImage
packages/desktop-electron/dist/*.flatpak

View File

@@ -52,5 +52,13 @@ jobs:
path: |
packages/desktop-electron/dist/*.dmg
packages/desktop-electron/dist/*.exe
!packages/desktop-electron/dist/Actual-windows.exe
packages/desktop-electron/dist/*.AppImage
packages/desktop-electron/dist/*.flatpak
- name: Upload Windows Store Build
if: ${{ startsWith(matrix.os, 'windows') }}
uses: actions/upload-artifact@v4
with:
name: actual-electron-${{ matrix.os }}-appx
path: |
packages/desktop-electron/dist/*.appx

View File

@@ -7,10 +7,20 @@ jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v8
- uses: actions/stale@v9
with:
stale-pr-message: 'This PR is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days.'
close-pr-message: 'This PR was closed because it has been stalled for 5 days with no activity.'
days-before-stale: 30
days-before-close: 5
days-before-issue-stale: -1
stale-wip:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v9
with:
stale-pr-message: ':wave: Hi! It looks like this PR has not had any changes for a week now. Would you like someone to review this PR? If so - please remove the "[WIP]" prefix from the PR title. That will let the community know that this PR is open for a review.'
days-before-stale: 7
any-of-labels: ':construction: WIP'
days-before-close: -1
days-before-issue-stale: -1

View File

@@ -1,39 +0,0 @@
##########################################################################################
# WARNING! This workflow uses the 'pull_request_target' event. That mans that it will #
# always run in the context of the main actualbudget/actual repo, even if the PR is from #
# a fork. This is necessary to get access to a GitHub token that can modify the PR. #
# Be VERY CAREFUL about adding things to this workflow, since forks can inject #
# arbitrary code into their branch, and can pollute the artifacts we download. Arbitrary #
# code execution in this workflow could lead to a compromise of the main repo. #
##########################################################################################
# See: https://securitylab.github.com/research/github-actions-preventing-pwn-requests #
##########################################################################################
name: Trafico Reviews
on:
pull_request_target:
types:
- opened
- closed
- reopened
- synchronize
- edited
- review_requested
- review_request_removed
pull_request_review:
types: [submitted, edited, dismissed]
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
manage-review:
runs-on: ubuntu-latest
permissions:
pull-requests: write
steps:
- uses: actualbudget/trafico@main
with:
github-token: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -1,27 +0,0 @@
name: Add WIP
on:
pull_request_target:
types:
- opened
jobs:
add_wip_prefix:
if: |
join(github.event.pull_request.requested_reviewers) == ''
&& !contains(github.event.pull_request.title, 'WIP')
&& !contains(github.event.pull_request.labels.*.name, 'WIP')
&& github.event.pull_request.draft != true
runs-on: ubuntu-latest
permissions:
pull-requests: write
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Add WIP
env:
TITLE: ${{ github.event.pull_request.title }}
shell: bash
run: |
echo ${{ secrets.GITHUB_TOKEN }} | gh auth login --with-token
gh pr edit ${{ github.event.pull_request.number }} -t "[WIP] ${TITLE}"

1
.gitignore vendored
View File

@@ -21,6 +21,7 @@ packages/api/dist
packages/api/@types
packages/crdt/dist
packages/desktop-electron/client-build
packages/desktop-electron/build
packages/desktop-electron/.electron-symbols
packages/desktop-electron/dist
packages/desktop-electron/loot-core

1
.husky/pre-commit Normal file
View File

@@ -0,0 +1 @@
yarn lint-staged

File diff suppressed because one or more lines are too long

894
.yarn/releases/yarn-4.3.1.cjs vendored Executable file

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.2.cjs
yarnPath: .yarn/releases/yarn-4.3.1.cjs

View File

@@ -14,22 +14,40 @@ Want to say thanks? Click the ⭐ at the top of the page.
- Actual [discord](https://discord.gg/pRYNYr4W5A) community.
- Actual [Community Documentation](https://actualbudget.org/docs)
- [Frequently asked questions](https://actualbudget.org/docs/faq)
## Installation
If you are only interested in running the latest version and not contributing to the source code, you don't need to clone this repo. You can get the latest version through npm.
There are four ways to deploy Actual:
### The easy way: using a server (recommended)
1. One-click deployment [via PikaPods](https://www.pikapods.com/pods?run=actual) (~1.40 $/month) - recommended for non-technical users
1. Managed hosting [via Fly.io](https://actualbudget.org/docs/install/fly) (~1.50 $/month)
1. Self-hosted by using [a Docker image](https://actualbudget.org/docs/install/docker)
1. Local-only apps - [downloadable Windows, Mac and Linux apps](https://actualbudget.org/download/) you can run on your device
The easiest way to get Actual running is to use the [actual-server](https://github.com/actualbudget/actual-server) project. That is the server for syncing changes across devices, and it comes with the latest version of Actual. The server will provide both the web project and a server for syncing.
Learn more in the [installation instructions docs](https://actualbudget.org/docs/install/).
You can get up and running quickly and easily by following our [Running Actual Locally Guide](https://actualbudget.org/docs/install/local)
## Ready to Start Budgeting?
Read about [Envelope budgeting](https://actualbudget.org/docs/getting-started/envelope-budgeting) to know more about the idea behind Actual Budget.
### Are you new to budgeting or want to start fresh?
Check out the community's [Starting Fresh](https://actualbudget.org/docs/getting-started/starting-fresh) guide so you can quickly get up and running!
### Are you migrating from other budgeting apps?
Check out the community's [Migration](https://actualbudget.org/docs/migration/) guide to start jumping on the Actual Budget train!
## Documentation
We have a wide range of documentation on how to use Actual, this is all available in our [Community Documentation](https://actualbudget.org/docs), this includes topics on Budgeting, Account Management, Tips & Tricks and some documentation for developers.
## Code structure
## Contributing
Actual is a community driven product. Learn more about [contributing to Actual](https://actualbudget.org/docs/contributing/).
### Code structure
The Actual app is split up into a few packages:
@@ -39,13 +57,21 @@ The Actual app is split up into a few packages:
More information on the project structure is available in our [community documentation](https://actualbudget.org/docs/contributing/project-details).
## Feature Requests
### Feature Requests
Current feature requests can be seen [here](https://github.com/actualbudget/actual/issues?q=is%3Aissue+label%3A%22needs+votes%22+sort%3Areactions-%2B1-desc).
Vote for your favorite requests by reacting :+1: to the top comment of the request.
To add new feature requests, open a new Issue of the "Feature Request" type.
### Translation
Make Actual Budget accessible to more people by helping with the [Internationalization](https://actualbudget.org/docs/contributing/i18n/) of Actual. We are using a crowd sourcing tool to manage the translations, see our [Weblate Project](https://hosted.weblate.org/projects/actualbudget/). Weblate proudly supports open-source software projects through their [Libre plan](https://weblate.org/en/hosting/#libre).
## Repo Activity
![Alt](https://repobeats.axiom.co/api/embed/e20537dd8b74956f86736726ccfbc6f0565bec22.svg 'Repobeats analytics image')
## Sponsors
Thanks to our wonderful sponsors who make Actual budget possible!

View File

@@ -34,8 +34,6 @@ if [ "$OSTYPE" == "msys" ]; then
fi
fi
yarn rebuild-electron
yarn workspace loot-core build:node
yarn workspace @actual-app/web build --mode=desktop
@@ -50,10 +48,10 @@ yarn workspace desktop-electron update-client
if [ -f ../../.secret-tokens ]; then
source ../../.secret-tokens
fi
yarn build --publish never --arm64 --x64
yarn build
echo "\nCreated release"
else
SKIP_NOTARIZATION=true yarn build --publish never --x64
SKIP_NOTARIZATION=true yarn build
fi
)

View File

@@ -30,6 +30,7 @@
"build:browser": "./bin/package-browser",
"build:desktop": "./bin/package-electron",
"build:api": "yarn workspace @actual-app/api build",
"generate:i18n": "yarn workspace @actual-app/web generate:i18n",
"test": "yarn workspaces foreach --all --parallel --verbose run test",
"test:debug": "yarn workspaces foreach --all --verbose run test",
"e2e": "yarn workspaces foreach --all --parallel --verbose run e2e",
@@ -40,24 +41,31 @@
"lint": "eslint . --max-warnings 0 --ext .js,.jsx,.ts,.tsx",
"lint:verbose": "DEBUG=eslint:cli-engine eslint . --max-warnings 0",
"typecheck": "yarn tsc && tsc-strict",
"jq": "./node_modules/node-jq/bin/jq"
"jq": "./node_modules/node-jq/bin/jq",
"prepare": "husky"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^8.1.0",
"@typescript-eslint/parser": "^8.1.0",
"confusing-browser-globals": "^1.0.11",
"cross-env": "^7.0.3",
"eslint": "^8.37.0",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-config-react-app": "7.0.1",
"eslint-import-resolver-typescript": "3.5.5",
"eslint-plugin-import": "2.27.5",
"eslint-plugin-prettier": "5.1.3",
"eslint-plugin-react": "7.32.2",
"eslint-import-resolver-typescript": "3.6.1",
"eslint-plugin-import": "2.29.1",
"eslint-plugin-jsx-a11y": "^6.9.0",
"eslint-plugin-prettier": "5.2.1",
"eslint-plugin-react": "7.35.0",
"eslint-plugin-react-hooks": "^4.6.2",
"eslint-plugin-rulesdir": "^0.2.2",
"husky": "^9.0.11",
"lint-staged": "^15.2.9",
"node-jq": "^4.0.1",
"npm-run-all": "^4.1.5",
"prettier": "3.2.4",
"prettier": "3.3.3",
"source-map-support": "^0.5.21",
"typescript": "^5.0.2",
"typescript-strict-plugin": "^2.2.2-beta.2"
"typescript": "^5.5.4",
"typescript-strict-plugin": "^2.4.4"
},
"resolutions": {
"rollup": "4.9.4"
@@ -65,7 +73,10 @@
"engines": {
"node": ">=18.0.0"
},
"packageManager": "yarn@4.0.2",
"lint-staged": {
"*.{js,jsx,ts,tsx,md,json}": "prettier --write"
},
"packageManager": "yarn@4.3.1",
"browserslist": [
"electron 24.0",
"defaults"

View File

@@ -58,6 +58,19 @@ describe('API CRUD operations', () => {
await api.loadBudget(budgetName);
});
// api: getBudgets
test('getBudgets', async () => {
const budgets = await api.getBudgets();
expect(budgets).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: 'test-budget',
name: 'Default Test Db',
}),
]),
);
});
// apis: getCategoryGroups, createCategoryGroup, updateCategoryGroup, deleteCategoryGroup
test('CategoryGroups: successfully update category groups', async () => {
const month = '2023-10';
@@ -68,28 +81,22 @@ describe('API CRUD operations', () => {
expect(groups).toEqual(
expect.arrayContaining([
expect.objectContaining({
hidden: 0,
hidden: false,
id: 'fc3825fd-b982-4b72-b768-5b30844cf832',
is_income: 0,
is_income: false,
name: 'Usual Expenses',
sort_order: 16384,
tombstone: 0,
}),
expect.objectContaining({
hidden: 0,
hidden: false,
id: 'a137772f-cf2f-4089-9432-822d2ddc1466',
is_income: 0,
is_income: false,
name: 'Investments and Savings',
sort_order: 32768,
tombstone: 0,
}),
expect.objectContaining({
hidden: 0,
hidden: false,
id: '2E1F5BDB-209B-43F9-AF2C-3CE28E380C00',
is_income: 1,
is_income: true,
name: 'Income',
sort_order: 32768,
tombstone: 0,
}),
]),
);
@@ -251,7 +258,7 @@ describe('API CRUD operations', () => {
);
});
//apis: createAccount, getAccounts, updateAccount, closeAccount, deleteAccount, reopenAccount
//apis: createAccount, getAccounts, updateAccount, closeAccount, deleteAccount, reopenAccount, getAccountBalance
test('Accounts: successfully complete account operators', async () => {
const accountId1 = await api.createAccount(
{ name: 'test-account1', offbudget: true },
@@ -272,6 +279,9 @@ describe('API CRUD operations', () => {
]),
);
expect(await api.getAccountBalance(accountId1)).toEqual(1000);
expect(await api.getAccountBalance(accountId2)).toEqual(0);
await api.updateAccount(accountId1, { offbudget: false });
await api.closeAccount(accountId1, accountId2, null);
await api.deleteAccount(accountId2);
@@ -547,10 +557,10 @@ describe('API CRUD operations', () => {
);
// delete rules
await api.deleteRule(rules[1]);
await api.deleteRule(rules[1].id);
expect(await api.getRules()).toHaveLength(1);
await api.deleteRule(rules[0]);
await api.deleteRule(rules[0].id);
expect(await api.getRules()).toHaveLength(0);
});
@@ -569,6 +579,11 @@ describe('API CRUD operations', () => {
});
expect(addResult).toBe('ok');
expect(await api.getAccountBalance(accountId)).toEqual(200);
expect(
await api.getAccountBalance(accountId, new Date(2023, 10, 2)),
).toEqual(0);
// confirm added transactions exist
let transactions = await api.getTransactions(
accountId,

View File

@@ -31,6 +31,10 @@ export async function downloadBudget(syncId, { password }: { password? } = {}) {
return send('api/download-budget', { syncId, password });
}
export async function getBudgets() {
return send('api/get-budgets');
}
export async function sync() {
return send('api/sync');
}
@@ -125,6 +129,10 @@ export function deleteAccount(id) {
return send('api/account-delete', { id });
}
export function getAccountBalance(id, cutoff?) {
return send('api/account-balance', { id, cutoff });
}
export function getCategoryGroups() {
return send('api/category-groups-get');
}
@@ -157,6 +165,10 @@ export function deleteCategory(id, transferCategoryId?) {
return send('api/category-delete', { id, transferCategoryId });
}
export function getCommonPayees() {
return send('api/common-payees-get');
}
export function getPayees() {
return send('api/payees-get');
}
@@ -173,6 +185,10 @@ export function deletePayee(id) {
return send('api/payee-delete', { id });
}
export function mergePayees(targetId, mergeIds) {
return send('api/payees-merge', { targetId, mergeIds });
}
export function getRules() {
return send('api/rules-get');
}
@@ -189,6 +205,14 @@ export function updateRule(rule) {
return send('api/rule-update', { rule });
}
export function deleteRule(id) {
return send('api/rule-delete', { id });
export function deleteRule(id: string) {
return send('api/rule-delete', id);
}
export function holdBudgetForNextMonth(month, amount) {
return send('api/budget-hold-for-next-month', { month, amount });
}
export function resetBudgetHold(month) {
return send('api/budget-reset-hold', { month });
}

View File

@@ -1,6 +1,6 @@
{
"name": "@actual-app/api",
"version": "6.8.1",
"version": "6.10.0",
"license": "MIT",
"description": "An API for Actual",
"engines": {
@@ -35,6 +35,6 @@
"@types/uuid": "^9.0.2",
"jest": "^27.5.1",
"tsc-alias": "^1.8.8",
"typescript": "^5.0.2"
"typescript": "^5.5.4"
}
}

View File

@@ -26,6 +26,6 @@
"@types/uuid": "^9.0.2",
"jest": "^27.5.1",
"ts-protoc-gen": "^0.15.0",
"typescript": "^5.0.2"
"typescript": "^5.5.4"
}
}

View File

@@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import * as merkle from './merkle';
import { Timestamp } from './timestamp';

View File

@@ -134,6 +134,7 @@ export function diff(trie1: TrieNode, trie2: TrieNode): number | null {
node2 = node2[diffkey] || emptyTrie();
}
// eslint-disable-next-line no-unreachable
return null;
}

View File

@@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import { Timestamp } from './timestamp';
describe('Timestamp', function () {

View File

@@ -154,7 +154,7 @@ export class Timestamp {
/**
* maximum timestamp
*/
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
static max = Timestamp.parse(
'9999-12-31T23:59:59.999Z-FFFF-FFFFFFFFFFFFFFFF',
)!;
@@ -294,7 +294,7 @@ export class Timestamp {
/**
* zero/minimum timestamp
*/
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
static zero = Timestamp.parse(
'1970-01-01T00:00:00.000Z-0000-0000000000000000',
)!;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 127 KiB

After

Width:  |  Height:  |  Size: 197 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 126 KiB

After

Width:  |  Height:  |  Size: 190 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 125 KiB

After

Width:  |  Height:  |  Size: 190 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 30 KiB

View File

@@ -16,8 +16,8 @@ export class AccountPage {
this.cancelTransactionButton = this.page.getByRole('button', {
name: 'Cancel',
});
this.menuButton = this.page.getByRole('button', {
name: 'Menu',
this.accountMenuButton = this.page.getByRole('button', {
name: 'Account menu',
});
this.transactionTable = this.page.getByTestId('transaction-table');
@@ -30,16 +30,28 @@ export class AccountPage {
this.selectTooltip = this.page.getByTestId('transactions-select-tooltip');
}
/**
* Enter details of a transaction
*/
async enterSingleTransaction(transaction) {
await this.addNewTransactionButton.click();
await this._fillTransactionFields(this.newTransactionRow, transaction);
}
/**
* Finish adding a transaction
*/
async addEnteredTransaction() {
await this.addTransactionButton.click();
await this.cancelTransactionButton.click();
}
/**
* Create a single transaction
*/
async createSingleTransaction(transaction) {
await this.addNewTransactionButton.click();
await this._fillTransactionFields(this.newTransactionRow, transaction);
await this.addTransactionButton.click();
await this.cancelTransactionButton.click();
await this.enterSingleTransaction(transaction);
await this.addEnteredTransaction();
}
/**
@@ -82,6 +94,15 @@ export class AccountPage {
*/
getNthTransaction(index) {
const row = this.transactionTableRow.nth(index);
return this._getTransactionDetails(row);
}
getEnteredTransaction() {
return this._getTransactionDetails(this.newTransactionRow);
}
_getTransactionDetails(row) {
const account = row.getByTestId('account');
return {
@@ -103,10 +124,10 @@ export class AccountPage {
* Open the modal for closing the account.
*/
async clickCloseAccount() {
await this.menuButton.click();
await this.accountMenuButton.click();
await this.page.getByRole('button', { name: 'Close Account' }).click();
return new CloseAccountModal(
this.page.locator('css=[aria-modal]'),
this.page.getByTestId('close-account-modal'),
this.page,
);
}

View File

@@ -5,22 +5,22 @@ export class ReportsPage {
}
async waitToLoad() {
return this.pageContent.getByRole('link', { name: /^Net/ }).waitFor();
return this.pageContent.getByRole('button', { name: /^Net/ }).waitFor();
}
async goToNetWorthPage() {
await this.pageContent.getByRole('link', { name: /^Net/ }).click();
await this.pageContent.getByRole('button', { name: /^Net/ }).click();
return new ReportsPage(this.page);
}
async goToCashFlowPage() {
await this.pageContent.getByRole('link', { name: /^Cash/ }).click();
await this.pageContent.getByRole('button', { name: /^Cash/ }).click();
return new ReportsPage(this.page);
}
async getAvailableReportList() {
return this.pageContent
.getByRole('link')
.getByRole('button')
.getByRole('heading')
.allTextContents();
}

View File

@@ -44,7 +44,7 @@ export class RulesPage {
.first()
.click();
await this.page
.getByRole('option', { exact: true, name: data.conditionsOp })
.getByRole('button', { exact: true, name: data.conditionsOp })
.click();
}
@@ -52,6 +52,7 @@ export class RulesPage {
await this._fillEditorFields(
data.conditions,
this.page.getByTestId('condition-list'),
true,
);
}
@@ -63,28 +64,19 @@ export class RulesPage {
}
if (data.splits) {
if (data.splits.beforeSplitActions) {
let idx = data.actions?.length ?? 0;
for (const splitActions of data.splits) {
await this.page.getByTestId('add-split-transactions').click();
await this._fillEditorFields(
data.splits.beforeSplitActions,
this.page.getByTestId('action-list'),
splitActions,
this.page.getByTestId('action-list').nth(idx),
);
}
if (data.splits.splitActions) {
let idx = data.splits?.beforeSplitActions.length ?? 0;
for (const splitActions of data.splits.splitActions) {
await this.page.getByTestId('add-split-transactions').click();
await this._fillEditorFields(
splitActions,
this.page.getByTestId('action-list').nth(idx),
);
idx++;
}
idx++;
}
}
}
async _fillEditorFields(data, rootElement) {
async _fillEditorFields(data, rootElement, fieldFirst = false) {
for (const idx in data) {
const { field, op, value } = data[idx];
@@ -94,16 +86,25 @@ export class RulesPage {
await rootElement.getByRole('button', { name: 'Add entry' }).click();
}
if (op && !fieldFirst) {
await row.getByTestId('op-select').getByRole('button').first().click();
await this.page.getByRole('button', { name: op, exact: true }).click();
}
if (field) {
await row.getByRole('button').first().click();
await row
.getByTestId('field-select')
.getByRole('button')
.first()
.click();
await this.page
.getByRole('option', { exact: true, name: field })
.getByRole('button', { name: field, exact: true })
.click();
}
if (op) {
await row.getByRole('button', { name: 'is' }).click();
await this.page.getByRole('option', { name: op, exact: true }).click();
if (op && fieldFirst) {
await row.getByTestId('op-select').getByRole('button').first().click();
await this.page.getByRole('button', { name: op, exact: true }).click();
}
if (value) {

View File

@@ -84,6 +84,10 @@ export class SchedulesPage {
if (data.amount) {
await this.page.getByLabel('Amount').fill(String(data.amount));
// For some readon, the input field does not trigger the change event on tests
// but it works on the browser. We can revisit this once migration to
// react aria components is complete.
await this.page.keyboard.press('Enter');
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 81 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: 81 KiB

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 62 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: 79 KiB

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

After

Width:  |  Height:  |  Size: 76 KiB

View File

@@ -32,6 +32,7 @@ test.describe('Rules', () => {
});
test('creates a rule and makes sure it is applied when creating a transaction', async () => {
await rulesPage.searchFor('Fast Internet');
await rulesPage.createRule({
conditions: [
{
@@ -48,7 +49,6 @@ test.describe('Rules', () => {
],
});
await rulesPage.searchFor('Fast Internet');
const rule = rulesPage.getNthRule(0);
await expect(rule.conditions).toHaveText(['payee is Fast Internet']);
await expect(rule.actions).toHaveText(['set category to General']);
@@ -79,35 +79,34 @@ test.describe('Rules', () => {
value: 'Ikea',
},
],
splits: {
beforeSplitActions: [
actions: [
{
op: 'set',
field: 'notes',
value: 'food / entertainment',
},
],
splits: [
[
{
field: 'notes',
value: 'food / entertainment',
field: 'a fixed percent of the remainder',
value: '90',
},
{
field: 'category',
value: 'Entertainment',
},
],
splitActions: [
[
{
field: 'a fixed percent',
value: '90',
},
{
field: 'category',
value: 'Entertainment',
},
],
[
{
field: 'an equal portion of the remainder',
},
{
field: 'category',
value: 'Food',
},
],
[
{
field: 'an equal portion of the remainder',
},
{
field: 'category',
value: 'Food',
},
],
},
],
});
const accountPage = await navigation.goToAccountPage(

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 68 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: 68 KiB

After

Width:  |  Height:  |  Size: 68 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: 83 KiB

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

After

Width:  |  Height:  |  Size: 82 KiB

View File

@@ -141,4 +141,26 @@ test.describe('Transactions', () => {
await expect(thirdTransaction.credit).toHaveText('');
await expect(page).toMatchThemeScreenshots();
});
test('creates a transfer test transaction', async () => {
await accountPage.enterSingleTransaction({
payee: 'Bank of America',
notes: 'Notes field',
debit: '12.34',
});
let transaction = accountPage.getEnteredTransaction();
await expect(transaction.category.locator('input')).toHaveValue('Transfer');
await expect(page).toMatchThemeScreenshots();
await accountPage.addEnteredTransaction();
transaction = accountPage.getNthTransaction(0);
await expect(transaction.payee).toHaveText('Bank of America');
await expect(transaction.notes).toHaveText('Notes field');
await expect(transaction.category).toHaveText('Transfer');
await expect(transaction.debit).toHaveText('12.34');
await expect(transaction.credit).toHaveText('');
await expect(page).toMatchThemeScreenshots();
});
});

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: 104 KiB

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.9 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.6 KiB

After

Width:  |  Height:  |  Size: 11 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: 108 KiB

After

Width:  |  Height:  |  Size: 108 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: 9.2 KiB

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.7 KiB

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 58 KiB

View File

@@ -0,0 +1,14 @@
module.exports = {
input: ['src/**/*.{js,jsx,ts,tsx}', '../loot-core/src/**/*.{js,jsx,ts,tsx}'],
output: 'src/locale/$LOCALE.json',
locales: ['en'],
sort: true,
keySeparator: false,
namespaceSeparator: false,
defaultValue: (locale, ns, key, value) => {
if (locale === 'en') {
return value || key;
}
return '';
},
};

View File

@@ -1,6 +1,6 @@
{
"name": "@actual-app/web",
"version": "24.6.0",
"version": "24.9.0",
"license": "MIT",
"files": [
"build"
@@ -8,7 +8,6 @@
"devDependencies": {
"@juggle/resize-observer": "^3.4.0",
"@playwright/test": "1.41.1",
"@reach/listbox": "^0.18.0",
"@react-aria/focus": "^3.16.0",
"@react-aria/listbox": "^3.11.3",
"@react-aria/utils": "^3.23.0",
@@ -25,6 +24,7 @@
"@types/promise-retry": "^1.1.6",
"@types/react": "^18.2.0",
"@types/react-dom": "^18.2.1",
"@types/react-grid-layout": "^1",
"@types/react-modal": "^3.16.0",
"@types/react-redux": "^7.1.25",
"@types/uuid": "^9.0.2",
@@ -40,6 +40,9 @@
"downshift": "7.6.2",
"focus-visible": "^4.1.5",
"glamor": "^2.20.40",
"i18next": "^23.11.5",
"i18next-parser": "^9.0.0",
"i18next-resources-to-backend": "^1.2.1",
"inter-ui": "^3.19.3",
"jest": "^27.5.1",
"jest-watch-typeahead": "^2.2.2",
@@ -48,13 +51,17 @@
"memoize-one": "^6.0.0",
"pikaday": "1.8.2",
"promise-retry": "^2.0.1",
"re-resizable": "^6.9.17",
"react": "18.2.0",
"react-aria-components": "^1.1.1",
"react-aria": "^3.33.1",
"react-aria-components": "^1.2.1",
"react-dnd": "^16.0.1",
"react-dnd-html5-backend": "^16.0.1",
"react-dom": "18.2.0",
"react-error-boundary": "^4.0.12",
"react-grid-layout": "^1.4.4",
"react-hotkeys-hook": "^4.5.0",
"react-i18next": "^14.1.2",
"react-markdown": "^8.0.7",
"react-modal": "3.16.1",
"react-redux": "7.2.9",
@@ -86,6 +93,7 @@
"build": "vite build",
"build:browser": "cross-env ./bin/build-browser",
"generate:icons": "rm src/icons/*/*.tsx; cd src/icons && svgr --template template.ts --index-template index-template.ts --typescript --expand-props start -d . .",
"generate:i18n": "i18next",
"test": "vitest",
"e2e": "npx playwright test --browser=chromium",
"vrt": "cross-env VRT=true npx playwright test --browser=chromium"

View File

@@ -57,6 +57,9 @@ export default defineConfig({
timeout: 20000, // 20 seconds
retries: 1,
testDir: 'e2e/',
reporter: !process.env.CI
? [['html', { open: 'never', outputFolder: 'test-results/html' }]]
: undefined,
use: {
userAgent: 'playwright',
screenshot: 'on',

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
<path d="M1.857 7.612H.98a.98.98 0 0 0-.98.98v2.224c0 .449.184.857.51 1.163.612.551 1.816 1.49 3.735 2.368a.571.571 0 0 1 .306.346l.939 3.286a.484.484 0 0 0 .469.347h1.47a.479.479 0 0 0 .469-.367l.47-1.817a.262.262 0 0 1 .326-.183c.571.122 1.183.163 1.795.163.613 0 1.225-.061 1.796-.163.143-.02.286.06.327.183l.47 1.817a.502.502 0 0 0 .468.367h1.47a.484.484 0 0 0 .47-.347l1.224-4.245a.487.487 0 0 1 .122-.224c.919-.98 1.53-2.163 1.735-3.47h.49c.53 0 .959-.448.938-.979-.02-.51-.47-.918-.98-.918h-.448C18.06 4.673 14.632 2 10.489 2c-1.775 0-3.428.49-4.755 1.326-.591-.408-1.469-.734-2.693-.632-.49.04-.715.612-.388.959.408.429.796 1 .877 1.735L1.857 7.612Zm3.122.98a.862.862 0 0 1-.857-.858c0-.469.388-.857.857-.857.47 0 .858.388.858.857 0 .47-.388.858-.858.858Z" />
</svg>

After

Width:  |  Height:  |  Size: 838 B

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
<path d="M19 18h-1v-8c0-.6-.4-1-1-1s-1 .4-1 1v8h-3V1c0-.6-.4-1-1-1s-1 .4-1 1v17H8V7c0-.6-.4-1-1-1s-1 .4-1 1v11H3V3c0-.6-.4-1-1-1s-1 .4-1 1v15c-.6 0-1 .4-1 1s.4 1 1 1h18c.6 0 1-.4 1-1s-.4-1-1-1z" />
</svg>

After

Width:  |  Height:  |  Size: 268 B

View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path d="M23 11.5a1.5 1.5 0 0 1-1.5 1.5h-20a1.5 1.5 0 0 1 0-3h20a1.5 1.5 0 0 1 1.5 1.5Z" />
<path d="M11.5 23a1.5 1.5 0 0 1-1.5-1.5v-20a1.5 1.5 0 0 1 3 0v20a1.5 1.5 0 0 1-1.5 1.5Z" />
</svg>

After

Width:  |  Height:  |  Size: 256 B

View File

@@ -28,6 +28,44 @@
"purpose": "maskable"
}
],
"shortcuts": [
{
"name": "Add Transaction",
"short_name": "Add Transaction",
"description": "Add a new transaction",
"url": "/transactions/new",
"icons": [
{
"src": "/shortcut-transaction.svg",
"sizes": "150x150"
}
]
},
{
"name": "Accounts",
"short_name": "Accounts",
"description": "View all accounts",
"url": "/accounts",
"icons": [
{
"src": "/shortcut-accounts.svg",
"sizes": "150x150"
}
]
},
{
"name": "Reports",
"short_name": "Reports",
"description": "View reports",
"url": "/reports",
"icons": [
{
"src": "/shortcut-reports.svg",
"sizes": "150x150"
}
]
}
],
"screenshots": [
{
"src": "/screenshot_wide.png",
@@ -43,7 +81,7 @@
"type": "image/png",
"sizes": "350x600"
}
],
],
"theme_color": "#8812E1",
"background_color": "#ffffff",
"display": "standalone",

View File

@@ -135,6 +135,14 @@ global.Actual = {
},
};
function inputFocused(e) {
return (
e.target.tagName === 'INPUT' ||
e.target.tagName === 'TEXTAREA' ||
e.target.isContentEditable
);
}
document.addEventListener('keydown', e => {
if (e.metaKey || e.ctrlKey) {
// Cmd/Ctrl+o
@@ -144,11 +152,7 @@ document.addEventListener('keydown', e => {
}
// Cmd/Ctrl+z
else if (e.key.toLowerCase() === 'z') {
if (
e.target.tagName === 'INPUT' ||
e.target.tagName === 'TEXTAREA' ||
e.target.isContentEditable
) {
if (inputFocused(e)) {
return;
}
e.preventDefault();
@@ -160,5 +164,10 @@ document.addEventListener('keydown', e => {
window.__actionsForMenu.undo();
}
}
} else if (e.key === '?') {
if (inputFocused(e)) {
return;
}
window.__actionsForMenu.pushModal('keyboard-shortcuts');
}
});

View File

@@ -6,6 +6,7 @@ import {
type FallbackProps,
} from 'react-error-boundary';
import { HotkeysProvider } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next';
import { useDispatch, useSelector } from 'react-redux';
import {
@@ -22,7 +23,7 @@ import {
send,
} from 'loot-core/src/platform/client/fetch';
import { useLocalPref } from '../hooks/useLocalPref';
import { useMetadataPref } from '../hooks/useMetadataPref';
import { installPolyfills } from '../polyfills';
import { ResponsiveProvider } from '../ResponsiveProvider';
import { styles, hasHiddenScrollbars, ThemeStyle } from '../style';
@@ -33,7 +34,6 @@ import { DevelopmentTopBar } from './DevelopmentTopBar';
import { FatalError } from './FatalError';
import { FinancesApp } from './FinancesApp';
import { ManagementApp } from './manager/ManagementApp';
import { MobileWebMessage } from './mobile/MobileWebMessage';
import { UpdateNotification } from './UpdateNotification';
type AppInnerProps = {
@@ -42,6 +42,7 @@ type AppInnerProps = {
};
function AppInner({ budgetId, cloudFileId }: AppInnerProps) {
const { t } = useTranslation();
const [initializing, setInitializing] = useState(true);
const { showBoundary: showErrorBoundary } = useErrorBoundary();
const loadingText = useSelector((state: State) => state.app.loadingText);
@@ -52,7 +53,7 @@ function AppInner({ budgetId, cloudFileId }: AppInnerProps) {
dispatch(
setAppState({
loadingText: 'Initializing the connection to the local database...',
loadingText: t('Initializing the connection to the local database...'),
}),
);
await initConnection(socketName);
@@ -60,7 +61,7 @@ function AppInner({ budgetId, cloudFileId }: AppInnerProps) {
// Load any global prefs
dispatch(
setAppState({
loadingText: 'Loading global preferences...',
loadingText: t('Loading global preferences...'),
}),
);
await dispatch(loadGlobalPrefs());
@@ -68,18 +69,20 @@ function AppInner({ budgetId, cloudFileId }: AppInnerProps) {
// Open the last opened budget, if any
dispatch(
setAppState({
loadingText: 'Opening last budget...',
loadingText: t('Opening last budget...'),
}),
);
const budgetId = await send('get-last-opened-backup');
if (budgetId) {
await dispatch(loadBudget(budgetId, 'Loading the last budget file...'));
await dispatch(
loadBudget(budgetId, t('Loading the last budget file...')),
);
// Check to see if this file has been remotely deleted (but
// don't block on this in case they are offline or something)
dispatch(
setAppState({
loadingText: 'Retrieving remote files...',
loadingText: t('Retrieving remote files...'),
}),
);
send('get-remote-files').then(files => {
@@ -124,7 +127,6 @@ function AppInner({ budgetId, cloudFileId }: AppInnerProps) {
))}
<UpdateNotification />
<MobileWebMessage />
</>
);
}
@@ -139,8 +141,8 @@ function ErrorFallback({ error }: FallbackProps) {
}
export function App() {
const [budgetId] = useLocalPref('id');
const [cloudFileId] = useLocalPref('cloudFileId');
const [budgetId] = useMetadataPref('id');
const [cloudFileId] = useMetadataPref('cloudFileId');
const [hiddenScrollbars, setHiddenScrollbars] = useState(
hasHiddenScrollbars(),
);

View File

@@ -3,7 +3,7 @@ import React, { useState, type ReactNode } from 'react';
import { LazyLoadFailedError } from 'loot-core/src/shared/errors';
import { Block } from './common/Block';
import { Button } from './common/Button';
import { Button } from './common/Button2';
import { Link } from './common/Link';
import { Modal } from './common/Modal';
import { Paragraph } from './common/Paragraph';
@@ -149,8 +149,8 @@ function SharedArrayBufferOverride() {
I understand the risks, run Actual in the unsupported fallback mode
</label>
<Button
disabled={!understand}
onClick={() => {
isDisabled={!understand}
onPress={() => {
window.localStorage.setItem('SharedArrayBufferOverride', 'true');
window.location.reload();
}}
@@ -191,7 +191,7 @@ export function FatalError({ error }: FatalErrorProps) {
)}
<Paragraph>
<Button onClick={() => window.Actual?.relaunch()}>Restart app</Button>
<Button onPress={() => window.Actual?.relaunch()}>Restart app</Button>
</Paragraph>
<Paragraph isLast={true} style={{ fontSize: 11 }}>
<Link variant="text" onClick={() => setShowError(state => !state)}>

View File

@@ -42,7 +42,7 @@ import { ScrollProvider } from './ScrollProvider';
import { Settings } from './settings';
import { FloatableSidebar } from './sidebar';
import { SidebarProvider } from './sidebar/SidebarProvider';
import { Titlebar, TitlebarProvider } from './Titlebar';
import { Titlebar } from './Titlebar';
function NarrowNotSupported({
redirectTo = '/budget',
@@ -246,15 +246,13 @@ export function FinancesApp() {
return (
<SpreadsheetProvider>
<TitlebarProvider>
<SidebarProvider>
<BudgetMonthCountProvider>
<DndProvider backend={Backend}>
<ScrollProvider>{app}</ScrollProvider>
</DndProvider>
</BudgetMonthCountProvider>
</SidebarProvider>
</TitlebarProvider>
<SidebarProvider>
<BudgetMonthCountProvider>
<DndProvider backend={Backend}>
<ScrollProvider>{app}</ScrollProvider>
</DndProvider>
</BudgetMonthCountProvider>
</SidebarProvider>
</SpreadsheetProvider>
);
}

View File

@@ -7,7 +7,7 @@ import { type State } from 'loot-core/src/client/state-types';
import { useActions } from '../hooks/useActions';
import { theme, styles, type CSSProperties } from '../style';
import { Button } from './common/Button';
import { Button } from './common/Button2';
import { Menu } from './common/Menu';
import { Popover } from './common/Popover';
import { Text } from './common/Text';
@@ -97,8 +97,8 @@ export function LoggedInUser({
<View style={{ flexDirection: 'row', alignItems: 'center', ...style }}>
<Button
ref={triggerRef}
type="bare"
onClick={() => setMenuOpen(true)}
variant="bare"
onPress={() => setMenuOpen(true)}
style={color && { color }}
>
{serverMessage()}

View File

@@ -13,6 +13,7 @@ import { pushModal } from 'loot-core/src/client/actions/modals';
import { initiallyLoadPayees } from 'loot-core/src/client/actions/queries';
import { send } from 'loot-core/src/platform/client/fetch';
import * as undo from 'loot-core/src/platform/client/undo';
import { getNormalisedString } from 'loot-core/src/shared/normalisation';
import { mapField, friendlyOp } from 'loot-core/src/shared/rules';
import { describeSchedule } from 'loot-core/src/shared/schedules';
import { type NewRuleEntity } from 'loot-core/src/types/models';
@@ -23,7 +24,7 @@ import { usePayees } from '../hooks/usePayees';
import { useSelected, SelectedProvider } from '../hooks/useSelected';
import { theme } from '../style';
import { Button } from './common/Button';
import { Button } from './common/Button2';
import { Link } from './common/Link';
import { Search } from './common/Search';
import { Stack } from './common/Stack';
@@ -78,6 +79,11 @@ function ruleToString(rule, data) {
data.payees.find(p => p.id === schedule._payee),
),
];
} else if (action.op === 'prepend-notes' || action.op === 'append-notes') {
return [
friendlyOp(action.op),
'“' + mapValue(action.field, action.value, data) + '”',
];
} else {
return [];
}
@@ -125,9 +131,9 @@ function ManageRulesContent({
(filter === ''
? allRules
: allRules.filter(rule =>
ruleToString(rule, filterData)
.toLowerCase()
.includes(filter.toLowerCase()),
getNormalisedString(ruleToString(rule, filterData)).includes(
getNormalisedString(filter),
),
)
).slice(0, 100 + page * 50),
[allRules, filter, filterData, page],
@@ -313,11 +319,11 @@ function ManageRulesContent({
>
<Stack direction="row" align="center" justify="flex-end" spacing={2}>
{selectedInst.items.size > 0 && (
<Button onClick={onDeleteSelected}>
<Button onPress={onDeleteSelected}>
Delete {selectedInst.items.size} rules
</Button>
)}
<Button type="primary" onClick={onCreateRule}>
<Button variant="primary" onPress={onCreateRule}>
Create new rule
</Button>
</Stack>

View File

@@ -1,17 +1,17 @@
// @ts-strict-ignore
import React, { useEffect } from 'react';
import { useSelector } from 'react-redux';
import { useDispatch } from 'react-redux';
import { useLocation } from 'react-router-dom';
import { type State } from 'loot-core/src/client/state-types';
import { closeModal } from 'loot-core/client/actions';
import { type PopModalAction } from 'loot-core/src/client/state-types/modals';
import { send } from 'loot-core/src/platform/client/fetch';
import * as monthUtils from 'loot-core/src/shared/months';
import { useActions } from '../hooks/useActions';
import { useModalState } from '../hooks/useModalState';
import { useSyncServerStatus } from '../hooks/useSyncServerStatus';
import { ModalTitle } from './common/Modal';
import { ModalTitle, ModalHeader } from './common/Modal2';
import { AccountAutocompleteModal } from './modals/AccountAutocompleteModal';
import { AccountMenuModal } from './modals/AccountMenuModal';
import { BudgetListModal } from './modals/BudgetListModal';
@@ -35,6 +35,7 @@ import { GoCardlessExternalMsg } from './modals/GoCardlessExternalMsg';
import { GoCardlessInitialise } from './modals/GoCardlessInitialise';
import { HoldBufferModal } from './modals/HoldBufferModal';
import { ImportTransactions } from './modals/ImportTransactions';
import { KeyboardShortcutModal } from './modals/KeyboardShortcutModal';
import { LoadBackup } from './modals/LoadBackup';
import { ManageRulesModal } from './modals/ManageRulesModal';
import { MergeUnusedPayees } from './modals/MergeUnusedPayees';
@@ -53,7 +54,6 @@ import { ScheduledTransactionMenuModal } from './modals/ScheduledTransactionMenu
import { SelectLinkedAccounts } from './modals/SelectLinkedAccounts';
import { SimpleFinInitialise } from './modals/SimpleFinInitialise';
import { SingleInputModal } from './modals/SingleInputModal';
import { SwitchBudgetTypeModal } from './modals/SwitchBudgetTypeModal';
import { TransferModal } from './modals/TransferModal';
import { DiscoverSchedules } from './schedules/DiscoverSchedules';
import { PostsOfflineNotification } from './schedules/PostsOfflineNotification';
@@ -71,64 +71,43 @@ export type CommonModalProps = {
};
export function Modals() {
const modalStack = useSelector((state: State) => state.modals.modalStack);
const isHidden = useSelector((state: State) => state.modals.isHidden);
const actions = useActions();
const location = useLocation();
const dispatch = useDispatch();
const { modalStack } = useModalState();
useEffect(() => {
if (modalStack.length > 0) {
actions.closeModal();
dispatch(closeModal());
}
}, [location]);
const syncServerStatus = useSyncServerStatus();
const modals = modalStack
.map(({ name, options }, idx) => {
const modalProps: CommonModalProps = {
onClose: actions.popModal,
onBack: actions.popModal,
showBack: idx > 0,
isCurrent: idx === modalStack.length - 1,
isHidden,
stackIndex: idx,
};
.map(({ name, options }) => {
switch (name) {
case 'keyboard-shortcuts':
return <KeyboardShortcutModal />;
case 'import-transactions':
return (
<ImportTransactions
key={name}
modalProps={modalProps}
options={options}
/>
);
return <ImportTransactions key={name} options={options} />;
case 'add-account':
return (
<CreateAccountModal
key={name}
modalProps={modalProps}
syncServerStatus={syncServerStatus}
upgradingAccountId={options?.upgradingAccountId}
/>
);
case 'add-local-account':
return (
<CreateLocalAccountModal
key={name}
modalProps={modalProps}
actions={actions}
/>
);
return <CreateLocalAccountModal key={name} />;
case 'close-account':
return (
<CloseAccountModal
key={name}
modalProps={modalProps}
account={options.account}
balance={options.balance}
canDelete={options.canDelete}
@@ -139,10 +118,8 @@ export function Modals() {
return (
<SelectLinkedAccounts
key={name}
modalProps={modalProps}
externalAccounts={options.accounts}
requisitionId={options.requisitionId}
actions={actions}
syncSource={options.syncSource}
/>
);
@@ -151,7 +128,6 @@ export function Modals() {
return (
<ConfirmCategoryDelete
key={name}
modalProps={modalProps}
category={options.category}
group={options.group}
onDelete={options.onDelete}
@@ -162,7 +138,6 @@ export function Modals() {
return (
<ConfirmUnlinkAccount
key={name}
modalProps={modalProps}
accountName={options.accountName}
onUnlink={options.onUnlink}
/>
@@ -172,7 +147,7 @@ export function Modals() {
return (
<ConfirmTransactionEdit
key={name}
modalProps={modalProps}
onCancel={options.onCancel}
onConfirm={options.onConfirm}
confirmReason={options.confirmReason}
/>
@@ -182,7 +157,7 @@ export function Modals() {
return (
<ConfirmTransactionDelete
key={name}
modalProps={modalProps}
message={options.message}
onConfirm={options.onConfirm}
/>
);
@@ -193,26 +168,17 @@ export function Modals() {
key={name}
watchUpdates
budgetId={options.budgetId}
modalProps={modalProps}
actions={actions}
backupDisabled={false}
/>
);
case 'manage-rules':
return (
<ManageRulesModal
key={name}
modalProps={modalProps}
payeeId={options?.payeeId}
/>
);
return <ManageRulesModal key={name} payeeId={options?.payeeId} />;
case 'edit-rule':
return (
<EditRule
key={name}
modalProps={modalProps}
defaultRule={options.rule}
onSave={options.onSave}
/>
@@ -222,7 +188,6 @@ export function Modals() {
return (
<MergeUnusedPayees
key={name}
modalProps={modalProps}
payeeIds={options.payeeIds}
targetPayeeId={options.targetPayeeId}
/>
@@ -230,27 +195,18 @@ export function Modals() {
case 'gocardless-init':
return (
<GoCardlessInitialise
key={name}
modalProps={modalProps}
onSuccess={options.onSuccess}
/>
<GoCardlessInitialise key={name} onSuccess={options.onSuccess} />
);
case 'simplefin-init':
return (
<SimpleFinInitialise
key={name}
modalProps={modalProps}
onSuccess={options.onSuccess}
/>
<SimpleFinInitialise key={name} onSuccess={options.onSuccess} />
);
case 'gocardless-external-msg':
return (
<GoCardlessExternalMsg
key={name}
modalProps={modalProps}
onMoveExternal={options.onMoveExternal}
onClose={() => {
options.onClose?.();
@@ -261,28 +217,15 @@ export function Modals() {
);
case 'create-encryption-key':
return (
<CreateEncryptionKeyModal
key={name}
modalProps={modalProps}
options={options}
/>
);
return <CreateEncryptionKeyModal key={name} options={options} />;
case 'fix-encryption-key':
return (
<FixEncryptionKeyModal
key={name}
modalProps={modalProps}
options={options}
/>
);
return <FixEncryptionKeyModal key={name} options={options} />;
case 'edit-field':
return (
<EditField
key={name}
modalProps={modalProps}
name={options.name}
onSubmit={options.onSubmit}
onClose={options.onClose}
@@ -293,7 +236,6 @@ export function Modals() {
return (
<CategoryAutocompleteModal
key={name}
modalProps={modalProps}
autocompleteProps={{
value: null,
onSelect: options.onSelect,
@@ -309,7 +251,6 @@ export function Modals() {
return (
<AccountAutocompleteModal
key={name}
modalProps={modalProps}
autocompleteProps={{
value: null,
onSelect: options.onSelect,
@@ -323,7 +264,6 @@ export function Modals() {
return (
<PayeeAutocompleteModal
key={name}
modalProps={modalProps}
autocompleteProps={{
value: null,
onSelect: options.onSelect,
@@ -336,8 +276,13 @@ export function Modals() {
return (
<SingleInputModal
key={name}
modalProps={modalProps}
title={<ModalTitle title="New Category" shrinkOnOverflow />}
name={name}
Header={props => (
<ModalHeader
{...props}
title={<ModalTitle title="New Category" shrinkOnOverflow />}
/>
)}
inputPlaceholder="Category name"
buttonText="Add"
onValidate={options.onValidate}
@@ -349,8 +294,15 @@ export function Modals() {
return (
<SingleInputModal
key={name}
modalProps={modalProps}
title={<ModalTitle title="New Category Group" shrinkOnOverflow />}
name={name}
Header={props => (
<ModalHeader
{...props}
title={
<ModalTitle title="New Category Group" shrinkOnOverflow />
}
/>
)}
inputPlaceholder="Category group name"
buttonText="Add"
onValidate={options.onValidate}
@@ -366,7 +318,6 @@ export function Modals() {
>
<RolloverBudgetSummaryModal
key={name}
modalProps={modalProps}
month={options.month}
onBudgetAction={options.onBudgetAction}
/>
@@ -374,21 +325,13 @@ export function Modals() {
);
case 'report-budget-summary':
return (
<ReportBudgetSummaryModal
key={name}
modalProps={modalProps}
month={options.month}
/>
);
return <ReportBudgetSummaryModal key={name} month={options.month} />;
case 'schedule-edit':
return (
<ScheduleDetails
key={name}
modalProps={modalProps}
id={options?.id || null}
actions={actions}
transaction={options?.transaction || null}
/>
);
@@ -397,46 +340,23 @@ export function Modals() {
return (
<ScheduleLink
key={name}
modalProps={modalProps}
actions={actions}
transactionIds={options?.transactionIds}
getTransaction={options?.getTransaction}
pushModal={options?.pushModal}
accountName={options?.accountName}
onScheduleLinked={options?.onScheduleLinked}
/>
);
case 'schedules-discover':
return (
<DiscoverSchedules
key={name}
modalProps={modalProps}
actions={actions}
/>
);
return <DiscoverSchedules key={name} />;
case 'schedule-posts-offline-notification':
return (
<PostsOfflineNotification
key={name}
modalProps={modalProps}
actions={actions}
/>
);
case 'switch-budget-type':
return (
<SwitchBudgetTypeModal
key={name}
modalProps={modalProps}
onSwitch={options.onSwitch}
/>
);
return <PostsOfflineNotification key={name} />;
case 'account-menu':
return (
<AccountMenuModal
key={name}
modalProps={modalProps}
accountId={options.accountId}
onSave={options.onSave}
onEditNotes={options.onEditNotes}
@@ -450,11 +370,11 @@ export function Modals() {
return (
<CategoryMenuModal
key={name}
modalProps={modalProps}
categoryId={options.categoryId}
onSave={options.onSave}
onEditNotes={options.onEditNotes}
onDelete={options.onDelete}
onToggleVisibility={options.onToggleVisibility}
onClose={options.onClose}
/>
);
@@ -466,7 +386,6 @@ export function Modals() {
value={monthUtils.sheetForMonth(options.month)}
>
<RolloverBudgetMenuModal
modalProps={modalProps}
categoryId={options.categoryId}
onUpdateBudget={options.onUpdateBudget}
onCopyLastMonthAverage={options.onCopyLastMonthAverage}
@@ -483,7 +402,6 @@ export function Modals() {
value={monthUtils.sheetForMonth(options.month)}
>
<ReportBudgetMenuModal
modalProps={modalProps}
categoryId={options.categoryId}
onUpdateBudget={options.onUpdateBudget}
onCopyLastMonthAverage={options.onCopyLastMonthAverage}
@@ -497,13 +415,13 @@ export function Modals() {
return (
<CategoryGroupMenuModal
key={name}
modalProps={modalProps}
groupId={options.groupId}
onSave={options.onSave}
onAddCategory={options.onAddCategory}
onEditNotes={options.onEditNotes}
onSaveNotes={options.onSaveNotes}
onDelete={options.onDelete}
onToggleVisibility={options.onToggleVisibility}
onClose={options.onClose}
/>
);
@@ -512,7 +430,6 @@ export function Modals() {
return (
<NotesModal
key={name}
modalProps={modalProps}
id={options.id}
name={options.name}
onSave={options.onSave}
@@ -526,7 +443,6 @@ export function Modals() {
value={monthUtils.sheetForMonth(options.month)}
>
<RolloverBalanceMenuModal
modalProps={modalProps}
categoryId={options.categoryId}
onCarryover={options.onCarryover}
onTransfer={options.onTransfer}
@@ -542,7 +458,6 @@ export function Modals() {
value={monthUtils.sheetForMonth(options.month)}
>
<RolloverToBudgetMenuModal
modalProps={modalProps}
onTransfer={options.onTransfer}
onCover={options.onCover}
onHoldBuffer={options.onHoldBuffer}
@@ -558,7 +473,6 @@ export function Modals() {
value={monthUtils.sheetForMonth(options.month)}
>
<HoldBufferModal
modalProps={modalProps}
month={options.month}
onSubmit={options.onSubmit}
/>
@@ -572,7 +486,6 @@ export function Modals() {
value={monthUtils.sheetForMonth(options.month)}
>
<ReportBalanceMenuModal
modalProps={modalProps}
categoryId={options.categoryId}
onCarryover={options.onCarryover}
/>
@@ -583,7 +496,6 @@ export function Modals() {
return (
<TransferModal
key={name}
modalProps={modalProps}
title={options.title}
month={options.month}
amount={options.amount}
@@ -596,10 +508,10 @@ export function Modals() {
return (
<CoverModal
key={name}
modalProps={modalProps}
title={options.title}
month={options.month}
showToBeBudgeted={options.showToBeBudgeted}
category={options.category}
onSubmit={options.onSubmit}
/>
);
@@ -608,7 +520,6 @@ export function Modals() {
return (
<ScheduledTransactionMenuModal
key={name}
modalProps={modalProps}
transactionId={options.transactionId}
onPost={options.onPost}
onSkip={options.onSkip}
@@ -619,11 +530,9 @@ export function Modals() {
return (
<BudgetPageMenuModal
key={name}
modalProps={modalProps}
onAddCategoryGroup={options.onAddCategoryGroup}
onToggleHiddenCategories={options.onToggleHiddenCategories}
onSwitchBudgetFile={options.onSwitchBudgetFile}
onSwitchBudgetType={options.onSwitchBudgetType}
/>
);
@@ -634,7 +543,6 @@ export function Modals() {
value={monthUtils.sheetForMonth(options.month)}
>
<RolloverBudgetMonthMenuModal
modalProps={modalProps}
month={options.month}
onBudgetAction={options.onBudgetAction}
onEditNotes={options.onEditNotes}
@@ -649,7 +557,6 @@ export function Modals() {
value={monthUtils.sheetForMonth(options.month)}
>
<ReportBudgetMonthMenuModal
modalProps={modalProps}
month={options.month}
onBudgetAction={options.onBudgetAction}
onEditNotes={options.onEditNotes}
@@ -658,7 +565,7 @@ export function Modals() {
);
case 'budget-list':
return <BudgetListModal key={name} modalProps={modalProps} />;
return <BudgetListModal key={name} />;
default:
console.error('Unknown modal:', name);

View File

@@ -6,7 +6,7 @@ import { useNotes } from '../hooks/useNotes';
import { SvgCustomNotesPaper } from '../icons/v2';
import { type CSSProperties, theme } from '../style';
import { Button } from './common/Button';
import { Button } from './common/Button2';
import { Popover } from './common/Popover';
import { Tooltip } from './common/Tooltip';
import { View } from './common/View';
@@ -52,7 +52,7 @@ export function NotesButton({
<View style={{ flexShrink: 0 }}>
<Button
ref={triggerRef}
type="bare"
variant="bare"
aria-label="View notes"
className={!hasNotes && !isOpen ? 'hover-visible' : ''}
style={{
@@ -61,8 +61,7 @@ export function NotesButton({
...(hasNotes && { display: 'flex !important' }),
...(isOpen && { color: theme.buttonNormalText }),
}}
onClick={event => {
event.stopPropagation();
onPress={() => {
setIsOpen(true);
}}
>

View File

@@ -5,17 +5,18 @@ import React, {
useMemo,
type SetStateAction,
} from 'react';
import { useSelector } from 'react-redux';
import { useDispatch, useSelector } from 'react-redux';
import { removeNotification } from 'loot-core/client/actions';
import { type State } from 'loot-core/src/client/state-types';
import type { NotificationWithId } from 'loot-core/src/client/state-types/notifications';
import { useActions } from '../hooks/useActions';
import { AnimatedLoading } from '../icons/AnimatedLoading';
import { SvgDelete } from '../icons/v0';
import { useResponsive } from '../ResponsiveProvider';
import { styles, theme, type CSSProperties } from '../style';
import { Button, ButtonWithLoading } from './common/Button';
import { Button, ButtonWithLoading } from './common/Button2';
import { Link } from './common/Link';
import { Stack } from './common/Stack';
import { Text } from './common/Text';
@@ -119,6 +120,11 @@ function Notification({
[message, messageActions],
);
const { isNarrowWidth } = useResponsive();
const narrowStyle: CSSProperties = isNarrowWidth
? { minHeight: styles.mobileMinHeight }
: {};
return (
<View
style={{
@@ -132,10 +138,11 @@ function Notification({
>
<Stack
align="center"
justify="space-between"
direction="row"
style={{
padding: '14px 14px',
fontSize: 14,
...styles.mediumText,
backgroundColor: positive
? theme.noticeBackgroundLight
: error
@@ -155,7 +162,15 @@ function Notification({
>
<Stack align="flex-start">
{title && (
<View style={{ fontWeight: 700, marginBottom: 10 }}>{title}</View>
<View
style={{
...styles.mediumText,
fontWeight: 700,
marginBottom: 10,
}}
>
{title}
</View>
)}
<View>{processedMessage}</View>
{pre
@@ -177,15 +192,15 @@ function Notification({
: null}
{button && (
<ButtonWithLoading
type="bare"
loading={loading}
onClick={async () => {
variant="bare"
isLoading={loading}
onPress={async () => {
setLoading(true);
await button.action();
onRemove();
setLoading(false);
}}
style={{
style={({ isHovered, isPressed }) => ({
backgroundColor: 'transparent',
border: `1px solid ${
positive
@@ -195,31 +210,32 @@ function Notification({
: theme.warningBorder
}`,
color: 'currentColor',
fontSize: 14,
...styles.mediumText,
flexShrink: 0,
'&:hover, &:active': {
backgroundColor: positive
? theme.noticeBackground
: error
? theme.errorBackground
: theme.warningBackground,
},
}}
...(isHovered || isPressed
? {
backgroundColor: positive
? theme.noticeBackground
: error
? theme.errorBackground
: theme.warningBackground,
}
: {}),
...narrowStyle,
})}
>
{button.title}
</ButtonWithLoading>
)}
</Stack>
{sticky && (
<Button
type="bare"
aria-label="Close"
style={{ flexShrink: 0, color: 'currentColor' }}
onClick={onRemove}
>
<SvgDelete style={{ width: 9, height: 9, color: 'currentColor' }} />
</Button>
)}
<Button
variant="bare"
aria-label="Close"
style={{ flexShrink: 0, color: 'currentColor' }}
onPress={onRemove}
>
<SvgDelete style={{ width: 9, height: 9, color: 'currentColor' }} />
</Button>
</Stack>
{overlayLoading && (
<View
@@ -244,16 +260,22 @@ function Notification({
}
export function Notifications({ style }: { style?: CSSProperties }) {
const { removeNotification } = useActions();
const dispatch = useDispatch();
const { isNarrowWidth } = useResponsive();
const notifications = useSelector(
(state: State) => state.notifications.notifications,
);
const notificationInset = useSelector(
(state: State) => state.notifications.inset,
);
return (
<View
style={{
position: 'fixed',
bottom: 20,
right: 13,
bottom: notificationInset?.bottom || 20,
top: notificationInset?.top,
right: notificationInset?.right || 13,
left: notificationInset?.left || (isNarrowWidth ? 13 : undefined),
zIndex: 10000,
...style,
}}
@@ -266,7 +288,7 @@ export function Notifications({ style }: { style?: CSSProperties }) {
if (note.onClose) {
note.onClose();
}
removeNotification(note.id);
dispatch(removeNotification(note.id));
}}
/>
))}

View File

@@ -6,7 +6,7 @@ import { SvgMoonStars, SvgSun, SvgSystem } from '../icons/v2';
import { useResponsive } from '../ResponsiveProvider';
import { type CSSProperties, themeOptions, useTheme } from '../style';
import { Button } from './common/Button';
import { Button } from './common/Button2';
import { Menu } from './common/Menu';
import { Popover } from './common/Popover';
@@ -44,9 +44,9 @@ export function ThemeSelector({ style }: ThemeSelectorProps) {
<>
<Button
ref={triggerRef}
type="bare"
variant="bare"
aria-label="Switch theme"
onClick={() => setMenuOpen(true)}
onPress={() => setMenuOpen(true)}
style={style}
>
<Icon style={{ width: 13, height: 13, color: 'inherit' }} />

View File

@@ -1,11 +1,4 @@
import React, {
createContext,
useState,
useEffect,
useRef,
useContext,
type ReactNode,
} from 'react';
import React, { useState, useEffect } from 'react';
import { useHotkeys } from 'react-hotkeys-hook';
import { Routes, Route, useLocation } from 'react-router-dom';
@@ -13,13 +6,12 @@ import * as Platform from 'loot-core/src/client/platform';
import * as queries from 'loot-core/src/client/queries';
import { listen } from 'loot-core/src/platform/client/fetch';
import { isDevelopmentEnvironment } from 'loot-core/src/shared/environment';
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 { useMetadataPref } from '../hooks/useMetadataPref';
import { useNavigate } from '../hooks/useNavigate';
import { useSyncedPref } from '../hooks/useSyncedPref';
import { SvgArrowLeft } from '../icons/v1';
import {
SvgAlertTriangle,
@@ -33,10 +25,8 @@ import { theme, type CSSProperties, styles } from '../style';
import { AccountSyncCheck } from './accounts/AccountSyncCheck';
import { AnimatedRefresh } from './AnimatedRefresh';
import { MonthCountSelector } from './budget/MonthCountSelector';
import { Button, ButtonWithLoading } from './common/Button';
import { Button } from './common/Button2';
import { Link } from './common/Link';
import { Paragraph } from './common/Paragraph';
import { Popover } from './common/Popover';
import { Text } from './common/Text';
import { View } from './common/View';
import { LoggedInUser } from './LoggedInUser';
@@ -45,55 +35,6 @@ import { useSidebar } from './sidebar/SidebarProvider';
import { useSheetValue } from './spreadsheet/useSheetValue';
import { ThemeSelector } from './ThemeSelector';
export const SWITCH_BUDGET_MESSAGE_TYPE = 'budget/switch-type';
type SwitchBudgetTypeMessage = {
type: typeof SWITCH_BUDGET_MESSAGE_TYPE;
payload: {
newBudgetType: LocalPrefs['budgetType'];
};
};
export type TitlebarMessage = SwitchBudgetTypeMessage;
type Listener = (msg: TitlebarMessage) => void;
export type TitlebarContextValue = {
sendEvent: (msg: TitlebarMessage) => void;
subscribe: (listener: Listener) => () => void;
};
export const TitlebarContext = createContext<TitlebarContextValue>({
sendEvent() {
throw new Error('TitlebarContext not initialized');
},
subscribe() {
throw new Error('TitlebarContext not initialized');
},
});
type TitlebarProviderProps = {
children?: ReactNode;
};
export function TitlebarProvider({ children }: TitlebarProviderProps) {
const listeners = useRef<Listener[]>([]);
function sendEvent(msg: TitlebarMessage) {
listeners.current.forEach(func => func(msg));
}
function subscribe(listener: Listener) {
listeners.current.push(listener);
return () =>
(listeners.current = listeners.current.filter(func => func !== listener));
}
return (
<TitlebarContext.Provider value={{ sendEvent, subscribe }}>
{children}
</TitlebarContext.Provider>
);
}
function UncategorizedButton() {
const count: number | null = useSheetValue(queries.uncategorizedCount());
if (count === null || count <= 0) {
@@ -120,15 +61,27 @@ type PrivacyButtonProps = {
function PrivacyButton({ style }: PrivacyButtonProps) {
const [isPrivacyEnabled, setPrivacyEnabledPref] =
useLocalPref('isPrivacyEnabled');
useSyncedPref('isPrivacyEnabled');
const privacyIconStyle = { width: 15, height: 15 };
useHotkeys(
'shift+ctrl+p, shift+cmd+p, shift+meta+p',
() => {
setPrivacyEnabledPref(!isPrivacyEnabled);
},
{
preventDefault: true,
scopes: ['app'],
},
[setPrivacyEnabledPref, isPrivacyEnabled],
);
return (
<Button
type="bare"
variant="bare"
aria-label={`${isPrivacyEnabled ? 'Disable' : 'Enable'} privacy mode`}
onClick={() => setPrivacyEnabledPref(!isPrivacyEnabled)}
onPress={() => setPrivacyEnabledPref(!isPrivacyEnabled)}
style={style}
>
{isPrivacyEnabled ? (
@@ -145,7 +98,7 @@ type SyncButtonProps = {
isMobile?: boolean;
};
function SyncButton({ style, isMobile = false }: SyncButtonProps) {
const [cloudFileId] = useLocalPref('cloudFileId');
const [cloudFileId] = useMetadataPref('cloudFileId');
const { sync } = useActions();
const [syncing, setSyncing] = useState(false);
@@ -244,10 +197,10 @@ function SyncButton({ style, isMobile = false }: SyncButtonProps) {
return (
<Button
type="bare"
variant="bare"
aria-label="Sync"
style={
isMobile
style={({ isHovered, isPressed }) => ({
...(isMobile
? {
...style,
WebkitAppRegion: 'none',
@@ -257,11 +210,11 @@ function SyncButton({ style, isMobile = false }: SyncButtonProps) {
...style,
WebkitAppRegion: 'none',
color: desktopColor,
}
}
hoveredStyle={hoveredStyle}
activeStyle={activeStyle}
onClick={sync}
}),
...(isHovered ? hoveredStyle : {}),
...(isPressed ? activeStyle : {}),
})}
onPress={sync}
>
{isMobile ? (
syncState === 'error' ? (
@@ -287,31 +240,6 @@ function SyncButton({ style, isMobile = false }: SyncButtonProps) {
function BudgetTitlebar() {
const [maxMonths, setMaxMonthsPref] = useGlobalPref('maxMonths');
const [budgetType] = useLocalPref('budgetType');
const { sendEvent } = useContext(TitlebarContext);
const [loading, setLoading] = useState(false);
const [showPopover, setShowPopover] = useState(false);
const triggerRef = useRef(null);
const reportBudgetEnabled = useFeatureFlag('reportBudget');
function onSwitchType() {
setLoading(true);
if (!loading) {
const newBudgetType = budgetType === 'rollover' ? 'report' : 'rollover';
sendEvent({
type: SWITCH_BUDGET_MESSAGE_TYPE,
payload: {
newBudgetType,
},
});
}
}
useEffect(() => {
setLoading(false);
}, [budgetType]);
return (
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
@@ -319,61 +247,6 @@ function BudgetTitlebar() {
maxMonths={maxMonths || 1}
onChange={value => setMaxMonthsPref(value)}
/>
{reportBudgetEnabled && (
<View style={{ marginLeft: -5 }}>
<ButtonWithLoading
ref={triggerRef}
type="bare"
loading={loading}
style={{
alignSelf: 'flex-start',
padding: '4px 7px',
}}
title="Learn more about budgeting"
onClick={() => setShowPopover(true)}
>
{budgetType === 'report' ? 'Report budget' : 'Rollover budget'}
</ButtonWithLoading>
<Popover
triggerRef={triggerRef}
placement="bottom start"
isOpen={showPopover}
onOpenChange={() => setShowPopover(false)}
style={{
padding: 10,
maxWidth: 400,
}}
>
<Paragraph>
You are currently using a{' '}
<Text style={{ fontWeight: 600 }}>
{budgetType === 'report' ? 'Report budget' : 'Rollover budget'}.
</Text>{' '}
Switching will not lose any data and you can always switch back.
</Paragraph>
<Paragraph>
<ButtonWithLoading
type="primary"
loading={loading}
onClick={onSwitchType}
>
Switch to a{' '}
{budgetType === 'report' ? 'Rollover budget' : 'Report budget'}
</ButtonWithLoading>
</Paragraph>
<Paragraph isLast={true}>
<Link
variant="external"
to="https://actualbudget.org/docs/experimental/report-budget"
linkColor="muted"
>
How do these types of budgeting work?
</Link>
</Paragraph>
</Popover>
</View>
)}
</View>
);
}
@@ -409,19 +282,15 @@ export function Titlebar({ style }: TitlebarProps) {
>
{(floatingSidebar || sidebar.alwaysFloats) && (
<Button
type="bare"
aria-label="Sidebar menu"
variant="bare"
style={{ marginRight: 8 }}
onPointerEnter={e => {
onHoverStart={e => {
if (e.pointerType === 'mouse') {
sidebar.setHidden(false);
}
}}
onPointerLeave={e => {
if (e.pointerType === 'mouse') {
sidebar.setHidden(true);
}
}}
onPointerUp={e => {
onPress={e => {
if (e.pointerType !== 'mouse') {
sidebar.setHidden(!sidebar.hidden);
}
@@ -439,7 +308,7 @@ export function Titlebar({ style }: TitlebarProps) {
path="/accounts"
element={
location.state?.goBack ? (
<Button type="bare" onClick={() => navigate(-1)}>
<Button variant="bare" onPress={() => navigate(-1)}>
<SvgArrowLeft
width={10}
height={10}

View File

@@ -7,7 +7,7 @@ import { useActions } from '../hooks/useActions';
import { SvgClose } from '../icons/v1';
import { theme } from '../style';
import { Button } from './common/Button';
import { Button } from './common/Button2';
import { Link } from './common/Link';
import { Text } from './common/Text';
import { View } from './common/View';
@@ -72,10 +72,10 @@ export function UpdateNotification() {
</Link>
)
<Button
type="bare"
variant="bare"
aria-label="Close"
style={{ display: 'inline', padding: '1px 7px 2px 7px' }}
onClick={() => {
onPress={() => {
// Set a flag to never show an update notification again for this session
setAppState({
updateInfo: null,

File diff suppressed because it is too large Load Diff

View File

@@ -7,7 +7,7 @@ import { useAccounts } from '../../hooks/useAccounts';
import { useActions } from '../../hooks/useActions';
import { SvgExclamationOutline } from '../../icons/v1';
import { theme } from '../../style';
import { Button } from '../common/Button';
import { Button } from '../common/Button2';
import { Link } from '../common/Link';
import { Popover } from '../common/Popover';
import { View } from '../common/View';
@@ -39,7 +39,15 @@ function getErrorMessage(type, code) {
return 'Your SimpleFIN Access Token is no longer valid. Please reset and generate a new token.';
case 'ACCOUNT_NEEDS_ATTENTION':
return 'The account needs your attention at [SimpleFIN](https://beta-bridge.simplefin.org/auth/login).';
return (
<>
The account needs your attention at{' '}
<Link variant="external" to="https://bridge.simplefin.org/auth/login">
SimpleFIN
</Link>
.
</>
);
default:
}
@@ -94,7 +102,7 @@ export function AccountSyncCheck() {
<View>
<Button
ref={triggerRef}
type="bare"
variant="bare"
style={{
flexDirection: 'row',
alignItems: 'center',
@@ -103,7 +111,7 @@ export function AccountSyncCheck() {
padding: '4px 8px',
borderRadius: 4,
}}
onClick={() => setOpen(true)}
onPress={() => setOpen(true)}
>
<SvgExclamationOutline
style={{ width: 14, height: 14, marginRight: 5 }}
@@ -129,13 +137,18 @@ export function AccountSyncCheck() {
<View style={{ justifyContent: 'flex-end', flexDirection: 'row' }}>
{showAuth ? (
<>
<Button onClick={unlink}>Unlink</Button>
<Button type="primary" onClick={reauth} style={{ marginLeft: 5 }}>
<Button onPress={unlink}>Unlink</Button>
<Button
variant="primary"
autoFocus
onPress={reauth}
style={{ marginLeft: 5 }}
>
Reauthorize
</Button>
</>
) : (
<Button onClick={unlink}>Unlink account</Button>
<Button onPress={unlink}>Unlink account</Button>
)}
</View>
</Popover>

View File

@@ -1,4 +1,6 @@
import React from 'react';
import React, { useRef } from 'react';
import { useHover } from 'usehooks-ts';
import { isPreviewId } from 'loot-core/shared/transactions';
import { useCachedSchedules } from 'loot-core/src/client/data-hooks/schedules';
@@ -8,7 +10,7 @@ import { getScheduledAmount } from 'loot-core/src/shared/schedules';
import { useSelectedItems } from '../../hooks/useSelected';
import { SvgArrowButtonRight1 } from '../../icons/v2';
import { theme } from '../../style';
import { Button } from '../common/Button';
import { Button } from '../common/Button2';
import { Text } from '../common/Text';
import { View } from '../common/View';
import { PrivacyFilter } from '../PrivacyFilter';
@@ -137,10 +139,12 @@ export function Balances({
showExtraBalances,
onToggleExtraBalances,
account,
filteredItems,
isFiltered,
filteredAmount,
}) {
const selectedItems = useSelectedItems();
const buttonRef = useRef(null);
const isButtonHovered = useHover(buttonRef);
return (
<View
@@ -152,14 +156,11 @@ export function Balances({
}}
>
<Button
ref={buttonRef}
data-testid="account-balance"
type="bare"
onClick={onToggleExtraBalances}
variant="bare"
onPress={onToggleExtraBalances}
style={{
'& svg': {
opacity: selectedItems.size > 0 || showExtraBalances ? 1 : 0,
},
'&:hover svg': { opacity: 1 },
paddingTop: 1,
paddingBottom: 1,
}}
@@ -188,6 +189,10 @@ export function Balances({
marginLeft: 10,
color: theme.pillText,
transform: showExtraBalances ? 'rotateZ(180deg)' : 'rotateZ(0)',
opacity:
isButtonHovered || selectedItems.size > 0 || showExtraBalances
? 1
: 0,
}}
/>
</Button>
@@ -196,9 +201,7 @@ export function Balances({
{selectedItems.size > 0 && (
<SelectedBalance selectedItems={selectedItems} account={account} />
)}
{filteredItems.length > 0 && (
<FilteredBalance filteredAmount={filteredAmount} />
)}
{isFiltered && <FilteredBalance filteredAmount={filteredAmount} />}
</View>
);
}

View File

@@ -14,7 +14,7 @@ import {
} from '../../icons/v2';
import { theme, styles } from '../../style';
import { AnimatedRefresh } from '../AnimatedRefresh';
import { Button } from '../common/Button';
import { Button } from '../common/Button2';
import { InitialFocus } from '../common/InitialFocus';
import { Input } from '../common/Input';
import { Menu } from '../common/Menu';
@@ -26,13 +26,12 @@ import { View } from '../common/View';
import { FilterButton } from '../filters/FiltersMenu';
import { FiltersStack } from '../filters/FiltersStack';
import { NotesButton } from '../NotesButton';
import { SelectedTransactionsButton } from '../transactions/SelectedTransactions';
import { SelectedTransactionsButton } from '../transactions/SelectedTransactionsButton';
import { Balances } from './Balance';
import { ReconcilingMessage, ReconcileMenu } from './Reconcile';
export function AccountHeader({
filteredAmount,
tableRef,
editingName,
isNameEditable,
@@ -40,7 +39,7 @@ export function AccountHeader({
accountName,
account,
filterId,
filtersList,
savedFilters,
accountsSyncing,
failedAccounts,
accounts,
@@ -53,10 +52,12 @@ export function AccountHeader({
balanceQuery,
reconcileAmount,
canCalculateBalance,
isFiltered,
filteredAmount,
isSorted,
search,
filters,
conditionsOp,
filterConditions,
filterConditionsOp,
pushModal,
onSearch,
onAddTransaction,
@@ -73,16 +74,19 @@ export function AccountHeader({
onBatchDelete,
onBatchDuplicate,
onBatchEdit,
onBatchUnlink,
onBatchLinkSchedule,
onBatchUnlinkSchedule,
onCreateRule,
onApplyFilter,
onUpdateFilter,
onClearFilters,
onReloadSavedFilter,
onCondOpChange,
onConditionsOpChange,
onDeleteFilter,
onScheduleAction,
onSetTransfer,
onMakeAsSplitTransaction,
onMakeAsNonSplitTransactions,
}) {
const [menuOpen, setMenuOpen] = useState(false);
const searchInput = useRef(null);
@@ -127,6 +131,33 @@ export function AccountHeader({
},
[searchInput],
);
useHotkeys(
't',
() => onAddTransaction(),
{
preventDefault: true,
scopes: ['app'],
},
[onAddTransaction],
);
useHotkeys(
'ctrl+i, cmd+i, meta+i',
() => onImport(),
{
scopes: ['app'],
},
[onImport],
);
useHotkeys(
'ctrl+b, cmd+b, meta+b',
() => onSync(),
{
enabled: canSync && !isServerOffline,
preventDefault: true,
scopes: ['app'],
},
[onSync],
);
return (
<>
@@ -211,10 +242,10 @@ export function AccountHeader({
/>
)}
<Button
type="bare"
variant="bare"
aria-label="Edit account name"
className="hover-visible"
onClick={() => onExposeName(true)}
onPress={() => onExposeName(true)}
>
<SvgPencil1
style={{
@@ -243,7 +274,7 @@ export function AccountHeader({
showExtraBalances={showExtraBalances}
onToggleExtraBalances={onToggleExtraBalances}
account={account}
filteredItems={filters}
isFiltered={isFiltered}
filteredAmount={filteredAmount}
/>
@@ -255,9 +286,9 @@ export function AccountHeader({
>
{((account && !account.closed) || canSync) && (
<Button
type="bare"
onClick={canSync ? onSync : onImport}
disabled={canSync && isServerOffline}
variant="bare"
onPress={canSync ? onSync : onImport}
isDisabled={canSync && isServerOffline}
>
{canSync ? (
<>
@@ -286,7 +317,7 @@ export function AccountHeader({
</Button>
)}
{!showEmptyMessage && (
<Button type="bare" onClick={onAddTransaction}>
<Button variant="bare" onPress={onAddTransaction}>
<SvgAdd width={10} height={10} style={{ marginRight: 3 }} /> Add
New
</Button>
@@ -307,39 +338,55 @@ export function AccountHeader({
</View>
) : (
<SelectedTransactionsButton
account={account}
getTransaction={id => transactions.find(t => t.id === id)}
onShow={onShowTransactions}
onDuplicate={onBatchDuplicate}
onDelete={onBatchDelete}
onEdit={onBatchEdit}
onUnlink={onBatchUnlink}
onLinkSchedule={onBatchLinkSchedule}
onUnlinkSchedule={onBatchUnlinkSchedule}
onCreateRule={onCreateRule}
onSetTransfer={onSetTransfer}
onScheduleAction={onScheduleAction}
pushModal={pushModal}
showMakeTransfer={showMakeTransfer}
onMakeAsSplitTransaction={onMakeAsSplitTransaction}
onMakeAsNonSplitTransactions={onMakeAsNonSplitTransactions}
/>
)}
<Button
type="bare"
disabled={search !== '' || filters.length > 0}
style={{ padding: 6, marginLeft: 10 }}
onClick={onToggleSplits}
title={
variant="bare"
aria-label={
splitsExpanded.state.mode === 'collapse'
? 'Collapse split transactions'
: 'Expand split transactions'
}
isDisabled={search !== '' || filterConditions.length > 0}
style={{ padding: 6, marginLeft: 10 }}
onPress={onToggleSplits}
>
{splitsExpanded.state.mode === 'collapse' ? (
<SvgArrowsShrink3 style={{ width: 14, height: 14 }} />
) : (
<SvgArrowsExpand3 style={{ width: 14, height: 14 }} />
)}
<View
title={
splitsExpanded.state.mode === 'collapse'
? 'Collapse split transactions'
: 'Expand split transactions'
}
>
{splitsExpanded.state.mode === 'collapse' ? (
<SvgArrowsShrink3 style={{ width: 14, height: 14 }} />
) : (
<SvgArrowsExpand3 style={{ width: 14, height: 14 }} />
)}
</View>
</Button>
{account ? (
<View>
<MenuButton ref={triggerRef} onClick={() => setMenuOpen(true)} />
<MenuButton
aria-label="Account menu"
ref={triggerRef}
onPress={() => setMenuOpen(true)}
/>
<Popover
triggerRef={triggerRef}
@@ -366,7 +413,11 @@ export function AccountHeader({
</View>
) : (
<View>
<MenuButton ref={triggerRef} onClick={() => setMenuOpen(true)} />
<MenuButton
aria-label="Account menu"
ref={triggerRef}
onPress={() => setMenuOpen(true)}
/>
<Popover
triggerRef={triggerRef}
@@ -391,17 +442,17 @@ export function AccountHeader({
)}
</Stack>
{filters && filters.length > 0 && (
{filterConditions?.length > 0 && (
<FiltersStack
filters={filters}
conditionsOp={conditionsOp}
conditions={filterConditions}
conditionsOp={filterConditionsOp}
onUpdateFilter={onUpdateFilter}
onDeleteFilter={onDeleteFilter}
onClearFilters={onClearFilters}
onReloadSavedFilter={onReloadSavedFilter}
filterId={filterId}
filtersList={filtersList}
onCondOpChange={onCondOpChange}
savedFilters={savedFilters}
onConditionsOpChange={onConditionsOpChange}
/>
)}
</View>

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