Add budget module and push notifications for transactions
All checks were successful
Deploy to VPS / deploy (push) Successful in 34s

Budget: recurring items CRUD, yearly/monthly projections with no-double-count
logic, and full UI (overview, monthly detail, recurring items manager).

Push notifications: Web Push via VAPID keys, triggered on transaction creation
from n8n. Includes service worker handlers, frontend subscription flow, and
a test button on the Dashboard (temporary).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Carlos Escalante
2026-03-26 22:28:14 -06:00
parent 2cd0d3b2e1
commit 8d76059ae8
25 changed files with 2225 additions and 13 deletions

View File

@@ -27,6 +27,9 @@ self.addEventListener('fetch', (event) => {
return;
}
// Only handle http(s) requests — skip chrome-extension:// etc.
if (!url.protocol.startsWith('http')) return;
// Cache-first for static assets
if (url.pathname.startsWith('/assets/')) {
event.respondWith(
@@ -50,3 +53,46 @@ self.addEventListener('fetch', (event) => {
// Default: network with cache fallback
event.respondWith(fetch(request).catch(() => caches.match(request)));
});
// --- Push Notifications ---
self.addEventListener('push', (event) => {
if (!event.data) return;
let data;
try {
data = event.data.json();
} catch {
// Fallback for plain-text pushes (e.g. browser test pushes)
data = { title: 'WealthySmart', body: event.data.text() };
}
const options = {
body: data.body,
icon: '/icons/icon-192.png',
badge: '/icons/icon-192.png',
data: { url: data.url || '/' },
vibrate: [200, 100, 200],
tag: 'transaction',
renotify: true,
};
event.waitUntil(self.registration.showNotification(data.title, options));
});
self.addEventListener('notificationclick', (event) => {
event.notification.close();
const url = event.notification.data?.url || '/';
event.waitUntil(
clients.matchAll({ type: 'window', includeUncontrolled: true }).then((windowClients) => {
for (const client of windowClients) {
if (client.url.includes(self.location.origin) && 'focus' in client) {
client.navigate(url);
return client.focus();
}
}
return clients.openWindow(url);
})
);
});