From 25eb18d9afe40973caea86b9d80dcc1a9733b5fc Mon Sep 17 00:00:00 2001 From: Guillem Arias Date: Mon, 18 May 2026 22:19:44 +0200 Subject: [PATCH] feat(goals): pending-pledge dot on card avatar Goals with open + unexpired pledges now carry a small amber DS::Pill dot at the top-right of the avatar on the index card. Same primitive + position pattern as the beta gate dot on the sidebar nav, so the 'small marker' affordance reads consistently across the app. Pledges are preloaded via the existing .includes(:open_pledges, ...) on the index query, so the indicator is free at request time. --- app/components/goals/card_component.html.erb | 11 +++++++++-- app/components/goals/card_component.rb | 7 +++++++ config/locales/views/goals/en.yml | 1 + 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/app/components/goals/card_component.html.erb b/app/components/goals/card_component.html.erb index b3a521654..5eb6416a3 100644 --- a/app/components/goals/card_component.html.erb +++ b/app/components/goals/card_component.html.erb @@ -1,9 +1,16 @@
" - <% if filterable %>data-goals-filter-target="card"<% end %> + <% if filterable %> data-goals-filter-target="card"<% end %> data-goal-name="<%= goal.name %>" data-goal-status="<%= goal.display_status %>">
- <%= render Goals::AvatarComponent.new(goal: goal, size: "lg") %> +
+ <%= render Goals::AvatarComponent.new(goal: goal, size: "lg") %> + <% if has_pending_pledge? %> + + <%= render DS::Pill.new(tone: :amber, size: :md, dot_only: true, title: t("goals.goal_card.pending_pledge")) %> + + <% end %> +

diff --git a/app/components/goals/card_component.rb b/app/components/goals/card_component.rb index 6ca6a32a5..ac49079a0 100644 --- a/app/components/goals/card_component.rb +++ b/app/components/goals/card_component.rb @@ -26,6 +26,13 @@ class Goals::CardComponent < ApplicationComponent @linked_accounts ||= goal.linked_accounts.to_a end + # Open + unexpired pledges are preloaded on the index via the + # `.includes(:open_pledges, ...)` chain in GoalsController#index, so + # this is a hit on the in-memory association — no N+1. + def has_pending_pledge? + goal.open_pledges.any? + end + def linked_accounts_count_label I18n.t("goals.goal_card.accounts", count: linked_accounts.size) end diff --git a/config/locales/views/goals/en.yml b/config/locales/views/goals/en.yml index 4ec1382e2..fa6e9698f 100644 --- a/config/locales/views/goals/en.yml +++ b/config/locales/views/goals/en.yml @@ -233,6 +233,7 @@ en: footer_reached: Goal reached footer_catch_up: "Save %{amount}/mo to catch up" footer_no_deadline: Open + pending_pledge: Pending pledge footer_no_pledges: No matched pledges yet footer_last_today: Last pledge matched today footer_last_days: