← back to Watches

src/components/OfflineIndicator.jsx

102 lines

import React, { useState, useEffect } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { setupOnlineDetection, getOnlineStatus } from '../utils/pwaUtils';

/**
 * Offline Status Indicator Component
 * Shows a banner when the app is offline
 */
function OfflineIndicator() {
  const [isOnline, setIsOnline] = useState(getOnlineStatus());
  const [showToast, setShowToast] = useState(false);

  useEffect(() => {
    // Setup online/offline detection
    setupOnlineDetection((online) => {
      const wasOffline = !isOnline;
      setIsOnline(online);

      // Show toast when status changes
      if (online && wasOffline) {
        setShowToast(true);
        setTimeout(() => setShowToast(false), 3000);
      }
    });
  }, [isOnline]);

  // Persistent offline banner at top
  if (!isOnline) {
    return (
      <motion.div
        initial={{ y: -50, opacity: 0 }}
        animate={{ y: 0, opacity: 1 }}
        exit={{ y: -50, opacity: 0 }}
        className="fixed top-0 left-0 right-0 z-50 bg-yellow-500 text-gray-900"
      >
        <div className="container mx-auto px-4 py-2">
          <div className="flex items-center justify-center gap-3">
            <svg
              className="w-5 h-5 animate-pulse"
              fill="none"
              stroke="currentColor"
              viewBox="0 0 24 24"
            >
              <path
                strokeLinecap="round"
                strokeLinejoin="round"
                strokeWidth={2}
                d="M18.364 5.636a9 9 0 010 12.728m0 0l-2.829-2.829m2.829 2.829L21 21M15.536 8.464a5 5 0 010 7.072m0 0l-2.829-2.829m-4.243 2.829a4.978 4.978 0 01-1.414-2.83m-1.414 5.658a9 9 0 01-2.167-9.238m7.824 2.167a1 1 0 111.414 1.414m-1.414-1.414L3 3m8.293 8.293l1.414 1.414"
              />
            </svg>
            <span className="font-semibold text-sm md:text-base">
              You're offline - Browsing cached data
            </span>
            <svg
              className="w-4 h-4"
              fill="currentColor"
              viewBox="0 0 20 20"
            >
              <path
                fillRule="evenodd"
                d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z"
                clipRule="evenodd"
              />
            </svg>
          </div>
        </div>
      </motion.div>
    );
  }

  // Toast notification when back online
  return (
    <AnimatePresence>
      {showToast && (
        <motion.div
          initial={{ y: -100, opacity: 0 }}
          animate={{ y: 0, opacity: 1 }}
          exit={{ y: -100, opacity: 0 }}
          className="fixed top-4 left-1/2 transform -translate-x-1/2 z-50"
        >
          <div className="bg-green-500 text-white px-6 py-3 rounded-full shadow-lg flex items-center gap-3">
            <svg
              className="w-5 h-5"
              fill="currentColor"
              viewBox="0 0 20 20"
            >
              <path
                fillRule="evenodd"
                d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
                clipRule="evenodd"
              />
            </svg>
            <span className="font-semibold">Back online!</span>
          </div>
        </motion.div>
      )}
    </AnimatePresence>
  );
}

export default OfflineIndicator;