CustomComponents hataları

This commit is contained in:
Sedat ÖZTÜRK 2025-08-11 13:48:36 +03:00
parent 8cc8ed07f9
commit 64ccc150df
8 changed files with 1000 additions and 921 deletions

View file

@ -18,7 +18,7 @@ services:
- ASPNETCORE_ENVIRONMENT=Dev
- SEED=${SEED}
networks:
- kurs-platform-data_db
- db
# Backend API
api:
@ -33,7 +33,7 @@ services:
- cdn:/etc/api/cdn
- api-keys:/root/.aspnet/DataProtection-Keys
networks:
- kurs-platform-data_db
- db
- default
# Frontend (UI)

View file

@ -3,7 +3,8 @@ name: kurs-platform-data
networks:
db:
external: false
external: true
name: kurs-platform-data_db
volumes:
pg:

View file

@ -8,16 +8,21 @@ export interface ComponentPreviewProps {
}
const ComponentPreview: React.FC<ComponentPreviewProps> = ({ componentName, className = '' }) => {
const { components } = useComponents()
const { components, loading } = useComponents()
if (!componentName) {
return <div className="text-sm text-gray-500">Bileşen ismi yok.</div>
}
// components dizisinin varlığını kontrol et
if (loading || !components || !Array.isArray(components)) {
return <div className="text-sm text-gray-500">Bileşenler yükleniyor...</div>
}
// Belirtilen bileşeni bul
const component = components.find((c) => c.name === componentName && c.isActive)
let dependencies: string[] = [];
let dependencies: string[] = []
if (component?.dependencies) {
try {

File diff suppressed because it is too large Load diff

View file

@ -59,10 +59,10 @@ const ComponentEditor: React.FC = () => {
}, [])
useEffect(() => {
if (code && editorState.components.length === 0) {
if (code && editorState.components?.length === 0) {
parseAndUpdateComponents(code)
}
}, [code, editorState.components.length])
}, [code, editorState.components?.length])
// Load existing component data - sadece edit modunda
useEffect(() => {
@ -144,7 +144,9 @@ const ComponentEditor: React.FC = () => {
<div className="h-screen flex items-center justify-center">
<div className="text-center">
<RefreshCw className="w-8 h-8 text-blue-500 animate-spin mx-auto mb-3" />
<p className="text-slate-600">{translate('::App.DeveloperKit.ComponentEditor.Loading')}</p>
<p className="text-slate-600">
{translate('::App.DeveloperKit.ComponentEditor.Loading')}
</p>
</div>
</div>
)
@ -220,7 +222,9 @@ const ComponentEditor: React.FC = () => {
className="flex items-center gap-2 bg-yellow-600 text-white px-4 py-2 rounded-lg hover:bg-yellow-700 transition-colors disabled:opacity-50 disabled:cursor-not-allowed shadow-sm"
>
<Save className="w-4 h-4" />
{isSaving ? translate('::App.DeveloperKit.ComponentEditor.Saving') : translate('::App.DeveloperKit.ComponentEditor.Save')}
{isSaving
? translate('::App.DeveloperKit.ComponentEditor.Saving')
: translate('::App.DeveloperKit.ComponentEditor.Save')}
</button>
</div>
</div>
@ -237,19 +241,26 @@ const ComponentEditor: React.FC = () => {
</div>
<div className="flex-1">
<p className="text-sm text-red-800 font-medium mb-2">
{validationErrors.length} {translate('::App.DeveloperKit.ComponentEditor.ValidationError.Title')}
{validationErrors.length !== 1 ? 's' : ''} {translate('::App.DeveloperKit.ComponentEditor.ValidationError.Found')}
{validationErrors.length}{' '}
{translate('::App.DeveloperKit.ComponentEditor.ValidationError.Title')}
{validationErrors.length !== 1 ? 's' : ''}{' '}
{translate('::App.DeveloperKit.ComponentEditor.ValidationError.Found')}
</p>
<div className="space-y-1">
{validationErrors.slice(0, 5).map((error, index) => (
<div key={index} className="text-sm text-red-700">
<span className="font-medium">{translate('::App.DeveloperKit.ComponentEditor.ValidationError.Line')} {error.startLineNumber}:</span>{' '}
<span className="font-medium">
{translate('::App.DeveloperKit.ComponentEditor.ValidationError.Line')}{' '}
{error.startLineNumber}:
</span>{' '}
{error.message}
</div>
))}
{validationErrors.length > 5 && (
<div className="text-sm text-red-600 italic">
... {translate('::App.DeveloperKit.ComponentEditor.ValidationError.And')} {validationErrors.length - 5} {translate('::App.DeveloperKit.ComponentEditor.ValidationError.More')}
... {translate('::App.DeveloperKit.ComponentEditor.ValidationError.And')}{' '}
{validationErrors.length - 5}{' '}
{translate('::App.DeveloperKit.ComponentEditor.ValidationError.More')}
{validationErrors.length - 5 !== 1 ? 's' : ''}
</div>
)}

View file

@ -1,6 +1,6 @@
import React, { useState } from "react";
import { Link } from "react-router-dom";
import { useComponents } from "../../contexts/ComponentContext";
import React, { useState } from 'react'
import { Link } from 'react-router-dom'
import { useComponents } from '../../contexts/ComponentContext'
import {
Plus,
Search,
@ -14,21 +14,19 @@ import {
CheckCircle,
XCircle,
View,
} from "lucide-react";
import { ROUTES_ENUM } from "@/routes/route.constant";
import { useLocalization } from "@/utils/hooks/useLocalization";
} from 'lucide-react'
import { ROUTES_ENUM } from '@/routes/route.constant'
import { useLocalization } from '@/utils/hooks/useLocalization'
const ComponentManager: React.FC = () => {
const { components, updateComponent, deleteComponent } = useComponents();
const [searchTerm, setSearchTerm] = useState("");
const [filterActive, setFilterActive] = useState<
"all" | "active" | "inactive"
>("all");
const { components, loading, updateComponent, deleteComponent } = useComponents()
const [searchTerm, setSearchTerm] = useState('')
const [filterActive, setFilterActive] = useState<'all' | 'active' | 'inactive'>('all')
// Calculate statistics
const totalComponents = components?.length;
const activeComponents = components?.filter((c) => c.isActive).length;
const inactiveComponents = totalComponents - activeComponents;
const totalComponents = components?.length || 0
const activeComponents = components?.filter((c) => c.isActive).length || 0
const inactiveComponents = totalComponents - activeComponents
const { translate } = useLocalization()
const stats = [
@ -36,62 +34,58 @@ const ComponentManager: React.FC = () => {
name: translate('::App.DeveloperKit.Component.Total'),
value: totalComponents,
icon: Puzzle,
color: "text-purple-600",
bgColor: "bg-purple-100",
color: 'text-purple-600',
bgColor: 'bg-purple-100',
},
{
name: translate('::App.DeveloperKit.Component.Active'),
value: activeComponents,
icon: CheckCircle,
color: "text-emerald-600",
bgColor: "bg-emerald-100",
color: 'text-emerald-600',
bgColor: 'bg-emerald-100',
},
{
name: translate('::App.DeveloperKit.Component.Inactive'),
value: inactiveComponents,
icon: XCircle,
color: "text-slate-600",
bgColor: "bg-slate-100",
color: 'text-slate-600',
bgColor: 'bg-slate-100',
},
];
]
const filteredComponents = components?.filter((component) => {
const matchesSearch =
component.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
(component.description || "")
.toLowerCase()
.includes(searchTerm.toLowerCase());
(component.description || '').toLowerCase().includes(searchTerm.toLowerCase())
const matchesFilter =
filterActive === "all" ||
(filterActive === "active" && component.isActive) ||
(filterActive === "inactive" && !component.isActive);
filterActive === 'all' ||
(filterActive === 'active' && component.isActive) ||
(filterActive === 'inactive' && !component.isActive)
return matchesSearch && matchesFilter;
});
return matchesSearch && matchesFilter
})
const handleToggleActive = async (id: string, isActive: boolean) => {
try {
const component = components.find((c) => c.id === id);
const component = components?.find((c) => c.id === id)
if (component) {
await updateComponent(id, { ...component, isActive });
await updateComponent(id, { ...component, isActive })
}
} catch (err) {
console.error("Failed to toggle component status:", err);
console.error('Failed to toggle component status:', err)
}
};
}
const handleDelete = async (id: string, name: string) => {
if (
window.confirm(translate('::App.DeveloperKit.Component.ConfirmDelete'))
) {
if (window.confirm(translate('::App.DeveloperKit.Component.ConfirmDelete'))) {
try {
await deleteComponent(id);
await deleteComponent(id)
} catch (err) {
console.error("Failed to delete component:", err);
console.error('Failed to delete component:', err)
}
}
};
}
return (
<div className="space-y-8">
@ -114,18 +108,11 @@ const ComponentManager: React.FC = () => {
{/* Statistics Cards */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 mb-6">
{stats.map((stat, index) => (
<div
key={index}
className="bg-white rounded-lg border border-slate-200 p-6"
>
<div key={index} className="bg-white rounded-lg border border-slate-200 p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-slate-600 mb-1">
{stat.name}
</p>
<p className="text-3xl font-bold text-slate-900">
{stat.value}
</p>
<p className="text-sm font-medium text-slate-600 mb-1">{stat.name}</p>
<p className="text-3xl font-bold text-slate-900">{stat.value}</p>
</div>
<div className={`p-3 rounded-lg ${stat.bgColor}`}>
<stat.icon className={`w-6 h-6 ${stat.color}`} />
@ -152,21 +139,27 @@ const ComponentManager: React.FC = () => {
<Filter className="w-5 h-5 text-slate-500" />
<select
value={filterActive}
onChange={(e) =>
setFilterActive(e.target.value as "all" | "active" | "inactive")
}
onChange={(e) => setFilterActive(e.target.value as 'all' | 'active' | 'inactive')}
className="px-3 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
>
<option value="all">{translate('::App.DeveloperKit.Component.Filter.All')}</option>
<option value="active">{translate('::App.DeveloperKit.Component.Filter.Active')}</option>
<option value="inactive">{translate('::App.DeveloperKit.Component.Filter.Inactive')}</option>
<option value="active">
{translate('::App.DeveloperKit.Component.Filter.Active')}
</option>
<option value="inactive">
{translate('::App.DeveloperKit.Component.Filter.Inactive')}
</option>
</select>
</div>
</div>
</div>
{/* Components List */}
{filteredComponents?.length > 0 ? (
{loading ? (
<div className="flex items-center justify-center py-12">
<div className="text-slate-600">Bileşenler yükleniyor...</div>
</div>
) : filteredComponents?.length > 0 ? (
<div className="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-6">
{filteredComponents.map((component) => (
<div
@ -177,43 +170,35 @@ const ComponentManager: React.FC = () => {
<div className="flex items-start justify-between mb-4">
<div className="flex-1">
<div className="flex items-center gap-2 mb-1">
<h3 className="text-lg font-semibold text-slate-900">
{component.name}
</h3>
<h3 className="text-lg font-semibold text-slate-900">{component.name}</h3>
<div
className={`w-2 h-2 rounded-full ${
component.isActive ? "bg-green-500" : "bg-slate-300"
component.isActive ? 'bg-green-500' : 'bg-slate-300'
}`}
/>
</div>
<p className="text-slate-600 text-sm mb-3">
{(() => {
try {
const parsed = JSON.parse(
component.dependencies ?? "[]"
);
const parsed = JSON.parse(component.dependencies ?? '[]')
return Array.isArray(parsed) && parsed.length > 0
? `${parsed.join(", ")}`
: translate('::App.DeveloperKit.Component.NoDependencies');
? `${parsed.join(', ')}`
: translate('::App.DeveloperKit.Component.NoDependencies')
} catch {
return translate('::App.DeveloperKit.Component.NoDependencies');
return translate('::App.DeveloperKit.Component.NoDependencies')
}
})()}
</p>
{component.description && (
<p className="text-slate-600 text-sm mb-3">
{component.description}
</p>
<p className="text-slate-600 text-sm mb-3">{component.description}</p>
)}
<div className="flex items-center gap-4 text-xs text-slate-500">
<div className="flex items-center gap-1">
<Calendar className="w-3 h-3" />
<span>
{component.lastModificationTime
? new Date(
component.lastModificationTime
).toLocaleDateString()
? new Date(component.lastModificationTime).toLocaleDateString()
: translate('::App.DeveloperKit.Component.DateNotAvailable')}
</span>
</div>
@ -237,13 +222,11 @@ const ComponentManager: React.FC = () => {
<div className="flex items-center justify-between pt-4 border-t border-slate-100">
<div className="flex items-center gap-2">
<button
onClick={() =>
handleToggleActive(component.id, !component.isActive)
}
onClick={() => handleToggleActive(component.id, !component.isActive)}
className={`flex items-center gap-1 px-2 py-1 rounded text-xs font-medium transition-colors ${
component.isActive
? "bg-green-100 text-green-700 hover:bg-green-200"
: "bg-slate-100 text-slate-600 hover:bg-slate-200"
? 'bg-green-100 text-green-700 hover:bg-green-200'
: 'bg-slate-100 text-slate-600 hover:bg-slate-200'
}`}
>
{component.isActive ? (
@ -261,7 +244,10 @@ const ComponentManager: React.FC = () => {
</div>
<div className="flex items-center gap-1">
<Link
to={ROUTES_ENUM.protected.saas.developerKitComponentsEdit.replace(':id', component.id)}
to={ROUTES_ENUM.protected.saas.developerKitComponentsEdit.replace(
':id',
component.id,
)}
target="_blank"
className="p-2 text-slate-600 hover:text-blue-600 hover:bg-blue-50 rounded transition-colors"
title={translate('::App.DeveloperKit.Component.Edit')}
@ -269,7 +255,10 @@ const ComponentManager: React.FC = () => {
<Edit className="w-4 h-4" />
</Link>
<Link
to={ROUTES_ENUM.protected.saas.developerKitComponentsView.replace(':id', component.id)}
to={ROUTES_ENUM.protected.saas.developerKitComponentsView.replace(
':id',
component.id,
)}
className="p-2 text-slate-600 hover:text-blue-600 hover:bg-blue-50 rounded transition-colors"
title={translate('::App.DeveloperKit.Component.View')}
>
@ -295,16 +284,16 @@ const ComponentManager: React.FC = () => {
<Plus className="w-8 h-8 text-slate-500" />
</div>
<h3 className="text-lg font-medium text-slate-900 mb-2">
{searchTerm || filterActive !== "all"
{searchTerm || filterActive !== 'all'
? translate('::App.DeveloperKit.Component.Empty.Filtered.Title')
: translate('::App.DeveloperKit.Component.Empty.Initial.Title')}
</h3>
<p className="text-slate-600 mb-6">
{searchTerm || filterActive !== "all"
{searchTerm || filterActive !== 'all'
? translate('::App.DeveloperKit.Component.Empty.Filtered.Description')
: translate('::App.DeveloperKit.Component.Empty.Initial.Description')}
</p>
{!searchTerm && filterActive === "all" && (
{!searchTerm && filterActive === 'all' && (
<Link
to={ROUTES_ENUM.protected.saas.developerKitComponentsNew}
className="inline-flex items-center gap-2 bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 transition-colors"
@ -317,7 +306,7 @@ const ComponentManager: React.FC = () => {
</div>
)}
</div>
);
};
)
}
export default ComponentManager;
export default ComponentManager

View file

@ -1,116 +1,123 @@
import { CreateUpdateCustomComponentDto, CustomComponent, CustomComponentDto } from '@/proxy/developerKit/models';
import { developerKitService } from '@/services/developerKit.service';
import { useStoreState } from '@/store/store';
import React, { createContext, useContext, useState, useEffect } from 'react';
import {
CreateUpdateCustomComponentDto,
CustomComponent,
CustomComponentDto,
} from '@/proxy/developerKit/models'
import { developerKitService } from '@/services/developerKit.service'
import { useStoreState } from '@/store/store'
import React, { createContext, useContext, useState, useEffect } from 'react'
interface ComponentContextType {
components: CustomComponent[];
loading: boolean;
error: string | null;
addComponent: (component: CreateUpdateCustomComponentDto) => Promise<void>;
updateComponent: (id: string, component: CreateUpdateCustomComponentDto) => Promise<void>;
deleteComponent: (id: string) => Promise<void>;
getComponent: (id: string) => CustomComponent | undefined;
getComponentByName: (name: string) => CustomComponent | undefined;
refreshComponents: () => Promise<void>;
registeredComponents: Record<string, React.ComponentType<unknown>>;
registerComponent: (name: string, component: React.ComponentType<unknown>) => void;
components: CustomComponent[]
loading: boolean
error: string | null
addComponent: (component: CreateUpdateCustomComponentDto) => Promise<void>
updateComponent: (id: string, component: CreateUpdateCustomComponentDto) => Promise<void>
deleteComponent: (id: string) => Promise<void>
getComponent: (id: string) => CustomComponent | undefined
getComponentByName: (name: string) => CustomComponent | undefined
refreshComponents: () => Promise<void>
registeredComponents: Record<string, React.ComponentType<unknown>>
registerComponent: (name: string, component: React.ComponentType<unknown>) => void
}
const ComponentContext = createContext<ComponentContextType | undefined>(undefined);
const ComponentContext = createContext<ComponentContextType | undefined>(undefined)
// eslint-disable-next-line react-refresh/only-export-components
export const useComponents = () => {
const context = useContext(ComponentContext);
const context = useContext(ComponentContext)
if (context === undefined) {
throw new Error('useComponents must be used within a ComponentProvider');
throw new Error('useComponents must be used within a ComponentProvider')
}
return context;
};
return context
}
export const ComponentProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const extraProperties = useStoreState((state) => state.abpConfig?.config?.extraProperties)
const [components, setComponents] = useState<CustomComponent[]>([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [registeredComponents, setRegisteredComponents] = useState<Record<string, React.ComponentType<unknown>>>({});
const [components, setComponents] = useState<CustomComponent[]>([])
const [loading, setLoading] = useState(false)
const [error, setError] = useState<string | null>(null)
const [registeredComponents, setRegisteredComponents] = useState<
Record<string, React.ComponentType<unknown>>
>({})
const refreshComponents = async () => {
try {
setLoading(true);
setError(null);
setLoading(true)
setError(null)
const customComponents = extraProperties?.customComponents as CustomComponentDto[]
setComponents(customComponents);
setComponents(customComponents || [])
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to fetch components');
console.error('Failed to fetch components:', err);
setError(err instanceof Error ? err.message : 'Failed to fetch components')
console.error('Failed to fetch components:', err)
setComponents([])
} finally {
setLoading(false);
setLoading(false)
}
};
}
useEffect(() => {
refreshComponents();
}, []);
refreshComponents()
}, [extraProperties])
const addComponent = async (componentData: CreateUpdateCustomComponentDto) => {
try {
setLoading(true);
setError(null);
const newComponent = await developerKitService.createCustomComponent(componentData);
setComponents(prev => [...prev, newComponent]);
setLoading(true)
setError(null)
const newComponent = await developerKitService.createCustomComponent(componentData)
setComponents((prev) => [...prev, newComponent])
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to create component');
throw err;
setError(err instanceof Error ? err.message : 'Failed to create component')
throw err
} finally {
setLoading(false);
setLoading(false)
}
};
}
const updateComponent = async (id: string, componentData: CreateUpdateCustomComponentDto) => {
try {
setLoading(true);
setError(null);
const updatedComponent = await developerKitService.updateCustomComponent(id, componentData);
setComponents(prev => prev.map(component =>
component.id === id ? updatedComponent : component
));
setLoading(true)
setError(null)
const updatedComponent = await developerKitService.updateCustomComponent(id, componentData)
setComponents((prev) =>
prev.map((component) => (component.id === id ? updatedComponent : component)),
)
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to update component');
throw err;
setError(err instanceof Error ? err.message : 'Failed to update component')
throw err
} finally {
setLoading(false);
setLoading(false)
}
};
}
const deleteComponent = async (id: string) => {
try {
setLoading(true);
setError(null);
await developerKitService.deleteCustomComponent(id);
setComponents(prev => prev.filter(component => component.id !== id));
setLoading(true)
setError(null)
await developerKitService.deleteCustomComponent(id)
setComponents((prev) => prev.filter((component) => component.id !== id))
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to delete component');
throw err;
setError(err instanceof Error ? err.message : 'Failed to delete component')
throw err
} finally {
setLoading(false);
setLoading(false)
}
};
}
const getComponent = (id: string) => {
return components.find(comp => comp.id === id);
};
return components?.find((comp) => comp.id === id)
}
const getComponentByName = (name: string) => {
return components.find(comp => comp.name === name);
};
return components?.find((comp) => comp.name === name)
}
const registerComponent = (name: string, component: React.ComponentType<unknown>) => {
setRegisteredComponents(prev => ({
setRegisteredComponents((prev) => ({
...prev,
[name]: component
}));
};
[name]: component,
}))
}
const value = {
components,
@ -123,8 +130,8 @@ export const ComponentProvider: React.FC<{ children: React.ReactNode }> = ({ chi
getComponentByName,
refreshComponents,
registeredComponents,
registerComponent
};
registerComponent,
}
return <ComponentContext.Provider value={value}>{children}</ComponentContext.Provider>;
};
return <ComponentContext.Provider value={value}>{children}</ComponentContext.Provider>
}

View file

@ -1,92 +1,140 @@
import React, { createContext, useCallback, useState, useEffect } from 'react';
import * as Babel from '@babel/standalone';
import { useComponents } from './ComponentContext';
import React, { createContext, useCallback, useState, useEffect } from 'react'
import * as Babel from '@babel/standalone'
import { useComponents } from './ComponentContext'
import {
Alert,
Avatar,
Badge,
Button,
Calendar,
Card,
Checkbox,
ConfigProvider,
DatePicker,
Dialog,
Drawer,
Dropdown,
FormItem,
FormContainer,
Input,
InputGroup,
Menu,
MenuItem,
Notification,
Pagination,
Progress,
Radio,
RangeCalendar,
ScrollBar,
Segment,
Select,
Skeleton,
Spinner,
Steps,
Switcher,
Table,
Tabs,
Tag,
TimeInput,
Timeline,
toast,
Tooltip,
Upload,
} from '../components/ui'
interface ComponentProps {
[key: string]: unknown;
[key: string]: unknown
}
interface ComponentRegistryContextType {
renderComponent: (name: string, props?: ComponentProps) => React.ReactNode;
compileAndRender: (code: string, props?: ComponentProps) => React.ReactNode;
isComponentRegistered: (name: string) => boolean;
getRegisteredComponents: () => string[];
getComponentCode: (name: string) => string | null;
renderComponent: (name: string, props?: ComponentProps) => React.ReactNode
compileAndRender: (code: string, props?: ComponentProps) => React.ReactNode
isComponentRegistered: (name: string) => boolean
getRegisteredComponents: () => string[]
getComponentCode: (name: string) => string | null
}
export const ComponentRegistryContext = createContext<ComponentRegistryContextType | undefined>(undefined);
export const ComponentRegistryContext = createContext<ComponentRegistryContextType | undefined>(
undefined,
)
const ComponentRegistryProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const { components } = useComponents();
const [compiledComponents, setCompiledComponents] = useState<Record<string, React.ComponentType<ComponentProps>>>({});
const { components } = useComponents()
const [compiledComponents, setCompiledComponents] = useState<
Record<string, React.ComponentType<ComponentProps>>
>({})
const extractComponentInfo = useCallback((code: string, defaultName = '') => {
try {
// FC Type declaration with explicit component name
const fcTypeMatch = code.match(/const\s+([A-Za-z]\w*)\s*:\s*React\.FC/);
if (fcTypeMatch) return fcTypeMatch[1];
const fcTypeMatch = code.match(/const\s+([A-Za-z]\w*)\s*:\s*React\.FC/)
if (fcTypeMatch) return fcTypeMatch[1]
// Function declaration: function MyComponent() {}
const functionMatch = code.match(/function\s+([A-Za-z]\w*)/);
if (functionMatch) return functionMatch[1];
const functionMatch = code.match(/function\s+([A-Za-z]\w*)/)
if (functionMatch) return functionMatch[1]
// Arrow function with explicit name: const MyComponent = () => {}
const arrowMatch = code.match(/const\s+([A-Za-z]\w*)\s*=/);
if (arrowMatch) return arrowMatch[1];
const arrowMatch = code.match(/const\s+([A-Za-z]\w*)\s*=/)
if (arrowMatch) return arrowMatch[1]
// Class declaration: class MyComponent extends React.Component {}
const classMatch = code.match(/class\s+([A-Za-z]\w*)/);
if (classMatch) return classMatch[1];
const classMatch = code.match(/class\s+([A-Za-z]\w*)/)
if (classMatch) return classMatch[1]
// Default export name
const exportMatch = code.match(/export\s+default\s+([A-Za-z]\w*)/);
if (exportMatch) return exportMatch[1];
const exportMatch = code.match(/export\s+default\s+([A-Za-z]\w*)/)
if (exportMatch) return exportMatch[1]
// Interface name which might indicate component name
const interfaceMatch = code.match(/interface\s+([A-Za-z]\w*)Props/);
if (interfaceMatch) return interfaceMatch[1];
const interfaceMatch = code.match(/interface\s+([A-Za-z]\w*)Props/)
if (interfaceMatch) return interfaceMatch[1]
// Look for TypeScript type definitions that might indicate a component name
const tsTypeMatch = code.match(/type\s+([A-Za-z]\w*)Props/);
if (tsTypeMatch) return tsTypeMatch[1];
const tsTypeMatch = code.match(/type\s+([A-Za-z]\w*)Props/)
if (tsTypeMatch) return tsTypeMatch[1]
// Try to find any capitalized identifier that might be a component
const capitalNameMatch = code.match(/\b([A-Z][A-Za-z0-9]*)\b/);
if (capitalNameMatch && capitalNameMatch[1] !== 'React' && capitalNameMatch[1] !== 'Component') {
return capitalNameMatch[1];
const capitalNameMatch = code.match(/\b([A-Z][A-Za-z0-9]*)\b/)
if (
capitalNameMatch &&
capitalNameMatch[1] !== 'React' &&
capitalNameMatch[1] !== 'Component'
) {
return capitalNameMatch[1]
}
// Use the default name provided (usually the component name from DB)
if (defaultName) {
return defaultName;
return defaultName
}
// Last resort - use "DynamicComponent" as it's descriptive and unlikely to conflict
return "DynamicComponent";
return 'DynamicComponent'
} catch (err) {
console.error("Error extracting component name:", err);
return defaultName || "DynamicComponent";
console.error('Error extracting component name:', err)
return defaultName || 'DynamicComponent'
}
}, []);
}, [])
// Compile all components when the component list changes
useEffect(() => {
if (!components || !components?.length) return;
if (!components || !components?.length) return
try {
// Create a bundle of all active components
const activeComponents = components?.filter(c => c.isActive);
const activeComponents = components?.filter((c) => c.isActive)
if (!activeComponents.length) {
setCompiledComponents({});
return;
setCompiledComponents({})
return
}
// First, extract all component names and create both lowercase and normal versions
const componentInfos = activeComponents.map(comp => {
const name = comp.name;
const componentInfos = activeComponents.map((comp) => {
const name = comp.name
// Create both original and capitalized versions for more reliable lookups
const nameCapitalized = name.charAt(0).toUpperCase() + name.slice(1);
const nameCapitalized = name.charAt(0).toUpperCase() + name.slice(1)
return {
name: name,
@ -95,82 +143,198 @@ const ComponentRegistryProvider: React.FC<{ children: React.ReactNode }> = ({ ch
code: comp.code
.replace(/import\s+.*?;/g, '')
.replace(/export\s+default\s+/, '')
.trim()
};
});
.trim(),
}
})
// Prepare the combined code in a way that avoids naming conflicts
const componentBundle = componentInfos.map(info =>
`// Component: ${info.name}\nconst ${info.name}_Component = (function() {\n${info.code}\nreturn ${info.internalName};\n})();`
).join('\n\n');
const componentBundle = componentInfos
.map(
(info) =>
`// Component: ${info.name}\nconst ${info.name}_Component = (function() {\n${info.code}\nreturn ${info.internalName};\n})();`,
)
.join('\n\n')
// Create a function that returns an object with all components
const bundledCode = `
(function(React) {
(function(React, Alert, Avatar, Badge, Button, Calendar, Card, Checkbox, ConfigProvider, DatePicker, Dialog, Drawer, Dropdown, FormItem, FormContainer, Input, InputGroup, Menu, MenuItem, Notification, Pagination, Progress, Radio, RangeCalendar, ScrollBar, Segment, Select, Skeleton, Spinner, Steps, Switcher, Table, Tabs, Tag, TimeInput, Timeline, toast, Tooltip, Upload) {
// Global components and hooks available to all custom components
const { useState, useEffect, useCallback, useMemo, useRef, createContext, useContext } = React;
// Basic HTML elements for fallback
const Div = (props) => React.createElement('div', props);
const Span = (props) => React.createElement('span', props);
const Textarea = (props) => React.createElement('textarea', props);
const Option = (props) => React.createElement('option', props);
const Form = (props) => React.createElement('form', props);
const Label = (props) => React.createElement('label', props);
const H1 = (props) => React.createElement('h1', props);
const H2 = (props) => React.createElement('h2', props);
const H3 = (props) => React.createElement('h3', props);
const H4 = (props) => React.createElement('h4', props);
const H5 = (props) => React.createElement('h5', props);
const H6 = (props) => React.createElement('h6', props);
const P = (props) => React.createElement('p', props);
const A = (props) => React.createElement('a', props);
const Img = (props) => React.createElement('img', props);
const Ul = (props) => React.createElement('ul', props);
const Li = (props) => React.createElement('li', props);
const Tr = (props) => React.createElement('tr', props);
const Td = (props) => React.createElement('td', props);
const Th = (props) => React.createElement('th', props);
const Thead = (props) => React.createElement('thead', props);
const Tbody = (props) => React.createElement('tbody', props);
const componentRegistry = {};
${componentBundle}
// Add all components to the registry with both original and capitalized names
${componentInfos.map(info => `
${componentInfos
.map(
(info) => `
// Register with original name
componentRegistry["${info.name}"] = ${info.name}_Component;
// Register with capitalized name for proper React convention
componentRegistry["${info.nameCapitalized}"] = ${info.name}_Component;
`).join('\n')}
`,
)
.join('\n')}
return componentRegistry;
})(React)
`;
})(React, Alert, Avatar, Badge, Button, Calendar, Card, Checkbox, ConfigProvider, DatePicker, Dialog, Drawer, Dropdown, FormItem, FormContainer, Input, InputGroup, Menu, MenuItem, Notification, Pagination, Progress, Radio, RangeCalendar, ScrollBar, Segment, Select, Skeleton, Spinner, Steps, Switcher, Table, Tabs, Tag, TimeInput, Timeline, toast, Tooltip, Upload)
`
// Compile the bundle
const compiledBundle = Babel.transform(bundledCode, {
presets: ['react', 'typescript'],
filename: 'components-bundle.tsx'
}).code;
filename: 'components-bundle.tsx',
}).code
if (!compiledBundle) {
throw new Error('Failed to compile components bundle');
throw new Error('Failed to compile components bundle')
}
// Evaluate the bundle to get all components
const componentsFactory = new Function('React', `return ${compiledBundle}`);
const compiledComponentsRegistry = componentsFactory(React);
const componentsFactory = new Function(
'React',
'Alert',
'Avatar',
'Badge',
'Button',
'Calendar',
'Card',
'Checkbox',
'ConfigProvider',
'DatePicker',
'Dialog',
'Drawer',
'Dropdown',
'FormItem',
'FormContainer',
'Input',
'InputGroup',
'Menu',
'MenuItem',
'Notification',
'Pagination',
'Progress',
'Radio',
'RangeCalendar',
'ScrollBar',
'Segment',
'Select',
'Skeleton',
'Spinner',
'Steps',
'Switcher',
'Table',
'Tabs',
'Tag',
'TimeInput',
'Timeline',
'toast',
'Tooltip',
'Upload',
`return ${compiledBundle}`,
)
const compiledComponentsRegistry = componentsFactory(
React,
Alert,
Avatar,
Badge,
Button,
Calendar,
Card,
Checkbox,
ConfigProvider,
DatePicker,
Dialog,
Drawer,
Dropdown,
FormItem,
FormContainer,
Input,
InputGroup,
Menu,
MenuItem,
Notification,
Pagination,
Progress,
Radio,
RangeCalendar,
ScrollBar,
Segment,
Select,
Skeleton,
Spinner,
Steps,
Switcher,
Table,
Tabs,
Tag,
TimeInput,
Timeline,
toast,
Tooltip,
Upload,
)
setCompiledComponents(compiledComponentsRegistry);
setCompiledComponents(compiledComponentsRegistry)
} catch (error) {
console.error('Error compiling components bundle:', error);
setCompiledComponents({});
console.error('Error compiling components bundle:', error)
setCompiledComponents({})
}
}, [components, extractComponentInfo]);
}, [components, extractComponentInfo])
const compileCode = useCallback((code: string) => {
try {
// Clean the code and extract component name
const cleanCode = code
.replace(/import\s+.*?;/g, '')
.replace(/export\s+default\s+/, '')
.trim();
const compileCode = useCallback(
(code: string) => {
try {
// Clean the code and extract component name
const cleanCode = code
.replace(/import\s+.*?;/g, '')
.replace(/export\s+default\s+/, '')
.trim()
// Try to extract a meaningful name from the component,
// but don't generate random names that could cause reference issues
const componentName = extractComponentInfo(code, 'AnonComponent');
// Try to extract a meaningful name from the component,
// but don't generate random names that could cause reference issues
const componentName = extractComponentInfo(code, 'AnonComponent')
// Extract all potential component references from JSX
// Look for JSX tags like <ComponentName ...> or <ComponentName/>
const jsxComponentRegex = /<([A-Z][A-Za-z0-9_]*)/g;
const jsxMatches = [...cleanCode.matchAll(jsxComponentRegex)].map(match => match[1]);
// Extract all potential component references from JSX
// Look for JSX tags like <ComponentName ...> or <ComponentName/>
const jsxComponentRegex = /<([A-Z][A-Za-z0-9_]*)/g
const jsxMatches = [...cleanCode.matchAll(jsxComponentRegex)].map((match) => match[1])
// Get unique component names from JSX
const jsxComponentNames = [...new Set(jsxMatches)];
// Get unique component names from JSX
const jsxComponentNames = [...new Set(jsxMatches)]
// Generate a warning for JSX tags that might be components
if (jsxComponentNames.length > 0) {
console.log("JSX tags that might be components:", jsxComponentNames.join(", "));
}
// Generate a warning for JSX tags that might be components
if (jsxComponentNames.length > 0) {
console.log('JSX tags that might be components:', jsxComponentNames.join(', '))
}
// Transform code to a component factory that wraps the component to provide dynamic component access
const transformedCode = `
// Transform code to a component factory that wraps the component to provide dynamic component access
const transformedCode = `
(function createComponent(React, componentsRegistry) {
// Define a component wrapper function that will handle component references
function DynamicComponentRenderer(name, props) {
@ -300,20 +464,23 @@ const ComponentRegistryProvider: React.FC<{ children: React.ReactNode }> = ({ ch
throw error;
}
})(React, compiledComponentsObj)
`;
`
// Compile the code
const compiledCode = Babel.transform(transformedCode, {
presets: ['react', 'typescript'],
filename: 'component.tsx'
}).code;
// Compile the code
const compiledCode = Babel.transform(transformedCode, {
presets: ['react', 'typescript'],
filename: 'component.tsx',
}).code
if (!compiledCode) {
throw new Error('Failed to compile component');
}
if (!compiledCode) {
throw new Error('Failed to compile component')
}
// Create and return the component with better error handling
const ComponentFactory = new Function('React', 'compiledComponentsObj', `
// Create and return the component with better error handling
const ComponentFactory = new Function(
'React',
'compiledComponentsObj',
`
try {
// Create a local variable to ensure it exists
var AnonComponent;
@ -350,306 +517,358 @@ const ComponentRegistryProvider: React.FC<{ children: React.ReactNode }> = ({ ch
]);
};
}
`);
`,
)
// Create the component with our registry of all other components
const Component = ComponentFactory(React, compiledComponents);
// Create the component with our registry of all other components
const Component = ComponentFactory(React, compiledComponents)
if (!Component || typeof Component !== 'function') {
throw new Error('Invalid component definition');
}
if (!Component || typeof Component !== 'function') {
throw new Error('Invalid component definition')
}
return Component;
} catch (error) {
console.error('Component compilation error:', error);
return () => (
<div className="p-4 border-2 border-red-300 rounded-lg bg-red-50 text-red-700">
<p className="font-semibold mb-2">Compilation Error</p>
<div className="text-sm whitespace-pre-wrap">{String(error)}</div>
</div>
);
}
}, [extractComponentInfo, compiledComponents]);
const compileAndRender = useCallback((code: string, props: ComponentProps = {}) => {
if (!code?.trim()) {
return null;
}
try {
// Create a component without adding it to registry yet
const Component = compileCode(code);
return <Component {...props} />;
} catch (error) {
console.error('Render error:', error);
return (
<div className="p-4 border-2 border-red-300 rounded-lg bg-red-50 text-red-700">
<p className="font-semibold mb-2">Render Error</p>
<div className="text-sm whitespace-pre-wrap">{String(error)}</div>
</div>
);
}
}, [compileCode]);
const renderComponent = useCallback((name: string, props: ComponentProps = {}) => {
// Check if the component is already compiled
if (compiledComponents[name]) {
const Component = compiledComponents[name];
return <Component {...props} />;
}
// Otherwise, try to compile it from the source code
const component = components.find(c => c.name === name && c.isActive);
if (!component) {
console.error(`Component not found: ${name}. Available components: ${Object.keys(compiledComponents).join(', ')}`);
return (
<div className="p-4 border-2 border-red-300 rounded-lg bg-red-50 text-red-700">
<div className="text-sm">Component not found: {name}</div>
<div className="text-xs mt-2">This could be because:
<ul className="list-disc ml-5 mt-1">
<li>The component has not been saved to the database</li>
<li>The component name is misspelled</li>
<li>There was an error compiling the component</li>
</ul>
return Component
} catch (error) {
console.error('Component compilation error:', error)
return () => (
<div className="p-4 border-2 border-red-300 rounded-lg bg-red-50 text-red-700">
<p className="font-semibold mb-2">Compilation Error</p>
<div className="text-sm whitespace-pre-wrap">{String(error)}</div>
</div>
</div>
);
}
)
}
},
[extractComponentInfo, compiledComponents],
)
try {
// Force refresh component registry when rendering a component directly
// This ensures all components are available for cross-referencing
if (Object.keys(compiledComponents).length === 0) {
// If no components are compiled yet, this is the first render
console.warn("Component registry is empty. Components might not be available for references.");
const compileAndRender = useCallback(
(code: string, props: ComponentProps = {}) => {
if (!code?.trim()) {
return null
}
return compileAndRender(component.code, props);
} catch (error) {
console.error(`Error rendering component ${name}:`, error);
return (
<div className="p-4 border-2 border-red-300 rounded-lg bg-red-50 text-red-700">
<div className="text-sm font-semibold">Error rendering {name}</div>
<div className="text-xs mt-2 whitespace-pre-wrap">{String(error)}</div>
</div>
);
}
}, [components, compileAndRender, compiledComponents]);
try {
// Create a component without adding it to registry yet
const Component = compileCode(code)
return <Component {...props} />
} catch (error) {
console.error('Render error:', error)
return (
<div className="p-4 border-2 border-red-300 rounded-lg bg-red-50 text-red-700">
<p className="font-semibold mb-2">Render Error</p>
<div className="text-sm whitespace-pre-wrap">{String(error)}</div>
</div>
)
}
},
[compileCode],
)
const isComponentRegistered = useCallback((name: string) => {
return components.some(c => c.name === name && c.isActive);
}, [components]);
const renderComponent = useCallback(
(name: string, props: ComponentProps = {}) => {
// Check if the component is already compiled
if (compiledComponents[name]) {
const Component = compiledComponents[name]
return <Component {...props} />
}
// Otherwise, try to compile it from the source code
const component = components.find((c) => c.name === name && c.isActive)
if (!component) {
console.error(
`Component not found: ${name}. Available components: ${Object.keys(compiledComponents).join(', ')}`,
)
return (
<div className="p-4 border-2 border-red-300 rounded-lg bg-red-50 text-red-700">
<div className="text-sm">Component not found: {name}</div>
<div className="text-xs mt-2">
This could be because:
<ul className="list-disc ml-5 mt-1">
<li>The component has not been saved to the database</li>
<li>The component name is misspelled</li>
<li>There was an error compiling the component</li>
</ul>
</div>
</div>
)
}
try {
// Force refresh component registry when rendering a component directly
// This ensures all components are available for cross-referencing
if (Object.keys(compiledComponents).length === 0) {
// If no components are compiled yet, this is the first render
console.warn(
'Component registry is empty. Components might not be available for references.',
)
}
return compileAndRender(component.code, props)
} catch (error) {
console.error(`Error rendering component ${name}:`, error)
return (
<div className="p-4 border-2 border-red-300 rounded-lg bg-red-50 text-red-700">
<div className="text-sm font-semibold">Error rendering {name}</div>
<div className="text-xs mt-2 whitespace-pre-wrap">{String(error)}</div>
</div>
)
}
},
[components, compileAndRender, compiledComponents],
)
const isComponentRegistered = useCallback(
(name: string) => {
return components.some((c) => c.name === name && c.isActive)
},
[components],
)
const getRegisteredComponents = useCallback(() => {
return components?.filter(c => c.isActive).map(c => c.name);
}, [components]);
return components?.filter((c) => c.isActive).map((c) => c.name)
}, [components])
const getComponentCode = useCallback((name: string) => {
const component = components.find(c => c.name === name);
return component ? component.code : null;
}, [components]);
const getComponentCode = useCallback(
(name: string) => {
const component = components.find((c) => c.name === name)
return component ? component.code : null
},
[components],
)
// Create a helper to process DOM nodes and replace custom lowercase elements with actual components
useEffect(() => {
if (typeof window === 'undefined' || !document) return;
if (typeof window === 'undefined' || !document) return
// Create a type definition for ReactDOM
// This approach avoids TypeScript errors
type ReactDOMType = {
render: (element: React.ReactNode, container: Element) => void;
createRoot?: (container: Element) => { render: (element: React.ReactNode) => void };
};
render: (element: React.ReactNode, container: Element) => void
createRoot?: (container: Element) => { render: (element: React.ReactNode) => void }
}
// Check if we have any components to work with
if (!Object.keys(compiledComponents).length) return;
if (!Object.keys(compiledComponents).length) return
// Get all lowercase component names from registry and add them to a Set for faster lookup
const lowercaseComponentNames = new Set<string>();
Object.keys(compiledComponents).forEach(name => {
const lowercaseComponentNames = new Set<string>()
Object.keys(compiledComponents).forEach((name) => {
// Only include lowercase component names, but not standard HTML elements
if (name.charAt(0) === name.charAt(0).toLowerCase() &&
name.charAt(0) !== name.charAt(0).toUpperCase() &&
!['div', 'span', 'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'button', 'a', 'img', 'input', 'form',
'label', 'select', 'option', 'textarea', 'ul', 'ol', 'li', 'table', 'tr', 'td', 'th'].includes(name)) {
lowercaseComponentNames.add(name);
if (
name.charAt(0) === name.charAt(0).toLowerCase() &&
name.charAt(0) !== name.charAt(0).toUpperCase() &&
![
'div',
'span',
'p',
'h1',
'h2',
'h3',
'h4',
'h5',
'h6',
'button',
'a',
'img',
'input',
'form',
'label',
'select',
'option',
'textarea',
'ul',
'ol',
'li',
'table',
'tr',
'td',
'th',
].includes(name)
) {
lowercaseComponentNames.add(name)
}
});
})
if (lowercaseComponentNames.size === 0) return;
if (lowercaseComponentNames.size === 0) return
// Create a function to process a DOM node and its children
const processNode = (rootNode: Element) => {
// Convert to array for better filtering
const componentsToFind = [...lowercaseComponentNames];
const componentsToFind = [...lowercaseComponentNames]
// Create CSS selector for all lowercase component tags
const selector = componentsToFind.join(',');
if (!selector) return false;
const selector = componentsToFind.join(',')
if (!selector) return false
// Find all matching elements
const elements = rootNode.tagName && lowercaseComponentNames.has(rootNode.tagName.toLowerCase())
? [rootNode]
: Array.from(rootNode.querySelectorAll(selector));
const elements =
rootNode.tagName && lowercaseComponentNames.has(rootNode.tagName.toLowerCase())
? [rootNode]
: Array.from(rootNode.querySelectorAll(selector))
if (elements.length === 0) return false;
if (elements.length === 0) return false
// Process each element
elements.forEach(element => {
elements.forEach((element) => {
// Skip if already processed
if (element.hasAttribute('data-component-processed')) return;
if (element.hasAttribute('data-component-processed')) return
try {
// Mark as processed to avoid infinite loops
element.setAttribute('data-component-processed', 'true');
element.setAttribute('data-component-processed', 'true')
// Get the tag name in lowercase
const tagName = element.tagName.toLowerCase();
const tagName = element.tagName.toLowerCase()
// Get the component from registry
const Component = compiledComponents[tagName];
const Component = compiledComponents[tagName]
if (!Component) {
console.warn(`Component ${tagName} exists in registry but couldn't be loaded`);
return;
console.warn(`Component ${tagName} exists in registry but couldn't be loaded`)
return
}
// Get props from element attributes
const props: Record<string, unknown> = {};
Array.from(element.attributes).forEach(attr => {
const props: Record<string, unknown> = {}
Array.from(element.attributes).forEach((attr) => {
// Skip data-component-processed attribute
if (attr.name === 'data-component-processed') return;
if (attr.name === 'data-component-processed') return
// Convert kebab-case to camelCase for props (React convention)
const propName = attr.name.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
let propValue: string | boolean | number = attr.value;
const propName = attr.name.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase())
let propValue: string | boolean | number = attr.value
// Handle boolean attributes (present without value)
if (propValue === '' || propValue === propName) {
propValue = true;
propValue = true
}
// Try to parse JSON values
if (propValue && typeof propValue === 'string') {
if ((propValue.startsWith('{') && propValue.endsWith('}')) ||
(propValue.startsWith('[') && propValue.endsWith(']')) ||
propValue === 'true' || propValue === 'false' || !isNaN(Number(propValue))) { try {
propValue = JSON.parse(propValue);
} catch {
// Keep as string if parsing fails
}
if (
(propValue.startsWith('{') && propValue.endsWith('}')) ||
(propValue.startsWith('[') && propValue.endsWith(']')) ||
propValue === 'true' ||
propValue === 'false' ||
!isNaN(Number(propValue))
) {
try {
propValue = JSON.parse(propValue)
} catch {
// Keep as string if parsing fails
}
}
}
props[propName] = propValue;
});
props[propName] = propValue
})
// Process children
const children = Array.from(element.childNodes).map(child => {
const children = Array.from(element.childNodes).map((child) => {
if (child.nodeType === Node.TEXT_NODE) {
return child.textContent;
return child.textContent
}
return child;
});
return child
})
if (children.length) {
props.children = children.length === 1 ? children[0] : children;
props.children = children.length === 1 ? children[0] : children
}
// Create a wrapper that preserves the original element's position
const wrapper = document.createElement('div');
wrapper.style.display = 'contents'; // Don't add extra layout structure
wrapper.dataset.customComponent = tagName;
const wrapper = document.createElement('div')
wrapper.style.display = 'contents' // Don't add extra layout structure
wrapper.dataset.customComponent = tagName
// Insert wrapper and remove original
element.parentNode?.insertBefore(wrapper, element);
element.parentNode?.removeChild(element);
element.parentNode?.insertBefore(wrapper, element)
element.parentNode?.removeChild(element)
// Render React component into wrapper
try {
const reactElement = React.createElement(Component, props);
const ReactDOM = (window as Window & typeof globalThis & { ReactDOM?: ReactDOMType }).ReactDOM;
const reactElement = React.createElement(Component, props)
const ReactDOM = (window as Window & typeof globalThis & { ReactDOM?: ReactDOMType })
.ReactDOM
if (ReactDOM) {
// Use modern createRoot API if available (React 18+)
if (ReactDOM.createRoot) {
const root = ReactDOM.createRoot(wrapper);
root.render(reactElement);
const root = ReactDOM.createRoot(wrapper)
root.render(reactElement)
}
// Fallback to legacy render API
else if (ReactDOM.render) {
ReactDOM.render(reactElement, wrapper);
ReactDOM.render(reactElement, wrapper)
}
} else {
console.error("ReactDOM not found in window - cannot render custom components");
console.error('ReactDOM not found in window - cannot render custom components')
}
} catch (err) {
console.error(`Error rendering ${tagName} React component:`, err);
console.error(`Error rendering ${tagName} React component:`, err)
// Show error UI in place of the component
wrapper.innerHTML = `<div style="border: 1px solid red; color: red; padding: 8px;">
Error rendering &lt;${tagName}&gt; component
</div>`;
</div>`
}
} catch (error) {
console.error(`Error processing ${element.tagName} element:`, error);
console.error(`Error processing ${element.tagName} element:`, error)
}
});
})
return elements.length > 0;
};
return elements.length > 0
}
// Create a mutation observer to watch for our custom elements
const observer = new MutationObserver((mutations) => {
let hasProcessed = false;
let hasProcessed = false
mutations.forEach(mutation => {
mutations.forEach((mutation) => {
if (mutation.type === 'childList') {
// Process added nodes
mutation.addedNodes.forEach(node => {
mutation.addedNodes.forEach((node) => {
if (node.nodeType === Node.ELEMENT_NODE) {
// Process this node and its children
hasProcessed = processNode(node as Element) || hasProcessed;
hasProcessed = processNode(node as Element) || hasProcessed
}
});
})
}
});
})
// Also check the entire document occasionally to catch any missed elements
// This handles cases where elements are added before our observer is active
if (!hasProcessed && document.body) {
processNode(document.body);
processNode(document.body)
}
});
})
// Start observing the entire document
observer.observe(document.body, {
childList: true,
subtree: true,
characterData: true
});
characterData: true,
})
// Initial scan of the whole document
if (document.body) {
processNode(document.body);
processNode(document.body)
}
return () => {
observer.disconnect();
};
}, [compiledComponents]);
observer.disconnect()
}
}, [compiledComponents])
const value = {
renderComponent,
compileAndRender,
isComponentRegistered,
getRegisteredComponents,
getComponentCode
};
getComponentCode,
}
return (
<ComponentRegistryContext.Provider value={value}>
{children}
</ComponentRegistryContext.Provider>
);
};
<ComponentRegistryContext.Provider value={value}>{children}</ComponentRegistryContext.Provider>
)
}
export default ComponentRegistryProvider;
export default ComponentRegistryProvider