diff --git a/app/controllers/account/logos_controller.rb b/app/controllers/account/logos_controller.rb
deleted file mode 100644
index dd56e665..00000000
--- 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 4dcbbb27..033ed3b8 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 542c80ef..b65033c5 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 8cbbcf21..19aa187e 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 ce27460f..7660f3f4 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 61d473d8..9dd49d60 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 00000000..68d85853
--- /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 00000000..f5e55749
--- /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 00000000..cb6ba0bd
--- /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 a937955d..d0e4546e 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 5c8bb0ae..bdde91c8 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 5aec6157..e4f81ae4 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 8ae1924d..90abe087 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 d5c583fe..1912899f 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 be8e8c2b..5051b69b 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 d9434f6b..90ca9e32 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 83be97f5..04a3737b 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 a23519ed..304c4d78 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 2ab34d7b..741070c2 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 19186e26..00000000
--- 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 b42df152..46e8004b 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 d8b400c7..d4b69527 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 e938c863..68c64316 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 ea73af73..1e9b6971 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 4fa0cce1..e85448e0 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 f8c8eb3e..348fc9b9 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 00000000..9036ad18
--- /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 00000000..8ebb53f1
--- /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 00000000..16b6e430
--- /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 0cf16bbd..8300df19 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 00000000..5feae417
--- /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 00000000..0a2f355a
--- /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 00000000..4ddd0e6e
--- /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 00000000..d6943303
--- /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 00000000..b4bf1fc3
--- /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 00000000..51889135
--- /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 3121528b..668326b5 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 00000000..781cc6d0
--- /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 00000000..51889135
--- /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 00000000..381f7273
--- /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 00000000..51889135
--- /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 00000000..381f7273
--- /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 00000000..51889135
--- /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 00000000..8da4905d
--- /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 952d2ad1..83432ebc 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 00000000..51889135
--- /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 ac32f413..cdd8c131 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 00000000..3ed1efcf
--- /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 00000000..51889135
--- /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 00000000..480136bf
--- /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 00000000..51889135
--- /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 00000000..480136bf
--- /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 00000000..27701588
--- /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 f7ede76a..c0fde9f9 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 00000000..07ff76a6
--- /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 00000000..51889135
--- /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 2455c67b..7d72431e 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 00000000..a79c89c2
--- /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 adf33f50..fedf238e 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 bb544e85..9e8a562f 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 d9041766..c49b64e2 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 feffec64..db9ab322 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 b0005dc8..7cbcf685 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 e62a104a..e038247e 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 6fc6f7e9..e3e1128d 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 9a70d493..b25e8497 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 328cb476..17f1948a 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 cfb79003..b4eda597 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 71a47b67..78648c48 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 cb6668ef..0e12a0f5 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 7227a9f2..e5fd7920 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 2cfa2b2d..079943a7 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 d4c1be45..e4c3bf36 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 9bfeffcb..748b3e6a 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 e69de29b..00000000
diff --git a/test/system/accounts_test.rb b/test/system/accounts_test.rb
index 1b7248c0..97f3fd91 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 7666269c..00000000
--- 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 541a445c..c9f7835b 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 4544c6c4..956e800b 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"