Add PWA support and fix PO BOX display in address
- Add comprehensive PWA configuration with manifest, service worker, and meta tags - Replace Android APK download with cross-platform PWA installation - Fix church address to include PO BOX information from API - Update contact page and footer to properly display multi-line addresses - Enable native app-like experience on all platforms without app store requirements
This commit is contained in:
parent
5f6430a4ee
commit
16caf6c3c4
32
astro-church-website/public/icons/app-icon.svg
Normal file
32
astro-church-website/public/icons/app-icon.svg
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="gradient" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||||
|
<stop offset="0%" stop-color="#d4af37" />
|
||||||
|
<stop offset="100%" stop-color="#b8941f" />
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
|
||||||
|
<!-- Background circle -->
|
||||||
|
<circle cx="256" cy="256" r="240" fill="#1a1a1a" stroke="url(#gradient)" stroke-width="8"/>
|
||||||
|
|
||||||
|
<!-- Church building -->
|
||||||
|
<rect x="180" y="300" width="152" height="120" fill="url(#gradient)" rx="4"/>
|
||||||
|
|
||||||
|
<!-- Church roof -->
|
||||||
|
<polygon points="256,200 160,280 352,280" fill="#d4af37"/>
|
||||||
|
|
||||||
|
<!-- Cross on top -->
|
||||||
|
<rect x="248" y="160" width="16" height="60" fill="#fff" rx="2"/>
|
||||||
|
<rect x="234" y="174" width="44" height="16" fill="#fff" rx="2"/>
|
||||||
|
|
||||||
|
<!-- Door -->
|
||||||
|
<rect x="236" y="340" width="40" height="80" fill="#1a1a1a" rx="20"/>
|
||||||
|
|
||||||
|
<!-- Windows -->
|
||||||
|
<rect x="200" y="320" width="24" height="32" fill="#1a1a1a" rx="4"/>
|
||||||
|
<rect x="288" y="320" width="24" height="32" fill="#1a1a1a" rx="4"/>
|
||||||
|
|
||||||
|
<!-- Bell tower -->
|
||||||
|
<rect x="230" y="220" width="52" height="80" fill="url(#gradient)" rx="4"/>
|
||||||
|
<rect x="238" y="210" width="36" height="20" fill="#d4af37" rx="2"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.2 KiB |
63
astro-church-website/public/manifest.json
Normal file
63
astro-church-website/public/manifest.json
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
{
|
||||||
|
"name": "Rockville Tolland SDA Church",
|
||||||
|
"short_name": "RTSDA Church",
|
||||||
|
"description": "Official website and app for Rockville Tolland Seventh-day Adventist Church",
|
||||||
|
"start_url": "/",
|
||||||
|
"display": "standalone",
|
||||||
|
"background_color": "#1a1a1a",
|
||||||
|
"theme_color": "#d4af37",
|
||||||
|
"orientation": "portrait-primary",
|
||||||
|
"categories": ["religion", "lifestyle", "education"],
|
||||||
|
"lang": "en-US",
|
||||||
|
"scope": "/",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "/favicon.svg",
|
||||||
|
"sizes": "72x72",
|
||||||
|
"type": "image/svg+xml",
|
||||||
|
"purpose": "any"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/favicon.svg",
|
||||||
|
"sizes": "96x96",
|
||||||
|
"type": "image/svg+xml",
|
||||||
|
"purpose": "any"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/favicon.svg",
|
||||||
|
"sizes": "128x128",
|
||||||
|
"type": "image/svg+xml",
|
||||||
|
"purpose": "any"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/favicon.svg",
|
||||||
|
"sizes": "144x144",
|
||||||
|
"type": "image/svg+xml",
|
||||||
|
"purpose": "any"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/favicon.svg",
|
||||||
|
"sizes": "152x152",
|
||||||
|
"type": "image/svg+xml",
|
||||||
|
"purpose": "any"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/favicon.svg",
|
||||||
|
"sizes": "192x192",
|
||||||
|
"type": "image/svg+xml",
|
||||||
|
"purpose": "any maskable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/favicon.svg",
|
||||||
|
"sizes": "384x384",
|
||||||
|
"type": "image/svg+xml",
|
||||||
|
"purpose": "any"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/favicon.svg",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"type": "image/svg+xml",
|
||||||
|
"purpose": "any maskable"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
43
astro-church-website/public/sw.js
Normal file
43
astro-church-website/public/sw.js
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
const CACHE_NAME = 'rtsda-church-v1';
|
||||||
|
const urlsToCache = [
|
||||||
|
'/',
|
||||||
|
'/about',
|
||||||
|
'/contact',
|
||||||
|
'/sermons',
|
||||||
|
'/events',
|
||||||
|
'/live',
|
||||||
|
'/manifest.json'
|
||||||
|
];
|
||||||
|
|
||||||
|
self.addEventListener('install', (event) => {
|
||||||
|
event.waitUntil(
|
||||||
|
caches.open(CACHE_NAME)
|
||||||
|
.then((cache) => {
|
||||||
|
return cache.addAll(urlsToCache);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
self.addEventListener('fetch', (event) => {
|
||||||
|
event.respondWith(
|
||||||
|
caches.match(event.request)
|
||||||
|
.then((response) => {
|
||||||
|
// Return cached version or fetch from network
|
||||||
|
return response || fetch(event.request);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
self.addEventListener('activate', (event) => {
|
||||||
|
event.waitUntil(
|
||||||
|
caches.keys().then((cacheNames) => {
|
||||||
|
return Promise.all(
|
||||||
|
cacheNames.map((cacheName) => {
|
||||||
|
if (cacheName !== CACHE_NAME) {
|
||||||
|
return caches.delete(cacheName);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
|
@ -149,7 +149,11 @@ const currentYear = new Date().getFullYear();
|
||||||
{address && (
|
{address && (
|
||||||
<div class="flex items-start space-x-2 text-sm text-gray-300">
|
<div class="flex items-start space-x-2 text-sm text-gray-300">
|
||||||
<i data-lucide="map-pin" class="w-4 h-4 text-gold-400 mt-0.5 flex-shrink-0"></i>
|
<i data-lucide="map-pin" class="w-4 h-4 text-gold-400 mt-0.5 flex-shrink-0"></i>
|
||||||
<span>{address}</span>
|
<div class="leading-relaxed">
|
||||||
|
{address.split('\n').map(line => (
|
||||||
|
<div class="mb-1 last:mb-0">{line}</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{phone && (
|
{phone && (
|
||||||
|
@ -222,17 +226,15 @@ const currentYear = new Date().getFullYear();
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<!-- Android APK Download -->
|
<!-- PWA Installation -->
|
||||||
<button onclick="downloadApk()" class="app-download-btn w-full text-left">
|
<button onclick="installPWA()" class="app-download-btn w-full text-left">
|
||||||
<div class="flex items-center space-x-3">
|
<div class="flex items-center space-x-3">
|
||||||
<div class="w-8 h-8 bg-gradient-to-br from-primary-600 to-primary-700 rounded-lg flex items-center justify-center">
|
<div class="w-8 h-8 bg-gradient-to-br from-primary-600 to-primary-700 rounded-lg flex items-center justify-center">
|
||||||
<svg viewBox="0 0 24 24" class="w-4 h-4 fill-gold-400">
|
<i data-lucide="smartphone" class="w-4 h-4 text-gold-400"></i>
|
||||||
<path d="M17.6 9.48l1.84-3.18c.16-.31.04-.69-.26-.85a.637.637 0 0 0-.83.22l-1.88 3.24a11.463 11.463 0 0 0-8.94 0L5.65 5.67a.643.643 0 0 0-.87-.2c-.28.18-.37.54-.22.83L6.4 9.48A10.78 10.78 0 0 0 1 18h22a10.78 10.78 0 0 0-5.4-8.52zM7 15.25a1.25 1.25 0 1 1 0-2.5 1.25 1.25 0 0 1 0 2.5zm10 0a1.25 1.25 0 1 1 0-2.5 1.25 1.25 0 0 1 0 2.5z"/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="text-left">
|
<div class="text-left">
|
||||||
<div class="text-xs text-gray-400">Download APK</div>
|
<div class="text-xs text-gray-400">Install as App</div>
|
||||||
<div class="text-sm font-medium text-white">Android</div>
|
<div class="text-sm font-medium text-white">Mobile & Desktop</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
|
@ -279,8 +281,71 @@ const currentYear = new Date().getFullYear();
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script is:inline>
|
<script is:inline>
|
||||||
// Make downloadApk function globally available
|
let deferredPrompt = null;
|
||||||
window.downloadApk = function() {
|
|
||||||
window.location.href = 'https://api.rockvilletollandsda.church/uploads/rtsda_android/current';
|
// Listen for the beforeinstallprompt event
|
||||||
|
window.addEventListener('beforeinstallprompt', (e) => {
|
||||||
|
console.log('beforeinstallprompt event fired');
|
||||||
|
e.preventDefault();
|
||||||
|
deferredPrompt = e;
|
||||||
|
});
|
||||||
|
|
||||||
|
// PWA installation handler
|
||||||
|
window.installPWA = function() {
|
||||||
|
if (deferredPrompt) {
|
||||||
|
// Show the install prompt
|
||||||
|
deferredPrompt.prompt();
|
||||||
|
deferredPrompt.userChoice.then((choiceResult) => {
|
||||||
|
if (choiceResult.outcome === 'accepted') {
|
||||||
|
console.log('User accepted the install prompt');
|
||||||
|
} else {
|
||||||
|
console.log('User dismissed the install prompt');
|
||||||
|
}
|
||||||
|
deferredPrompt = null;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Show manual instructions for different platforms
|
||||||
|
const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent);
|
||||||
|
const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
|
||||||
|
|
||||||
|
let instructions = '';
|
||||||
|
|
||||||
|
if (isIOS) {
|
||||||
|
instructions = `To install this app on iOS:
|
||||||
|
|
||||||
|
1. Tap the Share button (□↗) in Safari
|
||||||
|
2. Scroll down and tap "Add to Home Screen"
|
||||||
|
3. Tap "Add" to confirm
|
||||||
|
4. The app will appear on your home screen
|
||||||
|
|
||||||
|
This creates a full-screen app experience!`;
|
||||||
|
} else if (isMobile) {
|
||||||
|
instructions = `To install this app on Android:
|
||||||
|
|
||||||
|
1. Open this website in Chrome browser
|
||||||
|
2. Tap the menu (⋮) in the top right
|
||||||
|
3. Select "Add to Home screen" or "Install app"
|
||||||
|
4. Follow the prompts to install
|
||||||
|
5. The app will appear on your home screen
|
||||||
|
|
||||||
|
This creates a full-screen app experience!`;
|
||||||
|
} else {
|
||||||
|
instructions = `To install this app on Desktop:
|
||||||
|
|
||||||
|
Chrome/Edge:
|
||||||
|
1. Look for the install icon (⊕) in the address bar
|
||||||
|
2. Click it and select "Install"
|
||||||
|
3. The app will open in its own window
|
||||||
|
|
||||||
|
Firefox/Safari:
|
||||||
|
1. Click the menu button
|
||||||
|
2. Look for "Install" or "Add to Home Screen" option
|
||||||
|
3. Follow the prompts to install
|
||||||
|
|
||||||
|
This creates a dedicated app window without browser tabs!`;
|
||||||
|
}
|
||||||
|
|
||||||
|
alert(instructions);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
|
@ -16,6 +16,29 @@ const { title, description = 'Proclaiming the Three Angels\' Message with love a
|
||||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||||
<title>{title}</title>
|
<title>{title}</title>
|
||||||
|
|
||||||
|
<!-- PWA Meta Tags -->
|
||||||
|
<meta name="theme-color" content="#d4af37" />
|
||||||
|
<meta name="mobile-web-app-capable" content="yes" />
|
||||||
|
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||||
|
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
||||||
|
<meta name="apple-mobile-web-app-title" content="RTSDA Church" />
|
||||||
|
<meta name="application-name" content="RTSDA Church" />
|
||||||
|
<meta name="msapplication-TileColor" content="#d4af37" />
|
||||||
|
|
||||||
|
<!-- Web App Manifest -->
|
||||||
|
<link rel="manifest" href="/manifest.json" />
|
||||||
|
|
||||||
|
<!-- Apple Touch Icons -->
|
||||||
|
<link rel="apple-touch-icon" sizes="180x180" href="/favicon.svg" />
|
||||||
|
<link rel="apple-touch-icon" sizes="152x152" href="/favicon.svg" />
|
||||||
|
<link rel="apple-touch-icon" sizes="144x144" href="/favicon.svg" />
|
||||||
|
<link rel="apple-touch-icon" sizes="120x120" href="/favicon.svg" />
|
||||||
|
<link rel="apple-touch-icon" sizes="114x114" href="/favicon.svg" />
|
||||||
|
<link rel="apple-touch-icon" sizes="76x76" href="/favicon.svg" />
|
||||||
|
<link rel="apple-touch-icon" sizes="72x72" href="/favicon.svg" />
|
||||||
|
<link rel="apple-touch-icon" sizes="60x60" href="/favicon.svg" />
|
||||||
|
<link rel="apple-touch-icon" sizes="57x57" href="/favicon.svg" />
|
||||||
|
|
||||||
<!-- Preload Critical Fonts -->
|
<!-- Preload Critical Fonts -->
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
@ -117,6 +140,19 @@ const { title, description = 'Proclaiming the Three Angels\' Message with love a
|
||||||
document.querySelectorAll('[data-animate]').forEach(el => {
|
document.querySelectorAll('[data-animate]').forEach(el => {
|
||||||
observer.observe(el);
|
observer.observe(el);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Register Service Worker for PWA
|
||||||
|
if ('serviceWorker' in navigator) {
|
||||||
|
window.addEventListener('load', () => {
|
||||||
|
navigator.serviceWorker.register('/sw.js')
|
||||||
|
.then((registration) => {
|
||||||
|
console.log('SW registered: ', registration);
|
||||||
|
})
|
||||||
|
.catch((registrationError) => {
|
||||||
|
console.log('SW registration failed: ', registrationError);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
@ -18,6 +18,7 @@ try {
|
||||||
address = getChurchAddress();
|
address = getChurchAddress();
|
||||||
phone = getContactPhone();
|
phone = getContactPhone();
|
||||||
|
|
||||||
|
|
||||||
// Get the base email from church-core and make it dynamic based on current domain
|
// Get the base email from church-core and make it dynamic based on current domain
|
||||||
const baseEmail = getContactEmail();
|
const baseEmail = getContactEmail();
|
||||||
const currentUrl = Astro.url.hostname;
|
const currentUrl = Astro.url.hostname;
|
||||||
|
@ -152,7 +153,11 @@ try {
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h3 class="font-semibold text-gray-900 dark:text-white mb-1">Visit Us</h3>
|
<h3 class="font-semibold text-gray-900 dark:text-white mb-1">Visit Us</h3>
|
||||||
<p class="text-gray-600 dark:text-gray-300">{address}</p>
|
<div class="text-gray-600 dark:text-gray-300">
|
||||||
|
{address.split('\n').map(line => (
|
||||||
|
<p class="mb-1 last:mb-0">{line}</p>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -893,11 +893,31 @@ pub fn get_donation_url() -> String {
|
||||||
.unwrap_or_else(|| "https://adventistgiving.org/donate/ANRTOL".to_string())
|
.unwrap_or_else(|| "https://adventistgiving.org/donate/ANRTOL".to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get church address with fallback
|
/// Get church address with fallback - includes PO BOX if available
|
||||||
pub fn get_church_address() -> String {
|
pub fn get_church_address() -> String {
|
||||||
get_cached_config()
|
// Get the raw config JSON and parse it directly
|
||||||
.and_then(|config| config.church_address)
|
let config_json = fetch_config_json();
|
||||||
.unwrap_or_else(|| "115 Snipsic Lake Road, Tolland, CT 06084".to_string())
|
|
||||||
|
if let Ok(config_value) = serde_json::from_str::<serde_json::Value>(&config_json) {
|
||||||
|
let mut address_lines = Vec::new();
|
||||||
|
|
||||||
|
// Add physical address if available
|
||||||
|
if let Some(church_address) = config_value.get("church_address").and_then(|v| v.as_str()) {
|
||||||
|
address_lines.push(church_address.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add PO BOX as second line if available
|
||||||
|
if let Some(po_box) = config_value.get("po_box").and_then(|v| v.as_str()) {
|
||||||
|
address_lines.push(po_box.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
if !address_lines.is_empty() {
|
||||||
|
return address_lines.join("\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback if no config available
|
||||||
|
"9 Hartford Tpke Tolland CT 06084".to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get coordinates from config coordinates object or return empty vector
|
/// Get coordinates from config coordinates object or return empty vector
|
||||||
|
|
Loading…
Reference in a new issue