From 9bda7efc3f4a348383211471a74c6d79c8bfb00f Mon Sep 17 00:00:00 2001 From: Zach Gollwitzer Date: Thu, 18 Apr 2024 07:56:51 -0400 Subject: [PATCH] New Settings Menu, Routes and Controllers Organization (#641) * Add new settings routes and controllers * Add new settings view, restructure controllers and routes * Fix lint errors --- app/controllers/accounts_controller.rb | 12 +++ app/controllers/pages_controller.rb | 9 ++ .../settings/billings_controller.rb | 7 ++ .../settings/hostings_controller.rb | 46 ++++++++++ .../settings/notifications_controller.rb | 7 ++ .../settings/preferences_controller.rb | 7 ++ .../profiles_controller.rb} | 4 +- .../settings/securities_controller.rb | 7 ++ .../settings/self_hosting_controller.rb | 46 ---------- .../transactions/categories_controller.rb | 3 + .../transactions/merchants_controller.rb | 4 + .../transactions/rules_controller.rb | 4 + app/helpers/application_helper.rb | 21 ++--- app/helpers/settings/hosting_helper.rb | 2 + app/helpers/settings/self_hosting_helper.rb | 2 - app/helpers/settings_helper.rb | 9 ++ .../controllers/hotkey_controller.js | 12 ++- app/javascript/tailwindColors.js | 22 ++--- app/views/accounts/index.html.erb | 17 +++- app/views/accounts/summary.html.erb | 76 ++++++++++++++++ app/views/layouts/_sidebar.html.erb | 88 +++++++++++++++++++ app/views/layouts/application.html.erb | 77 ++++------------ .../pages/_account_percentages_table.html.erb | 2 +- app/views/pages/changelog.html.erb | 15 ++++ app/views/pages/feedback.html.erb | 15 ++++ app/views/pages/invites.html.erb | 14 +++ app/views/settings/_nav.html.erb | 56 ++++++++++++ app/views/settings/_nav_link_large.html.erb | 17 ++++ app/views/settings/billings/show.html.erb | 19 ++++ app/views/settings/edit.html.erb | 17 ---- .../edit.html.erb => hostings/show.html.erb} | 13 ++- .../settings/notifications/show.html.erb | 15 ++++ app/views/settings/preferences/show.html.erb | 15 ++++ app/views/settings/profiles/show.html.erb | 25 ++++++ app/views/settings/securities/show.html.erb | 15 ++++ .../transactions/categories/index.html.erb | 15 ++++ .../transactions/merchants/index.html.erb | 15 ++++ app/views/transactions/rules/index.html.erb | 15 ++++ config/locales/views/account/en.yml | 2 + config/locales/views/layout/en.yml | 10 +-- config/locales/views/settings/en.yml | 19 +++- config/routes.rb | 27 ++++-- test/application_system_test_case.rb | 7 +- .../settings/billings_controller_test.rb | 0 ...er_test.rb => hostings_controller_test.rb} | 10 +-- .../settings/notifications_controller_test.rb | 11 +++ .../settings/preferences_controller_test.rb | 11 +++ .../settings/profiles_controller_test.rb | 11 +++ .../settings/securities_controller_test.rb | 11 +++ test/system/accounts_test.rb | 20 ----- test/system/settings_test.rb | 66 ++++++++++++++ test/test_helper.rb | 4 + 52 files changed, 771 insertions(+), 203 deletions(-) create mode 100644 app/controllers/settings/billings_controller.rb create mode 100644 app/controllers/settings/hostings_controller.rb create mode 100644 app/controllers/settings/notifications_controller.rb create mode 100644 app/controllers/settings/preferences_controller.rb rename app/controllers/{settings_controller.rb => settings/profiles_controller.rb} (90%) create mode 100644 app/controllers/settings/securities_controller.rb delete mode 100644 app/controllers/settings/self_hosting_controller.rb create mode 100644 app/controllers/transactions/merchants_controller.rb create mode 100644 app/controllers/transactions/rules_controller.rb create mode 100644 app/helpers/settings/hosting_helper.rb delete mode 100644 app/helpers/settings/self_hosting_helper.rb create mode 100644 app/helpers/settings_helper.rb create mode 100644 app/views/accounts/summary.html.erb create mode 100644 app/views/layouts/_sidebar.html.erb create mode 100644 app/views/pages/changelog.html.erb create mode 100644 app/views/pages/feedback.html.erb create mode 100644 app/views/pages/invites.html.erb create mode 100644 app/views/settings/_nav.html.erb create mode 100644 app/views/settings/_nav_link_large.html.erb create mode 100644 app/views/settings/billings/show.html.erb delete mode 100644 app/views/settings/edit.html.erb rename app/views/settings/{self_hosting/edit.html.erb => hostings/show.html.erb} (79%) create mode 100644 app/views/settings/notifications/show.html.erb create mode 100644 app/views/settings/preferences/show.html.erb create mode 100644 app/views/settings/profiles/show.html.erb create mode 100644 app/views/settings/securities/show.html.erb create mode 100644 app/views/transactions/categories/index.html.erb create mode 100644 app/views/transactions/merchants/index.html.erb create mode 100644 app/views/transactions/rules/index.html.erb create mode 100644 test/controllers/settings/billings_controller_test.rb rename test/controllers/settings/{self_hosting_controller_test.rb => hostings_controller_test.rb} (62%) create mode 100644 test/controllers/settings/notifications_controller_test.rb create mode 100644 test/controllers/settings/preferences_controller_test.rb create mode 100644 test/controllers/settings/profiles_controller_test.rb create mode 100644 test/controllers/settings/securities_controller_test.rb delete mode 100644 test/system/accounts_test.rb create mode 100644 test/system/settings_test.rb diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb index fce6e71f..131de698 100644 --- a/app/controllers/accounts_controller.rb +++ b/app/controllers/accounts_controller.rb @@ -2,6 +2,18 @@ class AccountsController < ApplicationController include Filterable before_action :set_account, only: %i[ show update destroy sync ] + def index + @accounts = Current.family.accounts + end + + def summary + snapshot = Current.family.snapshot(@period) + @net_worth_series = snapshot[:net_worth_series] + @asset_series = snapshot[:asset_series] + @liability_series = snapshot[:liability_series] + @account_groups = Current.family.accounts.by_group(period: @period, currency: Current.family.currency) + end + def new @account = Account.new( balance: nil, diff --git a/app/controllers/pages_controller.rb b/app/controllers/pages_controller.rb index 7c10feb5..3ca53b26 100644 --- a/app/controllers/pages_controller.rb +++ b/app/controllers/pages_controller.rb @@ -8,4 +8,13 @@ class PagesController < ApplicationController @liability_series = snapshot[:liability_series] @account_groups = Current.family.accounts.by_group(period: @period, currency: Current.family.currency) end + + def changelog + end + + def feedback + end + + def invites + end end diff --git a/app/controllers/settings/billings_controller.rb b/app/controllers/settings/billings_controller.rb new file mode 100644 index 00000000..d218beaa --- /dev/null +++ b/app/controllers/settings/billings_controller.rb @@ -0,0 +1,7 @@ +class Settings::BillingsController < ApplicationController + def edit + end + + def update + end +end diff --git a/app/controllers/settings/hostings_controller.rb b/app/controllers/settings/hostings_controller.rb new file mode 100644 index 00000000..7d3a6639 --- /dev/null +++ b/app/controllers/settings/hostings_controller.rb @@ -0,0 +1,46 @@ +class Settings::HostingsController < ApplicationController + before_action :verify_hosting_mode + + def show + end + + def update + if all_updates_valid? + hosting_params.keys.each do |key| + Setting.send("#{key}=", hosting_params[key].strip) + end + + redirect_to settings_hosting_path, notice: t(".success") + else + flash.now[:error] = @errors.first.message + render :edit, status: :unprocessable_entity + end + end + + private + def all_updates_valid? + @errors = ActiveModel::Errors.new(Setting) + hosting_params.keys.each do |key| + setting = Setting.new(var: key) + setting.value = hosting_params[key].strip + + unless setting.valid? + @errors.merge!(setting.errors) + end + end + + if hosting_params[:upgrades_mode] == "auto" && hosting_params[:render_deploy_hook].blank? + @errors.add(:render_deploy_hook, t("settings.hostings.update.render_deploy_hook_error")) + end + + @errors.empty? + end + + def hosting_params + params.require(:setting).permit(:render_deploy_hook, :upgrades_mode, :upgrades_target) + end + + def verify_hosting_mode + head :not_found unless self_hosted? + end +end diff --git a/app/controllers/settings/notifications_controller.rb b/app/controllers/settings/notifications_controller.rb new file mode 100644 index 00000000..8fceff14 --- /dev/null +++ b/app/controllers/settings/notifications_controller.rb @@ -0,0 +1,7 @@ +class Settings::NotificationsController < ApplicationController + def edit + end + + def update + end +end diff --git a/app/controllers/settings/preferences_controller.rb b/app/controllers/settings/preferences_controller.rb new file mode 100644 index 00000000..aecbce53 --- /dev/null +++ b/app/controllers/settings/preferences_controller.rb @@ -0,0 +1,7 @@ +class Settings::PreferencesController < ApplicationController + def edit + end + + def update + end +end diff --git a/app/controllers/settings_controller.rb b/app/controllers/settings/profiles_controller.rb similarity index 90% rename from app/controllers/settings_controller.rb rename to app/controllers/settings/profiles_controller.rb index b990f90b..d510bf05 100644 --- a/app/controllers/settings_controller.rb +++ b/app/controllers/settings/profiles_controller.rb @@ -1,5 +1,5 @@ -class SettingsController < ApplicationController - def edit +class Settings::ProfilesController < ApplicationController + def show end def update diff --git a/app/controllers/settings/securities_controller.rb b/app/controllers/settings/securities_controller.rb new file mode 100644 index 00000000..6e49acf0 --- /dev/null +++ b/app/controllers/settings/securities_controller.rb @@ -0,0 +1,7 @@ +class Settings::SecuritiesController < ApplicationController + def edit + end + + def update + end +end diff --git a/app/controllers/settings/self_hosting_controller.rb b/app/controllers/settings/self_hosting_controller.rb deleted file mode 100644 index 7bd71791..00000000 --- a/app/controllers/settings/self_hosting_controller.rb +++ /dev/null @@ -1,46 +0,0 @@ -class Settings::SelfHostingController < ApplicationController - before_action :verify_self_hosting_enabled - - def edit - end - - def update - if all_updates_valid? - self_hosting_params.keys.each do |key| - Setting.send("#{key}=", self_hosting_params[key].strip) - end - - redirect_to edit_settings_self_hosting_path, notice: t(".success") - else - flash.now[:error] = @errors.first.message - render :edit, status: :unprocessable_entity - end - end - - private - def all_updates_valid? - @errors = ActiveModel::Errors.new(Setting) - self_hosting_params.keys.each do |key| - setting = Setting.new(var: key) - setting.value = self_hosting_params[key].strip - - unless setting.valid? - @errors.merge!(setting.errors) - end - end - - if self_hosting_params[:upgrades_mode] == "auto" && self_hosting_params[:render_deploy_hook].blank? - @errors.add(:render_deploy_hook, t("settings.self_hosting.update.render_deploy_hook_error")) - end - - @errors.empty? - end - - def self_hosting_params - params.require(:setting).permit(:render_deploy_hook, :upgrades_mode, :upgrades_target) - end - - def verify_self_hosting_enabled - head :not_found unless self_hosted? - end -end diff --git a/app/controllers/transactions/categories_controller.rb b/app/controllers/transactions/categories_controller.rb index 9a1834b1..036ea4bf 100644 --- a/app/controllers/transactions/categories_controller.rb +++ b/app/controllers/transactions/categories_controller.rb @@ -1,6 +1,9 @@ class Transactions::CategoriesController < ApplicationController before_action :set_category, only: [ :update, :destroy ] + def index + end + def create if Current.family.transaction_categories.create(category_params) redirect_to transactions_path, notice: t(".success") diff --git a/app/controllers/transactions/merchants_controller.rb b/app/controllers/transactions/merchants_controller.rb new file mode 100644 index 00000000..dae8ba3a --- /dev/null +++ b/app/controllers/transactions/merchants_controller.rb @@ -0,0 +1,4 @@ +class Transactions::MerchantsController < ApplicationController + def index + end +end diff --git a/app/controllers/transactions/rules_controller.rb b/app/controllers/transactions/rules_controller.rb new file mode 100644 index 00000000..ff27f690 --- /dev/null +++ b/app/controllers/transactions/rules_controller.rb @@ -0,0 +1,4 @@ +class Transactions::RulesController < ApplicationController + def index + end +end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 87913e3c..d37472bd 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -38,21 +38,16 @@ module ApplicationHelper end def sidebar_link_to(name, path, options = {}) - base_class_names = [ "block", "border", "border-transparent", "rounded-xl", "-ml-2", "p-2", "text-sm", "font-medium", "text-gray-500", "flex", "items-center" ] - hover_class_names = [ "hover:bg-white", "hover:border-alpha-black-50", "hover:text-gray-900", "hover:shadow-xs" ] - current_page_class_names = [ "bg-white", "border-alpha-black-50", "text-gray-900", "shadow-xs" ] + is_current = current_page?(path) || (request.path.start_with?(path) && path != "/") - link_class_names = if current_page?(path) || (request.path.start_with?(path) && path != "/") - base_class_names.delete("border-transparent") - base_class_names + hover_class_names + current_page_class_names - else - base_class_names + hover_class_names - end + classes = [ + "flex items-center gap-2 px-3 py-2 rounded-xl border text-sm font-medium text-gray-500", + (is_current ? "bg-white text-gray-900 shadow-xs border-alpha-black-50" : "hover:bg-gray-100 border-transparent") + ].compact.join(" ") - merged_options = options.reverse_merge(class: link_class_names.join(" ")).except(:icon) - - link_to path, merged_options do - lucide_icon(options[:icon], class: "w-5 h-5 mr-2") + name + link_to path, **options.merge(class: classes), aria: { current: ("page" if current_page?(path)) } do + concat(lucide_icon(options[:icon], class: "w-5 h-5")) if options[:icon] + concat(name) end end diff --git a/app/helpers/settings/hosting_helper.rb b/app/helpers/settings/hosting_helper.rb new file mode 100644 index 00000000..b92b592b --- /dev/null +++ b/app/helpers/settings/hosting_helper.rb @@ -0,0 +1,2 @@ +module Settings::HostingHelper +end diff --git a/app/helpers/settings/self_hosting_helper.rb b/app/helpers/settings/self_hosting_helper.rb deleted file mode 100644 index 7240b433..00000000 --- a/app/helpers/settings/self_hosting_helper.rb +++ /dev/null @@ -1,2 +0,0 @@ -module Settings::SelfHostingHelper -end diff --git a/app/helpers/settings_helper.rb b/app/helpers/settings_helper.rb new file mode 100644 index 00000000..f62f9af1 --- /dev/null +++ b/app/helpers/settings_helper.rb @@ -0,0 +1,9 @@ +module SettingsHelper + def next_setting(title, path) + render partial: "settings/nav_link_large", locals: { path: path, direction: "next", title: title } + end + + def previous_setting(title, path) + render partial: "settings/nav_link_large", locals: { path: path, direction: "previous", title: title } + end +end diff --git a/app/javascript/controllers/hotkey_controller.js b/app/javascript/controllers/hotkey_controller.js index 88df7f9d..b7b346e3 100644 --- a/app/javascript/controllers/hotkey_controller.js +++ b/app/javascript/controllers/hotkey_controller.js @@ -1,13 +1,17 @@ -import { Controller } from "@hotwired/stimulus" -import { install, uninstall } from "@github/hotkey" +import { Controller } from "@hotwired/stimulus"; +import { install, uninstall } from "@github/hotkey"; // Connects to data-controller="hotkey" export default class extends Controller { connect() { - install(this.element) + install(this.element); } disconnect() { - uninstall(this.element) + uninstall(this.element); + } + + navigateBack(event) { + window.history.back(); } } diff --git a/app/javascript/tailwindColors.js b/app/javascript/tailwindColors.js index 5006b01d..2f5e1a4f 100644 --- a/app/javascript/tailwindColors.js +++ b/app/javascript/tailwindColors.js @@ -38,17 +38,17 @@ export default { 900: "rgba(255, 255, 255, 0.7)", }, "alpha-black": { - 25: "rgba(20, 20, 20, 0.03)", - 50: "rgba(20, 20, 20, 0.05)", - 100: "rgba(20, 20, 20, 0.08)", - 200: "rgba(20, 20, 20, 0.1)", - 300: "rgba(20, 20, 20, 0.15)", - 400: "rgba(20, 20, 20, 0.2)", - 500: "rgba(20, 20, 20, 0.3)", - 600: "rgba(20, 20, 20, 0.4)", - 700: "rgba(20, 20, 20, 0.5)", - 800: "rgba(20, 20, 20, 0.6)", - 900: "rgba(20, 20, 20, 0.7)", + 25: "rgba(11, 11, 11, 0.03)", + 50: "rgba(11, 11, 11, 0.05)", + 100: "rgba(11, 11, 11, 0.08)", + 200: "rgba(11, 11, 11, 0.1)", + 300: "rgba(11, 11, 11, 0.15)", + 400: "rgba(11, 11, 11, 0.2)", + 500: "rgba(11, 11, 11, 0.3)", + 600: "rgba(11, 11, 11, 0.4)", + 700: "rgba(11, 11, 11, 0.5)", + 800: "rgba(11, 11, 11, 0.6)", + 900: "rgba(11, 11, 11, 0.7)", }, red: { 25: "#FFFBFB", diff --git a/app/views/accounts/index.html.erb b/app/views/accounts/index.html.erb index b0ecf2b9..c14d2aa3 100644 --- a/app/views/accounts/index.html.erb +++ b/app/views/accounts/index.html.erb @@ -1,3 +1,6 @@ +<% content_for :sidebar do %> + <%= render "settings/nav" %> +<% end %>

Accounts

@@ -6,7 +9,7 @@ <%= t(".new_account") %> <% end %>
- <% if Current.family.accounts.empty? %> + <% if @accounts.empty? %>

No accounts yet

@@ -19,8 +22,8 @@
<% else %>
- <% Current.family.accounts.by_provider.each do |item| %> -
+ <% @accounts.by_provider.each do |item| %> +
<%= lucide_icon("chevron-down", class: "hidden group-open:block w-5 h-5 text-gray-500") %> <%= lucide_icon("chevron-right", class: "group-open:hidden w-5 h-5 text-gray-500") %> @@ -54,4 +57,12 @@ <% end %>
<% end %> +
+ <% if self_hosted? %> + <%= previous_setting("Self Hosting", settings_hosting_path) %> + <% else %> + <%= previous_setting("Billing", settings_billing_path) %> + <% end %> + <%= next_setting("Categories", transactions_categories_path) %> +
diff --git a/app/views/accounts/summary.html.erb b/app/views/accounts/summary.html.erb new file mode 100644 index 00000000..a9c72486 --- /dev/null +++ b/app/views/accounts/summary.html.erb @@ -0,0 +1,76 @@ +
+
+

Accounts

+ <%= link_to new_account_path, class: "flex text-white text-sm font-medium items-center gap-1 bg-gray-900 rounded-lg p-2", data: { turbo_frame: "modal" } do %> + <%= lucide_icon("plus", class: "w-5 h-5") %> + <%= t(".new") %> + <% end %> +
+
+
+
+ <%= render partial: "shared/balance_heading", locals: { + label: "Assets", + period: @period, + balance: Current.family.assets, + trend: @asset_series.trend + } %> +
+
+
+
+
+ <%= render partial: "shared/balance_heading", locals: { + label: "Liabilities", + period: @period, + size: "md", + balance: Current.family.liabilities, + trend: @liability_series.trend + } %> +
+
+
+
+
+
+

Assets

+
+ <%= link_to new_account_path, class: "flex items-center gap-1 p-2 text-gray-900 text-sm font-medium bg-gray-50 rounded-lg hover:bg-gray-100", data: { turbo_frame: "modal" } do %> + <%= lucide_icon("plus", class: "w-5 h-5 text-gray-500") %> +

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

+ <% end %> + <%= form_with url: summary_accounts_path, method: :get, class: "flex items-center gap-4", data: { controller: "auto-submit-form" } do %> + <%= render partial: "shared/period_select", locals: { value: @period.name } %> + <% end %> +
+
+ <%= render partial: "pages/account_percentages_bar", locals: { account_groups: @account_groups[:assets].children } %> + <%= render partial: "pages/account_percentages_table", locals: { account_groups: @account_groups[:assets].children } %> +
+
+
+

Liabilities

+
+ <%= link_to new_account_path, class: "flex items-center gap-1 p-2 text-gray-900 text-sm font-medium bg-gray-50 rounded-lg hover:bg-gray-100", data: { turbo_frame: "modal" } do %> + <%= lucide_icon("plus", class: "w-5 h-5 text-gray-500") %> +

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

+ <% end %> + <%= form_with url: summary_accounts_path, method: :get, class: "flex items-center gap-4", data: { controller: "auto-submit-form" } do %> + <%= render partial: "shared/period_select", locals: { value: @period.name } %> + <% end %> +
+
+ <%= render partial: "pages/account_percentages_bar", locals: { account_groups: @account_groups[:liabilities].children } %> + <%= render partial: "pages/account_percentages_table", locals: { account_groups: @account_groups[:liabilities].children } %> +
+
diff --git a/app/views/layouts/_sidebar.html.erb b/app/views/layouts/_sidebar.html.erb new file mode 100644 index 00000000..6511ef9b --- /dev/null +++ b/app/views/layouts/_sidebar.html.erb @@ -0,0 +1,88 @@ +
+ <%= link_to root_path do %> + <%= image_tag "logo.svg", alt: "Maybe", class: "h-[22px]" %> + <% end %> +
+ + +
+
+ +
+
+ <%= link_to accounts_path, class: "text-xs uppercase text-gray-500 font-bold tracking-wide" do %> + <%= t(".accounts") %> + <% end %> + <%= link_to new_account_path, class: "block hover:bg-gray-100 p-2 text-sm font-semibold text-gray-900 flex items-center rounded", title: t(".new_account"), data: { turbo_frame: "modal" } do %> + <%= lucide_icon("plus", class: "w-5 h-5 text-gray-500") %> + <% end %> +
+ <%= link_to new_account_path, class: "flex items-center gap-4 px-2 py-3 mb-1 text-gray-500 text-sm font-medium rounded-[10px] hover:bg-gray-100", data: { turbo_frame: "modal" } do %> + <%= lucide_icon("plus", class: "w-5 h-5") %> +

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

+ <% end %> + <% account_groups.each do |group| %> + <%= render "accounts/account_list", group: group %> + <% end %> +
diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 6fa4d937..af2036b1 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -25,73 +25,28 @@
<%= safe_join(flash.map { |type, message| notification(message, type: type) }) %> -
-
-
- <%= link_to root_path do %> - <%= image_tag "logo.svg", alt: "Maybe", class: "h-[22px]" %> - <% end %> -
- - -
-
- -
-
- <%= link_to accounts_path, class: "text-xs uppercase text-gray-500 font-bold tracking-wide" do %> - <%= t(".accounts") %> - <% end %> - <%= link_to new_account_path, class: "block hover:bg-gray-100 p-2 text-sm font-semibold text-gray-900 flex items-center rounded", title: t(".new_account"), data: { turbo_frame: "modal" } do %> - <%= lucide_icon("plus", class: "w-5 h-5 text-gray-500") %> - <% end %> -
- <%= link_to new_account_path, class: "flex items-center gap-4 px-2 py-3 mb-1 text-gray-500 text-sm font-medium rounded-[10px] hover:bg-gray-100", data: { turbo_frame: "modal" } do %> - <%= lucide_icon("plus", class: "w-5 h-5") %> -

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

- <% end %> - <% account_groups.each do |group| %> - <%= render "accounts/account_list", group: group %> - <% end %> -
+
+
+ <% if content_for?(:sidebar) %> + <%= yield :sidebar %> + <% else %> + <%= render "layouts/sidebar" %> + <% end %>
-
+
<%= yield %>
<%= turbo_frame_tag "modal" %> <%= render "shared/custom_confirm_modal" %> <%= render "shared/upgrade_notification" %> + <% if self_hosted? %> +
+

Self-hosted Maybe: <%= Maybe.version.to_release_tag %>

+ <%= link_to settings_hosting_path, class: "flex gap-1 items-center hover:bg-gray-50 rounded-md p-2" do %> + <%= lucide_icon("settings", class: "w-4 h-4 text-gray-500 shrink-0") %> + <% end %> +
+ <% end %> diff --git a/app/views/pages/_account_percentages_table.html.erb b/app/views/pages/_account_percentages_table.html.erb index 68f9409e..f96d7ec1 100644 --- a/app/views/pages/_account_percentages_table.html.erb +++ b/app/views/pages/_account_percentages_table.html.erb @@ -15,6 +15,6 @@
- <%= render partial: "account_group_disclosure", collection: account_groups, as: :accountable_group %> + <%= render partial: "pages/account_group_disclosure", collection: account_groups, as: :accountable_group %>
diff --git a/app/views/pages/changelog.html.erb b/app/views/pages/changelog.html.erb new file mode 100644 index 00000000..ba35a34e --- /dev/null +++ b/app/views/pages/changelog.html.erb @@ -0,0 +1,15 @@ +<% content_for :sidebar do %> + <%= render "settings/nav" %> +<% end %> +
+

What's New

+
+
+

Changelog coming soon...

+
+
+
+ <%= previous_setting("Rules", transactions_rules_path) %> + <%= next_setting("Feedback", feedback_path) %> +
+
diff --git a/app/views/pages/feedback.html.erb b/app/views/pages/feedback.html.erb new file mode 100644 index 00000000..af242487 --- /dev/null +++ b/app/views/pages/feedback.html.erb @@ -0,0 +1,15 @@ +<% content_for :sidebar do %> + <%= render "settings/nav" %> +<% end %> +
+

Feedback

+
+
+

Feedback coming soon...

+
+
+
+ <%= previous_setting("What's New", changelog_path) %> + <%= next_setting("Invite friends", invites_path) %> +
+
diff --git a/app/views/pages/invites.html.erb b/app/views/pages/invites.html.erb new file mode 100644 index 00000000..a838cecc --- /dev/null +++ b/app/views/pages/invites.html.erb @@ -0,0 +1,14 @@ +<% content_for :sidebar do %> + <%= render "settings/nav" %> +<% end %> +
+

Invite friends

+
+
+

Invite friends coming soon...

+
+
+
+ <%= previous_setting("Feedback", feedback_path) %> +
+
diff --git a/app/views/settings/_nav.html.erb b/app/views/settings/_nav.html.erb new file mode 100644 index 00000000..9908a449 --- /dev/null +++ b/app/views/settings/_nav.html.erb @@ -0,0 +1,56 @@ +
+ <%= link_to root_path, class: "flex items-center gap-1 text-gray-900 font-medium text-sm" do %> + <%= lucide_icon "chevron-left", class: "w-5 h-5 text-gray-500" %> + Back + <% end %> + + esc + +
+ diff --git a/app/views/settings/_nav_link_large.html.erb b/app/views/settings/_nav_link_large.html.erb new file mode 100644 index 00000000..3b1b2b50 --- /dev/null +++ b/app/views/settings/_nav_link_large.html.erb @@ -0,0 +1,17 @@ +<%# locals: path, direction, title %> +<%= link_to path, class: "w-full bg-white hover:bg-gray-50 rounded-xl border border-alpha-black-25 shadow-xs p-4 flex items-center justify-between" do %> + <% if direction == 'previous' %> +
+ <%= lucide_icon("arrow-left") %> +
+ <% end %> +
<%= "text-right" if direction == "previous" %>"> + <%= t(".#{direction}") %> + <%= title %> +
+ <% if direction == 'next' %> +
+ <%= lucide_icon("arrow-right") %> +
+ <% end %> +<% end %> diff --git a/app/views/settings/billings/show.html.erb b/app/views/settings/billings/show.html.erb new file mode 100644 index 00000000..76a5756a --- /dev/null +++ b/app/views/settings/billings/show.html.erb @@ -0,0 +1,19 @@ +<% content_for :sidebar do %> + <%= render "settings/nav" %> +<% end %> +
+

Billing

+
+
+

Billing settings coming soon...

+
+
+
+ <%= previous_setting("Security", settings_security_path) %> + <% if self_hosted? %> + <%= next_setting("Self Hosting", settings_hosting_path) %> + <% else %> + <%= next_setting("Accounts", accounts_path) %> + <% end %> +
+
diff --git a/app/views/settings/edit.html.erb b/app/views/settings/edit.html.erb deleted file mode 100644 index 7f4f2aef..00000000 --- a/app/views/settings/edit.html.erb +++ /dev/null @@ -1,17 +0,0 @@ -

Update settings

-<%= form_with model: Current.user, url: settings_path, html: { class: "space-y-4" } do |form| %> - <%= form.fields_for :family_attributes do |family_fields| %> - <%= family_fields.text_field :name, placeholder: "Family name", value: Current.family.name, label: "Family name" %> - <%= family_fields.select :currency, options_for_select(Money::Currency.popular.map { |currency| ["#{currency.iso_code} (#{currency.name})", currency.iso_code] }, selected: Current.family.currency), { label: "Currency" } %> - <% end %> - <%= form.text_field :first_name, placeholder: "First name", value: Current.user.first_name, label: true %> - <%= form.text_field :last_name, placeholder: "Last name", value: Current.user.last_name, label: true %> - <%= form.email_field :email, placeholder: "Email", value: Current.user.email, label: true %> - <%= form.password_field :password, label: true %> - <%= form.password_field :password_confirmation, label: true %> -
- -
-<% end %> diff --git a/app/views/settings/self_hosting/edit.html.erb b/app/views/settings/hostings/show.html.erb similarity index 79% rename from app/views/settings/self_hosting/edit.html.erb rename to app/views/settings/hostings/show.html.erb index d721c9a1..32697741 100644 --- a/app/views/settings/self_hosting/edit.html.erb +++ b/app/views/settings/hostings/show.html.erb @@ -1,11 +1,14 @@ +<% content_for :sidebar do %> + <%= render "settings/nav" %> +<% end %>
-

Edit Self Hosting Settings

+

Self Hosting


- <%= form_with model: Setting.new, url: settings_self_hosting_path, method: :patch, local: true, html: { class: "space-y-4" } do |form| %> + <%= form_with model: Setting.new, url: settings_hosting_path, method: :patch, local: true, html: { class: "space-y-4" } do |form| %>

Render Deploy Hook

You must fill this in so your app can trigger upgrades when Maybe releases upgrades. Learn more about deploy hooks and how they work in the <%= link_to "Render documentation", "https://docs.render.com/docs/deploy-hooks", target: "_blank", rel: "noopener noreferrer", class: "text-blue-500 hover:underline" %>.

- <%= form.text_field :render_deploy_hook, label: "Render Deploy Hook", placeholder: "https://api.render.com/deploy/srv-xyz...", value: Setting.render_deploy_hook %> + <%= form.url_field :render_deploy_hook, label: "Render Deploy Hook", placeholder: "https://api.render.com/deploy/srv-xyz...", value: Setting.render_deploy_hook %>

Auto Upgrades Setting

@@ -37,4 +40,8 @@
<% end %> +
+ <%= previous_setting("Billing", settings_billing_path) %> + <%= next_setting("Accounts", accounts_path) %> +
diff --git a/app/views/settings/notifications/show.html.erb b/app/views/settings/notifications/show.html.erb new file mode 100644 index 00000000..3a217fc6 --- /dev/null +++ b/app/views/settings/notifications/show.html.erb @@ -0,0 +1,15 @@ +<% content_for :sidebar do %> + <%= render "settings/nav" %> +<% end %> +
+

Notifications

+
+
+

Notifications coming soon...

+
+
+
+ <%= previous_setting("Preferences", settings_preferences_path) %> + <%= next_setting("Security", settings_security_path) %> +
+
diff --git a/app/views/settings/preferences/show.html.erb b/app/views/settings/preferences/show.html.erb new file mode 100644 index 00000000..37a1ead2 --- /dev/null +++ b/app/views/settings/preferences/show.html.erb @@ -0,0 +1,15 @@ +<% content_for :sidebar do %> + <%= render "settings/nav" %> +<% end %> +
+

Preferences

+
+
+

Preferences coming soon...

+
+
+
+ <%= previous_setting("Account", settings_profile_path) %> + <%= next_setting("Notifications", settings_notifications_path) %> +
+
diff --git a/app/views/settings/profiles/show.html.erb b/app/views/settings/profiles/show.html.erb new file mode 100644 index 00000000..a6fb7fb9 --- /dev/null +++ b/app/views/settings/profiles/show.html.erb @@ -0,0 +1,25 @@ +<% content_for :sidebar do %> + <%= render "settings/nav" %> +<% end %> +
+

Account

+ <%= form_with model: Current.user, url: settings_profile_path, html: { class: "space-y-4" } do |form| %> + <%= form.fields_for :family_attributes do |family_fields| %> + <%= family_fields.text_field :name, placeholder: "Family name", value: Current.family.name, label: "Family name" %> + <%= family_fields.select :currency, options_for_select(Money::Currency.popular.map { |currency| ["#{currency.iso_code} (#{currency.name})", currency.iso_code] }, selected: Current.family.currency), { label: "Currency" } %> + <% end %> + <%= form.text_field :first_name, placeholder: "First name", value: Current.user.first_name, label: true %> + <%= form.text_field :last_name, placeholder: "Last name", value: Current.user.last_name, label: true %> + <%= form.email_field :email, placeholder: "Email", value: Current.user.email, label: true %> + <%= form.password_field :password, label: true %> + <%= form.password_field :password_confirmation, label: true %> +
+ +
+ <% end %> +
+ <%= next_setting("Preferences", settings_preferences_path) %> +
+
diff --git a/app/views/settings/securities/show.html.erb b/app/views/settings/securities/show.html.erb new file mode 100644 index 00000000..38fb8db5 --- /dev/null +++ b/app/views/settings/securities/show.html.erb @@ -0,0 +1,15 @@ +<% content_for :sidebar do %> + <%= render "settings/nav" %> +<% end %> +
+

Security

+
+
+

Security settings coming soon...

+
+
+
+ <%= previous_setting("Notifications", settings_notifications_path) %> + <%= next_setting("Billing", settings_billing_path) %> +
+
diff --git a/app/views/transactions/categories/index.html.erb b/app/views/transactions/categories/index.html.erb new file mode 100644 index 00000000..3a564eae --- /dev/null +++ b/app/views/transactions/categories/index.html.erb @@ -0,0 +1,15 @@ +<% content_for :sidebar do %> + <%= render "settings/nav" %> +<% end %> +
+

Categories

+
+
+

Transaction categories coming soon...

+
+
+
+ <%= previous_setting("Accounts", accounts_path) %> + <%= next_setting("Merchants", transactions_merchants_path) %> +
+
diff --git a/app/views/transactions/merchants/index.html.erb b/app/views/transactions/merchants/index.html.erb new file mode 100644 index 00000000..cbee888c --- /dev/null +++ b/app/views/transactions/merchants/index.html.erb @@ -0,0 +1,15 @@ +<% content_for :sidebar do %> + <%= render "settings/nav" %> +<% end %> +
+

Merchants

+
+
+

Manage transaction merchants coming soon...

+
+
+
+ <%= previous_setting("Categories", transactions_categories_path) %> + <%= next_setting("Rules", transactions_rules_path) %> +
+
diff --git a/app/views/transactions/rules/index.html.erb b/app/views/transactions/rules/index.html.erb new file mode 100644 index 00000000..70ae6f78 --- /dev/null +++ b/app/views/transactions/rules/index.html.erb @@ -0,0 +1,15 @@ +<% content_for :sidebar do %> + <%= render "settings/nav" %> +<% end %> +
+

Rules

+
+
+

Transaction rules coming soon...

+
+
+
+ <%= previous_setting("Merchants", transactions_merchants_path) %> + <%= next_setting("What's New", changelog_path) %> +
+
diff --git a/config/locales/views/account/en.yml b/config/locales/views/account/en.yml index bf533d7d..1aaf9cf5 100644 --- a/config/locales/views/account/en.yml +++ b/config/locales/views/account/en.yml @@ -16,6 +16,8 @@ en: placeholder: Example account name select_accountable_type: What would you like to add? title: Add an account + summary: + new: New account sync: success: Account sync started update: diff --git a/config/locales/views/layout/en.yml b/config/locales/views/layout/en.yml index 6c149c12..733c39be 100644 --- a/config/locales/views/layout/en.yml +++ b/config/locales/views/layout/en.yml @@ -1,11 +1,6 @@ --- en: layouts: - application: - accounts: Accounts - dashboard: Dashboard - new_account: New account - transactions: Transactions auth: or: or privacy_policy: Privacy Policy @@ -13,3 +8,8 @@ en: sign_up: create an account terms_of_service: Terms of Service your_account: Your account + sidebar: + accounts: Accounts + dashboard: Dashboard + new_account: New account + transactions: Transactions diff --git a/config/locales/views/settings/en.yml b/config/locales/views/settings/en.yml index 2b980511..7ef300ad 100644 --- a/config/locales/views/settings/en.yml +++ b/config/locales/views/settings/en.yml @@ -1,8 +1,25 @@ --- en: settings: - self_hosting: + hostings: update: render_deploy_hook_error: Render deploy hook must be provided to enable auto upgrades success: Settings updated successfully. + nav: + accounts_label: Accounts + billing_label: Billing + categories_label: Categories + feedback_label: Feedback + invite_label: Invite friends + merchants_label: Merchants + notifications_label: Notifications + preferences_label: Preferences + profile_label: Account + rules_label: Rules + security_label: Security + self_hosting_label: Self Hosting + whats_new_label: What's New + nav_link_large: + next: Next + previous: Back diff --git a/config/routes.rb b/config/routes.rb index 18d18f59..5ad51151 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,24 +1,39 @@ Rails.application.routes.draw do mount GoodJob::Engine => "jobs" + get "changelog" => "pages#changelog", as: :changelog + get "feedback" => "pages#feedback", as: :feedback + get "invites" => "pages#invites", as: :invites + resource :registration resource :session resource :password_reset resource :password - resource :settings, only: %i[edit update] do - resource :self_hosting, only: %i[edit update], controller: "settings/self_hosting" + namespace :settings do + resource :profile, only: %i[show update] + resource :preferences, only: %i[show update] + resource :notifications, only: %i[show update] + resource :billing, only: %i[show update] + resource :hosting, only: %i[show update] + resource :security, only: %i[show update] + end + + namespace :transactions do + resources :categories + + # TODO: These are *placeholders* + # Uncomment `only` and add the necessary actions as they are implemented. + resources :rules, only: [ :index ] + resources :merchants, only: [ :index ] end resources :transactions do match "search" => "transactions#search", on: :collection, via: [ :get, :post ], as: :search end - namespace :transactions do - resources :categories - end - resources :accounts, shallow: true do + get :summary, on: :collection post :sync, on: :member resources :valuations end diff --git a/test/application_system_test_case.rb b/test/application_system_test_case.rb index 431da1d5..145d7302 100644 --- a/test/application_system_test_case.rb +++ b/test/application_system_test_case.rb @@ -12,9 +12,10 @@ class ApplicationSystemTestCase < ActionDispatch::SystemTestCase fill_in "Password", with: "password" click_button "Log in" end - assert_text "Dashboard", wait: 5 - find('[data-controller="menu"]').click + end + + def sign_out + find("#user-menu").click click_button "Logout" - assert_text "Sign in to your account" end end diff --git a/test/controllers/settings/billings_controller_test.rb b/test/controllers/settings/billings_controller_test.rb new file mode 100644 index 00000000..e69de29b diff --git a/test/controllers/settings/self_hosting_controller_test.rb b/test/controllers/settings/hostings_controller_test.rb similarity index 62% rename from test/controllers/settings/self_hosting_controller_test.rb rename to test/controllers/settings/hostings_controller_test.rb index 0a899a56..80d8794a 100644 --- a/test/controllers/settings/self_hosting_controller_test.rb +++ b/test/controllers/settings/hostings_controller_test.rb @@ -1,6 +1,6 @@ require "test_helper" -class Settings::SelfHostingControllerTest < ActionDispatch::IntegrationTest +class Settings::HostingsControllerTest < ActionDispatch::IntegrationTest setup do ENV["SELF_HOSTING_ENABLED"] = "true" sign_in users(:family_admin) @@ -9,14 +9,14 @@ class Settings::SelfHostingControllerTest < ActionDispatch::IntegrationTest test "cannot edit when self hosting is disabled" do ENV["SELF_HOSTING_ENABLED"] = "false" - get edit_settings_self_hosting_url + get settings_hosting_url assert :not_found - patch settings_self_hosting_url, params: { setting: { render_deploy_hook: "https://example.com" } } + patch settings_hosting_url, params: { setting: { render_deploy_hook: "https://example.com" } } assert :not_found end test "should get edit when self hosting is enabled" do - get edit_settings_self_hosting_url + get settings_hosting_url assert_response :success end @@ -24,7 +24,7 @@ class Settings::SelfHostingControllerTest < ActionDispatch::IntegrationTest NEW_RENDER_DEPLOY_HOOK = "https://api.render.com/deploy/srv-abc123" assert_nil Setting.render_deploy_hook - patch settings_self_hosting_url, params: { setting: { render_deploy_hook: NEW_RENDER_DEPLOY_HOOK } } + patch settings_hosting_url, params: { setting: { render_deploy_hook: NEW_RENDER_DEPLOY_HOOK } } assert_equal NEW_RENDER_DEPLOY_HOOK, Setting.render_deploy_hook end diff --git a/test/controllers/settings/notifications_controller_test.rb b/test/controllers/settings/notifications_controller_test.rb new file mode 100644 index 00000000..bbd63ffb --- /dev/null +++ b/test/controllers/settings/notifications_controller_test.rb @@ -0,0 +1,11 @@ +require "test_helper" + +class Settings::NotificationsControllerTest < ActionDispatch::IntegrationTest + setup do + sign_in users(:family_admin) + end + test "get" do + get settings_notifications_url + assert_response :success + end +end diff --git a/test/controllers/settings/preferences_controller_test.rb b/test/controllers/settings/preferences_controller_test.rb new file mode 100644 index 00000000..c17b03f6 --- /dev/null +++ b/test/controllers/settings/preferences_controller_test.rb @@ -0,0 +1,11 @@ +require "test_helper" + +class Settings::PreferencesControllerTest < ActionDispatch::IntegrationTest + setup do + sign_in users(:family_admin) + end + test "get" do + get settings_preferences_url + assert_response :success + end +end diff --git a/test/controllers/settings/profiles_controller_test.rb b/test/controllers/settings/profiles_controller_test.rb new file mode 100644 index 00000000..dac21cf0 --- /dev/null +++ b/test/controllers/settings/profiles_controller_test.rb @@ -0,0 +1,11 @@ +require "test_helper" + +class Settings::ProfilesControllerTest < ActionDispatch::IntegrationTest + setup do + sign_in @user = users(:family_admin) + end + test "get" do + get settings_profile_url + assert_response :success + end +end diff --git a/test/controllers/settings/securities_controller_test.rb b/test/controllers/settings/securities_controller_test.rb new file mode 100644 index 00000000..eb5cc481 --- /dev/null +++ b/test/controllers/settings/securities_controller_test.rb @@ -0,0 +1,11 @@ +require "test_helper" + +class Settings::SecuritiesControllerTest < ActionDispatch::IntegrationTest + setup do + sign_in users(:family_admin) + end + test "get" do + get settings_security_url + assert_response :success + end +end diff --git a/test/system/accounts_test.rb b/test/system/accounts_test.rb deleted file mode 100644 index e45636c6..00000000 --- a/test/system/accounts_test.rb +++ /dev/null @@ -1,20 +0,0 @@ -require "application_system_test_case" - -class AccountsTest < ApplicationSystemTestCase - setup do - sign_in @user = users(:family_admin) - end - - test "should create account" do - skip("Disabling this test for now, UI is changing to quickly to do systems testing") - - click_on "New account" - click_on "Credit Card" - within "form" do - fill_in "Name", with: "VISA" - fill_in "Balance", with: "1000" - click_on "Submit" - end - assert_text "$1,000" - end -end diff --git a/test/system/settings_test.rb b/test/system/settings_test.rb new file mode 100644 index 00000000..76565b89 --- /dev/null +++ b/test/system/settings_test.rb @@ -0,0 +1,66 @@ +require "application_system_test_case" + +class SettingsTest < ApplicationSystemTestCase + setup do + sign_in @user = users(:family_admin) + + @settings_links = [ + [ "Account", "Account", settings_profile_path ], + [ "Preferences", "Preferences", settings_preferences_path ], + [ "Notifications", "Notifications", settings_notifications_path ], + [ "Security", "Security", settings_security_path ], + [ "Billing", "Billing", settings_billing_path ], + [ "Accounts", "Accounts", accounts_path ], + [ "Categories", "Categories", transactions_categories_path ], + [ "Merchants", "Merchants", transactions_merchants_path ], + [ "Rules", "Rules", transactions_rules_path ], + [ "What's New", "What's New", changelog_path ], + [ "Feedback", "Feedback", feedback_path ], + [ "Invite friends", "Invite friends", invites_path ] + ] + end + + test "can access settings from sidebar" do + open_settings_from_sidebar + assert_selector "h1", text: "Account" + assert_current_path settings_profile_path + end + + test "all settings views and links are accessible" do + open_settings_from_sidebar + + @settings_links.each_with_index do |(link_text, header_text, path), index| + next_setting_path = @settings_links[index + 1][2] if index < @settings_links.size - 1 + prev_setting_path = @settings_links[index - 1][2] if index > 0 + + find_link(link_text, exact: true).click + + assert_selector "h1", text: header_text + assert_current_path path + assert_link "Next", href: next_setting_path if next_setting_path.present? + assert_link "Back", href: prev_setting_path if prev_setting_path.present? + end + + # Conditional nav items don't show by default + assert_no_text "Self Hosting" + end + + test "can see conditional nav items" do + ENV["SELF_HOSTING_ENABLED"] = "true" + + open_settings_from_sidebar + + click_link "Self Hosting" + assert_selector "h1", text: "Self Hosting" + end + + test "clicking back or hitting escape key takes user back page they opened settings from" do + # TODO: Implement test for back navigation and escape key functionality. + end + + private + def open_settings_from_sidebar + find("#user-menu").click + click_link "Settings" + end +end diff --git a/test/test_helper.rb b/test/test_helper.rb index 3a9e1f50..48703fd4 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -1,3 +1,7 @@ +# Require individual test files to enable these as needed +ENV["SELF_HOSTING_ENABLED"] = "false" +ENV["UPGRADES_ENABLED"] = "false" + ENV["RAILS_ENV"] ||= "test" require_relative "../config/environment" require "rails/test_help"