← back to Watches

src/App-optimized.jsx

413 lines

import React, { useState, useEffect, lazy, Suspense } from 'react';
import { AnimatePresence, motion } from 'framer-motion';
import EnhancedHeader from './components/EnhancedHeader';
import { useDarkMode } from './hooks/useLocalStorage';
import { saveWatchesOffline, getWatchesOffline } from './utils/offlineStorage';
import { getOnlineStatus } from './utils/pwaUtils';
import {
  updateWatchPageSEO,
  updateListPageSEO,
  updateDashboardSEO,
  updateComparisonPageSEO,
  preloadCriticalResources,
  cleanupStructuredData
} from './utils/seo';

// Lazy load heavy components for code splitting
const EnhancedDashboard = lazy(() => import('./components/EnhancedDashboard'));
const EnhancedWatchList = lazy(() => import('./components/EnhancedWatchList'));
const PriceChart = lazy(() => import('./components/PriceChart'));
const WatchComparison = lazy(() => import('./components/WatchComparison'));
const InstallPrompt = lazy(() => import('./components/InstallPrompt'));
const OfflineIndicator = lazy(() => import('./components/OfflineIndicator'));

// Loading fallback component
const LoadingFallback = ({ message = 'Loading...' }) => (
  <div className="min-h-screen flex items-center justify-center bg-gray-100 dark:bg-gray-900">
    <motion.div
      initial={{ opacity: 0, scale: 0.9 }}
      animate={{ opacity: 1, scale: 1 }}
      className="text-center"
    >
      <div className="relative">
        <div className="animate-spin rounded-full h-20 w-20 border-b-4 border-omega-red mx-auto mb-4"></div>
        <div className="absolute inset-0 flex items-center justify-center">
          <span className="text-3xl">⌚</span>
        </div>
      </div>
      <h1 className="text-gray-600 dark:text-gray-300 text-lg font-semibold">
        {message}
      </h1>
    </motion.div>
  </div>
);

/**
 * PERFORMANCE OPTIMIZED Main App Component
 *
 * Optimizations:
 * - Lazy loading of all heavy components
 * - Code splitting by route
 * - Memoized callbacks
 * - Optimized re-renders
 * - Aggressive caching
 */
