"use client" import { useState, useEffect } from "react" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" import { Badge } from "@/components/ui/badge" import { Checkbox } from "@/components/ui/checkbox" import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from "@/components/ui/dialog" import { ArrowRight, Plus, Globe, BarChart3, Zap, Code, Search, Star, Clock, Users, Layers, AlertCircle, Edit, Trash2, User, Palette, GitBranch } from "lucide-react" import { useTemplates } from "@/hooks/useTemplates" import { CustomTemplateForm } from "@/components/custom-template-form" import { EditTemplateForm } from "@/components/edit-template-form" import { DeleteConfirmationDialog } from "@/components/delete-confirmation-dialog" import { DatabaseTemplate, TemplateFeature } from "@/lib/template-service" import AICustomFeatureCreator from "@/components/ai/AICustomFeatureCreator" import { BACKEND_URL } from "@/config/backend" // Removed Tooltip import as we are no longer using tooltips for title/description import WireframeCanvas from "@/components/wireframe-canvas" import TypeformSurvey, { SurveySummary } from "@/components/business-context/typeform-survey" import PromptSidePanel from "@/components/prompt-side-panel" import { DualCanvasEditor } from "@/components/dual-canvas-editor" import { getAccessToken } from "@/components/apis/authApiClients" import TechStackSummary from "@/components/tech-stack-summary" import { attachRepository, detectProvider, connectProvider, AttachRepositoryResponse } from "@/lib/api/vcs" import { getGitHubAuthStatus } from "@/lib/api/github" import ViewUserReposButton from "@/components/github/ViewUserReposButton" import { ErrorBanner } from "@/components/ui/error-banner" import { useAuth } from "@/contexts/auth-context" import { authApiClient } from "@/components/apis/authApiClients" interface Template { id: string title: string description: string category: string features: string[] complexity: number timeEstimate: string techStack: string[] popularity?: number lastUpdated?: string type?: string icon?: string | null gradient?: string | null border?: string | null text?: string | null subtext?: string | null is_active?: boolean created_at?: string updated_at?: string featureCount?: number is_custom?: boolean; // Add this field to identify custom templates } function TemplateSelectionStep({ onNext }: { onNext: (template: Template) => void }) { const { user } = useAuth() const [selectedCategory, setSelectedCategory] = useState("all") const [searchQuery, setSearchQuery] = useState("") const [showCustomForm, setShowCustomForm] = useState(false) const [showCreateOptionDialog, setShowCreateOptionDialog] = useState(false) const [showGitForm, setShowGitForm] = useState(false) const [gitProvider, setGitProvider] = useState('') const [gitUrl, setGitUrl] = useState('') const [gitBranch, setGitBranch] = useState('main') const [gitAuthMethod, setGitAuthMethod] = useState('') const [gitCredentials, setGitCredentials] = useState({ username: '', password: '', token: '', sshKey: '' }) const [authLoading, setAuthLoading] = useState(false) const [gitStep, setGitStep] = useState<'provider' | 'url'>('provider') // Sync progress state for private repositories const [syncProgress, setSyncProgress] = useState<{ show: boolean; provider: string; repositoryUrl: string; branchName: string; status: string; stage: string; } | null>(null) // Monitor private repository sync progress const monitorPrivateRepoSync = async (provider: string, repositoryUrl: string) => { let pollCount = 0; const maxPolls = 60; // 2 minutes with 2-second intervals (reduced from 5 minutes) const pollInterval = setInterval(async () => { pollCount++; try { // Poll for repository sync status const response = await authApiClient.get(`/api/vcs/${provider}/repositories?t=${Date.now()}`, { headers: { 'x-user-id': user?.id } }); if (response.data?.success) { const repositories = response.data.data || []; const repo = repositories.find((r: any) => r.repository_url === repositoryUrl); if (repo) { const status = repo.sync_status; let stage = ''; switch (status) { case 'authenticating': stage = 'Authenticating with provider...'; break; case 'syncing': stage = 'Downloading repository files...'; break; case 'synced': stage = 'Repository sync completed!'; clearInterval(pollInterval); setSyncProgress(null); alert(`✅ Repository attached successfully!\n\nProvider: ${provider.toUpperCase()}\nRepository: ${repositoryUrl}\n\nYour repository is now available in your repositories list.`); // Refresh repositories list window.location.reload(); return; case 'error': stage = 'Sync failed. Please try again.'; clearInterval(pollInterval); setTimeout(() => { setSyncProgress(null); }, 3000); alert(`❌ Repository sync failed!\n\nProvider: ${provider.toUpperCase()}\nRepository: ${repositoryUrl}\n\nPlease try again or contact support.`); return; default: stage = 'Processing...'; } setSyncProgress(prev => prev ? { ...prev, status, stage } : null); } else { // Repository not found in list yet, continue polling setSyncProgress(prev => prev ? { ...prev, stage: 'Processing...' } : null); } } } catch (error) { console.error('Error monitoring sync:', error); // If we get too many errors, stop polling if (pollCount > 10) { clearInterval(pollInterval); setSyncProgress(null); alert(`⚠️ Unable to monitor repository sync status.\n\nRepository: ${repositoryUrl}\n\nPlease check your repositories list manually.`); } } // Stop polling after max attempts if (pollCount >= maxPolls) { clearInterval(pollInterval); setSyncProgress(null); alert(`⏰ Repository sync is taking longer than expected.\n\nRepository: ${repositoryUrl}\n\nPlease check your repositories list manually.`); } }, 2000); // Poll every 2 seconds } const [isGithubConnected, setIsGithubConnected] = useState(null) const [connectionError, setConnectionError] = useState(null) // Cleanup sync progress on component unmount useEffect(() => { return () => { setSyncProgress(null); }; }, []); useEffect(() => { (async () => { try { setConnectionError(null) const status = await getGitHubAuthStatus() setIsGithubConnected(!!status?.data?.connected) } catch (error: any) { console.warn('Failed to check GitHub auth status:', error) setIsGithubConnected(false) // Check if it's a connectivity issue if (error?.code === 'ECONNREFUSED' || error?.code === 'ENOTFOUND' || error?.code === 'ETIMEDOUT') { setConnectionError('Unable to connect to the server. Please ensure the backend is running.') } else if (error?.response?.status >= 500) { setConnectionError('Server error. Please try again later.') } } })() }, []) // Handle OAuth callback for all providers with enhanced private repo flow useEffect(() => { const handleVcsCallback = async () => { // Check for private repo sync info from project-builder redirect try { const syncInfo = sessionStorage.getItem('private_repo_sync'); if (syncInfo) { const { provider, repositoryUrl, branchName, syncStatus } = JSON.parse(syncInfo); console.log(`🔄 [Main Dashboard] Restoring private repo sync:`, { provider, repositoryUrl, syncStatus }); setSyncProgress({ show: true, provider, repositoryUrl, branchName, status: syncStatus, stage: 'Starting sync...' }); // Start monitoring sync progress monitorPrivateRepoSync(provider, repositoryUrl); // Clear the stored info sessionStorage.removeItem('private_repo_sync'); } } catch (e) { console.warn('Failed to restore sync info:', e); } const urlParams = new URLSearchParams(window.location.search); const oauthSuccess = urlParams.get('oauth_success'); const provider = urlParams.get('provider'); const syncPrivateRepo = urlParams.get('sync_private_repo'); const repositoryUrl = urlParams.get('repository_url'); const branchName = urlParams.get('branch_name'); const syncStatus = urlParams.get('sync_status'); // Handle OAuth success redirect from backend if (oauthSuccess === 'true' && provider) { console.log(`🔐 [VCS OAuth] OAuth success for ${provider.toUpperCase()}`); if (syncPrivateRepo === 'true' && repositoryUrl) { console.log(`🔄 [VCS OAuth] Starting private repository sync monitoring: ${repositoryUrl}`); // Show progress notification for private repo sync setSyncProgress({ show: true, provider: provider, repositoryUrl: repositoryUrl, branchName: branchName || 'main', status: syncStatus || 'authenticating', stage: 'Starting sync...' }); // Start monitoring sync progress monitorPrivateRepoSync(provider, repositoryUrl); // Clean up URL parameters but keep sync progress visible const newUrl = window.location.pathname; window.history.replaceState({}, document.title, newUrl); } else { const attachRepo = urlParams.get('attach_repo'); if (attachRepo === 'true' && repositoryUrl) { console.log(`🔄 [VCS OAuth] Auto-attaching repository after OAuth: ${repositoryUrl}`); try { // Automatically attach the repository const response = await authApiClient.post(`/api/vcs/${provider}/attach-repository`, { repository_url: repositoryUrl, branch_name: branchName || undefined, user_id: user?.id }, { headers: { 'x-user-id': user?.id } }); if (response.data?.success) { alert(`✅ ${provider.toUpperCase()} account connected and repository attached successfully!`); // Clean up URL parameters const newUrl = window.location.pathname; window.history.replaceState({}, document.title, newUrl); // Reset form and close dialogs setShowGitForm(false); setShowCreateOptionDialog(false); setGitUrl(''); setGitBranch('main'); setGitProvider(''); } else { alert(`${provider.toUpperCase()} account connected, but failed to attach repository. Please try again.`); } } catch (attachError) { console.error('Failed to attach repository after OAuth:', attachError); alert(`${provider.toUpperCase()} account connected, but failed to attach repository. Please try again.`); } } else { console.log(`🔐 [VCS OAuth] ${provider.toUpperCase()} account connected successfully`); alert(`${provider.toUpperCase()} account connected successfully!`); // Clean up URL parameters const newUrl = window.location.pathname; window.history.replaceState({}, document.title, newUrl); } } } }; handleVcsCallback(); }, [user]) const [editingTemplate, setEditingTemplate] = useState(null) // Authentication handlers for different providers const handleOAuthAuth = async (provider: string) => { setAuthLoading(true) try { console.log(`🔐 [handleOAuthAuth] Starting OAuth for provider: ${provider}`) // Use the new VCS API for all providers await connectProvider(provider) } catch (error) { console.error('OAuth error:', error) alert('OAuth authentication failed') } finally { setAuthLoading(false) } } const handleTokenAuth = async (provider: string, token: string) => { setAuthLoading(true) try { const providerConfig = gitProviders[provider as keyof typeof gitProviders] // Validate token with provider API const response = await fetch(`${providerConfig.apiEndpoint}/user`, { headers: { 'Authorization': `Bearer ${token}`, 'Accept': 'application/json' } }) if (!response.ok) { throw new Error('Invalid token') } const userData = await response.json() alert(`Authenticated as ${userData.login || userData.username || userData.name}`) return true } catch (error) { console.error('Token auth error:', error) alert('Token authentication failed') return false } finally { setAuthLoading(false) } } const handleUsernamePasswordAuth = async (provider: string, username: string, password: string) => { setAuthLoading(true) try { // For username/password auth, you'd typically use Basic Auth // or convert to token-based auth const credentials = btoa(`${username}:${password}`) const response = await fetch(`${gitProviders[provider as keyof typeof gitProviders].apiEndpoint}/user`, { headers: { 'Authorization': `Basic ${credentials}`, 'Accept': 'application/json' } }) if (!response.ok) { throw new Error('Invalid credentials') } const userData = await response.json() alert(`Authenticated as ${userData.login || userData.username || userData.name}`) return true } catch (error) { console.error('Username/password auth error:', error) alert('Authentication failed') return false } finally { setAuthLoading(false) } } // Provider-specific configuration const gitProviders = { github: { name: 'GitHub', icon: 'M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z', placeholder: 'https://github.com/org/repo.git', authMethods: ['token', 'ssh', 'oauth'], oauthEndpoint: '/api/vcs/github/auth/start', apiEndpoint: 'https://api.github.com' }, bitbucket: { name: 'Bitbucket', icon: 'M.778 1.213a.768.768 0 00-.768.892l3.263 19.81c.084.5.515.868 1.022.873H19.95a.772.772 0 00.77-.646l3.27-20.03a.768.768 0 00-.768-.891L.778 1.213zM14.518 18.197H9.482l-1.4-8.893h7.436l-1.4 8.893z', placeholder: 'https://bitbucket.org/org/repo.git', authMethods: ['username_password', 'app_password', 'oauth'], oauthEndpoint: '/api/vcs/bitbucket/auth/start', apiEndpoint: 'https://api.bitbucket.org/2.0' }, gitlab: { name: 'GitLab', icon: 'M23.6004 9.5927l-.0337-.0862L20.3.9814a.851.851 0 0 0-.3362-.405.874.874 0 0 0-.9997.0539.874.874 0 0 0-.29.4399l-2.5465 7.7838H7.2162l-2.5465-7.7838a.857.857 0 0 0-.29-.4412.874.874 0 0 0-.9997-.0537.858.858 0 0 0-.3362.4049L.4332 9.5015l-.0325.0862a6.065 6.065 0 0 0 2.0119 7.0105l.0113.0087.03.0213 4.976 3.7264 2.462 1.8633 1.4995 1.1321a1.008 1.008 0 0 0 1.2197 0l1.4995-1.1321 2.4619-1.8633 5.006-3.7466.0125-.01a6.068 6.068 0 0 0 2.0094-7.003z', placeholder: 'https://gitlab.com/org/repo.git', authMethods: ['token', 'oauth', 'ssh'], oauthEndpoint: '/api/vcs/gitlab/auth/start', apiEndpoint: 'https://gitlab.com/api/v4' }, gitea: { name: 'Gitea', icon: 'M12 0C5.374 0 0 5.373 0 12s5.374 12 12 12 12-5.373 12-12S18.626 0 12 0zm0 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10-4.477 10-10 10zm-1-6h2v2h-2v-2zm0-8h2v6h-2V8z', placeholder: 'https://gitea.com/org/repo.git', authMethods: ['token', 'oauth', 'ssh'], oauthEndpoint: '/api/vcs/gitea/auth/start', apiEndpoint: 'https://gitea.com/api/v1' } } const [deletingTemplate, setDeletingTemplate] = useState(null) const [deleteLoading, setDeleteLoading] = useState(false) // Keep a stable list of all categories seen so the filter chips don't disappear const [knownCategories, setKnownCategories] = useState>(new Set(["all"])) // Cache counts per category using the API totals for each filtered fetch // Use undefined to indicate "unknown" instead of showing wrong initial counts const [categoryCounts, setCategoryCounts] = useState>({ all: 0 }) // Track per-card expanded state for descriptions const [expandedDescriptions, setExpandedDescriptions] = useState>({}) const [descDialogOpen, setDescDialogOpen] = useState(false) const [descDialogData, setDescDialogData] = useState<{ title: string; description: string }>({ title: '', description: '' }) const { user: templateUser, combined, loading, error, paginationState, createTemplate, updateTemplate, deleteTemplate, fetchTemplatesWithPagination, loadMoreTemplates, categories: hookCategories, } = useTemplates() // Initial fetch is handled inside useTemplates hook; avoid duplicate fetch here // Handle category changes immediately (no debounce) so switching chips is snappy useEffect(() => { if (selectedCategory !== paginationState.selectedCategory) { console.log('[TemplateSelectionStep] Triggering fetch due to category change:', { selectedCategory }); fetchTemplatesWithPagination({ page: 0, pageSize: paginationState.pageSize, category: selectedCategory, search: searchQuery, resetPagination: true, }); } }, [selectedCategory, paginationState.selectedCategory, paginationState.pageSize, searchQuery, fetchTemplatesWithPagination]); // Handle search changes with debouncing useEffect(() => { const timeoutId = setTimeout(() => { if (searchQuery !== paginationState.searchQuery) { console.log('[TemplateSelectionStep] Triggering fetch due to search change:', { searchQuery }); fetchTemplatesWithPagination({ page: 0, pageSize: paginationState.pageSize, category: selectedCategory, search: searchQuery, resetPagination: true, }); } }, 500); return () => clearTimeout(timeoutId); }, [searchQuery, selectedCategory, paginationState.searchQuery, paginationState.pageSize, fetchTemplatesWithPagination]); // Track categories seen across any fetch so the chips remain visible useEffect(() => { if (!combined?.data?.length) return; setKnownCategories((prev) => { const next = new Set(prev); combined.data.forEach((t) => { if (t.category) next.add(t.category); }); return next; }); }, [combined?.data]); // Seed known categories and counts from hookCategories (pre-fetched full counts) useEffect(() => { if (!hookCategories || hookCategories.length === 0) return; setKnownCategories((prev) => { const next = new Set(prev); hookCategories.forEach((c) => next.add(c.id)); return next; }); setCategoryCounts((prev) => { const next: Record = { ...prev }; hookCategories.forEach((c) => { next[c.id] = c.count; }); // Ensure 'all' exists if provided by hook const allFromHook = hookCategories.find((c) => c.id === 'all')?.count; if (typeof allFromHook === 'number') next['all'] = allFromHook; return next; }); }, [hookCategories]); // Update counts cache based on API totals for the currently selected category useEffect(() => { const currentCat = paginationState.selectedCategory || 'all'; const totalForFilter = paginationState.total || 0; setCategoryCounts((prev) => ({ ...prev, [currentCat]: totalForFilter, })); }, [paginationState.selectedCategory, paginationState.total]); const templates: Template[] = combined?.data?.length ? combined.data.map((t) => { console.log('[TemplateSelectionStep] Processing template:', { id: t.id, title: t.title, category: t.category, feature_count: t.feature_count }); return { id: t.id, title: t.title, description: t.description || "No description available", category: t.category, features: [], complexity: 3, // Default complexity since DatabaseTemplate doesn't have this property timeEstimate: "2-4 weeks", techStack: ["Next.js", "PostgreSQL", "Tailwind CSS"], popularity: t.avg_rating ? Math.round(t.avg_rating * 20) : 75, lastUpdated: t.updated_at ? new Date(t.updated_at).toISOString().split('T')[0] : undefined, type: t.type, icon: t.icon, gradient: t.gradient, border: t.border, text: t.text, subtext: t.subtext, is_active: t.is_active, created_at: t.created_at, updated_at: t.updated_at, featureCount: t.feature_count || 0, // Add feature count from API is_custom: t.is_custom, // Add is_custom field }; }) : []; // Debug logging console.log('[TemplateSelectionStep] Debug:', { hasCombined: !!combined, hasData: !!combined?.data, dataType: typeof combined?.data, isArray: Array.isArray(combined?.data), dataLength: combined?.data?.length || 0, templatesLength: templates.length, paginationState, templates: templates.map((t) => ({ id: t.id, title: t.title, category: t.category })), }); const getCategories = () => { const categoryMap = new Map; count: number }>(); // All category: prefer live total when currently filtered by 'all', // otherwise show the last cached overall total (if any) const liveAllTotal = paginationState.selectedCategory === 'all' ? (paginationState.total || 0) : 0; const cachedAllTotal = categoryCounts['all'] ?? 0; categoryMap.set("all", { name: "All Templates", icon: Globe, count: Math.max(liveAllTotal, cachedAllTotal) }); // Build chips from the union of all categories we have ever seen Array.from(knownCategories) .filter((c) => c !== 'all') .forEach((categoryId) => { const lower = categoryId.toLowerCase(); let icon = Code; if (lower.includes('marketing') || lower.includes('branding')) icon = Zap; else if (lower.includes('seo') || lower.includes('content')) icon = BarChart3; else if (lower.includes('food') || lower.includes('delivery')) icon = Users; // Prefer cached count for this category (set when that filter is active). // Do NOT fallback to visible page count to avoid misleading numbers. const count = categoryCounts[categoryId]; categoryMap.set(categoryId, { name: categoryId, icon, // If count is unknown, represent as 0 in data and handle placeholder in UI count: typeof count === 'number' ? count : 0, }); }); return Array.from(categoryMap.entries()).map(([id, data]) => ({ id, ...data })); }; const categories = getCategories(); const getComplexityColor = (complexity: number) => { if (complexity <= 2) return "bg-emerald-900/40 text-emerald-300 border border-emerald-800" if (complexity <= 3) return "bg-amber-900/40 text-amber-300 border border-amber-800" return "bg-rose-900/40 text-rose-300 border border-rose-800" } const getComplexityLabel = (complexity: number) => { if (complexity <= 2) return "Simple" if (complexity <= 3) return "Moderate" return "Complex" } // Truncate helper to restrict displayed characters const TITLE_MAX_CHARS = 15; const DESC_MAX_CHARS = 120; const truncate = (value: string | undefined | null, max: number) => { const v = (value || '').trim(); if (v.length <= max) return v; return v.slice(0, Math.max(0, max - 1)) + '…'; }; const handleCreateTemplate = async (templateData: Partial): Promise => { try { const created = await createTemplate({ ...(templateData as any), // Ensure backend routes to custom_templates table is_custom: true, source: 'custom', // Attach user id so custom template is associated with creator user_id: (templateUser as any)?.id, } as any); setShowCustomForm(false); return created; } catch (error) { console.error('[TemplateSelectionStep] Error creating template:', error); // Re-throw to satisfy the return type contract when errors should propagate throw error as Error; } }; const handleCreateFromGit = async () => { try { if (!gitUrl.trim()) { alert('Please enter a Git repository URL'); return; } // Detect provider from URL const detectedProvider = detectProvider(gitUrl.trim()); console.log('🔍 [handleCreateFromGit] Detected provider:', detectedProvider); console.log('🔍 [handleCreateFromGit] Git URL:', gitUrl.trim()); // Check if repository already exists for this user try { const existingReposResponse = await authApiClient.get(`/api/vcs/${detectedProvider}/repositories?t=${Date.now()}`, { headers: { 'x-user-id': user?.id } }); if (existingReposResponse.data?.success) { const repositories = existingReposResponse.data.data || []; const existingRepo = repositories.find((r: any) => r.repository_url === gitUrl.trim()); if (existingRepo) { alert(`✅ Repository already exists!\n\nProvider: ${detectedProvider.toUpperCase()}\nRepository: ${gitUrl.trim()}\nStatus: ${existingRepo.sync_status}\n\nThis repository is already available in your repositories list.`); setShowCreateOptionDialog(false); setShowGitForm(false); return; } } } catch (error) { console.log('🔍 [handleCreateFromGit] Could not check existing repositories, proceeding with attachment:', error); // Continue with attachment even if we can't check existing repos } // Attach the repository via backend (skip template creation) try { const attachResult = await attachRepository({ repository_url: gitUrl.trim(), branch_name: (gitBranch?.trim() || undefined), }) // Check if authentication is required if (attachResult.requires_auth) { console.log('🔐 Private repository detected, auto-redirecting to OAuth:', attachResult) // Store the auth_url for OAuth redirect try { sessionStorage.setItem('pending_git_attach', JSON.stringify({ repository_url: gitUrl.trim(), branch_name: (gitBranch?.trim() || undefined), provider: detectedProvider, auth_url: attachResult.auth_url })) console.log('💾 Stored pending git attach with auth_url in sessionStorage') } catch (e) { console.warn('⚠️ Failed to store pending git attach:', e) } // Auto-redirect to OAuth console.log('🔐 Private repository detected - auto-redirecting to OAuth:', attachResult.auth_url) if (attachResult.auth_url) { window.location.replace(attachResult.auth_url) } else { alert('Authentication URL not available. Please try again.') } return } // Show success message and reset form alert('Repository attached successfully! You can now proceed with your project.'); setShowCreateOptionDialog(false) setShowGitForm(false) setGitProvider('') setGitUrl('') setGitBranch('main') // Return a mock template object to proceed to next step return { id: 'git-imported', title: `Imported from ${detectedProvider.charAt(0).toUpperCase() + detectedProvider.slice(1)}`, description: `Template imported from ${detectedProvider.charAt(0).toUpperCase() + detectedProvider.slice(1)}: ${gitUrl}`, type: 'custom', category: 'imported', is_custom: true, source: 'git', git_url: gitUrl.trim(), git_branch: gitBranch?.trim() || 'main', git_provider: detectedProvider } } catch (attachErr) { console.error('[TemplateSelectionStep] attachRepository failed:', attachErr) const err: any = attachErr const status = err?.response?.status const data = err?.response?.data console.log('🔍 HandleCreateFromGit error response:', { status, data }) console.log('🔍 Error details:', { message: err?.message, requires_auth: data?.requires_auth, auth_url: data?.auth_url }) console.log('🔍 Full error object:', err) console.log('🔍 Error response object:', err?.response) // If backend signals auth required, show authentication button instead of auto-redirect if ((status === 401 || status === 200) && (data?.requires_auth || data?.message?.includes('authentication'))) { console.log('🔐 Private repository detected, showing authentication button:', { status, requires_auth: data?.requires_auth, message: data?.message }) // Store the auth_url for when user clicks the authenticate button try { sessionStorage.setItem('pending_git_attach', JSON.stringify({ repository_url: gitUrl.trim(), branch_name: (gitBranch?.trim() || undefined), provider: detectedProvider, auth_url: data?.auth_url })) console.log('💾 Stored pending git attach with auth_url in sessionStorage') } catch (e) { console.warn('⚠️ Failed to store pending git attach:', e) } // Don't auto-redirect, let the user click the authenticate button console.log('🔐 Private repository detected - user must click authenticate button') return } if (status === 403) { alert('Repository not accessible - you may not have permission to access this repository') return } // Fallback: if we have an auth_url in the error, try to redirect anyway if (data?.auth_url && !data?.success) { console.log('🔐 Fallback OAuth redirect - auth_url found in error response:', data.auth_url) window.location.replace(data.auth_url) return } // Additional fallback: check if the error message contains auth_url if (err?.message && err.message.includes('authentication') && data?.auth_url) { console.log('🔐 Additional fallback OAuth redirect - authentication message with auth_url:', data.auth_url) window.location.replace(data.auth_url) return } if (status === 404) { alert('Repository not found - please check the URL and try again') return } alert(data?.message || 'Failed to attach repository. Please verify the URL/branch and your auth.'); return; } } catch (error) { console.error('[TemplateSelectionStep] Error importing from Git:', error) const errorMessage = error instanceof Error ? error.message : 'Failed to import from Git' alert(`Error: ${errorMessage}`) throw error as Error } } const handleUpdateTemplate = async (id: string, templateData: Partial) => { try { // Find the template to determine if it's custom const template = templates.find(t => t.id === id); const isCustom = template?.is_custom || false; await updateTemplate(id, templateData, isCustom); setEditingTemplate(null); } catch (error) { console.error('[TemplateSelectionStep] Error updating template:', error); // Show user-friendly error message const errorMessage = error instanceof Error ? error.message : 'Failed to update template'; alert(`Error updating template: ${errorMessage}`); } }; const handleDeleteTemplate = async () => { if (!deletingTemplate) return; setDeleteLoading(true); try { // Find the template to determine if it's custom const template = templates.find(t => t.id === deletingTemplate.id); const isCustom = template?.is_custom || false; await deleteTemplate(deletingTemplate.id, isCustom); setDeletingTemplate(null); } catch (error) { console.error('[TemplateSelectionStep] Error deleting template:', error); // Show user-friendly error message const errorMessage = error instanceof Error ? error.message : 'Failed to delete template'; alert(`Error deleting template: ${errorMessage}`); } finally { setDeleteLoading(false); } }; if (loading && templates.length === 0) { return (

