Commit Graph

7 Commits

Author SHA1 Message Date
Guillem Arias
fa8f7e2418 fix(savings_goals/new): stepper line uses border-secondary token
bg-secondary is not a registered Sure utility, so the previous h-0.5
bg-secondary div rendered with no background — the line was just a
transparent slot, barely visible in either theme.

Replace with border-t-2 border-secondary on the connector div, and
toggle border-inverse / border-secondary on step transition. Both
classes are real Sure tokens with proper light/dark variants
(alpha-black-200 / alpha-white-300 for border-secondary).
2026-05-11 15:29:05 +02:00
Guillem Arias
27863882fe feat(savings_goals/new): checkbox styling, visible stepper, DS::Disclosure for notes, drop redundant cancel
Use Sure's .checkbox checkbox--light classes on the funding-account
check_box_tag — matches transactions / entries / settings pages.

Stepper line: 2px tall bg-secondary in resting state (was 1px bg-subdued
which disappeared in dark mode). Step 2 inactive circle: border-secondary
outline instead of bg-container-inset fill — visible in both themes.

Notes collapse switches from raw <details> to DS::Disclosure for
consistency with the rest of Sure's DS.

Drop the footer Cancel button — the close X in the modal header already
handles that, and double cancel was redundant. The footer-left slot now
only renders Back (with arrow-left icon) and only on step 2.
2026-05-11 15:13:04 +02:00
Guillem Arias
c3bf6a157f feat(savings_goals/new): inline per-field error state in stepper
Drop the top-of-form server-error banner (kept only when model.errors
on :base, which is rare) and stop relying on input.reportValidity()
browser-default tooltips.

Stepper validates step 1 manually when Continue is clicked:
- name empty → red ring on input + "Please give your goal a name." below
- target_amount ≤ 0 → red ring + "Set a target amount greater than zero."
- no funding account checked → "Select at least one funding account."

Each error clears as soon as the user fixes the field — typing in the
name field clears the name error, entering an amount > 0 clears the
amount error, checking any account clears the accounts error.

Drop the now-unused flashLinkedAccountsRequired() shake; replaced by
the per-field error pattern.
2026-05-11 15:04:01 +02:00
Guillem Arias
e3dd1c4c1e feat(savings_goals/new): live previewable name avatar + ghost cancel + circular header icon
Replace the big square DS::FilledIcon next to the name input with a
small Savings::GoalAvatarComponent that previews the goal's avatar
(seeded color + first character of the typed name, updates live via
new stepper#nameChanged action).

Switch the modal header's target avatar from FilledIcon(size: lg,
rounded: false) → (size: md, rounded: true) — matches the goal-avatar
shape used elsewhere on the page.

Replace the hand-rolled <button> for Cancel/Back with DS::Button
variant: "ghost". Stepper now drills into the button's inner span to
swap the label, same pattern already used for the Continue/Create
button on the right.

Drop the now-unused footerLeftLabel target.
2026-05-11 14:59:26 +02:00
Guillem Arias
696fbc0b43 feat(savings): match Claude Design — projection chart, target-icon modal, grouped funding accounts
Brings the savings goals UI closer to the Claude Design reference shared
by the user. Changes:

- Sidebar nav label: "Savings goals" → "Savings".
- Status pill copy: "Behind" → "Behind pace" (matches Pill component
  from GoalsCommon.jsx).
- Empty state rewritten with a large target icon, "No goals yet"
  heading, and the descriptive body copy from the design.

Goal detail page (matches GoalDetail.jsx):
- New "← All goals" back link above the header.
- 2-column hero: ring card on the left (320px column), Projection card
  on the right.
- Projection card uses a new D3 Stimulus controller
  (`savings-goal-projection-chart`) that draws:
    · saved area + line from goal creation → today (solid, primary)
    · dashed projection segment from today → target date (yellow when
      behind, green when on track)
    · horizontal dashed target line with label
    · today marker (vertical dashed line + dot)
  Data shape comes from `SavingsGoal#projection_payload`.
