Skip to content

Add savings calculator, push prefs, nav drawer and i18n - #473

Merged
nayt9 merged 2 commits into
stowp:mainfrom
nafiisatu:handsoff/issues-350-352-353-354-bc175f
Oct 18, 2026
Merged

nayt9 merged 2 commits into
stowp:mainfrom
nafiisatu:handsoff/issues-350-352-353-354-bc175f

Conversation

@nafiisatu

Copy link
Copy Markdown
Contributor

This PR adds four frontend features. All changes are under frontend/.

#350 Savings calculator widget

  • src/lib/savingsProjection.ts holds the projection math as pure functions. It compounds yield monthly at the chosen APR and adds the monthly contribution each month. It returns how many months it takes to reach the goal, a month-by-month series, and the balance at fixed horizons. It stops after 50 years, so a goal that is never reached can't loop forever.
  • SavingsCalculator is an interactive widget with inputs for goal amount, initial deposit and monthly contribution, plus an APR slider. It shows:
    • the estimated time to the goal and a target date
    • how much of the final balance is deposits and how much is yield, with a bar chart
    • projected balance and yield after 1, 3 and 5 years
    • a disclaimer that projections are not guaranteed
  • The widget is on the dashboard and exported from components/savings.

#352 Push notification preferences panel

  • The new PushNotificationPanel is part of Settings. It has:
    • a main switch that asks for browser permission and registers a service worker (public/sw.js)
    • a switch for each existing notification category, to choose which ones send a push
    • a "send test notification" button
    • a clear message when the browser doesn't support push or the user has blocked notifications
  • src/lib/push.ts subscribes through pushManager when NEXT_PUBLIC_VAPID_PUBLIC_KEY is set. It sends the subscription and the chosen event types to POST /api/user/push-subscriptions, and calls DELETE on the same endpoint when push is turned off. Note: this endpoint does not exist in the backend yet, so server-side delivery still needs a backend change.
  • Push permission and subscriptions belong to one browser, so each device keeps its own push choices in localStorage. The existing /api/user/preferences/notifications payload is unchanged.
  • The service worker shows incoming pushes. Clicking one focuses or opens the app at the URL sent with the push.

#353 Mobile navigation drawer

  • NavDrawer is mounted in the (app) layout and only shows below md. It adds a sticky top bar with a menu button that opens a side drawer linking to the app routes. The current page is marked with aria-current="page".
  • Animations: the drawer slides in and the backdrop fades. Both are turned off under prefers-reduced-motion.
  • Gestures: swipe right from the left edge of the screen to open the drawer, and swipe left on it to close. The drawer follows your finger while you drag. A mostly vertical swipe is treated as scrolling.
  • Keyboard and accessibility:
    • The drawer is a dialog (role="dialog", aria-modal) and is inert while closed.
    • Opening it moves focus to the first link, and Tab stays inside the drawer.
    • Escape or a tap on the backdrop closes it and returns focus to the menu button.
    • Arrow Up/Down, Home and End move between links.
    • Page scrolling is locked while the drawer is open.

#354 Multi-language support

  • This adds a small built-in i18n layer rather than a new npm library. I couldn't install packages for this change, and the layer covers what was needed:
    • src/i18n/config.ts lists the supported languages, picks the default, and matches the browser's language.
    • src/i18n/messages.ts looks up dot-separated keys and fills in {placeholders}.
    • I18nProvider and useTranslation() provide t(), with plural forms through Intl.PluralRules. Any key missing from a translation falls back to English.
  • Translation files are src/i18n/locales/{en,es,fr}.json. The new drawer, calculator and push panel use them.
  • Switching language: LanguageSwitcher is in the drawer and in a new "Language" section in Settings. The choice is saved in localStorage and <html lang> is updated. The server always renders the default language and the browser switches after it loads, so there are no hydration mismatches.
  • useTranslation() falls back to English when no provider is mounted, so the existing component tests still run without wrapping components in the provider.
  • The README explains how to add a language and documents the VAPID variable.

Closes #350
Closes #352
Closes #353
Closes #354

@drips-wave

drips-wave Bot commented Oct 18, 2026

Copy link
Copy Markdown

@nafiisatu Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@vercel

vercel Bot commented Oct 18, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
stow Error Error Oct 18, 2026 11:42am UTC

@nayt9
nayt9 merged commit 0d382d3 into stowp:main Oct 18, 2026
1 of 6 checks passed

This branch had an error being deployed

1 failed deployment
Preview — 78278ec2 Deployed Oct 18, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants