Deploy to production - ensure all endpoints operational

This commit is contained in:
defiQUG
2025-11-12 08:17:28 -08:00
parent b421d2964c
commit f1c61c8339
171 changed files with 50830 additions and 42363 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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
}

View File

@@ -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)
)
}
})