diff --git a/frontend/index.html b/frontend/index.html index f683e74..bdd7565 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -2,12 +2,43 @@ - - + + + + + + + + + + + + + + + + + + PriceGhost - Track Product Prices
+ + + diff --git a/frontend/public/icon.svg b/frontend/public/icon.svg new file mode 100644 index 0000000..947eda9 --- /dev/null +++ b/frontend/public/icon.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + $ + + diff --git a/frontend/public/manifest.json b/frontend/public/manifest.json new file mode 100644 index 0000000..5015675 --- /dev/null +++ b/frontend/public/manifest.json @@ -0,0 +1,25 @@ +{ + "name": "PriceGhost", + "short_name": "PriceGhost", + "description": "Track product prices and get notified when they drop", + "start_url": "/", + "display": "standalone", + "background_color": "#0f172a", + "theme_color": "#6366f1", + "orientation": "portrait-primary", + "icons": [ + { + "src": "/icon.svg", + "sizes": "any", + "type": "image/svg+xml", + "purpose": "any" + }, + { + "src": "/icon.svg", + "sizes": "512x512", + "type": "image/svg+xml", + "purpose": "maskable" + } + ], + "categories": ["shopping", "finance", "utilities"] +} diff --git a/frontend/public/sw.js b/frontend/public/sw.js new file mode 100644 index 0000000..1c26075 --- /dev/null +++ b/frontend/public/sw.js @@ -0,0 +1,64 @@ +const CACHE_NAME = 'priceghost-v1'; +const STATIC_ASSETS = [ + '/', + '/icon.svg', + '/manifest.json' +]; + +// Install - cache static assets +self.addEventListener('install', (event) => { + event.waitUntil( + caches.open(CACHE_NAME).then((cache) => { + return cache.addAll(STATIC_ASSETS); + }) + ); + self.skipWaiting(); +}); + +// Activate - clean up old caches +self.addEventListener('activate', (event) => { + event.waitUntil( + caches.keys().then((cacheNames) => { + return Promise.all( + cacheNames + .filter((name) => name !== CACHE_NAME) + .map((name) => caches.delete(name)) + ); + }) + ); + self.clients.claim(); +}); + +// Fetch - network first, fallback to cache +self.addEventListener('fetch', (event) => { + // Skip non-GET requests + if (event.request.method !== 'GET') return; + + // Skip API requests - always go to network + if (event.request.url.includes('/api/')) return; + + event.respondWith( + fetch(event.request) + .then((response) => { + // Clone the response before caching + const responseClone = response.clone(); + caches.open(CACHE_NAME).then((cache) => { + cache.put(event.request, responseClone); + }); + return response; + }) + .catch(() => { + // Network failed, try cache + return caches.match(event.request).then((cachedResponse) => { + if (cachedResponse) { + return cachedResponse; + } + // If it's a navigation request, return the cached index + if (event.request.mode === 'navigate') { + return caches.match('/'); + } + return new Response('Offline', { status: 503 }); + }); + }) + ); +});