← back to Watches

src/App.luxury.jsx

450 lines

import React, { useState, useEffect } from 'react';
import { AnimatePresence, motion } from 'framer-motion';
import LuxuryHero from './components/LuxuryHero';
import ParallaxSection from './components/ParallaxSection';
import LuxuryWatchCard from './components/LuxuryWatchCard';
import GestureControls from './components/GestureControls';
import PremiumLoader from './components/PremiumLoader';
import EnhancedHeader from './components/EnhancedHeader';
import PriceChart from './components/PriceChart';
import WatchComparison from './components/WatchComparison';
import InstallPrompt from './components/InstallPrompt';
import OfflineIndicator from './components/OfflineIndicator';
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';

/**
 * OMEGA LUXURY APP - Ultra-Premium Edition
 * Features:
 * - Cinematic hero section with parallax
 * - 3D card effects with mouse tracking
 * - Gesture-based navigation
 * - Haptic feedback simulation
 * - Premium loading states
 * - Gold accent animations
 * - Smooth page transitions
 */
function App() {
  const [watches, setWatches] = useState([]);
  const [statistics, setStatistics] = useState(null);
  const [selectedWatch, setSelectedWatch] = useState(null);
  const [view, setView] = useState('hero'); // hero, collection, chart, compare
  const [loading, setLoading] = useState(true);
  const [loadingProgress, setLoadingProgress] = useState(0);
  const [compareWatches, setCompareWatches] = useState([]);
  const [showComparison, setShowComparison] = useState(false);
  const [currentCollectionIndex, setCurrentCollectionIndex] = useState(0);
  const [darkMode] = useDarkMode();

  useEffect(() => {
    preloadCriticalResources();
    loadData();
    handleDeepLinking();
  }, []);

  useEffect(() => {
    cleanupStructuredData();

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

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

      if (getOnlineStatus()) {
        setLoadingProgress(30);

        const [watchesRes, statsRes] = await Promise.all([
          fetch('/api/watches'),
          fetch('/api/statistics')
        ]);

        setLoadingProgress(60);

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

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

        setLoadingProgress(90);

        await saveWatchesOffline(watchesList);
        setLoadingProgress(100);

        // Smooth transition out of loading
        setTimeout(() => setLoading(false), 500);
      } else {
        const offlineWatches = await getWatchesOffline();
        setWatches(offlineWatches);
        setStatistics(null);
        setLoadingProgress(100);
        setTimeout(() => setLoading(false), 500);
      }
    } catch (error) {
      console.error('Error loading data:', error);

      const offlineWatches = await getWatchesOffline();
      if (offlineWatches.length > 0) {
        setWatches(offlineWatches);
      }
      setLoadingProgress(100);
      setTimeout(() => setLoading(false), 500);
    }
  };

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

    if (viewParam && ['hero', 'collection', '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 = (watch) => {
    setSelectedWatch(watch);
    setView('chart');
    const newUrl = `${window.location.pathname}?watch=${watch.id}`;
    window.history.pushState({ watch: watch.id }, '', newUrl);
    updateWatchPageSEO(watch);
  };

  const handleBackToCollection = () => {
    setView('collection');
    const newUrl = `${window.location.pathname}?view=collection`;
    window.history.pushState({ view: 'collection' }, '', newUrl);
    updateListPageSEO();
  };

  const handleExploreCollection = () => {
    setView('collection');
    window.scrollTo({ top: 0, behavior: 'smooth' });
  };

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

  const handleRemoveFromCompare = (watchId) => {
    setCompareWatches(compareWatches.filter(w => w.id !== watchId));
  };

  // Group watches by series for collection view
  const groupedWatches = watches.reduce((acc, watch) => {
    const series = watch.series || 'Other';
    if (!acc[series]) {
      acc[series] = [];
    }
    acc[series].push(watch);
    return acc;
  }, {});

  if (loading) {
    return <PremiumLoader progress={loadingProgress} />;
  }

  return (
    <div className={`min-h-screen ${darkMode ? 'dark' : ''}`}>
      <div className="min-h-screen bg-gradient-luxury-dark transition-colors">
        {/* PWA Components */}
        <OfflineIndicator />
        <InstallPrompt variant="banner" />

        {/* Luxury Header */}
        <header role="banner" className="no-print">
          <EnhancedHeader view={view} setView={setView} />
        </header>

        {/* Main content with page transitions */}
        <main id="main-content" role="main">
          <AnimatePresence mode="wait">
            {/* Hero Section */}
            {view === 'hero' && (
              <motion.div
                key="hero"
                initial={{ opacity: 0 }}
                animate={{ opacity: 1 }}
                exit={{ opacity: 0 }}
                transition={{ duration: 0.6 }}
              >
                <LuxuryHero onExplore={handleExploreCollection} />

                {/* Statistics section with parallax */}
                {statistics && (
                  <ParallaxSection
                    title="Market Insights"
                    subtitle="Data-driven analysis of Omega's enduring value"
                  >
                    <div className="grid grid-cols-1 md:grid-cols-3 gap-8 mt-12">
                      <motion.div
                        className="luxury-card p-8 text-center"
                        initial={{ opacity: 0, y: 30 }}
                        whileInView={{ opacity: 1, y: 0 }}
                        viewport={{ once: true }}
                        transition={{ delay: 0.1 }}
                      >
                        <div className="text-5xl mb-4 text-gold">32</div>
                        <h3 className="text-xl font-display text-white">Iconic Models</h3>
                        <p className="text-gray-400 mt-2">Curated collection</p>
                      </motion.div>

                      <motion.div
                        className="luxury-card p-8 text-center"
                        initial={{ opacity: 0, y: 30 }}
                        whileInView={{ opacity: 1, y: 0 }}
                        viewport={{ once: true }}
                        transition={{ delay: 0.2 }}
                      >
                        <div className="text-5xl mb-4 text-gold">67</div>
                        <h3 className="text-xl font-display text-white">Years Tracked</h3>
                        <p className="text-gray-400 mt-2">Since 1957</p>
                      </motion.div>

                      <motion.div
                        className="luxury-card p-8 text-center"
                        initial={{ opacity: 0, y: 30 }}
                        whileInView={{ opacity: 1, y: 0 }}
                        viewport={{ once: true }}
                        transition={{ delay: 0.3 }}
                      >
                        <div className="text-5xl mb-4 text-green-400">+3,400%</div>
                        <h3 className="text-xl font-display text-white">Average Growth</h3>
                        <p className="text-gray-400 mt-2">Value appreciation</p>
                      </motion.div>
                    </div>

                    <motion.button
                      onClick={handleExploreCollection}
                      className="btn-luxury mt-12 haptic-click"
                      initial={{ opacity: 0, scale: 0.9 }}
                      whileInView={{ opacity: 1, scale: 1 }}
                      viewport={{ once: true }}
                      transition={{ delay: 0.5 }}
                    >
                      View Full Collection
                    </motion.button>
                  </ParallaxSection>
                )}
              </motion.div>
            )}

            {/* Collection View with Gesture Controls */}
            {view === 'collection' && (
              <motion.div
                key="collection"
                className="min-h-screen py-24 px-4"
                initial={{ opacity: 0 }}
                animate={{ opacity: 1 }}
                exit={{ opacity: 0 }}
                transition={{ duration: 0.6 }}
              >
                <div className="max-w-7xl mx-auto">
                  <motion.div
                    className="text-center mb-16"
                    initial={{ opacity: 0, y: 30 }}
                    animate={{ opacity: 1, y: 0 }}
                  >
                    <h1 className="text-5xl md:text-7xl font-display font-bold mb-6">
                      <span className="text-gold">The Collection</span>
                    </h1>
                    <p className="text-xl text-gray-300 font-luxury max-w-3xl mx-auto">
                      Explore 32 masterpieces of Swiss horology, each with its own story of
                      innovation, precision, and timeless elegance.
                    </p>
                  </motion.div>

                  {/* Watch grid by series */}
                  {Object.entries(groupedWatches).map(([series, seriesWatches], seriesIndex) => (
                    <motion.section
                      key={series}
                      className="mb-20"
                      initial={{ opacity: 0, y: 50 }}
                      animate={{ opacity: 1, y: 0 }}
                      transition={{ delay: seriesIndex * 0.2 }}
                    >
                      <h2 className="text-3xl md:text-4xl font-display font-bold text-gold mb-8 border-b border-gold/30 pb-4">
                        {series}
                      </h2>
                      <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
                        {seriesWatches.map((watch, index) => (
                          <LuxuryWatchCard
                            key={watch.id}
                            watch={watch}
                            onClick={handleWatchSelect}
                            index={index}
                          />
                        ))}
                      </div>
                    </motion.section>
                  ))}
                </div>

                {/* Gesture controls for mobile swipe navigation */}
                <GestureControls
                  items={Object.keys(groupedWatches)}
                  currentIndex={currentCollectionIndex}
                  onIndexChange={setCurrentCollectionIndex}
                />
              </motion.div>
            )}

            {/* Chart View */}
            {view === 'chart' && selectedWatch && (
              <motion.div
                key="chart"
                initial={{ opacity: 0, scale: 0.95 }}
                animate={{ opacity: 1, scale: 1 }}
                exit={{ opacity: 0, scale: 0.95 }}
                transition={{ duration: 0.4 }}
                className="min-h-screen py-24"
              >
                <PriceChart watch={selectedWatch} onBack={handleBackToCollection} />
              </motion.div>
            )}

            {/* Comparison View */}
            {view === 'compare' && (
              <motion.div
                key="compare"
                initial={{ opacity: 0 }}
                animate={{ opacity: 1 }}
                exit={{ opacity: 0 }}
                transition={{ duration: 0.4 }}
                className="min-h-screen py-24 px-4"
              >
                <div className="max-w-6xl mx-auto">
                  <div className="luxury-card p-8 md:p-12">
                    <h1 className="text-4xl md:text-5xl font-display font-bold text-gold mb-6">
                      Watch Comparison
                    </h1>
                    <p className="text-lg text-gray-300 mb-8">
                      Compare up to 3 watches side-by-side
                    </p>

                    {compareWatches.length > 0 && (
                      <button
                        onClick={() => setShowComparison(true)}
                        className="btn-luxury haptic-click"
                      >
                        Compare {compareWatches.length} {compareWatches.length === 1 ? 'Watch' : 'Watches'}
                      </button>
                    )}
                  </div>
                </div>
              </motion.div>
            )}
          </AnimatePresence>
        </main>

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

        {/* Luxury Footer */}
        <footer role="contentinfo" className="relative bg-black/50 backdrop-blur-xl text-white py-12 mt-24 border-t border-gold/20">
          <div className="max-w-7xl mx-auto px-4">
            <div className="grid grid-cols-1 md:grid-cols-3 gap-8 text-center md:text-left">
              <section>
                <h2 className="font-display text-2xl text-gold mb-4">OMEGA</h2>
                <p className="text-sm text-gray-400 font-luxury">
                  Historical price tracking and analysis for the world's most prestigious timepieces.
                  Experience Swiss excellence through data.
                </p>
              </section>

              <section>
                <h3 className="font-display text-lg text-gold mb-4">Collection</h3>
                <ul className="text-sm text-gray-400 space-y-2">
                  <li>32 Iconic Models</li>
                  <li>67 Years of Data (1957-2024)</li>
                  <li>Comprehensive Price Analytics</li>
                  <li>Investment Insights</li>
                </ul>
              </section>

              <section>
                <h3 className="font-display text-lg text-gold mb-4">Experience</h3>
                <ul className="text-sm text-gray-400 space-y-2">
                  <li>3D Interactive Cards</li>
                  <li>Gesture Navigation</li>
                  <li>Cinematic Animations</li>
                  <li>Luxury UI/UX</li>
                </ul>
              </section>
            </div>

            <div className="mt-12 pt-8 border-t border-gold/20 text-center">
              <p className="text-sm text-gray-500">
                © 2024 Omega Price History | Ultra-Luxury Edition
              </p>
              <p className="text-xs text-gray-600 mt-2">
                Server: 45.61.58.125:7600 | Status: <span className="text-green-400">Online</span>
              </p>
            </div>
          </div>

          {/* Decorative bottom accent */}
          <div className="absolute bottom-0 left-0 right-0 h-1 bg-gradient-gold" />
        </footer>
      </div>
    </div>
  );
}

export default App;