Note it!
+
import
import
Vrchsav-intoh Hi,
“**Hi,
Kafka
Full
So,
#INTRODUCTION
PRESENTATION
PPT
4 how
JS #
#Image
%------------------------ %
Honestly,
feedback1
I
Hello
>
amazon
INTERNSHIP During
Foodiego -->This
CARTOPIA Sir,
#CARTOPIA
mohit Tell
Rajat
To
class
-
1.
Machine
mohits
"use
https://github.com/techshade/terabox-player/blob/main/index.html https://github.com/MediaRealms-ORG/terabox-downloader-api/blob/main/api/index.py https://rapidapi.com/sampatsharma865/api/terabox-downloader-direct-download-link-generator/playground/apiendpoint_6c902c6c-b516-4905-9e93-4c905e454e02
I've
<h2
#
use
Example
https://play.typeracer.com?rt=1vs9hgtjqn def
Dear
RANDOM
PORTFOLIOS
ACOWALE
1.
README
F
ML
https://igetintopc.com/microsoft-office-2019-professional-plus-may-2020-free-download/ https://oneview.aktu.ac.in/webpages/aktu/oneview.aspx https://www.geeksforgeeks.org/unthinkable-solutions-interview-experience-on-campus-2022/
 
import
 https://skribbl.io/?kOC634m2
//
FEDERATED
 https://github.com/jgudo/ecommerce-react https://salinaka-ecommerce.web.app/
