PluginPage.tsx
1 import { useSyncExternalStore } from "react"; 2 import { Spinner } from "@nous-research/ui/ui/components/spinner"; 3 import { 4 getPluginComponent, 5 getPluginLoadError, 6 onPluginRegistered, 7 } from "./registry"; 8 import { useI18n } from "@/i18n"; 9 import { cn } from "@/lib/utils"; 10 import type { Translations } from "@/i18n/types"; 11 12 /** Renders a plugin tab once its bundle has called `register()`. */ 13 export function PluginPage({ name }: { name: string }) { 14 const { t } = useI18n(); 15 // Subscribe in render (via useSyncExternalStore) so we never miss 16 // `register()` if the script loads before a useEffect would run. 17 const Component = useSyncExternalStore( 18 (onChange) => onPluginRegistered(onChange), 19 () => getPluginComponent(name) ?? null, 20 () => null, 21 ); 22 const loadError = useSyncExternalStore( 23 (onChange) => onPluginRegistered(onChange), 24 () => getPluginLoadError(name) ?? null, 25 () => null, 26 ); 27 28 if (Component) { 29 return <Component />; 30 } 31 32 if (loadError) { 33 const message = formatPluginError(loadError, t); 34 return ( 35 <div 36 className={cn( 37 "max-w-lg p-4", 38 "font-mondwest text-sm tracking-[0.08em] text-midground/80", 39 )} 40 role="alert" 41 > 42 {message} 43 </div> 44 ); 45 } 46 47 return ( 48 <div 49 className={cn( 50 "flex items-center gap-2 p-4", 51 "font-mondwest text-sm tracking-[0.1em] text-midground/60", 52 )} 53 > 54 <Spinner className="shrink-0" /> 55 <span>{t.common.loading}</span> 56 </div> 57 ); 58 } 59 60 function formatPluginError(code: string, t: Translations): string { 61 if (code === "LOAD_FAILED") return t.common.pluginLoadFailed; 62 if (code === "NO_REGISTER") return t.common.pluginNotRegistered; 63 return code; 64 }