Loading Templates...

Fetching templates from /merged API

); } if (error) { // Check if this is an authentication error const isAuthError = error.includes('Please sign in') || error.includes('Authentication required'); return (
{isAuthError ? ( <>

Sign In Required

{error}

) : ( <>

Error Loading Templates

{error}

)}
); } // Show custom template form if (showCustomForm) { return (

Create Custom Template

Design your own project template

{ await handleCreateTemplate(templateData); }} onCancel={() => setShowCustomForm(false)} />
) } // Show edit template form if (editingTemplate) { return (

Edit Template

Modify your template settings

setEditingTemplate(null)} />
) } // Show delete confirmation dialog if (deletingTemplate) { return ( <>
{/* Header */}

Choose Your Project Template

Select from our comprehensive library of professionally designed templates

setDeletingTemplate(null)} loading={deleteLoading} /> ) } return (
{/* Sync Progress Notification */} {syncProgress?.show && (

Syncing {syncProgress.provider.toUpperCase()} Repository

{syncProgress.stage}

Repository: {syncProgress.repositoryUrl} {syncProgress.branchName !== 'main' && ` | Branch: ${syncProgress.branchName}`}
{syncProgress.status}
)} {/* Header */}

Choose Your Project Template

Select from our comprehensive library of professionally designed templates

{/* Connection Error Banner */} {connectionError && (
{ setConnectionError(null) // Retry the GitHub auth status check try { const status = await getGitHubAuthStatus() setIsGithubConnected(!!status?.data?.connected) } catch (error: any) { console.warn('Retry failed:', error) setIsGithubConnected(false) if (error?.code === 'ECONNREFUSED' || error?.code === 'ENOTFOUND' || error?.code === 'ETIMEDOUT') { setConnectionError('Unable to connect to the server. Please ensure the backend is running.') } } }} />
)} {!templateUser?.id && (

You're currently viewing public templates. {' '}to access your personal templates and create custom ones.

)}
{/* Right-aligned quick navigation to user repos */}
setSearchQuery(e.target.value)} className="pl-10 h-12 text-lg border border-white/10 bg-white/5 text-white placeholder:text-white/40 focus:border-white/30 focus:ring-white/30 rounded-xl" /> {paginationState.loading && (
)}
{(() => { const renderChip = (category: { id: string; name: string; icon: React.ComponentType<{ className?: string }>; count: number }) => { const Icon = category.icon; const active = selectedCategory === category.id; const knownCount = category.id === 'all' ? Math.max( selectedCategory === 'all' ? (paginationState.total || 0) : 0, categoryCounts['all'] ?? 0 ) : categoryCounts[category.id]; // Fallback to currently visible items count for initial render if unknown const fallbackCount = category.id === 'all' ? templates.length : templates.filter((t) => t.category === category.id).length; const displayCount = typeof knownCount === 'number' ? knownCount : fallbackCount; return ( ); }; const allChip = categories.find((c) => c.id === 'all'); const rest = categories.filter((c) => c.id !== 'all'); return (
{allChip && (
{renderChip(allChip)}
)}
{rest.map(renderChip)} {rest.map((c) => ({ ...c, id: `${c.id}-dup` })).map(renderChip)}
); })()}
{templates.length === 0 && !paginationState.loading ? (

No templates found for the current filters.

) : (
{templates.map((template) => (
{truncate(template.title, TITLE_MAX_CHARS)}
{getComplexityLabel(template.complexity)} {template.popularity && (
{template.popularity}%
)}
{/* Edit and Delete buttons - only show for database templates */} {template.id && template.id !== "marketing-website" && template.id !== "saas-platform" && (
)}
{(() => { const raw = (template.description || '').trim() const needsToggle = raw.length > DESC_MAX_CHARS const displayText = truncate(template.description, DESC_MAX_CHARS) return (

{displayText}

{needsToggle && ( )}
) })()}
{template.timeEstimate}
{template.featureCount || 0} features

Key Features

{template.features.slice(0, 3).map((feature, index) => ( {feature} ))} {template.features.length > 3 && ( +{template.features.length - 3} more )}
))}
)}
{(() => { const totalPages = Math.max(1, Math.ceil(paginationState.total / paginationState.pageSize)); const currentPage = paginationState.currentPage; const pages = []; // First page pages.push( ); // Ellipsis before current page if (currentPage > 3) { pages.push(...); } // Pages around current for (let i = Math.max(1, currentPage - 1); i <= Math.min(totalPages - 1, currentPage + 1); i++) { if (i > 0 && i < totalPages) { pages.push( ); } } // Ellipsis after current page if (currentPage < totalPages - 3) { pages.push(...); } // Last page (only if not already rendered by the middle range) if (totalPages > 1 && currentPage < totalPages - 2) { pages.push( ); } return pages; })()}
{ if (!templateUser?.id) { window.location.href = '/signin'; return } setShowCreateOptionDialog(true) }}>

{templateUser?.id ? 'Create Custom Template' : 'Sign In to Create Templates'}

{templateUser?.id ? "Don't worry, we'll guide you through each step. a custom project type with your specific requirements and tech stack." : "Sign in to create custom project templates with your specific requirements and tech stack." }

{searchQuery ? ( <> Showing {templates.length} template{templates.length !== 1 ? "s" : ""} matching "{searchQuery}" {paginationState.total > 0 && ` (${paginationState.total} total)`} ) : ( <> Showing {templates.length} template{templates.length !== 1 ? "s" : ""} {paginationState.total > 0 && ` of ${paginationState.total} total`} )} {paginationState.total > 0 && ( Page {paginationState.currentPage + 1} of {Math.ceil(paginationState.total / paginationState.pageSize)} )}

{descDialogData.title} {descDialogData.description} {/* Create Template Options Modal */} { setShowCreateOptionDialog(open) if (!open) { setShowGitForm(false) setGitProvider('') setGitUrl('') setGitBranch('main') setGitAuthMethod('') setGitCredentials({ username: '', password: '', token: '', sshKey: '' }) setGitStep('provider') } }}> Create Template Choose how you want to create a template. {!showGitForm ? (
) : gitStep === 'provider' ? (
{Object.entries(gitProviders).map(([key, provider]) => ( ))}
) : (
Import from {gitProviders[gitProvider as keyof typeof gitProviders]?.name}
{ setGitUrl(e.target.value) }} placeholder={gitProviders[gitProvider as keyof typeof gitProviders]?.placeholder} className="bg-white/10 border-white/20 text-white" />

Enter the full URL to your {gitProviders[gitProvider as keyof typeof gitProviders]?.name} repository

setGitBranch(e.target.value)} placeholder="main" className="bg-white/10 border-white/20 text-white" />

Leave empty to use the default branch

)}
) } // Feature Selection Step Component function FeatureSelectionStep({ template, onNext, onBack, }: { template: Template; onNext: (selected: TemplateFeature[]) => void; onBack: () => void }) { const { fetchFeatures, createFeature, updateFeature, deleteFeature } = useTemplates() const [features, setFeatures] = useState([]) const [essentialFeatures, setEssentialFeatures] = useState([]) const [customFeatures, setCustomFeatures] = useState([]) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const [selectedIds, setSelectedIds] = useState>(new Set()) const [showAIModal, setShowAIModal] = useState(false) const [expandedFeatureDescriptions, setExpandedFeatureDescriptions] = useState>({}) const [featureDescDialogOpen, setFeatureDescDialogOpen] = useState(false) const [featureDescDialogData, setFeatureDescDialogData] = useState<{ title: string; description: string }>({ title: '', description: '' }) const [rulesDialogOpen, setRulesDialogOpen] = useState(false) const [rulesForFeature, setRulesForFeature] = useState<{ featureId: string; featureName: string } | null>(null) const [featureRules, setFeatureRules] = useState>([]) const FEATURE_DESC_MAX_CHARS = 180 const truncateFeatureText = (value: string | undefined | null, max: number) => { const v = (value || '').trim() if (v.length <= max) return v return v.slice(0, Math.max(0, max - 1)) + '…' } const load = async () => { try { setLoading(true) console.log('[FeatureSelectionStep] Loading features for template:', template.id) console.log('[FeatureSelectionStep] Template object:', template) const data = await fetchFeatures(template.id) console.log('[FeatureSelectionStep] Raw features received:', data) console.log('[FeatureSelectionStep] API endpoint called:', `/api/templates/${template.id}/features`) console.log('[FeatureSelectionStep] Features by type:', { essential: data.filter(f => f.feature_type === 'essential').length, suggested: data.filter(f => f.feature_type === 'suggested').length, custom: data.filter(f => f.feature_type === 'custom').length, total: data.length }) console.log('[FeatureSelectionStep] All features with types:', data.map(f => ({ name: f.name, type: f.feature_type }))) setFeatures(data) // Separate custom features from essential features setEssentialFeatures(data.filter(f => f.feature_type !== 'custom')) setCustomFeatures(data.filter(f => f.feature_type === 'custom')) } catch (e) { console.error('[FeatureSelectionStep] Error loading features:', e) setError(e instanceof Error ? e.message : 'Failed to load features') } finally { setLoading(false) } } // Initial load useEffect(() => { load() }, [template.id]) const handleAddAIAnalyzed = async (payload: { name: string; description: string; complexity: 'low' | 'medium' | 'high'; logic_rules?: string[]; requirements?: Array<{ text: string; rules: string[] }>; business_rules?: Array<{ requirement: string; rules: string[] }> }) => { await createFeature(template.id, { name: payload.name, description: payload.description, feature_type: 'custom', complexity: payload.complexity, is_default: false, created_by_user: true, // @ts-expect-error backend accepts additional fields logic_rules: payload.logic_rules, business_rules: payload.business_rules ?? (payload.requirements ? payload.requirements.map(r => ({ requirement: r.text, rules: r.rules || [] })) : undefined), }) await load() } const [editingFeature, setEditingFeature] = useState(null) const handleUpdate = async (f: TemplateFeature, updates: Partial) => { // Use the actual id field directly (no need to extract from feature_id) await updateFeature(f.id, { ...updates, isCustom: f.feature_type === 'custom' }) await load() } const handleDelete = async (f: TemplateFeature) => { // Use the actual id field directly (no need to extract from feature_id) await deleteFeature(f.id, { isCustom: f.feature_type === 'custom' }) setSelectedIds((prev) => { const next = new Set(prev) next.delete(f.id) return next }) await load() } const toggleSelect = (f: TemplateFeature) => { setSelectedIds((prev) => { const next = new Set(prev) if (next.has(f.id)) next.delete(f.id) else next.add(f.id) return next }) } const extractRules = (f: TemplateFeature): Array => { // Prefer structured business_rules if available; fall back to additional_business_rules const candidate = (f as any).business_rules ?? (f as any).additional_business_rules ?? [] if (Array.isArray(candidate)) return candidate if (candidate && typeof candidate === 'object') return Object.entries(candidate).map(([k, v]) => `${k}: ${v as string}`) if (typeof candidate === 'string') return [candidate] return [] } const section = (title: string, list: TemplateFeature[]) => (

