Deploy to production - ensure all endpoints operational
This commit is contained in:
@@ -1,13 +1,13 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#ec4899;stop-opacity:1" />
|
||||
<stop offset="50%" style="stop-color:#8b5cf6;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#3b82f6;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect width="32" height="32" rx="8" fill="url(#grad1)"/>
|
||||
<path d="M16 8L20.5 14H11.5L16 8Z" fill="white" opacity="0.9"/>
|
||||
<circle cx="16" cy="18" r="3" fill="white" opacity="0.9"/>
|
||||
<path d="M10 22L16 20L22 22L20 26H12L10 22Z" fill="white" opacity="0.9"/>
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#ec4899;stop-opacity:1" />
|
||||
<stop offset="50%" style="stop-color:#8b5cf6;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#3b82f6;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect width="32" height="32" rx="8" fill="url(#grad1)"/>
|
||||
<path d="M16 8L20.5 14H11.5L16 8Z" fill="white" opacity="0.9"/>
|
||||
<circle cx="16" cy="18" r="3" fill="white" opacity="0.9"/>
|
||||
<path d="M10 22L16 20L22 22L20 26H12L10 22Z" fill="white" opacity="0.9"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 683 B After Width: | Height: | Size: 695 B |
@@ -1,15 +1,15 @@
|
||||
User-agent: *
|
||||
Allow: /
|
||||
|
||||
# Block access to sensitive files
|
||||
Disallow: /.env
|
||||
Disallow: /src/
|
||||
Disallow: /node_modules/
|
||||
Disallow: /dist/
|
||||
Disallow: /*.log
|
||||
|
||||
# Sitemap
|
||||
Sitemap: https://miraclesinmotion.org/sitemap.xml
|
||||
|
||||
# Crawl delay (optional)
|
||||
User-agent: *
|
||||
Allow: /
|
||||
|
||||
# Block access to sensitive files
|
||||
Disallow: /.env
|
||||
Disallow: /src/
|
||||
Disallow: /node_modules/
|
||||
Disallow: /dist/
|
||||
Disallow: /*.log
|
||||
|
||||
# Sitemap
|
||||
Sitemap: https://miraclesinmotion.org/sitemap.xml
|
||||
|
||||
# Crawl delay (optional)
|
||||
Crawl-delay: 1
|
||||
@@ -1,28 +1,28 @@
|
||||
{
|
||||
"name": "Miracles In Motion",
|
||||
"short_name": "MiraclesInMotion",
|
||||
"description": "A 501(c)3 non-profit providing essentials for student success",
|
||||
"start_url": "/",
|
||||
"display": "standalone",
|
||||
"background_color": "#ffffff",
|
||||
"theme_color": "#ec4899",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/favicon-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/favicon-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}
|
||||
],
|
||||
"categories": ["education", "social", "non-profit"],
|
||||
"lang": "en-US",
|
||||
"dir": "ltr",
|
||||
"orientation": "portrait-primary",
|
||||
"scope": "/",
|
||||
"related_applications": [],
|
||||
"prefer_related_applications": false
|
||||
{
|
||||
"name": "Miracles In Motion",
|
||||
"short_name": "MiraclesInMotion",
|
||||
"description": "A 501(c)3 non-profit providing essentials for student success",
|
||||
"start_url": "/",
|
||||
"display": "standalone",
|
||||
"background_color": "#ffffff",
|
||||
"theme_color": "#ec4899",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/favicon-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/favicon-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}
|
||||
],
|
||||
"categories": ["education", "social", "non-profit"],
|
||||
"lang": "en-US",
|
||||
"dir": "ltr",
|
||||
"orientation": "portrait-primary",
|
||||
"scope": "/",
|
||||
"related_applications": [],
|
||||
"prefer_related_applications": false
|
||||
}
|
||||
620
public/sw.js
620
public/sw.js
@@ -1,311 +1,311 @@
|
||||
// Miracles in Motion - Service Worker
|
||||
// Version 1.0.0
|
||||
|
||||
const CACHE_NAME = 'miracles-in-motion-v1'
|
||||
const STATIC_CACHE = 'static-v1'
|
||||
const DYNAMIC_CACHE = 'dynamic-v1'
|
||||
|
||||
// Assets to cache immediately
|
||||
const STATIC_ASSETS = [
|
||||
'/',
|
||||
'/index.html',
|
||||
'/favicon.svg',
|
||||
'/robots.txt',
|
||||
'/site.webmanifest'
|
||||
]
|
||||
|
||||
// Assets to cache on demand
|
||||
const CACHE_STRATEGIES = {
|
||||
// Cache first for static assets
|
||||
CACHE_FIRST: ['.js', '.css', '.woff', '.woff2', '.ttf', '.eot'],
|
||||
// Network first for API calls
|
||||
NETWORK_FIRST: ['/api/', '/analytics/'],
|
||||
// Stale while revalidate for images
|
||||
STALE_WHILE_REVALIDATE: ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg']
|
||||
}
|
||||
|
||||
// Install event - cache static assets
|
||||
self.addEventListener('install', event => {
|
||||
console.log('Service Worker: Installing...')
|
||||
|
||||
event.waitUntil(
|
||||
caches.open(STATIC_CACHE)
|
||||
.then(cache => {
|
||||
console.log('Service Worker: Caching static assets')
|
||||
return cache.addAll(STATIC_ASSETS)
|
||||
})
|
||||
.then(() => {
|
||||
console.log('Service Worker: Installation complete')
|
||||
return self.skipWaiting()
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Service Worker: Installation failed', error)
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
// Activate event - clean up old caches
|
||||
self.addEventListener('activate', event => {
|
||||
console.log('Service Worker: Activating...')
|
||||
|
||||
event.waitUntil(
|
||||
caches.keys()
|
||||
.then(cacheNames => {
|
||||
return Promise.all(
|
||||
cacheNames.map(cacheName => {
|
||||
if (cacheName !== CACHE_NAME && cacheName !== STATIC_CACHE && cacheName !== DYNAMIC_CACHE) {
|
||||
console.log('Service Worker: Deleting old cache', cacheName)
|
||||
return caches.delete(cacheName)
|
||||
}
|
||||
})
|
||||
)
|
||||
})
|
||||
.then(() => {
|
||||
console.log('Service Worker: Activation complete')
|
||||
return self.clients.claim()
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
// Fetch event - handle requests with appropriate caching strategy
|
||||
self.addEventListener('fetch', event => {
|
||||
const { request } = event
|
||||
const url = new URL(request.url)
|
||||
|
||||
// Skip non-GET requests
|
||||
if (request.method !== 'GET') return
|
||||
|
||||
// Skip cross-origin requests
|
||||
if (url.origin !== location.origin) return
|
||||
|
||||
event.respondWith(handleFetch(request))
|
||||
})
|
||||
|
||||
// Determine caching strategy based on file type
|
||||
function getCachingStrategy(url) {
|
||||
const pathname = new URL(url).pathname.toLowerCase()
|
||||
|
||||
// Check for cache-first assets
|
||||
if (CACHE_STRATEGIES.CACHE_FIRST.some(ext => pathname.includes(ext))) {
|
||||
return 'cache-first'
|
||||
}
|
||||
|
||||
// Check for network-first assets
|
||||
if (CACHE_STRATEGIES.NETWORK_FIRST.some(path => pathname.includes(path))) {
|
||||
return 'network-first'
|
||||
}
|
||||
|
||||
// Check for stale-while-revalidate assets
|
||||
if (CACHE_STRATEGIES.STALE_WHILE_REVALIDATE.some(ext => pathname.includes(ext))) {
|
||||
return 'stale-while-revalidate'
|
||||
}
|
||||
|
||||
// Default to network-first
|
||||
return 'network-first'
|
||||
}
|
||||
|
||||
// Handle fetch requests with appropriate strategy
|
||||
async function handleFetch(request) {
|
||||
const strategy = getCachingStrategy(request.url)
|
||||
|
||||
switch (strategy) {
|
||||
case 'cache-first':
|
||||
return cacheFirst(request)
|
||||
case 'network-first':
|
||||
return networkFirst(request)
|
||||
case 'stale-while-revalidate':
|
||||
return staleWhileRevalidate(request)
|
||||
default:
|
||||
return networkFirst(request)
|
||||
}
|
||||
}
|
||||
|
||||
// Cache-first strategy
|
||||
async function cacheFirst(request) {
|
||||
try {
|
||||
const cache = await caches.open(STATIC_CACHE)
|
||||
const cachedResponse = await cache.match(request)
|
||||
|
||||
if (cachedResponse) {
|
||||
return cachedResponse
|
||||
}
|
||||
|
||||
const networkResponse = await fetch(request)
|
||||
if (networkResponse.ok) {
|
||||
cache.put(request, networkResponse.clone())
|
||||
}
|
||||
return networkResponse
|
||||
} catch (error) {
|
||||
console.error('Cache-first strategy failed:', error)
|
||||
return new Response('Offline content unavailable', { status: 503 })
|
||||
}
|
||||
}
|
||||
|
||||
// Network-first strategy
|
||||
async function networkFirst(request) {
|
||||
try {
|
||||
const networkResponse = await fetch(request)
|
||||
|
||||
if (networkResponse.ok) {
|
||||
const cache = await caches.open(DYNAMIC_CACHE)
|
||||
cache.put(request, networkResponse.clone())
|
||||
}
|
||||
|
||||
return networkResponse
|
||||
} catch (error) {
|
||||
console.log('Network failed, trying cache:', error)
|
||||
|
||||
const cache = await caches.open(DYNAMIC_CACHE)
|
||||
const cachedResponse = await cache.match(request)
|
||||
|
||||
if (cachedResponse) {
|
||||
return cachedResponse
|
||||
}
|
||||
|
||||
return new Response('Content unavailable offline', {
|
||||
status: 503,
|
||||
headers: { 'Content-Type': 'text/plain' }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Stale-while-revalidate strategy
|
||||
async function staleWhileRevalidate(request) {
|
||||
const cache = await caches.open(DYNAMIC_CACHE)
|
||||
const cachedResponse = await cache.match(request)
|
||||
|
||||
const networkUpdate = fetch(request).then(response => {
|
||||
if (response.ok) {
|
||||
cache.put(request, response.clone())
|
||||
}
|
||||
return response
|
||||
})
|
||||
|
||||
return cachedResponse || networkUpdate
|
||||
}
|
||||
|
||||
// Background sync for offline form submissions
|
||||
self.addEventListener('sync', event => {
|
||||
if (event.tag === 'donation-submission') {
|
||||
event.waitUntil(syncDonations())
|
||||
}
|
||||
if (event.tag === 'assistance-request') {
|
||||
event.waitUntil(syncAssistanceRequests())
|
||||
}
|
||||
})
|
||||
|
||||
// Sync offline donations
|
||||
async function syncDonations() {
|
||||
try {
|
||||
const donations = await getOfflineData('pending-donations')
|
||||
for (const donation of donations) {
|
||||
await fetch('/api/donations', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(donation),
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
})
|
||||
}
|
||||
await clearOfflineData('pending-donations')
|
||||
console.log('Offline donations synced successfully')
|
||||
} catch (error) {
|
||||
console.error('Failed to sync donations:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// Sync offline assistance requests
|
||||
async function syncAssistanceRequests() {
|
||||
try {
|
||||
const requests = await getOfflineData('pending-requests')
|
||||
for (const request of requests) {
|
||||
await fetch('/api/assistance-requests', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(request),
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
})
|
||||
}
|
||||
await clearOfflineData('pending-requests')
|
||||
console.log('Offline assistance requests synced successfully')
|
||||
} catch (error) {
|
||||
console.error('Failed to sync assistance requests:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// Helper functions for offline data management
|
||||
function getOfflineData(key) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const request = indexedDB.open('MiraclesInMotion', 1)
|
||||
|
||||
request.onsuccess = () => {
|
||||
const db = request.result
|
||||
const transaction = db.transaction(['offlineData'], 'readonly')
|
||||
const store = transaction.objectStore('offlineData')
|
||||
const getRequest = store.get(key)
|
||||
|
||||
getRequest.onsuccess = () => {
|
||||
resolve(getRequest.result?.data || [])
|
||||
}
|
||||
|
||||
getRequest.onerror = () => reject(getRequest.error)
|
||||
}
|
||||
|
||||
request.onerror = () => reject(request.error)
|
||||
})
|
||||
}
|
||||
|
||||
function clearOfflineData(key) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const request = indexedDB.open('MiraclesInMotion', 1)
|
||||
|
||||
request.onsuccess = () => {
|
||||
const db = request.result
|
||||
const transaction = db.transaction(['offlineData'], 'readwrite')
|
||||
const store = transaction.objectStore('offlineData')
|
||||
const deleteRequest = store.delete(key)
|
||||
|
||||
deleteRequest.onsuccess = () => resolve()
|
||||
deleteRequest.onerror = () => reject(deleteRequest.error)
|
||||
}
|
||||
|
||||
request.onerror = () => reject(request.error)
|
||||
})
|
||||
}
|
||||
|
||||
// Push notification handling
|
||||
self.addEventListener('push', event => {
|
||||
const options = {
|
||||
body: event.data?.text() || 'New update available',
|
||||
icon: '/favicon.svg',
|
||||
badge: '/favicon.svg',
|
||||
vibrate: [200, 100, 200],
|
||||
data: {
|
||||
timestamp: Date.now(),
|
||||
url: '/'
|
||||
},
|
||||
actions: [
|
||||
{
|
||||
action: 'view',
|
||||
title: 'View',
|
||||
icon: '/favicon.svg'
|
||||
},
|
||||
{
|
||||
action: 'close',
|
||||
title: 'Close'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
event.waitUntil(
|
||||
self.registration.showNotification('Miracles in Motion', options)
|
||||
)
|
||||
})
|
||||
|
||||
// Notification click handling
|
||||
self.addEventListener('notificationclick', event => {
|
||||
event.notification.close()
|
||||
|
||||
if (event.action === 'view') {
|
||||
event.waitUntil(
|
||||
clients.openWindow(event.notification.data.url)
|
||||
)
|
||||
}
|
||||
// Miracles in Motion - Service Worker
|
||||
// Version 1.0.0
|
||||
|
||||
const CACHE_NAME = 'miracles-in-motion-v1'
|
||||
const STATIC_CACHE = 'static-v1'
|
||||
const DYNAMIC_CACHE = 'dynamic-v1'
|
||||
|
||||
// Assets to cache immediately
|
||||
const STATIC_ASSETS = [
|
||||
'/',
|
||||
'/index.html',
|
||||
'/favicon.svg',
|
||||
'/robots.txt',
|
||||
'/site.webmanifest'
|
||||
]
|
||||
|
||||
// Assets to cache on demand
|
||||
const CACHE_STRATEGIES = {
|
||||
// Cache first for static assets
|
||||
CACHE_FIRST: ['.js', '.css', '.woff', '.woff2', '.ttf', '.eot'],
|
||||
// Network first for API calls
|
||||
NETWORK_FIRST: ['/api/', '/analytics/'],
|
||||
// Stale while revalidate for images
|
||||
STALE_WHILE_REVALIDATE: ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg']
|
||||
}
|
||||
|
||||
// Install event - cache static assets
|
||||
self.addEventListener('install', event => {
|
||||
console.log('Service Worker: Installing...')
|
||||
|
||||
event.waitUntil(
|
||||
caches.open(STATIC_CACHE)
|
||||
.then(cache => {
|
||||
console.log('Service Worker: Caching static assets')
|
||||
return cache.addAll(STATIC_ASSETS)
|
||||
})
|
||||
.then(() => {
|
||||
console.log('Service Worker: Installation complete')
|
||||
return self.skipWaiting()
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Service Worker: Installation failed', error)
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
// Activate event - clean up old caches
|
||||
self.addEventListener('activate', event => {
|
||||
console.log('Service Worker: Activating...')
|
||||
|
||||
event.waitUntil(
|
||||
caches.keys()
|
||||
.then(cacheNames => {
|
||||
return Promise.all(
|
||||
cacheNames.map(cacheName => {
|
||||
if (cacheName !== CACHE_NAME && cacheName !== STATIC_CACHE && cacheName !== DYNAMIC_CACHE) {
|
||||
console.log('Service Worker: Deleting old cache', cacheName)
|
||||
return caches.delete(cacheName)
|
||||
}
|
||||
})
|
||||
)
|
||||
})
|
||||
.then(() => {
|
||||
console.log('Service Worker: Activation complete')
|
||||
return self.clients.claim()
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
// Fetch event - handle requests with appropriate caching strategy
|
||||
self.addEventListener('fetch', event => {
|
||||
const { request } = event
|
||||
const url = new URL(request.url)
|
||||
|
||||
// Skip non-GET requests
|
||||
if (request.method !== 'GET') return
|
||||
|
||||
// Skip cross-origin requests
|
||||
if (url.origin !== location.origin) return
|
||||
|
||||
event.respondWith(handleFetch(request))
|
||||
})
|
||||
|
||||
// Determine caching strategy based on file type
|
||||
function getCachingStrategy(url) {
|
||||
const pathname = new URL(url).pathname.toLowerCase()
|
||||
|
||||
// Check for cache-first assets
|
||||
if (CACHE_STRATEGIES.CACHE_FIRST.some(ext => pathname.includes(ext))) {
|
||||
return 'cache-first'
|
||||
}
|
||||
|
||||
// Check for network-first assets
|
||||
if (CACHE_STRATEGIES.NETWORK_FIRST.some(path => pathname.includes(path))) {
|
||||
return 'network-first'
|
||||
}
|
||||
|
||||
// Check for stale-while-revalidate assets
|
||||
if (CACHE_STRATEGIES.STALE_WHILE_REVALIDATE.some(ext => pathname.includes(ext))) {
|
||||
return 'stale-while-revalidate'
|
||||
}
|
||||
|
||||
// Default to network-first
|
||||
return 'network-first'
|
||||
}
|
||||
|
||||
// Handle fetch requests with appropriate strategy
|
||||
async function handleFetch(request) {
|
||||
const strategy = getCachingStrategy(request.url)
|
||||
|
||||
switch (strategy) {
|
||||
case 'cache-first':
|
||||
return cacheFirst(request)
|
||||
case 'network-first':
|
||||
return networkFirst(request)
|
||||
case 'stale-while-revalidate':
|
||||
return staleWhileRevalidate(request)
|
||||
default:
|
||||
return networkFirst(request)
|
||||
}
|
||||
}
|
||||
|
||||
// Cache-first strategy
|
||||
async function cacheFirst(request) {
|
||||
try {
|
||||
const cache = await caches.open(STATIC_CACHE)
|
||||
const cachedResponse = await cache.match(request)
|
||||
|
||||
if (cachedResponse) {
|
||||
return cachedResponse
|
||||
}
|
||||
|
||||
const networkResponse = await fetch(request)
|
||||
if (networkResponse.ok) {
|
||||
cache.put(request, networkResponse.clone())
|
||||
}
|
||||
return networkResponse
|
||||
} catch (error) {
|
||||
console.error('Cache-first strategy failed:', error)
|
||||
return new Response('Offline content unavailable', { status: 503 })
|
||||
}
|
||||
}
|
||||
|
||||
// Network-first strategy
|
||||
async function networkFirst(request) {
|
||||
try {
|
||||
const networkResponse = await fetch(request)
|
||||
|
||||
if (networkResponse.ok) {
|
||||
const cache = await caches.open(DYNAMIC_CACHE)
|
||||
cache.put(request, networkResponse.clone())
|
||||
}
|
||||
|
||||
return networkResponse
|
||||
} catch (error) {
|
||||
console.log('Network failed, trying cache:', error)
|
||||
|
||||
const cache = await caches.open(DYNAMIC_CACHE)
|
||||
const cachedResponse = await cache.match(request)
|
||||
|
||||
if (cachedResponse) {
|
||||
return cachedResponse
|
||||
}
|
||||
|
||||
return new Response('Content unavailable offline', {
|
||||
status: 503,
|
||||
headers: { 'Content-Type': 'text/plain' }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Stale-while-revalidate strategy
|
||||
async function staleWhileRevalidate(request) {
|
||||
const cache = await caches.open(DYNAMIC_CACHE)
|
||||
const cachedResponse = await cache.match(request)
|
||||
|
||||
const networkUpdate = fetch(request).then(response => {
|
||||
if (response.ok) {
|
||||
cache.put(request, response.clone())
|
||||
}
|
||||
return response
|
||||
})
|
||||
|
||||
return cachedResponse || networkUpdate
|
||||
}
|
||||
|
||||
// Background sync for offline form submissions
|
||||
self.addEventListener('sync', event => {
|
||||
if (event.tag === 'donation-submission') {
|
||||
event.waitUntil(syncDonations())
|
||||
}
|
||||
if (event.tag === 'assistance-request') {
|
||||
event.waitUntil(syncAssistanceRequests())
|
||||
}
|
||||
})
|
||||
|
||||
// Sync offline donations
|
||||
async function syncDonations() {
|
||||
try {
|
||||
const donations = await getOfflineData('pending-donations')
|
||||
for (const donation of donations) {
|
||||
await fetch('/api/donations', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(donation),
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
})
|
||||
}
|
||||
await clearOfflineData('pending-donations')
|
||||
console.log('Offline donations synced successfully')
|
||||
} catch (error) {
|
||||
console.error('Failed to sync donations:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// Sync offline assistance requests
|
||||
async function syncAssistanceRequests() {
|
||||
try {
|
||||
const requests = await getOfflineData('pending-requests')
|
||||
for (const request of requests) {
|
||||
await fetch('/api/assistance-requests', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(request),
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
})
|
||||
}
|
||||
await clearOfflineData('pending-requests')
|
||||
console.log('Offline assistance requests synced successfully')
|
||||
} catch (error) {
|
||||
console.error('Failed to sync assistance requests:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// Helper functions for offline data management
|
||||
function getOfflineData(key) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const request = indexedDB.open('MiraclesInMotion', 1)
|
||||
|
||||
request.onsuccess = () => {
|
||||
const db = request.result
|
||||
const transaction = db.transaction(['offlineData'], 'readonly')
|
||||
const store = transaction.objectStore('offlineData')
|
||||
const getRequest = store.get(key)
|
||||
|
||||
getRequest.onsuccess = () => {
|
||||
resolve(getRequest.result?.data || [])
|
||||
}
|
||||
|
||||
getRequest.onerror = () => reject(getRequest.error)
|
||||
}
|
||||
|
||||
request.onerror = () => reject(request.error)
|
||||
})
|
||||
}
|
||||
|
||||
function clearOfflineData(key) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const request = indexedDB.open('MiraclesInMotion', 1)
|
||||
|
||||
request.onsuccess = () => {
|
||||
const db = request.result
|
||||
const transaction = db.transaction(['offlineData'], 'readwrite')
|
||||
const store = transaction.objectStore('offlineData')
|
||||
const deleteRequest = store.delete(key)
|
||||
|
||||
deleteRequest.onsuccess = () => resolve()
|
||||
deleteRequest.onerror = () => reject(deleteRequest.error)
|
||||
}
|
||||
|
||||
request.onerror = () => reject(request.error)
|
||||
})
|
||||
}
|
||||
|
||||
// Push notification handling
|
||||
self.addEventListener('push', event => {
|
||||
const options = {
|
||||
body: event.data?.text() || 'New update available',
|
||||
icon: '/favicon.svg',
|
||||
badge: '/favicon.svg',
|
||||
vibrate: [200, 100, 200],
|
||||
data: {
|
||||
timestamp: Date.now(),
|
||||
url: '/'
|
||||
},
|
||||
actions: [
|
||||
{
|
||||
action: 'view',
|
||||
title: 'View',
|
||||
icon: '/favicon.svg'
|
||||
},
|
||||
{
|
||||
action: 'close',
|
||||
title: 'Close'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
event.waitUntil(
|
||||
self.registration.showNotification('Miracles in Motion', options)
|
||||
)
|
||||
})
|
||||
|
||||
// Notification click handling
|
||||
self.addEventListener('notificationclick', event => {
|
||||
event.notification.close()
|
||||
|
||||
if (event.action === 'view') {
|
||||
event.waitUntil(
|
||||
clients.openWindow(event.notification.data.url)
|
||||
)
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user