1
0
Fork 0
mirror of https://github.com/maybe-finance/maybe.git synced 2025-08-09 07:25:19 +02:00

Add sync states throughout app

This commit is contained in:
Zach Gollwitzer 2025-05-13 15:49:02 -04:00
parent 3cab7164d3
commit 9d8d6bd86b
34 changed files with 295 additions and 166 deletions

View file

@ -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 @@
}
}

View file

@ -85,3 +85,7 @@
background-color: var(--color-alpha-black-900);
}
}
@utility bg-loader {
@apply bg-surface-inset animate-pulse;
}

View file

@ -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);

View file

@ -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

View file

@ -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

View file

@ -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 } } }

View file

@ -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?

View file

@ -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)

View file

@ -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

View file

@ -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!

View file

@ -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

View file

@ -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,6 +43,7 @@ class Sync < ApplicationRecord
end
def perform
Rails.logger.tagged("Sync", id, syncable_type, syncable_id) do
start!
begin
@ -51,6 +54,7 @@ class Sync < ApplicationRecord
handle_error(e)
end
end
end
# If the sync doesn't have any in-progress children, finalize it.
def attempt_finalization
@ -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

View file

@ -30,9 +30,13 @@
<% end %>
</div>
<div class="flex items-center gap-8">
<% if account.syncing? %>
<div class="w-16 h-6 bg-loader rounded-full animate-pulse"></div>
<% else %>
<p class="text-sm font-medium <%= account.is_active ? "text-primary" : "text-subdued" %>">
<%= format_money account.balance_money %>
</p>
<% end %>
<% unless account.scheduled_for_deletion? %>
<%= styled_form_with model: account, data: { turbo_frame: "_top", controller: "auto-submit-form" } do |f| %>

View file

@ -40,7 +40,6 @@
full_width: true,
class: "justify-start"
) %>
<div>
<% 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"
) %>
<div>
<% 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"
) %>
<div>
<% family.balance_sheet.account_groups.each do |group| %>
<%= render "accounts/accountable_group", account_group: group %>

View file

@ -3,13 +3,21 @@
<%= 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 %>
<div class="ml-auto text-right grow">
<% if account_group.syncing? %>
<div class="space-y-1">
<div class="h-5 w-24 rounded ml-auto bg-loader"></div>
<div class="flex items-center w-8 h-4 ml-auto">
<div class="w-6 h-px bg-loader"></div>
</div>
</div>
<% 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 %>
<div class="flex items-center w-8 h-4 ml-auto">
<div class="w-6 h-px bg-surface-inset"></div>
<div class="w-6 h-px bg-loader"></div>
</div>
<% end %>
<% end %>
</div>
<% end %>
@ -29,17 +37,28 @@
<%= tag.p account.short_subtype_label, class: "text-sm text-secondary truncate" %>
</div>
<% if account_group.syncing? %>
<div class="ml-auto text-right grow h-10">
<div class="space-y-1">
<div class="h-5 w-24 bg-loader rounded ml-auto"></div>
<div class="flex items-center w-8 h-4 ml-auto">
<div class="w-6 h-px bg-loader"></div>
</div>
</div>
</div>
<% else %>
<div class="ml-auto text-right grow h-10">
<%= 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 %>
<div class="flex items-center w-8 h-5 ml-auto">
<div class="w-6 h-px bg-surface-inset"></div>
<div class="flex items-center w-8 h-4 ml-auto">
<div class="w-6 h-px bg-loader"></div>
</div>
<% end %>
</div>
<% end %>
<% end %>
<% end %>
</div>
<div class="my-2">

View file

@ -1,5 +1,7 @@
<div class="h-10">
<div class="px-4">
<div class="bg-loader rounded-md h-5 w-32"></div>
</div>
<div class="h-64 flex items-center justify-center">
<p class="text-secondary animate-pulse text-sm">Loading...</p>
<div class="p-4 h-52 flex items-center justify-center">
<div class="bg-loader rounded-md h-full w-full"></div>
</div>

View file

@ -2,6 +2,9 @@
<% trend = series.trend %>
<%= turbo_frame_tag dom_id(@account, :chart_details) do %>
<% if @account.syncing?%>
<%= render "accounts/chart_loader" %>
<% else %>
<div class="px-4">
<%= render partial: "shared/trend_change", locals: { trend: trend, comparison_label: @period.comparison_label } %>
</div>
@ -20,3 +23,4 @@
<% end %>
</div>
<% end %>
<% end %>

View file

@ -2,17 +2,6 @@
<h1 class="text-xl"><%= t(".accounts") %></h1>
<div class="flex items-center gap-5">
<div class="flex items-center gap-2">
<% 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),

View file

@ -6,9 +6,12 @@
<p><%= Accountable.from_type(group).display_name %></p>
<span class="text-subdued mx-2">&middot;</span>
<p><%= accounts.count %></p>
<% unless accounts.any?(&:syncing?) %>
<p class="ml-auto"><%= totals_by_currency(collection: accounts, money_method: :balance_money) %></p>
<% end %>
</div>
<div class="bg-container">
<div class="bg-container rounded-md">
<% accounts.each do |account| %>
<%= render account %>
<% end %>

