From a290556ba1cdf2966fad228c3478cc581ec9b018 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sedat=20=C3=96zt=C3=BCrk?= Date: Thu, 30 Oct 2025 22:23:59 +0300 Subject: [PATCH] =?UTF-8?q?Dinamik=20Route=20ve=20Dinamik=20Component=20d?= =?UTF-8?q?=C3=BCzenlemesi?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Seeds/LanguagesData.json | 6 + .../Seeds/MenusData.json | 7 + .../Seeds/PermissionsData.json | 11 +- .../Tenants/Seeds/TenantData.json | 10 +- ui/src/App.tsx | 13 +- .../components/developerKit/EntityManager.tsx | 2 +- ui/src/contexts/ComponentContext.tsx | 270 +++++- ui/src/contexts/ComponentRegistryContext.tsx | 878 ------------------ ui/src/routes/dynamicRouteLoader.tsx | 95 +- ui/src/routes/dynamicRouter.tsx | 9 +- 10 files changed, 399 insertions(+), 902 deletions(-) delete mode 100644 ui/src/contexts/ComponentRegistryContext.tsx diff --git a/api/src/Kurs.Platform.DbMigrator/Seeds/LanguagesData.json b/api/src/Kurs.Platform.DbMigrator/Seeds/LanguagesData.json index a7cfe434..d89e6838 100644 --- a/api/src/Kurs.Platform.DbMigrator/Seeds/LanguagesData.json +++ b/api/src/Kurs.Platform.DbMigrator/Seeds/LanguagesData.json @@ -121,6 +121,12 @@ } ], "LanguageTexts": [ + { + "resourceName": "Platform", + "key": "App.Product.ListComponent", + "en": "Product List Component", + "tr": "Ürün Liste Bileşeni" + }, { "resourceName": "Platform", "key": "App.Platform", diff --git a/api/src/Kurs.Platform.DbMigrator/Seeds/MenusData.json b/api/src/Kurs.Platform.DbMigrator/Seeds/MenusData.json index 03cf8c12..314b76f5 100644 --- a/api/src/Kurs.Platform.DbMigrator/Seeds/MenusData.json +++ b/api/src/Kurs.Platform.DbMigrator/Seeds/MenusData.json @@ -1,5 +1,12 @@ { "Routes": [ + { + "key": "dynamic.ProductListComponent", + "path": "/admin/ProductListComponent", + "componentPath": "dynamic:ProductListComponent", + "routeType": "protected", + "authority": [] + }, { "key": "home", "path": "/home", diff --git a/api/src/Kurs.Platform.DbMigrator/Seeds/PermissionsData.json b/api/src/Kurs.Platform.DbMigrator/Seeds/PermissionsData.json index cfd9e9e8..a31eb6ef 100644 --- a/api/src/Kurs.Platform.DbMigrator/Seeds/PermissionsData.json +++ b/api/src/Kurs.Platform.DbMigrator/Seeds/PermissionsData.json @@ -67,6 +67,15 @@ "MultiTenancySide": 3, "MenuGroup": "Erp|Kurs" }, + { + "GroupName": "App.Platform", + "Name": "App.Product.ListComponent", + "ParentName": null, + "DisplayName": "App.Product.ListComponent", + "IsEnabled": true, + "MultiTenancySide": 3, + "MenuGroup": "Erp|Kurs" + }, { "GroupName": "App.Saas", "Name": "AbpTenantManagement.Tenants", @@ -2239,7 +2248,7 @@ { "GroupName": "App.Administration", "Name": "App.DeveloperKit.CustomEndpoints", - "ParentName": null, + "ParentName": "App.DeveloperKit", "DisplayName": "App.DeveloperKit.CustomEndpoints", "IsEnabled": true, "MultiTenancySide": 3, diff --git a/api/src/Kurs.Platform.EntityFrameworkCore/Tenants/Seeds/TenantData.json b/api/src/Kurs.Platform.EntityFrameworkCore/Tenants/Seeds/TenantData.json index 3b7c1e29..b0245779 100644 --- a/api/src/Kurs.Platform.EntityFrameworkCore/Tenants/Seeds/TenantData.json +++ b/api/src/Kurs.Platform.EntityFrameworkCore/Tenants/Seeds/TenantData.json @@ -87,20 +87,20 @@ ], "CustomComponents": [ { - "name": "AxiosListComponent", - "code": "import React, { useEffect, useState } from \"react\";\nimport axios from \"axios\";\n\ninterface AxiosListComponentProps {\n title: string;\n}\n\nconst api = axios.create({\n baseURL: \"https://localhost:44344\", // defaults'ı her seferinde set etme\n});\n\nconst AxiosListComponent: React.FC = ({ title }) => {\n const [data, setData] = useState>([]);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState(null);\n\n useEffect(() => {\n const fetchData = async () => {\n setLoading(true);\n setError(null);\n\n try {\n const res = await api.get(`/api/app/dynamic/${title}`);\n const raw = Array.isArray(res.data) ? res.data : res.data?.items ?? [];\n\n const filtered = raw.map((item: any) => ({\n id: item.Id ?? item.id,\n name: item.Name ?? item.name,\n }));\n\n setData(filtered);\n } catch (err: any) {\n setError(err.message || \"Failed to fetch data\");\n } finally {\n setLoading(false);\n }\n };\n\n if (title) fetchData();\n }, [title]);\n\n if (loading) return
Loading...
;\n if (error) return
Error: {error}
;\n if (!data.length) return
No records found
;\n\n const headers = [\"id\", \"name\", \"actions\"];\n\n return (\n
\n \n \n \n {headers.map((key) => (\n \n {key === \"actions\" ? \"Actions\" : key}\n \n ))}\n \n \n \n {data.map((item, rowIndex) => (\n \n \n \n \n \n ))}\n \n
\n {item.id}\n \n {item.name}\n \n alert(item.name)}\n className=\"bg-blue-600 hover:bg-blue-700 text-white text-sm px-3 py-1 rounded-lg shadow-sm transition\"\n >\n Show Name\n \n
\n
\n );\n};\n\nexport default AxiosListComponent;", + "name": "DynamicEntityComponent", + "code": "import React, { useEffect, useState } from \"react\";\nimport axios from \"axios\";\n\ninterface DynamicEntityComponentProps {\n title: string;\n}\n\nconst api = axios.create({\n baseURL: \"https://localhost:44344\", // defaults'ı her seferinde set etme\n});\n\nconst DynamicEntityComponent: React.FC = ({ title }) => {\n const [data, setData] = useState>([]);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState(null);\n\n useEffect(() => {\n const fetchData = async () => {\n setLoading(true);\n setError(null);\n\n try {\n const res = await api.get(`/api/app/dynamic/${title}`);\n const raw = Array.isArray(res.data) ? res.data : res.data?.items ?? [];\n\n const filtered = raw.map((item: any) => ({\n id: item.Id ?? item.id,\n name: item.Name ?? item.name,\n }));\n\n setData(filtered);\n } catch (err: any) {\n setError(err.message || \"Failed to fetch data\");\n } finally {\n setLoading(false);\n }\n };\n\n if (title) fetchData();\n }, [title]);\n\n if (loading) return
Loading...
;\n if (error) return
Error: {error}
;\n if (!data.length) return
No records found
;\n\n const headers = [\"id\", \"name\", \"actions\"];\n\n return (\n
\n \n \n \n {headers.map((key) => (\n \n {key === \"actions\" ? \"Actions\" : key}\n \n ))}\n \n \n \n {data.map((item, rowIndex) => (\n \n \n \n \n \n ))}\n \n
\n {item.id}\n \n {item.name}\n \n alert(item.name)}\n className=\"bg-blue-600 hover:bg-blue-700 text-white text-sm px-3 py-1 rounded-lg shadow-sm transition\"\n >\n Show Name\n \n
\n
\n );\n};\n\nexport default DynamicEntityComponent;", "props": null, "description": null, "isActive": true, "dependencies": [] }, { - "name": "EntityListComponent", - "code": "const EntityListComponent = ({\n title = \"Product\"\n}) => {\n return (\n \n );\n};\n\nexport default EntityListComponent;", + "name": "ProductListComponent", + "code": "const ProductListComponent = ({\n title = \"Product\"\n}) => {\n return (\n \n );\n};\n\nexport default ProductListComponent;", "props": null, "description": null, "isActive": true, - "dependencies": ["AxiosListComponent"] + "dependencies": ["DynamicEntityComponent"] } ], "ReportCategories": [ diff --git a/ui/src/App.tsx b/ui/src/App.tsx index 6f44d206..bceba494 100644 --- a/ui/src/App.tsx +++ b/ui/src/App.tsx @@ -6,7 +6,6 @@ import { BrowserRouter } from 'react-router-dom' import { store } from './store' import { DynamicRoutesProvider } from './routes/dynamicRoutesContext' import { ComponentProvider } from './contexts/ComponentContext' -import ComponentRegistryProvider from './contexts/ComponentRegistryContext' import { registerServiceWorker } from './views/version/swRegistration' import { useEffect } from 'react' @@ -22,13 +21,11 @@ function App() { - - - - - - - + + + + + diff --git a/ui/src/components/developerKit/EntityManager.tsx b/ui/src/components/developerKit/EntityManager.tsx index 9a1cb6e4..0d0c3d50 100644 --- a/ui/src/components/developerKit/EntityManager.tsx +++ b/ui/src/components/developerKit/EntityManager.tsx @@ -231,7 +231,7 @@ const EntityManager: React.FC = () => {

- {translate('::App.DeveloperKit.Entity.FieldsLabel')}: + {translate('::App.DeveloperKit.Entity.FieldLabel')}

{entity.fields.slice(0, 4).map((field) => ( diff --git a/ui/src/contexts/ComponentContext.tsx b/ui/src/contexts/ComponentContext.tsx index fa7adea7..18218726 100644 --- a/ui/src/contexts/ComponentContext.tsx +++ b/ui/src/contexts/ComponentContext.tsx @@ -5,7 +5,53 @@ import { } from '@/proxy/developerKit/models' import { developerKitService } from '@/services/developerKit.service' import { useStoreState } from '@/store/store' -import React, { createContext, useContext, useState, useEffect } from 'react' +import React, { createContext, useContext, useState, useEffect, useCallback } from 'react' +import * as Babel from '@babel/standalone' +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' +import axios from 'axios' + +interface ComponentProps { + [key: string]: unknown +} interface ComponentContextType { components: CustomComponent[] @@ -17,8 +63,15 @@ interface ComponentContextType { getComponent: (id: string) => CustomComponent | undefined getComponentByName: (name: string) => CustomComponent | undefined refreshComponents: () => Promise + // Manual registered components registeredComponents: Record> registerComponent: (name: string, component: React.ComponentType) => void + // Database compiled components + 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 } const ComponentContext = createContext(undefined) @@ -40,6 +93,9 @@ export const ComponentProvider: React.FC<{ children: React.ReactNode }> = ({ chi const [registeredComponents, setRegisteredComponents] = useState< Record> >({}) + const [compiledComponents, setCompiledComponents] = useState< + Record> + >({}) const refreshComponents = async () => { try { @@ -119,6 +175,213 @@ export const ComponentProvider: React.FC<{ children: React.ReactNode }> = ({ chi })) } + // Register some default components on mount + useEffect(() => { + // Example components for testing + const HelloWorldComponent: React.ComponentType = () => { + return React.createElement('div', { + className: 'p-6 bg-blue-50 rounded-lg' + }, [ + React.createElement('h1', { + className: 'text-2xl font-bold text-blue-900 mb-4' + }, 'Hello World!'), + React.createElement('p', { + className: 'text-blue-700' + }, 'Bu manuel kayıtlı bir komponent!') + ]) + } + + const TestListComponent: React.ComponentType = () => { + return React.createElement('div', { + className: 'p-6 bg-green-50 rounded-lg' + }, [ + React.createElement('h1', { + className: 'text-2xl font-bold text-green-900 mb-4' + }, 'Test List'), + React.createElement('p', { + className: 'text-green-700' + }, 'Bu da test için kayıtlı komponent!') + ]) + } + + registerComponent('HelloWorld', HelloWorldComponent) + registerComponent('TestList', TestListComponent) + }, []) // Empty dependency array - only run once + + // Component compilation functions (moved from ComponentRegistryProvider) + const extractComponentInfo = useCallback((code: string, defaultName = '') => { + try { + const fcTypeMatch = code.match(/const\s+([A-Za-z]\w*)\s*:\s*React\.FC/) + if (fcTypeMatch) return fcTypeMatch[1] + + const functionMatch = code.match(/function\s+([A-Za-z]\w*)/) + if (functionMatch) return functionMatch[1] + + const arrowMatch = code.match(/const\s+([A-Za-z]\w*)\s*=/) + if (arrowMatch) return arrowMatch[1] + + const classMatch = code.match(/class\s+([A-Za-z]\w*)/) + if (classMatch) return classMatch[1] + + const exportMatch = code.match(/export\s+default\s+([A-Za-z]\w*)/) + if (exportMatch) return exportMatch[1] + + if (defaultName) return defaultName + return 'DynamicComponent' + } catch (err) { + console.error('Error extracting component name:', err) + return defaultName || 'DynamicComponent' + } + }, []) + + // Compile components when they change + useEffect(() => { + if (!components || !components?.length) return + + try { + const activeComponents = components?.filter((c) => c.isActive) + + if (!activeComponents.length) { + setCompiledComponents({}) + return + } + + const componentInfos = activeComponents.map((comp) => { + const name = comp.name + const nameCapitalized = name.charAt(0).toUpperCase() + name.slice(1) + + return { + name: name, + nameCapitalized: nameCapitalized, + internalName: extractComponentInfo(comp.code, nameCapitalized), + code: comp.code + .replace(/import\s+.*?;/g, '') + .replace(/export\s+default\s+/, '') + .trim(), + } + }) + + // Create cross-referencing bundle + const componentDeclarations = componentInfos + .map((info) => `let ${info.name}_Component;`) + .join('\n') + + const componentDefinitions = componentInfos + .map((info) => { + const componentVariables = componentInfos + .filter(other => other.name !== info.name) + .map(other => `const ${other.name} = ${other.name}_Component;`) + .join('\n ') + + return ` + ${info.name}_Component = (function() { + ${componentVariables} + ${info.code} + return ${info.internalName}; + })();` + }) + .join('\n') + + const componentBundle = componentDeclarations + '\n' + componentDefinitions + + const bundledCode = ` + (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, axios) { + const { useState, useEffect, useCallback, useMemo, useRef, createContext, useContext } = React; + const componentRegistry = {}; + + ${componentBundle} + + ${componentInfos + .map( + (info) => ` + componentRegistry["${info.name}"] = ${info.name}_Component; + componentRegistry["${info.nameCapitalized}"] = ${info.name}_Component; + `, + ) + .join('\n')} + + return componentRegistry; + })(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, axios) + ` + + const compiledBundle = Babel.transform(bundledCode, { + presets: ['react', 'typescript'], + filename: 'components-bundle.tsx', + }).code + + if (!compiledBundle) { + throw new Error('Failed to compile components bundle') + } + + 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', 'axios', + `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, axios, + ) + + setCompiledComponents(compiledComponentsRegistry) + } catch (error) { + console.error('Error compiling components bundle:', error) + setCompiledComponents({}) + } + }, [components, extractComponentInfo]) + + // Render functions + const renderComponent = useCallback( + (name: string, props: ComponentProps = {}) => { + if (compiledComponents[name]) { + const Component = compiledComponents[name] + return + } + + const component = components.find((c) => c.name === name && c.isActive) + if (!component) { + console.error(`Component not found: ${name}`) + return ( +
+
Component not found: {name}
+
+ ) + } + + return null + }, + [components, compiledComponents], + ) + + const compileAndRender = useCallback( + (code: string, props: ComponentProps = {}) => { + if (!code?.trim()) return null + // Simplified version - can be extended later + return
Code compilation not implemented yet
+ }, + [], + ) + + const isComponentRegistered = useCallback( + (name: string) => { + return components.some((c) => c.name === name && c.isActive) || !!compiledComponents[name] + }, + [components, compiledComponents], + ) + + const getRegisteredComponents = useCallback(() => { + const dbComponents = components?.filter((c) => c.isActive).map((c) => c.name) || [] + const compiledNames = Object.keys(compiledComponents) + return [...new Set([...dbComponents, ...compiledNames])] + }, [components, compiledComponents]) + + const getComponentCode = useCallback( + (name: string) => { + const component = components.find((c) => c.name === name) + return component ? component.code : null + }, + [components], + ) + const value = { components, loading, @@ -131,6 +394,11 @@ export const ComponentProvider: React.FC<{ children: React.ReactNode }> = ({ chi refreshComponents, registeredComponents, registerComponent, + renderComponent, + compileAndRender, + isComponentRegistered, + getRegisteredComponents, + getComponentCode, } return {children} diff --git a/ui/src/contexts/ComponentRegistryContext.tsx b/ui/src/contexts/ComponentRegistryContext.tsx deleted file mode 100644 index 1a266b91..00000000 --- a/ui/src/contexts/ComponentRegistryContext.tsx +++ /dev/null @@ -1,878 +0,0 @@ -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' -import axios from 'axios' - -interface ComponentProps { - [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 -} - -export const ComponentRegistryContext = createContext( - undefined, -) - -const ComponentRegistryProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { - const { components } = useComponents() - const [compiledComponents, setCompiledComponents] = useState< - Record> - >({}) - - 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] - - // Function declaration: function MyComponent() {} - 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] - - // Class declaration: class MyComponent extends React.Component {} - 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] - - // Interface name which might indicate component name - 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] - - // 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] - } - - // Use the default name provided (usually the component name from DB) - if (defaultName) { - return defaultName - } - - // Last resort - use "DynamicComponent" as it's descriptive and unlikely to conflict - return 'DynamicComponent' - } catch (err) { - console.error('Error extracting component name:', err) - return defaultName || 'DynamicComponent' - } - }, []) - - // Compile all components when the component list changes - useEffect(() => { - if (!components || !components?.length) return - - try { - // Create a bundle of all active components - const activeComponents = components?.filter((c) => c.isActive) - - if (!activeComponents.length) { - setCompiledComponents({}) - return - } - - // First, extract all component names and create both lowercase and normal versions - 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) - - return { - name: name, - nameCapitalized: nameCapitalized, - internalName: extractComponentInfo(comp.code, nameCapitalized), // Pass capitalized name as default - code: comp.code - .replace(/import\s+.*?;/g, '') - .replace(/export\s+default\s+/, '') - .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') - - // Create a function that returns an object with all components - const bundledCode = ` - (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, axios) { - // 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) => ` - // 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')} - - return componentRegistry; - })(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, axios) - ` - - // Compile the bundle - const compiledBundle = Babel.transform(bundledCode, { - presets: ['react', 'typescript'], - filename: 'components-bundle.tsx', - }).code - - if (!compiledBundle) { - throw new Error('Failed to compile components bundle') - } - - // Evaluate the bundle to get all components - 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', - 'axios', - `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, - axios, - ) - - setCompiledComponents(compiledComponentsRegistry) - } catch (error) { - console.error('Error compiling components bundle:', error) - setCompiledComponents({}) - } - }, [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() - - // 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 or - 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)] - - // 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 = ` - (function createComponent(React, componentsRegistry, axios) { - // Define a component wrapper function that will handle component references - function DynamicComponentRenderer(name, props) { - // Check if the name exists in the registry - if (componentsRegistry[name]) { - const ComponentToRender = componentsRegistry[name]; - return React.createElement(ComponentToRender, props); - } - - // If not found, show an error UI - console.error("Component not found:", name); - return React.createElement( - "div", - { style: { padding: '8px', border: '1px solid red', color: 'red', margin: '4px' } }, - "Component not found: " + name - ); - } - - // Wrap the JSX transformer - this is the magic that lets us use syntax - const originalCreateElement = React.createElement; - - // Standard HTML elements that React natively supports - const standardHtmlElements = new Set([ - 'a', 'abbr', 'address', 'area', 'article', 'aside', 'audio', 'b', 'base', 'bdi', 'bdo', - 'blockquote', 'body', 'br', 'button', 'canvas', 'caption', 'cite', 'code', 'col', 'colgroup', - 'data', 'datalist', 'dd', 'del', 'details', 'dfn', 'dialog', 'div', 'dl', 'dt', 'em', 'embed', - 'fieldset', 'figcaption', 'figure', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', - 'head', 'header', 'hr', 'html', 'i', 'iframe', 'img', 'input', 'ins', 'kbd', 'label', 'legend', - 'li', 'link', 'main', 'map', 'mark', 'menu', 'meta', 'meter', 'nav', 'noscript', 'object', - 'ol', 'optgroup', 'option', 'output', 'p', 'param', 'picture', 'pre', 'progress', 'q', 'rp', - 'rt', 'ruby', 's', 'samp', 'script', 'section', 'select', 'small', 'source', 'span', 'strong', - 'style', 'sub', 'summary', 'sup', 'svg', 'table', 'tbody', 'td', 'template', 'textarea', - 'tfoot', 'th', 'thead', 'time', 'title', 'tr', 'track', 'u', 'ul', 'var', 'video', 'wbr' - ]); - - // Override createElement to handle dynamic components - React.createElement = function(type, props, ...children) { - // If it's already a component function/class, use it as is - if (typeof type !== 'string') { - return originalCreateElement.apply(React, [type, props, ...children]); - } - - // If type is a string starting with uppercase, it's likely a component reference - if (/^[A-Z]/.test(type)) { - // First check if it's directly in our registry - if (componentsRegistry[type]) { - return DynamicComponentRenderer(type, Object.assign({}, props, { children })); - } - - // Otherwise let React try to handle it - // It might be a component defined elsewhere in the code - return originalCreateElement.apply(React, [type, props, ...children]); - } - - // Special case: check for lowercase component names - // If it's not a standard HTML element, it might be a custom component - if (!standardHtmlElements.has(type)) { - // First check if the lowercase name is registered directly - if (componentsRegistry[type]) { - console.warn("Using lowercase component name '" + type + "'. React components should use PascalCase."); - return DynamicComponentRenderer(type, Object.assign({}, props, { children })); - } - - // Then check if we have a PascalCase version of this component - const pascalCase = type.charAt(0).toUpperCase() + type.slice(1); - if (componentsRegistry[pascalCase]) { - console.warn("Using lowercase component name '" + type + "'. Consider using PascalCase: '" + pascalCase + "'"); - return DynamicComponentRenderer(pascalCase, Object.assign({}, props, { children })); - } - - // If neither matched, add a hint in the console to help debugging - console.info("Unknown element: <" + type + ">. Treating as HTML element. If this is a custom component, ensure it's registered in PascalCase."); - } - - // Otherwise use the original React.createElement for standard HTML elements - return originalCreateElement.apply(React, [type, props, ...children]); - }; - - // Safety net for direct CustomComponent references - var CustomComponent = function(props) { - console.error("A component is using 'CustomComponent' which was not properly defined"); - return React.createElement("div", { style: { padding: '12px', border: '2px solid red', color: 'red', margin: '8px' } }, - "Component definition error: CustomComponent was referenced but not defined properly"); - }; - - try { - // Add a safety declaration of AnonComponent to handle default component name - var AnonComponent; - - // Execute the component code in this enhanced environment - ${cleanCode} - - // Restore original createElement to prevent side effects - const component = ${componentName}; - React.createElement = originalCreateElement; - - // If component is still undefined (possible if code doesn't properly assign it) - // and we're using AnonComponent as the name, provide a fallback - if (${componentName} === undefined && "${componentName}" === "AnonComponent") { - console.warn("Component definition didn't properly expose the component. Creating fallback."); - // Try to find a React component in the execution context - const possibleComponents = Object.keys(this).filter(key => - typeof this[key] === "function" && - /^[A-Z]/.test(key) && - key !== "React" && - key !== "AnonComponent" - ); - - if (possibleComponents.length > 0) { - // Use the first component-like function found - return this[possibleComponents[0]]; - } - - // Last resort: Create a minimal functional component - return function DefaultComponent(props) { - return React.createElement("div", { - style: { padding: "8px", border: "1px solid #ccc" }, - ...props - }, props.children || "Unnamed Component"); - }; - } - - return component; - } catch (error) { - // Restore original createElement in case of error too - React.createElement = originalCreateElement; - throw error; - } - })(React, compiledComponentsObj, axios) - ` - - // Compile the code - const compiledCode = Babel.transform(transformedCode, { - presets: ['react', 'typescript'], - filename: 'component.tsx', - }).code - - if (!compiledCode) { - throw new Error('Failed to compile component') - } - - // Create and return the component with better error handling - const ComponentFactory = new Function( - 'React', - 'compiledComponentsObj', - 'axios', - ` - try { - // Create a local variable to ensure it exists - var AnonComponent; - - // Execute the compiled code - const component = ${compiledCode}; - - // Check if we got a valid component back - if (typeof component !== 'function') { - console.warn("Component did not return a function, got:", typeof component); - - // Return a fallback component - return function FallbackComponent(props) { - return React.createElement("div", { - style: { padding: '8px', backgroundColor: '#f8f9fa', border: '1px solid #ddd' } - }, [ - React.createElement("p", { style: { fontWeight: 'bold' } }, "Component Preview"), - React.createElement("div", {}, props.children || "Component content will appear here") - ]); - }; - } - - return component; - } catch (e) { - console.error("Error evaluating component:", e); - return function ErrorComponent() { - return React.createElement("div", { - style: { padding: '12px', border: '2px solid red', color: 'red', margin: '8px' } - }, [ - React.createElement("p", { style: { fontWeight: 'bold' } }, "Error evaluating component"), - React.createElement("pre", { style: { whiteSpace: 'pre-wrap', fontSize: '12px' } }, e.message), - React.createElement("p", { style: { marginTop: '8px', fontSize: '12px' } }, - "Check your code for syntax errors or undefined variables.") - ]); - }; - } - `, - ) - - // Create the component with our registry of all other components - const Component = ComponentFactory(React, compiledComponents, axios) - - if (!Component || typeof Component !== 'function') { - throw new Error('Invalid component definition') - } - - return Component - } catch (error) { - console.error('Component compilation error:', error) - return () => ( -
-

Compilation Error

-
{String(error)}
-
- ) - } - }, - [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 - } catch (error) { - console.error('Render error:', error) - return ( -
-

Render Error

-
{String(error)}
-
- ) - } - }, - [compileCode], - ) - - const renderComponent = useCallback( - (name: string, props: ComponentProps = {}) => { - // Check if the component is already compiled - if (compiledComponents[name]) { - const Component = compiledComponents[name] - return - } - - // 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 ( -
-
Component not found: {name}
-
- This could be because: -
    -
  • The component has not been saved to the database
  • -
  • The component name is misspelled
  • -
  • There was an error compiling the component
  • -
-
-
- ) - } - - 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 ( -
-
Error rendering {name}
-
{String(error)}
-
- ) - } - }, - [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]) - - 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 - - // 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 } - } - - // Check if we have any components to work with - 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() - 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 (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] - - // Create CSS selector for all lowercase component tags - 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)) - - if (elements.length === 0) return false - - // Process each element - elements.forEach((element) => { - // Skip if already processed - if (element.hasAttribute('data-component-processed')) return - - try { - // Mark as processed to avoid infinite loops - element.setAttribute('data-component-processed', 'true') - - // Get the tag name in lowercase - const tagName = element.tagName.toLowerCase() - - // Get the component from registry - const Component = compiledComponents[tagName] - if (!Component) { - console.warn(`Component ${tagName} exists in registry but couldn't be loaded`) - return - } - - // Get props from element attributes - const props: Record = {} - Array.from(element.attributes).forEach((attr) => { - // Skip data-component-processed attribute - 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 - - // Handle boolean attributes (present without value) - if (propValue === '' || propValue === propName) { - 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 - } - } - } - - props[propName] = propValue - }) - - // Process children - const children = Array.from(element.childNodes).map((child) => { - if (child.nodeType === Node.TEXT_NODE) { - return child.textContent - } - return child - }) - - if (children.length) { - 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 - - // Insert wrapper and remove original - 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 - - if (ReactDOM) { - // Use modern createRoot API if available (React 18+) - if (ReactDOM.createRoot) { - const root = ReactDOM.createRoot(wrapper) - root.render(reactElement) - } - // Fallback to legacy render API - else if (ReactDOM.render) { - ReactDOM.render(reactElement, wrapper) - } - } else { - console.error('ReactDOM not found in window - cannot render custom components') - } - } catch (err) { - console.error(`Error rendering ${tagName} React component:`, err) - - // Show error UI in place of the component - wrapper.innerHTML = `
- Error rendering <${tagName}> component -
` - } - } catch (error) { - console.error(`Error processing ${element.tagName} element:`, error) - } - }) - - return elements.length > 0 - } - - // Create a mutation observer to watch for our custom elements - const observer = new MutationObserver((mutations) => { - let hasProcessed = false - - mutations.forEach((mutation) => { - if (mutation.type === 'childList') { - // Process added nodes - mutation.addedNodes.forEach((node) => { - if (node.nodeType === Node.ELEMENT_NODE) { - // Process this node and its children - 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) - } - }) - - // Start observing the entire document - observer.observe(document.body, { - childList: true, - subtree: true, - characterData: true, - }) - - // Initial scan of the whole document - if (document.body) { - processNode(document.body) - } - - return () => { - observer.disconnect() - } - }, [compiledComponents]) - - const value = { - renderComponent, - compileAndRender, - isComponentRegistered, - getRegisteredComponents, - getComponentCode, - } - - return ( - {children} - ) -} - -export default ComponentRegistryProvider diff --git a/ui/src/routes/dynamicRouteLoader.tsx b/ui/src/routes/dynamicRouteLoader.tsx index baf48f1a..2c314908 100644 --- a/ui/src/routes/dynamicRouteLoader.tsx +++ b/ui/src/routes/dynamicRouteLoader.tsx @@ -1,5 +1,6 @@ import { RouteDto } from '@/proxy/routes/models' import { lazy } from 'react' +import { useComponents } from '@/contexts/ComponentContext' // Tüm view bileşenlerini import et (vite özel) // shared klasörü hariç, çünkü bu bileşenler genellikle başka yerlerde statik import ediliyor @@ -7,18 +8,32 @@ const modules = import.meta.glob(['../views/**/*.tsx', '!../views/shared/**/*.ts const lazyComponentCache = new Map>>() -export function loadComponent(componentPath: string) { +// ComponentPath'in fiziksel mi yoksa dinamik mi olduğunu belirle +function isPhysicalComponent(componentPath: string): boolean { + // @ ile başlayan path'ler fiziksel dosya yolu + // Başka bir kural: dynamic: ile başlayan path'ler dinamik + return componentPath.startsWith('@/') || componentPath.startsWith('../') +} + +function isDynamicComponent(componentPath: string): boolean { + // dynamic: ile başlayan path'ler dinamik komponent + return componentPath.startsWith('dynamic:') +} + +// Fiziksel komponent yükleme (mevcut mantık) +function loadPhysicalComponent(componentPath: string) { const cleanedPath = componentPath.replace(/^@\//, '') const fullPath = `../${cleanedPath}.tsx` if (lazyComponentCache.has(fullPath)) { + // console.log(`Physical component loaded from cache: ${fullPath}`) return lazyComponentCache.get(fullPath)! } const loader = modules[fullPath] if (!loader) { - console.error(`Component not found for path: ${fullPath}`) - throw new Error(`Component not found for path: ${fullPath}`) + console.error(`Physical component not found for path: ${fullPath}`) + throw new Error(`Physical component not found for path: ${fullPath}`) } const LazyComponent = lazy(loader as () => Promise<{ default: React.ComponentType }>) @@ -26,13 +41,79 @@ export function loadComponent(componentPath: string) { return LazyComponent } +// Dinamik komponent yükleme (yeni mantık) +function loadDynamicComponent( + componentPath: string, + registeredComponents: Record>, + renderComponent?: (name: string, props?: any) => React.ReactNode, + isComponentRegistered?: (name: string) => boolean +) { + const componentName = componentPath.replace('dynamic:', '') + + if (lazyComponentCache.has(componentPath)) { + // console.log(`Dynamic component loaded from cache: ${componentName}`) + return lazyComponentCache.get(componentPath)! + } + + // Önce manuel registered komponentleri kontrol et + let DynamicComponent = registeredComponents[componentName] + + // Eğer manuel registered'da yoksa, database compiled komponentleri kontrol et + if (!DynamicComponent && isComponentRegistered && renderComponent && isComponentRegistered(componentName)) { + // console.log(`Database component found: ${componentName}`) + // Database komponentini wrapper ile kullan + DynamicComponent = (props: any) => renderComponent(componentName, props) as React.ReactElement + } + + if (!DynamicComponent) { + console.error(`Dynamic component not found: ${componentName}`) + console.log('Available registered components:', Object.keys(registeredComponents)) + if (isComponentRegistered) { + console.log('Database component registry available - checking...') + } + throw new Error(`Dynamic component not found: ${componentName}`) + } + + // console.log(`Dynamic component loaded: ${componentName}`) + // Dinamik komponent için lazy wrapper oluştur + const LazyComponent = lazy(() => Promise.resolve({ default: DynamicComponent as React.ComponentType })) + lazyComponentCache.set(componentPath, LazyComponent) + return LazyComponent +} + +export function loadComponent( + componentPath: string, + registeredComponents?: Record>, + renderComponent?: (name: string, props?: any) => React.ReactNode, + isComponentRegistered?: (name: string) => boolean +) { + if (isPhysicalComponent(componentPath)) { + return loadPhysicalComponent(componentPath) + } else if (isDynamicComponent(componentPath)) { + if (!registeredComponents) { + throw new Error('Registered components required for dynamic component loading') + } + return loadDynamicComponent(componentPath, registeredComponents, renderComponent, isComponentRegistered) + } else { + // Backward compatibility: varsayılan olarak fiziksel komponent kabul et + return loadPhysicalComponent(componentPath) + } +} + // React Router için uygun bir route tipi export interface DynamicReactRoute { key: string path: string - getComponent: () => React.LazyExoticComponent> + getComponent: ( + registeredComponents?: Record>, + renderComponent?: (name: string, props?: any) => React.ReactNode, + isComponentRegistered?: (name: string) => boolean + ) => React.LazyExoticComponent> routeType: string authority?: string[] + componentPath: string + isPhysical: boolean + isDynamic: boolean } // API'den gelen route objesini, React Router için uygun hale getirir @@ -40,8 +121,12 @@ export function mapDynamicRoutes(routes: RouteDto[]): DynamicReactRoute[] { return routes.map((route) => ({ key: route.path, path: route.path, - getComponent: () => loadComponent(route.componentPath), + getComponent: (registeredComponents, renderComponent, isComponentRegistered) => + loadComponent(route.componentPath, registeredComponents, renderComponent, isComponentRegistered), routeType: route.routeType, authority: route.authority, + componentPath: route.componentPath, + isPhysical: isPhysicalComponent(route.componentPath), + isDynamic: isDynamicComponent(route.componentPath), })) } diff --git a/ui/src/routes/dynamicRouter.tsx b/ui/src/routes/dynamicRouter.tsx index 66a87d87..2e3d9f07 100644 --- a/ui/src/routes/dynamicRouter.tsx +++ b/ui/src/routes/dynamicRouter.tsx @@ -1,8 +1,9 @@ // DynamicRouter.tsx import React from 'react' import { Routes, Route, Navigate } from 'react-router-dom' -import { mapDynamicRoutes, loadComponent } from './dynamicRouteLoader' +import { mapDynamicRoutes } from './dynamicRouteLoader' import { useDynamicRoutes } from './dynamicRoutesContext' +import { useComponents } from '@/contexts/ComponentContext' import ProtectedRoute from '@/components/route/ProtectedRoute' import PermissionGuard from '@/components/route/PermissionGuard' import PageContainer from '@/components/template/PageContainer' @@ -15,6 +16,8 @@ const NotFound = React.lazy(() => import('@/views/NotFound')) export const DynamicRouter: React.FC = () => { const { routes, loading, error } = useDynamicRoutes() + const { registeredComponents, renderComponent, isComponentRegistered } = useComponents() + const dynamicRoutes = React.useMemo(() => mapDynamicRoutes(routes), [routes]) if (loading) return
Loading...
@@ -27,7 +30,7 @@ export const DynamicRouter: React.FC = () => { {dynamicRoutes .filter((r) => r.routeType === 'protected') .map((route) => { - const Component = route.getComponent() + const Component = route.getComponent(registeredComponents, renderComponent, isComponentRegistered) return ( { hasSubdomain() ? r.routeType === 'authenticated' : r.routeType !== 'protected', ) .map((route) => { - const Component = route.getComponent() + const Component = route.getComponent(registeredComponents, renderComponent, isComponentRegistered) return (