Compare commits

...

158 Commits

Author SHA1 Message Date
Zach Gollwitzer
bac2e64c19 Bump to v0.2.0
Signed-off-by: Zach Gollwitzer <zach@maybe.co>
2024-12-13 12:16:21 -05:00
Zach Gollwitzer
4866a4f8e4 Increase cache time for upgrades
Fixes #1525
2024-12-12 15:14:54 -05:00
Zach Gollwitzer
027c18297b Fix holding avg cost calculation 2024-12-12 15:11:06 -05:00
Zach Gollwitzer
800eb4c146 Plaid sync tests and multi-currency investment support (#1531)
* Plaid sync tests and multi-currency investment support

* Fix system test

* Cleanup

* Remove data migration
2024-12-12 08:56:52 -05:00
Zach Gollwitzer
b2a56aefc1 Update Plaid cash balance on each sync 2024-12-10 18:54:09 -05:00
Zach Gollwitzer
46131fb496 Fix unique constraint errors on sync 2024-12-10 18:16:53 -05:00
Zach Gollwitzer
49c353e10c Plaid portfolio sync algorithm and calculation improvements (#1526)
* Start tests rework

* Cash balance on schema

* Add reverse syncer

* Reverse balance sync with holdings

* Reverse holdings sync

* Reverse holdings sync should work with only trade entries

* Consolidate brokerage cash

* Add forward sync option

* Update new balance info after syncs

* Intraday balance calculator and sync fixes

* Show only balance for trade entries

* Tests passing

* Update Gemfile.lock

* Cleanup, performance improvements

* Remove account reloads for reliable sync outputs

* Simplify valuation view logic

* Special handling for Plaid cash holding
2024-12-10 17:41:20 -05:00
dependabot[bot]
a59ca5b7c6 Bump aws-sdk-s3 from 1.175.0 to 1.176.0 (#1519)
Bumps [aws-sdk-s3](https://github.com/aws/aws-sdk-ruby) from 1.175.0 to 1.176.0.
- [Release notes](https://github.com/aws/aws-sdk-ruby/releases)
- [Changelog](https://github.com/aws/aws-sdk-ruby/blob/version-3/gems/aws-sdk-s3/CHANGELOG.md)
- [Commits](https://github.com/aws/aws-sdk-ruby/commits)

---
updated-dependencies:
- dependency-name: aws-sdk-s3
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-09 11:31:29 -05:00
dependabot[bot]
ee79016e2a Bump pagy from 9.3.2 to 9.3.3 (#1520)
Bumps [pagy](https://github.com/ddnexus/pagy) from 9.3.2 to 9.3.3.
- [Release notes](https://github.com/ddnexus/pagy/releases)
- [Changelog](https://github.com/ddnexus/pagy/blob/master/CHANGELOG.md)
- [Commits](https://github.com/ddnexus/pagy/compare/9.3.2...9.3.3)

---
updated-dependencies:
- dependency-name: pagy
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-09 11:31:20 -05:00
dependabot[bot]
13cf4d70df Bump sentry-rails from 5.21.0 to 5.22.0 (#1522)
Bumps [sentry-rails](https://github.com/getsentry/sentry-ruby) from 5.21.0 to 5.22.0.
- [Release notes](https://github.com/getsentry/sentry-ruby/releases)
- [Changelog](https://github.com/getsentry/sentry-ruby/blob/master/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-ruby/compare/5.21.0...5.22.0)

---
updated-dependencies:
- dependency-name: sentry-rails
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-09 11:31:03 -05:00
dependabot[bot]
48e306a614 Bump mocha from 2.6.1 to 2.7.0 (#1523)
Bumps [mocha](https://github.com/freerange/mocha) from 2.6.1 to 2.7.0.
- [Changelog](https://github.com/freerange/mocha/blob/main/RELEASE.md)
- [Commits](https://github.com/freerange/mocha/compare/v2.6.1...v2.7.0)

---
updated-dependencies:
- dependency-name: mocha
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-09 11:22:00 -05:00
Zach Gollwitzer
a9daba16c1 Fix account activity view search 2024-12-05 08:39:16 -05:00
Zach Gollwitzer
2cba5177ba Revert out-of-sync schema changes 2024-12-04 18:40:43 -05:00
Nikhil Badyal
13bec4599f Handle invalid API key (#1515)
* Handle invalid API key

* Show error on invalid API key
2024-12-03 14:06:59 -05:00
Josh Pigford
565103caf3 Updated domain to maybefinance.com 2024-12-03 11:09:57 -06:00
Zach Gollwitzer
c456950de8 Fix transaction filters selection bar controller error 2024-12-02 14:06:56 -05:00
Zach Gollwitzer
9ec94cd1fa Add context to plaid sync errors 2024-12-02 12:04:54 -05:00
dependabot[bot]
d73e7eacce Bump good_job from 4.5.0 to 4.5.1 (#1509)
Bumps [good_job](https://github.com/bensheldon/good_job) from 4.5.0 to 4.5.1.
- [Release notes](https://github.com/bensheldon/good_job/releases)
- [Changelog](https://github.com/bensheldon/good_job/blob/main/CHANGELOG.md)
- [Commits](https://github.com/bensheldon/good_job/compare/v4.5.0...v4.5.1)

---
updated-dependencies:
- dependency-name: good_job
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-02 11:02:09 -05:00
dependabot[bot]
890638e06d Bump mocha from 2.6.0 to 2.6.1 (#1510)
Bumps [mocha](https://github.com/freerange/mocha) from 2.6.0 to 2.6.1.
- [Changelog](https://github.com/freerange/mocha/blob/main/RELEASE.md)
- [Commits](https://github.com/freerange/mocha/compare/v2.6.0...v2.6.1)

---
updated-dependencies:
- dependency-name: mocha
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-02 11:02:00 -05:00
dependabot[bot]
14fd5913fe Bump aws-sdk-s3 from 1.173.0 to 1.175.0 (#1511)
Bumps [aws-sdk-s3](https://github.com/aws/aws-sdk-ruby) from 1.173.0 to 1.175.0.
- [Release notes](https://github.com/aws/aws-sdk-ruby/releases)
- [Changelog](https://github.com/aws/aws-sdk-ruby/blob/version-3/gems/aws-sdk-s3/CHANGELOG.md)
- [Commits](https://github.com/aws/aws-sdk-ruby/commits)

---
updated-dependencies:
- dependency-name: aws-sdk-s3
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-02 11:01:54 -05:00
dependabot[bot]
e026f68895 Bump selenium-webdriver from 4.26.0 to 4.27.0 (#1512)
Bumps [selenium-webdriver](https://github.com/SeleniumHQ/selenium) from 4.26.0 to 4.27.0.
- [Release notes](https://github.com/SeleniumHQ/selenium/releases)
- [Changelog](https://github.com/SeleniumHQ/selenium/blob/trunk/rb/CHANGES)
- [Commits](https://github.com/SeleniumHQ/selenium/compare/selenium-4.26.0...selenium-4.27.0)

---
updated-dependencies:
- dependency-name: selenium-webdriver
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-02 11:01:45 -05:00
dependabot[bot]
1b8064b9fd Bump pagy from 9.3.1 to 9.3.2 (#1513)
Bumps [pagy](https://github.com/ddnexus/pagy) from 9.3.1 to 9.3.2.
- [Release notes](https://github.com/ddnexus/pagy/releases)
- [Changelog](https://github.com/ddnexus/pagy/blob/master/CHANGELOG.md)
- [Commits](https://github.com/ddnexus/pagy/compare/9.3.1...9.3.2)

---
updated-dependencies:
- dependency-name: pagy
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-02 11:01:36 -05:00
Zach Gollwitzer
d592495be5 Fix sync error when security missing 2024-12-02 10:53:16 -05:00
Zach Gollwitzer
c3248cd796 Improve account transaction, trade, and valuation editing and sync experience (#1506)
* Consolidate entry controller logic

* Transaction builder

* Update trades controller to use new params

* Load account charts in turbo frames, fix PG overflow

* Consolidate tests

* Tests passing

* Remove unused code

* Add client side trade form validations
2024-11-27 16:01:50 -05:00
Nikhil Badyal
76f2714006 Updated usage check threshold to 100pc instead of 1 (#1504) 2024-11-26 18:33:26 -05:00
Josh Pigford
a9b61a655b Synth error handling (#1502)
* Synth error handling

* Revert "Synth error handling"

This reverts commit fd6a0a12b4.

* Simplify overage messaging
2024-11-26 07:45:00 -06:00
Zach Gollwitzer
955f211fe0 Allow 0 qty for Plaid imported trades 2024-11-25 13:28:31 -05:00
Evlos
570a0c7ff6 [#] base_url on synth.rb (#1490) 2024-11-25 10:17:00 -05:00
dependabot[bot]
de9ffa7ca0 Bump ruby-lsp-rails from 0.3.26 to 0.3.27 (#1495)
Bumps [ruby-lsp-rails](https://github.com/Shopify/ruby-lsp-rails) from 0.3.26 to 0.3.27.
- [Release notes](https://github.com/Shopify/ruby-lsp-rails/releases)
- [Commits](https://github.com/Shopify/ruby-lsp-rails/compare/v0.3.26...v0.3.27)

---
updated-dependencies:
- dependency-name: ruby-lsp-rails
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-25 10:10:31 -05:00
dependabot[bot]
b5666ad7a9 Bump aws-sdk-s3 from 1.171.0 to 1.173.0 (#1496)
Bumps [aws-sdk-s3](https://github.com/aws/aws-sdk-ruby) from 1.171.0 to 1.173.0.
- [Release notes](https://github.com/aws/aws-sdk-ruby/releases)
- [Changelog](https://github.com/aws/aws-sdk-ruby/blob/version-3/gems/aws-sdk-s3/CHANGELOG.md)
- [Commits](https://github.com/aws/aws-sdk-ruby/commits)

---
updated-dependencies:
- dependency-name: aws-sdk-s3
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-25 10:10:20 -05:00
dependabot[bot]
fc603a1733 Bump good_job from 4.4.2 to 4.5.0 (#1497)
Bumps [good_job](https://github.com/bensheldon/good_job) from 4.4.2 to 4.5.0.
- [Release notes](https://github.com/bensheldon/good_job/releases)
- [Changelog](https://github.com/bensheldon/good_job/blob/main/CHANGELOG.md)
- [Commits](https://github.com/bensheldon/good_job/compare/v4.4.2...v4.5.0)

---
updated-dependencies:
- dependency-name: good_job
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-25 10:10:12 -05:00
dependabot[bot]
6c503e4d26 Bump puma from 6.4.3 to 6.5.0 (#1498)
Bumps [puma](https://github.com/puma/puma) from 6.4.3 to 6.5.0.
- [Release notes](https://github.com/puma/puma/releases)
- [Changelog](https://github.com/puma/puma/blob/master/History.md)
- [Commits](https://github.com/puma/puma/compare/v6.4.3...v6.5.0)

---
updated-dependencies:
- dependency-name: puma
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-25 10:08:11 -05:00
dependabot[bot]
57a87f2850 Bump pagy from 9.3.0 to 9.3.1 (#1499)
Bumps [pagy](https://github.com/ddnexus/pagy) from 9.3.0 to 9.3.1.
- [Release notes](https://github.com/ddnexus/pagy/releases)
- [Changelog](https://github.com/ddnexus/pagy/blob/master/CHANGELOG.md)
- [Commits](https://github.com/ddnexus/pagy/compare/9.3.0...9.3.1)

---
updated-dependencies:
- dependency-name: pagy
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-25 09:56:16 -05:00
dependabot[bot]
84f069448a Bump mocha from 2.5.0 to 2.6.0 (#1500)
Bumps [mocha](https://github.com/freerange/mocha) from 2.5.0 to 2.6.0.
- [Changelog](https://github.com/freerange/mocha/blob/main/RELEASE.md)
- [Commits](https://github.com/freerange/mocha/compare/v2.5.0...v2.6.0)

---
updated-dependencies:
- dependency-name: mocha
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-25 09:56:07 -05:00
dependabot[bot]
25e9bd4c60 Bump stripe from 13.1.2 to 13.2.0 (#1501)
Bumps [stripe](https://github.com/stripe/stripe-ruby) from 13.1.2 to 13.2.0.
- [Release notes](https://github.com/stripe/stripe-ruby/releases)
- [Changelog](https://github.com/stripe/stripe-ruby/blob/master/CHANGELOG.md)
- [Commits](https://github.com/stripe/stripe-ruby/compare/v13.1.2...v13.2.0)

---
updated-dependencies:
- dependency-name: stripe
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-25 09:55:58 -05:00
Zach Gollwitzer
a4adfed82b Disable Plaid i18n until we support full i18n 2024-11-25 09:48:21 -05:00
Zach Gollwitzer
03e92e63a5 Attempt to sync transactions regardless of main item type 2024-11-25 09:32:07 -05:00
Arsen Shkrumelyak
c1034e6edf Update index method in AccountsController to fetch all accounts (#1491)
* Fetch all manual accounts, regardless of their active status
* Fetch all Plaid items, regardless of their active status
2024-11-22 15:28:36 -05:00
Arsen Shkrumelyak
1c2f075053 Fix bug: Loan % doesn't allow exact rate (#1492)
* Fix bug: Loan % doesn't allow exact rate

Fixes #1487

Change the step of the interest rate field to 0.005.

It's unlikely that we'll see interest rates in smaller increments.

* step 0.005

* migration for loan interest rates precision

* add new line
2024-11-22 15:28:10 -05:00
Jestin Palamuttam
571fc4db75 Replaced Native Scrollbars with Tailwind Scrollbars on Windows (#1493)
* feat: scrollbar styling for windows browsers

* fix: lint
2024-11-22 15:27:07 -05:00
Josh Pigford
c8302a6d49 Let super admins toggle admin bar 2024-11-22 14:22:52 -06:00
Zach Gollwitzer
c309c8abf8 Safely call liability object when syncing 2024-11-22 10:41:16 -05:00
Nico
242eb5cea1 Calculates balance based on previous transaction on the same date (#1483) 2024-11-22 09:38:41 -05:00
Zach Gollwitzer
6996a225ba Add post-sync UI stream updates (#1482)
* Add post-sync UI stream updates

* Fix stream channel id
2024-11-20 16:46:06 -05:00
Zach Gollwitzer
e641cfccd4 Add post-sync hook (#1479) 2024-11-20 11:01:52 -05:00
Zach Gollwitzer
d1b506d16c Pass date as UTC for graphs 2024-11-19 16:23:21 -05:00
Zach Gollwitzer
81d604f3d4 Fix transfers and form currencies (#1477) 2024-11-18 15:50:47 -05:00
Zach Gollwitzer
fcb95207d7 Limit transaction editing for crypto accounts 2024-11-18 12:49:03 -05:00
Zach Gollwitzer
743e291d56 Fix tooltip trend color 2024-11-18 12:01:27 -05:00
Zach Gollwitzer
6105f822b7 Display chart dates in UTC 2024-11-18 11:44:41 -05:00
Alex
9cc9f42bdc Allow custom column separator for CSV parsing in uploads controller (#1470)
* Allow custom column separator for CSV parsing in uploads controller

* Add column separator parameter for CSV uploads in tests
2024-11-18 11:31:17 -05:00
dependabot[bot]
8b672c4062 Bump stripe from 13.1.1 to 13.1.2 (#1472)
Bumps [stripe](https://github.com/stripe/stripe-ruby) from 13.1.1 to 13.1.2.
- [Release notes](https://github.com/stripe/stripe-ruby/releases)
- [Changelog](https://github.com/stripe/stripe-ruby/blob/master/CHANGELOG.md)
- [Commits](https://github.com/stripe/stripe-ruby/compare/v13.1.1...v13.1.2)

---
updated-dependencies:
- dependency-name: stripe
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-18 10:57:31 -05:00
dependabot[bot]
8befb8a8b0 Bump aws-sdk-s3 from 1.170.0 to 1.171.0 (#1471)
Bumps [aws-sdk-s3](https://github.com/aws/aws-sdk-ruby) from 1.170.0 to 1.171.0.
- [Release notes](https://github.com/aws/aws-sdk-ruby/releases)
- [Changelog](https://github.com/aws/aws-sdk-ruby/blob/version-3/gems/aws-sdk-s3/CHANGELOG.md)
- [Commits](https://github.com/aws/aws-sdk-ruby/commits)

---
updated-dependencies:
- dependency-name: aws-sdk-s3
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-18 10:57:14 -05:00
dependabot[bot]
f15875560e Bump faraday from 2.12.0 to 2.12.1 (#1473)
Bumps [faraday](https://github.com/lostisland/faraday) from 2.12.0 to 2.12.1.
- [Release notes](https://github.com/lostisland/faraday/releases)
- [Changelog](https://github.com/lostisland/faraday/blob/main/CHANGELOG.md)
- [Commits](https://github.com/lostisland/faraday/compare/v2.12.0...v2.12.1)

---
updated-dependencies:
- dependency-name: faraday
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-18 10:57:02 -05:00
dependabot[bot]
951a29d923 Bump pagy from 9.2.1 to 9.3.0 (#1474)
Bumps [pagy](https://github.com/ddnexus/pagy) from 9.2.1 to 9.3.0.
- [Release notes](https://github.com/ddnexus/pagy/releases)
- [Changelog](https://github.com/ddnexus/pagy/blob/master/CHANGELOG.md)
- [Commits](https://github.com/ddnexus/pagy/compare/9.2.1...9.3.0)

---
updated-dependencies:
- dependency-name: pagy
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-18 10:56:53 -05:00
dependabot[bot]
91eedfbd1b Bump plaid from 33.0.0 to 34.0.0 (#1475)
Bumps [plaid](https://github.com/plaid/plaid-ruby) from 33.0.0 to 34.0.0.
- [Release notes](https://github.com/plaid/plaid-ruby/releases)
- [Changelog](https://github.com/plaid/plaid-ruby/blob/master/CHANGELOG.md)
- [Commits](https://github.com/plaid/plaid-ruby/compare/v33.0.0...v34.0.0)

---
updated-dependencies:
- dependency-name: plaid
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-18 10:56:36 -05:00
Zach Gollwitzer
0af5faaa9f Make encryption config optional for self hosting users (#1476)
* Fix redirect 404 bug

* Make encryption optional for self-hosters

* Fix test
2024-11-18 10:47:05 -05:00
Zach Gollwitzer
69f6d7f8ea Enable consent for additional plaid products 2024-11-15 17:33:18 -05:00
Zach Gollwitzer
cbba2ba675 Basic Plaid Integration (#1433)
* Basic plaid data model and linking

* Remove institutions, add plaid items

* Improve schema and Plaid provider

* Add webhook verification sketch

* Webhook verification

* Item accounts and balances sync setup

* Provide test encryption keys

* Fix test

* Only provide encryption keys in prod

* Try defining keys in test env

* Consolidate account sync logic

* Add back plaid account initialization

* Plaid transaction sync

* Sync UI overhaul for Plaid

* Add liability and investment syncing

* Handle investment webhooks and process current day holdings

* Remove logs

* Remove "all" period select for performance

* fix amount calc

* Remove todo comment

* Coming soon for investment historical data

* Document Plaid configuration

* Listen for holding updates
2024-11-15 13:49:37 -05:00
Sergio Behrends
3bc9da4105 Adds a common DE format (#1445) 2024-11-11 09:57:50 -05:00
dependabot[bot]
9522a191de Bump stripe from 13.1.0 to 13.1.1 (#1450)
Bumps [stripe](https://github.com/stripe/stripe-ruby) from 13.1.0 to 13.1.1.
- [Release notes](https://github.com/stripe/stripe-ruby/releases)
- [Changelog](https://github.com/stripe/stripe-ruby/blob/master/CHANGELOG.md)
- [Commits](https://github.com/stripe/stripe-ruby/compare/v13.1.0...v13.1.1)

---
updated-dependencies:
- dependency-name: stripe
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-11 09:57:30 -05:00
dependabot[bot]
ed87023c0f Bump pagy from 9.1.1 to 9.2.1 (#1453)
Bumps [pagy](https://github.com/ddnexus/pagy) from 9.1.1 to 9.2.1.
- [Release notes](https://github.com/ddnexus/pagy/releases)
- [Changelog](https://github.com/ddnexus/pagy/blob/master/CHANGELOG.md)
- [Commits](https://github.com/ddnexus/pagy/compare/9.1.1...9.2.1)

---
updated-dependencies:
- dependency-name: pagy
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-11 09:44:18 -05:00
dependabot[bot]
3d7a74862d Bump aws-sdk-s3 from 1.169.0 to 1.170.0 (#1452)
Bumps [aws-sdk-s3](https://github.com/aws/aws-sdk-ruby) from 1.169.0 to 1.170.0.
- [Release notes](https://github.com/aws/aws-sdk-ruby/releases)
- [Changelog](https://github.com/aws/aws-sdk-ruby/blob/version-3/gems/aws-sdk-s3/CHANGELOG.md)
- [Commits](https://github.com/aws/aws-sdk-ruby/commits)

---
updated-dependencies:
- dependency-name: aws-sdk-s3
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-11 09:44:04 -05:00
dependabot[bot]
fc3695dda9 Bump ruby-lsp-rails from 0.3.21 to 0.3.26 (#1451)
Bumps [ruby-lsp-rails](https://github.com/Shopify/ruby-lsp-rails) from 0.3.21 to 0.3.26.
- [Release notes](https://github.com/Shopify/ruby-lsp-rails/releases)
- [Commits](https://github.com/Shopify/ruby-lsp-rails/compare/v0.3.21...v0.3.26)

---
updated-dependencies:
- dependency-name: ruby-lsp-rails
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-11 09:43:51 -05:00
Tony Vincent
278d04a73a Fix registration fails silently when there are errors (#1455)
* Fix registration fails silently with long passwords

* Add maxlength
2024-11-11 09:41:17 -05:00
Zach Gollwitzer
31ecd3ccd4 Fix precision in money input 2024-11-11 09:39:32 -05:00
Zach Gollwitzer
3ef67faf7e Show search bar even when no results in entries
Fixes #1449
2024-11-11 09:26:19 -05:00
Zach Gollwitzer
8ba04b0330 Fix confirm message 2024-11-11 09:21:13 -05:00
Zach Gollwitzer
56ab092f6b Bump to v0.2.0-alpha.2
Signed-off-by: Zach Gollwitzer <zach@maybe.co>
2024-11-08 14:55:56 -05:00
Tony Vincent
b3ef995d1f Fix duplicate invites (#1437)
Co-authored-by: Josh Pigford <josh@joshpigford.com>
2024-11-08 09:58:35 -06:00
bruno costanzo
a113d573d6 Skip account valuation on entry balance_after_entry (#1435) 2024-11-08 09:17:55 -05:00
Tony Vincent
3b928775a8 Fix timeframe dropdown next to Portfolio (#1434) 2024-11-08 09:10:05 -05:00
Josh Pigford
31d9d926f7 Fix for certain securities returning incorrect prices 2024-11-07 11:52:16 -06:00
Tony Vincent
154a1a971b Exclude inactive accounts from networth calculation and from sidebar (#1432) 2024-11-07 10:14:12 -05:00
Tony Vincent
e434ed0e1f Add text-overflow: ellipsis property for account name display (#1431) 2024-11-07 08:42:51 -06:00
Zach Gollwitzer
2722254be9 Sync account after balance deletion
- Fixes #1416
- Fixes timezone bugs in forms
2024-11-05 19:31:24 -05:00
Zach Gollwitzer
455257bf51 Show onboarding unless invitation present
Fixes #1421
2024-11-05 19:08:45 -05:00
Zach Gollwitzer
f2739b79fb Improve password reset flow, normalize translations 2024-11-05 17:15:29 -05:00
Zach Gollwitzer
cee9692b35 Update Ruby version note
Signed-off-by: Zach Gollwitzer <zach@maybe.co>
2024-11-05 11:06:36 -05:00
Luis Ezcurdia
18266c3352 Bump ruby version to 3.3.5 (#1402) 2024-11-05 11:05:08 -05:00
dependabot[bot]
c3400856c7 Bump rails from 7.2.1.2 to 7.2.2 (#1410)
Bumps [rails](https://github.com/rails/rails) from 7.2.1.2 to 7.2.2.
- [Release notes](https://github.com/rails/rails/releases)
- [Commits](https://github.com/rails/rails/compare/v7.2.1.2...v7.2.2)

---
updated-dependencies:
- dependency-name: rails
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Zach Gollwitzer <zach@maybe.co>
2024-11-05 08:17:40 -05:00
dependabot[bot]
a0ad33e47c Bump stripe from 13.0.2 to 13.1.0 (#1411)
Bumps [stripe](https://github.com/stripe/stripe-ruby) from 13.0.2 to 13.1.0.
- [Release notes](https://github.com/stripe/stripe-ruby/releases)
- [Changelog](https://github.com/stripe/stripe-ruby/blob/master/CHANGELOG.md)
- [Commits](https://github.com/stripe/stripe-ruby/compare/v13.0.2...v13.1.0)

---
updated-dependencies:
- dependency-name: stripe
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Zach Gollwitzer <zach@maybe.co>
2024-11-05 08:16:11 -05:00
dependabot[bot]
65d46397d7 Bump pagy from 9.1.0 to 9.1.1 (#1409)
Bumps [pagy](https://github.com/ddnexus/pagy) from 9.1.0 to 9.1.1.
- [Release notes](https://github.com/ddnexus/pagy/releases)
- [Changelog](https://github.com/ddnexus/pagy/blob/master/CHANGELOG.md)
- [Commits](https://github.com/ddnexus/pagy/compare/9.1.0...9.1.1)

---
updated-dependencies:
- dependency-name: pagy
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Zach Gollwitzer <zach@maybe.co>
2024-11-05 08:15:52 -05:00
dependabot[bot]
905eb7bbe8 Bump selenium-webdriver from 4.25.0 to 4.26.0 (#1412)
Bumps [selenium-webdriver](https://github.com/SeleniumHQ/selenium) from 4.25.0 to 4.26.0.
- [Release notes](https://github.com/SeleniumHQ/selenium/releases)
- [Changelog](https://github.com/SeleniumHQ/selenium/blob/trunk/rb/CHANGES)
- [Commits](https://github.com/SeleniumHQ/selenium/compare/selenium-4.25.0...selenium-4.26.0)

---
updated-dependencies:
- dependency-name: selenium-webdriver
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Zach Gollwitzer <zach@maybe.co>
2024-11-05 08:15:40 -05:00
Zach Gollwitzer
65db49273c Account Activity View + Account Forms (#1406)
* Remove balance mode, sketch out refactor

* Activity view checkpoint

* Entry partials, checkpoint

* Finish txn partial

* Give entries context when editing for different turbo responses

* Calculate change of balance for each entry

* Account tabs consolidation

* Translations, linting, brakeman updates

* Account actions concern

* Finalize forms, get account system tests passing

* Get tests passing

* Lint, rubocop, schema updates

* Improve routing and stream responses

* Fix broken routes

* Add import option for adding accounts

* Fix system test

* Fix test specificity

* Fix sparklines

* Improve account redirects
2024-11-04 20:27:31 -05:00
alekseyp
12e4f1067d Update en.yml (#1401)
Signed-off-by: alekseyp <aleksey.potaneyko@gmail.com>
2024-11-01 15:01:47 -05:00
Josh Pigford
85779b4038 super_admin should be valid for admin 2024-11-01 10:30:30 -05:00
Josh Pigford
793bd852a0 Family invites (#1397)
* Initial pass at household invites

* Invitee setup

* Clean up add member form

* Lint and other tweaks

* Security cleanup

* Lint

* i18n fixes

* More i18n cleanup

* Show pending invites

* Don't use turbo on the form

* Improved email design

* Basic tests

* Lint

* Update onboardings_controller.rb

* Registration + invite cleanup

* Lint

* Update brakeman.ignore

* Update brakeman.ignore

* Self host invite links

* Test tweaks

* Address missing param error
2024-11-01 10:23:27 -05:00
Zach Gollwitzer
09b269273a Safe load yaml files 2024-11-01 09:42:00 -04:00
Harshit Chaudhary
47288a1629 Auto naming of Transfer Transaction (#1393)
* Remove Description field

* Auto naming of tranfer transaction

* Fix transfer test

* Improve Transfer entries names
2024-11-01 08:58:19 -04:00
Tony Vincent
2b61821336 Do not include income transactions in liability accounts for savings rate (#1385)
* Do not include income transactions in liability accounts for savings rate

* Do not include income in liability accounts in savings rate chart
2024-10-31 09:05:01 -04:00
Nico
7946cd7819 Adds condition to skip link to transaction form if it's not editable (#1394) 2024-10-31 08:57:06 -04:00
Josh Pigford
e7f09e6f71 Groundwork for security info (#1396)
* Groundwork for security info

* Lint
2024-10-30 18:08:19 -04:00
Josh Pigford
5e2b932648 Use Synth logo for holdings 2024-10-30 12:14:11 -04:00
Josh Pigford
5533b84895 Always include US stocks 2024-10-30 10:42:57 -04:00
Zach Gollwitzer
c9917674aa Add validation to security price model 2024-10-30 09:51:05 -04:00
Josh Pigford
cd91e66618 Initial pass at Synth-based ticker selection (#1392)
* Initial pass at Synth-based ticker selection

* Update _tickers.turbo_stream.erb

* Functional combobox display

* A few cleanup steps

* Linter

* Prevent long strings

* Another step towards functional combobox

* Deprecated files

* Custom Combobox implementation

* Lint

* Test suite fixes

* Lint

* Make direct use of mic codes

* Update splits

* Update trades_test.rb
2024-10-30 09:23:44 -04:00
Josh Pigford
490f44589e First pass at security price reference (#1388)
* First pass at security price reference

* Data cleanup

* Synth security fetching does better with a mic_code

* Update test suite

😭

* Update schema.rb

* Update generator.rb
2024-10-29 15:37:59 -04:00
Zach Gollwitzer
bf695972e4 Remove missing prices issue (#1390) 2024-10-29 14:55:46 -04:00
Josh Pigford
7d8028b505 Stock filter (#1376)
* Initial pass at stock filtering

* Rough in filter

* Cleaning up security listing

* Tweak to search function

* Combobox tweaks

* Clean up search query

* Update trades test with combobox

* Update securities.yml
2024-10-28 15:49:19 -04:00
Josh Pigford
c2561b5fb4 Handle manually entered securities 2024-10-28 13:33:27 -04:00
Zach Gollwitzer
e5eb69bdc7 Fix hidden selection bars on account views 2024-10-28 11:29:52 -04:00
Guillem Arias Fauste
3cd364af09 fix bulk action bar positioning (#1370)
* fix bulk action bar positioning

* remove extra space
2024-10-28 08:02:49 -04:00
dependabot[bot]
277fb3dc39 Bump mocha from 2.4.5 to 2.5.0 (#1378)
Bumps [mocha](https://github.com/freerange/mocha) from 2.4.5 to 2.5.0.
- [Changelog](https://github.com/freerange/mocha/blob/main/RELEASE.md)
- [Commits](https://github.com/freerange/mocha/compare/v2.4.5...v2.5.0)

---
updated-dependencies:
- dependency-name: mocha
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-28 07:56:44 -04:00
dependabot[bot]
439e50bb3e Bump pg from 1.5.8 to 1.5.9 (#1379)
Bumps [pg](https://github.com/ged/ruby-pg) from 1.5.8 to 1.5.9.
- [Changelog](https://github.com/ged/ruby-pg/blob/master/History.md)
- [Commits](https://github.com/ged/ruby-pg/compare/v1.5.8...v1.5.9)

---
updated-dependencies:
- dependency-name: pg
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-28 07:56:36 -04:00
dependabot[bot]
2141cbb041 Bump rails from 7.2.1.1 to 7.2.1.2 (#1380)
Bumps [rails](https://github.com/rails/rails) from 7.2.1.1 to 7.2.1.2.
- [Release notes](https://github.com/rails/rails/releases)
- [Commits](https://github.com/rails/rails/compare/v7.2.1.1...v7.2.1.2)

---
updated-dependencies:
- dependency-name: rails
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-28 07:56:25 -04:00
dependabot[bot]
d78f582af2 Bump ruby-lsp-rails from 0.3.20 to 0.3.21 (#1381)
Bumps [ruby-lsp-rails](https://github.com/Shopify/ruby-lsp-rails) from 0.3.20 to 0.3.21.
- [Release notes](https://github.com/Shopify/ruby-lsp-rails/releases)
- [Commits](https://github.com/Shopify/ruby-lsp-rails/compare/v0.3.20...v0.3.21)

---
updated-dependencies:
- dependency-name: ruby-lsp-rails
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-28 07:56:01 -04:00
dependabot[bot]
2adb54da99 Bump stripe from 13.0.1 to 13.0.2 (#1382)
Bumps [stripe](https://github.com/stripe/stripe-ruby) from 13.0.1 to 13.0.2.
- [Release notes](https://github.com/stripe/stripe-ruby/releases)
- [Changelog](https://github.com/stripe/stripe-ruby/blob/master/CHANGELOG.md)
- [Commits](https://github.com/stripe/stripe-ruby/compare/v13.0.1...v13.0.2)

---
updated-dependencies:
- dependency-name: stripe
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-28 07:55:50 -04:00
Josh Pigford
45935db5f3 Remove dependency on stock exchange table (#1368) 2024-10-25 13:09:02 -05:00
Zach Gollwitzer
b75b41a5e2 Bump to v0.2.0-alpha.1
Signed-off-by: Zach Gollwitzer <zach@maybe.co>
2024-10-25 09:37:50 -04:00
bruno costanzo
2cc89195bf Feature | Filter on uncategorized transactions (#1359)
* allow filtering uncategorized transactions

* user can filter uncategorized transactions test

* rubocop linting
2024-10-25 09:37:05 -04:00
Josh Pigford
aa3342b0dc Stock imports (#1363)
* Initial pass

* Marketstack data provider

* Marketstack data provider

* Refactor a bit
2024-10-24 16:36:50 -05:00
Zach Gollwitzer
b611dfdf37 Add back good job dashboard with auth (#1364) 2024-10-24 17:28:29 -04:00
Zach Gollwitzer
ba49fea89a Locale updates 2024-10-24 16:43:59 -04:00
Zach Gollwitzer
89e107e36c Remove good job dashboard route 2024-10-24 15:56:58 -04:00
Zach Gollwitzer
d93fbbcaa8 Fix tooltip on charts 2024-10-24 12:06:42 -04:00
Josh Pigford
e6403fab70 Modal design tweaks 2024-10-24 10:16:24 -05:00
Zach Gollwitzer
6baffe7539 Beta Testing Round 3 Bug Fixes (#1357)
* Clean up env example files

* Fix duplicate category creations

* Fix duplicate tag and merchant creation

* Add initial valuation to imported accounts

* Add upgrade modal prompt

* Don't hide content on billing page

* Add temporary session for new customers

* Lint fixes

* Fix unused translations

* Fix system tests
2024-10-24 11:02:27 -04:00
Zach Gollwitzer
1d20de770f User Onboarding + Bug Fixes (#1352)
* Bump min supported date to 20 years

* Add basic onboarding

* User onboarding

* Complete onboarding flow

* Cleanup, add user profile update test
2024-10-23 11:20:55 -04:00
Josh Pigford
73e184ad3d Stock Exchanges with seed (#1351)
* Stock Exchanges with seed

* Run the seed file on migration

* Fix for enum column
2024-10-22 14:30:57 -05:00
dependabot[bot]
d3a6f7e0f0 Bump tailwindcss-rails from 2.7.9 to 3.0.0 (#1341)
Bumps [tailwindcss-rails](https://github.com/rails/tailwindcss-rails) from 2.7.9 to 3.0.0.
- [Release notes](https://github.com/rails/tailwindcss-rails/releases)
- [Changelog](https://github.com/rails/tailwindcss-rails/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rails/tailwindcss-rails/compare/v2.7.9...v3.0.0)

---
updated-dependencies:
- dependency-name: tailwindcss-rails
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-22 15:11:26 -04:00
Josh Pigford
9313620968 Updated Synth env variable description 2024-10-22 13:10:51 -05:00
Josh Pigford
a4e87ffb4d Delete extensions.json 2024-10-21 20:20:52 -05:00
Zach Gollwitzer
728b10d08e Fix trade import mapping bug 2024-10-21 12:26:39 -04:00
dependabot[bot]
a27b17deae Bump ruby-lsp-rails from 0.3.19 to 0.3.20 (#1339)
Bumps [ruby-lsp-rails](https://github.com/Shopify/ruby-lsp-rails) from 0.3.19 to 0.3.20.
- [Release notes](https://github.com/Shopify/ruby-lsp-rails/releases)
- [Commits](https://github.com/Shopify/ruby-lsp-rails/compare/v0.3.19...v0.3.20)

---
updated-dependencies:
- dependency-name: ruby-lsp-rails
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-21 10:14:05 -04:00
Nico
1b654faf9a Fixes issue with mapping values during the transactions import (#1327)
* Adds custom debounce timeout to autosubmit form controller

- There's a default debounce timeout based on element type
- You can parameterize debounce timeout on a data-attribute

* Adds corrections based on js_lint

* Restores sleep on test

---------

Co-authored-by: Nicolás Galdámez <nicolas.galdamez@unagisoftware.com>
2024-10-21 10:13:55 -04:00
dependabot[bot]
9b6a2cce56 Bump turbo-rails from 2.0.10 to 2.0.11 (#1343)
Bumps [turbo-rails](https://github.com/hotwired/turbo-rails) from 2.0.10 to 2.0.11.
- [Release notes](https://github.com/hotwired/turbo-rails/releases)
- [Commits](https://github.com/hotwired/turbo-rails/compare/v2.0.10...v2.0.11)

---
updated-dependencies:
- dependency-name: turbo-rails
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-21 10:12:35 -04:00
dependabot[bot]
5ff9012d3e Bump rails from 7.2.1 to 7.2.1.1 (#1340)
Bumps [rails](https://github.com/rails/rails) from 7.2.1 to 7.2.1.1.
- [Release notes](https://github.com/rails/rails/releases)
- [Commits](https://github.com/rails/rails/compare/v7.2.1...v7.2.1.1)

---
updated-dependencies:
- dependency-name: rails
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-21 10:10:15 -04:00
dependabot[bot]
da7f19d5ab Bump erb_lint from 0.6.0 to 0.7.0 (#1337)
Bumps [erb_lint](https://github.com/Shopify/erb-lint) from 0.6.0 to 0.7.0.
- [Release notes](https://github.com/Shopify/erb-lint/releases)
- [Commits](https://github.com/Shopify/erb-lint/compare/v0.6.0...v0.7.0)

---
updated-dependencies:
- dependency-name: erb_lint
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-21 09:53:51 -04:00
dependabot[bot]
a2e8fb5ce1 Bump good_job from 4.4.1 to 4.4.2 (#1336)
Bumps [good_job](https://github.com/bensheldon/good_job) from 4.4.1 to 4.4.2.
- [Release notes](https://github.com/bensheldon/good_job/releases)
- [Changelog](https://github.com/bensheldon/good_job/blob/main/CHANGELOG.md)
- [Commits](https://github.com/bensheldon/good_job/compare/v4.4.1...v4.4.2)

---
updated-dependencies:
- dependency-name: good_job
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-21 09:53:41 -04:00
dependabot[bot]
b074762809 Bump faker from 3.4.2 to 3.5.1 (#1338)
Bumps [faker](https://github.com/faker-ruby/faker) from 3.4.2 to 3.5.1.
- [Release notes](https://github.com/faker-ruby/faker/releases)
- [Changelog](https://github.com/faker-ruby/faker/blob/main/CHANGELOG.md)
- [Commits](https://github.com/faker-ruby/faker/compare/v3.4.2...v3.5.1)

---
updated-dependencies:
- dependency-name: faker
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-21 09:53:33 -04:00
dependabot[bot]
3cc4cba2b3 Bump octokit from 9.1.0 to 9.2.0 (#1342)
Bumps [octokit](https://github.com/octokit/octokit.rb) from 9.1.0 to 9.2.0.
- [Release notes](https://github.com/octokit/octokit.rb/releases)
- [Changelog](https://github.com/octokit/octokit.rb/blob/main/RELEASE.md)
- [Commits](https://github.com/octokit/octokit.rb/compare/v9.1.0...v9.2.0)

---
updated-dependencies:
- dependency-name: octokit
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-21 09:52:49 -04:00
dependabot[bot]
cb752370cb Bump aws-sdk-s3 from 1.167.0 to 1.169.0 (#1344)
Bumps [aws-sdk-s3](https://github.com/aws/aws-sdk-ruby) from 1.167.0 to 1.169.0.
- [Release notes](https://github.com/aws/aws-sdk-ruby/releases)
- [Changelog](https://github.com/aws/aws-sdk-ruby/blob/version-3/gems/aws-sdk-s3/CHANGELOG.md)
- [Commits](https://github.com/aws/aws-sdk-ruby/commits)

---
updated-dependencies:
- dependency-name: aws-sdk-s3
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-21 09:52:37 -04:00
dependabot[bot]
720d7aedaf Bump stripe from 13.0.0 to 13.0.1 (#1345)
Bumps [stripe](https://github.com/stripe/stripe-ruby) from 13.0.0 to 13.0.1.
- [Release notes](https://github.com/stripe/stripe-ruby/releases)
- [Changelog](https://github.com/stripe/stripe-ruby/blob/master/CHANGELOG.md)
- [Commits](https://github.com/stripe/stripe-ruby/compare/v13.0.0...v13.0.1)

---
updated-dependencies:
- dependency-name: stripe
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-21 09:52:27 -04:00
Josh Pigford
07264e86cb Add accounts count to Intercom 2024-10-19 14:54:51 -05:00
Zach Gollwitzer
3c0fdd84ee Fix mode bug 2024-10-18 18:25:17 -04:00
Zach Gollwitzer
263d65ea7e Basic account onboarding (#1328)
* Basic account onboarding

* Cleanup
2024-10-18 17:18:54 -04:00
Zach Gollwitzer
e8e100e1d8 Rework account views and addition flow (#1324)
* Move accountable partials

* Split accountables into separate view partials

* Fix test

* Add form to permitted partials

* Fix failing system tests

* Update new account modal views

* New sync algorithm implementation

* Update account system test assertions to match new behavior

* Fix off by 1 date error

* Revert new balance sync algorithm

* Add missing account overviews
2024-10-18 14:37:42 -04:00
Josh Pigford
c7c281073f Impersonation (#1325)
* Initial impersonation

* Impersonation audit

* Keep super admin separate

* Remove vscode settings

* Comment cleanup

* Comment out impersonation fixtures for now

* Remove unused controlelr

* Add impersonation testing (#1326)

* Add impersonation testing

* Remove unused method

* Update schema.rb

* Update brakeman

---------

Co-authored-by: Zach Gollwitzer <zach@maybe.co>
2024-10-18 11:26:58 -05:00
Ender Ahmet Yurt
4a3685f503 Redirect upload step (#1323)
* Redirect upload step

* Change redirect page regarding state of the import
2024-10-18 08:10:18 -05:00
Josh Pigford
75a390f03e Account indexes to address some performance issues 2024-10-17 15:45:13 -05:00
Josh Pigford
d4bfcfb6f4 Fix for transaction drawer securities missing prices
Fixes #1321
2024-10-17 10:52:04 -05:00
Josh Pigford
b98f35af0e Another tweak to the bug template 2024-10-17 10:39:56 -05:00
Josh Pigford
629565f7d8 Updated bug report template 2024-10-17 10:20:42 -05:00
Josh Pigford
4118cc8a31 Fix for scrollbars on alerts
Fixes #1320
2024-10-17 10:16:34 -05:00
Josh Pigford
61bf53f233 Rescue RecordNotUnique
Fixes #1319
2024-10-17 09:52:06 -05:00
Guillem Arias Fauste
7f4c1755ef add dashboard account pill tooltips (#1315)
* add dashboard account pill tooltips

* Update app/views/shared/_text_tooltip.erb

Co-authored-by: Zach Gollwitzer <zach.gollwitzer@gmail.com>
Signed-off-by: Guillem Arias Fauste <gariasf@proton.me>

---------

Signed-off-by: Guillem Arias Fauste <gariasf@proton.me>
Co-authored-by: Zach Gollwitzer <zach.gollwitzer@gmail.com>
2024-10-16 13:14:43 -04:00
Tony Vincent
76decc06c3 Maintain order (#1318) 2024-10-16 12:09:52 -04:00
Zach Gollwitzer
f3bb80dde6 Fix pie chart 2024-10-14 17:21:51 -04:00
oxdev03
4ad28d6eff Add BiomeJS for Linting and Formatting JavaScript relates to #1295 (#1299)
* chore: add formatting and linting for javascript code relates to #1295

* use spaces instaed

* add to recommended extensions

* only enforce lint

* auto save
2024-10-14 17:09:27 -04:00
dependabot[bot]
fa3b8b078c Bump good_job from 4.3.0 to 4.4.1 (#1302)
Bumps [good_job](https://github.com/bensheldon/good_job) from 4.3.0 to 4.4.1.
- [Release notes](https://github.com/bensheldon/good_job/releases)
- [Changelog](https://github.com/bensheldon/good_job/blob/main/CHANGELOG.md)
- [Commits](https://github.com/bensheldon/good_job/compare/v4.3.0...v4.4.1)

---
updated-dependencies:
- dependency-name: good_job
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-14 11:44:13 -04:00
dependabot[bot]
d4e7a983f4 Bump tailwindcss-rails from 2.7.7 to 2.7.9 (#1304)
Bumps [tailwindcss-rails](https://github.com/rails/tailwindcss-rails) from 2.7.7 to 2.7.9.
- [Release notes](https://github.com/rails/tailwindcss-rails/releases)
- [Changelog](https://github.com/rails/tailwindcss-rails/blob/v2.7.9/CHANGELOG.md)
- [Commits](https://github.com/rails/tailwindcss-rails/compare/v2.7.7...v2.7.9)

---
updated-dependencies:
- dependency-name: tailwindcss-rails
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-14 11:02:10 -04:00
dependabot[bot]
7f7140b1cc Bump ruby-lsp-rails from 0.3.18 to 0.3.19 (#1300)
Bumps [ruby-lsp-rails](https://github.com/Shopify/ruby-lsp-rails) from 0.3.18 to 0.3.19.
- [Release notes](https://github.com/Shopify/ruby-lsp-rails/releases)
- [Commits](https://github.com/Shopify/ruby-lsp-rails/compare/v0.3.18...v0.3.19)

---
updated-dependencies:
- dependency-name: ruby-lsp-rails
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-14 11:01:56 -04:00
dependabot[bot]
437aa4bd39 Bump importmap-rails from 2.0.2 to 2.0.3 (#1301)
Bumps [importmap-rails](https://github.com/rails/importmap-rails) from 2.0.2 to 2.0.3.
- [Release notes](https://github.com/rails/importmap-rails/releases)
- [Commits](https://github.com/rails/importmap-rails/compare/v2.0.2...v2.0.3)

---
updated-dependencies:
- dependency-name: importmap-rails
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-14 11:01:44 -04:00
dependabot[bot]
eabec71f70 Bump rails-settings-cached from 2.9.4 to 2.9.5 (#1305)
Bumps [rails-settings-cached](https://github.com/huacnlee/rails-settings-cached) from 2.9.4 to 2.9.5.
- [Release notes](https://github.com/huacnlee/rails-settings-cached/releases)
- [Changelog](https://github.com/huacnlee/rails-settings-cached/blob/main/CHANGELOG.md)
- [Commits](https://github.com/huacnlee/rails-settings-cached/compare/v2.9.4...v2.9.5)

---
updated-dependencies:
- dependency-name: rails-settings-cached
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-14 11:01:35 -04:00
dependabot[bot]
3bc960e6c1 Bump sentry-ruby from 5.20.1 to 5.21.0 (#1306)
Bumps [sentry-ruby](https://github.com/getsentry/sentry-ruby) from 5.20.1 to 5.21.0.
- [Release notes](https://github.com/getsentry/sentry-ruby/releases)
- [Changelog](https://github.com/getsentry/sentry-ruby/blob/master/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-ruby/compare/5.20.1...5.21.0)

---
updated-dependencies:
- dependency-name: sentry-ruby
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-14 11:01:17 -04:00
Alex Hatzenbuhler
57a81e44ef Add period to value delete modal (#1297) 2024-10-14 10:19:33 -04:00
Zach Gollwitzer
e357c0485f Temp fix for Stimulus charts 2024-10-11 14:40:13 -04:00
526 changed files with 13650 additions and 5169 deletions

View File

@@ -1,4 +1,4 @@
ARG RUBY_VERSION=3.3.4
ARG RUBY_VERSION=3.3.5
FROM ruby:${RUBY_VERSION}-slim-bullseye
RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
@@ -17,4 +17,8 @@ RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
RUN gem install bundler
RUN gem install foreman
# Install Node.js 20
RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \
&& apt-get install -y nodejs
WORKDIR /workspace

View File

@@ -10,5 +10,13 @@
"remoteEnv": {
"PATH": "/workspace/bin:${containerEnv:PATH}"
},
"postCreateCommand": "bundle install"
"postCreateCommand": "bundle install && npm install",
"customizations": {
"vscode": {
"extensions": [
"biomejs.biome",
"EditorConfig.EditorConfig"
]
}
}
}

View File

@@ -1,9 +1,18 @@
# ================================ PLEASE READ ==========================================
# This file outlines all the possible environment variables supported by the Maybe app.
#
# This includes several features that are for our "hosted" version of Maybe, which most
# open-source contributors won't need.
#
# If you are developing locally, you should be referencing `.env.local.example` instead.
# =======================================================================================
# Custom port config
# For users who have other applications listening at 3000, this allows them to set a value puma will listen to.
PORT=3000
# Exchange Rate API
# This is used to convert between different currencies in the app. We use Synth, which is a Maybe product. You can sign up for a free account at synthfinance.com.
# Exchange Rate & Stock Pricing API
# This is used to convert between different currencies in the app. In addition, it fetches global stock prices. We use Synth, which is a Maybe product. You can sign up for a free account at synthfinance.com.
SYNTH_API_KEY=
# SMTP Configuration
@@ -101,4 +110,12 @@ GITHUB_REPO_BRANCH=main
#
STRIPE_PUBLISHABLE_KEY=
STRIPE_SECRET_KEY=
STRIPE_WEBHOOK_SECRET=
STRIPE_WEBHOOK_SECRET=
# ======================================================================================================
# Plaid Configuration
# ======================================================================================================
#
PLAID_CLIENT_ID=
PLAID_SECRET=
PLAID_ENV=

5
.env.local.example Normal file
View File

@@ -0,0 +1,5 @@
# To enable / disable self-hosting features.
SELF_HOSTED=false
# Enable Synth market data (careful, this will use your API credits)
SYNTH_API_KEY=yourapikeyhere

8
.env.test Normal file
View File

@@ -0,0 +1,8 @@
SELF_HOSTED=false
SYNTH_API_KEY=fookey
# Set to true if you want SimpleCov reports generated
COVERAGE=false
# Set to true to run test suite serially
DISABLE_PARALLELIZATION=false

View File

@@ -20,6 +20,12 @@ Steps to reproduce the behavior:
**Expected behavior**
A clear and concise description of what you expected to happen.
**What version of Maybe are you using?**
This could be "Hosted" (i.e. app.maybefinance.com) or "Self-hosted". If "Self-hosted", please include the version you're currently on.
**What operating system and browser are you using?**
The more info the better.
**Screenshots / Recordings**
If applicable, add screenshots or short video recordings to help show the bug in more detail.

View File

@@ -52,6 +52,26 @@ jobs:
- name: Lint code for consistent style
run: bin/rubocop -f github
lint_js:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js environment
uses: actions/setup-node@v4
with:
node-version: "20"
cache: "npm"
- name: Install dependencies
run: npm install
shell: bash
- name: Lint/Format js code
run: npm run lint
test:
runs-on: ubuntu-latest
timeout-minutes: 10

14
.gitignore vendored
View File

@@ -6,12 +6,13 @@
# Ignore bundler config.
/.bundle
/vendor/bundle
# Ignore all environment files (except templates).
/.env*
!/.env*.erb
!.env.example
!.env.test.example
!.env.test
!.env*.example
# Ignore all logfiles and tempfiles.
/log/*
@@ -43,7 +44,9 @@
.idea
# Ignore VS Code
.vscode
.vscode/*
!.vscode/extensions.json
!.vscode/*.code-snippets
# Ignore macOS specific files
*/.DS_Store
@@ -59,4 +62,7 @@ compose-dev.yaml
gcp-storage-keyfile.json
coverage
.cursorrules
.cursorrules
# Ignore node related files
node_modules

View File

@@ -1 +1 @@
3.3.4
3.3.5

View File

@@ -1,7 +1,7 @@
# syntax = docker/dockerfile:1
# Make sure RUBY_VERSION matches the Ruby version in .ruby-version and Gemfile
ARG RUBY_VERSION=3.3.4
ARG RUBY_VERSION=3.3.5
FROM registry.docker.com/library/ruby:$RUBY_VERSION-slim as base
# Rails app lives here

View File

@@ -3,7 +3,7 @@ source "https://rubygems.org"
ruby file: ".ruby-version"
# Rails
gem "rails", "~> 7.2.1"
gem "rails", "~> 7.2.2"
# Drivers
gem "pg", "~> 1.5"
@@ -21,6 +21,7 @@ gem "lucide-rails", github: "maybe-finance/lucide-rails"
# Hotwire
gem "stimulus-rails"
gem "turbo-rails"
gem "hotwire_combobox"
# Background Jobs
gem "good_job"
@@ -36,6 +37,7 @@ gem "image_processing", ">= 1.2"
# Other
gem "bcrypt", "~> 3.1"
gem "jwt"
gem "faraday"
gem "faraday-retry"
gem "faraday-multipart"
@@ -48,7 +50,7 @@ gem "csv"
gem "redcarpet"
gem "stripe"
gem "intercom-rails"
gem "holidays"
gem "plaid"
group :development, :test do
gem "debug", platforms: %i[mri windows]

View File

@@ -8,29 +8,29 @@ GIT
GEM
remote: https://rubygems.org/
specs:
actioncable (7.2.1)
actionpack (= 7.2.1)
activesupport (= 7.2.1)
actioncable (7.2.2)
actionpack (= 7.2.2)
activesupport (= 7.2.2)
nio4r (~> 2.0)
websocket-driver (>= 0.6.1)
zeitwerk (~> 2.6)
actionmailbox (7.2.1)
actionpack (= 7.2.1)
activejob (= 7.2.1)
activerecord (= 7.2.1)
activestorage (= 7.2.1)
activesupport (= 7.2.1)
actionmailbox (7.2.2)
actionpack (= 7.2.2)
activejob (= 7.2.2)
activerecord (= 7.2.2)
activestorage (= 7.2.2)
activesupport (= 7.2.2)
mail (>= 2.8.0)
actionmailer (7.2.1)
actionpack (= 7.2.1)
actionview (= 7.2.1)
activejob (= 7.2.1)
activesupport (= 7.2.1)
actionmailer (7.2.2)
actionpack (= 7.2.2)
actionview (= 7.2.2)
activejob (= 7.2.2)
activesupport (= 7.2.2)
mail (>= 2.8.0)
rails-dom-testing (~> 2.2)
actionpack (7.2.1)
actionview (= 7.2.1)
activesupport (= 7.2.1)
actionpack (7.2.2)
actionview (= 7.2.2)
activesupport (= 7.2.2)
nokogiri (>= 1.8.5)
racc
rack (>= 2.2.4, < 3.2)
@@ -39,36 +39,37 @@ GEM
rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.6)
useragent (~> 0.16)
actiontext (7.2.1)
actionpack (= 7.2.1)
activerecord (= 7.2.1)
activestorage (= 7.2.1)
activesupport (= 7.2.1)
actiontext (7.2.2)
actionpack (= 7.2.2)
activerecord (= 7.2.2)
activestorage (= 7.2.2)
activesupport (= 7.2.2)
globalid (>= 0.6.0)
nokogiri (>= 1.8.5)
actionview (7.2.1)
activesupport (= 7.2.1)
actionview (7.2.2)
activesupport (= 7.2.2)
builder (~> 3.1)
erubi (~> 1.11)
rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.6)
activejob (7.2.1)
activesupport (= 7.2.1)
activejob (7.2.2)
activesupport (= 7.2.2)
globalid (>= 0.3.6)
activemodel (7.2.1)
activesupport (= 7.2.1)
activerecord (7.2.1)
activemodel (= 7.2.1)
activesupport (= 7.2.1)
activemodel (7.2.2)
activesupport (= 7.2.2)
activerecord (7.2.2)
activemodel (= 7.2.2)
activesupport (= 7.2.2)
timeout (>= 0.4.0)
activestorage (7.2.1)
actionpack (= 7.2.1)
activejob (= 7.2.1)
activerecord (= 7.2.1)
activesupport (= 7.2.1)
activestorage (7.2.2)
actionpack (= 7.2.2)
activejob (= 7.2.2)
activerecord (= 7.2.2)
activesupport (= 7.2.2)
marcel (~> 1.0)
activesupport (7.2.1)
activesupport (7.2.2)
base64
benchmark (>= 0.3)
bigdecimal
concurrent-ruby (~> 1.0, >= 1.3.1)
connection_pool (>= 2.2.5)
@@ -82,23 +83,24 @@ GEM
public_suffix (>= 2.0.2, < 7.0)
ast (2.4.2)
aws-eventstream (1.3.0)
aws-partitions (1.985.0)
aws-sdk-core (3.209.1)
aws-partitions (1.1018.0)
aws-sdk-core (3.214.0)
aws-eventstream (~> 1, >= 1.3.0)
aws-partitions (~> 1, >= 1.651.0)
aws-partitions (~> 1, >= 1.992.0)
aws-sigv4 (~> 1.9)
jmespath (~> 1, >= 1.6.1)
aws-sdk-kms (1.94.0)
aws-sdk-core (~> 3, >= 3.207.0)
aws-sdk-kms (1.96.0)
aws-sdk-core (~> 3, >= 3.210.0)
aws-sigv4 (~> 1.5)
aws-sdk-s3 (1.167.0)
aws-sdk-core (~> 3, >= 3.207.0)
aws-sdk-s3 (1.176.0)
aws-sdk-core (~> 3, >= 3.210.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.5)
aws-sigv4 (1.10.0)
aws-sigv4 (1.10.1)
aws-eventstream (~> 1, >= 1.0.2)
base64 (0.2.0)
bcrypt (3.1.20)
benchmark (0.4.0)
better_html (2.1.1)
actionview (>= 6.0)
activesupport (>= 6.0)
@@ -110,7 +112,7 @@ GEM
bindex (0.8.1)
bootsnap (1.18.4)
msgpack (~> 1.2)
brakeman (6.2.1)
brakeman (6.2.2)
racc
builder (3.3.0)
capybara (3.40.0)
@@ -131,7 +133,7 @@ GEM
rexml
crass (1.0.6)
csv (3.3.0)
date (3.3.4)
date (3.4.0)
debug (1.9.2)
irb (~> 1.10)
reline (>= 0.3.8)
@@ -141,7 +143,7 @@ GEM
dotenv (= 3.1.4)
railties (>= 6.1)
drb (2.2.1)
erb_lint (0.6.0)
erb_lint (0.7.0)
activesupport
better_html (>= 2.0.1)
parser (>= 2.7.1.4)
@@ -151,16 +153,16 @@ GEM
erubi (1.13.0)
et-orbi (1.2.11)
tzinfo
faker (3.4.2)
faker (3.5.1)
i18n (>= 1.8.11, < 2)
faraday (2.12.0)
faraday-net_http (>= 2.0, < 3.4)
faraday (2.12.1)
faraday-net_http (>= 2.0, < 3.5)
json
logger
faraday-multipart (1.0.4)
multipart-post (~> 2)
faraday-net_http (3.3.0)
net-http
faraday-net_http (3.4.0)
net-http (>= 0.5.0)
faraday-retry (2.2.1)
faraday (~> 2.0)
ffi (1.17.0-aarch64-linux-gnu)
@@ -174,7 +176,7 @@ GEM
raabro (~> 1.4)
globalid (1.2.1)
activesupport (>= 6.1)
good_job (4.3.0)
good_job (4.5.1)
activejob (>= 6.1.0)
activerecord (>= 6.1.0)
concurrent-ruby (>= 1.3.1)
@@ -183,11 +185,14 @@ GEM
thor (>= 1.0.0)
hashdiff (1.1.1)
highline (3.0.1)
holidays (8.8.0)
hotwire-livereload (1.4.1)
actioncable (>= 6.0.0)
listen (>= 3.0.0)
railties (>= 6.0.0)
hotwire_combobox (0.3.2)
rails (>= 7.0.7.2)
stimulus-rails (>= 1.2)
turbo-rails (>= 1.2)
i18n (1.14.6)
concurrent-ruby (~> 1.0)
i18n-tasks (1.0.14)
@@ -203,7 +208,7 @@ GEM
image_processing (1.13.0)
mini_magick (>= 4.9.5, < 5)
ruby-vips (>= 2.0.17, < 3)
importmap-rails (2.0.2)
importmap-rails (2.0.3)
actionpack (>= 6.0.0)
activesupport (>= 6.0.0)
railties (>= 6.0.0)
@@ -212,12 +217,14 @@ GEM
nokogiri (>= 1.6)
intercom-rails (1.0.1)
activesupport (> 4.0)
io-console (0.7.2)
io-console (0.8.0)
irb (1.14.1)
rdoc (>= 4.0.0)
reline (>= 0.4.2)
jmespath (1.6.2)
json (2.7.2)
json (2.8.2)
jwt (2.9.3)
base64
language_server-protocol (3.17.0.3)
launchy (3.0.1)
addressable (~> 2.8)
@@ -227,8 +234,8 @@ GEM
listen (3.9.0)
rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10)
logger (1.6.1)
loofah (2.22.0)
logger (1.6.2)
loofah (2.23.1)
crass (~> 1.0.2)
nokogiri (>= 1.12.0)
mail (2.8.1)
@@ -240,14 +247,14 @@ GEM
matrix (0.4.2)
mini_magick (4.13.2)
mini_mime (1.1.5)
minitest (5.25.1)
mocha (2.4.5)
minitest (5.25.4)
mocha (2.7.0)
ruby2_keywords (>= 0.0.5)
msgpack (1.7.2)
multipart-post (2.4.1)
net-http (0.4.1)
net-http (0.5.0)
uri
net-imap (0.4.14)
net-imap (0.5.0)
date
net-protocol
net-pop (0.1.2)
@@ -256,79 +263,82 @@ GEM
timeout
net-smtp (0.5.0)
net-protocol
nio4r (2.7.3)
nokogiri (1.16.7-aarch64-linux)
nio4r (2.7.4)
nokogiri (1.17.0-aarch64-linux)
racc (~> 1.4)
nokogiri (1.16.7-arm-linux)
nokogiri (1.17.0-arm-linux)
racc (~> 1.4)
nokogiri (1.16.7-arm64-darwin)
nokogiri (1.17.0-arm64-darwin)
racc (~> 1.4)
nokogiri (1.16.7-x86-linux)
nokogiri (1.17.0-x86-linux)
racc (~> 1.4)
nokogiri (1.16.7-x86_64-darwin)
nokogiri (1.17.0-x86_64-darwin)
racc (~> 1.4)
nokogiri (1.16.7-x86_64-linux)
nokogiri (1.17.0-x86_64-linux)
racc (~> 1.4)
octokit (9.1.0)
octokit (9.2.0)
faraday (>= 1, < 3)
sawyer (~> 0.9)
pagy (9.1.0)
parallel (1.25.1)
parser (3.3.4.0)
pagy (9.3.3)
parallel (1.26.3)
parser (3.3.5.0)
ast (~> 2.4.1)
racc
pg (1.5.8)
prism (1.1.0)
pg (1.5.9)
plaid (34.0.0)
faraday (>= 1.0.1, < 3.0)
faraday-multipart (>= 1.0.1, < 2.0)
prism (1.2.0)
propshaft (1.1.0)
actionpack (>= 7.0.0)
activesupport (>= 7.0.0)
rack
railties (>= 7.0.0)
psych (5.1.2)
psych (5.2.1)
date
stringio
public_suffix (6.0.1)
puma (6.4.3)
puma (6.5.0)
nio4r (~> 2.0)
raabro (1.4.0)
racc (1.8.1)
rack (3.1.7)
rack (3.1.8)
rack-session (2.0.0)
rack (>= 3.0.0)
rack-test (2.1.0)
rack (>= 1.3)
rackup (2.1.0)
rackup (2.2.1)
rack (>= 3)
webrick (~> 1.8)
rails (7.2.1)
actioncable (= 7.2.1)
actionmailbox (= 7.2.1)
actionmailer (= 7.2.1)
actionpack (= 7.2.1)
actiontext (= 7.2.1)
actionview (= 7.2.1)
activejob (= 7.2.1)
activemodel (= 7.2.1)
activerecord (= 7.2.1)
activestorage (= 7.2.1)
activesupport (= 7.2.1)
rails (7.2.2)
actioncable (= 7.2.2)
actionmailbox (= 7.2.2)
actionmailer (= 7.2.2)
actionpack (= 7.2.2)
actiontext (= 7.2.2)
actionview (= 7.2.2)
activejob (= 7.2.2)
activemodel (= 7.2.2)
activerecord (= 7.2.2)
activestorage (= 7.2.2)
activesupport (= 7.2.2)
bundler (>= 1.15.0)
railties (= 7.2.1)
railties (= 7.2.2)
rails-dom-testing (2.2.0)
activesupport (>= 5.0.0)
minitest
nokogiri (>= 1.6)
rails-html-sanitizer (1.6.0)
rails-html-sanitizer (1.6.1)
loofah (~> 2.21)
nokogiri (~> 1.14)
nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
rails-i18n (7.0.9)
i18n (>= 0.7, < 2)
railties (>= 6.0.0, < 8)
rails-settings-cached (2.9.4)
rails-settings-cached (2.9.5)
activerecord (>= 5.0.0)
railties (>= 5.0.0)
railties (7.2.1)
actionpack (= 7.2.1)
activesupport (= 7.2.1)
railties (7.2.2)
actionpack (= 7.2.2)
activesupport (= 7.2.2)
irb (~> 1.13)
rackup (>= 1.0.0)
rake (>= 12.2)
@@ -341,25 +351,24 @@ GEM
ffi (~> 1.0)
rbs (3.6.1)
logger
rdoc (6.7.0)
rdoc (6.8.1)
psych (>= 4.0.0)
redcarpet (3.6.0)
regexp_parser (2.9.2)
reline (0.5.10)
reline (0.5.12)
io-console (~> 0.5)
rexml (3.3.8)
rubocop (1.65.1)
rexml (3.3.9)
rubocop (1.67.0)
json (~> 2.3)
language_server-protocol (>= 3.17.0)
parallel (~> 1.10)
parser (>= 3.3.0.2)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 2.4, < 3.0)
rexml (>= 3.2.5, < 4.0)
rubocop-ast (>= 1.31.1, < 2.0)
rubocop-ast (>= 1.32.2, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 2.4.0, < 3.0)
rubocop-ast (1.31.3)
rubocop-ast (1.32.3)
parser (>= 3.3.1.0)
rubocop-minitest (0.35.0)
rubocop (>= 1.61, < 2.0)
@@ -377,13 +386,13 @@ GEM
rubocop-minitest
rubocop-performance
rubocop-rails
ruby-lsp (0.19.1)
ruby-lsp (0.22.1)
language_server-protocol (~> 3.17.0)
prism (>= 1.1, < 2.0)
prism (>= 1.2, < 2.0)
rbs (>= 3, < 4)
sorbet-runtime (>= 0.5.10782)
ruby-lsp-rails (0.3.18)
ruby-lsp (>= 0.19.0, < 0.20.0)
ruby-lsp-rails (0.3.27)
ruby-lsp (>= 0.22.0, < 0.23.0)
ruby-progressbar (1.13.0)
ruby-vips (2.2.2)
ffi (~> 1.12)
@@ -393,17 +402,17 @@ GEM
sawyer (0.9.2)
addressable (>= 2.3.5)
faraday (>= 0.17.3, < 3)
securerandom (0.3.1)
selenium-webdriver (4.25.0)
securerandom (0.4.0)
selenium-webdriver (4.27.0)
base64 (~> 0.2)
logger (~> 1.4)
rexml (~> 3.2, >= 3.2.5)
rubyzip (>= 1.2.2, < 3.0)
websocket (~> 1.0)
sentry-rails (5.20.1)
sentry-rails (5.22.0)
railties (>= 5.0)
sentry-ruby (~> 5.20.1)
sentry-ruby (5.20.1)
sentry-ruby (~> 5.22.0)
sentry-ruby (5.22.0)
bigdecimal
concurrent-ruby (~> 1.0, >= 1.0.2)
simplecov (0.22.0)
@@ -413,36 +422,33 @@ GEM
simplecov-html (0.12.3)
simplecov_json_formatter (0.1.4)
smart_properties (1.17.0)
sorbet-runtime (0.5.11597)
sorbet-runtime (0.5.11663)
stackprof (0.2.26)
stimulus-rails (1.3.4)
railties (>= 6.0.0)
stringio (3.1.1)
stripe (13.0.0)
tailwindcss-rails (2.7.7)
railties (>= 7.0.0)
tailwindcss-rails (2.7.7-aarch64-linux)
railties (>= 7.0.0)
tailwindcss-rails (2.7.7-arm-linux)
railties (>= 7.0.0)
tailwindcss-rails (2.7.7-arm64-darwin)
railties (>= 7.0.0)
tailwindcss-rails (2.7.7-x86_64-darwin)
railties (>= 7.0.0)
tailwindcss-rails (2.7.7-x86_64-linux)
stringio (3.1.2)
stripe (13.2.0)
tailwindcss-rails (3.0.0)
railties (>= 7.0.0)
tailwindcss-ruby
tailwindcss-ruby (3.4.14)
tailwindcss-ruby (3.4.14-aarch64-linux)
tailwindcss-ruby (3.4.14-arm-linux)
tailwindcss-ruby (3.4.14-arm64-darwin)
tailwindcss-ruby (3.4.14-x86_64-darwin)
tailwindcss-ruby (3.4.14-x86_64-linux)
terminal-table (3.0.2)
unicode-display_width (>= 1.1.1, < 3)
thor (1.3.2)
timeout (0.4.1)
turbo-rails (2.0.10)
timeout (0.4.2)
turbo-rails (2.0.11)
actionpack (>= 6.0.0)
railties (>= 6.0.0)
tzinfo (2.0.6)
concurrent-ruby (~> 1.0)
unicode-display_width (2.5.0)
uri (0.13.1)
useragent (0.16.10)
unicode-display_width (2.6.0)
uri (1.0.2)
useragent (0.16.11)
vcr (6.3.1)
base64
web-console (4.2.1)
@@ -454,14 +460,13 @@ GEM
addressable (>= 2.8.0)
crack (>= 0.3.2)
hashdiff (>= 0.4.0, < 2.0.0)
webrick (1.8.2)
websocket (1.2.11)
websocket-driver (0.7.6)
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.5)
xpath (3.2.0)
nokogiri (~> 1.8)
zeitwerk (2.6.18)
zeitwerk (2.7.1)
PLATFORMS
aarch64-linux
@@ -487,22 +492,24 @@ DEPENDENCIES
faraday-multipart
faraday-retry
good_job
holidays
hotwire-livereload
hotwire_combobox
i18n-tasks
image_processing (>= 1.2)
importmap-rails
inline_svg
intercom-rails
jwt
letter_opener
lucide-rails!
mocha
octokit
pagy
pg (~> 1.5)
plaid
propshaft
puma (>= 5.0)
rails (~> 7.2.1)
rails (~> 7.2.2)
rails-settings-cached
redcarpet
rubocop-rails-omakase
@@ -522,7 +529,7 @@ DEPENDENCIES
webmock
RUBY VERSION
ruby 3.3.4p94
ruby 3.3.5p100
BUNDLED WITH
2.5.9
2.5.22

View File

@@ -1,3 +1,3 @@
web: ${DEBUG:+rdbg -O -n -c --} bin/rails server -b 0.0.0.0
css: bin/rails tailwindcss:watch
web: bundle exec ${DEBUG:+rdbg -O -n -c --} bin/rails server -b 0.0.0.0
css: bundle exec bin/rails tailwindcss:watch
worker: bundle exec good_job start

View File

@@ -4,7 +4,7 @@
# Maybe: The OS for your personal finances
<b>Get
involved: [Discord](https://link.maybe.co/discord) • [Website](https://maybe.co) • [Issues](https://github.com/maybe-finance/maybe/issues)</b>
involved: [Discord](https://link.maybe.co/discord) • [Website](https://maybefinance.com) • [Issues](https://github.com/maybe-finance/maybe/issues)</b>
_If you're looking for the previous React codebase, you can find it
at [maybe-finance/maybe-archive](https://github.com/maybe-finance/maybe-archive)._
@@ -42,14 +42,14 @@ The instructions below are for developers to get started with contributing to th
### Requirements
- Ruby 3.3.4
- See `.ruby-version` file for required Ruby version
- PostgreSQL >9.3 (ideally, latest stable version)
After cloning the repo, the basic setup commands are:
```sh
cd maybe
cp .env.example .env
cp .env.local.example .env.local
bin/setup
bin/dev

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 299 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

View File

@@ -0,0 +1,10 @@
<svg width="944" height="201" viewBox="0 0 944 201" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0 56.5502L14.4845 52.101L28.9689 50.1276L43.4534 51.7926L57.9379 40.2042L72.4224 35.6995L86.9068 35.0612L101.391 51.2218L115.876 73.6398L130.36 65.7562L144.845 64.7572L159.329 78.5795L173.814 81.9833L188.298 71.3186L202.783 80.5112L217.267 86L231.752 84.5697L246.236 83.0772L260.721 78.4002L275.205 77.343L289.689 71.8152L304.174 52.25L318.658 51.5349L333.143 48.185L347.627 47.2522L362.112 45.4586L376.596 49.2356L391.081 47.5566L405.565 31.0549L420.05 28.5641L434.534 36.6352H449.019L463.503 42.7572L477.988 37.7564L492.472 42.3467L506.957 49.3852L521.441 59.4839L535.925 52.7514L550.41 47.1535L564.894 58.6703L579.379 49.8343L593.863 50.5123H608.348L622.832 54.192L637.317 58.4763L651.801 57.2522L666.286 59.3943L677.01 62.8533L688.553 59.3943L709.129 67.4827L724.224 60.8386L738.708 52.27L753.193 58.6965L767.677 37.887L782.162 28.3178L796.646 16.383L811.13 20.9733L825.615 10.2626L840.099 11.7927L854.584 6.59032L869.068 15.771L883.553 8.12043L898.037 6.59032L912.522 2L927.006 14.8529L944 15.771" stroke="#0B0B0B" stroke-opacity="0.25" stroke-width="2" stroke-miterlimit="16"/>
<path d="M14.4845 52.5538L0 57.0432V201H944V15.8954L927.006 14.9691L912.522 2L898.037 6.63181L883.553 8.17575L869.068 15.8954L854.584 6.63181L840.099 11.8812L825.615 10.3373L811.13 21.1448L796.646 16.513L782.161 28.5557L767.677 38.2114L753.193 59.2089L738.708 52.7244L724.224 61.3704L709.129 68.0745L688.553 59.9131L677.01 63.4034L666.286 59.9131L651.801 57.7516L637.317 58.9868L622.832 54.6637L608.348 50.9508H593.863L579.379 50.2667L564.894 59.1826L550.41 47.5616L535.925 53.2102L521.441 60.0035L506.957 49.8135L492.472 42.7114L477.988 38.0796L463.503 43.1256L449.019 36.9483H434.534L420.05 28.8042L405.565 31.3175L391.081 47.9684L376.596 49.6626L362.112 45.8514L347.627 47.6612L333.143 48.6024L318.658 51.9826L304.174 52.7042L289.689 72.4463L275.205 78.024L260.721 79.0908L246.236 83.8101L231.752 85.3161L217.267 86.7593L202.783 81.2209L188.298 71.9451L173.814 82.7063L159.329 79.2716L144.845 65.3245L130.36 66.3325L115.876 74.2874L101.391 51.6667L86.9068 35.3601L72.4224 36.0041L57.9379 40.5496L43.4534 52.2427L28.9689 50.5627L14.4845 52.5538Z" fill="url(#paint0_linear_4023_1299)" fill-opacity="0.5"/>
<defs>
<linearGradient id="paint0_linear_4023_1299" x1="445.5" y1="174.496" x2="445.5" y2="51.9672" gradientUnits="userSpaceOnUse">
<stop stop-color="white"/>
<stop offset="1" stop-color="#E5E5E5" stop-opacity="0.6"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -0,0 +1,6 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="Frame 1321315963">
<rect width="20" height="20" rx="10" fill="#635BFF"/>
<path id="Vector" fill-rule="evenodd" clip-rule="evenodd" d="M9.35663 7.69056C9.35663 7.20077 9.75747 7.01238 10.4214 7.01238C11.3734 7.01238 12.5759 7.30124 13.5279 7.81615V4.86482C12.4882 4.45037 11.461 4.28711 10.4214 4.28711C7.87854 4.28711 6.1875 5.61835 6.1875 7.84127C6.1875 11.3075 10.9475 10.7549 10.9475 12.2494C10.9475 12.8271 10.4464 13.0155 9.74495 13.0155C8.70527 13.0155 7.37749 12.5885 6.32529 12.0108V14.9998C7.49023 15.5022 8.66769 15.7157 9.74495 15.7157C12.3504 15.7157 14.1416 14.4221 14.1416 12.1741C14.1291 8.43154 9.35663 9.09716 9.35663 7.69056Z" fill="white"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 775 B

View File

@@ -19,7 +19,8 @@
@apply focus-within:border-gray-900 focus-within:shadow-none focus-within:ring-4 focus-within:ring-gray-100;
}
.form-field__label {
.form-field__label, .hw-combobox__label {
@apply block text-xs text-gray-500 peer-disabled:text-gray-400;
}
@@ -100,7 +101,7 @@
}
.btn {
@apply px-3 py-2 rounded-lg text-sm font-medium cursor-pointer;
@apply px-3 py-2 rounded-lg text-sm font-medium cursor-pointer disabled:cursor-not-allowed focus:outline-gray-500;
}
.btn--primary {
@@ -112,7 +113,7 @@
}
.btn--outline {
@apply border border-alpha-black-200 text-gray-900 hover:bg-gray-50;
@apply border border-alpha-black-200 text-gray-900 hover:bg-gray-50 disabled:bg-gray-50 disabled:hover:bg-gray-50 disabled:text-gray-400;
}
.btn--ghost {
@@ -120,6 +121,33 @@
}
}
.combobox {
.hw-combobox__main__wrapper, .hw-combobox__input {
@apply w-full;
}
.hw-combobox__main__wrapper {
@apply border-0 p-0 focus:border-0 ring-0 focus:ring-0 shadow-none focus:shadow-none focus-within:shadow-none;
}
.hw-combobox__listbox {
@apply absolute top-[160%] right-0 w-full bg-transparent rounded z-30;
}
.hw_combobox__pagination__wrapper {
@apply h-px;
&:only-child {
@apply bg-transparent;
}
}
--hw-border-color: rgba(0, 0, 0, 0.2);
--hw-handle-width: 20px;
--hw-handle-height: 20px;
--hw-handle-offset-right: 0px;
}
/* Small, single purpose classes that should take precedence over other styles */
@layer utilities {
.scrollbar::-webkit-scrollbar {
@@ -134,4 +162,20 @@
.scrollbar::-webkit-scrollbar-thumb:hover {
background: #a6a6a6;
}
}
/* Custom scrollbar implementation for Windows browsers */
.windows {
::-webkit-scrollbar {
width: 4px;
}
::-webkit-scrollbar-thumb {
background: #d6d6d6;
border-radius: 10px;
}
::-webkit-scrollbar-thumb:hover {
background: #a6a6a6;
}
}

View File

@@ -1,14 +0,0 @@
class Account::CashesController < ApplicationController
layout :with_sidebar
before_action :set_account
def index
end
private
def set_account
@account = Current.family.accounts.find(params[:account_id])
end
end

View File

@@ -2,47 +2,25 @@ class Account::EntriesController < ApplicationController
layout :with_sidebar
before_action :set_account
before_action :set_entry, only: %i[edit update show destroy]
def edit
render entryable_view_path(:edit)
end
def update
@entry.update!(entry_params)
@entry.sync_account_later
respond_to do |format|
format.html { redirect_to account_entry_path(@account, @entry), notice: t(".success") }
format.turbo_stream { render turbo_stream: turbo_stream.replace(@entry) }
end
end
def show
render entryable_view_path(:show)
end
def destroy
@entry.destroy!
@entry.sync_account_later
redirect_to account_url(@entry.account), notice: t(".success")
def index
@q = search_params
@pagy, @entries = pagy(entries_scope.search(@q).reverse_chronological, limit: params[:per_page] || "10")
end
private
def entryable_view_path(action)
@entry.entryable_type.underscore.pluralize + "/" + action.to_s
end
def set_account
@account = Current.family.accounts.find(params[:account_id])
end
def set_entry
@entry = @account.entries.find(params[:id])
def entries_scope
scope = Current.family.entries
scope = scope.where(account: @account) if @account
scope
end
def entry_params
params.require(:account_entry).permit(:name, :date, :amount, :currency)
def search_params
params.fetch(:q, {})
.permit(:search)
end
end

View File

@@ -1,11 +1,10 @@
class Account::HoldingsController < ApplicationController
layout :with_sidebar
before_action :set_account
before_action :set_holding, only: %i[show destroy]
def index
@holdings = @account.holdings.current
@account = Current.family.accounts.find(params[:account_id])
end
def show
@@ -13,16 +12,17 @@ class Account::HoldingsController < ApplicationController
def destroy
@holding.destroy_holding_and_entries!
redirect_back_or_to account_holdings_path(@account)
flash[:notice] = t(".success")
respond_to do |format|
format.html { redirect_back_or_to account_path(@holding.account) }
format.turbo_stream { render turbo_stream: turbo_stream.action(:redirect, account_path(@holding.account)) }
end
end
private
def set_account
@account = Current.family.accounts.find(params[:account_id])
end
def set_holding
@holding = @account.holdings.current.find(params[:id])
@holding = Current.family.holdings.find(params[:id])
end
end

View File

@@ -1,10 +0,0 @@
class Account::LogosController < ApplicationController
def show
@account = Current.family.accounts.find(params[:account_id])
render_placeholder
end
def render_placeholder
render formats: :svg
end
end

View File

@@ -1,59 +1,37 @@
class Account::TradesController < ApplicationController
layout :with_sidebar
include EntryableResource
before_action :set_account
before_action :set_entry, only: :update
def new
@entry = @account.entries.account_trades.new(entryable_attributes: {})
end
def index
@entries = @account.entries.reverse_chronological.where(entryable_type: %w[Account::Trade Account::Transaction])
end
def create
@builder = Account::EntryBuilder.new(entry_params)
if entry = @builder.save
entry.sync_account_later
redirect_to account_path(@account), notice: t(".success")
else
flash[:alert] = t(".failure")
redirect_back_or_to account_path(@account)
end
end
def update
@entry.update!(entry_params)
respond_to do |format|
format.html { redirect_to account_entry_path(@account, @entry), notice: t(".success") }
format.turbo_stream { render turbo_stream: turbo_stream.replace(@entry) }
end
end
permitted_entryable_attributes :id, :qty, :price
private
def set_account
@account = Current.family.accounts.find(params[:account_id])
def build_entry
Account::TradeBuilder.new(create_entry_params)
end
def set_entry
@entry = @account.entries.find(params[:id])
def create_entry_params
params.require(:account_entry).permit(
:account_id, :date, :amount, :currency, :qty, :price, :ticker, :type, :transfer_account_id
).tap do |params|
account_id = params.delete(:account_id)
params[:account] = Current.family.accounts.find(account_id)
end
end
def entry_params
params.require(:account_entry)
.permit(
:type, :date, :qty, :ticker, :price, :amount, :notes, :excluded, :currency, :transfer_account_id, :entryable_type,
entryable_attributes: [
:id,
:qty,
:ticker,
:price
]
)
.merge(account: @account)
def update_entry_params
return entry_params unless entry_params[:entryable_attributes].present?
update_params = entry_params
update_params = update_params.merge(entryable_type: "Account::Trade")
qty = update_params[:entryable_attributes][:qty]
price = update_params[:entryable_attributes][:price]
if qty.present? && price.present?
qty = update_params[:nature] == "inflow" ? -qty.to_d : qty.to_d
update_params[:entryable_attributes][:qty] = qty
update_params[:amount] = qty * price.to_d
end
update_params.except(:nature)
end
end

View File

@@ -0,0 +1,22 @@
class Account::TransactionCategoriesController < ApplicationController
def update
@entry = Current.family.entries.account_transactions.find(params[:transaction_id])
@entry.update!(entry_params)
respond_to do |format|
format.html { redirect_back_or_to account_transaction_path(@entry) }
format.turbo_stream do
render turbo_stream: turbo_stream.replace(
"category_menu_account_transaction_#{@entry.account_transaction_id}",
partial: "categories/menu",
locals: { transaction: @entry.account_transaction }
)
end
end
end
private
def entry_params
params.require(:account_entry).permit(:entryable_type, entryable_attributes: [ :id, :category_id ])
end
end

View File

@@ -1,57 +1,55 @@
class Account::TransactionsController < ApplicationController
layout :with_sidebar
include EntryableResource
before_action :set_account
before_action :set_entry, only: :update
permitted_entryable_attributes :id, :category_id, :merchant_id, { tag_ids: [] }
def index
@pagy, @entries = pagy(
@account.entries.account_transactions.reverse_chronological,
limit: params[:per_page] || "10"
)
def bulk_delete
destroyed = Current.family.entries.destroy_by(id: bulk_delete_params[:entry_ids])
destroyed.map(&:account).uniq.each(&:sync_later)
redirect_back_or_to transactions_url, notice: t(".success", count: destroyed.count)
end
def update
@entry.update!(entry_params)
def bulk_edit
end
respond_to do |format|
format.html { redirect_to account_entry_path(@account, @entry), notice: t(".success") }
format.turbo_stream { render turbo_stream: turbo_stream.replace(@entry) }
end
def bulk_update
updated = Current.family
.entries
.where(id: bulk_update_params[:entry_ids])
.bulk_update!(bulk_update_params)
redirect_back_or_to transactions_url, notice: t(".success", count: updated)
end
def mark_transfers
Current.family
.entries
.where(id: bulk_update_params[:entry_ids])
.mark_transfers!
redirect_back_or_to transactions_url, notice: t(".success")
end
def unmark_transfers
Current.family
.entries
.where(id: bulk_update_params[:entry_ids])
.update_all marked_as_transfer: false
redirect_back_or_to transactions_url, notice: t(".success")
end
private
def set_account
@account = Current.family.accounts.find(params[:account_id])
def bulk_delete_params
params.require(:bulk_delete).permit(entry_ids: [])
end
def set_entry
@entry = @account.entries.find(params[:id])
def bulk_update_params
params.require(:bulk_update).permit(:date, :notes, :category_id, :merchant_id, entry_ids: [])
end
def entry_params
params.require(:account_entry)
.permit(
:name, :date, :amount, :currency, :excluded, :notes, :entryable_type, :nature,
entryable_attributes: [
:id,
:category_id,
:merchant_id,
{ tag_ids: [] }
]
).tap do |permitted_params|
nature = permitted_params.delete(:nature)
if permitted_params[:amount]
amount_value = permitted_params[:amount].to_d
if nature == "income"
amount_value *= -1
end
permitted_params[:amount] = amount_value
end
end
def search_params
params.fetch(:q, {})
.permit(:start_date, :end_date, :search, :amount, :amount_operator, accounts: [], account_ids: [], categories: [], merchants: [], types: [], tags: [])
end
end

View File

@@ -1,21 +1,22 @@
class Account::TransfersController < ApplicationController
layout :with_sidebar
before_action :set_transfer, only: :destroy
before_action :set_transfer, only: %i[destroy show update]
def new
@transfer = Account::Transfer.new
end
def show
end
def create
from_account = Current.family.accounts.find(transfer_params[:from_account_id])
to_account = Current.family.accounts.find(transfer_params[:to_account_id])
@transfer = Account::Transfer.build_from_accounts from_account, to_account, \
date: transfer_params[:date],
amount: transfer_params[:amount].to_d,
currency: transfer_params[:currency],
name: transfer_params[:name]
amount: transfer_params[:amount].to_d
if @transfer.save
@transfer.entries.each(&:sync_account_later)
@@ -28,18 +29,33 @@ class Account::TransfersController < ApplicationController
end
end
def update
@transfer.update_entries!(transfer_update_params)
redirect_back_or_to transactions_url, notice: t(".success")
end
def destroy
@transfer.destroy_and_remove_marks!
@transfer.destroy!
redirect_back_or_to transactions_url, notice: t(".success")
end
private
def set_transfer
@transfer = Account::Transfer.find(params[:id])
record = Account::Transfer.find(params[:id])
unless record.entries.all? { |entry| Current.family.accounts.include?(entry.account) }
raise ActiveRecord::RecordNotFound
end
@transfer = record
end
def transfer_params
params.require(:account_transfer).permit(:from_account_id, :to_account_id, :amount, :date, :name)
params.require(:account_transfer).permit(:from_account_id, :to_account_id, :amount, :date, :name, :excluded)
end
def transfer_update_params
params.require(:account_transfer).permit(:excluded, :notes)
end
end

View File

@@ -1,35 +1,3 @@
class Account::ValuationsController < ApplicationController
layout :with_sidebar
before_action :set_account
def new
@entry = @account.entries.account_valuations.new(entryable_attributes: {})
end
def create
@entry = @account.entries.account_valuations.new(entry_params.merge(entryable_attributes: {}))
if @entry.save
@entry.sync_account_later
redirect_back_or_to account_valuations_path(@account), notice: t(".success")
else
flash[:alert] = @entry.errors.full_messages.to_sentence
redirect_to account_path(@account)
end
end
def index
@entries = @account.entries.account_valuations.reverse_chronological
end
private
def set_account
@account = Current.family.accounts.find(params[:account_id])
end
def entry_params
params.require(:account_entry).permit(:name, :date, :amount, :currency)
end
include EntryableResource
end

View File

@@ -1,88 +1,51 @@
class AccountsController < ApplicationController
layout :with_sidebar
include Filterable
before_action :set_account, only: %i[edit show destroy sync update]
before_action :set_account, only: %i[sync]
def index
@institutions = Current.family.institutions
@accounts = Current.family.accounts.ungrouped.alphabetically
@manual_accounts = Current.family.accounts.where(scheduled_for_deletion: false).manual.alphabetically
@plaid_items = Current.family.plaid_items.where(scheduled_for_deletion: false).ordered
end
def summary
@period = Period.from_param(params[:period])
snapshot = Current.family.snapshot(@period)
@net_worth_series = snapshot[:net_worth_series]
@asset_series = snapshot[:asset_series]
@liability_series = snapshot[:liability_series]
@accounts = Current.family.accounts
@accounts = Current.family.accounts.active
@account_groups = @accounts.by_group(period: @period, currency: Current.family.currency)
end
def list
@period = Period.from_param(params[:period])
render layout: false
end
def new
@account = Account.new(
accountable: Accountable.from_type(params[:type])&.new,
currency: Current.family.currency
)
@account.accountable.address = Address.new if @account.accountable.is_a?(Property)
if params[:institution_id]
@account.institution = Current.family.institutions.find_by(id: params[:institution_id])
end
end
def show
@series = @account.series(period: @period)
@trend = @series.trend
end
def edit
@account.accountable.build_address if @account.accountable.is_a?(Property) && @account.accountable.address.blank?
end
def update
@account.update_with_sync!(account_params)
redirect_back_or_to account_path(@account), notice: t(".success")
end
def create
@account = Current.family
.accounts
.create_with_optional_start_balance! \
attributes: account_params.except(:start_date, :start_balance),
start_date: account_params[:start_date],
start_balance: account_params[:start_balance]
@account.sync_later
redirect_back_or_to account_path(@account), notice: t(".success")
end
def destroy
@account.destroy!
redirect_to accounts_path, notice: t(".success")
end
def sync
unless @account.syncing?
@account.sync_later
end
redirect_to account_path(@account)
end
def chart
@account = Current.family.accounts.find(params[:id])
render layout: "application"
end
def sync_all
Current.family.accounts.active.sync
redirect_back_or_to accounts_path, notice: t(".success")
unless Current.family.syncing?
Current.family.sync_later
end
redirect_to accounts_path
end
private
def set_account
@account = Current.family.accounts.find(params[:id])
end
def account_params
params.require(:account).permit(:name, :accountable_type, :balance, :start_date, :start_balance, :currency, :subtype, :is_active, :institution_id)
end
end

View File

@@ -1,12 +1,41 @@
class ApplicationController < ActionController::Base
include Localize, AutoSync, Authentication, Invitable, SelfHostable, StoreLocation
include Onboardable, Localize, AutoSync, Authentication, Invitable, SelfHostable, StoreLocation, Impersonatable
include Pagy::Backend
helper_method :require_upgrade?, :subscription_pending?
before_action :detect_os
private
def require_upgrade?
return false if self_hosted?
return false unless Current.session
return false if Current.family.subscribed?
return false if subscription_pending? || request.path == settings_billing_path
true
end
def subscription_pending?
subscribed_at = Current.session.subscribed_at
subscribed_at.present? && subscribed_at <= Time.current && subscribed_at > 1.hour.ago
end
def with_sidebar
return "turbo_rails/frame" if turbo_frame_request?
"with_sidebar"
end
def detect_os
user_agent = request.user_agent
@os = case user_agent
when /Windows/i then "windows"
when /Macintosh/i then "mac"
when /Linux/i then "linux"
when /Android/i then "android"
when /iPhone|iPad/i then "ios"
else ""
end
end
end

View File

@@ -1,7 +1,7 @@
class CategoriesController < ApplicationController
layout :with_sidebar
before_action :set_category, only: %i[edit update]
before_action :set_category, only: %i[edit update destroy]
before_action :set_transaction, only: :create
def index
@@ -13,12 +13,14 @@ class CategoriesController < ApplicationController
end
def create
Category.transaction do
category = Current.family.categories.create!(category_params)
@transaction.update!(category_id: category.id) if @transaction
end
@category = Current.family.categories.new(category_params)
redirect_back_or_to transactions_path, notice: t(".success")
if @category.save
@transaction.update(category_id: @category.id) if @transaction
redirect_back_or_to transactions_path, notice: t(".success")
else
redirect_back_or_to transactions_path, alert: t(".failure", error: @category.errors.full_messages.to_sentence)
end
end
def edit
@@ -30,6 +32,12 @@ class CategoriesController < ApplicationController
redirect_back_or_to transactions_path, notice: t(".success")
end
def destroy
@category.destroy
redirect_back_or_to categories_path, notice: t(".success")
end
private
def set_category
@category = Current.family.categories.find(params[:id])

View File

@@ -0,0 +1,75 @@
module AccountableResource
extend ActiveSupport::Concern
included do
layout :with_sidebar
before_action :set_account, only: [ :show, :edit, :update, :destroy ]
before_action :set_link_token, only: :new
end
class_methods do
def permitted_accountable_attributes(*attrs)
@permitted_accountable_attributes = attrs if attrs.any?
@permitted_accountable_attributes ||= [ :id ]
end
end
def new
@account = Current.family.accounts.build(
currency: Current.family.currency,
accountable: accountable_type.new
)
end
def show
end
def edit
end
def create
@account = Current.family.accounts.create_and_sync(account_params.except(:return_to))
redirect_to account_params[:return_to].presence || @account, notice: t("accounts.create.success", type: accountable_type.name.underscore.humanize)
end
def update
@account.update_with_sync!(account_params.except(:return_to))
redirect_back_or_to @account, notice: t("accounts.update.success", type: accountable_type.name.underscore.humanize)
end
def destroy
@account.destroy_later
redirect_to accounts_path, notice: t("accounts.destroy.success", type: accountable_type.name.underscore.humanize)
end
private
def set_link_token
@link_token = Current.family.get_link_token(
webhooks_url: webhooks_url,
redirect_url: accounts_url,
accountable_type: accountable_type.name
)
end
def webhooks_url
return webhooks_plaid_url if Rails.env.production?
base_url = ENV.fetch("DEV_WEBHOOKS_URL", root_url.chomp("/"))
base_url + "/webhooks/plaid"
end
def accountable_type
controller_name.classify.constantize
end
def set_account
@account = Current.family.accounts.find(params[:id])
end
def account_params
params.require(:account).permit(
:name, :is_active, :balance, :subtype, :currency, :accountable_type, :return_to,
accountable_attributes: self.class.permitted_accountable_attributes
)
end
end

View File

@@ -14,7 +14,7 @@ module Authentication
private
def authenticate_user!
if session_record = Session.find_by_id(cookies.signed[:session_token])
if session_record = find_session_by_cookie
Current.session = session_record
else
if self_hosted_first_login?
@@ -25,6 +25,10 @@ module Authentication
end
end
def find_session_by_cookie
Session.find_by(id: cookies.signed[:session_token])
end
def create_session_for(user)
session = user.sessions.create!
cookies.signed.permanent[:session_token] = { value: session.id, httponly: true }

View File

@@ -2,12 +2,20 @@ module AutoSync
extend ActiveSupport::Concern
included do
before_action :sync_family, if: -> { Current.family.present? && Current.family.needs_sync? }
before_action :sync_family, if: :family_needs_auto_sync?
end
private
def sync_family
Current.family.sync
Current.family.update!(last_synced_at: Time.current)
Current.family.sync_later
end
def family_needs_auto_sync?
return false unless Current.family.present?
return false unless Current.family.accounts.any?
Current.family.last_synced_at.blank? ||
Current.family.last_synced_at.to_date < Date.current
end
end

View File

@@ -0,0 +1,126 @@
module EntryableResource
extend ActiveSupport::Concern
included do
layout :with_sidebar
before_action :set_entry, only: %i[show update destroy]
end
class_methods do
def permitted_entryable_attributes(*attrs)
@permitted_entryable_attributes = attrs if attrs.any?
@permitted_entryable_attributes ||= [ :id ]
end
end
def show
end
def new
account = Current.family.accounts.find_by(id: params[:account_id])
@entry = Current.family.entries.new(
account: account,
currency: account ? account.currency : Current.family.currency,
entryable: entryable_type.new
)
end
def create
@entry = build_entry
if @entry.save
@entry.sync_account_later
flash[:notice] = t("account.entries.create.success")
respond_to do |format|
format.html { redirect_back_or_to account_path(@entry.account) }
redirect_target_url = request.referer || account_path(@entry.account)
format.turbo_stream { render turbo_stream: turbo_stream.action(:redirect, redirect_target_url) }
end
else
render :new, status: :unprocessable_entity
end
end
def update
if @entry.update(update_entry_params)
@entry.sync_account_later
respond_to do |format|
format.html { redirect_back_or_to account_path(@entry.account), notice: t("account.entries.update.success") }
format.turbo_stream do
render turbo_stream: turbo_stream.replace(
"header_account_entry_#{@entry.id}",
partial: "#{entryable_type.name.underscore.pluralize}/header",
locals: { entry: @entry }
)
end
end
else
render :show, status: :unprocessable_entity
end
end
def destroy
account = @entry.account
@entry.destroy!
@entry.sync_account_later
flash[:notice] = t("account.entries.destroy.success")
respond_to do |format|
format.html { redirect_back_or_to account_path(account) }
redirect_target_url = request.referer || account_path(@entry.account)
format.turbo_stream { render turbo_stream: turbo_stream.action(:redirect, redirect_target_url) }
end
end
private
def entryable_type
permitted_entryable_types = %w[Account::Transaction Account::Valuation Account::Trade]
klass = params[:entryable_type] || "Account::#{controller_name.classify}"
klass.constantize if permitted_entryable_types.include?(klass)
end
def set_entry
@entry = Current.family.entries.find(params[:id])
end
def build_entry
Current.family.entries.new(create_entry_params)
end
def update_entry_params
prepared_entry_params
end
def create_entry_params
prepared_entry_params.merge({
entryable_type: entryable_type.name,
entryable_attributes: entry_params[:entryable_attributes] || {}
})
end
def prepared_entry_params
default_params = entry_params.except(:nature)
default_params = default_params.merge(entryable_type: entryable_type.name) if entry_params[:entryable_attributes].present?
if entry_params[:nature].present? && entry_params[:amount].present?
signed_amount = entry_params[:nature] == "inflow" ? -entry_params[:amount].to_d : entry_params[:amount].to_d
default_params = default_params.merge(amount: signed_amount)
end
default_params
end
def entry_params
params.require(:account_entry).permit(
:account_id, :name, :date, :amount, :currency, :excluded, :notes, :nature,
entryable_attributes: self.class.permitted_entryable_attributes
)
end
end

View File

@@ -1,23 +0,0 @@
module Filterable
extend ActiveSupport::Concern
included do
before_action :set_period
end
private
def set_period
@period = Period.find_by_name(params[:period])
if @period.nil?
start_date = params[:start_date].presence&.to_date
end_date = params[:end_date].presence&.to_date
if start_date.is_a?(Date) && end_date.is_a?(Date) && start_date <= end_date
@period = Period.new(name: "custom", date_range: start_date..end_date)
else
params[:period] = "last_30_days"
@period = Period.find_by_name(params[:period])
end
end
end
end

View File

@@ -0,0 +1,21 @@
module Impersonatable
extend ActiveSupport::Concern
included do
after_action :create_impersonation_session_log
end
private
def create_impersonation_session_log
return unless Current.session&.active_impersonator_session.present?
Current.session.active_impersonator_session.logs.create!(
controller: controller_name,
action: action_name,
path: request.fullpath,
method: request.method,
ip_address: request.ip,
user_agent: request.user_agent
)
end
end

View File

@@ -7,6 +7,7 @@ module Invitable
private
def invite_code_required?
return false if @invitation.present?
self_hosted? ? Setting.require_invite_for_signup : ENV["REQUIRE_INVITE_CODE"] == "true"
end

View File

@@ -3,6 +3,7 @@ module Localize
included do
around_action :switch_locale
around_action :switch_timezone
end
private
@@ -10,4 +11,9 @@ module Localize
locale = Current.family.try(:locale) || I18n.default_locale
I18n.with_locale(locale, &action)
end
def switch_timezone(&action)
timezone = Current.family.try(:timezone) || Time.zone
Time.use_zone(timezone, &action)
end
end

View File

@@ -0,0 +1,17 @@
module Onboardable
extend ActiveSupport::Concern
included do
before_action :redirect_to_onboarding, if: :needs_onboarding?
end
private
def redirect_to_onboarding
redirect_to onboarding_path
end
def needs_onboarding?
Current.user && Current.user.onboarded_at.blank? &&
!%w[/users /onboarding /sessions].any? { |path| request.path.start_with?(path) }
end
end

View File

@@ -5,6 +5,8 @@ module StoreLocation
helper_method :previous_path
before_action :store_return_to
after_action :clear_previous_path
rescue_from ActiveRecord::RecordNotFound, with: :handle_not_found
end
def previous_path
@@ -12,6 +14,14 @@ module StoreLocation
end
private
def handle_not_found
if request.fullpath == session[:return_to]
session.delete(:return_to)
redirect_to fallback_path
else
head :not_found
end
end
def store_return_to
if params[:return_to].present?

View File

@@ -1,41 +1,12 @@
class CreditCardsController < ApplicationController
before_action :set_account, only: :update
include AccountableResource
def create
account = Current.family
.accounts
.create_with_optional_start_balance! \
attributes: account_params.except(:start_date, :start_balance),
start_date: account_params[:start_date],
start_balance: account_params[:start_balance]
account.sync_later
redirect_to account, notice: t(".success")
end
def update
@account.update_with_sync!(account_params)
redirect_to @account, notice: t(".success")
end
private
def set_account
@account = Current.family.accounts.find(params[:id])
end
def account_params
params.require(:account)
.permit(
:name, :balance, :institution_id, :start_date, :start_balance, :currency, :accountable_type,
accountable_attributes: [
:id,
:available_credit,
:minimum_payment,
:apr,
:annual_fee,
:expiration_date
]
)
end
permitted_accountable_attributes(
:id,
:available_credit,
:minimum_payment,
:apr,
:annual_fee,
:expiration_date
)
end

View File

@@ -0,0 +1,3 @@
class CryptosController < ApplicationController
include AccountableResource
end

View File

@@ -0,0 +1,3 @@
class DepositoriesController < ApplicationController
include AccountableResource
end

View File

@@ -0,0 +1,58 @@
class ImpersonationSessionsController < ApplicationController
before_action :require_super_admin!, only: [ :create, :join, :leave ]
before_action :set_impersonation_session, only: [ :approve, :reject, :complete ]
def create
Current.true_user.request_impersonation_for(session_params[:impersonated_id])
redirect_to root_path, notice: t(".success")
end
def join
@impersonation_session = Current.true_user.impersonator_support_sessions.find_by(id: params[:impersonation_session_id])
Current.session.update!(active_impersonator_session: @impersonation_session)
redirect_to root_path, notice: t(".success")
end
def leave
Current.session.update!(active_impersonator_session: nil)
redirect_to root_path, notice: t(".success")
end
def approve
raise_unauthorized! unless @impersonation_session.impersonated == Current.true_user
@impersonation_session.approve!
redirect_to root_path, notice: t(".success")
end
def reject
raise_unauthorized! unless @impersonation_session.impersonated == Current.true_user
@impersonation_session.reject!
redirect_to root_path, notice: t(".success")
end
def complete
@impersonation_session.complete!
redirect_to root_path, notice: t(".success")
end
private
def session_params
params.require(:impersonation_session).permit(:impersonated_id)
end
def set_impersonation_session
@impersonation_session =
Current.true_user.impersonated_support_sessions.find_by(id: params[:id]) ||
Current.true_user.impersonator_support_sessions.find_by(id: params[:id])
end
def require_super_admin!
raise_unauthorized! unless Current.true_user&.super_admin?
end
def raise_unauthorized!
raise ActionController::RoutingError.new("Not Found")
end
end

View File

@@ -20,6 +20,21 @@ class Import::ConfigurationsController < ApplicationController
end
def import_params
params.require(:import).permit(:date_col_label, :date_format, :name_col_label, :category_col_label, :tags_col_label, :amount_col_label, :signage_convention, :account_col_label, :notes_col_label, :entity_type_col_label)
params.require(:import).permit(
:date_col_label,
:amount_col_label,
:name_col_label,
:category_col_label,
:tags_col_label,
:account_col_label,
:qty_col_label,
:ticker_col_label,
:price_col_label,
:entity_type_col_label,
:notes_col_label,
:currency_col_label,
:date_format,
:signage_convention
)
end
end

View File

@@ -32,7 +32,7 @@ class Import::UploadsController < ApplicationController
require "csv"
begin
csv = CSV.parse(str || "", headers: true)
csv = CSV.parse(str || "", headers: true, col_sep: upload_params[:col_sep])
return false if csv.headers.empty?
return false if csv.count == 0
true

View File

@@ -24,7 +24,11 @@ class ImportsController < ApplicationController
end
def show
redirect_to import_confirm_path(@import), alert: "Please finalize your mappings before proceeding." unless @import.publishable?
if !@import.uploaded?
redirect_to import_upload_path(@import), alert: "Please finalize your file upload."
elsif !@import.publishable?
redirect_to import_confirm_path(@import), alert: "Please finalize your mappings before proceeding."
end
end
def destroy

View File

@@ -1,40 +0,0 @@
class InstitutionsController < ApplicationController
before_action :set_institution, except: %i[new create]
def new
@institution = Institution.new
end
def create
Current.family.institutions.create!(institution_params)
redirect_to accounts_path, notice: t(".success")
end
def edit
end
def update
@institution.update!(institution_params)
redirect_to accounts_path, notice: t(".success")
end
def destroy
@institution.destroy!
redirect_to accounts_path, notice: t(".success")
end
def sync
@institution.sync
redirect_back_or_to accounts_path, notice: t(".success")
end
private
def institution_params
params.require(:institution).permit(:name, :logo)
end
def set_institution
@institution = Current.family.institutions.find(params[:id])
end
end

View File

@@ -0,0 +1,3 @@
class InvestmentsController < ApplicationController
include AccountableResource
end

View File

@@ -0,0 +1,42 @@
class InvitationsController < ApplicationController
skip_authentication only: :accept
def new
@invitation = Invitation.new
end
def create
unless Current.user.admin?
flash[:alert] = t(".failure")
redirect_to settings_profile_path
return
end
@invitation = Current.family.invitations.build(invitation_params)
@invitation.inviter = Current.user
if @invitation.save
InvitationMailer.invite_email(@invitation).deliver_later unless self_hosted?
flash[:notice] = t(".success")
else
flash[:alert] = t(".failure")
end
redirect_to settings_profile_path
end
def accept
@invitation = Invitation.find_by!(token: params[:id])
if @invitation.pending?
redirect_to new_registration_path(invitation: @invitation.token)
else
raise ActiveRecord::RecordNotFound
end
end
private
def invitation_params
params.require(:invitation).permit(:email, :role)
end
end

View File

@@ -3,8 +3,9 @@ class Issue::ExchangeRateProviderMissingsController < ApplicationController
def update
Setting.synth_api_key = exchange_rate_params[:synth_api_key]
@issue.issuable.sync_later
redirect_back_or_to account_path(@issue.issuable)
account = @issue.issuable
account.sync_later
redirect_back_or_to account
end
private

View File

@@ -1,39 +1,7 @@
class LoansController < ApplicationController
before_action :set_account, only: :update
include AccountableResource
def create
account = Current.family
.accounts
.create_with_optional_start_balance! \
attributes: account_params.except(:start_date, :start_balance),
start_date: account_params[:start_date],
start_balance: account_params[:start_balance]
account.sync_later
redirect_to account, notice: t(".success")
end
def update
@account.update_with_sync!(account_params)
redirect_to @account, notice: t(".success")
end
private
def set_account
@account = Current.family.accounts.find(params[:id])
end
def account_params
params.require(:account)
.permit(
:name, :balance, :institution_id, :start_date, :start_balance, :currency, :accountable_type,
accountable_attributes: [
:id,
:rate_type,
:interest_rate,
:term_months
]
)
end
permitted_accountable_attributes(
:id, :rate_type, :interest_rate, :term_months
)
end

View File

@@ -12,8 +12,13 @@ class MerchantsController < ApplicationController
end
def create
Current.family.merchants.create!(merchant_params)
redirect_to merchants_path, notice: t(".success")
@merchant = Current.family.merchants.new(merchant_params)
if @merchant.save
redirect_to merchants_path, notice: t(".success")
else
redirect_to merchants_path, alert: t(".error", error: @merchant.errors.full_messages.to_sentence)
end
end
def edit

View File

@@ -0,0 +1,24 @@
class OnboardingsController < ApplicationController
layout "application"
before_action :set_user
before_action :load_invitation
def show
end
def profile
end
def preferences
end
private
def set_user
@user = Current.user
end
def load_invitation
@invitation = Current.family.invitations.accepted.find_by(email: Current.user.email)
end
end

View File

@@ -0,0 +1,3 @@
class OtherAssetsController < ApplicationController
include AccountableResource
end

View File

@@ -0,0 +1,3 @@
class OtherLiabilitiesController < ApplicationController
include AccountableResource
end

View File

@@ -2,9 +2,8 @@ class PagesController < ApplicationController
skip_before_action :authenticate_user!, only: %i[early_access]
layout :with_sidebar, except: %i[early_access]
include Filterable
def dashboard
@period = Period.from_param(params[:period])
snapshot = Current.family.snapshot(@period)
@net_worth_series = snapshot[:net_worth_series]
@asset_series = snapshot[:asset_series]
@@ -20,7 +19,7 @@ class PagesController < ApplicationController
@top_earners = snapshot_account_transactions[:top_earners]
@top_savers = snapshot_account_transactions[:top_savers]
@accounts = Current.family.accounts
@accounts = Current.family.accounts.active
@account_groups = @accounts.by_group(period: @period, currency: Current.family.currency)
@transaction_entries = Current.family.entries.account_transactions.limit(6).reverse_chronological

View File

@@ -16,7 +16,7 @@ class PasswordResetsController < ApplicationController
).password_reset.deliver_later
end
redirect_to root_path, notice: t(".requested")
redirect_to new_password_reset_path(step: "pending")
end
def edit

View File

@@ -0,0 +1,38 @@
class PlaidItemsController < ApplicationController
before_action :set_plaid_item, only: %i[destroy sync]
def create
Current.family.plaid_items.create_from_public_token(
plaid_item_params[:public_token],
item_name: item_name,
)
redirect_to accounts_path, notice: t(".success")
end
def destroy
@plaid_item.destroy_later
redirect_to accounts_path, notice: t(".success")
end
def sync
unless @plaid_item.syncing?
@plaid_item.sync_later
end
redirect_to accounts_path
end
private
def set_plaid_item
@plaid_item = Current.family.plaid_items.find(params[:id])
end
def plaid_item_params
params.require(:plaid_item).permit(:public_token, metadata: {})
end
def item_name
plaid_item_params.dig(:metadata, :institution, :name)
end
end

View File

@@ -1,40 +1,21 @@
class PropertiesController < ApplicationController
before_action :set_account, only: :update
include AccountableResource
def create
account = Current.family
.accounts
.create_with_optional_start_balance! \
attributes: account_params.except(:start_date, :start_balance),
start_date: account_params[:start_date],
start_balance: account_params[:start_balance]
permitted_accountable_attributes(
:id, :year_built, :area_unit, :area_value,
address_attributes: [ :line1, :line2, :locality, :region, :country, :postal_code ]
)
account.sync_later
redirect_to account, notice: t(".success")
def new
@account = Current.family.accounts.build(
currency: Current.family.currency,
accountable: Property.new(
address: Address.new
)
)
end
def update
@account.update_with_sync!(account_params)
redirect_to @account, notice: t(".success")
def edit
@account.accountable.address ||= Address.new
end
private
def set_account
@account = Current.family.accounts.find(params[:id])
end
def account_params
params.require(:account)
.permit(
:name, :balance, :institution_id, :start_date, :start_balance, :currency, :accountable_type,
accountable_attributes: [
:id,
:year_built,
:area_unit,
:area_value,
address_attributes: [ :line1, :line2, :locality, :region, :country, :postal_code ]
]
)
end
end

View File

@@ -4,36 +4,49 @@ class RegistrationsController < ApplicationController
layout "auth"
before_action :set_user, only: :create
before_action :set_invitation
before_action :claim_invite_code, only: :create, if: :invite_code_required?
def new
@user = User.new
@user = User.new(email: @invitation&.email)
end
def create
family = Family.new
@user.family = family
@user.role = :admin
if @invitation
@user.family = @invitation.family
@user.role = @invitation.role
@user.email = @invitation.email
else
family = Family.new
@user.family = family
@user.role = :admin
end
if @user.save
Category.create_default_categories(@user.family)
@invitation&.update!(accepted_at: Time.current)
Category.create_default_categories(@user.family) unless @invitation
@session = create_session_for(@user)
flash[:notice] = t(".success")
redirect_to root_path
redirect_to root_path, notice: t(".success")
else
flash[:alert] = t(".failure")
render :new, status: :unprocessable_entity
render :new, status: :unprocessable_entity, alert: t(".failure")
end
end
private
def set_user
@user = User.new user_params.except(:invite_code)
def set_invitation
token = params[:invitation]
token ||= params[:user][:invitation] if params[:user].present?
@invitation = Invitation.pending.find_by(token: token)
end
def user_params
params.require(:user).permit(:name, :email, :password, :password_confirmation, :invite_code)
def set_user
@user = User.new user_params.except(:invite_code, :invitation)
end
def user_params(specific_param = nil)
params = self.params.require(:user).permit(:name, :email, :password, :password_confirmation, :invite_code, :invitation)
specific_param ? params[specific_param] : params
end
def claim_invite_code

View File

@@ -0,0 +1,18 @@
class SecuritiesController < ApplicationController
def index
query = params[:q]
return render json: [] if query.blank? || query.length < 2 || query.length > 100
@securities = Security.search({
search: query,
country: country_code_filter
})
end
private
def country_code_filter
filter = params[:country_code]
filter = "#{filter},US" unless filter == "US"
filter
end
end

View File

@@ -19,7 +19,7 @@ class SessionsController < ApplicationController
def destroy
@session.destroy
redirect_to root_path, notice: t(".logout_successful")
redirect_to new_session_path, notice: t(".logout_successful")
end
private

View File

@@ -1,2 +1,5 @@
class Settings::BillingsController < SettingsController
def show
@user = Current.user
end
end

View File

@@ -1,26 +1,5 @@
class Settings::PreferencesController < SettingsController
def edit
def show
@user = Current.user
end
def update
preference_params_with_family = preference_params
if Current.family && preference_params[:family_attributes]
family_attributes = preference_params[:family_attributes].merge({ id: Current.family.id })
preference_params_with_family[:family_attributes] = family_attributes
end
if Current.user.update(preference_params_with_family)
redirect_to settings_preferences_path, notice: t(".success")
else
redirect_to settings_preferences_path, notice: t(".success")
render :show, status: :unprocessable_entity
end
end
private
def preference_params
params.require(:user).permit(family_attributes: [ :id, :currency, :locale ])
end
end

View File

@@ -1,38 +1,7 @@
class Settings::ProfilesController < SettingsController
def show
@user = Current.user
@users = Current.family.users.order(:created_at)
@pending_invitations = Current.family.invitations.pending
end
def update
user_params_with_family = user_params
if params[:user][:delete_profile_image] == "true"
Current.user.profile_image.purge
end
if Current.family && user_params_with_family[:family_attributes]
family_attributes = user_params_with_family[:family_attributes].merge({ id: Current.family.id })
user_params_with_family[:family_attributes] = family_attributes
end
if Current.user.update(user_params_with_family)
redirect_to settings_profile_path, notice: t(".success")
else
redirect_to settings_profile_path, alert: Current.user.errors.full_messages.to_sentence
end
end
def destroy
if Current.user.deactivate
Current.session.destroy
redirect_to root_path, notice: t(".success")
else
redirect_to settings_profile_path, alert: Current.user.errors.full_messages.to_sentence
end
end
private
def user_params
params.require(:user).permit(:first_name, :last_name, :profile_image,
family_attributes: [ :name, :id ])
end
end

View File

@@ -1,16 +1,14 @@
class SubscriptionsController < ApplicationController
def new
client = Stripe::StripeClient.new(ENV["STRIPE_SECRET_KEY"])
if Current.family.stripe_customer_id.blank?
customer = client.v1.customers.create(
customer = stripe_client.v1.customers.create(
email: Current.family.primary_user.email,
metadata: { family_id: Current.family.id }
)
Current.family.update(stripe_customer_id: customer.id)
end
session = client.v1.checkout.sessions.create({
session = stripe_client.v1.checkout.sessions.create({
customer: Current.family.stripe_customer_id,
line_items: [ {
price: ENV["STRIPE_PLAN_ID"],
@@ -18,7 +16,7 @@ class SubscriptionsController < ApplicationController
} ],
mode: "subscription",
allow_promotion_codes: true,
success_url: settings_billing_url,
success_url: success_subscription_url + "?session_id={CHECKOUT_SESSION_ID}",
cancel_url: settings_billing_url
})
@@ -26,12 +24,24 @@ class SubscriptionsController < ApplicationController
end
def show
client = Stripe::StripeClient.new(ENV["STRIPE_SECRET_KEY"])
portal_session = client.v1.billing_portal.sessions.create(
portal_session = stripe_client.v1.billing_portal.sessions.create(
customer: Current.family.stripe_customer_id,
return_url: settings_billing_url
)
redirect_to portal_session.url, allow_other_host: true, status: :see_other
end
def success
checkout_session = stripe_client.v1.checkout.sessions.retrieve(params[:session_id])
Current.session.update(subscribed_at: Time.at(checkout_session.created))
redirect_to root_path, notice: "You have successfully subscribed to Maybe+."
rescue Stripe::InvalidRequestError
redirect_to settings_billing_path, alert: "Something went wrong processing your subscription. Please contact us to get this fixed."
end
private
def stripe_client
@stripe_client ||= Stripe::StripeClient.new(ENV["STRIPE_SECRET_KEY"])
end
end

View File

@@ -1,7 +1,7 @@
class TagsController < ApplicationController
layout :with_sidebar
before_action :set_tag, only: %i[edit update]
before_action :set_tag, only: %i[edit update destroy]
def index
@tags = Current.family.tags.alphabetically
@@ -12,8 +12,13 @@ class TagsController < ApplicationController
end
def create
Current.family.tags.create!(tag_params)
redirect_to tags_path, notice: t(".created")
@tag = Current.family.tags.new(tag_params)
if @tag.save
redirect_to tags_path, notice: t(".created")
else
redirect_to tags_path, alert: t(".error", error: @tag.errors.full_messages.to_sentence)
end
end
def edit
@@ -24,6 +29,11 @@ class TagsController < ApplicationController
redirect_to tags_path, notice: t(".updated")
end
def destroy
@tag.destroy!
redirect_to tags_path, notice: t(".deleted")
end
private
def set_tag

View File

@@ -13,93 +13,13 @@ class TransactionsController < ApplicationController
}
end
def new
@entry = Current.family.entries.new(entryable: Account::Transaction.new).tap do |e|
if params[:account_id]
e.account = Current.family.accounts.find(params[:account_id])
e.currency = e.account.currency
else
e.currency = Current.family.currency
end
end
end
def create
@entry = Current.family
.accounts
.find(params[:account_entry][:account_id])
.entries
.create!(transaction_entry_params.merge(amount: amount))
@entry.sync_account_later
redirect_back_or_to account_path(@entry.account), notice: t(".success")
end
def bulk_delete
destroyed = Current.family.entries.destroy_by(id: bulk_delete_params[:entry_ids])
redirect_back_or_to transactions_url, notice: t(".success", count: destroyed.count)
end
def bulk_edit
end
def bulk_update
updated = Current.family
.entries
.where(id: bulk_update_params[:entry_ids])
.bulk_update!(bulk_update_params)
redirect_back_or_to transactions_url, notice: t(".success", count: updated)
end
def mark_transfers
Current.family
.entries
.where(id: bulk_update_params[:entry_ids])
.mark_transfers!
redirect_back_or_to transactions_url, notice: t(".success")
end
def unmark_transfers
Current.family
.entries
.where(id: bulk_update_params[:entry_ids])
.update_all marked_as_transfer: false
redirect_back_or_to transactions_url, notice: t(".success")
end
private
def amount
if nature.income?
transaction_entry_params[:amount].to_d * -1
else
transaction_entry_params[:amount].to_d
end
end
def nature
params[:account_entry][:nature].to_s.inquiry
end
def bulk_delete_params
params.require(:bulk_delete).permit(entry_ids: [])
end
def bulk_update_params
params.require(:bulk_update).permit(:date, :notes, :category_id, :merchant_id, entry_ids: [])
end
def search_params
params.fetch(:q, {})
.permit(:start_date, :end_date, :search, :amount, :amount_operator, accounts: [], account_ids: [], categories: [], merchants: [], types: [], tags: [])
end
def transaction_entry_params
params.require(:account_entry)
.permit(:name, :date, :amount, :currency, :entryable_type, entryable_attributes: [ :category_id ])
.with_defaults(entryable_type: "Account::Transaction", entryable_attributes: {})
.permit(
:start_date, :end_date, :search, :amount,
:amount_operator, accounts: [], account_ids: [],
categories: [], merchants: [], types: [], tags: []
)
end
end

View File

@@ -0,0 +1,51 @@
class UsersController < ApplicationController
before_action :set_user
def update
@user = Current.user
@user.update!(user_params.except(:redirect_to, :delete_profile_image))
@user.profile_image.purge if should_purge_profile_image?
handle_redirect(t(".success"))
end
def destroy
if @user.deactivate
Current.session.destroy
redirect_to root_path, notice: t(".success")
else
redirect_to settings_profile_path, alert: @user.errors.full_messages.to_sentence
end
end
private
def handle_redirect(notice)
case user_params[:redirect_to]
when "onboarding_preferences"
redirect_to preferences_onboarding_path
when "home"
redirect_to root_path
when "preferences"
redirect_to settings_preferences_path, notice: notice
else
redirect_to settings_profile_path, notice: notice
end
end
def should_purge_profile_image?
user_params[:delete_profile_image] == "1" &&
user_params[:profile_image].blank?
end
def user_params
params.require(:user).permit(
:first_name, :last_name, :profile_image, :redirect_to, :delete_profile_image, :onboarded_at,
family_attributes: [ :name, :currency, :country, :locale, :date_format, :timezone, :id ]
)
end
def set_user
@user = Current.user
end
end

View File

@@ -1,41 +1,7 @@
class VehiclesController < ApplicationController
before_action :set_account, only: :update
include AccountableResource
def create
account = Current.family
.accounts
.create_with_optional_start_balance! \
attributes: account_params.except(:start_date, :start_balance),
start_date: account_params[:start_date],
start_balance: account_params[:start_balance]
account.sync_later
redirect_to account, notice: t(".success")
end
def update
@account.update_with_sync!(account_params)
redirect_to @account, notice: t(".success")
end
private
def set_account
@account = Current.family.accounts.find(params[:id])
end
def account_params
params.require(:account)
.permit(
:name, :balance, :institution_id, :start_date, :start_balance, :currency, :accountable_type,
accountable_attributes: [
:id,
:make,
:model,
:year,
:mileage_value,
:mileage_unit
]
)
end
permitted_accountable_attributes(
:id, :make, :model, :year, :mileage_value, :mileage_unit
)
end

View File

@@ -1,7 +1,19 @@
class WebhooksController < ApplicationController
skip_before_action :verify_authenticity_token, only: [ :stripe ]
skip_before_action :verify_authenticity_token
skip_authentication
def plaid
webhook_body = request.body.read
plaid_verification_header = request.headers["Plaid-Verification"]
Provider::Plaid.validate_webhook!(plaid_verification_header, webhook_body)
Provider::Plaid.process_webhook(webhook_body)
render json: { received: true }, status: :ok
rescue => error
render json: { error: "Invalid webhook: #{error.message}" }, status: :bad_request
end
def stripe
webhook_body = request.body.read
sig_header = request.env["HTTP_STRIPE_SIGNATURE"]

View File

@@ -12,43 +12,13 @@ module Account::EntriesHelper
transfers.map(&:transfer).uniq
end
def entry_icon(entry, is_oldest: false)
if is_oldest
"keyboard"
elsif entry.trend.direction.up?
"arrow-up"
elsif entry.trend.direction.down?
"arrow-down"
else
"minus"
end
end
def entry_style(entry, is_oldest: false)
color = is_oldest ? "#D444F1" : entry.trend.color
mixed_hex_styles(color)
end
def entry_name(entry)
if entry.account_trade?
trade = entry.account_trade
prefix = trade.sell? ? "Sell " : "Buy "
generated = prefix + "#{trade.qty.abs} shares of #{trade.security.ticker}"
name = entry.name || generated
name
else
entry.name || "Transaction"
end
end
def entries_by_date(entries, selectable: true)
entries.group_by(&:date).map do |date, grouped_entries|
def entries_by_date(entries, selectable: true, totals: false)
entries.reverse_chronological.group_by(&:date).map do |date, grouped_entries|
content = capture do
yield grouped_entries
end
render partial: "account/entries/entry_group", locals: { date:, entries: grouped_entries, content:, selectable: }
render partial: "account/entries/entry_group", locals: { date:, entries: grouped_entries, content:, selectable:, totals: }
end.join.html_safe
end

View File

@@ -1,13 +1,13 @@
module Account::CashesHelper
def brokerage_cash(account)
module Account::HoldingsHelper
def brokerage_cash_holding(account)
currency = Money::Currency.new(account.currency)
account.holdings.build \
date: Date.current,
qty: account.balance,
qty: account.cash_balance,
price: 1,
amount: account.balance,
currency: account.currency,
amount: account.cash_balance,
currency: currency.iso_code,
security: Security.new(ticker: currency.iso_code, name: currency.name)
end
end

View File

@@ -1,4 +1,27 @@
module AccountsHelper
def period_label(period)
return "since account creation" if period.date_range.begin.nil?
start_date, end_date = period.date_range.first, period.date_range.last
return "Starting from #{start_date.strftime('%b %d, %Y')}" if end_date.nil?
return "Ending at #{end_date.strftime('%b %d, %Y')}" if start_date.nil?
days_apart = (end_date - start_date).to_i
case days_apart
when 1
"vs. yesterday"
when 7
"vs. last week"
when 30, 31
"vs. last month"
when 365, 366
"vs. last year"
else
"from #{start_date.strftime('%b %d, %Y')} to #{end_date.strftime('%b %d, %Y')}"
end
end
def summary_card(title:, &block)
content = capture(&block)
render "accounts/summary_card", title: title, content: content
@@ -28,65 +51,9 @@ module AccountsHelper
class_mapping(accountable_type)[:hex]
end
# Eventually, we'll have an accountable form for each type of accountable, so
# this helper is a convenience for now to reuse common logic in the accounts controller
def new_account_form_url(account)
case account.accountable_type
when "Property"
properties_path
when "Vehicle"
vehicles_path
when "Loan"
loans_path
when "CreditCard"
credit_cards_path
else
accounts_path
end
end
def edit_account_form_url(account)
case account.accountable_type
when "Property"
property_path(account)
when "Vehicle"
vehicle_path(account)
when "Loan"
loan_path(account)
when "CreditCard"
credit_card_path(account)
else
account_path(account)
end
end
def account_tabs(account)
overview_tab = { key: "overview", label: t("accounts.show.overview"), path: account_path(account, tab: "overview"), partial_path: "accounts/overview" }
holdings_tab = { key: "holdings", label: t("accounts.show.holdings"), path: account_path(account, tab: "holdings"), route: account_holdings_path(account) }
cash_tab = { key: "cash", label: t("accounts.show.cash"), path: account_path(account, tab: "cash"), route: account_cashes_path(account) }
value_tab = { key: "valuations", label: t("accounts.show.value"), path: account_path(account, tab: "valuations"), route: account_valuations_path(account) }
transactions_tab = { key: "transactions", label: t("accounts.show.transactions"), path: account_path(account, tab: "transactions"), route: account_transactions_path(account) }
trades_tab = { key: "trades", label: t("accounts.show.trades"), path: account_path(account, tab: "trades"), route: account_trades_path(account) }
return [ value_tab ] if account.other_asset? || account.other_liability?
return [ overview_tab, value_tab ] if account.property? || account.vehicle?
return [ holdings_tab, cash_tab, trades_tab, value_tab ] if account.investment?
return [ overview_tab, value_tab, transactions_tab ] if account.loan? || account.credit_card?
[ value_tab, transactions_tab ]
end
def selected_account_tab(account)
available_tabs = account_tabs(account)
tab = available_tabs.find { |tab| tab[:key] == params[:tab] }
tab || available_tabs.first
end
def account_groups(period: nil)
assets, liabilities = Current.family.accounts.by_group(currency: Current.family.currency, period: period || Period.last_30_days).values_at(:assets, :liabilities)
[ assets.children, liabilities.children ].flatten
assets, liabilities = Current.family.accounts.active.by_group(currency: Current.family.currency, period: period || Period.last_30_days).values_at(:assets, :liabilities)
[ assets.children.sort_by(&:name), liabilities.children.sort_by(&:name) ].flatten
end
private

View File

@@ -1,6 +1,20 @@
module ApplicationHelper
include Pagy::Frontend
def date_format_options
[
[ "DD-MM-YYYY", "%d-%m-%Y" ],
[ "DD.MM.YY", "%d.%m.%Y" ],
[ "MM-DD-YYYY", "%m-%d-%Y" ],
[ "YYYY-MM-DD", "%Y-%m-%d" ],
[ "DD/MM/YYYY", "%d/%m/%Y" ],
[ "YYYY/MM/DD", "%Y/%m/%d" ],
[ "MM/DD/YYYY", "%m/%d/%Y" ],
[ "D/MM/YYYY", "%e/%m/%Y" ],
[ "YYYY.MM.DD", "%Y.%m.%d" ]
]
end
def title(page_title)
content_for(:title) { page_title }
end
@@ -9,10 +23,6 @@ module ApplicationHelper
content_for(:header_title) { page_title }
end
def permitted_accountable_partial(name)
name.underscore
end
def family_notifications_stream
turbo_stream_from [ Current.family, :notifications ] if Current.family
end
@@ -52,9 +62,9 @@ module ApplicationHelper
# <div>Content here</div>
# <% end %>
#
def drawer(&block)
def drawer(reload_on_close: false, &block)
content = capture &block
render partial: "shared/drawer", locals: { content: content }
render partial: "shared/drawer", locals: { content:, reload_on_close: }
end
def disclosure(title, &block)
@@ -80,8 +90,8 @@ module ApplicationHelper
color = hex || "#1570EF" # blue-600
<<-STYLE.strip
background-color: color-mix(in srgb, #{color} 5%, white);
border-color: color-mix(in srgb, #{color} 10%, white);
background-color: color-mix(in srgb, #{color} 10%, white);
border-color: color-mix(in srgb, #{color} 30%, white);
color: #{color};
STYLE
end
@@ -113,26 +123,16 @@ module ApplicationHelper
{ bg_class: bg_class, text_class: text_class, symbol: symbol, icon: icon }
end
def period_label(period)
return "since account creation" if period.date_range.begin.nil?
start_date, end_date = period.date_range.first, period.date_range.last
# Wrapper around I18n.l to support custom date formats
def format_date(object, format = :default, options = {})
date = object.to_date
return "Starting from #{start_date.strftime('%b %d, %Y')}" if end_date.nil?
return "Ending at #{end_date.strftime('%b %d, %Y')}" if start_date.nil?
format_code = options[:format_code] || Current.family&.date_format
days_apart = (end_date - start_date).to_i
case days_apart
when 1
"vs. yesterday"
when 7
"vs. last week"
when 30, 31
"vs. last month"
when 365, 366
"vs. last year"
if format_code.present?
date.strftime(format_code)
else
"from #{start_date.strftime('%b %d, %Y')} to #{end_date.strftime('%b %d, %Y')}"
I18n.l(date, format: format, **options)
end
end
@@ -158,4 +158,12 @@ module ApplicationHelper
.map { |_currency, money| format_money(money) }
.join(separator)
end
def show_super_admin_bar?
if params[:admin].present?
cookies.permanent[:admin] = params[:admin]
end
cookies[:admin] == "true"
end
end

View File

@@ -4,4 +4,8 @@ module CategoriesHelper
name: "Uncategorized",
color: Category::UNCATEGORIZED_COLOR
end
def family_categories
[ null_category ].concat(Current.family.categories.alphabetically)
end
end

View File

@@ -18,7 +18,7 @@ module FormsHelper
end
def period_select(form:, selected:, classes: "border border-alpha-black-100 shadow-xs rounded-lg text-sm pr-7 cursor-pointer text-gray-900 focus:outline-none focus:ring-0")
periods_for_select = [ [ "7D", "last_7_days" ], [ "1M", "last_30_days" ], [ "1Y", "last_365_days" ], [ "All", "all" ] ]
periods_for_select = [ [ "7D", "last_7_days" ], [ "1M", "last_30_days" ], [ "1Y", "last_365_days" ] ]
form.select(:period, periods_for_select, { selected: selected }, class: classes, data: { "auto-submit-form-target": "auto" })
end

View File

@@ -0,0 +1,2 @@
module ImpersonationSessionsHelper
end

View File

@@ -1,5 +0,0 @@
module InstitutionsHelper
def institution_logo(institution)
institution.logo.attached? ? institution.logo : institution.logo_url
end
end

View File

@@ -0,0 +1,2 @@
module InvitationsHelper
end

View File

@@ -0,0 +1,370 @@
module LanguagesHelper
LANGUAGE_MAPPING = {
en: "English",
ru: "Russian",
ar: "Arabic",
bg: "Bulgarian",
'ca-CAT': "Catalan (Catalonia)",
ca: "Catalan",
'da-DK': "Danish (Denmark)",
'de-AT': "German (Austria)",
'de-CH': "German (Switzerland)",
de: "German",
ee: "Ewe",
'en-AU': "English (Australia)",
'en-BORK': "English (Bork)",
'en-CA': "English (Canada)",
'en-GB': "English (United Kingdom)",
'en-IND': "English (India)",
'en-KE': "English (Kenya)",
'en-MS': "English (Malaysia)",
'en-NEP': "English (Nepal)",
'en-NG': "English (Nigeria)",
'en-NZ': "English (New Zealand)",
'en-PAK': "English (Pakistan)",
'en-SG': "English (Singapore)",
'en-TH': "English (Thailand)",
'en-UG': "English (Uganda)",
'en-US': "English (United States)",
'en-ZA': "English (South Africa)",
'en-au-ocker': "English (Australian Ocker)",
'es-AR': "Spanish (Argentina)",
'es-MX': "Spanish (Mexico)",
es: "Spanish",
fa: "Persian",
'fi-FI': "Finnish (Finland)",
fr: "French",
'fr-CA': "French (Canada)",
'fr-CH': "French (Switzerland)",
he: "Hebrew",
hy: "Armenian",
id: "Indonesian",
it: "Italian",
ja: "Japanese",
ko: "Korean",
lt: "Lithuanian",
lv: "Latvian",
'mi-NZ': "Maori (New Zealand)",
'nb-NO': "Norwegian Bokmål (Norway)",
nl: "Dutch",
'no-NO': "Norwegian (Norway)",
pl: "Polish",
'pt-BR': "Portuguese (Brazil)",
pt: "Portuguese",
sk: "Slovak",
sv: "Swedish",
th: "Thai",
tr: "Turkish",
uk: "Ukrainian",
vi: "Vietnamese",
'zh-CN': "Chinese (Simplified)",
'zh-TW': "Chinese (Traditional)",
af: "Afrikaans",
az: "Azerbaijani",
be: "Belarusian",
bn: "Bengali",
bs: "Bosnian",
cs: "Czech",
cy: "Welsh",
da: "Danish",
'de-DE': "German (Germany)",
dz: "Dzongkha",
'el-CY': "Greek (Cyprus)",
el: "Greek",
'en-CY': "English (Cyprus)",
'en-IE': "English (Ireland)",
'en-IN': "English (India)",
'en-TT': "English (Trinidad and Tobago)",
eo: "Esperanto",
'es-419': "Spanish (Latin America)",
'es-CL': "Spanish (Chile)",
'es-CO': "Spanish (Colombia)",
'es-CR': "Spanish (Costa Rica)",
'es-EC': "Spanish (Ecuador)",
'es-ES': "Spanish (Spain)",
'es-NI': "Spanish (Nicaragua)",
'es-PA': "Spanish (Panama)",
'es-PE': "Spanish (Peru)",
'es-US': "Spanish (United States)",
'es-VE': "Spanish (Venezuela)",
et: "Estonian",
eu: "Basque",
fi: "Finnish",
'fr-FR': "French (France)",
fy: "Western Frisian",
gd: "Scottish Gaelic",
gl: "Galician",
'hi-IN': "Hindi (India)",
hi: "Hindi",
hr: "Croatian",
hu: "Hungarian",
is: "Icelandic",
'it-CH': "Italian (Switzerland)",
ka: "Georgian",
kk: "Kazakh",
km: "Khmer",
kn: "Kannada",
lb: "Luxembourgish",
lo: "Lao",
mg: "Malagasy",
mk: "Macedonian",
ml: "Malayalam",
mn: "Mongolian",
'mr-IN': "Marathi (India)",
ms: "Malay",
nb: "Norwegian Bokmål",
ne: "Nepali",
nn: "Norwegian Nynorsk",
oc: "Occitan",
or: "Odia",
pa: "Punjabi",
rm: "Romansh",
ro: "Romanian",
sc: "Sardinian",
sl: "Slovenian",
sq: "Albanian",
sr: "Serbian",
st: "Southern Sotho",
'sv-FI': "Swedish (Finland)",
'sv-SE': "Swedish (Sweden)",
sw: "Swahili",
ta: "Tamil",
te: "Telugu",
tl: "Tagalog",
tt: "Tatar",
ug: "Uyghur",
ur: "Urdu",
uz: "Uzbek",
wo: "Wolof"
}.freeze
EXCLUDED_LOCALES = [
# Test locales
"en-BORK",
"en-au-ocker",
# Duplicate locales
"fr-FR",
"de-DE",
"hi-IN",
"sv-SE",
"ca-CAT",
"en-US",
"fi-FI",
"en-IND"
].freeze
COUNTRY_MAPPING = {
AF: "Afghanistan",
AL: "Albania",
DZ: "Algeria",
AD: "Andorra",
AO: "Angola",
AG: "Antigua and Barbuda",
AR: "Argentina",
AM: "Armenia",
AU: "Australia",
AT: "Austria",
AZ: "Azerbaijan",
BS: "Bahamas",
BH: "Bahrain",
BD: "Bangladesh",
BB: "Barbados",
BY: "Belarus",
BE: "Belgium",
BZ: "Belize",
BJ: "Benin",
BT: "Bhutan",
BO: "Bolivia",
BA: "Bosnia and Herzegovina",
BW: "Botswana",
BR: "Brazil",
BN: "Brunei",
BG: "Bulgaria",
BF: "Burkina Faso",
BI: "Burundi",
KH: "Cambodia",
CM: "Cameroon",
CA: "Canada",
CV: "Cape Verde",
CF: "Central African Republic",
TD: "Chad",
CL: "Chile",
CN: "China",
CO: "Colombia",
KM: "Comoros",
CG: "Congo",
CD: "Congo, Democratic Republic of the",
CR: "Costa Rica",
CI: "Côte d'Ivoire",
HR: "Croatia",
CU: "Cuba",
CY: "Cyprus",
CZ: "Czech Republic",
DK: "Denmark",
DJ: "Djibouti",
DM: "Dominica",
DO: "Dominican Republic",
EC: "Ecuador",
EG: "Egypt",
SV: "El Salvador",
GQ: "Equatorial Guinea",
ER: "Eritrea",
EE: "Estonia",
ET: "Ethiopia",
FJ: "Fiji",
FI: "Finland",
FR: "France",
GA: "Gabon",
GM: "Gambia",
GE: "Georgia",
DE: "Germany",
GH: "Ghana",
GR: "Greece",
GD: "Grenada",
GT: "Guatemala",
GN: "Guinea",
GW: "Guinea-Bissau",
GY: "Guyana",
HT: "Haiti",
HN: "Honduras",
HU: "Hungary",
IS: "Iceland",
IN: "India",
ID: "Indonesia",
IR: "Iran",
IQ: "Iraq",
IE: "Ireland",
IL: "Israel",
IT: "Italy",
JM: "Jamaica",
JP: "Japan",
JO: "Jordan",
KZ: "Kazakhstan",
KE: "Kenya",
KI: "Kiribati",
KP: "North Korea",
KR: "South Korea",
KW: "Kuwait",
KG: "Kyrgyzstan",
LA: "Laos",
LV: "Latvia",
LB: "Lebanon",
LS: "Lesotho",
LR: "Liberia",
LY: "Libya",
LI: "Liechtenstein",
LT: "Lithuania",
LU: "Luxembourg",
MK: "North Macedonia",
MG: "Madagascar",
MW: "Malawi",
MY: "Malaysia",
MV: "Maldives",
ML: "Mali",
MT: "Malta",
MH: "Marshall Islands",
MR: "Mauritania",
MU: "Mauritius",
MX: "Mexico",
FM: "Micronesia",
MD: "Moldova",
MC: "Monaco",
MN: "Mongolia",
ME: "Montenegro",
MA: "Morocco",
MZ: "Mozambique",
MM: "Myanmar",
NA: "Namibia",
NR: "Nauru",
NP: "Nepal",
NL: "Netherlands",
NZ: "New Zealand",
NI: "Nicaragua",
NE: "Niger",
NG: "Nigeria",
NO: "Norway",
OM: "Oman",
PK: "Pakistan",
PW: "Palau",
PA: "Panama",
PG: "Papua New Guinea",
PY: "Paraguay",
PE: "Peru",
PH: "Philippines",
PL: "Poland",
PT: "Portugal",
QA: "Qatar",
RO: "Romania",
RU: "Russia",
RW: "Rwanda",
KN: "Saint Kitts and Nevis",
LC: "Saint Lucia",
VC: "Saint Vincent and the Grenadines",
WS: "Samoa",
SM: "San Marino",
ST: "Sao Tome and Principe",
SA: "Saudi Arabia",
SN: "Senegal",
RS: "Serbia",
SC: "Seychelles",
SL: "Sierra Leone",
SG: "Singapore",
SK: "Slovakia",
SI: "Slovenia",
SB: "Solomon Islands",
SO: "Somalia",
ZA: "South Africa",
SS: "South Sudan",
ES: "Spain",
LK: "Sri Lanka",
SD: "Sudan",
SR: "Suriname",
SE: "Sweden",
CH: "Switzerland",
SY: "Syria",
TW: "Taiwan",
TJ: "Tajikistan",
TZ: "Tanzania",
TH: "Thailand",
TL: "Timor-Leste",
TG: "Togo",
TO: "Tonga",
TT: "Trinidad and Tobago",
TN: "Tunisia",
TR: "Turkey",
TM: "Turkmenistan",
TV: "Tuvalu",
UG: "Uganda",
UA: "Ukraine",
AE: "United Arab Emirates",
GB: "United Kingdom",
US: "United States",
UY: "Uruguay",
UZ: "Uzbekistan",
VU: "Vanuatu",
VA: "Vatican City",
VE: "Venezuela",
VN: "Vietnam",
YE: "Yemen",
ZM: "Zambia",
ZW: "Zimbabwe"
}.freeze
def country_options
COUNTRY_MAPPING.keys.map { |key| [ COUNTRY_MAPPING[key], key ] }
end
def language_options
I18n.available_locales
.reject { |locale| EXCLUDED_LOCALES.include?(locale.to_s) }
.map do |locale|
label = LANGUAGE_MAPPING[locale.to_sym] || locale.to_s.humanize
[ "#{label} (#{locale})", locale ]
end
.sort_by { |label, locale| label }
end
def timezone_options
ActiveSupport::TimeZone.all.map { |tz| [ tz.name + " (#{tz.tzinfo.identifier})", tz.tzinfo.identifier ] }
end
end

View File

@@ -0,0 +1,2 @@
module SecuritiesHelper
end

View File

@@ -5,10 +5,10 @@ module SettingsHelper
{ name: I18n.t("settings.nav.self_hosting_label"), path: :settings_hosting_path, condition: :self_hosted? },
{ name: I18n.t("settings.nav.billing_label"), path: :settings_billing_path },
{ name: I18n.t("settings.nav.accounts_label"), path: :accounts_path },
{ name: I18n.t("settings.nav.imports_label"), path: :imports_path },
{ name: I18n.t("settings.nav.tags_label"), path: :tags_path },
{ name: I18n.t("settings.nav.categories_label"), path: :categories_path },
{ name: I18n.t("settings.nav.merchants_label"), path: :merchants_path },
{ name: I18n.t("settings.nav.imports_label"), path: :imports_path },
{ name: I18n.t("settings.nav.whats_new_label"), path: :changelog_path },
{ name: I18n.t("settings.nav.feedback_label"), path: :feedback_path }
]

View File

@@ -24,7 +24,7 @@ class StyledFormBuilder < ActionView::Helpers::FormBuilder
def select(method, choices, options = {}, html_options = {})
merged_html_options = { class: "form-field__input" }.merge(html_options)
label = build_label(method, options)
label = build_label(method, options.merge(required: merged_html_options[:required]))
field = super(method, choices, options, merged_html_options)
build_styled_field(label, field, options, remove_padding_right: true)
@@ -33,7 +33,7 @@ class StyledFormBuilder < ActionView::Helpers::FormBuilder
def collection_select(method, collection, value_method, text_method, options = {}, html_options = {})
merged_html_options = { class: "form-field__input" }.merge(html_options)
label = build_label(method, options)
label = build_label(method, options.merge(required: merged_html_options[:required]))
field = super(method, collection, value_method, text_method, options, merged_html_options)
build_styled_field(label, field, options, remove_padding_right: true)
@@ -49,7 +49,12 @@ class StyledFormBuilder < ActionView::Helpers::FormBuilder
end
def submit(value = nil, options = {})
merged_options = { class: "btn btn--primary w-full" }.merge(options)
default_options = {
data: { turbo_submits_with: "Submitting..." },
class: "btn btn--primary w-full"
}
merged_options = default_options.merge(options)
value, options = nil, value if value.is_a?(Hash)
super(value, merged_options)
end
@@ -68,7 +73,17 @@ class StyledFormBuilder < ActionView::Helpers::FormBuilder
def build_label(method, options)
return "".html_safe unless options[:label]
return label(method, class: "form-field__label") if options[:label] == true
label(method, options[:label], class: "form-field__label")
label_text = options[:label]
if options[:required]
label_text = @template.safe_join([
label_text == true ? method.to_s.humanize : label_text,
@template.tag.span("*", class: "text-red-500 ml-0.5")
])
end
return label(method, class: "form-field__label") if label_text == true
label(method, label_text, class: "form-field__label")
end
end

View File

@@ -1,3 +1,7 @@
// Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails
import "@hotwired/turbo-rails"
import "controllers"
import "@hotwired/turbo-rails";
import "controllers";
Turbo.StreamActions.redirect = function () {
Turbo.visit(this.target);
};

View File

@@ -1,51 +1,51 @@
import { Controller } from "@hotwired/stimulus"
import { Controller } from "@hotwired/stimulus";
// Connects to data-controller="account-collapse"
export default class extends Controller {
static values = { type: String }
initialToggle = false
STORAGE_NAME = "accountCollapseStates"
static values = { type: String };
initialToggle = false;
STORAGE_NAME = "accountCollapseStates";
connect() {
this.element.addEventListener("toggle", this.onToggle)
this.updateFromLocalStorage()
this.element.addEventListener("toggle", this.onToggle);
this.updateFromLocalStorage();
}
disconnect() {
this.element.removeEventListener("toggle", this.onToggle)
this.element.removeEventListener("toggle", this.onToggle);
}
onToggle = () => {
if (this.initialToggle) {
this.initialToggle = false
return
this.initialToggle = false;
return;
}
const items = this.getItemsFromLocalStorage()
const items = this.getItemsFromLocalStorage();
if (items.has(this.typeValue)) {
items.delete(this.typeValue)
items.delete(this.typeValue);
} else {
items.add(this.typeValue)
items.add(this.typeValue);
}
localStorage.setItem(this.STORAGE_NAME, JSON.stringify([...items]))
}
localStorage.setItem(this.STORAGE_NAME, JSON.stringify([...items]));
};
updateFromLocalStorage() {
const items = this.getItemsFromLocalStorage()
const items = this.getItemsFromLocalStorage();
if (items.has(this.typeValue)) {
this.initialToggle = true
this.element.setAttribute("open", "")
this.initialToggle = true;
this.element.setAttribute("open", "");
}
}
getItemsFromLocalStorage() {
try {
const items = localStorage.getItem(this.STORAGE_NAME)
return new Set(items ? JSON.parse(items) : [])
const items = localStorage.getItem(this.STORAGE_NAME);
return new Set(items ? JSON.parse(items) : []);
} catch (error) {
console.error("Error parsing items from localStorage:", error)
return new Set()
console.error("Error parsing items from localStorage:", error);
return new Set();
}
}
}

View File

@@ -1,12 +1,12 @@
import { Application } from "@hotwired/stimulus"
import { Application } from "@hotwired/stimulus";
const application = Application.start()
const application = Application.start();
// Configure Stimulus development experience
application.debug = false
window.Stimulus = application
application.debug = false;
window.Stimulus = application;
Turbo.setConfirmMethod((message) => {
Turbo.config.forms.confirm = (message) => {
const dialog = document.getElementById("turbo-confirm");
try {
@@ -34,10 +34,24 @@ Turbo.setConfirmMethod((message) => {
dialog.showModal();
return new Promise((resolve) => {
dialog.addEventListener("close", () => {
resolve(dialog.returnValue == "confirm")
}, { once: true })
})
})
dialog.addEventListener(
"close",
() => {
const confirmed = dialog.returnValue === "confirm";
export { application }
if (!confirmed) {
document.getElementById("turbo-confirm-title").innerHTML =
"Are you sure?";
document.getElementById("turbo-confirm-body").innerHTML =
"You will not be able to undo this decision";
document.getElementById("turbo-confirm-accept").innerHTML = "Confirm";
}
resolve(confirmed);
},
{ once: true },
);
});
};
export { application };

View File

@@ -24,10 +24,31 @@ export default class extends Controller {
});
}
handleInput = () => {
handleInput = (event) => {
const target = event.target
clearTimeout(this.timeout);
this.timeout = setTimeout(() => {
this.element.requestSubmit();
}, 500);
}, this.#debounceTimeout(target));
};
#debounceTimeout(element) {
if(element.dataset.autosubmitDebounceTimeout) {
return Number.parseInt(element.dataset.autosubmitDebounceTimeout);
}
const type = element.type || element.tagName;
switch (type.toLowerCase()) {
case 'input':
case 'textarea':
return 500;
case 'select-one':
case 'select-multiple':
return 0;
default:
return 500;
}
}
}

View File

@@ -1,134 +1,158 @@
import { Controller } from "@hotwired/stimulus"
import { Controller } from "@hotwired/stimulus";
// Connects to data-controller="bulk-select"
export default class extends Controller {
static targets = ["row", "group", "selectionBar", "selectionBarText", "bulkEditDrawerTitle"]
static targets = [
"row",
"group",
"selectionBar",
"selectionBarText",
"bulkEditDrawerTitle",
];
static values = {
resource: String,
selectedIds: { type: Array, default: [] }
}
singularLabel: String,
pluralLabel: String,
selectedIds: { type: Array, default: [] },
};
connect() {
document.addEventListener("turbo:load", this._updateView)
document.addEventListener("turbo:load", this._updateView);
this._updateView()
this._updateView();
}
disconnect() {
document.removeEventListener("turbo:load", this._updateView)
document.removeEventListener("turbo:load", this._updateView);
}
bulkEditDrawerTitleTargetConnected(element) {
element.innerText = `Edit ${this.selectedIdsValue.length} ${this.#pluralizedResourceName()}`
element.innerText = `Edit ${
this.selectedIdsValue.length
} ${this._pluralizedResourceName()}`;
}
submitBulkRequest(e) {
const form = e.target.closest("form");
const scope = e.params.scope
this.#addHiddenFormInputsForSelectedIds(form, `${scope}[entry_ids][]`, this.selectedIdsValue)
form.requestSubmit()
const scope = e.params.scope;
this._addHiddenFormInputsForSelectedIds(
form,
`${scope}[entry_ids][]`,
this.selectedIdsValue,
);
form.requestSubmit();
}
togglePageSelection(e) {
if (e.target.checked) {
this.#selectAll()
this._selectAll();
} else {
this.deselectAll()
this.deselectAll();
}
}
toggleGroupSelection(e) {
const group = this.groupTargets.find(group => group.contains(e.target))
const group = this.groupTargets.find((group) => group.contains(e.target));
this.#rowsForGroup(group).forEach(row => {
this._rowsForGroup(group).forEach((row) => {
if (e.target.checked) {
this.#addToSelection(row.dataset.id)
this._addToSelection(row.dataset.id);
} else {
this.#removeFromSelection(row.dataset.id)
this._removeFromSelection(row.dataset.id);
}
})
});
}
toggleRowSelection(e) {
if (e.target.checked) {
this.#addToSelection(e.target.dataset.id)
this._addToSelection(e.target.dataset.id);
} else {
this.#removeFromSelection(e.target.dataset.id)
this._removeFromSelection(e.target.dataset.id);
}
}
deselectAll() {
this.selectedIdsValue = []
this.element.querySelectorAll('input[type="checkbox"]').forEach(el => el.checked = false)
this.selectedIdsValue = [];
this.element.querySelectorAll('input[type="checkbox"]').forEach((el) => {
el.checked = false;
});
}
selectedIdsValueChanged() {
this._updateView()
this._updateView();
}
#addHiddenFormInputsForSelectedIds(form, paramName, transactionIds) {
this.#resetFormInputs(form, paramName);
_addHiddenFormInputsForSelectedIds(form, paramName, transactionIds) {
this._resetFormInputs(form, paramName);
transactionIds.forEach(id => {
transactionIds.forEach((id) => {
const input = document.createElement("input");
input.type = 'hidden'
input.name = paramName
input.value = id
form.appendChild(input)
})
input.type = "hidden";
input.name = paramName;
input.value = id;
form.appendChild(input);
});
}
#resetFormInputs(form, paramName) {
_resetFormInputs(form, paramName) {
const existingInputs = form.querySelectorAll(`input[name='${paramName}']`);
existingInputs.forEach((input) => input.remove());
}
#rowsForGroup(group) {
return this.rowTargets.filter(row => group.contains(row))
_rowsForGroup(group) {
return this.rowTargets.filter((row) => group.contains(row));
}
#addToSelection(idToAdd) {
_addToSelection(idToAdd) {
this.selectedIdsValue = Array.from(
new Set([...this.selectedIdsValue, idToAdd])
)
new Set([...this.selectedIdsValue, idToAdd]),
);
}
#removeFromSelection(idToRemove) {
this.selectedIdsValue = this.selectedIdsValue.filter(id => id !== idToRemove)
_removeFromSelection(idToRemove) {
this.selectedIdsValue = this.selectedIdsValue.filter(
(id) => id !== idToRemove,
);
}
#selectAll() {
this.selectedIdsValue = this.rowTargets.map(t => t.dataset.id)
_selectAll() {
this.selectedIdsValue = this.rowTargets.map((t) => t.dataset.id);
}
_updateView = () => {
this.#updateSelectionBar()
this.#updateGroups()
this.#updateRows()
this._updateSelectionBar();
this._updateGroups();
this._updateRows();
};
_updateSelectionBar() {
const count = this.selectedIdsValue.length;
this.selectionBarTextTarget.innerText = `${count} ${this._pluralizedResourceName()} selected`;
this.selectionBarTarget.classList.toggle("hidden", count === 0);
this.selectionBarTarget.querySelector("input[type='checkbox']").checked =
count > 0;
}
#updateSelectionBar() {
const count = this.selectedIdsValue.length
this.selectionBarTextTarget.innerText = `${count} ${this.#pluralizedResourceName()} selected`
this.selectionBarTarget.hidden = count === 0
this.selectionBarTarget.querySelector("input[type='checkbox']").checked = count > 0
_pluralizedResourceName() {
if (this.selectedIdsValue.length === 1) {
return this.singularLabelValue;
}
return this.pluralLabelValue;
}
#pluralizedResourceName() {
return `${this.resourceValue}${this.selectedIdsValue.length === 1 ? "" : "s"}`
_updateGroups() {
this.groupTargets.forEach((group) => {
const rows = this.rowTargets.filter((row) => group.contains(row));
const groupSelected =
rows.length > 0 &&
rows.every((row) => this.selectedIdsValue.includes(row.dataset.id));
group.querySelector("input[type='checkbox']").checked = groupSelected;
});
}
#updateGroups() {
this.groupTargets.forEach(group => {
const rows = this.rowTargets.filter(row => group.contains(row))
const groupSelected = rows.length > 0 && rows.every(row => this.selectedIdsValue.includes(row.dataset.id))
group.querySelector("input[type='checkbox']").checked = groupSelected
})
}
#updateRows() {
this.rowTargets.forEach(row => {
row.checked = this.selectedIdsValue.includes(row.dataset.id)
})
_updateRows() {
this.rowTargets.forEach((row) => {
row.checked = this.selectedIdsValue.includes(row.dataset.id);
});
}
}

View File

@@ -1,28 +1,28 @@
import { Controller } from "@hotwired/stimulus"
import { Controller } from "@hotwired/stimulus";
export default class extends Controller {
static targets = ["source", "iconDefault", "iconSuccess"]
static targets = ["source", "iconDefault", "iconSuccess"];
copy(event) {
event.preventDefault();
if (this.sourceTarget && this.sourceTarget.textContent) {
navigator.clipboard.writeText(this.sourceTarget.textContent)
if (this.sourceTarget?.textContent) {
navigator.clipboard
.writeText(this.sourceTarget.textContent)
.then(() => {
this.showSuccess();
})
.catch((error) => {
console.error('Failed to copy text: ', error);
console.error("Failed to copy text: ", error);
});
}
}
showSuccess() {
this.iconDefaultTarget.classList.add('hidden');
this.iconSuccessTarget.classList.remove('hidden');
this.iconDefaultTarget.classList.add("hidden");
this.iconSuccessTarget.classList.remove("hidden");
setTimeout(() => {
this.iconDefaultTarget.classList.remove('hidden');
this.iconSuccessTarget.classList.add('hidden');
this.iconDefaultTarget.classList.remove("hidden");
this.iconSuccessTarget.classList.add("hidden");
}, 3000);
}
}

View File

@@ -3,10 +3,7 @@ import { Controller } from "@hotwired/stimulus";
// Connects to data-controller="color-avatar"
// Used by the transaction merchant form to show a preview of what the avatar will look like
export default class extends Controller {
static targets = [
"name",
"avatar"
];
static targets = ["name", "avatar"];
connect() {
this.nameTarget.addEventListener("input", this.handleNameChange);
@@ -17,8 +14,10 @@ export default class extends Controller {
}
handleNameChange = (e) => {
this.avatarTarget.textContent = (e.currentTarget.value?.[0] || "?").toUpperCase();
}
this.avatarTarget.textContent = (
e.currentTarget.value?.[0] || "?"
).toUpperCase();
};
handleColorChange(e) {
const color = e.currentTarget.value;
@@ -26,4 +25,4 @@ export default class extends Controller {
this.avatarTarget.style.borderColor = `color-mix(in srgb, ${color} 10%, white)`;
this.avatarTarget.style.color = color;
}
}
}

View File

@@ -1,59 +1,65 @@
import { Controller } from "@hotwired/stimulus";
export default class extends Controller {
static targets = [ "input", "decoration" ]
static values = { selection: String }
static targets = ["input", "decoration"];
static values = { selection: String };
connect() {
this.#renderOptions()
this.#renderOptions();
}
select({ target }) {
this.selectionValue = target.dataset.value
this.selectionValue = target.dataset.value;
}
selectionValueChanged() {
this.#options.forEach(option => {
this.#options.forEach((option) => {
if (option.dataset.value === this.selectionValue) {
this.#check(option)
this.inputTarget.value = this.selectionValue
this.#check(option);
this.inputTarget.value = this.selectionValue;
} else {
this.#uncheck(option)
this.#uncheck(option);
}
})
});
}
#renderOptions() {
this.#options.forEach(option => option.style.backgroundColor = option.dataset.value)
this.#options.forEach((option) => {
option.style.backgroundColor = option.dataset.value;
});
}
#check(option) {
option.setAttribute("aria-checked", "true")
option.style.boxShadow = `0px 0px 0px 4px ${hexToRGBA(option.dataset.value, 0.2)}`
this.decorationTarget.style.backgroundColor = option.dataset.value
option.setAttribute("aria-checked", "true");
option.style.boxShadow = `0px 0px 0px 4px ${hexToRGBA(
option.dataset.value,
0.2,
)}`;
this.decorationTarget.style.backgroundColor = option.dataset.value;
}
#uncheck(option) {
option.setAttribute("aria-checked", "false")
option.style.boxShadow = "none"
option.setAttribute("aria-checked", "false");
option.style.boxShadow = "none";
}
get #options() {
return Array.from(this.element.querySelectorAll("[role='radio']"))
return Array.from(this.element.querySelectorAll("[role='radio']"));
}
}
function hexToRGBA(hex, alpha = 1) {
hex = hex.replace(/^#/, '');
let hexCode = hex.replace(/^#/, "");
let calculatedAlpha = alpha;
if (hex.length === 8) {
alpha = parseInt(hex.slice(6, 8), 16) / 255;
hex = hex.slice(0, 6);
if (hexCode.length === 8) {
calculatedAlpha = Number.parseInt(hexCode.slice(6, 8), 16) / 255;
hexCode = hexCode.slice(0, 6);
}
let r = parseInt(hex.slice(0, 2), 16);
let g = parseInt(hex.slice(2, 4), 16);
let b = parseInt(hex.slice(4, 6), 16);
const r = Number.parseInt(hexCode.slice(0, 2), 16);
const g = Number.parseInt(hexCode.slice(2, 4), 16);
const b = Number.parseInt(hexCode.slice(4, 6), 16);
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
return `rgba(${r}, ${g}, ${b}, ${calculatedAlpha})`;
}

View File

@@ -1,30 +1,30 @@
import { Controller } from "@hotwired/stimulus";
export default class extends Controller {
static targets = ["replacementField", "submitButton"]
static classes = [ "dangerousAction", "safeAction" ]
static targets = ["replacementField", "submitButton"];
static classes = ["dangerousAction", "safeAction"];
static values = {
submitTextWhenReplacing: String,
submitTextWhenNotReplacing: String
}
submitTextWhenNotReplacing: String,
};
updateSubmitButton() {
if (this.replacementFieldTarget.value) {
this.submitButtonTarget.value = this.submitTextWhenReplacingValue
this.#markSafe()
this.submitButtonTarget.value = this.submitTextWhenReplacingValue;
this.#markSafe();
} else {
this.submitButtonTarget.value = this.submitTextWhenNotReplacingValue
this.#markDangerous()
this.submitButtonTarget.value = this.submitTextWhenNotReplacingValue;
this.#markDangerous();
}
}
#markSafe() {
this.submitButtonTarget.classList.remove(...this.dangerousActionClasses)
this.submitButtonTarget.classList.add(...this.safeActionClasses)
this.submitButtonTarget.classList.remove(...this.dangerousActionClasses);
this.submitButtonTarget.classList.add(...this.safeActionClasses);
}
#markDangerous() {
this.submitButtonTarget.classList.remove(...this.safeActionClasses)
this.submitButtonTarget.classList.add(...this.dangerousActionClasses)
this.submitButtonTarget.classList.remove(...this.safeActionClasses);
this.submitButtonTarget.classList.add(...this.dangerousActionClasses);
}
}

View File

@@ -1,9 +1,8 @@
import { Controller } from '@hotwired/stimulus'
import { Controller } from "@hotwired/stimulus";
// Connects to data-controller="element-removal"
export default class extends Controller {
remove() {
this.element.remove()
this.element.remove();
}
}

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