{title} ({list.length})

6 ? 'max-h-[480px] overflow-y-auto pr-2' : ''}`}>
{list.map((f) => (
toggleSelect(f)} className="border-white/20 data-[state=checked]:bg-orange-500 data-[state=checked]:border-orange-500" /> {f.name}
{f.feature_type === 'custom' && (
)}
{(() => { const raw = (f.description || 'No description provided.').trim() const needsToggle = raw.length > FEATURE_DESC_MAX_CHARS const displayText = truncateFeatureText(raw, FEATURE_DESC_MAX_CHARS) return (

{displayText}

{needsToggle && ( )}
) })()}
{f.feature_type} {f.complexity} {typeof f.usage_count === 'number' && used {f.usage_count}}
))}
) if (loading) { return
Loading features...
} if (error) { return
{error}
} const essentials = features.filter(f => f.feature_type === 'essential') const suggested = features.filter(f => f.feature_type === 'suggested') const custom = features.filter(f => f.feature_type === 'custom') return (

Select Features for {template.title}

Choose from essential and suggested features.

{essentialFeatures.length > 0 && section('Essential Features', essentialFeatures)} {/* Add custom feature with AI */}

Add Custom Feature

Use AI to analyze and create custom features for your project

AI will analyze your requirements and create optimized features
{customFeatures.length > 0 && section('Custom Features', customFeatures)} {(showAIModal || editingFeature) && ( { if (editingFeature) { // Update existing feature await handleUpdate(editingFeature, f) setEditingFeature(null) } else { // Add new feature await handleAddAIAnalyzed(f) setShowAIModal(false) } }} onClose={() => { setShowAIModal(false) setEditingFeature(null) }} editingFeature={editingFeature || undefined} /> )}
Select at least 3 features to continue. Selected {selectedIds.size}/3.
{featureDescDialogData.title} {featureDescDialogData.description} {rulesForFeature?.featureName || 'Feature Rules'} {featureRules.length === 0 ? (
No rules found for this feature.
) : (
    {featureRules.map((r, idx) => { if (typeof r === 'string') return
  • {r}
  • const req = (r as any).requirement const rules = (r as any).rules return (
  • {req ?
    {req}
    : null} {Array.isArray(rules) && rules.length > 0 && (
      {rules.map((rr: string, j: number) => (
    • {rr}
    • ))}
    )}
  • ) })}
)}
) } // Business Questions Step Component function BusinessQuestionsStep({ template, selected, onBack, onDone, }: { template: Template; selected: TemplateFeature[]; onBack: () => void; onDone: (completeData: any, recommendations: any) => void }) { const [businessQuestions, setBusinessQuestions] = useState([]) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const [showSummary, setShowSummary] = useState(false) const [answers, setAnswers] = useState([]) const [submitting, setSubmitting] = useState(false) const [answeredQuestionsCount, setAnsweredQuestionsCount] = useState(0) const selectedKey = selected.map(s => s.id).join(',') useEffect(() => { const load = async () => { try { setLoading(true) setError(null) if (selected.length === 0) { setError('No features selected') return } const token = getAccessToken() const resp = await fetch(`${BACKEND_URL}/api/questions/generate-comprehensive-business-questions`, { method: 'POST', headers: { 'Content-Type': 'application/json', ...(token ? { Authorization: `Bearer ${token}` } : {}) }, body: JSON.stringify({ allFeatures: selected, projectName: template.title, projectType: template.type || template.category, totalFeatures: selected.length, }), }) if (!resp.ok) throw new Error(`HTTP ${resp.status}`) const data = await resp.json() const qs: string[] = data?.data?.businessQuestions || [] setBusinessQuestions(qs) } catch (e: any) { setError(e?.message || 'Failed to load questions') } finally { setLoading(false) } } load() }, [template.id, selectedKey]) const handleSurveyComplete = (surveyAnswers: any[]) => { setAnswers(surveyAnswers) setAnsweredQuestionsCount(surveyAnswers.length) setShowSummary(true) } const handleSurveyProgress = (currentAnswers: any[]) => { const answeredCount = currentAnswers.filter(answer => answer.answer && answer.answer.trim().length > 0).length setAnsweredQuestionsCount(answeredCount) // Store the current answers so they're available for submission setAnswers(currentAnswers) } const handleSubmit = async () => { try { setSubmitting(true) setError(null) const token = getAccessToken() // Filter to only include answered questions const answeredQuestions = answers.filter(answer => answer.answer && answer.answer.trim().length > 0) const questionsWithAnswers = answeredQuestions.map(answer => ({ question: answer.question, answer: answer.answer, })) const completeData = { template, features: selected, businessContext: { questions: questionsWithAnswers, }, projectName: template.title, projectType: template.type || template.category, } console.log('🚀 Submitting comprehensive recommendations request...') console.log('📊 Total answers received:', answers.length) console.log('📊 Answered questions:', answeredQuestions.length) console.log('📊 Questions with answers:', questionsWithAnswers) console.log('📊 Complete data:', completeData) const response = await fetch(`${BACKEND_URL}/api/unified/comprehensive-recommendations`, { method: 'POST', headers: { 'Content-Type': 'application/json', ...(token ? { Authorization: `Bearer ${token}` } : {}), }, body: JSON.stringify({ template, features: selected, businessContext: { questions: questionsWithAnswers, }, projectName: template.title, projectType: template.type || template.category, templateId: template.id, budget: 15000, // Default budget - could be made configurable domain: template.category?.toLowerCase() || 'general', includeClaude: true, includeTemplateBased: true, includeDomainBased: true, }), }) if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`) } const recommendations = await response.json() console.log('✅ Comprehensive recommendations received:', recommendations) // Extract the tech recommendations from the response for the frontend component const techRecommendations = recommendations?.data?.claude?.data?.claude_recommendations || null console.log('🎯 Extracted tech recommendations:', techRecommendations) // Proceed to next step with complete data and recommendations onDone(completeData, techRecommendations) } catch (error: any) { console.error('❌ Error submitting business context:', error) setError(error?.message || 'Failed to submit business context') } finally { setSubmitting(false) } } if (loading) { return (

