diff --git a/app/components/tabs_component.html.erb b/app/components/tabs_component.html.erb index 4ec901fa..9a1d938a 100644 --- a/app/components/tabs_component.html.erb +++ b/app/components/tabs_component.html.erb @@ -1,6 +1,7 @@ <%= tag.div data: { controller: "tabs", testid: testid, + tabs_session_key_value: session_key, tabs_url_param_key_value: url_param_key, tabs_nav_btn_active_class: active_btn_classes, tabs_nav_btn_inactive_class: inactive_btn_classes @@ -10,6 +11,11 @@ <% else %> <%= nav %> + <%= form_with url: cookie_session_path, scope: :cookie_session, method: :put, data: { tabs_target: "persistSelectionForm" } do |f| %> + <%= f.hidden_field :tab_key, value: "account_group_tab" %> + <%= f.hidden_field :tab_value, value: "all", data: { tabs_target: "selectionInput" } %> + <% end %> + <% panels.each do |panel| %> <%= panel %> <% end %> diff --git a/app/components/tabs_component.rb b/app/components/tabs_component.rb index 4017b308..747a9420 100644 --- a/app/components/tabs_component.rb +++ b/app/components/tabs_component.rb @@ -27,11 +27,12 @@ class TabsComponent < ViewComponent::Base } } - attr_reader :active_tab, :url_param_key, :variant, :testid + attr_reader :active_tab, :url_param_key, :session_key, :variant, :testid - def initialize(active_tab:, url_param_key: nil, variant: :default, active_btn_classes: "", inactive_btn_classes: "", testid: nil) + def initialize(active_tab:, url_param_key: nil, session_key: nil, variant: :default, active_btn_classes: "", inactive_btn_classes: "", testid: nil) @active_tab = active_tab @url_param_key = url_param_key + @session_key = session_key @variant = variant.to_sym @active_btn_classes = active_btn_classes @inactive_btn_classes = inactive_btn_classes diff --git a/app/components/tabs_controller.js b/app/components/tabs_controller.js index 43a4b192..5b83b0ad 100644 --- a/app/components/tabs_controller.js +++ b/app/components/tabs_controller.js @@ -3,8 +3,8 @@ import { Controller } from "@hotwired/stimulus"; // Connects to data-controller="tabs--components" export default class extends Controller { static classes = ["navBtnActive", "navBtnInactive"]; - static targets = ["panel", "navBtn"]; - static values = { urlParamKey: String }; + static targets = ["panel", "navBtn", "persistSelectionForm", "selectionInput"]; + static values = { sessionKey: String, urlParamKey: String }; show(e) { const btn = e.target.closest("button"); @@ -28,11 +28,16 @@ export default class extends Controller { } }); - // Update URL with the selected tab if (this.urlParamKeyValue) { const url = new URL(window.location.href); url.searchParams.set(this.urlParamKeyValue, selectedTabId); window.history.replaceState({}, "", url); } + + // Update URL with the selected tab + if (this.sessionKeyValue) { + this.selectionInputTarget.value = selectedTabId; + this.persistSelectionFormTarget.requestSubmit(); + } } } diff --git a/app/controllers/concerns/account_groupable.rb b/app/controllers/concerns/account_groupable.rb index cf7e9e1b..6d4a41c3 100644 --- a/app/controllers/concerns/account_groupable.rb +++ b/app/controllers/concerns/account_groupable.rb @@ -5,40 +5,20 @@ module AccountGroupable before_action :set_account_group_tab end - def set_account_group_tab - last_selected_tab = session[:account_group_tab] || "asset" - - selected_tab = if account_group_tab_param - account_group_tab_param - elsif on_asset_page? - "asset" - elsif on_liability_page? - "liability" - else - last_selected_tab - end - - session[:account_group_tab] = selected_tab - @account_group_tab = selected_tab - end - private + def set_account_group_tab + last_selected_tab = session["custom_account_group_tab"] || "asset" + + @account_group_tab = account_group_tab_param || last_selected_tab + end + + def valid_account_group_tabs + %w[asset liability all] + end + def account_group_tab_param - valid_tabs = %w[asset liability all] - params[:account_group_tab].in?(valid_tabs) ? params[:account_group_tab] : nil - end - - def on_asset_page? - accountable_controller_names_for("asset").include?(controller_name) - end - - def on_liability_page? - accountable_controller_names_for("liability").include?(controller_name) - end - - def accountable_controller_names_for(classification) - Accountable::TYPES.map(&:constantize) - .select { |a| a.classification == classification } - .map { |a| a.model_name.plural } + param_value = params[:account_group_tab] + return nil unless param_value.in?(valid_account_group_tabs) + param_value end end diff --git a/app/controllers/cookie_sessions_controller.rb b/app/controllers/cookie_sessions_controller.rb new file mode 100644 index 00000000..7e76636f --- /dev/null +++ b/app/controllers/cookie_sessions_controller.rb @@ -0,0 +1,22 @@ +class CookieSessionsController < ApplicationController + def update + save_kv_to_session( + cookie_session_params[:tab_key], + cookie_session_params[:tab_value] + ) + + redirect_back_or_to root_path + end + + private + def cookie_session_params + params.require(:cookie_session).permit(:tab_key, :tab_value) + end + + def save_kv_to_session(key, value) + raise "Key must be a string" unless key.is_a?(String) + raise "Value must be a string" unless value.is_a?(String) + + session["custom_#{key}"] = value + end +end diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index 3b7357f8..a5fe41d9 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -31,4 +31,8 @@ class SessionsController < ApplicationController def set_session @session = Current.user.sessions.find(params[:id]) end + + def session_params + params.require(:session).permit(:tab_key, :tab_value) + end end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 2b3c0866..4cad0863 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -87,7 +87,7 @@ class UsersController < ApplicationController def user_params params.require(:user).permit( - :first_name, :last_name, :email, :profile_image, :redirect_to, :delete_profile_image, :onboarded_at, + :first_name, :last_name, :email, :profile_image, :redirect_to, :delete_profile_image, :onboarded_at, :preferred_account_group_tab, :show_sidebar, :default_period, :show_ai_sidebar, :ai_enabled, :theme, :set_onboarding_preferences_at, :set_onboarding_goals_at, family_attributes: [ :name, :currency, :country, :locale, :date_format, :timezone, :id ], goals: [] diff --git a/app/models/balance_sheet.rb b/app/models/balance_sheet.rb index 54ed199c..090775d4 100644 --- a/app/models/balance_sheet.rb +++ b/app/models/balance_sheet.rb @@ -48,9 +48,10 @@ class BalanceSheet def account_groups(classification = nil) classification_accounts = classification ? totals_query.filter { |t| t.classification == classification } : totals_query classification_total = classification_accounts.sum(&:converted_balance) - account_groups = classification_accounts.group_by(&:accountable_type).transform_keys { |k| Accountable.from_type(k) } + account_groups = classification_accounts.group_by(&:accountable_type) + .transform_keys { |k| Accountable.from_type(k) } - account_groups.map do |accountable, accounts| + groups = account_groups.map do |accountable, accounts| group_total = accounts.sum(&:converted_balance) AccountGroup.new( @@ -71,7 +72,13 @@ class BalanceSheet account end.sort_by(&:weight).reverse ) - end.sort_by(&:weight).reverse + end + + groups.sort_by do |group| + manual_order = Accountable::TYPES + type_name = group.key.camelize + manual_order.index(type_name) || Float::INFINITY + end end def net_worth_series(period: Period.last_30_days) diff --git a/app/views/accounts/_account_sidebar_tabs.html.erb b/app/views/accounts/_account_sidebar_tabs.html.erb index 06f70c2b..9fb3acca 100644 --- a/app/views/accounts/_account_sidebar_tabs.html.erb +++ b/app/views/accounts/_account_sidebar_tabs.html.erb @@ -21,7 +21,7 @@ <% end %> - <%= render TabsComponent.new(active_tab: active_account_group_tab, testid: "account-sidebar-tabs") do |tabs| %> + <%= render TabsComponent.new(active_tab: active_account_group_tab, session_key: "account_group_tab", testid: "account-sidebar-tabs") do |tabs| %> <% tabs.with_nav do |nav| %> <% nav.with_btn(id: "asset", label: "Assets") %> <% nav.with_btn(id: "liability", label: "Debts") %> diff --git a/app/views/accounts/chart.html.erb b/app/views/accounts/chart.html.erb index b5ad27a1..b181ef20 100644 --- a/app/views/accounts/chart.html.erb +++ b/app/views/accounts/chart.html.erb @@ -2,7 +2,7 @@ <% trend = series.trend %> <%= turbo_frame_tag dom_id(@account, :chart_details) do %> - <% if @account.syncing?%> + <% if @account.syncing? %> <%= render "accounts/chart_loader" %> <% else %>
diff --git a/app/views/accounts/show/_chart.html.erb b/app/views/accounts/show/_chart.html.erb index cc6c4f79..6a1dc545 100644 --- a/app/views/accounts/show/_chart.html.erb +++ b/app/views/accounts/show/_chart.html.erb @@ -14,7 +14,7 @@ <% end %>
- <% if account.syncing? %> + <% if account.syncing? %>
<% else %> <%= tag.p format_money(account.balance_money), class: "text-primary text-3xl font-medium truncate" %> diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 361aabfd..4b6ff347 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -26,7 +26,7 @@ <%= render( "accounts/account_sidebar_tabs", family: Current.family, - active_account_group_tab: params[:account_group_tab] || "assets" + active_account_group_tab: @account_group_tab ) %> @@ -81,7 +81,7 @@ <% else %>
- <%= render "accounts/account_sidebar_tabs", family: Current.family, active_account_group_tab: params[:account_group_tab] || "assets" %> + <%= render "accounts/account_sidebar_tabs", family: Current.family, active_account_group_tab: @account_group_tab %>
<% if Current.family.trialing? && !self_hosted? %> diff --git a/app/views/pages/dashboard.html.erb b/app/views/pages/dashboard.html.erb index 79d18d4b..1f2f347e 100644 --- a/app/views/pages/dashboard.html.erb +++ b/app/views/pages/dashboard.html.erb @@ -26,9 +26,9 @@
<% if Current.family.accounts.any? %>
- <%= render partial: "pages/dashboard/net_worth_chart", locals: { - balance_sheet: @balance_sheet, - period: @period + <%= render partial: "pages/dashboard/net_worth_chart", locals: { + balance_sheet: @balance_sheet, + period: @period } %>
<% else %> diff --git a/app/views/pages/dashboard/_balance_sheet.html.erb b/app/views/pages/dashboard/_balance_sheet.html.erb index 196d96ae..c3606614 100644 --- a/app/views/pages/dashboard/_balance_sheet.html.erb +++ b/app/views/pages/dashboard/_balance_sheet.html.erb @@ -42,7 +42,7 @@
<% end %>
- <% end%> + <% end %>
diff --git a/app/views/rules/_rule.html.erb b/app/views/rules/_rule.html.erb index 9307eb80..1dbf9641 100644 --- a/app/views/rules/_rule.html.erb +++ b/app/views/rules/_rule.html.erb @@ -1,5 +1,5 @@ <%# locals: (rule:) %> -
+
">
<% if rule.name.present? %>

<%= rule.name %>

@@ -49,7 +49,7 @@ <% if rule.effective_date.nil? %> All past and future <%= rule.resource_type.pluralize %> <% else %> - <%= rule.resource_type.pluralize %> on or after <%= rule.effective_date.strftime('%b %-d, %Y') %> + <%= rule.resource_type.pluralize %> on or after <%= rule.effective_date.strftime("%b %-d, %Y") %> <% end %>

diff --git a/app/views/rules/confirm.html.erb b/app/views/rules/confirm.html.erb index 28be9496..03311791 100644 --- a/app/views/rules/confirm.html.erb +++ b/app/views/rules/confirm.html.erb @@ -1,5 +1,5 @@ <%= render DialogComponent.new(reload_on_close: true) do |dialog| %> - <% + <% title = if @rule.name.present? "Confirm changes to \"#{@rule.name}\"" else @@ -7,7 +7,7 @@ end %> <% dialog.with_header(title: title) %> - + <% dialog.with_body do %>

You are about to apply this rule to diff --git a/app/views/rules/edit.html.erb b/app/views/rules/edit.html.erb index f73edc90..91dea816 100644 --- a/app/views/rules/edit.html.erb +++ b/app/views/rules/edit.html.erb @@ -1,7 +1,7 @@ <%= link_to "Back to rules", rules_path %> <%= render DialogComponent.new do |dialog| %> - <% + <% title = if @rule.name.present? "Edit #{@rule.resource_type} rule \"#{@rule.name}\"" else diff --git a/app/views/rules/index.html.erb b/app/views/rules/index.html.erb index d549e0f8..c6ca513f 100644 --- a/app/views/rules/index.html.erb +++ b/app/views/rules/index.html.erb @@ -60,7 +60,7 @@

<% @rules.each_with_index do |rule, idx| %> - <%= render "rule", rule: rule%> + <%= render "rule", rule: rule %> <% unless idx == @rules.size - 1 %>
<% end %> diff --git a/config/routes.rb b/config/routes.rb index 132fa06a..1d68af04 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -26,6 +26,8 @@ Rails.application.routes.draw do get "changelog", to: "pages#changelog" get "feedback", to: "pages#feedback" + resource :cookie_session, only: %i[update] + resource :registration, only: %i[new create] resources :sessions, only: %i[new create destroy] resource :password_reset, only: %i[new create edit update] diff --git a/db/schema.rb b/db/schema.rb index 10ac65c8..04b8548b 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.2].define(version: 2025_05_13_122703) do +ActiveRecord::Schema[7.2].define(version: 2025_05_14_143017) do # These are extensions that must be enabled in order to support this database enable_extension "pgcrypto" enable_extension "plpgsql" @@ -696,6 +696,7 @@ ActiveRecord::Schema[7.2].define(version: 2025_05_13_122703) do t.text "goals", default: [], array: true t.datetime "set_onboarding_preferences_at" t.datetime "set_onboarding_goals_at" + t.string "preferred_account_group_tab", default: "asset" t.index ["email"], name: "index_users_on_email", unique: true t.index ["family_id"], name: "index_users_on_family_id" t.index ["last_viewed_chat_id"], name: "index_users_on_last_viewed_chat_id"