PWA Features Implemented: ✅ Offline Fallback Page (/offline) - User-friendly offline page with connection status - Auto-redirect when back online - Lists available offline features - Retry and home navigation buttons ✅ Install Prompt UI (InstallPrompt component) - beforeinstallprompt event handler for Android/Desktop - iOS-specific install instructions with Share icon - Smart dismissal with 7-day cooldown - Already-installed detection ✅ Background Sync for Pending Actions - useBackgroundSync hook with multiple sync triggers - Periodic sync every 5 minutes when online - Sync on tab visibility change - Service Worker sync registration - BackgroundSyncProvider integration ✅ next-pwa Configuration Updates - Offline fallback to /offline page - Network timeout (10s) for better offline detection - skipWaiting and clientsClaim enabled - Runtime caching with NetworkFirst strategy Files Created: - app/offline/page.tsx (131 lines) - components/pwa/InstallPrompt.tsx (164 lines) - hooks/useBackgroundSync.ts (71 lines) - components/providers/BackgroundSyncProvider.tsx (10 lines) Files Modified: - app/layout.tsx (added InstallPrompt and BackgroundSyncProvider) - next.config.mjs (offline fallback + workbox options) Total: 376 new lines across 4 new files + 2 modified files 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
72 lines
2.4 KiB
TypeScript
72 lines
2.4 KiB
TypeScript
import { useEffect, useRef } from 'react';
|
|
import { useAppDispatch, useAppSelector } from '@/store/hooks';
|
|
import { setOnlineStatus } from '@/store/slices/networkSlice';
|
|
|
|
export function useBackgroundSync() {
|
|
const dispatch = useAppDispatch();
|
|
const { isOnline } = useAppSelector((state) => state.network);
|
|
const { pendingActions } = useAppSelector((state) => state.offline);
|
|
const syncIntervalRef = useRef<NodeJS.Timeout | null>(null);
|
|
|
|
useEffect(() => {
|
|
// Register background sync if supported
|
|
if ('serviceWorker' in navigator && 'sync' in ServiceWorkerRegistration.prototype) {
|
|
navigator.serviceWorker.ready.then((registration) => {
|
|
// Register a sync event
|
|
return (registration as any).sync.register('sync-pending-actions');
|
|
}).catch((error) => {
|
|
console.error('Background sync registration failed:', error);
|
|
});
|
|
}
|
|
|
|
// Listen for sync events
|
|
if ('serviceWorker' in navigator) {
|
|
navigator.serviceWorker.addEventListener('message', (event) => {
|
|
if (event.data && event.data.type === 'BACKGROUND_SYNC') {
|
|
// Trigger sync from service worker by simulating online status
|
|
dispatch(setOnlineStatus(true));
|
|
}
|
|
});
|
|
}
|
|
}, [dispatch]);
|
|
|
|
useEffect(() => {
|
|
// Periodic sync every 5 minutes when online and have pending actions
|
|
if (isOnline && pendingActions.length > 0) {
|
|
syncIntervalRef.current = setInterval(() => {
|
|
console.log('Periodic sync check...');
|
|
// Trigger sync by re-dispatching online status
|
|
dispatch(setOnlineStatus(true));
|
|
}, 5 * 60 * 1000); // 5 minutes
|
|
}
|
|
|
|
return () => {
|
|
if (syncIntervalRef.current) {
|
|
clearInterval(syncIntervalRef.current);
|
|
}
|
|
};
|
|
}, [isOnline, pendingActions.length, dispatch]);
|
|
|
|
// Sync on visibility change (when user returns to tab)
|
|
useEffect(() => {
|
|
const handleVisibilityChange = () => {
|
|
if (document.visibilityState === 'visible' && isOnline && pendingActions.length > 0) {
|
|
console.log('Tab visible, syncing pending actions...');
|
|
// Trigger sync by re-dispatching online status
|
|
dispatch(setOnlineStatus(true));
|
|
}
|
|
};
|
|
|
|
document.addEventListener('visibilitychange', handleVisibilityChange);
|
|
|
|
return () => {
|
|
document.removeEventListener('visibilitychange', handleVisibilityChange);
|
|
};
|
|
}, [isOnline, pendingActions.length, dispatch]);
|
|
|
|
return {
|
|
pendingCount: pendingActions.length,
|
|
isOnline,
|
|
};
|
|
}
|