From 4be2ca2eeb5d3156345f8d2c6ca4effc2ae89ab1 Mon Sep 17 00:00:00 2001 From: Guillem Arias Date: Mon, 11 May 2026 20:22:45 +0200 Subject: [PATCH] ux(goals/show): catch-up consolidation + confirm dialogs on archive / mark complete MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A — Catch-up math triple-encoding. The catch-up amount (e.g. $1,531/mo) was rendered verbatim in three places: the banner title, the projection card subtitle, and the pace stat ("target $X/mo"). Only new information anywhere was the buried "Behind by $Y/mo" delta in pace-card subtext. - Banner body now carries the delta: "...currently $Y/mo behind." - Projection sentence drops the "Bump to %{required}/mo" restatement; reduces to "At %{current}/mo you'll miss your target date." Chart aria-description benefits from the simpler phrasing too. - Pace stat drops the "· target $X/mo" sub-line. Pair becomes "$avg/mo" + "Behind by $delta/mo" — same delta now in the banner, surfaced twice intentionally (alert vs at-a-glance stat). K — Destructive transition confirms. Pause / Resume stay no-confirm (recoverable). Mark complete (irreversible via UI; no may_uncomplete? event exists) and Archive (goal disappears from active list) now wear CustomConfirm. New locale keys: goals.show.confirm_{complete,archive}_ {title,body,cta}. Locale catch_up body strings now interpolate %{delta} alongside %{date}; projection.behind drops %{required}. Controller#projection_summary still passes both keys — extras are ignored by I18n. --- app/views/goals/show.html.erb | 35 ++++++++++++++++++++++++------- config/locales/views/goals/en.yml | 12 ++++++++--- 2 files changed, 37 insertions(+), 10 deletions(-) diff --git a/app/views/goals/show.html.erb b/app/views/goals/show.html.erb index e22884010..95b353c2b 100644 --- a/app/views/goals/show.html.erb +++ b/app/views/goals/show.html.erb @@ -57,10 +57,32 @@ <% menu.with_item(variant: "button", text: t(".resume"), icon: "play", href: resume_goal_path(@goal), method: :patch) %> <% end %> <% if @goal.may_complete? %> - <% menu.with_item(variant: "button", text: t(".complete"), icon: "circle-check-big", href: complete_goal_path(@goal), method: :patch) %> + <% menu.with_item( + variant: "button", + text: t(".complete"), + icon: "circle-check-big", + href: complete_goal_path(@goal), + method: :patch, + confirm: CustomConfirm.new( + title: t(".confirm_complete_title"), + body: t(".confirm_complete_body"), + btn_text: t(".confirm_complete_cta") + ) + ) %> <% end %> <% if @goal.may_archive? %> - <% menu.with_item(variant: "button", text: t(".archive"), icon: "archive", href: archive_goal_path(@goal), method: :patch) %> + <% menu.with_item( + variant: "button", + text: t(".archive"), + icon: "archive", + href: archive_goal_path(@goal), + method: :patch, + confirm: CustomConfirm.new( + title: t(".confirm_archive_title"), + body: t(".confirm_archive_body"), + btn_text: t(".confirm_archive_cta") + ) + ) %> <% end %> <% if @goal.may_unarchive? %> <% menu.with_item(variant: "button", text: t(".unarchive"), icon: "archive-restore", href: unarchive_goal_path(@goal), method: :patch) %> @@ -113,12 +135,14 @@ <% elsif @goal.status == :behind && @goal.monthly_target_amount %> <%# Catch-up callout %> <% catch_up_money = Money.new(@goal.monthly_target_amount, @goal.currency) %> + <% catch_up_delta = @goal.monthly_target_amount.to_d - @stats[:avg_monthly].to_d %> + <% catch_up_delta_money = Money.new(catch_up_delta, @goal.currency) %> <%= render DS::Alert.new(variant: "warning", title: t("goals.show.catch_up.title", amount: catch_up_money.format)) do %>

<% if @goal.target_date %> - <%= t("goals.show.catch_up.body_with_date", date: I18n.l(@goal.target_date, format: :long)) %> + <%= t("goals.show.catch_up.body_with_date", date: I18n.l(@goal.target_date, format: :long), delta: catch_up_delta_money.format) %> <% else %> - <%= t("goals.show.catch_up.body") %> + <%= t("goals.show.catch_up.body", delta: catch_up_delta_money.format) %> <% end %>

@@ -240,9 +264,6 @@

<%= Money.new(@stats[:avg_monthly], @goal.currency).format %>

/mo

- <% if @goal.monthly_target_amount && @goal.monthly_target_amount.to_d.positive? %> -

· <%= t(".stats.target_of", amount: Money.new(@goal.monthly_target_amount, @goal.currency).format) %>

- <% end %>
<% if @goal.monthly_target_amount && @goal.monthly_target_amount.to_d.positive? %> <% delta = @goal.monthly_target_amount.to_d - @stats[:avg_monthly].to_d %> diff --git a/config/locales/views/goals/en.yml b/config/locales/views/goals/en.yml index 992c6677e..bb9cf5721 100644 --- a/config/locales/views/goals/en.yml +++ b/config/locales/views/goals/en.yml @@ -119,14 +119,20 @@ en: reached: Goal reached. Nice work. no_target_date: No target date set. Set one to project a finish line. no_pace: No contributions yet. Add some to start a projection. - behind: At %{current}/mo you'll fall short. Bump to %{required}/mo to hit it on time. + behind: At %{current}/mo you'll miss your target date. on_track: At your current pace, you'll reach this goal around %{date}. aria_label: "Projection chart for %{name}" catch_up: title: "Save %{amount}/mo to catch up" - body_with_date: "Bump your monthly contribution to stay on track for %{date}." - body: Bump your monthly contribution to stay on track. + body_with_date: "Bump your monthly contribution to stay on track for %{date}. Currently %{delta}/mo behind." + body: "Bump your monthly contribution to stay on track. Currently %{delta}/mo behind." cta: "Add %{amount}" + confirm_complete_title: Mark this goal complete? + confirm_complete_body: It leaves the Ongoing list. You can still archive or restore it later. + confirm_complete_cta: Mark complete + confirm_archive_title: Archive this goal? + confirm_archive_body: Archived goals disappear from the main list. You can restore them later. + confirm_archive_cta: Archive paused_banner: title: This goal is paused body: Resume it to keep tracking your progress.