'use client'; import { useState, useEffect, useRef } from 'react'; import { apiClient } from '@/lib/api'; import { initializeOAuth, loginWithHuggingFace, loginDevMode, logout, getStoredUserInfo, isAuthenticated as checkIsAuthenticated, isDevelopmentMode } from '@/lib/auth'; import type { Model, Language } from '@/types'; import type { OAuthUserInfo } from '@/lib/auth'; interface LandingPageProps { onStart: (prompt: string, language: Language, modelId: string, repoId?: string, shouldCreatePR?: boolean) => void; onImport?: (code: string, language: Language, importUrl?: string) => void; isAuthenticated: boolean; initialLanguage?: Language; initialModel?: string; onAuthChange?: () => void; setPendingPR?: (pr: { repoId: string; language: Language } | null) => void; pendingPRRef?: React.MutableRefObject<{ repoId: string; language: Language } | null>; } export default function LandingPage({ onStart, onImport, isAuthenticated, initialLanguage = 'html', initialModel = 'deepseek-ai/DeepSeek-V3.2-Exp', onAuthChange, setPendingPR, pendingPRRef }: LandingPageProps) { const [prompt, setPrompt] = useState(''); const [selectedLanguage, setSelectedLanguage] = useState(initialLanguage); const [selectedModel, setSelectedModel] = useState(initialModel); const [models, setModels] = useState([]); const [languages, setLanguages] = useState([]); const [isLoading, setIsLoading] = useState(true); // Auth states const [userInfo, setUserInfo] = useState(null); const [isAuthLoading, setIsAuthLoading] = useState(true); const [showDevLogin, setShowDevLogin] = useState(false); const [devUsername, setDevUsername] = useState(''); const isDevMode = isDevelopmentMode(); // Dropdown states const [showLanguageDropdown, setShowLanguageDropdown] = useState(false); const [showModelDropdown, setShowModelDropdown] = useState(false); const [showImportDialog, setShowImportDialog] = useState(false); const [showRedesignDialog, setShowRedesignDialog] = useState(false); const languageDropdownRef = useRef(null); const modelDropdownRef = useRef(null); const importDialogRef = useRef(null); const redesignDialogRef = useRef(null); // Trending apps state const [trendingApps, setTrendingApps] = useState([]); // Import project state const [importUrl, setImportUrl] = useState(''); const [isImporting, setIsImporting] = useState(false); const [importError, setImportError] = useState(''); const [importAction, setImportAction] = useState<'duplicate' | 'update' | 'pr'>('duplicate'); // Default to duplicate const [isSpaceOwner, setIsSpaceOwner] = useState(false); // Track if user owns the space // Redesign project state const [redesignUrl, setRedesignUrl] = useState(''); const [isRedesigning, setIsRedesigning] = useState(false); const [redesignError, setRedesignError] = useState(''); const [createPR, setCreatePR] = useState(false); // Default to normal redesign (not PR) // Debug effect for dropdown state useEffect(() => { console.log('showModelDropdown state changed to:', showModelDropdown); }, [showModelDropdown]); // Debug effect for models state useEffect(() => { console.log('models state changed, length:', models.length, 'models:', models); }, [models]); useEffect(() => { console.log('Component mounted, initial load starting...'); loadData(); handleOAuthInit(); loadTrendingApps(); // Check auth status periodically to catch OAuth redirects const interval = setInterval(() => { const authenticated = checkIsAuthenticated(); if (authenticated && !userInfo) { handleOAuthInit(); } }, 1000); return () => clearInterval(interval); }, []); const handleOAuthInit = async () => { setIsAuthLoading(true); try { const oauthResult = await initializeOAuth(); if (oauthResult) { setUserInfo(oauthResult.userInfo); apiClient.setToken(oauthResult.accessToken); if (onAuthChange) onAuthChange(); } else { const storedUserInfo = getStoredUserInfo(); if (storedUserInfo) { setUserInfo(storedUserInfo); } } } catch (error) { console.error('OAuth initialization error:', error); } finally { setIsAuthLoading(false); } }; const handleLogin = async () => { try { await loginWithHuggingFace(); } catch (error) { console.error('Login failed:', error); alert('Failed to start login process. Please try again.'); } }; const handleLogout = () => { logout(); apiClient.logout(); setUserInfo(null); if (onAuthChange) onAuthChange(); window.location.reload(); }; const handleDevLogin = () => { if (!devUsername.trim()) { alert('Please enter a username'); return; } try { const result = loginDevMode(devUsername); setUserInfo(result.userInfo); apiClient.setToken(result.accessToken); setShowDevLogin(false); setDevUsername(''); if (onAuthChange) onAuthChange(); } catch (error) { console.error('Dev login failed:', error); alert('Failed to login in dev mode'); } }; // Close dropdowns when clicking outside useEffect(() => { const handleClickOutside = (event: MouseEvent) => { if (languageDropdownRef.current && !languageDropdownRef.current.contains(event.target as Node)) { setShowLanguageDropdown(false); } if (modelDropdownRef.current && !modelDropdownRef.current.contains(event.target as Node)) { setShowModelDropdown(false); } if (importDialogRef.current && !importDialogRef.current.contains(event.target as Node)) { setShowImportDialog(false); } if (redesignDialogRef.current && !redesignDialogRef.current.contains(event.target as Node)) { setShowRedesignDialog(false); } }; document.addEventListener('mousedown', handleClickOutside); return () => { document.removeEventListener('mousedown', handleClickOutside); }; }, []); const loadData = async () => { console.log('loadData called'); setIsLoading(true); await Promise.all([loadModels(), loadLanguages()]); setIsLoading(false); console.log('loadData completed'); }; const loadModels = async () => { try { console.log('Loading models...'); const modelsList = await apiClient.getModels(); console.log('Models loaded successfully:', modelsList); console.log('Number of models:', modelsList.length); setModels(modelsList); console.log('Models state updated'); } catch (error) { console.error('Failed to load models:', error); setModels([]); // Set empty array on error } }; const loadLanguages = async () => { try { const { languages: languagesList } = await apiClient.getLanguages(); setLanguages(languagesList); } catch (error) { console.error('Failed to load languages:', error); } }; const loadTrendingApps = async () => { try { const apps = await apiClient.getTrendingAnycoderApps(); setTrendingApps(apps); } catch (error) { console.error('Failed to load trending apps:', error); } }; const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); if (prompt.trim() && isAuthenticated) { onStart(prompt.trim(), selectedLanguage, selectedModel); } else if (!isAuthenticated) { alert('Please sign in with HuggingFace first!'); } }; const formatLanguageName = (lang: Language) => { if (lang === 'html') return 'HTML'; if (lang === 'transformers.js') return 'Transformers.js'; if (lang === 'comfyui') return 'ComfyUI'; return lang.charAt(0).toUpperCase() + lang.slice(1); }; // Check if user owns the imported space const checkSpaceOwnership = (url: string) => { if (!url || !userInfo?.preferred_username) { setIsSpaceOwner(false); return; } const spaceMatch = url.match(/huggingface\.co\/spaces\/([^\/\s\)]+)\/[^\/\s\)]+/); if (spaceMatch) { const spaceOwner = spaceMatch[1]; const isOwner = spaceOwner === userInfo.preferred_username; setIsSpaceOwner(isOwner); console.log('[Import] Space owner:', spaceOwner, '| Current user:', userInfo.preferred_username, '| Is owner:', isOwner); // Auto-select update mode if owner, otherwise duplicate if (isOwner) { setImportAction('update'); } else { setImportAction('duplicate'); } } else { setIsSpaceOwner(false); } }; const handleImportProject = async () => { if (!importUrl.trim()) { setImportError('Please enter a valid URL'); return; } if (!isAuthenticated) { alert('Please sign in with HuggingFace first!'); return; } setIsImporting(true); setImportError(''); try { console.log('[Import] ========== STARTING IMPORT =========='); console.log('[Import] Import URL:', importUrl); console.log('[Import] Action:', importAction); // Extract space ID from URL const spaceMatch = importUrl.match(/huggingface\.co\/spaces\/([^\/\s\)]+\/[^\/\s\)]+)/); console.log('[Import] Space regex match result:', spaceMatch); if (spaceMatch) { const fromSpaceId = spaceMatch[1]; console.log('[Import] ✅ Detected HF Space:', fromSpaceId); // Import the code first (always needed to load in editor) const importResult = await apiClient.importProject(importUrl); if (importResult.status !== 'success') { setImportError(importResult.message || 'Failed to import project'); setIsImporting(false); return; } // Handle different import actions if (importAction === 'update' && isSpaceOwner) { // Option 1: Update existing space directly (for owners) console.log('[Import] Owner update - loading code for direct update to:', fromSpaceId); if (onImport && importResult.code) { // Pass the original space URL so future deployments update it onImport(importResult.code, importResult.language || 'html', importUrl); alert(`✅ Code loaded!\n\nYou can now make changes and deploy them directly to: ${importUrl}\n\nThe code has been loaded in the editor.`); } setShowImportDialog(false); setImportUrl(''); } else if (importAction === 'pr') { // Option 2: Create Pull Request console.log('[Import] PR mode - loading code to create PR to:', fromSpaceId); if (onImport && importResult.code) { // Load code in editor with the original space for PR tracking onImport(importResult.code, importResult.language || 'html', importUrl); // Set pending PR state so any future code generation creates a PR if (setPendingPR && pendingPRRef) { const prInfo = { repoId: fromSpaceId, language: (importResult.language || 'html') as Language }; setPendingPR(prInfo); pendingPRRef.current = prInfo; console.log('[Import PR] Set pending PR:', prInfo); } // Show success message alert(`✅ Code loaded in PR mode!\n\nYou can now:\n• Make manual edits in the editor\n• Generate new features with AI\n\nWhen you deploy, a Pull Request will be created to: ${fromSpaceId}`); } setShowImportDialog(false); setImportUrl(''); } else { // Option 3: Duplicate space (default) console.log('[Import] Duplicate mode - will duplicate:', fromSpaceId); const duplicateResult = await apiClient.duplicateSpace(fromSpaceId); console.log('[Import] Duplicate API response:', duplicateResult); if (duplicateResult.success) { console.log('[Import] ========== DUPLICATE SUCCESS =========='); console.log('[Import] Duplicated space URL:', duplicateResult.space_url); console.log('[Import] Duplicated space ID:', duplicateResult.space_id); console.log('[Import] =========================================='); if (onImport && importResult.code) { console.log('[Import] Calling onImport with duplicated space URL:', duplicateResult.space_url); // Pass the duplicated space URL so it's tracked for future deployments onImport(importResult.code, importResult.language || 'html', duplicateResult.space_url); // Show success message with link to duplicated space alert(`✅ Space duplicated successfully!\n\nYour space: ${duplicateResult.space_url}\n\nThe code has been loaded in the editor. Any changes you deploy will update this duplicated space.`); } setShowImportDialog(false); setImportUrl(''); } else { setImportError(duplicateResult.message || 'Failed to duplicate space'); } } } else { // Not a Space URL - fall back to regular import console.log('[Import] ❌ Not a HF Space URL - using regular import'); const result = await apiClient.importProject(importUrl); if (result.status === 'success') { if (onImport && result.code) { onImport(result.code, result.language || 'html', importUrl); } else { const importMessage = `Imported from ${importUrl}`; onStart(importMessage, result.language || 'html', selectedModel); } setShowImportDialog(false); setImportUrl(''); } else { setImportError(result.message || 'Failed to import project'); } } } catch (error: any) { console.error('Import error:', error); setImportError(error.response?.data?.message || error.message || 'Failed to import project'); } finally { setIsImporting(false); } }; const handleRedesignProject = async () => { if (!redesignUrl.trim()) { setRedesignError('Please enter a valid URL'); return; } if (!isAuthenticated) { alert('Please sign in with HuggingFace first!'); return; } setIsRedesigning(true); setRedesignError(''); try { // Extract space ID from URL const spaceMatch = redesignUrl.match(/huggingface\.co\/spaces\/([^\/\s\)]+\/[^\/\s\)]+)/); const repoId = spaceMatch ? spaceMatch[1] : null; if (!repoId) { setRedesignError('Please enter a valid HuggingFace Space URL'); setIsRedesigning(false); return; } // Import the code first const result = await apiClient.importProject(redesignUrl); if (result.status !== 'success') { setRedesignError(result.message || 'Failed to import project for redesign'); setIsRedesigning(false); return; } if (!createPR) { // Option 1: Redesign WITHOUT PR - Duplicate space first, then generate redesign console.log('[Redesign] Duplicating space first:', repoId); try { const duplicateResult = await apiClient.duplicateSpace(repoId); console.log('[Redesign] Duplicate result:', duplicateResult); if (!duplicateResult.success) { setRedesignError(duplicateResult.message || 'Failed to duplicate space'); setIsRedesigning(false); return; } // Load code and trigger redesign if (onImport && onStart) { // Pass duplicated space URL onImport(result.code, result.language || 'html', duplicateResult.space_url); // Extract duplicated space ID to pass to generation const dupSpaceMatch = duplicateResult.space_url?.match(/huggingface\.co\/spaces\/([^\/\s\)]+\/[^\/\s\)]+)/); const duplicatedRepoId = dupSpaceMatch ? dupSpaceMatch[1] : undefined; console.log('[Redesign] Duplicated space ID:', duplicatedRepoId); setTimeout(() => { const isGradio = (result.language || 'html') === 'gradio'; const redesignPrompt = `I have existing code in the editor from a duplicated space. Please redesign it to make it look better with minimal components needed, mobile friendly, and modern design. Current code: \`\`\`${result.language || 'html'} ${result.code} \`\`\` Please redesign this with: - Minimal, clean components - Mobile-first responsive design - Modern UI/UX best practices - Better visual hierarchy and spacing ${isGradio ? '\n\nIMPORTANT: Only output app.py with the redesigned UI (themes, layout, styling). Do NOT modify or output any other .py files (utils.py, models.py, etc.). Do NOT include requirements.txt or README.md.' : ''}`; if (onStart) { // Pass duplicated space ID so auto-deploy updates it console.log('[Redesign] Calling onStart with duplicated repo ID:', duplicatedRepoId); console.log('[Redesign] Using Claude-Sonnet-4.5 for redesign'); onStart(redesignPrompt, result.language || 'html', 'claude-sonnet-4.5', duplicatedRepoId); } }, 100); // Show success message alert(`✅ Space duplicated!\n\nYour space: ${duplicateResult.space_url}\n\nGenerating redesign now...`); } setShowRedesignDialog(false); setRedesignUrl(''); } catch (dupError: any) { console.error('[Redesign] Duplication error:', dupError); setRedesignError(dupError.response?.data?.message || dupError.message || 'Failed to duplicate space'); setIsRedesigning(false); return; } } else { // Option 2: Redesign WITH PR - Import code and generate, then create PR if (onImport && onStart) { onImport(result.code, result.language || 'html', redesignUrl); setTimeout(() => { const isGradio = (result.language || 'html') === 'gradio'; const redesignPrompt = `I have existing code in the editor that I imported from ${redesignUrl}. Please redesign it to make it look better with minimal components needed, mobile friendly, and modern design. Current code: \`\`\`${result.language || 'html'} ${result.code} \`\`\` Please redesign this with: - Minimal, clean components - Mobile-first responsive design - Modern UI/UX best practices - Better visual hierarchy and spacing ${isGradio ? '\n\nIMPORTANT: Only output app.py with the redesigned UI (themes, layout, styling). Do NOT modify or output any other .py files (utils.py, models.py, etc.). Do NOT include requirements.txt or README.md.' : ''} Note: After generating the redesign, I will create a Pull Request on the original space.`; if (onStart) { console.log('[Redesign] Will create PR - not passing repo ID'); console.log('[Redesign] Using Claude-Sonnet-4.5 for redesign'); onStart(redesignPrompt, result.language || 'html', 'claude-sonnet-4.5', repoId, true); // Pass true for shouldCreatePR } console.log('[Redesign] Will create PR after code generation completes'); }, 100); setShowRedesignDialog(false); setRedesignUrl(''); } else { setRedesignError('Missing required callbacks. Please try again.'); } } } catch (error: any) { console.error('Redesign error:', error); setRedesignError(error.response?.data?.message || error.message || 'Failed to process redesign request'); } finally { setIsRedesigning(false); } }; return (
{/* Header - Apple style */}
AnyCoder {/* Auth Section */}
{isAuthLoading ? ( Loading... ) : userInfo ? (
{userInfo.avatarUrl && ( {userInfo.name} )} {userInfo.preferredUsername || userInfo.name}
) : (
{/* Dev Mode Login (only on localhost) */} {isDevMode && ( <> {showDevLogin ? (
setDevUsername(e.target.value)} onKeyPress={(e) => e.key === 'Enter' && handleDevLogin()} placeholder="username" className="px-3 py-1.5 rounded-lg text-sm bg-[#1d1d1f] text-[#f5f5f7] border border-[#424245] focus:outline-none focus:border-white/50 w-32 font-medium" autoFocus />
) : ( )} or )} {/* OAuth Login */}
)}
{/* Main Content - Apple-style centered layout */}
{/* Apple-style Headline */}

Build with AnyCoder

Create apps with AI

{/* Simple prompt form */}
{/* Textarea */}