188 lines
6.2 KiB
JavaScript
188 lines
6.2 KiB
JavaScript
|
|
// PWA Service Worker Registration
|
||
|
|
if ('serviceWorker' in navigator) {
|
||
|
|
window.addEventListener('load', () => {
|
||
|
|
navigator.serviceWorker.register('/static/js/service-worker.js')
|
||
|
|
.then(registration => {
|
||
|
|
console.log('[PWA] Service Worker registered successfully:', registration.scope);
|
||
|
|
|
||
|
|
// Check for updates
|
||
|
|
registration.addEventListener('updatefound', () => {
|
||
|
|
const newWorker = registration.installing;
|
||
|
|
newWorker.addEventListener('statechange', () => {
|
||
|
|
if (newWorker.state === 'installed' && navigator.serviceWorker.controller) {
|
||
|
|
console.log('[PWA] New version available! Refresh to update.');
|
||
|
|
// Optionally show a notification to the user
|
||
|
|
if (confirm('A new version of FINA is available. Reload to update?')) {
|
||
|
|
newWorker.postMessage({ type: 'SKIP_WAITING' });
|
||
|
|
window.location.reload();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
});
|
||
|
|
});
|
||
|
|
})
|
||
|
|
.catch(error => {
|
||
|
|
console.log('[PWA] Service Worker registration failed:', error);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
// PWA Install Prompt
|
||
|
|
let deferredPrompt;
|
||
|
|
const installPrompt = document.getElementById('pwa-install-prompt');
|
||
|
|
const installBtn = document.getElementById('pwa-install-btn');
|
||
|
|
const dismissBtn = document.getElementById('pwa-dismiss-btn');
|
||
|
|
|
||
|
|
// Check if already installed (standalone mode)
|
||
|
|
const isInstalled = () => {
|
||
|
|
return window.matchMedia('(display-mode: standalone)').matches ||
|
||
|
|
window.navigator.standalone === true ||
|
||
|
|
document.referrer.includes('android-app://');
|
||
|
|
};
|
||
|
|
|
||
|
|
// Detect iOS
|
||
|
|
const isIOS = () => {
|
||
|
|
return /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
|
||
|
|
};
|
||
|
|
|
||
|
|
// Show iOS install instructions
|
||
|
|
function showIOSInstallPrompt() {
|
||
|
|
if (installPrompt) {
|
||
|
|
const promptText = installPrompt.querySelector('.pwa-prompt-text p');
|
||
|
|
if (promptText && isIOS() && !window.navigator.standalone) {
|
||
|
|
promptText.textContent = 'Tap Share button and then "Add to Home Screen"';
|
||
|
|
installBtn.style.display = 'none'; // Hide install button on iOS
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
window.addEventListener('beforeinstallprompt', (e) => {
|
||
|
|
// Prevent the default mini-infobar
|
||
|
|
e.preventDefault();
|
||
|
|
// Store the event for later use
|
||
|
|
deferredPrompt = e;
|
||
|
|
|
||
|
|
// Don't show if already installed
|
||
|
|
if (isInstalled()) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Show custom install prompt if not dismissed
|
||
|
|
const dismissed = localStorage.getItem('pwa-install-dismissed');
|
||
|
|
const dismissedUntil = parseInt(dismissed || '0');
|
||
|
|
|
||
|
|
if (Date.now() > dismissedUntil && installPrompt) {
|
||
|
|
installPrompt.style.display = 'block';
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
// Handle iOS separately
|
||
|
|
if (isIOS() && !isInstalled()) {
|
||
|
|
const dismissed = localStorage.getItem('pwa-install-dismissed');
|
||
|
|
const dismissedUntil = parseInt(dismissed || '0');
|
||
|
|
|
||
|
|
if (Date.now() > dismissedUntil && installPrompt) {
|
||
|
|
setTimeout(() => {
|
||
|
|
installPrompt.style.display = 'block';
|
||
|
|
showIOSInstallPrompt();
|
||
|
|
}, 2000); // Show after 2 seconds
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (installBtn) {
|
||
|
|
installBtn.addEventListener('click', async () => {
|
||
|
|
if (!deferredPrompt) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Show the install prompt
|
||
|
|
deferredPrompt.prompt();
|
||
|
|
|
||
|
|
// Wait for the user's response
|
||
|
|
const { outcome } = await deferredPrompt.userChoice;
|
||
|
|
console.log(`[PWA] User response: ${outcome}`);
|
||
|
|
|
||
|
|
// Clear the saved prompt since it can't be used again
|
||
|
|
deferredPrompt = null;
|
||
|
|
|
||
|
|
// Hide the prompt
|
||
|
|
installPrompt.style.display = 'none';
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
if (dismissBtn) {
|
||
|
|
dismissBtn.addEventListener('click', () => {
|
||
|
|
installPrompt.style.display = 'none';
|
||
|
|
// Remember dismissal for 7 days
|
||
|
|
localStorage.setItem('pwa-install-dismissed', Date.now() + (7 * 24 * 60 * 60 * 1000));
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
// Check if app is installed
|
||
|
|
window.addEventListener('appinstalled', () => {
|
||
|
|
console.log('[PWA] App installed successfully');
|
||
|
|
if (installPrompt) {
|
||
|
|
installPrompt.style.display = 'none';
|
||
|
|
}
|
||
|
|
localStorage.removeItem('pwa-install-dismissed');
|
||
|
|
});
|
||
|
|
|
||
|
|
// Online/Offline status
|
||
|
|
window.addEventListener('online', () => {
|
||
|
|
console.log('[PWA] Back online');
|
||
|
|
// Show notification or update UI
|
||
|
|
showToast('Connection restored', 'success');
|
||
|
|
});
|
||
|
|
|
||
|
|
window.addEventListener('offline', () => {
|
||
|
|
console.log('[PWA] Gone offline');
|
||
|
|
showToast('You are offline. Some features may be limited.', 'info');
|
||
|
|
});
|
||
|
|
|
||
|
|
// Toast notification function
|
||
|
|
function showToast(message, type = 'info') {
|
||
|
|
const toast = document.createElement('div');
|
||
|
|
toast.className = `alert alert-${type} glass-card`;
|
||
|
|
toast.textContent = message;
|
||
|
|
document.body.appendChild(toast);
|
||
|
|
|
||
|
|
setTimeout(() => {
|
||
|
|
toast.classList.add('hiding');
|
||
|
|
setTimeout(() => toast.remove(), 300);
|
||
|
|
}, 3000);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Language menu toggle
|
||
|
|
function toggleLanguageMenu() {
|
||
|
|
const menu = document.getElementById('language-menu');
|
||
|
|
menu.classList.toggle('show');
|
||
|
|
}
|
||
|
|
|
||
|
|
// Close language menu when clicking outside
|
||
|
|
document.addEventListener('click', function(event) {
|
||
|
|
const switcher = document.querySelector('.language-switcher');
|
||
|
|
const menu = document.getElementById('language-menu');
|
||
|
|
|
||
|
|
if (menu && switcher && !switcher.contains(event.target)) {
|
||
|
|
menu.classList.remove('show');
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
document.addEventListener('DOMContentLoaded', function() {
|
||
|
|
console.log('Finance Tracker loaded');
|
||
|
|
|
||
|
|
// Auto-hide flash messages after 2 seconds
|
||
|
|
const alerts = document.querySelectorAll('.alert');
|
||
|
|
|
||
|
|
alerts.forEach(function(alert) {
|
||
|
|
// Add hiding animation after 2 seconds
|
||
|
|
setTimeout(function() {
|
||
|
|
alert.classList.add('hiding');
|
||
|
|
|
||
|
|
// Remove from DOM after animation completes
|
||
|
|
setTimeout(function() {
|
||
|
|
alert.remove();
|
||
|
|
}, 300); // Wait for animation to finish
|
||
|
|
}, 2000); // Show for 2 seconds
|
||
|
|
});
|
||
|
|
});
|