View file

@ -3,15 +3,22 @@
<% period = @period || Period.last_30_days %>
<% default_value_title = account.asset? ? t(".balance") : t(".owed") %>
<div id="<%= dom_id(account, :chart) %>" class="bg-container shadow-xs rounded-xl border border-alpha-black-25 rounded-lg space-y-2">
<div id="<%= dom_id(account, :chart) %>" class="bg-container shadow-border-xs rounded-xl space-y-2">
<div class="flex justify-between flex-col-reverse lg:flex-row gap-2 px-4 pt-4 mb-2">
<div class="space-y-2 w-full">
<div class="flex items-center gap-1">
<%= tag.p title || default_value_title, class: "text-sm font-medium text-secondary" %>
<% unless account.syncing? %>
<%= tooltip %>
<% end %>
</div>
<% if account.syncing? %>
<div class="bg-loader rounded-md h-7 w-20"></div>
<% else %>
<%= tag.p format_money(account.balance_money), class: "text-primary text-3xl font-medium truncate" %>
<% end %>
</div>
<%= form_with url: request.path, method: :get, data: { controller: "auto-submit-form" } do |form| %>

View file

@ -19,8 +19,13 @@
<div class="col-span-2 flex justify-end items-center gap-2">
<% cash_weight = account.balance.zero? ? 0 : account.cash_balance / account.balance * 100 %>
<% if account.syncing? %>
<div class="w-16 h-6 bg-loader rounded-full"></div>
<% else %>
<%= render "shared/progress_circle", progress: cash_weight %>
<%= tag.p number_to_percentage(cash_weight, precision: 1) %>
<% end %>
</div>
<div class="col-span-2 text-right">
@ -28,7 +33,13 @@
</div>
<div class="col-span-2 text-right">
<% if account.syncing? %>
<div class="flex justify-end">
<div class="w-16 h-6 bg-loader rounded-full"></div>
</div>
<% else %>
<%= tag.p format_money account.cash_balance_money %>
<% end %>
</div>
<div class="col-span-2 text-right">

View file

@ -17,7 +17,9 @@
</div>
<div class="col-span-2 flex justify-end items-center gap-2">
<% if holding.weight %>
<% if holding.account.syncing? %>
<div class="w-16 h-6 bg-loader rounded-full"></div>
<% elsif holding.weight %>
<%= render "shared/progress_circle", progress: holding.weight %>
<%= tag.p number_to_percentage(holding.weight, precision: 1) %>
<% else %>
@ -26,21 +28,39 @@
</div>
<div class="col-span-2 text-right">
<% if holding.account.syncing? %>
<div class="flex justify-end">
<div class="w-16 h-6 bg-loader rounded-full"></div>
</div>
<% else %>
<%= tag.p format_money holding.avg_cost %>
<%= tag.p t(".per_share"), class: "font-normal text-secondary" %>
<% end %>
</div>
<div class="col-span-2 text-right">
<% if holding.account.syncing? %>
<div class="flex flex-col gap-2 items-end">
<div class="w-16 h-4 bg-loader rounded-full"></div>
<div class="w-16 h-2 bg-loader rounded-full"></div>
</div>
<% 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 %>
</div>
<div class="col-span-2 text-right">
<% if holding.trend %>
<% if holding.account.syncing? %>
<div class="flex flex-col gap-2 items-end">
<div class="w-16 h-4 bg-loader rounded-full"></div>
<div class="w-16 h-2 bg-loader rounded-full"></div>
</div>
<% 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 %>

View file

@ -23,10 +23,6 @@
<%= render_flash_notifications %>
<div id="cta"></div>
<% if Current.family&.syncing? %>
<%= render "shared/notifications/loading", id: "syncing-notice", message: "Syncing accounts data..." %>
<% end %>
</div>
</div>

View file

@ -25,11 +25,14 @@
<div class="w-full space-y-6 pb-24">
<% if Current.family.accounts.any? %>
<section class="bg-container py-4 rounded-xl shadow-border-xs px-0.5">
<%= render partial: "pages/dashboard/net_worth_chart", locals: { series: @balance_sheet.net_worth_series(period: @period), period: @period } %>
<section class="bg-container py-4 rounded-xl shadow-border-xs">
<%= render partial: "pages/dashboard/net_worth_chart", locals: {
balance_sheet: @balance_sheet,
period: @period
} %>
</section>
<% else %>
<section class="p-0.5">
<section>
<%= render "pages/dashboard/no_accounts_graph_placeholder" %>
</section>
<% end %>

View file

