All files / src/components OfflineOverlay.tsx

98.68% Statements 75/76
87.5% Branches 7/8
100% Functions 3/3
98.68% Lines 75/76

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 1091x 1x 1x 1x 1x 1x   1x                                 1x 1x 25x 25x 25x     25x 12x 12x 12x 12x 12x 12x 12x 12x 12x 12x 25x   25x 25x   25x   25x 25x 25x 25x 25x   25x 25x 25x 25x 25x 25x 25x 25x 25x 25x 25x 25x 25x   25x 25x 25x 25x 25x 25x 25x 25x 25x   25x 25x   25x 25x 25x 25x   25x     25x 25x 25x 25x 25x 25x 25x 25x 25x 25x 25x 25x 25x         25x  
import { i18n } from '@/helpers/i18n';
import { ThemeContext } from '@/themes';
import Box from '@mui/material/Box';
import Modal from '@mui/material/Modal';
import Typography from '@mui/material/Typography';
import { useContext, useEffect, useState } from 'react';
 
/**
 * Full-screen overlay shown whenever the browser reports it is offline. It
 * listens for the `window` `offline`/`online` events and covers the whole UI
 * with a "network connection issue" message that clears itself once the
 * connection returns. The Chrome offline dino (theme-matched) sits across the
 * bottom.
 * @component
 * @category Components
 * @returns The offline overlay (renders nothing while online).
 * @example
 * ```tsx
 * <ThemeProvider>
 *   <OfflineOverlay />
 *   <App />
 * </ThemeProvider>
 * ```
 * @source
 */
export default function OfflineOverlay() {
  const themeContext = useContext(ThemeContext);
  const [offline, setOffline] = useState(
    () => typeof navigator !== 'undefined' && !navigator.onLine,
  );
 
  useEffect(() => {
    const handleOffline = () => setOffline(true);
    const handleOnline = () => setOffline(false);
    window.addEventListener('offline', handleOffline);
    window.addEventListener('online', handleOnline);
    // Reconcile in case connectivity changed between the initial render and mount.
    setOffline(!navigator.onLine);
    return () => {
      window.removeEventListener('offline', handleOffline);
      window.removeEventListener('online', handleOnline);
    };
  }, []);
 
  const dinoSrc =
    themeContext?.mode === 'dark'
      ? '/static/images/dino-dark-theme.png'
      : '/static/images/dino-light-theme.png';
 
  return (
    <Modal
      open={offline}
      aria-labelledby="offline-overlay-title"
      aria-describedby="offline-overlay-message"
    >
      <Box
        sx={{
          position: 'fixed',
          inset: 0,
          display: 'flex',
          flexDirection: 'column',
          alignItems: 'center',
          textAlign: 'center',
          bgcolor: 'background.default',
          color: 'text.primary',
          p: 3,
          outline: 'none',
        }}
      >
        <Box
          sx={{
            flex: 1,
            display: 'flex',
            flexDirection: 'column',
            alignItems: 'center',
            justifyContent: 'center',
            gap: 1,
          }}
        >
          <Typography id="offline-overlay-title" variant="h6" component="h2">
            {i18n('offline_title')}
          </Typography>
          <Typography
            id="offline-overlay-message"
            variant="body2"
            sx={{ maxWidth: 320, color: 'text.secondary' }}
          >
            {i18n('offline_message')}
          </Typography>
        </Box>
        <Box
          component="img"
          data-testid="offline-dino"
          src={dinoSrc}
          alt=""
          sx={{
            width: '100%',
            //maxWidth: 260,
            height: 'auto',
            imageRendering: 'pixelated',
            mt: 'auto',
            pb: 1,
          }}
        />
      </Box>
    </Modal>
  );
}