/ web / src / plugins / PluginPage.tsx
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  }