- Card subtitle generates a contextual sentence ("At $X/mo you'll fall
  short. Bump to $Y/mo to hit it on time." / "At your current pace
  you'll reach this goal around Month YYYY." / "Goal reached. Nice
  work.") with a strong tag highlighting the actionable figure.
- Stat row now shows Linked balance (sum across linked accounts) +
  "N accounts" sub-caption instead of duplicate "Target date" stat.

New goal modal (matches the design images 2 + 3):
- DS::Dialog custom header: DS::FilledIcon target glyph + title + step
  subtitle ("Step 1 of 2 · Goal details" / "Step 2 of 2 · Review &
  start") that updates as the user advances.
- Connected stepper at top of body: numbered circles connected by a
  bar, step-1 circle flips to ✓ when complete.
- Step 1 heading "What are you saving for?" + supporting copy.
- Name field paired with a target glyph affordance on its left.
- Target amount + Target date in a 2-col grid.
- Funding accounts list now grouped by account subtype with uppercase
  section headers (CHECKING / SAVINGS / HSA / CD / MONEY MARKET /
  OTHER), each row showing avatar + name + subtype + balance.
- Step 2 heading "Looks good?" + Review card (goal target + funding
  accounts summary + suggested monthly = target/months_remaining), and
  a disclosure for the optional initial contribution.
- Footer: "Cancel" left text-button (closes modal) / "Back" left text
  when on step 2; "Continue →" or "Create goal →" right arrow button.

Demo generator: Depository accounts now set `subtype` ("checking" /
"savings") on the accountable so they group correctly in the modal.

Tests: all green, 35 runs in the savings suite, 92 assertions.
2026-05-11 12:08:47 +02:00
Guillem Arias
8a9f4b1a67 fix(savings): DS conformance pass on stepper, ring, card, status pill
- StatusPill: use functional `text-success` / `text-warning` tokens with
  matching icon colors and `px-2 py-1`, mirroring
  `app/views/budget_categories/_budget_category.html.erb:29-43`.
- ProgressRing: rework center text to match `_budget_donut.html.erb`
  (small "Saved" label, `text-3xl font-medium` headline, "of $X"
  underline). Stroke color now derives from goal.status (yellow when
  behind, blue on track, green reached, gray for no-date).
- GoalCard bar: track height + transition match budget category bar
  (`h-1.5`, `transition-all duration-500`, `inline-size`).
- Index/show layouts: render page header inline (`<h1>` + actions). The
  default application layout doesn't yield `:page_actions`, so the
  CTA + kebab menu wouldn't appear when emitted via `content_for`.
- Stepper review summary: target the actual form inputs by `name`
  rather than relying on the `data-target` Stimulus attribute, since
  `money_field` puts the attribute on the wrapper. Step 1 validation
  scoped to the step 1 panel.
- Demo generator: filter Depository accounts via
  `where(accountable_type: "Depository")` — Rails delegated_type
  generates the `depository?` predicate, not a `.depository` scope.
2026-05-11 11:39:13 +02:00
Guillem Arias
77660d2ee4 feat(savings): add savings goals
Adds a standalone Savings goals feature: a piggy-bank style tracker that
lets a family set a target, link one or more Depository accounts as
funding sources, and log manual contributions over time. Supersedes #1569
(closed) — same intent, redesigned per reviewer + Discord feedback.

What this adds:

- New `/savings_goals` sidebar entry (piggy-bank icon) with index, show,
  state-filtered tabs (all/active/paused/completed/archived), and a
  2-step modal stepper for creation (Identity → Review).
- Multi-account funding via a `SavingsGoalAccount` join: a goal requires
  ≥1 linked Depository account (checking/savings/HSA/CD/money-market),
  and all linked accounts must share the goal's currency.
- Tracker balance model: goal balance = SUM(contributions.amount). No
  auto-flow from account balances. Contributions are pure logical
  records and don't move money between accounts.
- Manual contributions modal scoped to the goal's linked accounts.
  Initial contributions seeded at creation can't be deleted; manual
  ones can.
- AASM lifecycle: active / paused / completed / archived.
  Hard-delete only after archive.
- Status pills (On track / Behind / Reached / No date) derived from
  pace vs target_date.
- AI Assistant tool `create_savings_goal` lets the sidebar chat create
  a goal end-to-end from a natural-language prompt; soft errors carry
  the available-accounts list back to the LLM (mirrors the existing
  `import_bank_statement` pattern).
- Family-scoped throughout (`Current.family`-only access, account
  family-scoping enforced both in controllers and the AI tool).
- Demo data seed wires up 4 sample goals across the Depository accounts.

Intentionally out of scope (separate PRs / v1.1):

- Auto-fund from budget surplus + Sidekiq cron + budget-show card.
- Dashboard "Savings goals" widget.
- "Behind pace" projection chart on the detail page.
- `evaluate_savings_goal_feasibility` LLM tool (level-setting before
  create_savings_goal).
- Spend-less goals inside Budgets.
- Family-member-private goals (deferred investigation).
2026-05-11 11:20:37 +02:00