AI is generating comprehensive business questions...

Analyzing {selected.length} features as integrated system

) } if (error) { return (
Error Loading Questions
{error}
) } if (showSummary) { return ( setShowSummary(false)} onSubmit={handleSubmit} loading={submitting} /> ) } return (

Business Context Questions

Help us understand your {template.title} project better with these AI-generated questions

{/* Navigation buttons */}
{answeredQuestionsCount < 3 ? `Answer at least 3 questions to generate your tech stack recommendations (${answeredQuestionsCount}/3 answered)` : `Ready to generate tech stack for ${template.title} (${answeredQuestionsCount} questions answered)` }
) } // AI Mockup Step Component // function AIMockupStep({ // template, // selectedFeatures, // onNext, // onBack, // }: { // template: Template // selectedFeatures: TemplateFeature[] // onNext: () => void // onBack: () => void // }) { // const [mounted, setMounted] = useState(false) // const [wireframeData, setWireframeData] = useState(null) // const [isGenerating, setIsGenerating] = useState(false) // const [selectedDevice, setSelectedDevice] = useState<'desktop' | 'tablet' | 'mobile'>('desktop') // const [initialPrompt, setInitialPrompt] = useState("") // // Load state from localStorage after component mounts // useEffect(() => { // setMounted(true) // if (typeof window !== 'undefined') { // // Load device type // const savedDevice = localStorage.getItem('wireframe_device_type') // if (savedDevice && ['desktop', 'tablet', 'mobile'].includes(savedDevice)) { // setSelectedDevice(savedDevice as 'desktop' | 'tablet' | 'mobile') // } // // Load wireframe data // const savedWireframeData = localStorage.getItem('wireframe_data') // if (savedWireframeData) { // try { // const parsed = JSON.parse(savedWireframeData) // setWireframeData(parsed) // } catch (error) { // console.error('Failed to parse saved wireframe data:', error) // } // } // } // }, []) // // Build prompt from selected features and template // useEffect(() => { // if (!template) return // const featureNames = (selectedFeatures || []).map(f => f.name).filter(Boolean) // const base = `${template.title} dashboard` // const parts = featureNames.length > 0 ? ` with ${featureNames.join(', ')}` : '' // const footer = '. Optimize layout and spacing. Include navigation.' // const prompt = `${base}${parts}${footer}` // setInitialPrompt(prompt) // }, [template, JSON.stringify(selectedFeatures)]) // const handleWireframeGenerated = (data: any) => { // setWireframeData(data) // setIsGenerating(false) // } // const handleWireframeGenerationStart = () => { // setIsGenerating(true) // } // const handleDeviceChange = (device: 'desktop' | 'tablet' | 'mobile') => { // console.log('DEBUG: AIMockupStep handleDeviceChange called with:', device) // console.log('DEBUG: Previous selectedDevice state:', selectedDevice) // setSelectedDevice(device) // // Save to localStorage (only after mounting) // if (mounted) { // localStorage.setItem('wireframe_device_type', device) // console.log('DEBUG: Saved device type to localStorage:', device) // } // console.log('DEBUG: New selectedDevice state:', device) // } // // Save wireframe data to localStorage when it changes (only after mounting) // useEffect(() => { // if (wireframeData && mounted) { // localStorage.setItem('wireframe_data', JSON.stringify(wireframeData)) // } // }, [wireframeData, mounted]) // // Debug: Log when selectedDevice prop changes // useEffect(() => { // console.log('DEBUG: AIMockupStep selectedDevice state changed to:', selectedDevice) // }, [selectedDevice]) // // Debug: Log when selectedDevice prop changes // useEffect(() => { // console.log('DEBUG: WireframeCanvas selectedDevice prop changed to:', selectedDevice) // }, [selectedDevice]) // return ( //
//
//

