whisper-money/resources/js/hooks
Víctor Falcón ed157f6f8a
refactor(frontend): call backend routes through Wayfinder instead of literal URLs (#772)
Follow-up to the Inertia v3 upgrade (#769). While auditing whether the
frontend used Wayfinder's current API, I found **32 HTTP call sites
across 16 files that built their URL by hand** — every one of which
already had a generated Wayfinder action sitting unused.

## Why this matters

Wayfinder exists so a route rename fails at **compile** time. A
hand-written `'/api/transactions/bulk'` fails at **runtime**, in
production, on a path a unit test can't catch because the tests mock
`axios`. The worst offender was `services/transaction-sync.ts` — the
offline sync service — which had five of them.

I verified each URL against `php artisan route:list` before changing it;
there were no missing routes, only unused generated ones.

## What changed

| File | Sites | Now uses |
| --- | --- | --- |
| `services/transaction-sync.ts` | 6 | `TransactionController` +
`Sync/TransactionSyncController` |
| `components/transactions/saved-filters.tsx` | 4 |
`Api/SavedFilterController` |
| `hooks/use-cashflow-data.ts` | 5 | `Api/CashflowAnalyticsController` |
| `components/transactions/import-transactions-drawer.tsx` | 3 |
`TransactionController@categorize` |
| `components/accounts/account-balance-chart.tsx` | 2 |
`Api/DashboardAnalyticsController` |
| `hooks/use-decrypt-account-names.ts` | 2 | `Api/AccountController` |
| `components/dashboard/net-worth-chart.tsx` | 2 | the two net-worth
preference controllers |
| `pages/transactions/index.tsx` | 2 |
`TransactionController@bulkUpdate` |
| `lib/import-config-storage.ts` | 2 |
`Api/AccountImportConfigController` |
| `app.tsx`, `use-decrypt-transactions.ts`,
`encryption-key-context.tsx`, `import-step-preview.tsx`,
`import-transactions-button.tsx`, `category-analysis-drawer.tsx`,
`settings/appearance.tsx` | 1 each | respective controllers |

## Query strings got simpler

The endpoints with parameters were assembling `URLSearchParams` by hand.
The generated `.url({ query })` helper does it, so that scaffolding is
gone:

```diff
-const periodParams = new URLSearchParams({ from: fromStr, to: toStr });
-const periodQuery = `?${periodParams.toString()}`;
-fetch(`/api/cashflow/breakdown${periodQuery}&type=income`)
+const periodQuery = { from: fromStr, to: toStr };
+fetch(cashflowBreakdown.url({ query: { ...periodQuery, type: 'income' } }))
```

`lib/import-config-storage.ts` also loses its local `configUrl()`
helper, which only existed to interpolate an account id.

## Two aliases, on purpose

`transaction-sync.ts` and `import-transactions-button.tsx` import with
`as` aliases because the plain names collide with a method (`update`,
`store`, `destroy`) and a `useState` variable (`importData`) already in
those files. Named imports are kept everywhere so tree-shaking still
works.

## Verification

- `bun run test` — **356/356, with zero test changes.** That is the
useful signal here: `transaction-sync.test.ts` asserts `axios.delete`
was called with the literal `'/transactions/txn-1'`, and it still
passes, so the generated URLs are byte-identical to the strings they
replaced.
- `bun run types` — no new errors. (`transaction-sync.ts:45` and the
`.test.tsx` matcher errors are pre-existing; the former just shifted
line number as imports were added.)
- `bun run build`, `bun run lint`, `bun run format`, `bun run dry` —
green.

### Browser check

Tests mock `axios`, so a wrong URL would still pass them. I exercised
the rewritten endpoints in a real browser and captured the actual
network traffic — all **200**, query strings identical to what the
manual code produced:

```
200 /api/cashflow/summary?from=2026-08-01&to=2026-08-31
200 /api/cashflow/trend?months=12&to=2026-08-31
200 /api/cashflow/breakdown?from=2026-08-01&to=2026-08-31&type=income
200 /api/cashflow/breakdown?from=2026-08-01&to=2026-08-31&type=expense
200 /api/dashboard/account/{id}/balance-evolution?from=2025-08-11&to=2026-08-11
200 /api/saved-filters
```

0 failed requests, 0 console errors across cashflow, transactions,
accounts, account detail and appearance.

## Out of scope

11 hardcoded **navigation** URLs remain (`href="/register"`,
`href="/privacy"`, `router.visit('/dashboard')`), mostly on the
marketing pages. They are static routes with a much lower rename risk,
so I left them for a separate pass rather than widen this diff.
2026-08-11 13:26:10 +00:00
..
use-admin.tsx feat: Add privacy mode to hide monetary amounts (#28) 2025-12-14 16:03:02 +01:00
use-appearance.tsx fix(ui): stop unusable web storage from white-screening the app (#743) 2026-08-09 15:33:22 +00:00
use-cashflow-data.ts refactor(frontend): call backend routes through Wayfinder instead of literal URLs (#772) 2026-08-11 13:26:10 +00:00
use-categorize-transactions.ts refactor(encryption): strip client-side transaction encryption (#514) 2026-06-20 16:13:26 +00:00
use-chart-color-scheme.tsx fix(ui): stop unusable web storage from white-screening the app (#743) 2026-08-09 15:33:22 +00:00
use-chart-views.ts fix(dashboard): treat loans as debt in net worth (#238) 2026-03-20 09:55:53 +00:00
use-clipboard.ts Set up a fresh Laravel app 2025-11-07 12:01:36 +00:00
use-connect-flow.ts feat(banking): enable Interactive Brokers for all users (#593) 2026-06-26 11:03:21 +02:00
use-count-up.ts User Onboarding Flow (#23) 2025-12-12 13:06:08 +01:00
use-dashboard-data.test.ts feat(settings): centralize currency options and split profile/account support (#256) 2026-04-02 19:23:10 +02:00
use-dashboard-data.ts fix(dashboard): stop negative asset balances inflating net worth (#660) 2026-07-08 15:22:16 +00:00
use-decrypt-account-names.ts refactor(frontend): call backend routes through Wayfinder instead of literal URLs (#772) 2026-08-11 13:26:10 +00:00
use-decrypt-transactions.test.tsx refactor(encryption): strip client-side transaction encryption (#514) 2026-06-20 16:13:26 +00:00
use-decrypt-transactions.ts refactor(frontend): call backend routes through Wayfinder instead of literal URLs (#772) 2026-08-11 13:26:10 +00:00
use-expandable-categories.ts feat: expand parent categories inline in breakdowns (#486) 2026-06-04 11:19:21 +02:00
use-initials.tsx Set up a fresh Laravel app 2025-11-07 12:01:36 +00:00
use-locale.ts feat: Spanish localization (#74) 2026-02-08 11:58:08 +01:00
use-mobile-navigation.ts Set up a fresh Laravel app 2025-11-07 12:01:36 +00:00
use-mobile.tsx fix(appearance): support MediaQueryList change events on legacy Safari (PHP-LARAVEL-41) (#646) 2026-07-05 17:03:35 +00:00
use-onboarding-state.test.tsx feat(ai): suggest automation rules during onboarding (#523) 2026-06-13 22:51:15 +02:00
use-onboarding-state.ts feat(ai): suggest automation rules during onboarding (#523) 2026-06-13 22:51:15 +02:00
use-online-status.ts Fix CI: Update bun.lock and fix linting errors 2025-12-01 15:16:06 +01:00
use-poll-job-status.ts feat(ai): manage AI consent outside onboarding with live backfill (#591) 2026-06-25 10:50:35 +02:00
use-pwa-install.ts feat: Show PWA install button on mobile landing page (#99) 2026-02-07 09:33:12 +01:00
use-re-evaluate-all-transactions.tsx fix(security): scope job-status endpoints to owner + feature-area fixes (#627) 2026-07-03 14:49:32 +02:00
use-scroll-progress.test.ts refactor(landing): extract the scroll-progress hook every preview duplicated (#768) 2026-08-11 14:34:24 +02:00
use-scroll-progress.ts refactor(landing): extract the scroll-progress hook every preview duplicated (#768) 2026-08-11 14:34:24 +02:00
use-two-factor-auth.ts Set up a fresh Laravel app 2025-11-07 12:01:36 +00:00
use-web-haptics.test.tsx fix(haptics): use a local WebHaptics wrapper (#225) 2026-03-16 11:26:04 +00:00
use-web-haptics.ts fix(haptics): use a local WebHaptics wrapper (#225) 2026-03-16 11:26:04 +00:00