import React, { useState, useRef, useEffect } from 'react'; import { Product, AppView } from './types'; import { PRODUCTS } from './constants'; import { generateTryOn } from './services/geminiService'; import Navigation from './components/Navigation'; import ProductCard from './components/ProductCard'; import CameraView from './components/CameraView'; import ResultView from './components/ResultView'; import { Sparkles, Timer } from 'lucide-react'; const STORAGE_KEY = 'stylegenie_last_req_v2'; const COOLDOWN_SECONDS = 120; // 2 minutes is safe for public shared keys function App() { const [currentView, setCurrentView] = useState(AppView.CATALOG); const [selectedProduct, setSelectedProduct] = useState(null); const [userImage, setUserImage] = useState(null); const [generatedResult, setGeneratedResult] = useState(null); const [isGenerating, setIsGenerating] = useState(false); const [generationError, setGenerationError] = useState(null); const [retryAfter, setRetryAfter] = useState(null); const [globalCooldown, setGlobalCooldown] = useState(0); // Sync cooldown from localStorage to survive refreshes useEffect(() => { const updateCooldown = () => { const last = localStorage.getItem(STORAGE_KEY); if (last) { const diff = (Date.now() - parseInt(last)) / 1000; if (diff < COOLDOWN_SECONDS) { setGlobalCooldown(Math.ceil(COOLDOWN_SECONDS - diff)); } else { setGlobalCooldown(0); } } }; updateCooldown(); const interval = setInterval(updateCooldown, 1000); return () => clearInterval(interval); }, []); const handleSelectProduct = (product: Product) => { if (globalCooldown > 0) return; setSelectedProduct(product); setCurrentView(AppView.CAMERA); }; const handleBackToCatalog = () => { setCurrentView(AppView.CATALOG); setSelectedProduct(null); setUserImage(null); setGeneratedResult(null); setGenerationError(null); setRetryAfter(null); }; const handleCapture = async (imageSrc: string) => { setUserImage(imageSrc); setCurrentView(AppView.RESULT); if (selectedProduct) { await processTryOn(imageSrc, selectedProduct); } }; const processTryOn = async (imageSrc: string, product: Product) => { // Final check for cooldown if (globalCooldown > 0) { setRetryAfter(globalCooldown); return; } setIsGenerating(true); setGenerationError(null); setRetryAfter(null); // Set lock immediately to prevent race conditions localStorage.setItem(STORAGE_KEY, Date.now().toString()); try { const resultImage = await generateTryOn(imageSrc, product); setGeneratedResult(resultImage); } catch (error: any) { const msg = error.message || ""; if (msg.includes("FREE_LIMIT_HIT") || msg.includes("429")) { setRetryAfter(COOLDOWN_SECONDS); } else { setGenerationError(msg); } } finally { setIsGenerating(false); } }; const handleRetake = () => { setCurrentView(AppView.CAMERA); setGeneratedResult(null); setGenerationError(null); setRetryAfter(null); }; return (
{currentView === AppView.CATALOG && (
Optimized Free Mode

StyleGenie Try-On

{globalCooldown > 0 ? (

AI is Resting: {globalCooldown}s

Free tier allows 1 generation every 2 minutes.

) : (

Select an item below to see the magic happen.

)}
{PRODUCTS.map(product => ( 0} /> ))}
)} {currentView === AppView.CAMERA && selectedProduct && ( )} {currentView === AppView.RESULT && selectedProduct && userImage && ( processTryOn(userImage, selectedProduct)} /> )}
); } export default App;