class
https://skribbl.io/?GwCFpJkR
#
CV
###
class
###
Write
Preview
import React, { useState, useRef, useCallback, useEffect } from 'react'; const EnhancedCourseBuilder = () => { const [components, setComponents] = useState([ { id: 1, type: 'Header', fields: { 'Background image': '', 'Button name': '', 'Logo': '', 'Subtitle': '', 'Title': '' }, children: [] } ]); const [draggedItem, setDraggedItem] = useState(null); const [dragOverTarget, setDragOverTarget] = useState(null); const [dragOverPosition, setDragOverPosition] = useState(null); const [dragOverField, setDragOverField] = useState(null); const [editingField, setEditingField] = useState(null); // {componentId, fieldName} const [editingFieldName, setEditingFieldName] = useState(null); // {componentId, fieldName} const [tempFieldName, setTempFieldName] = useState(''); const dragCounter = useRef(0); // Track which inputs should be shown as bullet display (Enter pressed) const [displayKeys, setDisplayKeys] = useState(new Set()); const isDisplayKey = (key) => displayKeys.has(key); const enableDisplay = (key) => setDisplayKeys(prev => new Set(prev).add(key)); const disableDisplay = (key) => setDisplayKeys(prev => { const n = new Set(prev); n.delete(key); return n; }); // Keep inputs focused while typing (prevents focus loss on re-render) const [activeInputKey, setActiveInputKey] = useState(null); const inputRefs = useRef({}); useEffect(() => { if (!activeInputKey) return; const el = inputRefs.current[activeInputKey]; if (el) { // Re-focus and place caret at end el.focus(); const len = el.value?.length || 0; try { el.setSelectionRange(len, len); } catch (_) {} } }, [components, activeInputKey]); // Helper: show input by default; when Enter pressed -> bullet. Double-click bullet -> input again const renderInputOrBullet = ({ keyId, value, onChange, placeholder, inputClassName = 'w-full p-2 border border-gray-300 rounded text-sm', bulletClassName = 'text-sm cursor-pointer' }) => { if (isDisplayKey(keyId)) { return ( <div className={bulletClassName} onDoubleClick={() => { disableDisplay(keyId); setActiveInputKey(keyId); }} title="Double-click to edit" > <span className="mr-2">•</span> {value || <span className="text-gray-400 italic">{placeholder}</span>} </div> ); } return ( <input type="text" value={value} onChange={(e) => { onChange(e); setActiveInputKey(keyId); }} onFocus={() => setActiveInputKey(keyId)} onKeyDown={(e) => { if (e.key === 'Enter') { e.preventDefault(); enableDisplay(keyId); if (activeInputKey === keyId) setActiveInputKey(null); } }} ref={(el) => { if (el) inputRefs.current[keyId] = el; }} className={inputClassName} placeholder={placeholder} /> ); }; const availableComponents = [ { type: 'Header', fields: { 'Background image': '', 'Button name': '', 'Logo': '', 'Subtitle': '', 'Title': '' } }, { type: 'Course components', fields: { 'Sections': [], 'Title': '' } }, { type: 'Course features', fields: { 'Items': [], 'Title': '' } }, { type: 'Course content', fields: { 'Items': [], 'Title': '' } }, { type: 'How it works', fields: { 'Footer': '', 'Footer image': '', 'Point image': '', 'Sections': [{ Title: '', Items: [] }], 'Title': '' } }, { type: 'Doctors', fields: { 'Items': [], 'Title': '' } }, { type: 'Form', fields: { 'Cta': '', 'Sections': [{ Title: '', Name: '', Type: '', 'Error Message': '', Required: false }], 'Submit subtitle': '', 'Submit title': '', 'Title': '', 'Type': '' } }, { type: 'Faqs', fields: { 'Sections': [{ Title: '', Items: [] }], 'Subtitle': '', 'Title': '' } }, { type: 'Footer', fields: { 'Title': '' } }, // Individual Field Components { type: 'Background image', fields: { 'Background image': '' } }, { type: 'Button name', fields: { 'Button name': '' } }, { type: 'Logo', fields: { 'Logo': '' } }, { type: 'Title', fields: { 'Title': '' } }, { type: 'Subtitle', fields: { 'Subtitle': '' } }, { type: 'Text', fields: { 'Text': '' } }, { type: 'Image', fields: { 'Image URL': '', 'Alt text': '' } }, { type: 'Button', fields: { 'Button text': '', 'Button link': '' } }, { type: 'Dropdown', fields: { 'Label': '', 'Options': [] }, isDropdown: true }, { type: 'Items', fields: { 'Items': [] }, isItemsList: true }, { type: 'Sections', fields: { 'Sections': [] }, isSectionsList: true } ]; const generateId = () => Math.random().toString(36).substr(2, 9); const handleDragStart = (e, item, isFromLibrary = false) => { setDraggedItem({ ...item, isFromLibrary }); e.dataTransfer.effectAllowed = 'move'; }; const handleDragOver = (e) => { e.preventDefault(); e.dataTransfer.dropEffect = 'move'; }; const handleDragEnter = (e, targetId, position = 'inside', fieldName = null, fieldIndex = null) => { e.preventDefault(); e.stopPropagation(); dragCounter.current++; setDragOverTarget(targetId); setDragOverPosition(position); setDragOverField(fieldName); }; const handleDragLeave = (e) => { e.stopPropagation(); dragCounter.current--; if (dragCounter.current === 0) { setDragOverTarget(null); setDragOverPosition(null); setDragOverField(null); } }; const handleDrop = (e, targetId = null, position = 'after', fieldName = null, fieldIndex = null) => { e.preventDefault(); e.stopPropagation(); dragCounter.current = 0; setDragOverTarget(null); setDragOverPosition(null); setDragOverField(null); if (!draggedItem) return; if (draggedItem.isFromLibrary) { const newComponent = { id: generateId(), type: draggedItem.type, fields: JSON.parse(JSON.stringify(draggedItem.fields)), children: [], isDropdown: draggedItem.isDropdown, isItemsList: draggedItem.isItemsList, isSectionsList: draggedItem.isSectionsList }; if (fieldName && targetId) { if (position === 'field-before' || position === 'field-after') { setComponents(prev => insertFieldAtPosition(prev, targetId, fieldName, newComponent, position, fieldIndex)); } else if (fieldName === 'Items' || fieldName === 'Options' || fieldName === 'Sections') { setComponents(prev => addItemToComponent(prev, targetId, fieldName, '')); } } else if (targetId && position === 'inside') { setComponents(prev => addChildToComponent(prev, targetId, newComponent)); } else if (targetId && (position === 'before' || position === 'after')) { setComponents(prev => insertComponentAtPosition(prev, targetId, newComponent, position)); } else { setComponents(prev => [...prev, newComponent]); } } else { setComponents(prev => moveComponent(prev, draggedItem.id, targetId, position)); } setDraggedItem(null); }; const insertFieldAtPosition = (components, componentId, afterFieldName, newComponent, position, fieldIndex) => { return components.map(comp => { if (comp.id === componentId) { const fieldEntries = Object.entries(comp.fields); const targetIndex = fieldEntries.findIndex(([key]) => key === afterFieldName); if (targetIndex !== -1) { const insertIndex = position === 'field-before' ? targetIndex : targetIndex + 1; const newFields = { ...comp.fields }; // Add the new field at the specified position const newFieldEntries = [...fieldEntries]; const [newFieldKey] = Object.keys(newComponent.fields); newFieldEntries.splice(insertIndex, 0, [newFieldKey, newComponent.fields[newFieldKey]]); // Reconstruct the fields object in the new order const orderedFields = {}; newFieldEntries.forEach(([key, value]) => { orderedFields[key] = value; }); return { ...comp, fields: orderedFields }; } } if (comp.children?.length > 0) { return { ...comp, children: insertFieldAtPosition(comp.children, componentId, afterFieldName, newComponent, position, fieldIndex) }; } return comp; }); }; const insertComponentAtPosition = (components, targetId, newComponent, position) => { const insertAtIndex = (arr, targetId, newComp, pos) => { const index = arr.findIndex(comp => comp.id === targetId); if (index === -1) return arr; const newArr = [...arr]; if (pos === 'before') { newArr.splice(index, 0, newComp); } else { newArr.splice(index + 1, 0, newComp); } return newArr; }; const rootIndex = components.findIndex(comp => comp.id === targetId); if (rootIndex !== -1) { return insertAtIndex(components, targetId, newComponent, position); } return components.map(comp => { if (comp.children?.length > 0) { const childIndex = comp.children.findIndex(child => child.id === targetId); if (childIndex !== -1) { return { ...comp, children: insertAtIndex(comp.children, targetId, newComponent, position) }; } return { ...comp, children: insertComponentAtPosition(comp.children, targetId, newComponent, position) }; } return comp; }); }; const moveComponent = (components, draggedId, targetId, position) => { // Find and remove the dragged component let draggedComponent = null; const removeFromTree = (comps) => { return comps.filter(comp => { if (comp.id === draggedId) { draggedComponent = comp; return false; } if (comp.children?.length > 0) { comp.children = removeFromTree(comp.children); } return true; }).map(comp => { if (comp.children?.length > 0) { comp.children = removeFromTree(comp.children); } return comp; }); }; let newComponents = removeFromTree([...components]); if (!draggedComponent) return components; // Insert the component at the new position if (targetId && (position === 'before' || position === 'after')) { return insertComponentAtPosition(newComponents, targetId, draggedComponent, position); } else if (targetId && position === 'inside') { return addChildToComponent(newComponents, targetId, draggedComponent); } else { // Add to the end if no specific target return [...newComponents, draggedComponent]; } }; const addChildToComponent = (components, parentId, newChild) => { return components.map(comp => { if (comp.id === parentId) { return { ...comp, children: [...(comp.children || []), newChild] }; } if (comp.children?.length > 0) { return { ...comp, children: addChildToComponent(comp.children, parentId, newChild) }; } return comp; }); }; const updateFieldValue = useCallback((componentId, fieldName, value, isChild = false, parentId = null) => { setComponents(prev => updateComponentField(prev, componentId, fieldName, value, isChild, parentId)); }, []); const updateComponentField = (components, componentId, fieldName, value, isChild, parentId) => { return components.map(comp => { if (!isChild && comp.id === componentId) { return { ...comp, fields: { ...comp.fields, [fieldName]: value } }; } if (comp.children?.length > 0) { return { ...comp, children: updateComponentField(comp.children, componentId, fieldName, value, isChild, parentId) }; } return comp; }); }; const removeComponent = (componentId, isChild = false, parentId = null) => { setComponents(prev => removeComponentFromTree(prev, componentId, isChild, parentId)); }; const removeComponentFromTree = (components, componentId, isChild, parentId) => { if (!isChild) { return components.filter(comp => comp.id !== componentId); } return components.map(comp => { if (comp.id === parentId && comp.children) { return { ...comp, children: comp.children.filter(child => child.id !== componentId) }; } if (comp.children?.length > 0) { return { ...comp, children: removeComponentFromTree(comp.children, componentId, isChild, parentId) }; } return comp; }); }; // Add function to remove individual fields const removeField = (componentId, fieldName) => { setComponents(prev => removeFieldFromComponent(prev, componentId, fieldName)); }; const removeFieldFromComponent = (components, componentId, fieldName) => { return components.map(comp => { if (comp.id === componentId) { const newFields = { ...comp.fields }; delete newFields[fieldName]; return { ...comp, fields: newFields }; } if (comp.children?.length > 0) { return { ...comp, children: removeFieldFromComponent(comp.children, componentId, fieldName) }; } return comp; }); }; // Functions for editing field values const startEditingField = (componentId, fieldName) => { setEditingField({ componentId, fieldName }); }; const stopEditingField = () => { setEditingField(null); }; const handleFieldKeyPress = (e, componentId, fieldName, value) => { if (e.key === 'Enter') { updateFieldValue(componentId, fieldName, value); stopEditingField(); } }; // Functions for editing field names const startEditingFieldName = (componentId, fieldName) => { setEditingFieldName({ componentId, fieldName }); setTempFieldName(fieldName); }; const stopEditingFieldName = () => { setEditingFieldName(null); setTempFieldName(''); }; const handleFieldNameKeyPress = (e, componentId, oldFieldName) => { if (e.key === 'Enter') { updateFieldName(componentId, oldFieldName, tempFieldName); stopEditingFieldName(); } }; const updateFieldName = (componentId, oldFieldName, newFieldName) => { if (oldFieldName === newFieldName || !newFieldName.trim()) return; setComponents(prev => updateFieldNameInComponent(prev, componentId, oldFieldName, newFieldName)); }; const updateFieldNameInComponent = (components, componentId, oldFieldName, newFieldName) => { return components.map(comp => { if (comp.id === componentId) { const newFields = {}; Object.entries(comp.fields).forEach(([key, value]) => { if (key === oldFieldName) { newFields[newFieldName] = value; } else { newFields[key] = value; } }); return { ...comp, fields: newFields }; } if (comp.children?.length > 0) { return { ...comp, children: updateFieldNameInComponent(comp.children, componentId, oldFieldName, newFieldName) }; } return comp; }); }; const addNestedComponent = (parentId, componentType) => { const componentTemplate = availableComponents.find(c => c.type === componentType); const newComponent = { id: generateId(), type: componentType, fields: JSON.parse(JSON.stringify(componentTemplate?.fields || {})), children: [], isDropdown: componentTemplate?.isDropdown, isItemsList: componentTemplate?.isItemsList, isSectionsList: componentTemplate?.isSectionsList }; setComponents(prev => addChildToComponent(prev, parentId, newComponent)); }; const addArrayItem = (componentId, fieldName) => { setComponents(prev => addItemToComponent(prev, componentId, fieldName, '')); }; const addItemToComponent = (components, componentId, fieldName, newItem) => { return components.map(comp => { if (comp.id === componentId) { const currentItems = Array.isArray(comp.fields[fieldName]) ? comp.fields[fieldName] : []; const valueToInsert = (fieldName === 'Sections' && ['Course components', 'How it works', 'Faqs'].includes(comp.type)) ? { Title: '', Items: [] } : (fieldName === 'Items' && comp.type === 'Course content') ? { Title: '' } : (fieldName === 'Items' && comp.type === 'Doctors') ? { 'Upload image': '', 'Image URL': '', 'Name': '', 'Specialty': [] } : newItem; return { ...comp, fields: { ...comp.fields, [fieldName]: [...currentItems, valueToInsert] } }; } if (comp.children?.length > 0) { return { ...comp, children: addItemToComponent(comp.children, componentId, fieldName, newItem) }; } return comp; }); }; const updateArrayItem = (componentId, fieldName, index, value) => { setComponents(prev => updateArrayItemInComponent(prev, componentId, fieldName, index, value)); }; const updateArrayItemInComponent = (components, componentId, fieldName, index, value) => { return components.map(comp => { if (comp.id === componentId) { const currentItems = [...comp.fields[fieldName]]; currentItems[index] = value; return { ...comp, fields: { ...comp.fields, [fieldName]: currentItems } }; } if (comp.children?.length > 0) { return { ...comp, children: updateArrayItemInComponent(comp.children, componentId, fieldName, index, value) }; } return comp; }); }; const removeArrayItem = (componentId, fieldName, index) => { setComponents(prev => removeArrayItemFromComponent(prev, componentId, fieldName, index)); }; const removeArrayItemFromComponent = (components, componentId, fieldName, index) => { return components.map(comp => { if (comp.id === componentId) { const currentItems = [...comp.fields[fieldName]]; currentItems.splice(index, 1); return { ...comp, fields: { ...comp.fields, [fieldName]: currentItems } }; } if (comp.children?.length > 0) { return { ...comp, children: removeArrayItemFromComponent(comp.children, componentId, fieldName, index) }; } return comp; }); }; // Helpers for nested Sections in "Course components" const addCourseSection = (componentId) => { setComponents(prev => addCourseSectionInTree(prev, componentId)); }; const addCourseSectionInTree = (components, componentId) => { return components.map(comp => { if (comp.id === componentId) { const sections = Array.isArray(comp.fields['Sections']) ? comp.fields['Sections'] : []; return { ...comp, fields: { ...comp.fields, 'Sections': [...sections, { Title: '', Items: [] }] } }; } if (comp.children?.length > 0) { return { ...comp, children: addCourseSectionInTree(comp.children, componentId) }; } return comp; }); }; const removeCourseSection = (componentId, sectionIndex) => { setComponents(prev => removeCourseSectionInTree(prev, componentId, sectionIndex)); }; const removeCourseSectionInTree = (components, componentId, sectionIndex) => { return components.map(comp => { if (comp.id === componentId) { const sections = Array.isArray(comp.fields['Sections']) ? [...comp.fields['Sections']] : []; sections.splice(sectionIndex, 1); return { ...comp, fields: { ...comp.fields, 'Sections': sections } }; } if (comp.children?.length > 0) { return { ...comp, children: removeCourseSectionInTree(comp.children, componentId, sectionIndex) }; } return comp; }); }; const updateCourseSectionTitle = (componentId, sectionIndex, value) => { setComponents(prev => updateCourseSectionTitleInTree(prev, componentId, sectionIndex, value)); }; const updateCourseSectionTitleInTree = (components, componentId, sectionIndex, value) => { return components.map(comp => { if (comp.id === componentId) { const sections = Array.isArray(comp.fields['Sections']) ? [...comp.fields['Sections']] : []; const section = { ...(sections[sectionIndex] || { Title: '', Items: [] }) }; section.Title = value; sections[sectionIndex] = section; return { ...comp, fields: { ...comp.fields, 'Sections': sections } }; } if (comp.children?.length > 0) { return { ...comp, children: updateCourseSectionTitleInTree(comp.children, componentId, sectionIndex, value) }; } return comp; }); }; const addCourseSectionItem = (componentId, sectionIndex) => { setComponents(prev => addCourseSectionItemInTree(prev, componentId, sectionIndex)); }; const addCourseSectionItemInTree = (components, componentId, sectionIndex) => { return components.map(comp => { if (comp.id === componentId) { const sections = Array.isArray(comp.fields['Sections']) ? [...comp.fields['Sections']] : []; const section = { ...(sections[sectionIndex] || { Title: '', Items: [] }) }; const items = Array.isArray(section.Items) ? [...section.Items] : []; items.push(''); section.Items = items; sections[sectionIndex] = section; return { ...comp, fields: { ...comp.fields, 'Sections': sections } }; } if (comp.children?.length > 0) { return { ...comp, children: addCourseSectionItemInTree(comp.children, componentId, sectionIndex) }; } return comp; }); }; const updateCourseSectionItem = (componentId, sectionIndex, itemIndex, value) => { setComponents(prev => updateCourseSectionItemInTree(prev, componentId, sectionIndex, itemIndex, value)); }; const updateCourseSectionItemInTree = (components, componentId, sectionIndex, itemIndex, value) => { return components.map(comp => { if (comp.id === componentId) { const sections = Array.isArray(comp.fields['Sections']) ? [...comp.fields['Sections']] : []; const section = { ...(sections[sectionIndex] || { Title: '', Items: [] }) }; const items = Array.isArray(section.Items) ? [...section.Items] : []; items[itemIndex] = value; section.Items = items; sections[sectionIndex] = section; return { ...comp, fields: { ...comp.fields, 'Sections': sections } }; } if (comp.children?.length > 0) { return { ...comp, children: updateCourseSectionItemInTree(comp.children, componentId, sectionIndex, itemIndex, value) }; } return comp; }); }; const removeCourseSectionItem = (componentId, sectionIndex, itemIndex) => { setComponents(prev => removeCourseSectionItemInTree(prev, componentId, sectionIndex, itemIndex)); }; const removeCourseSectionItemInTree = (components, componentId, sectionIndex, itemIndex) => { return components.map(comp => { if (comp.id === componentId) { const sections = Array.isArray(comp.fields['Sections']) ? [...comp.fields['Sections']] : []; const section = { ...(sections[sectionIndex] || { Title: '', Items: [] }) }; const items = Array.isArray(section.Items) ? [...section.Items] : []; items.splice(itemIndex, 1); section.Items = items; sections[sectionIndex] = section; return { ...comp, fields: { ...comp.fields, 'Sections': sections } }; } if (comp.children?.length > 0) { return { ...comp, children: removeCourseSectionItemInTree(comp.children, componentId, sectionIndex, itemIndex) }; } return comp; }); }; const handleFileUpload = (componentId, fieldName, file) => { if (file) { const reader = new FileReader(); reader.onload = (e) => { updateFieldValue(componentId, fieldName, e.target.result); }; reader.readAsDataURL(file); } }; // Course features: nested items with Upload image, Point, Image URL const addCourseFeatureItem = (componentId) => { setComponents(prev => addCourseFeatureItemInTree(prev, componentId)); }; const addCourseFeatureItemInTree = (components, componentId) => { return components.map(comp => { if (comp.id === componentId) { const items = Array.isArray(comp.fields['Items']) ? [...comp.fields['Items']] : []; items.push({ 'Upload image': '', 'Point': '', 'Image URL': '' }); return { ...comp, fields: { ...comp.fields, 'Items': items } }; } if (comp.children?.length > 0) { return { ...comp, children: addCourseFeatureItemInTree(comp.children, componentId) }; } return comp; }); }; const updateCourseFeatureItemField = (componentId, index, key, value) => { setComponents(prev => updateCourseFeatureItemFieldInTree(prev, componentId, index, key, value)); }; const updateCourseFeatureItemFieldInTree = (components, componentId, index, key, value) => { return components.map(comp => { if (comp.id === componentId) { const items = Array.isArray(comp.fields['Items']) ? [...comp.fields['Items']] : []; const current = { ...(items[index] || { 'Upload image': '', 'Point': '', 'Image URL': '' }) }; current[key] = value; items[index] = current; return { ...comp, fields: { ...comp.fields, 'Items': items } }; } if (comp.children?.length > 0) { return { ...comp, children: updateCourseFeatureItemFieldInTree(comp.children, componentId, index, key, value) }; } return comp; }); }; const removeCourseFeatureItem = (componentId, index) => { setComponents(prev => removeCourseFeatureItemInTree(prev, componentId, index)); }; const removeCourseFeatureItemInTree = (components, componentId, index) => { return components.map(comp => { if (comp.id === componentId) { const items = Array.isArray(comp.fields['Items']) ? [...comp.fields['Items']] : []; items.splice(index, 1); return { ...comp, fields: { ...comp.fields, 'Items': items } }; } if (comp.children?.length > 0) { return { ...comp, children: removeCourseFeatureItemInTree(comp.children, componentId, index) }; } return comp; }); }; const handleNestedFileUpload = (componentId, index, key, file) => { if (!file) return; const reader = new FileReader(); reader.onload = (e) => { updateCourseFeatureItemField(componentId, index, key, e.target.result); }; reader.readAsDataURL(file); }; // Doctors: nested items with Upload image, Image URL, and repeatable Specialty const addDoctorItem = (componentId) => { setComponents(prev => addDoctorItemInTree(prev, componentId)); }; const addDoctorItemInTree = (components, componentId) => { return components.map(comp => { if (comp.id === componentId) { const items = Array.isArray(comp.fields['Items']) ? [...comp.fields['Items']] : []; items.push({ 'Upload image': '', 'Image URL': '', 'Name': '', 'Specialty': [] }); return { ...comp, fields: { ...comp.fields, 'Items': items } }; } if (comp.children?.length > 0) { return { ...comp, children: addDoctorItemInTree(comp.children, componentId) }; } return comp; }); }; const updateDoctorItemField = (componentId, index, key, value) => { setComponents(prev => updateDoctorItemFieldInTree(prev, componentId, index, key, value)); }; const updateDoctorItemFieldInTree = (components, componentId, index, key, value) => { return components.map(comp => { if (comp.id === componentId) { const items = Array.isArray(comp.fields['Items']) ? [...comp.fields['Items']] : []; const current = { ...(items[index] || { 'Upload image': '', 'Image URL': '', 'Name': '', 'Specialty': [] }) }; current[key] = value; items[index] = current; return { ...comp, fields: { ...comp.fields, 'Items': items } }; } if (comp.children?.length > 0) { return { ...comp, children: updateDoctorItemFieldInTree(comp.children, componentId, index, key, value) }; } return comp; }); }; const addDoctorSpecialty = (componentId, index) => { setComponents(prev => addDoctorSpecialtyInTree(prev, componentId, index)); }; const addDoctorSpecialtyInTree = (components, componentId, index) => { return components.map(comp => { if (comp.id === componentId) { const items = Array.isArray(comp.fields['Items']) ? [...comp.fields['Items']] : []; const current = { ...(items[index] || { 'Upload image': '', 'Image URL': '', 'Name': '', 'Specialty': [] }) }; const specs = Array.isArray(current['Specialty']) ? [...current['Specialty']] : []; specs.push(''); current['Specialty'] = specs; items[index] = current; return { ...comp, fields: { ...comp.fields, 'Items': items } }; } if (comp.children?.length > 0) { return { ...comp, children: addDoctorSpecialtyInTree(comp.children, componentId, index) }; } return comp; }); }; const updateDoctorSpecialty = (componentId, index, specIndex, value) => { setComponents(prev => updateDoctorSpecialtyInTree(prev, componentId, index, specIndex, value)); }; const updateDoctorSpecialtyInTree = (components, componentId, index, specIndex, value) => { return components.map(comp => { if (comp.id === componentId) { const items = Array.isArray(comp.fields['Items']) ? [...comp.fields['Items']] : []; const current = { ...(items[index] || { 'Upload image': '', 'Image URL': '', 'Name': '', 'Specialty': [] }) }; const specs = Array.isArray(current['Specialty']) ? [...current['Specialty']] : []; specs[specIndex] = value; current['Specialty'] = specs; items[index] = current; return { ...comp, fields: { ...comp.fields, 'Items': items } }; } if (comp.children?.length > 0) { return { ...comp, children: updateDoctorSpecialtyInTree(comp.children, componentId, index, specIndex, value) }; } return comp; }); }; const removeDoctorSpecialty = (componentId, index, specIndex) => { setComponents(prev => removeDoctorSpecialtyInTree(prev, componentId, index, specIndex)); }; const removeDoctorSpecialtyInTree = (components, componentId, index, specIndex) => { return components.map(comp => { if (comp.id === componentId) { const items = Array.isArray(comp.fields['Items']) ? [...comp.fields['Items']] : []; const current = { ...(items[index] || { 'Upload image': '', 'Image URL': '', 'Specialty': [] }) }; const specs = Array.isArray(current['Specialty']) ? [...current['Specialty']] : []; specs.splice(specIndex, 1); current['Specialty'] = specs; items[index] = current; return { ...comp, fields: { ...comp.fields, 'Items': items } }; } if (comp.children?.length > 0) { return { ...comp, children: removeDoctorSpecialtyInTree(comp.children, componentId, index, specIndex) }; } return comp; }); }; const removeDoctorItem = (componentId, index) => { setComponents(prev => removeDoctorItemInTree(prev, componentId, index)); }; const removeDoctorItemInTree = (components, componentId, index) => { return components.map(comp => { if (comp.id === componentId) { const items = Array.isArray(comp.fields['Items']) ? [...comp.fields['Items']] : []; items.splice(index, 1); return { ...comp, fields: { ...comp.fields, 'Items': items } }; } if (comp.children?.length > 0) { return { ...comp, children: removeDoctorItemInTree(comp.children, componentId, index) }; } return comp; }); }; const handleDoctorFileUpload = (componentId, index, file) => { if (!file) return; const reader = new FileReader(); reader.onload = (e) => { updateDoctorItemField(componentId, index, 'Upload image', e.target.result); }; reader.readAsDataURL(file); }; // Course content: Items are objects with a single Title field const addCourseContentItem = (componentId) => { setComponents(prev => addCourseContentItemInTree(prev, componentId)); }; const addCourseContentItemInTree = (components, componentId) => { return components.map(comp => { if (comp.id === componentId) { const items = Array.isArray(comp.fields['Items']) ? [...comp.fields['Items']] : []; items.push({ Title: '' }); return { ...comp, fields: { ...comp.fields, 'Items': items } }; } if (comp.children?.length > 0) { return { ...comp, children: addCourseContentItemInTree(comp.children, componentId) }; } return comp; }); }; const updateCourseContentItemTitle = (componentId, index, value) => { setComponents(prev => updateCourseContentItemTitleInTree(prev, componentId, index, value)); }; const updateCourseContentItemTitleInTree = (components, componentId, index, value) => { return components.map(comp => { if (comp.id === componentId) { const items = Array.isArray(comp.fields['Items']) ? [...comp.fields['Items']] : []; const current = { ...(items[index] || { Title: '' }) }; current.Title = value; items[index] = current; return { ...comp, fields: { ...comp.fields, 'Items': items } }; } if (comp.children?.length > 0) { return { ...comp, children: updateCourseContentItemTitleInTree(comp.children, componentId, index, value) }; } return comp; }); }; const removeCourseContentItem = (componentId, index) => { setComponents(prev => removeCourseContentItemInTree(prev, componentId, index)); }; const removeCourseContentItemInTree = (components, componentId, index) => { return components.map(comp => { if (comp.id === componentId) { const items = Array.isArray(comp.fields['Items']) ? [...comp.fields['Items']] : []; items.splice(index, 1); return { ...comp, fields: { ...comp.fields, 'Items': items } }; } if (comp.children?.length > 0) { return { ...comp, children: removeCourseContentItemInTree(comp.children, componentId, index) }; } return comp; }); }; const ComponentRenderer = ({ component, isChild = false, parentId = null, level = 0 }) => { const bgColor = level === 0 ? 'bg-white' : level === 1 ? 'bg-gray-50' : 'bg-gray-100'; const borderColor = dragOverTarget === component.id && dragOverPosition === 'inside' ? 'border-[#2d6c90] bg-[#3c8dbc]' : 'border-gray-200'; return ( <div className="relative"> {/* Drop Zone - Before Component */} {draggedItem && ( <div className={`h-2 -mb-1 transition-all duration-200 ${ dragOverTarget === component.id && dragOverPosition === 'before' ? 'bg-[#3c8dbc] border-2 border-[#3c8dbc] border-dashed rounded' : 'bg-transparent' }`} onDragOver={handleDragOver} onDragEnter={(e) => handleDragEnter(e, component.id, 'before')} onDragLeave={handleDragLeave} onDrop={(e) => handleDrop(e, component.id, 'before')} /> )} <div className={`border-2 ${borderColor} ${bgColor} mb-4 transition-all duration-200`} draggable onDragStart={(e) => handleDragStart(e, component)} onDragOver={handleDragOver} onDragEnter={(e) => handleDragEnter(e, component.id, 'inside')} onDragLeave={handleDragLeave} onDrop={(e) => handleDrop(e, component.id, 'inside')} > {/* Component Header */} <div className={`${level === 0 ? 'bg-[#3c8dbc]' : 'bg-gray-400'} text-white p-3 flex justify-between items-center`}> <span className="font-medium">{component.type}</span> <div className="flex gap-2"> <button onClick={() => removeComponent(component.id, isChild, parentId)} className="text-white hover:text-red-200 text-sm" > ✕ </button> </div> </div> {/* Component Fields */} <div className="p-4"> {Object.entries(component.fields).map(([fieldName, fieldValue], fieldIndex) => ( <div key={`${component.id}-${fieldName}`}> {/* Drop Zone - Before Field */} {draggedItem && ( <div className={`h-1 mb-2 transition-all duration-200 ${ dragOverTarget === component.id && dragOverPosition === 'field-before' && dragOverField === fieldName ? 'bg-[#3c8dbc] border border-[#3c8dbc] border-dashed rounded' : 'bg-transparent' }`} onDragOver={handleDragOver} onDragEnter={(e) => handleDragEnter(e, component.id, 'field-before', fieldName, fieldIndex)} onDragLeave={handleDragLeave} onDrop={(e) => handleDrop(e, component.id, 'field-before', fieldName, fieldIndex)} /> )} <div className="mb-4"> <div className="flex items-center justify-between mb-2"> {editingFieldName?.componentId === component.id && editingFieldName?.fieldName === fieldName ? ( <input type="text" value={tempFieldName} onChange={(e) => setTempFieldName(e.target.value)} onKeyPress={(e) => handleFieldNameKeyPress(e, component.id, fieldName)} onBlur={() => stopEditingFieldName()} className="text-sm font-medium text-gray-700 bg-yellow-100 border border-yellow-300 rounded px-2 py-1" autoFocus /> ) : ( <label className="block text-sm font-medium text-gray-700 cursor-pointer" onDoubleClick={() => startEditingFieldName(component.id, fieldName)} title="Double-click to edit field name" > {fieldName} </label> )} {/* Add remove button for individual fields (only for dragged fields, not default component fields) */} {!['Header', 'Course components', 'Course features', 'Course content', 'How it works', 'Doctors', 'Form', 'Faqs', 'Footer'].includes(component.type) && ( <button onClick={() => removeField(component.id, fieldName)} className="text-red-500 hover:text-red-700 text-xs ml-2" title="Remove field" > ✕ </button> )} </div> {/* Image Upload Fields */} {(fieldName.includes('image') || fieldName === 'Logo') ? ( <div className="space-y-2"> {fieldValue && ( <div className="relative"> <img src={fieldValue} alt={`${fieldName} preview`} className="w-32 h-20 object-cover border border-gray-300 rounded" /> <button onClick={() => updateFieldValue(component.id, fieldName, '')} className="absolute -top-2 -right-2 bg-red-500 text-white rounded-full w-5 h-5 flex items-center justify-center text-xs hover:bg-red-600" > ✕ </button> </div> )} <div className="flex items-center gap-2"> <label className="flex items-center gap-2 px-4 py-2 bg-[#3c8dbc] text-white rounded cursor-pointer transition-colors"> <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12" /> </svg> Upload {fieldName} <input type="file" accept="image/*" onChange={(e) => { const file = e.target.files?.[0]; if (file) { handleFileUpload(component.id, fieldName, file); } }} className="hidden" /> </label> {fieldValue && ( <span className="text-sm">✓ Image uploaded</span> )} </div> </div> ) : Array.isArray(fieldValue) ? ( /* Array fields (Items, Options, Sections) */ <div className="space-y-2"> {fieldName === 'Sections' && ['Course components', 'How it works', 'Faqs'].includes(component.type) ? ( <div className="space-y-4"> {fieldValue.map((section, sIdx) => ( <div key={`${component.id}-section-${sIdx}`} className="border rounded p-3 bg-gray-50"> <div className="flex items-center justify-between mb-2"> <label className="text-sm font-medium text-gray-700">Title</label> <button onClick={() => removeCourseSection(component.id, sIdx)} className="text-red-500 hover:text-red-700 text-xs" > ✕ </button> </div> {renderInputOrBullet({ keyId: `${component.id}-section-${sIdx}-title`, value: section?.Title || '', onChange: (e) => updateCourseSectionTitle(component.id, sIdx, e.target.value), inputClassName: 'w-full p-2 border border-gray-300 rounded text-sm mb-3', placeholder: 'Please enter Section title (if applicable)' })} <div className="space-y-2"> <div className="text-sm font-medium text-gray-700">Items</div> {(section?.Items || []).map((it, iIdx) => ( <div key={`${component.id}-section-${sIdx}-item-${iIdx}`} className="flex items-center gap-2"> {renderInputOrBullet({ keyId: `${component.id}-section-${sIdx}-item-${iIdx}`, value: it, onChange: (e) => updateCourseSectionItem(component.id, sIdx, iIdx, e.target.value), inputClassName: 'flex-1 p-2 border border-gray-300 rounded text-sm', bulletClassName: 'flex-1 text-sm cursor-pointer', placeholder: `Item ${iIdx + 1}` })} <button onClick={() => removeCourseSectionItem(component.id, sIdx, iIdx)} className="px-2 py-1 bg-red-500 text-white text-xs rounded hover:bg-red-600" > ✕ </button> </div> ))} <button onClick={() => addCourseSectionItem(component.id, sIdx)} className="w-80px px-3 py-2 bg-[#3c8dbc] text-white text-sm rounded transition-colors" > + Add Item </button> </div> </div> ))} <button onClick={() => addCourseSection(component.id)} className="w-80px px-3 py-2 bg-[#3c8dbc] text-white text-sm rounded transition-colors" > + Add Section </button> </div> ) : fieldName === 'Items' && component.type === 'Course features' ? ( <div className="space-y-4"> {fieldValue.map((feature, fIdx) => ( <div key={`${component.id}-feature-${fIdx}`} className="border rounded p-3 bg-gray-50"> <div className="flex items-start gap-4"> <div className="flex flex-col items-center"> {feature['Upload image'] && ( <img src={feature['Upload image']} alt="feature" className="w-24 h-24 rounded-full object-cover border border-gray-300 mb-2" /> )} <label className="flex items-center gap-2 px-3 py-2 bg-[#3c8dbc] text-white rounded cursor-pointer text-xs"> <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12" /> </svg> Upload image <input type="file" accept="image/*" onChange={(e) => { const file = e.target.files?.[0]; if (file) handleNestedFileUpload(component.id, fIdx, 'Upload image', file); }} className="hidden" /> </label> </div> <div className="flex-1 space-y-3"> <div> <label className="block text-xs text-gray-600 mb-1">Point</label> {renderInputOrBullet({ keyId: `${component.id}-feature-${fIdx}-point`, value: feature['Point'] || '', onChange: (e) => updateCourseFeatureItemField(component.id, fIdx, 'Point', e.target.value), inputClassName: 'w-full p-2 border border-gray-300 rounded text-sm', placeholder: 'Enter point' })} </div> <div> <label className="block text-xs text-gray-600 mb-1">Image URL</label> {renderInputOrBullet({ keyId: `${component.id}-feature-${fIdx}-imgurl`, value: feature['Image URL'] || '', onChange: (e) => updateCourseFeatureItemField(component.id, fIdx, 'Image URL', e.target.value), inputClassName: 'w-full p-2 border border-gray-300 rounded text-sm', placeholder: 'https://...' })} </div> </div> <button onClick={() => removeCourseFeatureItem(component.id, fIdx)} className="px-2 py-1 bg-red-500 text-white text-xs rounded h-7 mt-1 hover:bg-red-600" > ✕ </button> </div> </div> ))} <button onClick={() => addCourseFeatureItem(component.id)} className="w-80px px-3 py-2 bg-[#3c8dbc] text-white text-sm rounded transition-colors" > + Add Item </button> </div> ) : fieldName === 'Items' && component.type === 'Doctors' ? ( <div className="space-y-4"> {fieldValue.map((doc, dIdx) => ( <div key={`${component.id}-doctor-${dIdx}`} className="border rounded p-3 bg-gray-50"> <div className="flex items-start gap-4"> <div className="flex flex-col items-center"> {doc['Upload image'] && ( <img src={doc['Upload image']} alt="doctor" className="w-24 h-24 rounded-full object-cover border border-gray-300 mb-2" /> )} <label className="flex items-center gap-2 px-3 py-2 bg-[#3c8dbc] text-white rounded cursor-pointer text-xs"> <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12" /> </svg> Upload image <input type="file" accept="image/*" onChange={(e) => { const file = e.target.files?.[0]; if (file) handleDoctorFileUpload(component.id, dIdx, file); }} className="hidden" /> </label> </div> <div className="flex-1 space-y-3"> <div> <label className="block text-xs text-gray-600 mb-1">Name</label> {renderInputOrBullet({ keyId: `${component.id}-doctor-${dIdx}-name`, value: doc['Name'] || '', onChange: (e) => updateDoctorItemField(component.id, dIdx, 'Name', e.target.value), inputClassName: 'w-full p-2 border border-gray-300 rounded text-sm', placeholder: 'Doctor name' })} </div> <div> <label className="block text-xs text-gray-600 mb-1">Image URL</label> {renderInputOrBullet({ keyId: `${component.id}-doctor-${dIdx}-imgurl`, value: doc['Image URL'] || '', onChange: (e) => updateDoctorItemField(component.id, dIdx, 'Image URL', e.target.value), inputClassName: 'w-full p-2 border border-gray-300 rounded text-sm', placeholder: 'https://...' })} </div> <div> <label className="block text-xs text-gray-600 mb-1">Specialty</label> <div className="space-y-2"> {(doc['Specialty'] || []).map((spec, sIdx) => ( <div key={`${component.id}-doctor-${dIdx}-spec-${sIdx}`} className="flex items-center gap-2"> {renderInputOrBullet({ keyId: `${component.id}-doctor-${dIdx}-spec-${sIdx}`, value: spec, onChange: (e) => updateDoctorSpecialty(component.id, dIdx, sIdx, e.target.value), inputClassName: 'flex-1 p-2 border border-gray-300 rounded text-sm', bulletClassName: 'flex-1 text-sm cursor-pointer', placeholder: `Specialty ${sIdx + 1}` })} <button onClick={() => removeDoctorSpecialty(component.id, dIdx, sIdx)} className="px-2 py-1 bg-red-500 text-white text-xs rounded hover:bg-red-600" > ✕ </button> </div> ))} <button onClick={() => addDoctorSpecialty(component.id, dIdx)} className="w-80px px-3 py-2 bg-[#3c8dbc] text-white text-sm rounded transition-colors" > + Add Specialty </button> </div> </div> </div> <button onClick={() => removeDoctorItem(component.id, dIdx)} className="px-2 py-1 bg-red-500 text-white text-xs rounded h-7 mt-1 hover:bg-red-600" > ✕ </button> </div> </div> ))} <button onClick={() => addDoctorItem(component.id)} className="w-80px px-3 py-2 bg-[#3c8dbc] text-white text-sm rounded transition-colors" > + Add Item </button> </div> ) : fieldName === 'Items' && component.type === 'Course content' ? ( <div className="space-y-4"> {fieldValue.map((item, idx) => ( <div key={`${component.id}-content-item-${idx}`} className="border rounded p-3 bg-gray-50"> <div className="flex items-center justify-between mb-2"> <label className="text-sm font-medium text-gray-700">Title</label> <button onClick={() => removeCourseContentItem(component.id, idx)} className="text-red-500 hover:text-red-700 text-xs" > ✕ </button> </div> {renderInputOrBullet({ keyId: `${component.id}-content-${idx}-title`, value: item?.Title || '', onChange: (e) => updateCourseContentItemTitle(component.id, idx, e.target.value), inputClassName: 'w-full p-2 border border-gray-300 rounded text-sm', placeholder: 'Please enter title' })} </div> ))} <button onClick={() => addCourseContentItem(component.id)} className="w-80px px-3 py-2 bg-[#3c8dbc] text-white text-sm rounded transition-colors" > + Add Item </button> </div> ) : ( <> {draggedItem && (['Items', 'Options', 'Sections'].includes(fieldName)) && ( <div className={`p-3 border-2 border-dashed rounded transition-all duration-200 ${ dragOverTarget === component.id && dragOverField === fieldName ? 'border-[#3c8dbc] bg-[#3c8dbc]' : 'border-gray-300' }`} onDragOver={handleDragOver} onDragEnter={(e) => handleDragEnter(e, component.id, 'inside', fieldName)} onDragLeave={handleDragLeave} onDrop={(e) => handleDrop(e, component.id, 'inside', fieldName)} > <div className="text-center text-gray-500 text-sm"> 📎 Drop here to add to {fieldName} </div> </div> )} {fieldValue.map((item, index) => ( <div key={`${component.id}-${fieldName}-${index}`} className="flex items-center gap-2"> {renderInputOrBullet({ keyId: `${component.id}-${fieldName}-${index}`, value: item, onChange: (e) => updateArrayItem(component.id, fieldName, index, e.target.value), inputClassName: 'flex-1 p-2 border border-gray-300 rounded text-sm', bulletClassName: 'flex-1 text-sm cursor-pointer', placeholder: `${fieldName.slice(0, -1)} ${index + 1}` })} <button onClick={() => removeArrayItem(component.id, fieldName, index)} className="px-2 py-1 bg-red-500 text-white text-xs rounded hover:bg-red-600" > ✕ </button> </div> ))} <button onClick={() => addArrayItem(component.id, fieldName)} className="w-full px-3 py-2 bg-[#3c8dbc] text-white text-sm rounded transition-colors" > + Add {fieldName === 'Items' ? 'Item' : fieldName === 'Sections' ? 'Section' : 'Option'} </button> </> )} </div> ) : ( renderInputOrBullet({ keyId: `${component.id}-${fieldName}`, value: fieldValue, onChange: (e) => updateFieldValue(component.id, fieldName, e.target.value, isChild, parentId), inputClassName: 'w-full p-2 border border-gray-300 rounded text-sm', bulletClassName: 'text-sm cursor-pointer', placeholder: `Enter ${fieldName.toLowerCase()}` }) )} </div> {/* Drop Zone - After Field */} {draggedItem && ( <div className={`h-1 -mt-2 mb-2 transition-all duration-200 ${ dragOverTarget === component.id && dragOverPosition === 'field-after' && dragOverField === fieldName ? 'bg-[#3c8dbc] border border-[#3c8dbc] border-dashed rounded' : 'bg-transparent' }`} onDragOver={handleDragOver} onDragEnter={(e) => handleDragEnter(e, component.id, 'field-after', fieldName, fieldIndex)} onDragLeave={handleDragLeave} onDrop={(e) => handleDrop(e, component.id, 'field-after', fieldName, fieldIndex)} /> )} </div> ))} {/* Nested Components */} {component.children && component.children.length > 0 && ( <div className="mt-4 pl-4 border-l-2 border-gray-300"> {component.children.map((child) => ( <ComponentRenderer key={child.id} component={child} isChild={true} parentId={component.id} level={level + 1} /> ))} </div> )} </div> </div> {/* Drop Zone - After Component */} {draggedItem && ( <div className={`h-2 -mt-5 mb-1 transition-all duration-200 ${ dragOverTarget === component.id && dragOverPosition === 'after' ? 'bg-[#3c8dbc] border-2 border-[#3c8dbc] border-dashed rounded' : 'bg-transparent' }`} onDragOver={handleDragOver} onDragEnter={(e) => handleDragEnter(e, component.id, 'after')} onDragLeave={handleDragLeave} onDrop={(e) => handleDrop(e, component.id, 'after')} /> )} </div> ); }; return ( <div className="min-h-screen bg-gray-100"> <div className="flex h-screen"> {/* Component Library */} <div className="w-80 bg-white border-r border-gray-200 p-4 overflow-y-auto"> <h2 className="text-lg font-semibold mb-4 text-gray-800">Components</h2> {/* Full Components Section */} <div className="mb-6"> <h3 className="text-sm font-semibold text-gray-600 mb-2 uppercase tracking-wide">Full Components</h3> <div className="space-y-2"> {availableComponents.filter(comp => !['Background image', 'Button name', 'Logo', 'Title', 'Subtitle', 'Text', 'Image', 'Button', 'Dropdown', 'Items', 'Sections'].includes(comp.type)).map((component, index) => ( <div key={`full-comp-${index}`} draggable onDragStart={(e) => handleDragStart(e, component, true)} className="p-3 border border-gray-200 rounded cursor-grab hover:bg-[#3c8dbc] transition-all active:cursor-grabbing" > <div className="font-medium text-gray-800">{component.type}</div> <div className="text-xs text-gray-500 mt-1"> {Object.keys(component.fields).length} fields </div> </div> ))} </div> </div> {/* Individual Fields Section */} <div> <h3 className="text-sm font-semibold text-gray-600 mb-2 uppercase tracking-wide">Individual Fields</h3> <div className="space-y-2"> {availableComponents.filter(comp => ['Background image', 'Button name', 'Logo', 'Title', 'Subtitle', 'Text', 'Image', 'Button', 'Dropdown', 'Items', 'Sections'].includes(comp.type)).map((component, index) => ( <div key={`field-comp-${index}`} draggable onDragStart={(e) => handleDragStart(e, component, true)} className="p-2 border border-gray-200 rounded cursor-grab hover:border-[#3c8dbc] hover:bg-[#3c8dbc] hover:text-white transition-all active:cursor-grabbing bg-gray-50" > <div className="font-medium text-gray-800 text-sm">{component.type}</div> {component.isDropdown && ( <div className="text-xs mt-1">With options</div> )} {component.isItemsList && ( <div className="text-xs mt-1">Items list</div> )} {component.isSectionsList && ( <div className="text-xs mt-1">Sections list</div> )} </div> ))} </div> </div> </div> {/* Main Canvas */} <div className="flex-1 p-6 overflow-y-auto"> <div className="max-w-4xl mx-auto"> <div className="flex justify-between items-center mb-6"> <h1 className="text-2xl font-bold text-gray-800">Course Builder</h1> <div className="flex gap-2"> <button className="px-4 py-2 bg-[#3c8dbc] text-white rounded"> Save </button> <button className="px-4 py-2 bg-gray-500 text-white rounded hover:bg-gray-600"> Preview </button> </div> </div> {/* Drop Zone */} <div className={`min-h-96 p-4 border-2 border-dashed border-gray-300 rounded-lg ${ dragOverTarget === 'canvas' ? 'border-[#2d6c90] bg-[#3c8dbc]' : '' }`} onDragOver={handleDragOver} onDragEnter={(e) => handleDragEnter(e, 'canvas', 'inside')} onDragLeave={handleDragLeave} onDrop={(e) => handleDrop(e)} > {components.length === 0 ? ( <div className="text-center text-gray-500 mt-20"> <div className="text-4xl mb-4">📦</div> <h3 className="text-lg font-medium mb-2">Start Building Your Course</h3> <p>Drag components from the library to begin</p> </div> ) : ( components.map((component) => ( <ComponentRenderer key={component.id} component={component} /> )) )} </div> </div> </div> </div> </div> ); }; export default EnhancedCourseBuilder;