Files
maternal-app/maternal-web/components/common/OfflineIndicator.tsx
andupetcu b62342fe2d Add missing pages with AppShell layout integration
- Created /track, /insights, /children, /family, /settings, /logout pages
- Wrapped all authenticated pages with AppShell and ProtectedRoute
- Updated AI assistant page to use AppShell layout
- All pages now have proper header/navigation and footer/tabbar
- Added responsive mobile and desktop layouts
- Integrated with existing navigation system

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-30 22:05:56 +03:00

159 lines
4.7 KiB
TypeScript

'use client';
import { useEffect, useState } from 'react';
import { Alert, LinearProgress, Box, Typography } from '@mui/material';
import { motion, AnimatePresence } from 'framer-motion';
import { CloudOff, CloudQueue, CloudDone } from '@mui/icons-material';
interface OfflineIndicatorProps {
isOnline?: boolean;
pendingActionsCount?: number;
syncInProgress?: boolean;
}
export const OfflineIndicator = ({
isOnline: propIsOnline,
pendingActionsCount = 0,
syncInProgress = false,
}: OfflineIndicatorProps) => {
const [isOnline, setIsOnline] = useState(true);
useEffect(() => {
// Set initial online status
setIsOnline(navigator.onLine);
// Listen for online/offline events
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);
};
}, []);
const effectiveIsOnline = propIsOnline !== undefined ? propIsOnline : isOnline;
return (
<>
<AnimatePresence>
{!effectiveIsOnline && (
<motion.div
initial={{ y: -100, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
exit={{ y: -100, opacity: 0 }}
transition={{ type: 'spring', stiffness: 300, damping: 30 }}
style={{
position: 'fixed',
top: 0,
left: 0,
right: 0,
zIndex: 9999,
}}
>
<Alert
severity="warning"
icon={<CloudOff />}
sx={{
borderRadius: 0,
boxShadow: 2,
}}
>
<Box>
<Typography variant="body2" fontWeight="600">
You're offline
</Typography>
{pendingActionsCount > 0 && (
<Typography variant="caption">
{pendingActionsCount} action{pendingActionsCount !== 1 ? 's' : ''} will sync when you're back online
</Typography>
)}
</Box>
</Alert>
</motion.div>
)}
{effectiveIsOnline && syncInProgress && (
<motion.div
initial={{ y: -100, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
exit={{ y: -100, opacity: 0 }}
transition={{ type: 'spring', stiffness: 300, damping: 30 }}
style={{
position: 'fixed',
top: 0,
left: 0,
right: 0,
zIndex: 9999,
}}
>
<Alert
severity="info"
icon={<CloudQueue />}
sx={{
borderRadius: 0,
boxShadow: 2,
}}
>
<Box>
<Typography variant="body2" fontWeight="600">
Syncing data...
</Typography>
{pendingActionsCount > 0 && (
<Typography variant="caption">
{pendingActionsCount} action{pendingActionsCount !== 1 ? 's' : ''} remaining
</Typography>
)}
</Box>
</Alert>
<LinearProgress />
</motion.div>
)}
{effectiveIsOnline && !syncInProgress && pendingActionsCount === 0 &&
typeof propIsOnline !== 'undefined' && propIsOnline && (
<motion.div
initial={{ y: -100, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
exit={{ y: -100, opacity: 0 }}
transition={{ type: 'spring', stiffness: 300, damping: 30 }}
onAnimationComplete={() => {
// Auto-hide after 3 seconds
setTimeout(() => {
const element = document.getElementById('sync-complete-alert');
if (element) {
element.style.display = 'none';
}
}, 3000);
}}
style={{
position: 'fixed',
top: 0,
left: 0,
right: 0,
zIndex: 9999,
}}
id="sync-complete-alert"
>
<Alert
severity="success"
icon={<CloudDone />}
sx={{
borderRadius: 0,
boxShadow: 2,
}}
>
<Typography variant="body2" fontWeight="600">
All data synced successfully!
</Typography>
</Alert>
</motion.div>
)}
</AnimatePresence>
</>
);
};