diff --git a/app/controllers/account/logos_controller.rb b/app/controllers/account/logos_controller.rb
deleted file mode 100644
index dd56e6656..000000000
--- a/app/controllers/account/logos_controller.rb
+++ /dev/null
@@ -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
diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb
index 4dcbbb272..033ed3b8f 100644
--- a/app/controllers/accounts_controller.rb
+++ b/app/controllers/accounts_controller.rb
@@ -23,11 +23,8 @@ class AccountsController < ApplicationController
end
def new
- @account = Account.new(
- accountable: Accountable.from_type(params[:type])&.new,
- currency: Current.family.currency
- )
-
+ @account = Account.new(currency: Current.family.currency)
+ @account.accountable = Accountable.from_type(params[:type])&.new if params[:type].present?
@account.accountable.address = Address.new if @account.accountable.is_a?(Property)
if params[:institution_id]
@@ -36,8 +33,6 @@ class AccountsController < ApplicationController
end
def show
- @series = @account.series(period: @period)
- @trend = @series.trend
end
def edit
@@ -57,6 +52,7 @@ class AccountsController < ApplicationController
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
diff --git a/app/helpers/accounts_helper.rb b/app/helpers/accounts_helper.rb
index 542c80ef9..b65033c59 100644
--- a/app/helpers/accounts_helper.rb
+++ b/app/helpers/accounts_helper.rb
@@ -1,4 +1,14 @@
module AccountsHelper
+ def permitted_accountable_partial(account, name = nil)
+ permitted_names = %w[tooltip header tabs form]
+ folder = account.accountable_type.underscore
+ name ||= account.accountable_type.underscore
+
+ raise "Unpermitted accountable partial: #{name}" unless permitted_names.include?(name)
+
+ "accounts/accountables/#{folder}/#{name}"
+ end
+
def summary_card(title:, &block)
content = capture(&block)
render "accounts/summary_card", title: title, content: content
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 8cbbcf217..19aa187e2 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -9,10 +9,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
@@ -80,8 +76,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
diff --git a/app/javascript/controllers/time_series_chart_controller.js b/app/javascript/controllers/time_series_chart_controller.js
index ce27460f1..7660f3f4a 100644
--- a/app/javascript/controllers/time_series_chart_controller.js
+++ b/app/javascript/controllers/time_series_chart_controller.js
@@ -51,12 +51,17 @@ export default class extends Controller {
_normalizeDataPoints() {
this._normalDataPoints = (this.dataValue.values || []).map((d) => ({
...d,
- date: new Date(d.date),
+ date: this._parseDate(d.date),
value: d.value.amount ? +d.value.amount : +d.value,
currency: d.value.currency,
}));
}
+ _parseDate(dateString) {
+ const [year, month, day] = dateString.split("-").map(Number);
+ return new Date(year, month - 1, day);
+ }
+
_rememberInitialContainerSize() {
this._d3InitialContainerWidth = this._d3Container.node().clientWidth;
this._d3InitialContainerHeight = this._d3Container.node().clientHeight;
diff --git a/app/models/account.rb b/app/models/account.rb
index 61d473d8e..9dd49d607 100644
--- a/app/models/account.rb
+++ b/app/models/account.rb
@@ -27,6 +27,8 @@ class Account < ApplicationRecord
scope :alphabetically, -> { order(:name) }
scope :ungrouped, -> { where(institution_id: nil) }
+ has_one_attached :logo
+
delegated_type :accountable, types: Accountable::TYPES, dependent: :destroy
accepts_nested_attributes_for :accountable
diff --git a/app/models/account/balance/calculator.rb b/app/models/account/balance/calculator.rb
new file mode 100644
index 000000000..68d858530
--- /dev/null
+++ b/app/models/account/balance/calculator.rb
@@ -0,0 +1,57 @@
+class Account::Balance::Calculator
+ def initialize(account, sync_start_date)
+ @account = account
+ @sync_start_date = sync_start_date
+ end
+
+ def calculate(is_partial_sync: false)
+ cached_entries = account.entries.where("date >= ?", sync_start_date).to_a
+ sync_starting_balance = is_partial_sync ? find_start_balance_for_partial_sync : find_start_balance_for_full_sync(cached_entries)
+
+ prior_balance = sync_starting_balance
+
+ (sync_start_date..Date.current).map do |date|
+ current_balance = calculate_balance_for_date(date, entries: cached_entries, prior_balance:)
+
+ prior_balance = current_balance
+
+ build_balance(date, current_balance)
+ end
+ end
+
+ private
+ attr_reader :account, :sync_start_date
+
+ def find_start_balance_for_partial_sync
+ account.balances.find_by(currency: account.currency, date: sync_start_date - 1.day).balance
+ end
+
+ def find_start_balance_for_full_sync(cached_entries)
+ account.balance + net_entry_flows(cached_entries)
+ end
+
+ def calculate_balance_for_date(date, entries:, prior_balance:)
+ valuation = entries.find { |e| e.date == date && e.account_valuation? }
+
+ return valuation.amount if valuation
+
+ entries = entries.select { |e| e.date == date }
+
+ prior_balance - net_entry_flows(entries)
+ end
+
+ def net_entry_flows(entries, target_currency = account.currency)
+ converted_entry_amounts = entries.map { |t| t.amount_money.exchange_to(target_currency, date: t.date) }
+
+ flows = converted_entry_amounts.sum(&:amount)
+
+ account.liability? ? flows * -1 : flows
+ end
+
+ def build_balance(date, balance, currency = nil)
+ account.balances.build \
+ date: date,
+ balance: balance,
+ currency: currency || account.currency
+ end
+end
diff --git a/app/models/account/balance/converter.rb b/app/models/account/balance/converter.rb
new file mode 100644
index 000000000..f5e557493
--- /dev/null
+++ b/app/models/account/balance/converter.rb
@@ -0,0 +1,46 @@
+class Account::Balance::Converter
+ def initialize(account, sync_start_date)
+ @account = account
+ @sync_start_date = sync_start_date
+ end
+
+ def convert(balances)
+ calculate_converted_balances(balances)
+ end
+
+ private
+ attr_reader :account, :sync_start_date
+
+ def calculate_converted_balances(balances)
+ from_currency = account.currency
+ to_currency = account.family.currency
+
+ if ExchangeRate.exchange_rates_provider.nil?
+ account.observe_missing_exchange_rate_provider
+ return []
+ end
+
+ exchange_rates = ExchangeRate.find_rates from: from_currency,
+ to: to_currency,
+ start_date: sync_start_date
+
+ missing_exchange_rates = balances.map(&:date) - exchange_rates.map(&:date)
+
+ if missing_exchange_rates.any?
+ account.observe_missing_exchange_rates(from: from_currency, to: to_currency, dates: missing_exchange_rates)
+ return []
+ end
+
+ balances.map do |balance|
+ exchange_rate = exchange_rates.find { |er| er.date == balance.date }
+ build_balance(balance.date, exchange_rate.rate * balance.balance, to_currency)
+ end
+ end
+
+ def build_balance(date, balance, currency = nil)
+ account.balances.build \
+ date: date,
+ balance: balance,
+ currency: currency || account.currency
+ end
+end
diff --git a/app/models/account/balance/loader.rb b/app/models/account/balance/loader.rb
new file mode 100644
index 000000000..cb6ba0bdc
--- /dev/null
+++ b/app/models/account/balance/loader.rb
@@ -0,0 +1,37 @@
+class Account::Balance::Loader
+ def initialize(account)
+ @account = account
+ end
+
+ def load(balances, start_date)
+ Account::Balance.transaction do
+ upsert_balances!(balances)
+ purge_stale_balances!(start_date)
+
+ account.reload
+
+ update_account_balance!(balances)
+ end
+ end
+
+ private
+ attr_reader :account
+
+ def update_account_balance!(balances)
+ last_balance = balances.select { |db| db.currency == account.currency }.last&.balance
+ account.update! balance: last_balance if last_balance.present?
+ end
+
+ def upsert_balances!(balances)
+ current_time = Time.now
+ balances_to_upsert = balances.map do |balance|
+ balance.attributes.slice("date", "balance", "currency").merge("updated_at" => current_time)
+ end
+
+ account.balances.upsert_all(balances_to_upsert, unique_by: %i[account_id date currency])
+ end
+
+ def purge_stale_balances!(start_date)
+ account.balances.delete_by("date < ?", start_date)
+ end
+end
diff --git a/app/models/account/balance/syncer.rb b/app/models/account/balance/syncer.rb
index a937955d9..d0e4546ec 100644
--- a/app/models/account/balance/syncer.rb
+++ b/app/models/account/balance/syncer.rb
@@ -1,133 +1,51 @@
class Account::Balance::Syncer
def initialize(account, start_date: nil)
@account = account
+ @provided_start_date = start_date
@sync_start_date = calculate_sync_start_date(start_date)
+ @loader = Account::Balance::Loader.new(account)
+ @converter = Account::Balance::Converter.new(account, sync_start_date)
+ @calculator = Account::Balance::Calculator.new(account, sync_start_date)
end
def run
- daily_balances = calculate_daily_balances
- daily_balances += calculate_converted_balances(daily_balances) if account.currency != account.family.currency
+ daily_balances = calculator.calculate(is_partial_sync: is_partial_sync?)
+ daily_balances += converter.convert(daily_balances) if account.currency != account.family.currency
- Account::Balance.transaction do
- upsert_balances!(daily_balances)
- purge_stale_balances!
-
- if daily_balances.any?
- account.reload
- last_balance = daily_balances.select { |db| db.currency == account.currency }.last&.balance
- account.update! balance: last_balance
- end
- end
+ loader.load(daily_balances, account_start_date)
rescue Money::ConversionError => e
account.observe_missing_exchange_rates(from: e.from_currency, to: e.to_currency, dates: [ e.date ])
end
private
- attr_reader :sync_start_date, :account
-
- def upsert_balances!(balances)
- current_time = Time.now
- balances_to_upsert = balances.map do |balance|
- balance.attributes.slice("date", "balance", "currency").merge("updated_at" => current_time)
- end
-
- account.balances.upsert_all(balances_to_upsert, unique_by: %i[account_id date currency])
- end
-
- def purge_stale_balances!
- account.balances.delete_by("date < ?", account_start_date)
- end
-
- def calculate_balance_for_date(date, entries:, prior_balance:)
- valuation = entries.find { |e| e.date == date && e.account_valuation? }
-
- return valuation.amount if valuation
- return derived_sync_start_balance(entries) unless prior_balance
-
- entries = entries.select { |e| e.date == date }
-
- prior_balance - net_entry_flows(entries)
- end
-
- def calculate_daily_balances
- entries = account.entries.where("date >= ?", sync_start_date).to_a
- prior_balance = find_prior_balance
-
- (sync_start_date..Date.current).map do |date|
- current_balance = calculate_balance_for_date(date, entries:, prior_balance:)
-
- prior_balance = current_balance
-
- build_balance(date, current_balance)
- end
- end
-
- def calculate_converted_balances(balances)
- from_currency = account.currency
- to_currency = account.family.currency
-
- if ExchangeRate.exchange_rates_provider.nil?
- account.observe_missing_exchange_rate_provider
- return []
- end
-
- exchange_rates = ExchangeRate.find_rates from: from_currency,
- to: to_currency,
- start_date: sync_start_date
-
- missing_exchange_rates = balances.map(&:date) - exchange_rates.map(&:date)
-
- if missing_exchange_rates.any?
- account.observe_missing_exchange_rates(from: from_currency, to: to_currency, dates: missing_exchange_rates)
- return []
- end
-
- balances.map do |balance|
- exchange_rate = exchange_rates.find { |er| er.date == balance.date }
- build_balance(balance.date, exchange_rate.rate * balance.balance, to_currency)
- end
- end
-
- def build_balance(date, balance, currency = nil)
- account.balances.build \
- date: date,
- balance: balance,
- currency: currency || account.currency
- end
-
- def derived_sync_start_balance(entries)
- transactions_and_trades = entries.reject { |e| e.account_valuation? }.select { |e| e.date > sync_start_date }
-
- account.balance + net_entry_flows(transactions_and_trades)
- end
-
- def find_prior_balance
- account.balances.where(currency: account.currency).where("date < ?", sync_start_date).order(date: :desc).first&.balance
- end
-
- def net_entry_flows(entries, target_currency = account.currency)
- converted_entry_amounts = entries.map { |t| t.amount_money.exchange_to(target_currency, date: t.date) }
-
- flows = converted_entry_amounts.sum(&:amount)
-
- account.liability? ? flows * -1 : flows
- end
+ attr_reader :sync_start_date, :provided_start_date, :account, :loader, :converter, :calculator
def account_start_date
@account_start_date ||= begin
- oldest_entry_date = account.entries.chronological.first.try(:date)
+ oldest_entry = account.entries.chronological.first
- return Date.current unless oldest_entry_date
+ return Date.current unless oldest_entry.present?
- oldest_entry_is_valuation = account.entries.account_valuations.where(date: oldest_entry_date).exists?
-
- oldest_entry_date -= 1 unless oldest_entry_is_valuation
- oldest_entry_date
+ if oldest_entry.account_valuation?
+ oldest_entry.date
+ else
+ oldest_entry.date - 1.day
+ end
end
end
def calculate_sync_start_date(provided_start_date)
- [ provided_start_date, account_start_date ].compact.max
+ return provided_start_date if provided_start_date.present? && prior_balance_available?(provided_start_date)
+
+ account_start_date
+ end
+
+ def prior_balance_available?(date)
+ account.balances.find_by(currency: account.currency, date: date - 1.day).present?
+ end
+
+ def is_partial_sync?
+ sync_start_date == provided_start_date && sync_start_date < Date.current
end
end
diff --git a/app/models/credit_card.rb b/app/models/credit_card.rb
index 5c8bb0ae9..bdde91c80 100644
--- a/app/models/credit_card.rb
+++ b/app/models/credit_card.rb
@@ -12,4 +12,8 @@ class CreditCard < ApplicationRecord
def annual_fee_money
annual_fee ? Money.new(annual_fee, account.currency) : nil
end
+
+ def color
+ "#F13636"
+ end
end
diff --git a/app/models/crypto.rb b/app/models/crypto.rb
index 5aec6157c..e4f81ae43 100644
--- a/app/models/crypto.rb
+++ b/app/models/crypto.rb
@@ -1,3 +1,7 @@
class Crypto < ApplicationRecord
include Accountable
+
+ def color
+ "#737373"
+ end
end
diff --git a/app/models/depository.rb b/app/models/depository.rb
index 8ae1924d2..90abe0872 100644
--- a/app/models/depository.rb
+++ b/app/models/depository.rb
@@ -1,3 +1,7 @@
class Depository < ApplicationRecord
include Accountable
+
+ def color
+ "#875BF7"
+ end
end
diff --git a/app/models/investment.rb b/app/models/investment.rb
index d5c583fe5..1912899fc 100644
--- a/app/models/investment.rb
+++ b/app/models/investment.rb
@@ -46,4 +46,8 @@ class Investment < ApplicationRecord
rescue Money::ConversionError
TimeSeries.new([])
end
+
+ def color
+ "#1570EF"
+ end
end
diff --git a/app/models/loan.rb b/app/models/loan.rb
index be8e8c2b1..5051b69b8 100644
--- a/app/models/loan.rb
+++ b/app/models/loan.rb
@@ -16,4 +16,8 @@ class Loan < ApplicationRecord
Money.new(payment.round, account.currency)
end
+
+ def color
+ "#D444F1"
+ end
end
diff --git a/app/models/other_asset.rb b/app/models/other_asset.rb
index d9434f6b2..90ca9e323 100644
--- a/app/models/other_asset.rb
+++ b/app/models/other_asset.rb
@@ -1,3 +1,7 @@
class OtherAsset < ApplicationRecord
include Accountable
+
+ def color
+ "#12B76A"
+ end
end
diff --git a/app/models/other_liability.rb b/app/models/other_liability.rb
index 83be97f54..04a3737bb 100644
--- a/app/models/other_liability.rb
+++ b/app/models/other_liability.rb
@@ -1,3 +1,7 @@
class OtherLiability < ApplicationRecord
include Accountable
+
+ def color
+ "#737373"
+ end
end
diff --git a/app/models/property.rb b/app/models/property.rb
index a23519edd..304c4d785 100644
--- a/app/models/property.rb
+++ b/app/models/property.rb
@@ -19,6 +19,10 @@ class Property < ApplicationRecord
TimeSeries::Trend.new(current: account.balance_money, previous: first_valuation_amount)
end
+ def color
+ "#06AED4"
+ end
+
private
def first_valuation_amount
account.entries.account_valuations.order(:date).first&.amount_money || account.balance_money
diff --git a/app/models/vehicle.rb b/app/models/vehicle.rb
index 2ab34d7bc..741070c2e 100644
--- a/app/models/vehicle.rb
+++ b/app/models/vehicle.rb
@@ -15,6 +15,10 @@ class Vehicle < ApplicationRecord
TimeSeries::Trend.new(current: account.balance_money, previous: first_valuation_amount)
end
+ def color
+ "#F23E94"
+ end
+
private
def first_valuation_amount
account.entries.account_valuations.order(:date).first&.amount_money || account.balance_money
diff --git a/app/views/account/logos/show.svg.erb b/app/views/account/logos/show.svg.erb
deleted file mode 100644
index 19186e264..000000000
--- a/app/views/account/logos/show.svg.erb
+++ /dev/null
@@ -1,21 +0,0 @@
-
-
-
-
- <%= @account.name[0].upcase %>
-
-
-
diff --git a/app/views/accounts/_account_list.html.erb b/app/views/accounts/_account_list.html.erb
index b42df1520..46e8004be 100644
--- a/app/views/accounts/_account_list.html.erb
+++ b/app/views/accounts/_account_list.html.erb
@@ -30,7 +30,7 @@
<% group.children.sort_by(&:name).each do |account_value_node| %>
<% account = account_value_node.original %>
<%= link_to account_path(account), class: "flex items-center w-full gap-3 px-3 py-2 mb-1 hover:bg-gray-100 rounded-[10px]" do %>
- <%= image_tag account_logo_url(account), class: "w-6 h-6" %>
+ <%= render "accounts/logo", account: account, size: "sm" %>
<%= account_value_node.name %>
<% if account.subtype %>
@@ -63,7 +63,7 @@
<% end %>
<%= link_to new_account_path(step: "method", type: type.name.demodulize), class: "flex items-center min-h-10 gap-4 px-3 py-2 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") %>
-
New <%= type.model_name.human.downcase %>
+ <%= t(".new_account", type: type.model_name.human.downcase) %>
<% end %>
<% end %>
diff --git a/app/views/accounts/_account_type.html.erb b/app/views/accounts/_account_type.html.erb
index d8b400c7c..d4b695271 100644
--- a/app/views/accounts/_account_type.html.erb
+++ b/app/views/accounts/_account_type.html.erb
@@ -1,5 +1,4 @@
<%= link_to new_account_path(
- step: "method",
type: type.class.name.demodulize,
institution_id: params[:institution_id]
),
diff --git a/app/views/accounts/_empty.html.erb b/app/views/accounts/_empty.html.erb
index e938c863d..68c643162 100644
--- a/app/views/accounts/_empty.html.erb
+++ b/app/views/accounts/_empty.html.erb
@@ -3,7 +3,7 @@
<%= tag.p t(".no_accounts"), class: "text-gray-900 mb-1 font-medium" %>
<%= tag.p t(".empty_message"), class: "text-gray-500 mb-4" %>
- <%= link_to new_account_path, class: "w-fit flex text-white text-sm font-medium items-center gap-1 bg-gray-900 rounded-lg p-2 pr-3", data: { turbo_frame: "modal" } do %>
+ <%= link_to new_account_path(step: "method"), class: "w-fit flex text-white text-sm font-medium items-center gap-1 bg-gray-900 rounded-lg p-2 pr-3", data: { turbo_frame: "modal" } do %>
<%= lucide_icon("plus", class: "w-5 h-5") %>
<%= t(".new_account") %>
<% end %>
diff --git a/app/views/accounts/_entry_method.html.erb b/app/views/accounts/_entry_method.html.erb
index ea73af732..1e9b69712 100644
--- a/app/views/accounts/_entry_method.html.erb
+++ b/app/views/accounts/_entry_method.html.erb
@@ -1,12 +1,14 @@
-<% if local_assigns[:disabled] && disabled %>
-
+<%# locals: (text:, icon:, disabled: false) %>
+
+<% if disabled %>
+
<%= lucide_icon(icon, class: "text-gray-500 w-5 h-5") %>
<%= text %>
<% else %>
- <%= link_to new_account_path(type: type.class.name.demodulize, institution_id: params[:institution_id]), class: "flex items-center gap-4 w-full text-center focus:outline-none focus:bg-gray-50 border border-transparent focus:border focus:border-gray-200 px-2 hover:bg-gray-50 rounded-lg p-2" do %>
+ <%= link_to new_account_path(institution_id: params[:institution_id]), class: "flex items-center gap-4 w-full text-center focus:outline-none focus:bg-gray-50 border border-transparent focus:border focus:border-gray-200 px-2 hover:bg-gray-50 rounded-lg p-2" do %>
<%= lucide_icon(icon, class: "text-gray-500 w-5 h-5") %>
diff --git a/app/views/accounts/_form.html.erb b/app/views/accounts/_form.html.erb
index 4fa0cce11..e85448e01 100644
--- a/app/views/accounts/_form.html.erb
+++ b/app/views/accounts/_form.html.erb
@@ -2,8 +2,8 @@
<%= styled_form_with model: account, url: url, scope: :account, class: "flex flex-col gap-4 justify-between grow", data: { turbo: false } do |f| %>
- <%= f.hidden_field :accountable_type %>
- <%= f.text_field :name, placeholder: t(".name_placeholder"), required: "required", label: t(".name_label"), autofocus: true %>
+ <%= f.select :accountable_type, Accountable::TYPES.map { |type| [type.titleize, type] }, { label: t(".accountable_type"), prompt: t(".type_prompt") }, required: true, autofocus: true %>
+ <%= f.text_field :name, placeholder: t(".name_placeholder"), required: "required", label: t(".name_label") %>
<% if account.new_record? %>
<%= f.hidden_field :institution_id %>
@@ -13,15 +13,10 @@
<%= f.money_field :balance, label: t(".balance"), required: true, default_currency: Current.family.currency %>
- <% if account.new_record? %>
-
-
<%= f.date_field :start_date, label: t(".start_date"), max: Date.yesterday, min: Account::Entry.min_supported_date %>
-
<%= f.money_field :start_balance, label: t(".start_balance"), placeholder: 90, hide_currency: true, default_currency: Current.family.currency %>
-
+ <% if account.accountable %>
+ <%= render permitted_accountable_partial(account, "form"), f: f %>
<% end %>
-
- <%= render "accounts/accountables/#{permitted_accountable_partial(account.accountable_type)}", f: f %>
- <%= f.submit "#{account.new_record? ? "Add" : "Update"} #{account.accountable.model_name.human.downcase}" %>
+ <%= f.submit %>
<% end %>
diff --git a/app/views/accounts/_header.html.erb b/app/views/accounts/_header.html.erb
index f8c8eb3e5..348fc9b98 100644
--- a/app/views/accounts/_header.html.erb
+++ b/app/views/accounts/_header.html.erb
@@ -13,7 +13,7 @@
<% end %>
- <%= link_to new_account_path, class: "rounded-lg bg-gray-900 text-white flex items-center gap-1 justify-center hover:bg-gray-700 px-3 py-2", data: { turbo_frame: :modal } do %>
+ <%= link_to new_account_path(step: "method"), class: "rounded-lg bg-gray-900 text-white flex items-center gap-1 justify-center hover:bg-gray-700 px-3 py-2", data: { turbo_frame: :modal } do %>
<%= lucide_icon("plus", class: "w-5 h-5") %>
<%= t(".new") %>
<% end %>
diff --git a/app/views/accounts/_logo.html.erb b/app/views/accounts/_logo.html.erb
new file mode 100644
index 000000000..9036ad184
--- /dev/null
+++ b/app/views/accounts/_logo.html.erb
@@ -0,0 +1,14 @@
+<%# locals: (account:, size: "md") %>
+
+<% size_classes = {
+ "sm" => "w-6 h-6",
+ "md" => "w-9 h-9",
+ "lg" => "w-10 h-10",
+ "full" => "w-full h-full"
+} %>
+
+<% if account.logo.attached? %>
+ <%= image_tag account.logo, class: size_classes[size] %>
+<% else %>
+ <%= circle_logo(account.name, hex: account.accountable.color, size: size) %>
+<% end %>
diff --git a/app/views/accounts/_menu.html.erb b/app/views/accounts/_menu.html.erb
new file mode 100644
index 000000000..8ebb53f12
--- /dev/null
+++ b/app/views/accounts/_menu.html.erb
@@ -0,0 +1,33 @@
+<%# locals: (account:) %>
+
+<%= contextual_menu do %>
+
+ <%= link_to edit_account_path(account),
+ data: { turbo_frame: :modal },
+ class: "block w-full py-2 px-3 space-x-2 text-gray-900 hover:bg-gray-50 flex items-center rounded-lg" do %>
+ <%= lucide_icon "pencil-line", class: "w-5 h-5 text-gray-500" %>
+
+ <%= t(".edit") %>
+ <% end %>
+
+ <%= link_to new_import_path,
+ class: "block w-full py-2 px-3 space-x-2 text-gray-900 hover:bg-gray-50 flex items-center rounded-lg" do %>
+ <%= lucide_icon "download", class: "w-5 h-5 text-gray-500" %>
+
+ <%= t(".import") %>
+ <% end %>
+
+ <%= button_to account_path(account),
+ method: :delete,
+ class: "block w-full py-2 px-3 space-x-2 text-red-600 hover:bg-red-50 flex items-center rounded-lg",
+ data: {
+ turbo_confirm: {
+ title: t(".confirm_title"),
+ body: t(".confirm_body_html"),
+ accept: t(".confirm_accept", name: account.name)
+ }
+ } do %>
+ <%= lucide_icon("trash-2", class: "w-5 h-5 mr-2") %> Delete account
+ <% end %>
+
+<% end %>
diff --git a/app/views/accounts/_new_account_setup_bar.html.erb b/app/views/accounts/_new_account_setup_bar.html.erb
new file mode 100644
index 000000000..16b6e430c
--- /dev/null
+++ b/app/views/accounts/_new_account_setup_bar.html.erb
@@ -0,0 +1,8 @@
+
+
Setup your new account
+
+
+ <%= link_to "Track balances only", new_account_valuation_path(@account), class: "btn btn--ghost", data: { turbo_frame: dom_id(@account.entries.account_valuations.new) } %>
+ <%= link_to "Add your first transaction", new_transaction_path(account_id: @account.id), class: "btn btn--primary", data: { turbo_frame: :modal } %>
+
+
diff --git a/app/views/accounts/_sync_all_button.html.erb b/app/views/accounts/_sync_all_button.html.erb
index 0cf16bbd0..8300df194 100644
--- a/app/views/accounts/_sync_all_button.html.erb
+++ b/app/views/accounts/_sync_all_button.html.erb
@@ -1,4 +1,4 @@
-<%= button_to sync_all_accounts_path, class: "btn btn--outline flex items-center gap-2", title: "Sync All" do %>
+<%= button_to sync_all_accounts_path, class: "btn btn--outline flex items-center gap-2", title: t(".sync") do %>
<%= lucide_icon "refresh-cw", class: "w-5 h-5" %>
- <%= t("accounts.sync_all.button_text") %>
+ <%= t(".sync") %>
<% end %>
diff --git a/app/views/accounts/accountables/_default_header.html.erb b/app/views/accounts/accountables/_default_header.html.erb
new file mode 100644
index 000000000..5feae417e
--- /dev/null
+++ b/app/views/accounts/accountables/_default_header.html.erb
@@ -0,0 +1,7 @@
+
+ <%= render "accounts/logo", account: account %>
+
+
+
<%= account.name %>
+
+
diff --git a/app/views/accounts/accountables/_default_tabs.html.erb b/app/views/accounts/accountables/_default_tabs.html.erb
new file mode 100644
index 000000000..0a2f355a1
--- /dev/null
+++ b/app/views/accounts/accountables/_default_tabs.html.erb
@@ -0,0 +1,7 @@
+<%# locals: (account:) %>
+
+<% if account.transactions.any? %>
+ <%= render "accounts/accountables/transactions", account: account %>
+<% else %>
+ <%= render "accounts/accountables/valuations", account: account %>
+<% end %>
diff --git a/app/views/accounts/accountables/_tab.html.erb b/app/views/accounts/accountables/_tab.html.erb
new file mode 100644
index 000000000..4ddd0e6ef
--- /dev/null
+++ b/app/views/accounts/accountables/_tab.html.erb
@@ -0,0 +1,8 @@
+<%# locals: (account:, key:, is_selected:) %>
+
+<%= link_to key.titleize,
+ account_path(account, tab: key),
+ class: [
+ "px-2 py-1.5 rounded-md border border-transparent",
+ "bg-white shadow-xs border-alpha-black-50": is_selected
+ ] %>
diff --git a/app/views/accounts/accountables/_transactions.html.erb b/app/views/accounts/accountables/_transactions.html.erb
new file mode 100644
index 000000000..d69433037
--- /dev/null
+++ b/app/views/accounts/accountables/_transactions.html.erb
@@ -0,0 +1,5 @@
+<%# locals: (account:) %>
+
+<%= turbo_frame_tag dom_id(account, :transactions), src: account_transactions_path(account) do %>
+ <%= render "account/entries/loading" %>
+<% end %>
diff --git a/app/views/accounts/accountables/_valuations.html.erb b/app/views/accounts/accountables/_valuations.html.erb
new file mode 100644
index 000000000..b4bf1fc33
--- /dev/null
+++ b/app/views/accounts/accountables/_valuations.html.erb
@@ -0,0 +1,5 @@
+<%# locals: (account:) %>
+
+<%= turbo_frame_tag dom_id(account, :valuations), src: account_valuations_path(account) do %>
+ <%= render "account/entries/loading" %>
+<% end %>
diff --git a/app/views/accounts/accountables/_credit_card.html.erb b/app/views/accounts/accountables/credit_card/_form.html.erb
similarity index 100%
rename from app/views/accounts/accountables/_credit_card.html.erb
rename to app/views/accounts/accountables/credit_card/_form.html.erb
diff --git a/app/views/accounts/accountables/credit_card/_header.html.erb b/app/views/accounts/accountables/credit_card/_header.html.erb
new file mode 100644
index 000000000..518891358
--- /dev/null
+++ b/app/views/accounts/accountables/credit_card/_header.html.erb
@@ -0,0 +1 @@
+<%= render "accounts/accountables/default_header", account: account %>
diff --git a/app/views/accounts/accountables/credit_card/_overview.html.erb b/app/views/accounts/accountables/credit_card/_overview.html.erb
index 3121528b5..668326b5a 100644
--- a/app/views/accounts/accountables/credit_card/_overview.html.erb
+++ b/app/views/accounts/accountables/credit_card/_overview.html.erb
@@ -25,3 +25,7 @@
<%= format_money(account.credit_card.annual_fee_money || Money.new(0, account.currency)) %>
<% end %>
+
+
+ <%= link_to "Edit account details", edit_account_path(account), class: "btn btn--ghost", data: { turbo_frame: :modal } %>
+
\ No newline at end of file
diff --git a/app/views/accounts/accountables/credit_card/_tabs.html.erb b/app/views/accounts/accountables/credit_card/_tabs.html.erb
new file mode 100644
index 000000000..781cc6d0e
--- /dev/null
+++ b/app/views/accounts/accountables/credit_card/_tabs.html.erb
@@ -0,0 +1,15 @@
+<%# locals: (account:, selected_tab:) %>
+
+
+ <%= render "accounts/accountables/tab", account: account, key: "overview", is_selected: selected_tab.in?([nil, "overview"]) %>
+ <%= render "accounts/accountables/tab", account: account, key: "transactions", is_selected: selected_tab == "transactions" %>
+
+
+
+ <% case selected_tab %>
+ <% when nil, "overview" %>
+ <%= render "accounts/accountables/credit_card/overview", account: account %>
+ <% when "transactions" %>
+ <%= render "accounts/accountables/transactions", account: account %>
+ <% end %>
+
diff --git a/app/views/accounts/accountables/_crypto.html.erb b/app/views/accounts/accountables/crypto/_form.html.erb
similarity index 100%
rename from app/views/accounts/accountables/_crypto.html.erb
rename to app/views/accounts/accountables/crypto/_form.html.erb
diff --git a/app/views/accounts/accountables/crypto/_header.html.erb b/app/views/accounts/accountables/crypto/_header.html.erb
new file mode 100644
index 000000000..518891358
--- /dev/null
+++ b/app/views/accounts/accountables/crypto/_header.html.erb
@@ -0,0 +1 @@
+<%= render "accounts/accountables/default_header", account: account %>
diff --git a/app/views/accounts/accountables/crypto/_tabs.html.erb b/app/views/accounts/accountables/crypto/_tabs.html.erb
new file mode 100644
index 000000000..381f72739
--- /dev/null
+++ b/app/views/accounts/accountables/crypto/_tabs.html.erb
@@ -0,0 +1 @@
+<%= render "accounts/accountables/default_tabs", account: account %>
diff --git a/app/views/accounts/accountables/_depository.html.erb b/app/views/accounts/accountables/depository/_form.html.erb
similarity index 100%
rename from app/views/accounts/accountables/_depository.html.erb
rename to app/views/accounts/accountables/depository/_form.html.erb
diff --git a/app/views/accounts/accountables/depository/_header.html.erb b/app/views/accounts/accountables/depository/_header.html.erb
new file mode 100644
index 000000000..518891358
--- /dev/null
+++ b/app/views/accounts/accountables/depository/_header.html.erb
@@ -0,0 +1 @@
+<%= render "accounts/accountables/default_header", account: account %>
diff --git a/app/views/accounts/accountables/depository/_tabs.html.erb b/app/views/accounts/accountables/depository/_tabs.html.erb
new file mode 100644
index 000000000..381f72739
--- /dev/null
+++ b/app/views/accounts/accountables/depository/_tabs.html.erb
@@ -0,0 +1 @@
+<%= render "accounts/accountables/default_tabs", account: account %>
diff --git a/app/views/accounts/accountables/_investment.html.erb b/app/views/accounts/accountables/investment/_form.html.erb
similarity index 100%
rename from app/views/accounts/accountables/_investment.html.erb
rename to app/views/accounts/accountables/investment/_form.html.erb
diff --git a/app/views/accounts/accountables/investment/_header.html.erb b/app/views/accounts/accountables/investment/_header.html.erb
new file mode 100644
index 000000000..518891358
--- /dev/null
+++ b/app/views/accounts/accountables/investment/_header.html.erb
@@ -0,0 +1 @@
+<%= render "accounts/accountables/default_header", account: account %>
diff --git a/app/views/accounts/accountables/investment/_tabs.html.erb b/app/views/accounts/accountables/investment/_tabs.html.erb
new file mode 100644
index 000000000..8da4905d0
--- /dev/null
+++ b/app/views/accounts/accountables/investment/_tabs.html.erb
@@ -0,0 +1,28 @@
+<%# locals: (account:, selected_tab:) %>
+
+<% if account.entries.account_trades.any? || account.entries.account_transactions.any? %>
+
+ <%= render "accounts/accountables/tab", account: account, key: "holdings", is_selected: selected_tab.in?([nil, "holdings"]) %>
+ <%= render "accounts/accountables/tab", account: account, key: "cash", is_selected: selected_tab == "cash" %>
+ <%= render "accounts/accountables/tab", account: account, key: "transactions", is_selected: selected_tab == "transactions" %>
+
+
+
+ <% case selected_tab %>
+ <% when nil, "holdings" %>
+ <%= turbo_frame_tag dom_id(account, :holdings), src: account_holdings_path(account) do %>
+ <%= render "account/entries/loading" %>
+ <% end %>
+ <% when "cash" %>
+ <%= turbo_frame_tag dom_id(account, :cash), src: account_cashes_path(account) do %>
+ <%= render "account/entries/loading" %>
+ <% end %>
+ <% when "transactions" %>
+ <%= turbo_frame_tag dom_id(account, :trades), src: account_trades_path(account) do %>
+ <%= render "account/entries/loading" %>
+ <% end %>
+ <% end %>
+
+<% else %>
+ <%= render "accounts/accountables/valuations", account: account %>
+<% end %>
diff --git a/app/views/accounts/_tooltip.html.erb b/app/views/accounts/accountables/investment/_tooltip.html.erb
similarity index 99%
rename from app/views/accounts/_tooltip.html.erb
rename to app/views/accounts/accountables/investment/_tooltip.html.erb
index 952d2ad18..83432ebc7 100644
--- a/app/views/accounts/_tooltip.html.erb
+++ b/app/views/accounts/accountables/investment/_tooltip.html.erb
@@ -1,4 +1,5 @@
<%# locals: (account:) -%>
+
<%= lucide_icon("info", class: "w-4 h-4 shrink-0 text-gray-500") %>
diff --git a/app/views/accounts/accountables/_loan.html.erb b/app/views/accounts/accountables/loan/_form.html.erb
similarity index 100%
rename from app/views/accounts/accountables/_loan.html.erb
rename to app/views/accounts/accountables/loan/_form.html.erb
diff --git a/app/views/accounts/accountables/loan/_header.html.erb b/app/views/accounts/accountables/loan/_header.html.erb
new file mode 100644
index 000000000..518891358
--- /dev/null
+++ b/app/views/accounts/accountables/loan/_header.html.erb
@@ -0,0 +1 @@
+<%= render "accounts/accountables/default_header", account: account %>
diff --git a/app/views/accounts/accountables/loan/_overview.html.erb b/app/views/accounts/accountables/loan/_overview.html.erb
index ac32f4136..cdd8c1318 100644
--- a/app/views/accounts/accountables/loan/_overview.html.erb
+++ b/app/views/accounts/accountables/loan/_overview.html.erb
@@ -43,3 +43,7 @@
<%= account.loan.rate_type&.titleize || t(".unknown") %>
<% end %>
+
+
+ <%= link_to "Edit account details", edit_account_path(account), class: "btn btn--ghost", data: { turbo_frame: :modal } %>
+
\ No newline at end of file
diff --git a/app/views/accounts/accountables/loan/_tabs.html.erb b/app/views/accounts/accountables/loan/_tabs.html.erb
new file mode 100644
index 000000000..3ed1efcfd
--- /dev/null
+++ b/app/views/accounts/accountables/loan/_tabs.html.erb
@@ -0,0 +1,15 @@
+<%# locals: (account:, selected_tab:) %>
+
+
+ <%= render "accounts/accountables/tab", account: account, key: "overview", is_selected: selected_tab.in?([nil, "overview"]) %>
+ <%= render "accounts/accountables/tab", account: account, key: "value", is_selected: selected_tab == "value" %>
+
+
+
+ <% case selected_tab %>
+ <% when nil, "overview" %>
+ <%= render "accounts/accountables/loan/overview", account: account %>
+ <% when "value" %>
+ <%= render "accounts/accountables/valuations", account: account %>
+ <% end %>
+
diff --git a/app/views/accounts/accountables/_other_asset.html.erb b/app/views/accounts/accountables/other_asset/_form.html.erb
similarity index 100%
rename from app/views/accounts/accountables/_other_asset.html.erb
rename to app/views/accounts/accountables/other_asset/_form.html.erb
diff --git a/app/views/accounts/accountables/other_asset/_header.html.erb b/app/views/accounts/accountables/other_asset/_header.html.erb
new file mode 100644
index 000000000..518891358
--- /dev/null
+++ b/app/views/accounts/accountables/other_asset/_header.html.erb
@@ -0,0 +1 @@
+<%= render "accounts/accountables/default_header", account: account %>
diff --git a/app/views/accounts/accountables/other_asset/_tabs.html.erb b/app/views/accounts/accountables/other_asset/_tabs.html.erb
new file mode 100644
index 000000000..480136bfd
--- /dev/null
+++ b/app/views/accounts/accountables/other_asset/_tabs.html.erb
@@ -0,0 +1 @@
+<%= render "accounts/accountables/valuations", account: account %>
diff --git a/app/views/accounts/accountables/_other_liability.html.erb b/app/views/accounts/accountables/other_liability/_form.html.erb
similarity index 100%
rename from app/views/accounts/accountables/_other_liability.html.erb
rename to app/views/accounts/accountables/other_liability/_form.html.erb
diff --git a/app/views/accounts/accountables/other_liability/_header.html.erb b/app/views/accounts/accountables/other_liability/_header.html.erb
new file mode 100644
index 000000000..518891358
--- /dev/null
+++ b/app/views/accounts/accountables/other_liability/_header.html.erb
@@ -0,0 +1 @@
+<%= render "accounts/accountables/default_header", account: account %>
diff --git a/app/views/accounts/accountables/other_liability/_tabs.html.erb b/app/views/accounts/accountables/other_liability/_tabs.html.erb
new file mode 100644
index 000000000..480136bfd
--- /dev/null
+++ b/app/views/accounts/accountables/other_liability/_tabs.html.erb
@@ -0,0 +1 @@
+<%= render "accounts/accountables/valuations", account: account %>
diff --git a/app/views/accounts/accountables/_property.html.erb b/app/views/accounts/accountables/property/_form.html.erb
similarity index 100%
rename from app/views/accounts/accountables/_property.html.erb
rename to app/views/accounts/accountables/property/_form.html.erb
diff --git a/app/views/accounts/accountables/property/_header.html.erb b/app/views/accounts/accountables/property/_header.html.erb
new file mode 100644
index 000000000..27701588d
--- /dev/null
+++ b/app/views/accounts/accountables/property/_header.html.erb
@@ -0,0 +1,11 @@
+
+ <%= render "accounts/logo", account: account %>
+
+
+
<%= account.name %>
+
+ <% if account.property.address&.line1.present? %>
+
<%= account.property.address %>
+ <% end %>
+
+
diff --git a/app/views/accounts/accountables/property/_overview.html.erb b/app/views/accounts/accountables/property/_overview.html.erb
index f7ede76a5..c0fde9f93 100644
--- a/app/views/accounts/accountables/property/_overview.html.erb
+++ b/app/views/accounts/accountables/property/_overview.html.erb
@@ -27,3 +27,7 @@
<%= account.property.area || t(".unknown") %>
<% end %>
+
+
+ <%= link_to "Edit account details", edit_account_path(account), class: "btn btn--ghost", data: { turbo_frame: :modal } %>
+
diff --git a/app/views/accounts/accountables/property/_tabs.html.erb b/app/views/accounts/accountables/property/_tabs.html.erb
new file mode 100644
index 000000000..07ff76a6b
--- /dev/null
+++ b/app/views/accounts/accountables/property/_tabs.html.erb
@@ -0,0 +1,15 @@
+<%# locals: (account:, selected_tab:) %>
+
+
+ <%= render "accounts/accountables/tab", account: account, key: "overview", is_selected: selected_tab.in?([nil, "overview"]) %>
+ <%= render "accounts/accountables/tab", account: account, key: "value", is_selected: selected_tab == "value" %>
+
+
+
+ <% case selected_tab %>
+ <% when nil, "overview" %>
+ <%= render "accounts/accountables/property/overview", account: account %>
+ <% when "value" %>
+ <%= render "accounts/accountables/valuations", account: account %>
+ <% end %>
+
diff --git a/app/views/accounts/accountables/_vehicle.html.erb b/app/views/accounts/accountables/vehicle/_form.html.erb
similarity index 100%
rename from app/views/accounts/accountables/_vehicle.html.erb
rename to app/views/accounts/accountables/vehicle/_form.html.erb
diff --git a/app/views/accounts/accountables/vehicle/_header.html.erb b/app/views/accounts/accountables/vehicle/_header.html.erb
new file mode 100644
index 000000000..518891358
--- /dev/null
+++ b/app/views/accounts/accountables/vehicle/_header.html.erb
@@ -0,0 +1 @@
+<%= render "accounts/accountables/default_header", account: account %>
diff --git a/app/views/accounts/accountables/vehicle/_overview.html.erb b/app/views/accounts/accountables/vehicle/_overview.html.erb
index 2455c67b6..7d72431e9 100644
--- a/app/views/accounts/accountables/vehicle/_overview.html.erb
+++ b/app/views/accounts/accountables/vehicle/_overview.html.erb
@@ -31,3 +31,7 @@
<% end %>
+
+
+ <%= link_to "Edit account details", edit_account_path(account), class: "btn btn--ghost", data: { turbo_frame: :modal } %>
+
diff --git a/app/views/accounts/accountables/vehicle/_tabs.html.erb b/app/views/accounts/accountables/vehicle/_tabs.html.erb
new file mode 100644
index 000000000..a79c89c23
--- /dev/null
+++ b/app/views/accounts/accountables/vehicle/_tabs.html.erb
@@ -0,0 +1,15 @@
+<%# locals: (account:, selected_tab:) %>
+
+
+ <%= render "accounts/accountables/tab", account: account, key: "overview", is_selected: selected_tab.in?([nil, "overview"]) %>
+ <%= render "accounts/accountables/tab", account: account, key: "value", is_selected: selected_tab == "value" %>
+
+
+
+ <% case selected_tab %>
+ <% when nil, "overview" %>
+ <%= render "accounts/accountables/vehicle/overview", account: account %>
+ <% when "value" %>
+ <%= render "accounts/accountables/valuations", account: account %>
+ <% end %>
+
diff --git a/app/views/accounts/index.html.erb b/app/views/accounts/index.html.erb
index adf33f505..fedf238ed 100644
--- a/app/views/accounts/index.html.erb
+++ b/app/views/accounts/index.html.erb
@@ -20,7 +20,7 @@
<%= render "sync_all_button" %>
- <%= link_to new_account_path,
+ <%= link_to new_account_path(step: "method"),
data: { turbo_frame: "modal" },
class: "btn btn--primary flex items-center gap-1" do %>
<%= lucide_icon("plus", class: "w-5 h-5") %>
diff --git a/app/views/accounts/new.html.erb b/app/views/accounts/new.html.erb
index bb544e857..9e8a562f8 100644
--- a/app/views/accounts/new.html.erb
+++ b/app/views/accounts/new.html.erb
@@ -1,58 +1,24 @@
<%= t(".title") %>
<%= modal do %>
- <% if @account.accountable.blank? %>
-
- <%= t ".select_accountable_type" %>
-
-
- Previous
- Next
- <%= render "account_type", type: Depository.new, bg_color: "bg-blue-500/5", text_color: "text-blue-500", icon: "landmark" %>
- <%= render "account_type", type: Investment.new, bg_color: "bg-green-500/5", text_color: "text-green-500", icon: "line-chart" %>
- <%= render "account_type", type: Crypto.new, bg_color: "bg-orange-500/5", text_color: "text-orange-500", icon: "bitcoin" %>
- <%= render "account_type", type: Property.new, bg_color: "bg-pink-500/5", text_color: "text-pink-500", icon: "home" %>
- <%= render "account_type", type: Vehicle.new, bg_color: "bg-cyan-500/5", text_color: "text-cyan-500", icon: "car-front" %>
- <%= render "account_type", type: CreditCard.new, bg_color: "bg-violet-500/5", text_color: "text-violet-500", icon: "credit-card" %>
- <%= render "account_type", type: Loan.new, bg_color: "bg-yellow-500/5", text_color: "text-yellow-500", icon: "hand-coins" %>
- <%= render "account_type", type: OtherAsset.new, bg_color: "bg-green-500/5", text_color: "text-green-500", icon: "plus" %>
- <%= render "account_type", type: OtherLiability.new, bg_color: "bg-red-500/5", text_color: "text-red-500", icon: "minus" %>
-
-
-
-
- Select
- <%= lucide_icon("corner-down-left", class: "inline w-3 h-3") %>
-
-
- Navigate
- <%= lucide_icon("arrow-up", class: "inline w-3 h-3") %>
- <%= lucide_icon("arrow-down", class: "inline w-3 h-3") %>
-
-
-
- Close
- ESC
-
-
- <% elsif params[:step] == 'method' && @account.accountable.present? %>
+ <% if params[:step] == 'method' %>
- <%= link_to new_account_path, class: "flex w-8 h-8 shrink-0 grow-0 items-center justify-center rounded-lg bg-alpha-black-50 back focus:outline-gray-300 focus:outline" do %>
- <%= lucide_icon("arrow-left", class: "text-gray-500 w-5 h-5") %>
- <% end %>
How would you like to add it?
Previous
Next
- <%= render "entry_method", type: @account.accountable, text: "Enter account balance manually", icon: "keyboard" %>
- <%= link_to new_import_path, class: "flex items-center gap-4 w-full text-center focus:outline-none focus:bg-gray-50 border border-transparent focus:border focus:border-gray-200 px-2 hover:bg-gray-50 rounded-lg p-2" do %>
+
+ <%= render "entry_method", text: t(".manual_entry"), icon: "keyboard" %>
+
+ <%= link_to new_import_path(import: { type: "AccountImport" }), class: "flex items-center gap-4 w-full text-center focus:outline-none focus:bg-gray-50 border border-transparent focus:border focus:border-gray-200 px-2 hover:bg-gray-50 rounded-lg p-2" do %>
<%= lucide_icon("sheet", class: "text-gray-500 w-5 h-5") %>
- Upload CSV
+ <%= t(".csv_entry") %>
<% end %>
- <%= render "entry_method", type: @account.accountable, text: "Securely link bank account with data provider (coming soon)", icon: "link-2", disabled: true %>
+
+ <%= render "entry_method", text: t(".connected_entry"), icon: "link-2", disabled: true %>
@@ -73,13 +39,13 @@
<% else %>
- <%= link_to new_account_path(step: "method", type: params[:type]), class: "flex w-8 h-8 shrink-0 grow-0 items-center justify-center rounded-lg bg-alpha-black-50 focus:outline-gray-300 focus:outline" do %>
+ <%= link_to new_account_path(step: "method"), class: "flex w-8 h-8 shrink-0 grow-0 items-center justify-center rounded-lg bg-alpha-black-50 focus:outline-gray-300 focus:outline" do %>
<%= lucide_icon("arrow-left", class: "text-gray-500 w-5 h-5") %>
<% end %>
- Add <%= @account.accountable.model_name.human.downcase %>
+ Add account
-
+
<%= render "form", account: @account, url: new_account_form_url(@account) %>
<% end %>
diff --git a/app/views/accounts/show.html.erb b/app/views/accounts/show.html.erb
index d90417663..c49b64e2a 100644
--- a/app/views/accounts/show.html.erb
+++ b/app/views/accounts/show.html.erb
@@ -1,56 +1,25 @@
<%= turbo_stream_from @account %>
-<%= tag.div id: dom_id(@account), class: "space-y-4" do %>
-
-
- <%= image_tag account_logo_url(@account), class: "w-8 h-8" %>
-
-
<%= @account.name %>
+<% series = @account.series(period: @period) %>
+<% trend = series.trend %>
- <% if @account.property? && @account.property.address&.line1.present? %>
-
<%= @account.property.address %>
- <% end %>
-
-
-
+<%= tag.div id: dom_id(@account), class: "space-y-4" do %>
+
+ <%= render permitted_accountable_partial(@account, "header"), account: @account %>
+
+
<%= button_to sync_account_path(@account), method: :post, class: "flex items-center gap-2", title: "Sync Account" do %>
<%= lucide_icon "refresh-cw", class: "w-4 h-4 text-gray-500 hover:text-gray-400" %>
<% end %>
- <%= contextual_menu do %>
-
- <%= link_to edit_account_path(@account),
- data: { turbo_frame: :modal },
- class: "block w-full py-2 px-3 space-x-2 text-gray-900 hover:bg-gray-50 flex items-center rounded-lg" do %>
- <%= lucide_icon "pencil-line", class: "w-5 h-5 text-gray-500" %>
-
- <%= t(".edit") %>
- <% end %>
-
- <%= link_to new_import_path,
- class: "block w-full py-2 px-3 space-x-2 text-gray-900 hover:bg-gray-50 flex items-center rounded-lg" do %>
- <%= lucide_icon "download", class: "w-5 h-5 text-gray-500" %>
-
- <%= t(".import") %>
- <% end %>
-
- <%= button_to account_path(@account),
- method: :delete,
- class: "block w-full py-2 px-3 space-x-2 text-red-600 hover:bg-red-50 flex items-center rounded-lg",
- data: {
- turbo_confirm: {
- title: t(".confirm_title"),
- body: t(".confirm_body_html"),
- accept: t(".confirm_accept", name: @account.name)
- }
- } do %>
- <%= lucide_icon("trash-2", class: "w-5 h-5 mr-2") %> Delete account
- <% end %>
-
- <% end %>
+ <%= render "menu", account: @account %>
+ <% if @account.entries.empty? && @account.depository? %>
+ <%= render "accounts/new_account_setup_bar", account: @account %>
+ <% end %>
+
<% if @account.highest_priority_issue %>
<%= render partial: "issues/issue", locals: { issue: @account.highest_priority_issue } %>
<% end %>
@@ -66,47 +35,35 @@
<%= tag.p t(".total_owed"), class: "text-sm font-medium text-gray-500" %>
<% end %>
- <%= render "tooltip", account: @account if @account.investment? %>
+
+ <%= render permitted_accountable_partial(@account, "tooltip"), account: @account if @account.investment? %>
+
<%= tag.p format_money(@account.value), class: "text-gray-900 text-3xl font-medium" %>
+
- <% if @series.trend.direction.flat? %>
+ <% if trend.direction.flat? %>
<%= tag.span t(".no_change"), class: "text-gray-500" %>
<% else %>
- <%= tag.span format_money(@series.trend.value), style: "color: #{@trend.color}" %>
- <%= tag.span "(#{@trend.percent}%)", style: "color: #{@trend.color}" %>
+ <%= tag.span format_money(trend.value), style: "color: #{trend.color}" %>
+ <%= tag.span "(#{trend.percent}%)", style: "color: #{trend.color}" %>
<% end %>
<%= tag.span period_label(@period), class: "text-gray-500" %>
+
<%= form_with url: account_path(@account), method: :get, data: { controller: "auto-submit-form" } do |form| %>
<%= period_select form: form, selected: @period.name %>
<% end %>
+
- <%= render partial: "shared/line_chart", locals: { series: @series } %>
+ <%= render "shared/line_chart", series: @account.series(period: @period) %>
- <% selected_tab = selected_account_tab(@account) %>
- <% selected_tab_key = selected_tab[:key] %>
- <% selected_tab_partial_path = selected_tab[:partial_path] %>
- <% selected_tab_route = selected_tab[:route] %>
-
-
- <% account_tabs(@account).each do |tab| %>
- <%= link_to tab[:label], tab[:path], class: ["px-2 py-1.5 rounded-md border border-transparent", "bg-white shadow-xs border-alpha-black-50": selected_tab_key == tab[:key]] %>
- <% end %>
-
-
- <% if selected_tab_route.present? %>
- <%= turbo_frame_tag dom_id(@account, selected_tab_key), src: selected_tab_route do %>
- <%= render "account/entries/loading" %>
- <% end %>
- <% else %>
- <%= render selected_tab_partial_path, account: @account %>
- <% end %>
+ <%= render permitted_accountable_partial(@account, "tabs"), account: @account, selected_tab: params[:tab] %>
<% end %>
diff --git a/app/views/accounts/summary.html.erb b/app/views/accounts/summary.html.erb
index feffec64f..db9ab322c 100644
--- a/app/views/accounts/summary.html.erb
+++ b/app/views/accounts/summary.html.erb
@@ -41,7 +41,7 @@
Assets
- <%= link_to new_account_path, class: "btn btn--secondary flex items-center gap-1", data: { turbo_frame: "modal" } do %>
+ <%= link_to new_account_path(step: "method"), class: "btn btn--secondary flex items-center gap-1", data: { turbo_frame: "modal" } do %>
<%= lucide_icon("plus", class: "w-5 h-5 text-gray-500") %>
<%= t(".new") %>
<% end %>
@@ -66,7 +66,7 @@
Liabilities
- <%= link_to new_account_path, class: "btn btn--secondary flex items-center gap-1", data: { turbo_frame: "modal" } do %>
+ <%= link_to new_account_path(step: "method"), class: "btn btn--secondary flex items-center gap-1", data: { turbo_frame: "modal" } do %>
<%= lucide_icon("plus", class: "w-5 h-5 text-gray-500") %>
<%= t(".new") %>
<% end %>
diff --git a/app/views/imports/new.html.erb b/app/views/imports/new.html.erb
index b0005dc81..7cbcf685b 100644
--- a/app/views/imports/new.html.erb
+++ b/app/views/imports/new.html.erb
@@ -15,14 +15,14 @@
<%= t(".sources") %>
- <% if @pending_import.present? %>
+ <% if @pending_import.present? && (params[:type].nil? || params[:type] == @pending_import.type) %>
<%= link_to import_path(@pending_import), class: "flex items-center justify-between p-4 group cursor-pointer", data: { turbo: false } do %>
<%= lucide_icon("loader", class: "w-5 h-5 text-orange-500") %>
- <%= t(".resume") %>
+ <%= t(".resume", type: @pending_import.type.titleize) %>
<%= lucide_icon("chevron-right", class: "w-5 h-5 text-gray-500") %>
@@ -33,75 +33,84 @@
<% end %>
-
- <%= button_to imports_path(import: { type: "TransactionImport" }), class: "flex items-center justify-between p-4 group cursor-pointer w-full", data: { turbo: false } do %>
-
-
- <%= lucide_icon("file-spreadsheet", class: "w-5 h-5 text-indigo-500") %>
+
+ <% if Current.family.accounts.any? && (params[:type].nil? || params[:type] == "TransactionImport") %>
+
+ <%= button_to imports_path(import: { type: "TransactionImport" }), class: "flex items-center justify-between p-4 group cursor-pointer w-full", data: { turbo: false } do %>
+
+
+ <%= lucide_icon("file-spreadsheet", class: "w-5 h-5 text-indigo-500") %>
+
+
+ <%= t(".import_transactions") %>
+
-
- <%= t(".import_transactions") %>
-
+ <%= lucide_icon("chevron-right", class: "w-5 h-5 text-gray-500") %>
+ <% end %>
+
+
- <%= lucide_icon("chevron-right", class: "w-5 h-5 text-gray-500") %>
- <% end %>
+
+ <% end %>
-
-
-
-
- <%= button_to imports_path(import: { type: "TradeImport" }), class: "flex items-center justify-between p-4 group cursor-pointer w-full", data: { turbo: false } do %>
-
-
- <%= lucide_icon("square-percent", class: "w-5 h-5 text-yellow-500") %>
+ <% if Current.family.accounts.any? && (params[:type].nil? || params[:type] == "TradeImport") %>
+
+ <%= button_to imports_path(import: { type: "TradeImport" }), class: "flex items-center justify-between p-4 group cursor-pointer w-full", data: { turbo: false } do %>
+
+
+ <%= lucide_icon("square-percent", class: "w-5 h-5 text-yellow-500") %>
+
+
+ <%= t(".import_portfolio") %>
+
-
- <%= t(".import_portfolio") %>
-
+ <%= lucide_icon("chevron-right", class: "w-5 h-5 text-gray-500") %>
+ <% end %>
+
+
- <%= lucide_icon("chevron-right", class: "w-5 h-5 text-gray-500") %>
- <% end %>
+
+ <% end %>
-
-
-
-
- <%= button_to imports_path(import: { type: "AccountImport" }), class: "flex items-center justify-between p-4 group cursor-pointer w-full", data: { turbo: false } do %>
-
-
- <%= lucide_icon("building", class: "w-5 h-5 text-violet-500") %>
+ <% if params[:type].nil? || params[:type] == "AccountImport" %>
+
+ <%= button_to imports_path(import: { type: "AccountImport" }), class: "flex items-center justify-between p-4 group cursor-pointer w-full", data: { turbo: false } do %>
+
+
+ <%= lucide_icon("building", class: "w-5 h-5 text-violet-500") %>
+
+
+ <%= t(".import_accounts") %>
+
-
- <%= t(".import_accounts") %>
-
+ <%= lucide_icon("chevron-right", class: "w-5 h-5 text-gray-500") %>
+ <% end %>
+
+
- <%= lucide_icon("chevron-right", class: "w-5 h-5 text-gray-500") %>
- <% end %>
+
+ <% end %>
-
-
+ <% if Current.family.accounts.any? && (params[:type].nil? || params[:type] == "MintImport" || params[:type] == "TransactionImport") %>
+
+ <%= button_to imports_path(import: { type: "MintImport" }), class: "flex items-center justify-between p-4 group w-full", data: { turbo: false } do %>
+
+ <%= image_tag("mint-logo.jpeg", alt: "Mint logo", class: "w-8 h-8 rounded-md") %>
+
+ <%= t(".import_mint") %>
+
+
+ <%= lucide_icon("chevron-right", class: "w-5 h-5 text-gray-500") %>
+ <% end %>
-
- <%= button_to imports_path(import: { type: "MintImport" }), class: "flex items-center justify-between p-4 group w-full", data: { turbo: false } do %>
-
- <%= image_tag("mint-logo.jpeg", alt: "Mint logo", class: "w-8 h-8 rounded-md") %>
-
- <%= t(".import_mint") %>
-
+
- <%= lucide_icon("chevron-right", class: "w-5 h-5 text-gray-500") %>
- <% end %>
-
-
-
+
+ <% end %>
diff --git a/app/views/layouts/_sidebar.html.erb b/app/views/layouts/_sidebar.html.erb
index e62a104a2..e038247e0 100644
--- a/app/views/layouts/_sidebar.html.erb
+++ b/app/views/layouts/_sidebar.html.erb
@@ -103,7 +103,7 @@
<%= period_select form: form, selected: "last_30_days", classes: "w-full border-none pl-2 pr-7 text-xs bg-transparent gap-1 cursor-pointer font-semibold tracking-wide focus:outline-none focus:ring-0" %>
<% end %>
- <%= link_to new_account_path, id: "sidebar-new-account", class: "block hover:bg-gray-100 font-semibold text-gray-900 flex items-center rounded", title: t(".new_account"), data: { turbo_frame: "modal" } do %>
+ <%= link_to new_account_path(step: "method"), id: "sidebar-new-account", class: "block hover:bg-gray-100 font-semibold text-gray-900 flex items-center rounded p-1", title: t(".new_account"), data: { turbo_frame: "modal" } do %>
<%= lucide_icon("plus", class: "w-5 h-5 text-gray-500") %>
<% end %>
@@ -114,7 +114,7 @@
<%= render "accounts/account_list", group: group %>
<% end %>
<% else %>
- <%= link_to new_account_path, class: "flex items-center min-h-10 gap-4 px-3 py-2 mb-1 text-gray-500 text-sm font-medium rounded-[10px] hover:bg-gray-100", data: { turbo_frame: "modal" } do %>
+ <%= link_to new_account_path(step: "method"), class: "flex items-center min-h-10 gap-4 px-3 py-2 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") %>
<%= tag.p t(".new_account") %>
<% end %>
diff --git a/app/views/pages/_account_group_disclosure.erb b/app/views/pages/_account_group_disclosure.erb
index 6fc6f7e96..e3e1128dc 100644
--- a/app/views/pages/_account_group_disclosure.erb
+++ b/app/views/pages/_account_group_disclosure.erb
@@ -25,7 +25,7 @@
<% accountable_group.children.map do |account_value_node| %>
- <%= image_tag account_logo_url(account_value_node.original), class: "w-8 h-8" %>
+ <%= render "accounts/logo", account: account_value_node.original, size: "sm" %>
<%= account_value_node.name %>
diff --git a/app/views/pages/dashboard.html.erb b/app/views/pages/dashboard.html.erb
index 9a70d4938..b25e8497b 100644
--- a/app/views/pages/dashboard.html.erb
+++ b/app/views/pages/dashboard.html.erb
@@ -17,7 +17,7 @@
<% end %>
- <%= link_to new_account_path, class: "flex items-center gap-1 btn btn--primary", data: { turbo_frame: "modal" } do %>
+ <%= link_to new_account_path(step: "method"), class: "flex items-center gap-1 btn btn--primary", data: { turbo_frame: "modal" } do %>
<%= lucide_icon("plus", class: "w-5 h-5") %>
<%= t(".new") %>
<% end %>
@@ -70,10 +70,10 @@
data-time-series-chart-use-labels-value="false"
data-time-series-chart-use-tooltip-value="false">
-
+
<% @top_earners.first(3).each do |account| %>
<%= link_to account, class: "border border-alpha-black-25 rounded-full p-1 pr-2 flex items-center gap-1 text-xs text-gray-900 font-medium hover:bg-gray-25", data: { controller: "tooltip" } do %>
- <%= image_tag account_logo_url(account), class: "w-5 h-5" %>
+ <%= render "accounts/logo", account: account, size: "sm" %>
+<%= Money.new(account.income, account.currency) %>
<%= render partial: "shared/text_tooltip", locals: { tooltip_text: account.name } %>
<% end %>
@@ -103,12 +103,12 @@
data-time-series-chart-use-labels-value="false"
data-time-series-chart-use-tooltip-value="false">
-
+
<% @top_spenders.first(3).each do |account| %>
<%= link_to account, class: "border border-alpha-black-25 rounded-full p-1 pr-2 flex items-center gap-1 text-xs text-gray-900 font-medium hover:bg-gray-25", data: { controller: "tooltip" } do %>
- <%= image_tag account_logo_url(account), class: "w-5 h-5" %>
+ <%= render "accounts/logo", account: account, size: "sm" %>
-<%= Money.new(account.spending, account.currency) %>
- <%= render partial: "shared/text_tooltip", locals: { tooltip_text: account.name } %>
+ <%= render partial: "shared/text_tooltip", locals: { tooltip_text: account.name } %>
<% end %>
<% end %>
<% if @top_spenders.count > 3 %>
@@ -141,9 +141,9 @@
<% @top_savers.first(3).each do |account| %>
<% unless account.savings_rate.infinite? %>
<%= link_to account, class: "border border-alpha-black-25 rounded-full p-1 pr-2 flex items-center gap-1 text-xs text-gray-900 font-medium hover:bg-gray-25", data: { controller: "tooltip" } do %>
- <%= image_tag account_logo_url(account), class: "w-5 h-5" %>
+ <%= render "accounts/logo", account: account, size: "sm" %>
<%= account.savings_rate > 0 ? "+" : "-" %><%= number_to_percentage(account.savings_rate.abs * 100, precision: 2) %>
- <%= render partial: "shared/text_tooltip", locals: { tooltip_text: account.name } %>
+ <%= render partial: "shared/text_tooltip", locals: { tooltip_text: account.name } %>
<% end %>
<% end %>
<% end %>
diff --git a/app/views/shared/_circle_logo.html.erb b/app/views/shared/_circle_logo.html.erb
index 328cb4763..17f1948af 100644
--- a/app/views/shared/_circle_logo.html.erb
+++ b/app/views/shared/_circle_logo.html.erb
@@ -2,7 +2,7 @@
<% size_classes = {
"sm" => "w-6 h-6",
- "md" => "w-8 h-8",
+ "md" => "w-9 h-9",
"lg" => "w-10 h-10",
"full" => "w-full h-full"
} %>
diff --git a/app/views/shared/_no_account_empty_state.html.erb b/app/views/shared/_no_account_empty_state.html.erb
index cfb790030..b4eda5976 100644
--- a/app/views/shared/_no_account_empty_state.html.erb
+++ b/app/views/shared/_no_account_empty_state.html.erb
@@ -7,7 +7,7 @@
<%= t(".no_account_subtitle") %>
- <%= link_to new_account_path, class: "btn btn--primary flex items-center gap-1", data: { turbo_frame: "modal" } do %>
+ <%= link_to new_account_path(step: "method"), class: "btn btn--primary flex items-center gap-1", data: { turbo_frame: "modal" } do %>
<%= lucide_icon("plus", class: "w-5 h-5") %>
<%= t(".new_account") %>
<% end %>
diff --git a/config/brakeman.ignore b/config/brakeman.ignore
index 71a47b67b..78648c482 100644
--- a/config/brakeman.ignore
+++ b/config/brakeman.ignore
@@ -23,6 +23,74 @@
],
"note": ""
},
+ {
+ "warning_type": "Dynamic Render Path",
+ "warning_code": 15,
+ "fingerprint": "42595161ffdc9ce9a10c4ba2a75fd2bb668e273bc4e683880b0ea906d0bd28f8",
+ "check_name": "Render",
+ "message": "Render path contains parameter value",
+ "file": "app/views/accounts/show.html.erb",
+ "line": 8,
+ "link": "https://brakemanscanner.org/docs/warning_types/dynamic_render_path/",
+ "code": "render(action => permitted_accountable_partial(Current.family.accounts.find(params[:id]), \"header\"), { :account => Current.family.accounts.find(params[:id]) })",
+ "render_path": [
+ {
+ "type": "controller",
+ "class": "AccountsController",
+ "method": "show",
+ "line": 39,
+ "file": "app/controllers/accounts_controller.rb",
+ "rendered": {
+ "name": "accounts/show",
+ "file": "app/views/accounts/show.html.erb"
+ }
+ }
+ ],
+ "location": {
+ "type": "template",
+ "template": "accounts/show"
+ },
+ "user_input": "params[:id]",
+ "confidence": "Weak",
+ "cwe_id": [
+ 22
+ ],
+ "note": ""
+ },
+ {
+ "warning_type": "Dynamic Render Path",
+ "warning_code": 15,
+ "fingerprint": "a35b18785608dbdf35607501363573576ed8c304039f8387997acd1408ca1025",
+ "check_name": "Render",
+ "message": "Render path contains parameter value",
+ "file": "app/views/accounts/show.html.erb",
+ "line": 35,
+ "link": "https://brakemanscanner.org/docs/warning_types/dynamic_render_path/",
+ "code": "render(action => permitted_accountable_partial(Current.family.accounts.find(params[:id]), \"tooltip\"), { :account => Current.family.accounts.find(params[:id]) })",
+ "render_path": [
+ {
+ "type": "controller",
+ "class": "AccountsController",
+ "method": "show",
+ "line": 39,
+ "file": "app/controllers/accounts_controller.rb",
+ "rendered": {
+ "name": "accounts/show",
+ "file": "app/views/accounts/show.html.erb"
+ }
+ }
+ ],
+ "location": {
+ "type": "template",
+ "template": "accounts/show"
+ },
+ "user_input": "params[:id]",
+ "confidence": "Weak",
+ "cwe_id": [
+ 22
+ ],
+ "note": ""
+ },
{
"warning_type": "Cross-Site Scripting",
"warning_code": 2,
@@ -38,7 +106,7 @@
"type": "controller",
"class": "PagesController",
"method": "changelog",
- "line": 35,
+ "line": 36,
"file": "app/controllers/pages_controller.rb",
"rendered": {
"name": "pages/changelog",
@@ -60,19 +128,19 @@
{
"warning_type": "Dynamic Render Path",
"warning_code": 15,
- "fingerprint": "b7a59d6dd91f4d30873b271659636c7975e25b47f436b4f03900a08809af2e92",
+ "fingerprint": "c5c512a13c34c9696024bd4e2367a657a5c140b5b6a0f5c352e9b69965f63e1b",
"check_name": "Render",
"message": "Render path contains parameter value",
"file": "app/views/accounts/show.html.erb",
- "line": 105,
+ "line": 63,
"link": "https://brakemanscanner.org/docs/warning_types/dynamic_render_path/",
- "code": "render(action => selected_account_tab(Current.family.accounts.find(params[:id]))[:partial_path], { :account => Current.family.accounts.find(params[:id]) })",
+ "code": "render(action => permitted_accountable_partial(Current.family.accounts.find(params[:id]), \"tabs\"), { :account => Current.family.accounts.find(params[:id]), :selected_tab => params[:tab] })",
"render_path": [
{
"type": "controller",
"class": "AccountsController",
"method": "show",
- "line": 38,
+ "line": 39,
"file": "app/controllers/accounts_controller.rb",
"rendered": {
"name": "accounts/show",
@@ -98,7 +166,7 @@
"check_name": "Render",
"message": "Render path contains parameter value",
"file": "app/views/import/configurations/show.html.erb",
- "line": 13,
+ "line": 15,
"link": "https://brakemanscanner.org/docs/warning_types/dynamic_render_path/",
"code": "render(partial => permitted_import_configuration_path(Current.family.imports.find(params[:import_id])), { :locals => ({ :import => Current.family.imports.find(params[:import_id]) }) })",
"render_path": [
@@ -126,6 +194,6 @@
"note": ""
}
],
- "updated": "2024-09-28 13:27:09 -0400",
+ "updated": "2024-10-17 11:30:15 -0400",
"brakeman_version": "6.2.1"
}
diff --git a/config/locales/views/accounts/en.yml b/config/locales/views/accounts/en.yml
index cb6668ef2..0e12a0f55 100644
--- a/config/locales/views/accounts/en.yml
+++ b/config/locales/views/accounts/en.yml
@@ -1,37 +1,88 @@
---
en:
accounts:
+ sync_all_button:
+ sync: Sync all
account:
has_issues: Issue detected.
troubleshoot: Troubleshoot
+ account_list:
+ new_account: "New %{type}"
+ empty:
+ no_accounts: No accounts yet
+ empty_message: Add an account either via connection, importing or entering manually.
+ new_account: New account
+ form:
+ name_label: Account name
+ name_placeholder: Example account name
+ institution: Financial institution
+ ungrouped: "(none)"
+ balance: Today's balance
+ accountable_type: Account type
+ type_prompt: Select a type
+ header:
+ accounts: Accounts
+ manage: Manage accounts
+ new: New account
+ institution_accounts:
+ add_account_to_institution: Add new account
+ has_issues: Issue detected, see accounts
+ syncing: Syncing...
+ status: "Last synced %{last_synced_at} ago"
+ status_never: Requires data sync
+ edit: Edit institution
+ delete: Delete institution
+ confirm_title: Delete financial institution?
+ confirm_body: Don't worry, none of the accounts within this institution will be affected by this deletion. Accounts will be ungrouped and all historical data will remain intact.
+ confirm_accept: Delete institution
+ new_account: Add account
+ institutionless_accounts:
+ other_accounts: Other accounts
+ menu:
+ edit: Edit
+ import: Import transactions
+ confirm_title: Delete account?
+ confirm_body_html: "
By deleting this account, you will erase its value history, affecting various aspects of your overall account. This action will have a direct impact on your net worth calculations and the account graphs.
After deletion, there is no way you'll be able to restore the account information because you'll need to add it as a new account.
"
+ confirm_accept: 'Delete "%{name}"'
accountables:
- investment:
- prompt: Select a subtype
- none: None
credit_card:
- annual_fee: Annual fee
- annual_fee_placeholder: '99'
- apr: APR
- apr_placeholder: '15.99'
- available_credit: Available credit
- available_credit_placeholder: '10000'
- expiration_date: Expiration date
- minimum_payment: Minimum payment
- minimum_payment_placeholder: '100'
+ form:
+ available_credit: Available credit
+ available_credit_placeholder: '10000'
+ minimum_payment: Minimum payment
+ minimum_payment_placeholder: '100'
+ apr: APR
+ apr_placeholder: '15.99'
+ expiration_date: Expiration date
+ annual_fee: Annual fee
+ annual_fee_placeholder: '99'
overview:
amount_owed: Amount Owed
- annual_fee: Annual Fee
- apr: APR
available_credit: Available Credit
- expiration_date: Expiration Date
minimum_payment: Minimum Payment
+ apr: APR
+ expiration_date: Expiration Date
+ annual_fee: Annual Fee
unknown: Unknown
depository:
- prompt: Select a subtype
- none: None
+ form:
+ none: None
+ prompt: Select a subtype
+ investment:
+ form:
+ none: None
+ prompt: Select a subtype
+ tooltip:
+ cash: Cash
+ holdings: Holdings
+ total_value_tooltip: The total value is the sum of cash balance and your holdings value, minus margin loans.
loan:
- interest_rate: Interest rate
- interest_rate_placeholder: '5.25'
+ form:
+ interest_rate: Interest rate
+ interest_rate_placeholder: '5.25'
+ rate_type: Rate type
+ term_months: Term (months)
+ term_months_placeholder: '360'
overview:
interest_rate: Interest Rate
monthly_payment: Monthly Payment
@@ -41,18 +92,19 @@ en:
term: Term
type: Type
unknown: Unknown
- rate_type: Rate type
- term_months: Term (months)
- term_months_placeholder: '360'
property:
- additional_info: Additional info
- area_unit: Area unit
- area_value: Area value
- city: City
- country: Country
- line1: Address line 1
- line2: Address line 2
- optional: optional
+ form:
+ additional_info: Additional info
+ area_unit: Area unit
+ area_value: Area value
+ city: City
+ country: Country
+ line1: Address line 1
+ line2: Address line 2
+ optional: optional
+ postal_code: Postal code
+ state: State
+ year_built: Year built
overview:
living_area: Living Area
market_value: Market Value
@@ -60,17 +112,17 @@ en:
trend: Trend
unknown: Unknown
year_built: Year Built
- postal_code: Postal code
- state: State
- year_built: Year built
vehicle:
- make: Make
- make_placeholder: Toyota
- mileage: Mileage
- mileage_placeholder: '15000'
- mileage_unit: Unit
- model: Model
- model_placeholder: Camry
+ form:
+ make: Make
+ make_placeholder: Toyota
+ mileage: Mileage
+ mileage_placeholder: '15000'
+ mileage_unit: Unit
+ model: Model
+ model_placeholder: Camry
+ year: Year
+ year_placeholder: '2023'
overview:
current_price: Current Price
make_model: Make & Model
@@ -79,70 +131,22 @@ en:
trend: Trend
unknown: Unknown
year: Year
- year: Year
- year_placeholder: '2023'
- create:
- success: New account created successfully
- destroy:
- success: Account deleted successfully
edit:
- edit: Edit %{account}
- empty:
- empty_message: Add an account either via connection, importing or entering manually.
- new_account: New account
- no_accounts: No accounts yet
- form:
- institution: Financial institution
- ungrouped: "(none)"
- balance: Current balance
- name_label: Account name
- name_placeholder: Example account name
- start_balance: Start balance (optional)
- start_date: Start date (optional)
- header:
- accounts: Accounts
- manage: Manage accounts
- new: New account
+ edit: "Edit %{account}"
index:
accounts: Accounts
add_institution: Add institution
new_account: New account
- institution_accounts:
- add_account_to_institution: Add new account
- confirm_accept: Delete institution
- confirm_body: Don't worry, none of the accounts within this institution will
- be affected by this deletion. Accounts will be ungrouped and all historical
- data will remain intact.
- confirm_title: Delete financial institution?
- delete: Delete institution
- edit: Edit institution
- has_issues: Issue detected, see accounts
- new_account: Add account
- status: Last synced %{last_synced_at} ago
- status_never: Requires data sync
- syncing: Syncing...
- institutionless_accounts:
- other_accounts: Other accounts
new:
- select_accountable_type: What would you like to add?
title: Add an account
+ manual_entry: Enter account manually
+ csv_entry: Import accounts CSV
+ connected_entry: Securely link account with Plaid (coming soon)
show:
cash: Cash
- confirm_accept: Delete "%{name}"
- confirm_body_html: "
By deleting this account, you will erase its value history,
- affecting various aspects of your overall account. This action will have a
- direct impact on your net worth calculations and the account graphs.
After deletion, there is no way you'll be able to restore the account
- information because you'll need to add it as a new account.
"
- confirm_title: Delete account?
- edit: Edit
holdings: Holdings
- import: Import transactions
no_change: No change
overview: Overview
- sync_message_missing_rates: Since exchange rates haven't been synced, balance
- graphs may not reflect accurate values.
- sync_message_unknown_error: An error has occurred during the sync.
total_owed: Total Owed
total_value: Total Value
trades: Transactions
@@ -151,21 +155,17 @@ en:
summary:
new: New
no_assets: No assets found
- no_assets_description: Add an asset either via connection, importing or entering
- manually.
+ no_assets_description: Add an asset either via connection, importing or entering manually.
no_liabilities: No liabilities found
- no_liabilities_description: Add a liability either via connection, importing
- or entering manually.
- sync_all:
- button_text: Sync all
- success: Successfully queued accounts for syncing.
- tooltip:
- cash: Cash
- holdings: Holdings
- total_value_tooltip: The total value is the sum of cash balance and your holdings
- value, minus margin loans.
+ no_liabilities_description: Add a liability either via connection, importing or entering manually.
+ create:
+ success: New account created successfully
+ destroy:
+ success: Account deleted successfully
update:
success: Account updated
+ sync_all:
+ success: Successfully queued accounts for syncing.
credit_cards:
create:
success: Credit card created successfully
diff --git a/config/locales/views/imports/en.yml b/config/locales/views/imports/en.yml
index 7227a9f20..e5fd7920f 100644
--- a/config/locales/views/imports/en.yml
+++ b/config/locales/views/imports/en.yml
@@ -69,7 +69,7 @@ en:
import_mint: Import from Mint
import_portfolio: Import investments
import_transactions: Import transactions
- resume: Resume latest import
+ resume: Resume %{type}
sources: Sources
title: New CSV Import
ready:
diff --git a/config/routes.rb b/config/routes.rb
index 2cfa2b2d2..079943a7d 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -61,8 +61,6 @@ Rails.application.routes.draw do
end
scope module: :account do
- resource :logo, only: :show
-
resources :holdings, only: %i[index new show destroy]
resources :cashes, only: :index
diff --git a/test/helpers/application_helper_test.rb b/test/helpers/application_helper_test.rb
index d4c1be45e..e4c3bf361 100644
--- a/test/helpers/application_helper_test.rb
+++ b/test/helpers/application_helper_test.rb
@@ -11,12 +11,6 @@ class ApplicationHelperTest < ActionView::TestCase
assert_equal "Test Header Title", content_for(:header_title)
end
- test "#permitted_accountable_partial(accountable_type)" do
- assert_equal "account", permitted_accountable_partial("Account")
- assert_equal "user", permitted_accountable_partial("User")
- assert_equal "admin_user", permitted_accountable_partial("AdminUser")
- end
-
def setup
@account1 = Account.new(currency: "USD", balance: 1)
@account2 = Account.new(currency: "USD", balance: 2)
diff --git a/test/models/account/balance/syncer_test.rb b/test/models/account/balance/syncer_test.rb
index 9bfeffcbe..748b3e6ad 100644
--- a/test/models/account/balance/syncer_test.rb
+++ b/test/models/account/balance/syncer_test.rb
@@ -35,15 +35,6 @@ class Account::Balance::SyncerTest < ActiveSupport::TestCase
assert_equal [ 19600, 19500, 19500, 20000, 20000, 20000 ], @account.balances.chronological.map(&:balance)
end
- test "syncs account with trades only" do
- aapl = securities(:aapl)
- create_trade(aapl, account: @investment_account, date: 1.day.ago.to_date, qty: 10)
-
- run_sync_for @investment_account
-
- assert_equal [ 52140, 50000, 50000 ], @investment_account.balances.chronological.map(&:balance)
- end
-
test "syncs account with valuations and transactions" do
create_valuation(account: @account, date: 5.days.ago.to_date, amount: 20000)
create_transaction(account: @account, date: 3.days.ago.to_date, amount: -500)
diff --git a/test/system/.keep b/test/system/.keep
deleted file mode 100644
index e69de29bb..000000000
diff --git a/test/system/accounts_test.rb b/test/system/accounts_test.rb
index 1b7248c05..97f3fd911 100644
--- a/test/system/accounts_test.rb
+++ b/test/system/accounts_test.rb
@@ -80,19 +80,15 @@ class AccountsTest < ApplicationSystemTestCase
end
def assert_account_created(accountable_type, &block)
- click_link humanized_accountable(accountable_type)
- click_link "Enter account balance manually"
+ click_link "Enter account manually"
account_name = "[system test] #{accountable_type} Account"
+ select accountable_type.titleize, from: "Account type"
fill_in "Account name", with: account_name
fill_in "account[balance]", with: 100.99
- fill_in "Start date (optional)", with: 10.days.ago.to_date
- fill_in "account[start_balance]", with: 95.25
- yield if block_given?
-
- click_button "Add #{humanized_accountable(accountable_type).downcase}"
+ click_button "Create Account"
find("details", text: humanized_accountable(accountable_type)).click
assert_text account_name
@@ -107,8 +103,10 @@ class AccountsTest < ApplicationSystemTestCase
click_on "Edit"
end
+ yield if block_given?
+
fill_in "Account name", with: "Updated account name"
- click_button "Update #{humanized_accountable(accountable_type).downcase}"
+ click_button "Update Account"
assert_selector "h2", text: "Updated account name"
end
diff --git a/test/system/tooltips_test.rb b/test/system/tooltips_test.rb
deleted file mode 100644
index 7666269cf..000000000
--- a/test/system/tooltips_test.rb
+++ /dev/null
@@ -1,28 +0,0 @@
-require "application_system_test_case"
-
-class TooltipsTest < ApplicationSystemTestCase
- include ActionView::Helpers::NumberHelper
- include ApplicationHelper
-
- setup do
- sign_in @user = users(:family_admin)
- @account = accounts(:investment)
- end
-
- test "can see account information tooltip" do
- visit account_path(@account)
- tooltip_element = find('[data-controller="tooltip"]')
- tooltip_element.hover
- tooltip_contents = find('[data-tooltip-target="tooltip"]')
- assert tooltip_contents.visible?
- within tooltip_contents do
- assert_text I18n.t("accounts.tooltip.total_value_tooltip")
- assert_text I18n.t("accounts.tooltip.holdings")
- assert_text format_money(@account.investment.holdings_value, precision: 0)
- assert_text I18n.t("accounts.tooltip.cash")
- assert_text format_money(@account.balance_money, precision: 0)
- end
- find("body").click
- assert find('[data-tooltip-target="tooltip"]', visible: false)
- end
-end
diff --git a/test/system/trades_test.rb b/test/system/trades_test.rb
index 541a445cc..c9f7835b9 100644
--- a/test/system/trades_test.rb
+++ b/test/system/trades_test.rb
@@ -62,6 +62,6 @@ class TradesTest < ApplicationSystemTestCase
end
def visit_account_trades
- visit account_url(@account, tab: "trades")
+ visit account_url(@account, tab: "transactions")
end
end
diff --git a/test/system/transactions_test.rb b/test/system/transactions_test.rb
index 4544c6c4b..956e800b3 100644
--- a/test/system/transactions_test.rb
+++ b/test/system/transactions_test.rb
@@ -156,6 +156,7 @@ class TransactionsTest < ApplicationSystemTestCase
test "can create deposit transaction for investment account" do
investment_account = accounts(:investment)
+ investment_account.entries.create!(name: "Investment account", date: Date.current, amount: 1000, currency: "USD", entryable: Account::Transaction.new)
transfer_date = Date.current
visit account_path(investment_account)
click_on "New transaction"