diff --git a/components/ProjectDashboard.js b/components/ProjectDashboard.js index 08fe8ba..73adab6 100644 --- a/components/ProjectDashboard.js +++ b/components/ProjectDashboard.js @@ -50,6 +50,7 @@ export default function ProjectDashboard({ projectData }) { const [projectComponents, setProjectComponents] = useState({ pages: projectData.pages.map((page) => ({ ...page, + instances: [], components: [], // Ensure no components are pre-rendered })), }); @@ -87,24 +88,48 @@ export default function ProjectDashboard({ projectData }) { const [sqlCode, setSqlCode] = useState(''); // New state for SQL code const initialFolderStructure = useGenerateFolderStructure(projectData, projectComponents, serverlessApis, sqlCode); // Initial folder structure - - const [availableComponents, setAvailableComponents] = useState([]); - const [dropAreaHeight, setDropAreaHeight] = useState(800); // Default height - const [isResizing, setIsResizing] = useState(false); - const [initialMouseY, setInitialMouseY] = useState(0); - const [initialHeight, setInitialHeight] = useState(800); // Match initial dropAreaHeight - - useEffect(() => { + + const [availableComponents, setAvailableComponents] = useState([]); + const [dropAreaHeight, setDropAreaHeight] = useState(800); // Default height + const [isResizing, setIsResizing] = useState(false); + const [initialMouseY, setInitialMouseY] = useState(0); + const [initialHeight, setInitialHeight] = useState(800); // Match initial dropAreaHeight + + useEffect(() => { const components = projectComponents.pages.flatMap((page) => - page.components.map((component) => component.name) + page.components.map((component) => component.name) ); setAvailableComponents(components); - }, [projectComponents]); + }, [projectComponents]); + - useEffect(() => { - setFolderStructure(initialFolderStructure); - }, [initialFolderStructure]); + const enhancedProjectData = { + ...projectData, + pages: projectData.pages.map((page) => { + const pageInstances = droppedComponents.filter( + (comp) => comp.page === page.name + ); + return { + ...page, + components: pageInstances.map((instance) => ({ + name: instance.name, + code: instance.instanceCode || instance.code, + position: instance.position, + dimensions: instance.dimensions, + })), + }; + }), + }; + + const newStructure = useGenerateFolderStructure( + enhancedProjectData, + projectComponents, + serverlessApis, + sqlCode + ); + setFolderStructure(newStructure); + }, [projectData, projectComponents, droppedComponents, serverlessApis, sqlCode]); const handleShowFolderStructure = () => { setShowFolderStructure(prev => !prev); @@ -112,110 +137,175 @@ export default function ProjectDashboard({ projectData }) { const handleDropComponent = useCallback( (componentName, offset) => { - console.log('Dropping component:', componentName, 'on page:', selectedPage); - - const dropAreaRect = document - .getElementById('drop-area') - .getBoundingClientRect(); - const x = offset.x - dropAreaRect.left; - const y = offset.y - dropAreaRect.top; - - // Find component data - const componentData = projectComponents.pages - .flatMap((page) => page.components) - .find((comp) => comp.name === componentName); - - if (componentData) { - const newComponent = { - ...componentData, - id: uuidv4(), // Assign a unique ID - position: { x, y }, - dimensions: { width: 500, height: 300 }, - page: selectedPage, - }; - - console.log('New component added:', newComponent); - - setDroppedComponents((prev) => [...prev, newComponent]); - } else { - console.error('Component data not found for:', componentName); - } + const dropAreaRect = document + .getElementById('drop-area') + .getBoundingClientRect(); + const x = offset.x - dropAreaRect.left; + const y = offset.y - dropAreaRect.top; + + // Find component data + const componentData = projectComponents.pages + .flatMap((page) => page.components) + .find((comp) => comp.name === componentName); + + if (componentData) { + const newInstance = { + ...componentData, + id: uuidv4(), + position: { x, y }, + dimensions: { width: 500, height: 300 }, + page: selectedPage, + instanceCode: componentData.code, // Track instance-specific code + }; + + // Update dropped components + setDroppedComponents((prev) => [...prev, newInstance]); + + // Update project components with instance data + setProjectComponents((prev) => ({ + ...prev, + pages: prev.pages.map((page) => + page.name === selectedPage + ? { + ...page, + instances: [...page.instances, newInstance], + } + : page + ), + })); + } }, [selectedPage, projectComponents] - ); - - + ); + + useEffect(() => { const generateComponents = async () => { try { - if (projectData.pages && projectData.pages.length > 0) { - if (selectedPage === '' || selectedPage === null) { - setSelectedPage(projectData.pages[0].name); - } - - const uniqueComponents = new Set(); - projectData.pages.forEach(page => { - page.components.forEach(component => { - uniqueComponents.add(component); - }); + setLoading(true); + // First, handle page selection + if (selectedPage === '' || selectedPage === null) { + setSelectedPage(projectData.pages[0].name); + } + + // Generate components first + const uniqueComponents = new Set(); + projectData.pages.forEach(page => { + page.components.forEach(component => { + uniqueComponents.add(component); }); - - const componentMap = {}; - const componentPromises = Array.from(uniqueComponents).map(async (component) => { + }); + + const componentMap = {}; + const componentResults = await Promise.allSettled( + Array.from(uniqueComponents).map(async (component) => { updateComponentStatus(component, 'generating'); - const generatedComponent = await generateComponent(component, JSON.stringify(projectData.styling)); - componentMap[component] = generatedComponent; - }); - - await Promise.all(componentPromises); - - const generatedPages = projectData.pages.map(page => { - const generatedComponents = page.components.map(component => componentMap[component]); - return { ...page, components: generatedComponents }; - }); - - setProjectComponents({ + try { + const generatedComponent = await generateComponent( + component, + "", // Remove page purpose + JSON.stringify(projectData.styling) + ); + componentMap[component] = generatedComponent; + updateComponentStatus(component, 'success'); + } catch (error) { + console.error(`Failed to generate component ${component}:`, error); + updateComponentStatus(component, 'failed'); + } + }) + ); + + // Only proceed if at least some components generated successfully + if (Object.keys(componentMap).length > 0) { + setProjectComponents(prevState => ({ ...projectData, - pages: generatedPages, - }); - } - - if (projectData.apis && projectData.apis.length > 0) { - const generatedApis = await Promise.all( - projectData.apis.map(async (api, index) => { - updateApiStatus(api.name, 'generating'); - const prompt = constructApiPrompt(api, projectData.databaseSchema); - const apiCode = await retryApiGeneration(prompt, api.name); - return { ...api, code: apiCode }; - }) - ); - setServerlessApis(generatedApis); - } - - if (projectData.databaseSchema) { - setSqlStatus('generating'); - const generatedSQL = await generateSQLDatabaseQuery(JSON.stringify(projectData.databaseSchema)); - setSqlCode(generatedSQL); - setSqlStatus('success'); + pages: projectData.pages.map(page => ({ + ...page, + components: page.components.map(component => componentMap[component] || null) + })) + })); + + // Then handle APIs + if (projectData.apis?.length > 0) { + const apiResults = await Promise.allSettled( + projectData.apis.map(async (api) => { + updateApiStatus(api.name, 'generating'); + const prompt = constructApiPrompt(api, projectData.databaseSchema); + return retryApiGeneration(prompt, api.name); + }) + ); + + const successfulApis = apiResults + .map((result, index) => ({ + ...projectData.apis[index], + code: result.status === 'fulfilled' ? result.value : null + })) + .filter(api => api.code !== null); + + setServerlessApis(successfulApis); + } + + // Finally, handle SQL + if (projectData.databaseSchema) { + setSqlStatus('generating'); + const generatedSQL = await generateSQLDatabaseQuery( + JSON.stringify(projectData.databaseSchema) + ); + setSqlCode(generatedSQL); + setSqlStatus('success'); + } + } else { + throw new Error('No components were generated successfully'); } } catch (error) { - console.error('Error generating components:', error); - setSqlStatus('failed'); + console.error('Error in component generation:', error); + // Don't set loading to false here - let the finally block handle it } finally { setLoading(false); } }; - - generateComponents(); - // eslint-disable-next-line react-hooks/exhaustive-deps + + let mounted = true; + + const initializeGeneration = async () => { + try { + if (mounted) { + await generateComponents(); + } + } catch (error) { + console.error('Fatal error during generation:', error); + if (mounted) { + setLoading(false); + } + } + }; + + initializeGeneration(); + + return () => { + mounted = false; + }; }, []); - + const updateComponentStatus = (componentName, status) => { - setComponentStatus(prevStatus => ({ - ...prevStatus, - [componentName]: status - })); + setComponentStatus(prevStatus => { + const newStatus = { + ...prevStatus, + [componentName]: status + }; + + // Check if all components are either success or failed + const isComplete = Object.values(newStatus).every( + status => status === 'success' || status === 'failed' + ); + + if (isComplete && status === 'failed') { + setLoading(false); + } + + return newStatus; + }); }; const updateApiStatus = (apiName, status) => { @@ -241,36 +331,47 @@ export default function ProjectDashboard({ projectData }) { throw new Error('Failed to generate API after multiple attempts'); }; - const generateComponent = async (componentName, pagePurpose, stylingGuide) => { + const generateComponent = async (componentName, stylingGuide) => { let retryCount = 0; const maxRetries = 3; while (retryCount < maxRetries) { try { - const prompt = `Generate a React component named ${componentName} for a page with the following purpose: ${pagePurpose}. Follow this styling guide: ${stylingGuide}`; + // Simplified prompt for more reliable generation + const prompt = `Generate a React component named ${componentName}. Follow this styling guide: ${stylingGuide}. The component should be a functional component that uses styled-components.`; const result = await generateDynamicallyRenderingReactComponent(prompt); const code = typeof result === 'string' ? result : result?.code; - if (typeof code !== 'string' || !code.trim()) { - throw new Error(`Invalid code generated for component ${componentName}`); + if (!code || typeof code !== 'string') { + throw new Error('Invalid code generated'); } - new Function('React', 'styled', `return ${code}`) - - updateComponentStatus(componentName, 'success'); + // Validate the code structure + if (!code.includes('export default') || !code.includes('return')) { + throw new Error('Generated code missing required React component structure'); + } - return { name: componentName, code }; + // Test component compilation + try { + new Function('React', 'styled', `${code}`); + updateComponentStatus(componentName, 'success'); + return { name: componentName, code }; + } catch (compileError) { + throw new Error(`Component compilation failed: ${compileError.message}`); + } } catch (error) { - console.error(`Error generating component '${componentName}' on attempt ${retryCount + 1}:`, error); - retryCount += 1; + console.error(`Generation attempt ${retryCount + 1} failed for ${componentName}:`, error); + retryCount++; updateComponentStatus(componentName, `retrying (${retryCount})`); } } updateComponentStatus(componentName, 'failed'); - - return { name: componentName, code: `() =>