diff --git a/backend/static/icons/icon-192.png b/backend/static/icons/icon-192.png new file mode 100644 index 0000000..9a734af Binary files /dev/null and b/backend/static/icons/icon-192.png differ diff --git a/backend/static/icons/icon-512.png b/backend/static/icons/icon-512.png new file mode 100644 index 0000000..3b0c530 Binary files /dev/null and b/backend/static/icons/icon-512.png differ diff --git a/backend/static/manifest.json b/backend/static/manifest.json new file mode 100644 index 0000000..c75994d --- /dev/null +++ b/backend/static/manifest.json @@ -0,0 +1,25 @@ +{ + "name": "청춘약국 마일리지", + "short_name": "청춘약국", + "description": "청춘약국 QR 마일리지 적립 서비스", + "start_url": "/my-page", + "display": "standalone", + "background_color": "#f5f7fa", + "theme_color": "#6366f1", + "orientation": "portrait", + "lang": "ko", + "icons": [ + { + "src": "/static/icons/icon-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "any maskable" + }, + { + "src": "/static/icons/icon-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "any maskable" + } + ] +} diff --git a/backend/static/sw.js b/backend/static/sw.js new file mode 100644 index 0000000..ceff4cc --- /dev/null +++ b/backend/static/sw.js @@ -0,0 +1,63 @@ +const CACHE_NAME = 'chungchun-pharmacy-v1'; +const STATIC_ASSETS = [ + '/static/js/lottie.min.js', + '/static/animations/ai-loading.json', + '/static/icons/icon-192.png', + '/static/icons/icon-512.png' +]; + +// Install: pre-cache static assets +self.addEventListener('install', (event) => { + event.waitUntil( + caches.open(CACHE_NAME).then((cache) => cache.addAll(STATIC_ASSETS)) + ); + self.skipWaiting(); +}); + +// Activate: clean old caches +self.addEventListener('activate', (event) => { + event.waitUntil( + caches.keys().then((keys) => + Promise.all( + keys.filter((key) => key !== CACHE_NAME).map((key) => caches.delete(key)) + ) + ) + ); + self.clients.claim(); +}); + +// Fetch: cache-first for static, network-only for dynamic +self.addEventListener('fetch', (event) => { + const url = new URL(event.request.url); + + // Skip non-GET requests + if (event.request.method !== 'GET') return; + + // Skip dynamic routes entirely + if (url.pathname.startsWith('/api/') || + url.pathname.startsWith('/admin') || + url.pathname.startsWith('/claim') || + url.pathname.startsWith('/my-page') || + url.pathname === '/privacy' || + url.pathname === '/logout' || + url.pathname === '/') { + return; + } + + // Cache-first for static assets and fonts + if (url.pathname.startsWith('/static/') || + url.hostname === 'fonts.googleapis.com' || + url.hostname === 'fonts.gstatic.com') { + event.respondWith( + caches.match(event.request).then((cached) => { + return cached || fetch(event.request).then((response) => { + if (response.ok) { + const clone = response.clone(); + caches.open(CACHE_NAME).then((cache) => cache.put(event.request, clone)); + } + return response; + }); + }) + ); + } +});