AI Wireframe Mockup

//

// Generate and customize wireframes for {template.title} using AI-powered design tools //

//
// // AI-Powered Wireframe Generation //
//
// {/* Dual Canvas Editor */} //
// //
// {/* Wireframe Status and Controls */} //
//
//
//
// {wireframeData ? '✅' : '⏳'} //
//
// {wireframeData ? 'Wireframe Generated' : 'Ready to Generate'} //
//
// {wireframeData ? 'Click Continue to proceed' : 'Use the AI panel to create wireframes'} //
//
//
//
// {selectedFeatures.length} //
//
Features Selected
//
// {template.title} template //
//
//
//
// {isGenerating ? '🔄' : '🎨'} //
//
// {isGenerating ? 'Generating...' : 'AI Ready'} //
//
// {isGenerating ? 'Creating wireframe layout' : 'Claude AI powered'} //
//
//
//
// {/* Navigation */} //
//
// // //
//
// {wireframeData // ? 'Wireframe generated successfully! Continue to define business requirements.' // : 'Generate a wireframe first to continue to the next step.' // } //
//
//
// ) // } // Main Dashboard Component export function MainDashboard() { const [mounted, setMounted] = useState(false) const [currentStep, setCurrentStep] = useState(1) const [selectedTemplate, setSelectedTemplate] = useState