Files
maternal-app/maternal-web/app/offline/page.tsx
Andrei 898a76c83a
Some checks failed
CI/CD Pipeline / Lint and Test (push) Has been cancelled
CI/CD Pipeline / E2E Tests (push) Has been cancelled
CI/CD Pipeline / Build Application (push) Has been cancelled
feat: Complete PWA implementation with offline support and install prompts
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>
2025-10-03 07:38:47 +00:00

154 lines
3.9 KiB
TypeScript

'use client';
import { useEffect, useState } from 'react';
import { Box, Container, Typography, Button, Paper } from '@mui/material';
import { WifiOff, Refresh, Home } from '@mui/icons-material';
import { useRouter } from 'next/navigation';
export default function OfflinePage() {
const router = useRouter();
const [isOnline, setIsOnline] = useState(false);
useEffect(() => {
// Check online status
const handleOnline = () => setIsOnline(true);
const handleOffline = () => setIsOnline(false);
setIsOnline(navigator.onLine);
window.addEventListener('online', handleOnline);
window.addEventListener('offline', handleOffline);
return () => {
window.removeEventListener('online', handleOnline);
window.removeEventListener('offline', handleOffline);
};
}, []);
useEffect(() => {
// Redirect when back online
if (isOnline) {
router.push('/');
}
}, [isOnline, router]);
const handleRetry = () => {
if (navigator.onLine) {
router.push('/');
} else {
window.location.reload();
}
};
const handleGoHome = () => {
router.push('/');
};
return (
<Container maxWidth="sm">
<Box
sx={{
minHeight: '100vh',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
py: 4,
}}
>
<Paper
elevation={3}
sx={{
p: 4,
textAlign: 'center',
width: '100%',
}}
>
<WifiOff
sx={{
fontSize: 80,
color: 'error.main',
mb: 2,
}}
/>
<Typography
variant="h4"
component="h1"
gutterBottom
sx={{ fontWeight: 600 }}
>
You're Offline
</Typography>
<Typography
variant="body1"
color="text.secondary"
sx={{ mb: 4 }}
>
It looks like you've lost your internet connection. Some features may be limited while offline.
</Typography>
<Box sx={{ mb: 3 }}>
<Typography variant="h6" gutterBottom sx={{ fontWeight: 500 }}>
What you can still do:
</Typography>
<Box
component="ul"
sx={{
textAlign: 'left',
display: 'inline-block',
pl: 2,
}}
>
<li>
<Typography variant="body2" sx={{ mb: 1 }}>
View previously loaded activities and data
</Typography>
</li>
<li>
<Typography variant="body2" sx={{ mb: 1 }}>
Create new activities (will sync when back online)
</Typography>
</li>
<li>
<Typography variant="body2" sx={{ mb: 1 }}>
Access cached pages and information
</Typography>
</li>
</Box>
</Box>
<Box sx={{ display: 'flex', gap: 2, justifyContent: 'center' }}>
<Button
variant="contained"
startIcon={<Refresh />}
onClick={handleRetry}
size="large"
>
Try Again
</Button>
<Button
variant="outlined"
startIcon={<Home />}
onClick={handleGoHome}
size="large"
>
Go Home
</Button>
</Box>
{isOnline && (
<Typography
variant="body2"
color="success.main"
sx={{ mt: 2, fontWeight: 500 }}
>
Connection restored! Redirecting...
</Typography>
)}
</Paper>
</Box>
</Container>
);
}