Files
leocrm/frontend/src/components/plugins/PluginLoader.tsx
T

85 lines
2.6 KiB
TypeScript
Raw Normal View History

2026-07-23 19:01:18 +02:00
import React, { Suspense, lazy, Component, ReactNode } from 'react';
import { Loader2 } from 'lucide-react';
// ── Error Boundary ──────────────────────────────────────────────────────
interface PluginErrorBoundaryProps {
children: ReactNode;
pluginName: string;
}
interface PluginErrorBoundaryState {
hasError: boolean;
}
class PluginErrorBoundary extends Component<PluginErrorBoundaryProps, PluginErrorBoundaryState> {
state = { hasError: false };
static getDerivedStateFromError(): PluginErrorBoundaryState {
return { hasError: true };
}
render() {
if (this.state.hasError) {
return (
<div className="p-4 text-sm text-red-600" role="alert">
Failed to load plugin: {this.props.pluginName}
</div>
);
}
return this.props.children;
}
}
// ── Lazy Component Cache ────────────────────────────────────────────────
const componentCache = new Map<string, React.LazyExoticComponent<React.ComponentType<any>>>();
function getLazyComponent(componentPath: string): React.LazyExoticComponent<React.ComponentType<any>> {
if (componentCache.has(componentPath)) {
return componentCache.get(componentPath)!;
}
// Convert @/pages/Mail to ../pages/Mail for dynamic import
const importPath = componentPath.replace(/^@\//, '../');
const LazyComp = lazy(() =>
import(/* @vite-ignore */ importPath).then((m) => ({
default: m.default || m[Object.keys(m)[0]],
}))
);
componentCache.set(componentPath, LazyComp);
return LazyComp;
}
// ── PluginPage Component ───────────────────────────────────────────────
interface PluginPageProps {
component: string;
pluginName: string;
}
/**
* PluginPage — dynamically loads a plugin page component using React.lazy().
* Wraps the lazy component in Suspense with a centered spinner fallback
* and an ErrorBoundary that shows a fallback message on failure.
*/
export function PluginPage({ component, pluginName }: PluginPageProps) {
const LazyComp = getLazyComponent(component);
return (
<PluginErrorBoundary pluginName={pluginName}>
<Suspense
fallback={
<div className="flex items-center justify-center min-h-[50vh]">
<Loader2 className="animate-spin h-8 w-8 text-primary-500" />
</div>
}
>
<LazyComp />
</Suspense>
</PluginErrorBoundary>
);
}