Files
budget-manager/frontend/public/sw.js
T
biggy 26e3b76b11 feat: add global styles and transitions for dark mode support
- Created style.css for base styles and color variables for light and dark themes.
- Added tailwind.css for Tailwind CSS integration and custom theme variables.
- Introduced transitions.css to manage global transitions and reduce motion preferences.
- Updated tsconfig.app.json to include path aliases for easier imports.
- Modified tsconfig.json to set baseUrl and paths for module resolution.
- Added typed-router.d.ts for auto-generated route types.
- Enhanced vite.config.ts with Vue Router plugin, Tailwind CSS integration, and server proxy settings.
2025-05-23 21:35:43 +02:00

216 lines
5.5 KiB
JavaScript

// Service Worker for Budget Manager PWA
const CACHE_NAME = "budget-manager-v1";
// Assets to precache
const precacheResources = [
"/",
"/index.html",
"/src/main.ts",
"/src/App.vue",
"/styles/tailwind.css",
"/styles/transitions.css",
"/styles/style.css",
"/icons/icon-192px.jpg",
"/icons/icon-512px.jpg",
];
// Install event - precache assets
self.addEventListener("install", (event) => {
console.log("Service worker install event");
event.waitUntil(
caches
.open(CACHE_NAME)
.then((cache) => {
return cache.addAll(precacheResources);
})
.then(() => {
// Skip waiting to activate immediately
return self.skipWaiting();
}),
);
});
// Activate event - clean up old caches
self.addEventListener("activate", (event) => {
console.log("Service worker activate event");
event.waitUntil(
caches
.keys()
.then((cacheNames) => {
return Promise.all(
cacheNames.map((cacheName) => {
if (cacheName !== CACHE_NAME) {
console.log("Deleting old cache:", cacheName);
return caches.delete(cacheName);
}
}),
);
})
.then(() => {
// Take control of all clients
return self.clients.claim();
}),
);
});
// Fetch event - serve from cache first, then network
self.addEventListener("fetch", (event) => {
// Skip cross-origin requests
if (
!event.request.url.startsWith(self.location.origin) ||
event.request.url.includes("/api/")
) {
return;
}
event.respondWith(
caches.match(event.request).then((cachedResponse) => {
if (cachedResponse) {
return cachedResponse;
}
return fetch(event.request)
.then((response) => {
// Cache successful responses
if (
response &&
response.status === 200 &&
response.type === "basic"
) {
const responseToCache = response.clone();
caches.open(CACHE_NAME).then((cache) => {
cache.put(event.request, responseToCache);
});
}
return response;
})
.catch(() => {
// If fetch fails (offline), return a fallback
if (event.request.mode === "navigate") {
return caches.match("/");
}
return null;
});
}),
);
});
// Background sync for offline transactions
self.addEventListener("sync", (event) => {
if (event.tag === "sync-transactions") {
event.waitUntil(syncTransactions());
} else if (event.tag === "sync-categories") {
event.waitUntil(syncCategories());
}
});
// Push notification handler
self.addEventListener("push", (event) => {
const data = event.data.json();
const options = {
body: data.body,
icon: "/icons/icon-192px.jpg",
badge: "/icons/badge-96x96.png",
data: data.data,
actions: data.actions || [],
};
event.waitUntil(self.registration.showNotification(data.title, options));
});
// Notification click handler
self.addEventListener("notificationclick", (event) => {
event.notification.close();
event.waitUntil(clients.openWindow("/"));
});
// Sync transactions with the server
async function syncTransactions() {
try {
const pendingTransactions = await getPendingItems("pendingTransactions");
const token = await getAuthToken();
if (!token || pendingTransactions.length === 0) return;
for (const transaction of pendingTransactions) {
const response = await fetch("http://localhost:3000/api/transactions", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify(transaction),
});
if (response.ok) {
await removePendingItem("pendingTransactions", transaction.id);
}
}
} catch (error) {
console.error("Error syncing transactions:", error);
}
}
// Sync categories with the server
async function syncCategories() {
try {
const pendingCategories = await getPendingItems("pendingCategories");
const token = await getAuthToken();
if (!token || pendingCategories.length === 0) return;
for (const category of pendingCategories) {
const response = await fetch("http://localhost:3000/api/categories", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify(category),
});
if (response.ok) {
await removePendingItem("pendingCategories", category.id);
}
}
} catch (error) {
console.error("Error syncing categories:", error);
}
}
// Helper function to get pending items from IndexedDB
async function getPendingItems(storeName) {
return []; // Placeholder - in a real app, this would access IndexedDB
}
// Helper function to remove a pending item from IndexedDB
async function removePendingItem(storeName, id) {
// Placeholder - in a real app, this would access IndexedDB
}
// Helper function to get auth token from client
async function getAuthToken() {
const clients = await self.clients.matchAll();
if (clients.length === 0) {
return localStorage.getItem("token");
}
// Ask client for token
const client = clients[0];
return new Promise((resolve) => {
const channel = new MessageChannel();
channel.port1.onmessage = (event) => {
resolve(event.data.token);
};
client.postMessage({ type: "GET_AUTH_TOKEN" }, [channel.port2]);
});
}