diff --git a/Gemfile b/Gemfile index 47bb67b1..26b29fc9 100644 --- a/Gemfile +++ b/Gemfile @@ -60,7 +60,7 @@ gem "stripe" gem "intercom-rails" gem "plaid" gem "rotp", "~> 6.3" -gem "rqrcode", "~> 2.2" +gem "rqrcode", "~> 3.0" gem "activerecord-import" # AI diff --git a/Gemfile.lock b/Gemfile.lock index 4c2dfed6..fc762e15 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -134,7 +134,7 @@ GEM chunky_png (1.4.0) climate_control (1.2.0) concurrent-ruby (1.3.5) - connection_pool (2.5.0) + connection_pool (2.5.2) crack (1.0.0) bigdecimal rexml @@ -163,7 +163,7 @@ GEM event_stream_parser (1.0.0) faker (3.5.1) i18n (>= 1.8.11, < 2) - faraday (2.13.0) + faraday (2.13.1) faraday-net_http (>= 2.0, < 3.5) json logger @@ -230,7 +230,7 @@ GEM rdoc (>= 4.0.0) reline (>= 0.4.2) jmespath (1.6.2) - json (2.10.2) + json (2.11.3) jwt (2.10.1) base64 language_server-protocol (3.17.0.4) @@ -316,7 +316,7 @@ GEM racc (~> 1.4) nokogiri (1.18.6-x86_64-linux-musl) racc (~> 1.4) - octokit (9.2.0) + octokit (10.0.0) faraday (>= 1, < 3) sawyer (~> 0.9) pagy (9.3.4) @@ -325,7 +325,7 @@ GEM ast (~> 2.4.1) racc pg (1.5.9) - plaid (36.1.0) + plaid (37.0.0) faraday (>= 1.0.1, < 3.0) faraday-multipart (>= 1.0.1, < 2.0) platform_agent (1.0.1) @@ -347,7 +347,7 @@ GEM puma (6.6.0) nio4r (~> 2.0) racc (1.8.1) - rack (3.1.12) + rack (3.1.13) rack-mini-profiler (3.3.1) rack (>= 1.2.0) rack-session (2.1.0) @@ -397,7 +397,7 @@ GEM rb-fsevent (0.11.2) rb-inotify (0.11.1) ffi (~> 1.0) - rbs (3.9.1) + rbs (3.9.2) logger rdoc (6.13.0) psych (>= 4.0.0) @@ -412,10 +412,10 @@ GEM rexml (3.4.1) rotp (6.3.0) rouge (4.5.1) - rqrcode (2.2.0) + rqrcode (3.0.0) chunky_png (~> 1.0) - rqrcode_core (~> 1.0) - rqrcode_core (1.2.0) + rqrcode_core (~> 2.0) + rqrcode_core (2.0.0) rubocop (1.74.0) json (~> 2.3) language_server-protocol (~> 3.17.0.2) @@ -443,13 +443,13 @@ GEM rubocop (>= 1.72) rubocop-performance (>= 1.24) rubocop-rails (>= 2.30) - ruby-lsp (0.23.12) + ruby-lsp (0.23.15) language_server-protocol (~> 3.17.0) prism (>= 1.2, < 2.0) rbs (>= 3, < 4) sorbet-runtime (>= 0.5.10782) - ruby-lsp-rails (0.4.0) - ruby-lsp (>= 0.23.0, < 0.24.0) + ruby-lsp-rails (0.4.1) + ruby-lsp (>= 0.23.14, < 0.24.0) ruby-openai (8.1.0) event_stream_parser (>= 0.3.0, < 2.0.0) faraday (>= 1) @@ -479,7 +479,7 @@ GEM sentry-sidekiq (5.23.0) sentry-ruby (~> 5.23.0) sidekiq (>= 3.0) - sidekiq (8.0.2) + sidekiq (8.0.3) connection_pool (>= 2.5.0) json (>= 2.9.0) logger (>= 1.6.2) @@ -494,7 +494,7 @@ GEM skylight (6.0.4) activesupport (>= 5.2.0) smart_properties (1.17.0) - sorbet-runtime (0.5.11953) + sorbet-runtime (0.5.12043) stimulus-rails (1.3.4) railties (>= 6.0.0) stringio (3.1.5) @@ -602,7 +602,7 @@ DEPENDENCIES redcarpet redis (~> 5.4) rotp (~> 6.3) - rqrcode (~> 2.2) + rqrcode (~> 3.0) rubocop-rails-omakase ruby-lsp-rails ruby-openai diff --git a/README.md b/README.md index c0dd6e65..b55b642a 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ -dashboard_mockup -(Note: The image above is a mockup of what we're working towards. We're rapidly approaching the functionality shown, but not all of the parts are ready just yet.) -# Maybe: The OS for your personal finances +maybe_hero + +# Maybe: The personal finance app for everyone Get involved: [Discord](https://link.maybe.co/discord) • [Website](https://maybefinance.com) • [Issues](https://github.com/maybe-finance/maybe/issues) diff --git a/app/javascript/controllers/time_series_chart_controller.js b/app/javascript/controllers/time_series_chart_controller.js index 7fc86100..664e989d 100644 --- a/app/javascript/controllers/time_series_chart_controller.js +++ b/app/javascript/controllers/time_series_chart_controller.js @@ -322,7 +322,7 @@ export default class extends Controller { .attr("class", "data-point-circle") .attr("cx", this._d3XScale(d.date)) .attr("cy", this._d3YScale(this._getDatumValue(d))) - .attr("r", 8) + .attr("r", 10) .attr("fill", this._trendColor) .attr("fill-opacity", "0.1") .attr("pointer-events", "none"); @@ -333,7 +333,7 @@ export default class extends Controller { .attr("class", "data-point-circle") .attr("cx", this._d3XScale(d.date)) .attr("cy", this._d3YScale(this._getDatumValue(d))) - .attr("r", 3) + .attr("r", 5) .attr("fill", this._trendColor) .attr("pointer-events", "none"); @@ -364,19 +364,17 @@ export default class extends Controller {
${datum.date_formatted}
- -
-
- - - - +
+
+
+ ${datum.trend.previous.amount === datum.trend.current.amount ? ` + + ` : Number(datum.trend.previous.amount) < Number(datum.trend.current.amount) ? ` + + ` : ` + + `} +
${this._extractFormattedValue(datum.trend.current)}
diff --git a/app/models/sync.rb b/app/models/sync.rb index 04141570..201fed02 100644 --- a/app/models/sync.rb +++ b/app/models/sync.rb @@ -83,6 +83,7 @@ class Sync < ApplicationRecord Sentry.capture_exception(error) do |scope| scope.set_context("sync", { id: id, syncable_type: syncable_type, syncable_id: syncable_id }) + scope.set_tags(sync_id: id) end update!( diff --git a/app/views/accounts/chart.html.erb b/app/views/accounts/chart.html.erb index 6be29472..11dcbaac 100644 --- a/app/views/accounts/chart.html.erb +++ b/app/views/accounts/chart.html.erb @@ -3,16 +3,7 @@ <%= turbo_frame_tag dom_id(@account, :chart_details) do %>
- <% if trend.direction.flat? %> - <%= tag.span t(".no_change"), class: "text-secondary" %> - <% else %> - <%= tag.span "#{trend.value.positive? ? "+" : ""}#{format_money(trend.value)}", style: "color: #{trend.color}" %> - <% unless trend.percent.infinite? %> - <%= tag.span "(#{trend.percent}%)", style: "color: #{trend.color}" %> - <% end %> - <% end %> - - <%= tag.span @period.comparison_label, class: "text-secondary" %> + <%= render partial: "shared/trend_change", locals: { trend: trend, comparison_label: @period.comparison_label } %>
@@ -24,7 +15,7 @@ data-time-series-chart-data-value="<%= series.to_json %>">
<% else %>
-

No data available for the selected period.

+

<%= t(".data_not_available") %>

<% end %>
diff --git a/app/views/holdings/index.html.erb b/app/views/holdings/index.html.erb index 0b6eb62d..7da9cd4c 100644 --- a/app/views/holdings/index.html.erb +++ b/app/views/holdings/index.html.erb @@ -15,7 +15,7 @@
<%= tag.p t(".name"), class: "col-span-4" %> <%= tag.p t(".weight"), class: "col-span-2 justify-self-end" %> - <%= tag.p t(".cost"), class: "col-span-2 justify-self-end" %> + <%= tag.p t(".average_cost"), class: "col-span-2 justify-self-end" %> <%= tag.p t(".holdings"), class: "col-span-2 justify-self-end" %> <%= tag.p t(".return"), class: "col-span-2 justify-self-end" %>
diff --git a/app/views/holdings/show.html.erb b/app/views/holdings/show.html.erb index 195aba63..f536da9c 100644 --- a/app/views/holdings/show.html.erb +++ b/app/views/holdings/show.html.erb @@ -38,7 +38,7 @@
-
<%= t(".trend_label") %>
+
<%= t(".total_return_label") %>
<%= @holding.trend ? render("shared/trend_change", trend: @holding.trend) : t(".unknown") %>
diff --git a/app/views/pages/dashboard/_net_worth_chart.html.erb b/app/views/pages/dashboard/_net_worth_chart.html.erb index b36e57ba..dfa22031 100644 --- a/app/views/pages/dashboard/_net_worth_chart.html.erb +++ b/app/views/pages/dashboard/_net_worth_chart.html.erb @@ -3,19 +3,14 @@
-

Net Worth

+

<%= t(".title") %>

<%= series.current.format %>

<% if series.trend.nil? %> -

Data not available for the selected period

- <% elsif series.trend.direction.flat? %> -

No change vs. prior period

+

<%= t(".data_not_available") %>

<% else %> -
- <%= render partial: "shared/trend_change", locals: { trend: series.trend } %> - <%= period.comparison_label %> -
+ <%= render partial: "shared/trend_change", locals: { trend: series.trend, comparison_label: period.comparison_label } %> <% end %>
@@ -32,6 +27,6 @@ data-time-series-chart-data-value="<%= series.to_json %>">
<% else %>
-

No data available for the selected period.

+

<%= t(".data_not_available") %>

<% end %> diff --git a/app/views/shared/_trend_change.html.erb b/app/views/shared/_trend_change.html.erb index 3235bc0e..b2503a7a 100644 --- a/app/views/shared/_trend_change.html.erb +++ b/app/views/shared/_trend_change.html.erb @@ -1,8 +1,8 @@ -<%# locals: { trend: } %> +<%# locals: { trend:, comparison_label: nil } %>

<% if trend.direction.flat? %> - No change + <%= t(".no_change") %><%= " #{comparison_label}" if defined?(comparison_label) && comparison_label.present? %> <% else %> <%= trend.value.is_a?(Money) ? format_money(trend.value) : trend.value.round(2) %> @@ -10,5 +10,8 @@ <% unless trend.percent.infinite? %> (<%= lucide_icon(trend.icon, class: "w-4 h-4 align-text-bottom inline") %><%= trend.percent_formatted %>) <% end %> + + <%= " #{comparison_label}" if defined?(comparison_label) && comparison_label.present? %> + <% end %>

diff --git a/compose.example.yml b/compose.example.yml index ea6b5b63..c50602a7 100644 --- a/compose.example.yml +++ b/compose.example.yml @@ -41,6 +41,8 @@ x-rails-env: &rails_env DB_HOST: db DB_PORT: 5432 REDIS_URL: redis://redis:6379/1 +# NOTE: enabling OpenAI will incur costs when you use AI-related features in the app (chat, rules). Make sure you have set appropriate spend limits on your account before adding this. + OPENAI_ACCESS_TOKEN: ${OPENAI_ACCESS_TOKEN} services: web: @@ -97,4 +99,4 @@ services: volumes: app-storage: postgres-data: - redis-data: \ No newline at end of file + redis-data: diff --git a/config/locales/views/accounts/en.yml b/config/locales/views/accounts/en.yml index f557755e..e384ef79 100644 --- a/config/locales/views/accounts/en.yml +++ b/config/locales/views/accounts/en.yml @@ -4,7 +4,7 @@ en: account: troubleshoot: Troubleshoot chart: - no_change: no change + data_not_available: Data not available for the selected period create: success: "%{type} account created" destroy: diff --git a/config/locales/views/holdings/en.yml b/config/locales/views/holdings/en.yml index 3003d629..933a8b83 100644 --- a/config/locales/views/holdings/en.yml +++ b/config/locales/views/holdings/en.yml @@ -9,13 +9,13 @@ en: per_share: per share shares: "%{qty} shares" index: - cost: cost + average_cost: Average cost holdings: Holdings - name: name + name: Name new_holding: New transaction no_holdings: No holdings to show. - return: total return - weight: weight + return: Total return + weight: Weight missing_price_tooltip: description: This investment has missing values and we could not calculate its returns or value. @@ -33,5 +33,5 @@ en: settings: Settings ticker_label: Ticker trade_history_entry: "%{qty} shares of %{security} at %{price}" - trend_label: Trend + total_return_label: Total Return unknown: Unknown diff --git a/config/locales/views/pages/en.yml b/config/locales/views/pages/en.yml index ad5f4942..1ee15ca3 100644 --- a/config/locales/views/pages/en.yml +++ b/config/locales/views/pages/en.yml @@ -4,6 +4,9 @@ en: changelog: title: What's new dashboard: + net_worth_chart: + data_not_available: Data not available for the selected period + title: Net Worth no_account_empty_state: new_account: New account no_account_subtitle: Since no accounts have been added, there's no data to diff --git a/config/locales/views/shared/en.yml b/config/locales/views/shared/en.yml index cb632738..37e22cd3 100644 --- a/config/locales/views/shared/en.yml +++ b/config/locales/views/shared/en.yml @@ -10,3 +10,5 @@ en: label: Amount syncing_notice: syncing: Syncing accounts data... + trend_change: + no_change: "no change"