diff --git a/app/assets/tailwind/maybe-design-system.css b/app/assets/tailwind/maybe-design-system.css
index 8bf9c6c8..bfca010d 100644
--- a/app/assets/tailwind/maybe-design-system.css
+++ b/app/assets/tailwind/maybe-design-system.css
@@ -241,6 +241,15 @@
stroke-dashoffset: 0;
}
}
+
+ @keyframes shiny-wave {
+ 0% {
+ background-position: -200% 0;
+ }
+ 100% {
+ background-position: 200% 0;
+ }
+ }
}
/* Specific override for strong tags in prose under dark mode */
@@ -429,5 +438,3 @@
}
}
-
-
diff --git a/app/assets/tailwind/maybe-design-system/background-utils.css b/app/assets/tailwind/maybe-design-system/background-utils.css
index 1c7bc56a..fad493b0 100644
--- a/app/assets/tailwind/maybe-design-system/background-utils.css
+++ b/app/assets/tailwind/maybe-design-system/background-utils.css
@@ -84,4 +84,8 @@
@variant theme-dark {
background-color: var(--color-alpha-black-900);
}
+}
+
+@utility bg-loader {
+ @apply bg-surface-inset animate-pulse;
}
\ No newline at end of file
diff --git a/app/assets/tailwind/maybe-design-system/border-utils.css b/app/assets/tailwind/maybe-design-system/border-utils.css
index 94c54a55..bc2d7a60 100644
--- a/app/assets/tailwind/maybe-design-system/border-utils.css
+++ b/app/assets/tailwind/maybe-design-system/border-utils.css
@@ -1,6 +1,7 @@
/* Custom shadow borders used for surfaces / containers */
@utility shadow-border-xs {
box-shadow: var(--shadow-xs), 0px 0px 0px 1px var(--color-alpha-black-50);
+ transform: translateZ(0);
@variant theme-dark {
box-shadow: var(--shadow-xs), 0px 0px 0px 1px var(--color-alpha-white-50);
@@ -9,6 +10,7 @@
@utility shadow-border-sm {
box-shadow: var(--shadow-sm), 0px 0px 0px 1px var(--color-alpha-black-50);
+ transform: translateZ(0);
@variant theme-dark {
box-shadow: var(--shadow-sm), 0px 0px 0px 1px var(--color-alpha-white-50);
@@ -17,6 +19,7 @@
@utility shadow-border-md {
box-shadow: var(--shadow-md), 0px 0px 0px 1px var(--color-alpha-black-50);
+ transform: translateZ(0);
@variant theme-dark {
box-shadow: var(--shadow-md), 0px 0px 0px 1px var(--color-alpha-white-50);
@@ -25,6 +28,7 @@
@utility shadow-border-lg {
box-shadow: var(--shadow-lg), 0px 0px 0px 1px var(--color-alpha-black-50);
+ transform: translateZ(0);
@variant theme-dark {
box-shadow: var(--shadow-lg), 0px 0px 0px 1px var(--color-alpha-white-50);
@@ -33,6 +37,7 @@
@utility shadow-border-xl {
box-shadow: var(--shadow-xl), 0px 0px 0px 1px var(--color-alpha-black-50);
+ transform: translateZ(0);
@variant theme-dark {
box-shadow: var(--shadow-xl), 0px 0px 0px 1px var(--color-alpha-white-50);
diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb
index f003ab31..904be2b5 100644
--- a/app/controllers/accounts_controller.rb
+++ b/app/controllers/accounts_controller.rb
@@ -26,14 +26,6 @@ class AccountsController < ApplicationController
render layout: false
end
- def sync_all
- unless family.syncing?
- family.sync_later
- end
-
- redirect_back_or_to accounts_path
- end
-
private
def family
Current.family
diff --git a/app/controllers/concerns/auto_sync.rb b/app/controllers/concerns/auto_sync.rb
index 4e375359..a353a7be 100644
--- a/app/controllers/concerns/auto_sync.rb
+++ b/app/controllers/concerns/auto_sync.rb
@@ -14,6 +14,12 @@ module AutoSync
return false unless Current.family.present?
return false unless Current.family.accounts.active.any?
- (Current.family.last_synced_at&.to_date || 1.day.ago) < Date.current
+ should_sync = (Current.family.last_synced_at&.to_date || 1.day.ago) < Date.current
+
+ if should_sync
+ Rails.logger.info "Auto-syncing family #{Current.family.id}, last sync was #{Current.family.last_synced_at}"
+ end
+
+ should_sync
end
end
diff --git a/app/controllers/concerns/notifiable.rb b/app/controllers/concerns/notifiable.rb
index 0d8ea384..b1689f67 100644
--- a/app/controllers/concerns/notifiable.rb
+++ b/app/controllers/concerns/notifiable.rb
@@ -46,8 +46,6 @@ module Notifiable
[ { partial: "shared/notifications/alert", locals: { message: data } } ]
when "cta"
[ resolve_cta(data) ]
- when "loading"
- [ { partial: "shared/notifications/loading", locals: { message: data } } ]
when "notice"
messages = Array(data)
messages.map { |message| { partial: "shared/notifications/notice", locals: { message: message } } }
diff --git a/app/models/account.rb b/app/models/account.rb
index ee2fc963..5e99315c 100644
--- a/app/models/account.rb
+++ b/app/models/account.rb
@@ -61,6 +61,10 @@ class Account < ApplicationRecord
end
end
+ def syncing?
+ true
+ end
+
def institution_domain
url_string = plaid_account&.plaid_item&.institution_url
return nil unless url_string.present?
diff --git a/app/models/balance_sheet.rb b/app/models/balance_sheet.rb
index c289f86f..54ed199c 100644
--- a/app/models/balance_sheet.rb
+++ b/app/models/balance_sheet.rb
@@ -22,20 +22,25 @@ class BalanceSheet
end
def classification_groups
+ asset_groups = account_groups("asset")
+ liability_groups = account_groups("liability")
+
[
ClassificationGroup.new(
key: "asset",
display_name: "Assets",
icon: "plus",
total_money: total_assets_money,
- account_groups: account_groups("asset")
+ account_groups: asset_groups,
+ syncing?: asset_groups.any?(&:syncing?)
),
ClassificationGroup.new(
key: "liability",
display_name: "Debts",
icon: "minus",
total_money: total_liabilities_money,
- account_groups: account_groups("liability")
+ account_groups: liability_groups,
+ syncing?: liability_groups.any?(&:syncing?)
)
]
end
@@ -57,6 +62,7 @@ class BalanceSheet
weight: classification_total.zero? ? 0 : group_total / classification_total.to_d * 100,
missing_rates?: accounts.any? { |a| a.missing_rates? },
color: accountable.color,
+ syncing?: accounts.any?(&:is_syncing),
accounts: accounts.map do |account|
account.define_singleton_method(:weight) do
classification_total.zero? ? 0 : account.converted_balance / classification_total.to_d * 100
@@ -76,9 +82,13 @@ class BalanceSheet
family.currency
end
+ def syncing?
+ classification_groups.any? { |group| group.syncing? }
+ end
+
private
- ClassificationGroup = Struct.new(:key, :display_name, :icon, :total_money, :account_groups, keyword_init: true)
- AccountGroup = Struct.new(:key, :name, :accountable_type, :classification, :total, :total_money, :weight, :accounts, :color, :missing_rates?, keyword_init: true)
+ ClassificationGroup = Struct.new(:key, :display_name, :icon, :total_money, :account_groups, :syncing?, keyword_init: true)
+ AccountGroup = Struct.new(:key, :name, :accountable_type, :classification, :total, :total_money, :weight, :accounts, :color, :missing_rates?, :syncing?, keyword_init: true)
def active_accounts
family.accounts.active.with_attached_logo
@@ -87,9 +97,11 @@ class BalanceSheet
def totals_query
@totals_query ||= active_accounts
.joins(ActiveRecord::Base.sanitize_sql_array([ "LEFT JOIN exchange_rates ON exchange_rates.date = CURRENT_DATE AND accounts.currency = exchange_rates.from_currency AND exchange_rates.to_currency = ?", currency ]))
+ .joins("LEFT JOIN syncs ON syncs.syncable_id = accounts.id AND syncs.syncable_type = 'Account' AND (syncs.status = 'pending' OR syncs.status = 'syncing')")
.select(
"accounts.*",
"SUM(accounts.balance * COALESCE(exchange_rates.rate, 1)) as converted_balance",
+ "COUNT(syncs.id) > 0 as is_syncing",
ActiveRecord::Base.sanitize_sql_array([ "COUNT(CASE WHEN accounts.currency <> ? AND exchange_rates.rate IS NULL THEN 1 END) as missing_rates", currency ])
)
.group(:classification, :accountable_type, :id)
diff --git a/app/models/family.rb b/app/models/family.rb
index 0644776b..3383b0d4 100644
--- a/app/models/family.rb
+++ b/app/models/family.rb
@@ -39,15 +39,13 @@ class Family < ApplicationRecord
broadcast_remove target: "syncing-notice"
end
- # If family has any syncs pending/syncing within the last 10 minutes, we show a persistent "syncing" notice.
- # Ignore syncs older than 10 minutes as they are considered "stale"
+ # If any accounts or plaid items are syncing, the family is also syncing, even if a formal "Family Sync" is not running.
def syncing?
- Sync.where(
- "(syncable_type = 'Family' AND syncable_id = ?) OR
- (syncable_type = 'Account' AND syncable_id IN (SELECT id FROM accounts WHERE family_id = ? AND plaid_account_id IS NULL)) OR
- (syncable_type = 'PlaidItem' AND syncable_id IN (SELECT id FROM plaid_items WHERE family_id = ?))",
- id, id, id
- ).where(status: [ "pending", "syncing" ], created_at: 10.minutes.ago..).exists?
+ Sync.joins("LEFT JOIN plaid_items ON plaid_items.id = syncs.syncable_id AND syncs.syncable_type = 'PlaidItem'")
+ .joins("LEFT JOIN accounts ON accounts.id = syncs.syncable_id AND syncs.syncable_type = 'Account'")
+ .where("syncs.syncable_id = ? OR accounts.family_id = ? OR plaid_items.family_id = ?", id, id, id)
+ .incomplete
+ .exists?
end
def assigned_merchants
diff --git a/app/models/plaid_item.rb b/app/models/plaid_item.rb
index c2708f49..4aae91ca 100644
--- a/app/models/plaid_item.rb
+++ b/app/models/plaid_item.rb
@@ -52,6 +52,14 @@ class PlaidItem < ApplicationRecord
DestroyJob.perform_later(self)
end
+ def syncing?
+ Sync.joins("LEFT JOIN accounts a ON a.id = syncs.syncable_id AND syncs.syncable_type = 'Account'")
+ .joins("LEFT JOIN plaid_accounts pa ON pa.id = a.plaid_account_id")
+ .where("syncs.syncable_id = ? OR pa.plaid_item_id = ?", id, id)
+ .incomplete
+ .exists?
+ end
+
def auto_match_categories!
if family.categories.none?
family.categories.bootstrap!
diff --git a/app/models/subscription.rb b/app/models/subscription.rb
index 5f96361e..90b5303d 100644
--- a/app/models/subscription.rb
+++ b/app/models/subscription.rb
@@ -17,6 +17,7 @@ class Subscription < ApplicationRecord
validates :stripe_id, presence: true, if: :active?
validates :trial_ends_at, presence: true, if: :trialing?
+ validates :family_id, uniqueness: true
class << self
def new_trial_ends_at
diff --git a/app/models/sync.rb b/app/models/sync.rb
index 0ccd3533..65e47749 100644
--- a/app/models/sync.rb
+++ b/app/models/sync.rb
@@ -20,6 +20,8 @@ class Sync < ApplicationRecord
state :completed
state :failed
+ after_all_transitions :log_status_change
+
event :start, after_commit: :report_warnings do
transitions from: :pending, to: :syncing
end
@@ -41,14 +43,16 @@ class Sync < ApplicationRecord
end
def perform
- start!
+ Rails.logger.tagged("Sync", id, syncable_type, syncable_id) do
+ start!
- begin
- syncable.perform_sync(self)
- attempt_finalization
- rescue => e
- fail!
- handle_error(e)
+ begin
+ syncable.perform_sync(self)
+ attempt_finalization
+ rescue => e
+ fail!
+ handle_error(e)
+ end
end
end
@@ -68,6 +72,10 @@ class Sync < ApplicationRecord
end
private
+ def log_status_change
+ Rails.logger.info("changing from #{aasm.from_state} to #{aasm.to_state} (event: #{aasm.current_event})")
+ end
+
def has_failed_children?
children.failed.any?
end
diff --git a/app/views/accounts/_account.html.erb b/app/views/accounts/_account.html.erb
index 07ffd3d5..475f953e 100644
--- a/app/views/accounts/_account.html.erb
+++ b/app/views/accounts/_account.html.erb
@@ -30,9 +30,13 @@
<% end %>
-
">
- <%= format_money account.balance_money %>
-
+ <% if account.syncing? %>
+
+ <% else %>
+
">
+ <%= format_money account.balance_money %>
+
+ <% end %>
<% unless account.scheduled_for_deletion? %>
<%= styled_form_with model: account, data: { turbo_frame: "_top", controller: "auto-submit-form" } do |f| %>
diff --git a/app/views/accounts/_account_sidebar_tabs.html.erb b/app/views/accounts/_account_sidebar_tabs.html.erb
index 9ca2781c..d8841a44 100644
--- a/app/views/accounts/_account_sidebar_tabs.html.erb
+++ b/app/views/accounts/_account_sidebar_tabs.html.erb
@@ -40,7 +40,6 @@
full_width: true,
class: "justify-start"
) %>
-
<% family.balance_sheet.account_groups("asset").each do |group| %>
<%= render "accounts/accountable_group", account_group: group %>
@@ -60,7 +59,6 @@
full_width: true,
class: "justify-start"
) %>
-
<% family.balance_sheet.account_groups("liability").each do |group| %>
<%= render "accounts/accountable_group", account_group: group %>
@@ -80,7 +78,6 @@
frame: :modal,
class: "justify-start"
) %>
-
<% family.balance_sheet.account_groups.each do |group| %>
<%= render "accounts/accountable_group", account_group: group %>
diff --git a/app/views/accounts/_accountable_group.html.erb b/app/views/accounts/_accountable_group.html.erb
index ab10dec7..10ce632c 100644
--- a/app/views/accounts/_accountable_group.html.erb
+++ b/app/views/accounts/_accountable_group.html.erb
@@ -3,12 +3,20 @@
<%= render DisclosureComponent.new(title: account_group.name, align: :left, open: account_group.accounts.any? { |account| page_active?(account_path(account)) }) do |disclosure| %>
<% disclosure.with_summary_content do %>
- <%= tag.p format_money(account_group.total_money), class: "text-sm font-medium text-primary" %>
-
- <%= turbo_frame_tag "#{account_group.key}_sparkline", src: accountable_sparkline_path(account_group.key), loading: "lazy" do %>
-
-
+ <% if account_group.syncing? %>
+
+ <% else %>
+ <%= tag.p format_money(account_group.total_money), class: "text-sm font-medium text-primary" %>
+ <%= turbo_frame_tag "#{account_group.key}_sparkline", src: accountable_sparkline_path(account_group.key), loading: "lazy" do %>
+
+ <% end %>
<% end %>
<% end %>
@@ -29,15 +37,26 @@
<%= tag.p account.short_subtype_label, class: "text-sm text-secondary truncate" %>
-
- <%= tag.p format_money(account.balance_money), class: "text-sm font-medium text-primary whitespace-nowrap" %>
-
- <%= turbo_frame_tag dom_id(account, :sparkline), src: sparkline_account_path(account), loading: "lazy" do %>
-
-
+ <% if account_group.syncing? %>
+
+
+ <% else %>
+
+ <%= tag.p format_money(account.balance_money), class: "text-sm font-medium text-primary whitespace-nowrap" %>
+
+ <%= turbo_frame_tag dom_id(account, :sparkline), src: sparkline_account_path(account), loading: "lazy" do %>
+
+ <% end %>
+
+ <% end %>
<% end %>
<% end %>
diff --git a/app/views/accounts/_chart_loader.html.erb b/app/views/accounts/_chart_loader.html.erb
index f6a9c852..a5e72097 100644
--- a/app/views/accounts/_chart_loader.html.erb
+++ b/app/views/accounts/_chart_loader.html.erb
@@ -1,5 +1,7 @@
-
+
-
-
Loading...
+
+
diff --git a/app/views/accounts/chart.html.erb b/app/views/accounts/chart.html.erb
index 11dcbaac..b5ad27a1 100644
--- a/app/views/accounts/chart.html.erb
+++ b/app/views/accounts/chart.html.erb
@@ -2,21 +2,25 @@
<% trend = series.trend %>
<%= turbo_frame_tag dom_id(@account, :chart_details) do %>
-
- <%= render partial: "shared/trend_change", locals: { trend: trend, comparison_label: @period.comparison_label } %>
-
+ <% if @account.syncing?%>
+ <%= render "accounts/chart_loader" %>
+ <% else %>
+
+ <%= render partial: "shared/trend_change", locals: { trend: trend, comparison_label: @period.comparison_label } %>
+
-
- <% if series.any? %>
-
+ <% if series.any? %>
+
- <% else %>
-
-
<%= t(".data_not_available") %>
-
- <% end %>
-
+ <% else %>
+
+
<%= t(".data_not_available") %>
+
+ <% end %>
+
+ <% end %>
<% end %>
diff --git a/app/views/accounts/index.html.erb b/app/views/accounts/index.html.erb
index b4c78332..35e647ea 100644
--- a/app/views/accounts/index.html.erb
+++ b/app/views/accounts/index.html.erb
@@ -2,17 +2,6 @@
<%= t(".accounts") %>
- <% if Rails.env.development? %>
- <%= render ButtonComponent.new(
- text: "Sync all",
- href: sync_all_accounts_path,
- method: :post,
- variant: "outline",
- disabled: Current.family.syncing?,
- icon: "refresh-cw",
- ) %>
- <% end %>
-
<%= render LinkComponent.new(
text: "New account",
href: new_account_path(return_to: accounts_path),
diff --git a/app/views/accounts/index/_account_groups.erb b/app/views/accounts/index/_account_groups.erb
index 6347339c..51ba4d8b 100644
--- a/app/views/accounts/index/_account_groups.erb
+++ b/app/views/accounts/index/_account_groups.erb
@@ -6,9 +6,12 @@
<%= Accountable.from_type(group).display_name %>
·
<%= accounts.count %>
-
<%= totals_by_currency(collection: accounts, money_method: :balance_money) %>
+
+ <% unless accounts.any?(&:syncing?) %>
+
<%= totals_by_currency(collection: accounts, money_method: :balance_money) %>
+ <% end %>
-
+
<% accounts.each do |account| %>
<%= render account %>
<% end %>
diff --git a/app/views/accounts/show/_chart.html.erb b/app/views/accounts/show/_chart.html.erb
index 08e67e84..cc6c4f79 100644
--- a/app/views/accounts/show/_chart.html.erb
+++ b/app/views/accounts/show/_chart.html.erb
@@ -3,15 +3,22 @@
<% period = @period || Period.last_30_days %>
<% default_value_title = account.asset? ? t(".balance") : t(".owed") %>
-
+
<%= tag.p title || default_value_title, class: "text-sm font-medium text-secondary" %>
- <%= tooltip %>
+
+ <% unless account.syncing? %>
+ <%= tooltip %>
+ <% end %>
- <%= tag.p format_money(account.balance_money), class: "text-primary text-3xl font-medium truncate" %>
+ <% if account.syncing? %>
+
+ <% else %>
+ <%= tag.p format_money(account.balance_money), class: "text-primary text-3xl font-medium truncate" %>
+ <% end %>
<%= form_with url: request.path, method: :get, data: { controller: "auto-submit-form" } do |form| %>
diff --git a/app/views/holdings/_cash.html.erb b/app/views/holdings/_cash.html.erb
index a279d9ab..702f40d0 100644
--- a/app/views/holdings/_cash.html.erb
+++ b/app/views/holdings/_cash.html.erb
@@ -19,8 +19,13 @@
<% cash_weight = account.balance.zero? ? 0 : account.cash_balance / account.balance * 100 %>
- <%= render "shared/progress_circle", progress: cash_weight %>
- <%= tag.p number_to_percentage(cash_weight, precision: 1) %>
+
+ <% if account.syncing? %>
+
+ <% else %>
+ <%= render "shared/progress_circle", progress: cash_weight %>
+ <%= tag.p number_to_percentage(cash_weight, precision: 1) %>
+ <% end %>
@@ -28,7 +33,13 @@
- <%= tag.p format_money account.cash_balance_money %>
+ <% if account.syncing? %>
+
+ <% else %>
+ <%= tag.p format_money account.cash_balance_money %>
+ <% end %>
diff --git a/app/views/holdings/_holding.html.erb b/app/views/holdings/_holding.html.erb
index 5fe0e4c9..c8a2ac59 100644
--- a/app/views/holdings/_holding.html.erb
+++ b/app/views/holdings/_holding.html.erb
@@ -17,7 +17,9 @@
- <% if holding.weight %>
+ <% if holding.account.syncing? %>
+
+ <% elsif holding.weight %>
<%= render "shared/progress_circle", progress: holding.weight %>
<%= tag.p number_to_percentage(holding.weight, precision: 1) %>
<% else %>
@@ -26,21 +28,39 @@
- <%= tag.p format_money holding.avg_cost %>
- <%= tag.p t(".per_share"), class: "font-normal text-secondary" %>
-
-
-
- <% if holding.amount_money %>
- <%= tag.p format_money holding.amount_money %>
+ <% if holding.account.syncing? %>
+
<% else %>
- <%= tag.p "--", class: "text-secondary" %>
+ <%= tag.p format_money holding.avg_cost %>
+ <%= tag.p t(".per_share"), class: "font-normal text-secondary" %>
<% end %>
- <%= tag.p t(".shares", qty: number_with_precision(holding.qty, precision: 1)), class: "font-normal text-secondary" %>
- <% if holding.trend %>
+ <% if holding.account.syncing? %>
+
+ <% else %>
+ <% if holding.amount_money %>
+ <%= tag.p format_money holding.amount_money %>
+ <% else %>
+ <%= tag.p "--", class: "text-secondary" %>
+ <% end %>
+ <%= tag.p t(".shares", qty: number_with_precision(holding.qty, precision: 1)), class: "font-normal text-secondary" %>
+ <% end %>
+
+
+
+ <% if holding.account.syncing? %>
+
+ <% elsif holding.trend %>
<%= tag.p format_money(holding.trend.value), style: "color: #{holding.trend.color};" %>
<%= tag.p "(#{number_to_percentage(holding.trend.percent, precision: 1)})", style: "color: #{holding.trend.color};" %>
<% else %>
diff --git a/app/views/layouts/shared/_htmldoc.html.erb b/app/views/layouts/shared/_htmldoc.html.erb
index fadcd396..30887f7b 100644
--- a/app/views/layouts/shared/_htmldoc.html.erb
+++ b/app/views/layouts/shared/_htmldoc.html.erb
@@ -23,10 +23,6 @@
<%= render_flash_notifications %>
-
- <% if Current.family&.syncing? %>
- <%= render "shared/notifications/loading", id: "syncing-notice", message: "Syncing accounts data..." %>
- <% end %>
diff --git a/app/views/pages/dashboard.html.erb b/app/views/pages/dashboard.html.erb
index 569caaa2..79d18d4b 100644
--- a/app/views/pages/dashboard.html.erb
+++ b/app/views/pages/dashboard.html.erb
@@ -25,11 +25,14 @@
<% if Current.family.accounts.any? %>
-
- <%= render partial: "pages/dashboard/net_worth_chart", locals: { series: @balance_sheet.net_worth_series(period: @period), period: @period } %>
+
+ <%= render partial: "pages/dashboard/net_worth_chart", locals: {
+ balance_sheet: @balance_sheet,
+ period: @period
+ } %>
<% else %>
-
+
<%= render "pages/dashboard/no_accounts_graph_placeholder" %>
<% end %>
diff --git a/app/views/pages/dashboard/_balance_sheet.html.erb b/app/views/pages/dashboard/_balance_sheet.html.erb
index 302ae103..196d96ae 100644
--- a/app/views/pages/dashboard/_balance_sheet.html.erb
+++ b/app/views/pages/dashboard/_balance_sheet.html.erb
@@ -1,6 +1,6 @@
<%# locals: (balance_sheet:) %>
-
+
<% balance_sheet.classification_groups.each do |classification_group| %>
@@ -11,26 +11,38 @@
<% if classification_group.account_groups.any? %>
·
- <%= classification_group.total_money.format(precision: 0) %>
+ <% if classification_group.syncing? %>
+
+ <% else %>
+ <%= classification_group.total_money.format(precision: 0) %>
+ <% end %>
<% end %>
<% if classification_group.account_groups.any? %>
+
<% classification_group.account_groups.each do |account_group| %>
<% end %>
-
- <% classification_group.account_groups.each do |account_group| %>
-
-
-
<%= account_group.name %>
-
<%= number_to_percentage(account_group.weight, precision: 0) %>
-
- <% end %>
-
+
+ <% if classification_group.syncing? %>
+
Calculating latest balance data...
+ <% else %>
+
+ <% classification_group.account_groups.each do |account_group| %>
+
+
+
<%= account_group.name %>
+
<%= number_to_percentage(account_group.weight, precision: 0) %>
+
+ <% end %>
+
+ <% end%>
@@ -56,15 +68,27 @@
<%= account_group.name %>
-
-
- <%= render "pages/dashboard/group_weight", weight: account_group.weight, color: account_group.color %>
-
+ <% if account_group.syncing? %>
+
+
-
-
<%= format_money(account_group.total_money) %>
+
-
+ <% else %>
+
+
+ <%= render "pages/dashboard/group_weight", weight: account_group.weight, color: account_group.color %>
+
+
+
+
<%= format_money(account_group.total_money) %>
+
+
+ <% end %>
@@ -76,15 +100,27 @@
<%= link_to account.name, account_path(account) %>
-
-
- <%= render "pages/dashboard/group_weight", weight: account.weight, color: account_group.color %>
-
+ <% if account.syncing? %>
+
+
-
-
<%= format_money(account.balance_money) %>
+
-
+ <% else %>
+
+
+ <%= render "pages/dashboard/group_weight", weight: account.weight, color: account_group.color %>
+
+
+
+
<%= format_money(account.balance_money) %>
+
+
+ <% end %>
<% if idx < account_group.accounts.size - 1 %>
diff --git a/app/views/pages/dashboard/_net_worth_chart.html.erb b/app/views/pages/dashboard/_net_worth_chart.html.erb
index a8870cac..4d1e443a 100644
--- a/app/views/pages/dashboard/_net_worth_chart.html.erb
+++ b/app/views/pages/dashboard/_net_worth_chart.html.erb
@@ -1,16 +1,27 @@
-<%# locals: (series:, period:) %>
+<%# locals: (balance_sheet:, period:) %>
+
+<% series = balance_sheet.net_worth_series(period: period) %>
<%= t(".title") %>
-
- <%= series.current.format %>
-
- <% if series.trend.nil? %>
-
<%= t(".data_not_available") %>
+
+ <% if balance_sheet.syncing? %>
+
<% else %>
- <%= render partial: "shared/trend_change", locals: { trend: series.trend, comparison_label: period.comparison_label } %>
+
+ <%= series.current.format %>
+
+
+ <% if series.trend.nil? %>
+
<%= t(".data_not_available") %>
+ <% else %>
+ <%= render partial: "shared/trend_change", locals: { trend: series.trend, comparison_label: period.comparison_label } %>
+ <% end %>
<% end %>
@@ -24,14 +35,20 @@
<% end %>
-<% if series.any? %>
-
+
+<% else %>
+ <% if series.any? %>
+
-<% else %>
-
-
<%= t(".data_not_available") %>
-
+ <% else %>
+
+
<%= t(".data_not_available") %>
+
+ <% end %>
<% end %>
diff --git a/app/views/shared/notifications/_loading.html.erb b/app/views/shared/notifications/_loading.html.erb
deleted file mode 100644
index 18eabd89..00000000
--- a/app/views/shared/notifications/_loading.html.erb
+++ /dev/null
@@ -1,9 +0,0 @@
-<%# locals: (message:, id: nil) %>
-
-<%= tag.div id: id, class: "flex gap-3 rounded-lg bg-container p-4 group w-full md:max-w-80 shadow-border-xs" do %>
-
- <%= icon "loader", class: "animate-pulse" %>
-
-
- <%= tag.p message, class: "text-primary text-sm font-medium" %>
-<% end %>
diff --git a/app/views/transactions/index.html.erb b/app/views/transactions/index.html.erb
index 9781672f..52bb9c3d 100644
--- a/app/views/transactions/index.html.erb
+++ b/app/views/transactions/index.html.erb
@@ -4,9 +4,6 @@
<%= render MenuComponent.new do |menu| %>
- <% if Rails.env.development? %>
- <% menu.with_item(variant: "button", text: "Dev only: Sync all", href: sync_all_accounts_path, method: :post, icon: "refresh-cw") %>
- <% end %>
<% menu.with_item(variant: "link", text: "New rule", href: new_rule_path(resource_type: "transaction"), icon: "plus", data: { turbo_frame: :modal }) %>
<% menu.with_item(variant: "link", text: "Edit rules", href: rules_path, icon: "git-branch", data: { turbo_frame: :_top }) %>
<% menu.with_item(variant: "link", text: "Edit categories", href: categories_path, icon: "shapes", data: { turbo_frame: :_top }) %>
diff --git a/config/initializers/mini_profiler.rb b/config/initializers/mini_profiler.rb
index d304a50e..69391c80 100644
--- a/config/initializers/mini_profiler.rb
+++ b/config/initializers/mini_profiler.rb
@@ -1,3 +1,4 @@
Rails.application.configure do
Rack::MiniProfiler.config.skip_paths = [ "/design-system" ]
+ Rack::MiniProfiler.config.max_traces_to_show = 30
end
diff --git a/config/routes.rb b/config/routes.rb
index e44265f6..132fa06a 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -105,10 +105,6 @@ Rails.application.routes.draw do
end
resources :accounts, only: %i[index new], shallow: true do
- collection do
- post :sync_all
- end
-
member do
post :sync
get :chart
diff --git a/db/schema.rb b/db/schema.rb
index 1f14a5c9..c2fa7e20 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema[7.2].define(version: 2025_05_12_171654) do
+ActiveRecord::Schema[7.2].define(version: 2025_05_13_122703) do
# These are extensions that must be enabled in order to support this database
enable_extension "pgcrypto"
enable_extension "plpgsql"
diff --git a/test/controllers/accounts_controller_test.rb b/test/controllers/accounts_controller_test.rb
index d85a5ffa..a3d827e8 100644
--- a/test/controllers/accounts_controller_test.rb
+++ b/test/controllers/accounts_controller_test.rb
@@ -15,9 +15,4 @@ class AccountsControllerTest < ActionDispatch::IntegrationTest
post sync_account_path(@account)
assert_redirected_to account_path(@account)
end
-
- test "can sync all accounts" do
- post sync_all_accounts_path
- assert_redirected_to accounts_path
- end
end
diff --git a/test/controllers/subscriptions_controller_test.rb b/test/controllers/subscriptions_controller_test.rb
index 1e791632..0b406dca 100644
--- a/test/controllers/subscriptions_controller_test.rb
+++ b/test/controllers/subscriptions_controller_test.rb
@@ -32,8 +32,6 @@ class SubscriptionsControllerTest < ActionDispatch::IntegrationTest
end
test "users who have already trialed cannot create a new subscription" do
- @family.start_trial_subscription!
-
assert_no_difference "Subscription.count" do
post subscription_path
end
diff --git a/test/models/subscription_test.rb b/test/models/subscription_test.rb
index 3986335c..389aaaf0 100644
--- a/test/models/subscription_test.rb
+++ b/test/models/subscription_test.rb
@@ -2,7 +2,7 @@ require "test_helper"
class SubscriptionTest < ActiveSupport::TestCase
setup do
- @family = families(:empty)
+ @family = Family.create!(name: "Test Family")
end
test "can create subscription without stripe details if trial" do