← back to Watches

src/components/InstallPrompt.jsx

233 lines

import React, { useState, useEffect } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import {
  setupInstallPrompt,
  showInstallPrompt,
  isAppInstalled,
  isIOS,
  isAndroid,
  getInstallStatusMessage
} from '../utils/pwaUtils';

/**
 * Install Prompt Component
 * Shows a banner or button to install the PWA
 */
function InstallPrompt({ variant = 'banner' }) {
  const [installable, setInstallable] = useState(false);
  const [showBanner, setShowBanner] = useState(false);
  const [installed, setInstalled] = useState(false);
  const [dismissed, setDismissed] = useState(false);

  useEffect(() => {
    // Check if already installed
    if (isAppInstalled()) {
      setInstalled(true);
      return;
    }

    // Check if banner was previously dismissed
    const wasDismissed = localStorage.getItem('installBannerDismissed');
    if (wasDismissed) {
      const dismissedAt = parseInt(wasDismissed);
      const daysSince = (Date.now() - dismissedAt) / (1000 * 60 * 60 * 24);

      // Show again after 7 days
      if (daysSince < 7) {
        setDismissed(true);
        return;
      }
    }

    // Setup install prompt
    setupInstallPrompt(
      (isInstallable) => {
        setInstallable(isInstallable);
        setShowBanner(true);
      },
      () => {
        setInstalled(true);
        setShowBanner(false);
      }
    );

    // Show banner after 10 seconds
    const timer = setTimeout(() => {
      if (!installed && !dismissed) {
        setShowBanner(true);
      }
    }, 10000);

    return () => clearTimeout(timer);
  }, [installed, dismissed]);

  const handleInstall = async () => {
    const accepted = await showInstallPrompt();

    if (accepted) {
      setShowBanner(false);
    }
  };

  const handleDismiss = () => {
    setShowBanner(false);
    setDismissed(true);
    localStorage.setItem('installBannerDismissed', Date.now().toString());
  };

  // Don't show if already installed or dismissed
  if (installed || dismissed) {
    return null;
  }

  // Button variant (for header or settings)
  if (variant === 'button') {
    return (
      <button
        onClick={handleInstall}
        className="flex items-center gap-2 px-4 py-2 bg-omega-red text-white rounded-lg hover:bg-red-700 transition-colors"
      >
        <svg
          className="w-5 h-5"
          fill="none"
          stroke="currentColor"
          viewBox="0 0 24 24"
        >
          <path
            strokeLinecap="round"
            strokeLinejoin="round"
            strokeWidth={2}
            d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"
          />
        </svg>
        <span className="hidden sm:inline">Install App</span>
      </button>
    );
  }

  // iOS banner (different UI since no install prompt API)
  if (isIOS() && showBanner) {
    return (
      <AnimatePresence>
        <motion.div
          initial={{ y: 100, opacity: 0 }}
          animate={{ y: 0, opacity: 1 }}
          exit={{ y: 100, opacity: 0 }}
          className="fixed bottom-0 left-0 right-0 z-50 p-4 bg-gradient-to-r from-omega-navy to-omega-red text-white shadow-2xl"
        >
          <div className="container mx-auto max-w-2xl">
            <div className="flex items-start gap-4">
              <div className="flex-shrink-0">
                <svg
                  className="w-12 h-12"
                  fill="currentColor"
                  viewBox="0 0 24 24"
                >
                  <path d="M12 2L2 7v10c0 5.55 3.84 10.74 9 12 5.16-1.26 9-6.45 9-12V7l-10-5z" />
                </svg>
              </div>
              <div className="flex-1">
                <h3 className="font-bold text-lg mb-1">Install Omega Watch App</h3>
                <p className="text-sm text-gray-200 mb-2">
                  Add to your home screen for quick access and offline browsing
                </p>
                <div className="flex items-center gap-2 text-sm">
                  <svg
                    className="w-4 h-4"
                    fill="none"
                    stroke="currentColor"
                    viewBox="0 0 24 24"
                  >
                    <path
                      strokeLinecap="round"
                      strokeLinejoin="round"
                      strokeWidth={2}
                      d="M8.684 13.342C8.886 12.938 9 12.482 9 12c0-.482-.114-.938-.316-1.342m0 2.684a3 3 0 110-2.684m0 2.684l6.632 3.316m-6.632-6l6.632-3.316m0 0a3 3 0 105.367-2.684 3 3 0 00-5.367 2.684zm0 9.316a3 3 0 105.368 2.684 3 3 0 00-5.368-2.684z"
                    />
                  </svg>
                  <span>Tap Share, then "Add to Home Screen"</span>
                </div>
              </div>
              <button
                onClick={handleDismiss}
                className="flex-shrink-0 p-2 hover:bg-white/20 rounded-lg transition-colors"
              >
                <svg
                  className="w-6 h-6"
                  fill="none"
                  stroke="currentColor"
                  viewBox="0 0 24 24"
                >
                  <path
                    strokeLinecap="round"
                    strokeLinejoin="round"
                    strokeWidth={2}
                    d="M6 18L18 6M6 6l12 12"
                  />
                </svg>
              </button>
            </div>
          </div>
        </motion.div>
      </AnimatePresence>
    );
  }

  // Android/Chrome banner (with install button)
  if (installable && showBanner) {
    return (
      <AnimatePresence>
        <motion.div
          initial={{ y: 100, opacity: 0 }}
          animate={{ y: 0, opacity: 1 }}
          exit={{ y: 100, opacity: 0 }}
          className="fixed bottom-4 left-4 right-4 z-50 mx-auto max-w-2xl"
        >
          <div className="bg-white dark:bg-gray-800 rounded-2xl shadow-2xl border-2 border-omega-red overflow-hidden">
            <div className="p-6">
              <div className="flex items-start gap-4">
                <div className="flex-shrink-0 w-16 h-16 bg-gradient-to-br from-omega-red to-red-700 rounded-xl flex items-center justify-center">
                  <svg
                    className="w-8 h-8 text-white"
                    fill="currentColor"
                    viewBox="0 0 24 24"
                  >
                    <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z" />
                  </svg>
                </div>
                <div className="flex-1">
                  <h3 className="font-bold text-lg text-gray-800 dark:text-white mb-1">
                    Install Omega Watch App
                  </h3>
                  <p className="text-sm text-gray-600 dark:text-gray-300 mb-4">
                    Get quick access and browse watches offline
                  </p>
                  <div className="flex gap-3">
                    <button
                      onClick={handleInstall}
                      className="flex-1 bg-omega-red hover:bg-red-700 text-white font-semibold py-3 px-6 rounded-lg transition-colors"
                    >
                      Install Now
                    </button>
                    <button
                      onClick={handleDismiss}
                      className="px-4 py-3 text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors"
                    >
                      Not Now
                    </button>
                  </div>
                </div>
              </div>
            </div>
            <div className="bg-gradient-to-r from-omega-red to-red-700 h-1"></div>
          </div>
        </motion.div>
      </AnimatePresence>
    );
  }

  return null;
}

export default InstallPrompt;