105 lines
2.8 KiB
TypeScript
105 lines
2.8 KiB
TypeScript
|
|
'use client';
|
||
|
|
|
||
|
|
import { useEffect, useState, useCallback } from 'react';
|
||
|
|
|
||
|
|
export function useOnlineStatus() {
|
||
|
|
const [isOnline, setIsOnline] = useState(
|
||
|
|
typeof navigator !== 'undefined' ? navigator.onLine : true
|
||
|
|
);
|
||
|
|
|
||
|
|
useEffect(() => {
|
||
|
|
const handleOnline = () => setIsOnline(true);
|
||
|
|
const handleOffline = () => setIsOnline(false);
|
||
|
|
|
||
|
|
window.addEventListener('online', handleOnline);
|
||
|
|
window.addEventListener('offline', handleOffline);
|
||
|
|
|
||
|
|
return () => {
|
||
|
|
window.removeEventListener('online', handleOnline);
|
||
|
|
window.removeEventListener('offline', handleOffline);
|
||
|
|
};
|
||
|
|
}, []);
|
||
|
|
|
||
|
|
return isOnline;
|
||
|
|
}
|
||
|
|
|
||
|
|
export function usePWAInstall() {
|
||
|
|
const [deferredPrompt, setDeferredPrompt] = useState<any>(null);
|
||
|
|
const [isInstallable, setIsInstallable] = useState(false);
|
||
|
|
const [isInstalled, setIsInstalled] = useState(false);
|
||
|
|
|
||
|
|
useEffect(() => {
|
||
|
|
// Check if already installed
|
||
|
|
if (window.matchMedia('(display-mode: standalone)').matches) {
|
||
|
|
setIsInstalled(true);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
const handleBeforeInstall = (e: Event) => {
|
||
|
|
e.preventDefault();
|
||
|
|
setDeferredPrompt(e as any);
|
||
|
|
setIsInstallable(true);
|
||
|
|
};
|
||
|
|
|
||
|
|
const handleAppInstalled = () => {
|
||
|
|
setIsInstalled(true);
|
||
|
|
setIsInstallable(false);
|
||
|
|
setDeferredPrompt(null);
|
||
|
|
};
|
||
|
|
|
||
|
|
window.addEventListener('beforeinstallprompt', handleBeforeInstall);
|
||
|
|
window.addEventListener('appinstalled', handleAppInstalled);
|
||
|
|
|
||
|
|
return () => {
|
||
|
|
window.removeEventListener('beforeinstallprompt', handleBeforeInstall);
|
||
|
|
window.removeEventListener('appinstalled', handleAppInstalled);
|
||
|
|
};
|
||
|
|
}, []);
|
||
|
|
|
||
|
|
const install = useCallback(async () => {
|
||
|
|
if (!deferredPrompt) return false;
|
||
|
|
|
||
|
|
deferredPrompt.prompt();
|
||
|
|
const { outcome } = await deferredPrompt.userChoice;
|
||
|
|
setDeferredPrompt(null);
|
||
|
|
|
||
|
|
if (outcome === 'accepted') {
|
||
|
|
setIsInstalled(true);
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
return false;
|
||
|
|
}, [deferredPrompt]);
|
||
|
|
|
||
|
|
const dismiss = useCallback(() => {
|
||
|
|
setIsInstallable(false);
|
||
|
|
}, []);
|
||
|
|
|
||
|
|
return { isInstallable, isInstalled, install, dismiss };
|
||
|
|
}
|
||
|
|
|
||
|
|
export async function registerServiceWorker() {
|
||
|
|
if (typeof window === 'undefined' || !('serviceWorker' in navigator)) return null;
|
||
|
|
|
||
|
|
try {
|
||
|
|
const registration = await navigator.serviceWorker.register('/sw.js', {
|
||
|
|
scope: '/',
|
||
|
|
});
|
||
|
|
|
||
|
|
registration.addEventListener('updatefound', () => {
|
||
|
|
const newWorker = registration.installing;
|
||
|
|
if (newWorker) {
|
||
|
|
newWorker.addEventListener('statechange', () => {
|
||
|
|
if (newWorker.state === 'activated') {
|
||
|
|
// New SW activated - could notify user
|
||
|
|
console.log('[PWA] New service worker activated');
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
return registration;
|
||
|
|
} catch (error) {
|
||
|
|
console.error('[PWA] SW registration failed:', error);
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
}
|