Spaces:
Running
Running
| import { useEffect, useState } from 'react'; | |
| import { AnimatePresence, motion } from 'framer-motion'; | |
| import { GenerationProvider, useGeneration } from '@/context/GenerationContext'; | |
| import { AuthProvider, useAuth } from '@/context/AuthContext'; | |
| import { | |
| ProviderSelect, | |
| GenerationForm, | |
| GenerationProgress, | |
| GenerationComplete, | |
| ErrorDisplay, | |
| Login, | |
| LogoIcon | |
| } from '@/components'; | |
| import { checkHealth } from '@/utils/api'; | |
| import type { HealthStatus } from '@/types'; | |
| // Main App Content (uses context) | |
| function AppContent() { | |
| const { isAuthenticated, loading: authLoading, logout } = useAuth(); | |
| const { state, selectProvider, reset } = useGeneration(); | |
| const [healthStatus, setHealthStatus] = useState<HealthStatus | null>(null); | |
| const [healthError, setHealthError] = useState<string | null>(null); | |
| // Check backend health on mount (must be called before any conditional returns) | |
| useEffect(() => { | |
| if (isAuthenticated) { | |
| checkHealth() | |
| .then(setHealthStatus) | |
| .catch((err) => setHealthError(err.message)); | |
| } | |
| }, [isAuthenticated]); | |
| // Show login if not authenticated | |
| if (authLoading) { | |
| return ( | |
| <div className="min-h-screen flex items-center justify-center bg-mesh-pattern"> | |
| <div className="text-center"> | |
| <div className="animate-spin rounded-full h-12 w-12 border-b-2 border-coral-500 mx-auto mb-4"></div> | |
| <p className="text-void-400">Loading...</p> | |
| </div> | |
| </div> | |
| ); | |
| } | |
| if (!isAuthenticated) { | |
| return <Login />; | |
| } | |
| // Render based on current step | |
| const renderContent = () => { | |
| switch (state.step) { | |
| case 'idle': | |
| return ( | |
| <ProviderSelect | |
| onSelect={(provider) => selectProvider(provider)} | |
| /> | |
| ); | |
| case 'configuring': | |
| return ( | |
| <GenerationForm | |
| provider={state.provider!} | |
| onBack={() => reset()} | |
| /> | |
| ); | |
| case 'generating_prompts': | |
| case 'generating_video': | |
| case 'processing': | |
| return <GenerationProgress />; | |
| case 'completed': | |
| return <GenerationComplete />; | |
| case 'error': | |
| return <ErrorDisplay />; | |
| default: | |
| return ( | |
| <ProviderSelect | |
| onSelect={(provider) => selectProvider(provider)} | |
| /> | |
| ); | |
| } | |
| }; | |
| return ( | |
| <div className="min-h-screen flex flex-col bg-mesh-pattern"> | |
| {/* Header */} | |
| <header className="glass-dark sticky top-0 z-50"> | |
| <div className="max-w-7xl mx-auto px-6 py-4"> | |
| <div className="flex items-center justify-between"> | |
| {/* Logo */} | |
| <button | |
| onClick={reset} | |
| className="flex items-center gap-3 hover:opacity-80 transition-opacity" | |
| > | |
| <LogoIcon size={36} /> | |
| <div> | |
| <h1 className="text-lg font-display font-bold text-void-100"> | |
| Video AdGenesis | |
| </h1> | |
| <p className="text-xs text-void-500 -mt-0.5">Studio</p> | |
| </div> | |
| </button> | |
| {/* Status Indicator */} | |
| <div className="flex items-center gap-4"> | |
| {/* Provider Badge */} | |
| {state.provider && state.step !== 'idle' && ( | |
| <motion.span | |
| initial={{ opacity: 0, scale: 0.9 }} | |
| animate={{ opacity: 1, scale: 1 }} | |
| className={` | |
| px-3 py-1 rounded-full text-xs font-semibold | |
| ${state.provider === 'kling' | |
| ? 'bg-coral-500/20 text-coral-300' | |
| : 'bg-electric-500/20 text-electric-300' | |
| } | |
| `} | |
| > | |
| {state.provider === 'kling' ? 'KIE API' : 'Replicate'} | |
| </motion.span> | |
| )} | |
| {/* Backend Status */} | |
| <div className="flex items-center gap-2"> | |
| <div | |
| className={`w-2 h-2 rounded-full ${ | |
| healthError ? 'bg-red-500' : | |
| healthStatus ? 'bg-green-500' : 'bg-amber-500 animate-pulse' | |
| }`} | |
| /> | |
| <span className="text-xs text-void-400"> | |
| {healthError ? 'Backend Offline' : | |
| healthStatus ? (healthStatus.is_dev_mode ? 'Dev Mode' : 'Production') : | |
| 'Connecting...'} | |
| </span> | |
| </div> | |
| {/* Logout Button */} | |
| <button | |
| onClick={logout} | |
| className="px-3 py-1.5 text-xs font-medium text-void-400 hover:text-void-200 | |
| hover:bg-void-800 rounded-lg transition-colors" | |
| title="Logout" | |
| > | |
| Logout | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </header> | |
| {/* Main Content */} | |
| <main className="flex-grow"> | |
| <AnimatePresence mode="wait"> | |
| <motion.div | |
| key={state.step} | |
| initial={{ opacity: 0, y: 10 }} | |
| animate={{ opacity: 1, y: 0 }} | |
| exit={{ opacity: 0, y: -10 }} | |
| transition={{ duration: 0.3 }} | |
| > | |
| {renderContent()} | |
| </motion.div> | |
| </AnimatePresence> | |
| </main> | |
| {/* Footer */} | |
| <footer className="py-6 text-center"> | |
| <p className="text-xs text-void-600"> | |
| Powered by {' '} | |
| <span className="text-coral-400">AdGenesis</span> | |
| </p> | |
| </footer> | |
| </div> | |
| ); | |
| } | |
| // App Wrapper with Providers | |
| function App() { | |
| return ( | |
| <AuthProvider> | |
| <GenerationProvider> | |
| <AppContent /> | |
| </GenerationProvider> | |
| </AuthProvider> | |
| ); | |
| } | |
| export default App; | |