function App() {
  const [watches, setWatches] = useState([]);
  const [statistics, setStatistics] = useState(null);
  const [selectedWatch, setSelectedWatch] = useState(null);
  const [view, setView] = useState('dashboard');
  const [loading, setLoading] = useState(true);
  const [compareWatches, setCompareWatches] = useState([]);
  const [showComparison, setShowComparison] = useState(false);
  const [darkMode] = useDarkMode();

  // Preload critical resources on mount
  useEffect(() => {
    preloadCriticalResources();
    loadData();
    handleDeepLinking();

    // Prefetch API data for instant navigation
    if ('requestIdleCallback' in window) {
      requestIdleCallback(() => {
        fetch('/api/statistics').then(r => r.json());
      });
    }
  }, []);

  // Update SEO when view changes
  useEffect(() => {
    cleanupStructuredData();

    switch (view) {
      case 'dashboard':
        updateDashboardSEO();
        break;
      case 'list':
        updateListPageSEO();
        break;
      case 'chart':
        if (selectedWatch) {
          updateWatchPageSEO(selectedWatch);
        }
        break;
      case 'compare':
        updateComparisonPageSEO(compareWatches);
        break;
      default:
        updateDashboardSEO();
    }
  }, [view, selectedWatch, compareWatches]);

  const loadData = async () => {
    try {
      setLoading(true);

      if (getOnlineStatus()) {
        // Use Promise.all for parallel requests
        const [watchesRes, statsRes] = await Promise.all([
          fetch('/api/watches'),
          fetch('/api/statistics')
        ]);

        const watchesData = await watchesRes.json();
        const statsData = await statsRes.json();

        const watchesList = watchesData.watches || watchesData;
        setWatches(watchesList);
        setStatistics(statsData);

        // Save to offline storage asynchronously
        saveWatchesOffline(watchesList);
      } else {
        console.log('Loading from offline storage...');
        const offlineWatches = await getWatchesOffline();
        setWatches(offlineWatches);
        setStatistics(null);
      }
    } catch (error) {
      console.error('Error loading data:', error);

      // Fallback to offline storage
      const offlineWatches = await getWatchesOffline();
      if (offlineWatches.length > 0) {
        console.log('Loaded from offline cache');
        setWatches(offlineWatches);
      }
    } finally {
      setLoading(false);
    }
  };

  const handleDeepLinking = () => {
    const params = new URLSearchParams(window.location.search);
    const watchId = params.get('watch');
    const viewParam = params.get('view');

    if (viewParam && ['dashboard', 'list', 'chart', 'compare'].includes(viewParam)) {
      setView(viewParam);
    }

    if (watchId) {
      const checkAndSelect = setInterval(() => {
        if (watches.length > 0) {
          const watch = watches.find(w => w.id === watchId);
          if (watch) {
            setSelectedWatch(watch);
            setView('chart');
          }
          clearInterval(checkAndSelect);
        }
      }, 100);

      setTimeout(() => clearInterval(checkAndSelect), 5000);
    }
  };

  const handleWatchSelect = React.useCallback((watch) => {
    setSelectedWatch(watch);
    setView('chart');

    const newUrl = `${window.location.pathname}?watch=${watch.id}`;
    window.history.pushState({ watch: watch.id }, '', newUrl);

    updateWatchPageSEO(watch);
  }, []);

  const handleBackToList = React.useCallback(() => {
    setView('list');
    const newUrl = `${window.location.pathname}?view=list`;
    window.history.pushState({ view: 'list' }, '', newUrl);
    updateListPageSEO();
  }, []);

  const handleCompareToggle = React.useCallback(() => {
    setShowComparison(prev => !prev);
  }, []);

  const handleAddToCompare = React.useCallback((watch) => {
    setCompareWatches(prev => {
      if (prev.length < 3 && !prev.find(w => w.id === watch.id)) {
        return [...prev, watch];
      }
      return prev;
    });
  }, []);

  const handleRemoveFromCompare = React.useCallback((watchId) => {
    setCompareWatches(prev => prev.filter(w => w.id !== watchId));
  }, []);

  if (loading) {
    return <LoadingFallback message="Loading Omega Watch Collection..." />;
  }

  return (
    <div className={`min-h-screen ${darkMode ? 'dark' : ''}`}>
      <div className="min-h-screen bg-gray-50 dark:bg-gray-900 transition-colors">
        {/* Lazy load PWA components */}
        <Suspense fallback={null}>
          <OfflineIndicator />
          <InstallPrompt variant="banner" />
        </Suspense>

        {/* Header */}
        <header role="banner">
          <EnhancedHeader view={view} setView={setView} />
        </header>

        {/* Main content with lazy loading */}
        <main id="main-content" role="main" className="container mx-auto px-4 py-6 md:py-8">
          <AnimatePresence mode="wait">
            {view === 'dashboard' && (
              <Suspense fallback={<LoadingFallback message="Loading Dashboard..." />}>
                <motion.article
                  key="dashboard"
                  initial={{ opacity: 0, y: 20 }}
                  animate={{ opacity: 1, y: 0 }}
                  exit={{ opacity: 0, y: -20 }}
                  transition={{ duration: 0.3 }}
                >
                  <EnhancedDashboard
                    statistics={statistics}
                    watches={watches}
                    onWatchSelect={handleWatchSelect}
                    loading={loading}
                  />
                </motion.article>
              </Suspense>
            )}

            {view === 'list' && (
              <Suspense fallback={<LoadingFallback message="Loading Watch List..." />}>
                <motion.section
                  key="list"
                  initial={{ opacity: 0, y: 20 }}
                  animate={{ opacity: 1, y: 0 }}
                  exit={{ opacity: 0, y: -20 }}
                  transition={{ duration: 0.3 }}
                >
                  <EnhancedWatchList
                    watches={watches}
                    onWatchSelect={handleWatchSelect}
                    loading={loading}
                    onCompare={handleAddToCompare}
                  />
                </motion.section>
              </Suspense>
            )}

            {view === 'chart' && selectedWatch && (
              <Suspense fallback={<LoadingFallback message="Loading Price Chart..." />}>
                <motion.article
                  key="chart"
                  initial={{ opacity: 0, scale: 0.95 }}
                  animate={{ opacity: 1, scale: 1 }}
                  exit={{ opacity: 0, scale: 0.95 }}
                  transition={{ duration: 0.3 }}
                >
                  <PriceChart watch={selectedWatch} onBack={handleBackToList} />
                </motion.article>
              </Suspense>
            )}

            {view === 'compare' && (
              <Suspense fallback={<LoadingFallback message="Loading Comparison..." />}>
                <motion.section
                  key="compare"
                  initial={{ opacity: 0, y: 20 }}
                  animate={{ opacity: 1, y: 0 }}
                  exit={{ opacity: 0, y: -20 }}
                  transition={{ duration: 0.3 }}
                >
                  <div className="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6">
                    <h1 className="text-2xl font-bold text-gray-800 dark:text-white mb-4">
                      Watch Comparison Tool
                    </h1>
                    <p className="text-gray-600 dark:text-gray-300 mb-6">
                      Select watches from the list to compare their specifications and price history side-by-side.
                    </p>
                    <button
                      onClick={() => setShowComparison(true)}
                      className="bg-omega-red hover:bg-red-700 text-white font-semibold px-6 py-3 rounded-lg transition-colors"
                      aria-label="Compare selected watches"
                    >
                      {compareWatches.length > 0
                        ? `Compare ${compareWatches.length} ${compareWatches.length === 1 ? 'Watch' : 'Watches'}`
                        : 'Select Watches to Compare'}
                    </button>

                    {statistics && statistics.topPerformers && (
                      <section className="mt-8" aria-labelledby="top-performers-heading">
                        <h2 id="top-performers-heading" className="text-lg font-bold text-gray-800 dark:text-white mb-4">
                          Quick Select: Top Performers
                        </h2>
                        <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
                          {statistics.topPerformers.slice(0, 6).map(topWatch => {
                            const fullWatch = watches.find(w => w.id === topWatch.id);
                            const isSelected = compareWatches.find(w => w.id === topWatch.id);
                            return fullWatch ? (
                              <button
                                key={fullWatch.id}
                                onClick={() => {
                                  if (isSelected) {
                                    handleRemoveFromCompare(fullWatch.id);
                                  } else {
                                    handleAddToCompare(fullWatch);
                                  }
                                }}
                                className={`p-4 rounded-lg border-2 transition-all text-left ${
                                  isSelected
                                    ? 'border-omega-red bg-red-50 dark:bg-red-900/20'
                                    : 'border-gray-200 dark:border-gray-700 hover:border-omega-red'
                                }`}
                                aria-pressed={!!isSelected}
                              >
                                <h3 className="font-semibold text-gray-800 dark:text-white">
                                  {fullWatch.model}
                                </h3>
                                <p className="text-sm text-gray-500 dark:text-gray-400">
                                  {fullWatch.series}
                                </p>
                                <p className="text-lg font-bold text-green-600 mt-2">
                                  +{topWatch.appreciation}%
                                </p>
                              </button>
                            ) : null;
                          })}
                        </div>
                      </section>
                    )}
                  </div>
                </motion.section>
              </Suspense>
            )}
          </AnimatePresence>
        </main>

        {/* Comparison Modal */}
        <AnimatePresence>
          {showComparison && compareWatches.length > 0 && (
            <Suspense fallback={null}>
              <WatchComparison
                watches={watches}
                selectedWatches={compareWatches}
                onClose={() => setShowComparison(false)}
                onAddWatch={handleAddToCompare}
                onRemoveWatch={handleRemoveFromCompare}
              />
            </Suspense>
          )}
        </AnimatePresence>

        {/* Footer */}
        <footer role="contentinfo" className="bg-omega-navy dark:bg-black text-white py-6 mt-12">
          <div className="container mx-auto px-4">
            <div className="grid grid-cols-1 md:grid-cols-3 gap-6 text-center md:text-left">
              <section aria-labelledby="footer-about">
                <h2 id="footer-about" className="font-bold text-lg mb-2">Omega Price History</h2>
                <p className="text-sm text-gray-400">
                  Historical price tracking and analysis system for Omega timepieces.
                  Track 32 iconic models from 1957 to 2024.
                </p>
              </section>
              <section aria-labelledby="footer-features">
                <h2 id="footer-features" className="font-bold text-lg mb-2">Features</h2>
                <ul className="text-sm text-gray-400 space-y-1">
                  <li>32 Iconic Omega Watches</li>
                  <li>Historical Price Data (1957-2024)</li>
                  <li>Advanced Analytics & Predictions</li>
                  <li>Dark Mode Support</li>
                  <li>Watch Comparison Tools</li>
                </ul>
              </section>
              <section aria-labelledby="footer-info">
                <h2 id="footer-info" className="font-bold text-lg mb-2">System Info</h2>
                <p className="text-sm text-gray-400">
                  Server: 45.61.58.125:7600
                </p>
                <p className="text-sm text-gray-400">
                  Status: <span className="text-green-400">Online</span>
                </p>
                <nav aria-label="Footer navigation">
                  <ul className="text-sm text-gray-400 space-y-1 mt-2">
                    <li><a href="/sitemap.xml" className="hover:text-white">Sitemap</a></li>
                    <li><a href="/api/docs" className="hover:text-white">API Documentation</a></li>
                  </ul>
                </nav>
              </section>
            </div>
            <div className="mt-6 pt-6 border-t border-gray-700 text-center text-sm text-gray-400">
              <p>© 2024 Omega Watch Price History System | Educational purposes only</p>
              <p className="mt-1">Historical pricing data for luxury watch collectors and investors</p>
            </div>
          </div>
        </footer>
      </div>
    </div>
  );
}

export default App;