@ -1,6 +1,6 @@
<%# locals: (balance_sheet:) %>
<div class="space-y-4 overflow-x-auto p-0.5">
<div class="space-y-4">
<% balance_sheet.classification_groups.each do |classification_group| %>
<div class="bg-container shadow-border-xs rounded-xl space-y-4 p-4">
<h2 class="text-lg font-medium inline-flex items-center gap-1.5">
@ -11,17 +11,28 @@
<% if classification_group.account_groups.any? %>
<span class="text-secondary">&middot;</span>
<% if classification_group.syncing? %>
<div class="flex items-center w-8 h-4 ml-auto">
<div class="bg-loader w-full h-full rounded-md"></div>
</div>
<% else %>
<span class="text-secondary font-medium text-lg"><%= classification_group.total_money.format(precision: 0) %></span>
<% end %>
<% end %>
</h2>
<% if classification_group.account_groups.any? %>
<div class="space-y-4">
<div class="flex gap-1">
<% classification_group.account_groups.each do |account_group| %>
<div class="h-1.5 rounded-sm" style="width: <%= account_group.weight %>%; background-color: <%= account_group.color %>;"></div>
<% end %>
</div>
<% if classification_group.syncing? %>
<p class="text-xs text-subdued animate-pulse">Calculating latest balance data...</p>
<% else %>
<div class="flex flex-wrap gap-4">
<% classification_group.account_groups.each do |account_group| %>
<div class="flex items-center gap-2 text-sm">
@ -31,6 +42,7 @@
</div>
<% end %>
</div>
<% end%>
</div>
<div class="bg-surface rounded-xl p-1 space-y-1 overflow-x-auto">
@ -56,6 +68,17 @@
<p><%= account_group.name %></p>
</div>
<% if account_group.syncing? %>
<div class="flex items-center justify-between text-right gap-6">
<div class="w-28 shrink-0 flex items-center justify-end gap-2">
<div class="bg-loader rounded-md h-4 w-12"></div>
</div>
<div class="w-40 shrink-0 flex justify-end">
<div class="bg-loader rounded-md h-4 w-12"></div>
</div>
</div>
<% else %>
<div class="flex items-center justify-between text-right gap-6">
<div class="w-28 shrink-0 flex items-center justify-end gap-2">
<%= render "pages/dashboard/group_weight", weight: account_group.weight, color: account_group.color %>
@ -65,6 +88,7 @@
<p><%= format_money(account_group.total_money) %></p>
</div>
</div>
<% end %>
</summary>
<div>
@ -76,6 +100,17 @@
<%= link_to account.name, account_path(account) %>
</div>
<% if account.syncing? %>
<div class="ml-auto flex items-center text-right gap-6">
<div class="w-28 shrink-0 flex items-center justify-end gap-2">
<div class="bg-loader rounded-md h-4 w-12"></div>
</div>
<div class="w-40 shrink-0 flex justify-end">
<div class="bg-loader rounded-md h-4 w-12"></div>
</div>
</div>
<% else %>
<div class="ml-auto flex items-center text-right gap-6">
<div class="w-28 shrink-0 flex items-center justify-end gap-2">
<%= render "pages/dashboard/group_weight", weight: account.weight, color: account_group.color %>
@ -85,6 +120,7 @@
<p><%= format_money(account.balance_money) %></p>
</div>
</div>
<% end %>
</div>
<% if idx < account_group.accounts.size - 1 %>

View file

@ -1,17 +1,28 @@
<%# locals: (series:, period:) %>
<%# locals: (balance_sheet:, period:) %>
<% series = balance_sheet.net_worth_series(period: period) %>
<div class="flex justify-between gap-4 px-4">
<div class="space-y-2">
<div class="space-y-2">
<p class="text-sm text-secondary font-medium"><%= t(".title") %></p>
<% if balance_sheet.syncing? %>
<div class="flex flex-col gap-2">
<div class="bg-loader rounded-md h-7 w-20"></div>
<div class="bg-loader rounded-md h-5 w-32"></div>
</div>
<% else %>
<p class="text-primary -space-x-0.5 text-3xl font-medium">
<%= series.current.format %>
</p>
<% if series.trend.nil? %>
<p class="text-sm text-secondary"><%= t(".data_not_available") %></p>
<% else %>
<%= render partial: "shared/trend_change", locals: { trend: series.trend, comparison_label: period.comparison_label } %>
<% end %>
<% end %>
</div>
</div>
@ -24,6 +35,11 @@
<% end %>
</div>
<% if balance_sheet.syncing? %>
<div class="w-full flex items-center justify-center p-4 h-52">
<div class="bg-loader rounded-md h-full w-full"></div>
</div>
<% else %>
<% if series.any? %>
<div
id="netWorthChart"
@ -35,3 +51,4 @@
<p class="text-secondary text-sm"><%= t(".data_not_available") %></p>
</div>
<% end %>
<% end %>

View file

@ -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 %>
<div class="h-5 w-5 shrink-0 p-px text-primary">
<%= icon "loader", class: "animate-pulse" %>
</div>
<%= tag.p message, class: "text-primary text-sm font-medium" %>
<% end %>

View file

@ -4,9 +4,6 @@
<div class="flex items-center gap-5">
<div class="flex items-center gap-2">
<%= 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 }) %>

View file

@ -1,3 +1,4 @@
Rails.application.configure do
Rack::MiniProfiler.config.skip_paths = [ "/design-system" ]
Rack::MiniProfiler.config.max_traces_to_show = 30
end

View file

@ -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

2
db/schema.rb generated
View file

@ -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"

View file

@ -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

View file

@ -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

View file

@ -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