"use client"; import { useState } from "react"; import { useTranslation } from "react-i18next"; import { IconRefresh } from "@tabler/icons-react"; import { Button } from "@kandev/ui/button"; import { Switch } from "@kandev/ui/switch"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@kandev/ui/tabs"; import { SettingsPageTemplate } from "@/components/settings/settings-page-template"; import { useAutoUpdateSettings } from "@/hooks/domains/plugins/use-auto-update-settings"; import { usePlugins } from "@/hooks/domains/plugins/use-plugins"; import { usePluginUpdates } from "@/hooks/domains/plugins/use-plugin-updates"; import type { MarketplaceEntry } from "@/lib/types/plugins"; import { InstallPluginDialog } from "./install-plugin-dialog"; import { MarketplaceBrowser } from "./marketplace-browser"; import { PluginRow } from "./plugin-row"; import { UninstallPluginDialog } from "./uninstall-plugin-dialog"; import { usePluginActions } from "./use-plugin-actions"; /** * Operator UI to browse, install, enable, disable, uninstall, and update kandev * plugins (docs/specs/plugins/marketplace.md). Gated on the `plugins` feature * flag by the page-level default export. */ export function PluginsSettings() { const { t } = useTranslation(); const list = usePlugins(); const actions = usePluginActions(); const autoUpdate = useAutoUpdateSettings(); const { updates, reload: reloadUpdates } = usePluginUpdates(); const [updatingId, setUpdatingId] = useState(null); // Update = install the newer package over the current one (marketplaceInstall // upserts the new record into the store, so the row's version refreshes), // then re-check the catalog so the resolved update drops off the row. const handleUpdate = async (entry: MarketplaceEntry) => { setUpdatingId(entry.id); try { await actions.marketplaceInstall(entry.package_url); reloadUpdates(); } finally { setUpdatingId(null); } }; return ( undefined} showSaveButton={false} > {t("plugins:tabInstalled")} {t("plugins:tabBrowse")} ); } type InstalledTabProps = { list: ReturnType; actions: ReturnType; autoUpdate: ReturnType; updates: Map; updatingId: string | null; onUpdate: (entry: MarketplaceEntry) => void; }; /** The Installed tab: auto-update toggle, sync/install toolbar, sync errors, and the plugin list. */ function InstalledTab({ list, actions, autoUpdate, updates, updatingId, onUpdate, }: InstalledTabProps) { const { t } = useTranslation(); return ( <>
{t("plugins:installedPlugins")}
{actions.syncErrors.length > 0 && (
{actions.syncErrors.map((err) => (
{err.path}: {err.reason}
))}
)} ); } /** * The instance-wide "Automatically update plugins" switch. When on, every * installed plugin without its own per-row override is auto-updated in the * background. Individual rows can still override this either way. */ function GlobalAutoUpdateToggle({ settings, }: { settings: ReturnType; }) { const { t } = useTranslation(); return (

{t("plugins:autoUpdateDescription")}

); } type PluginListProps = { list: ReturnType; actions: ReturnType; autoUpdateDefault: boolean; updates: Map; updatingId: string | null; onUpdate: (entry: MarketplaceEntry) => void; }; function PluginList({ list, actions, autoUpdateDefault, updates, updatingId, onUpdate, }: PluginListProps) { const { t } = useTranslation(); const { items, loaded, loading, error } = list; if (error) { return (
{error}
); } if (!loaded && loading) { return (
{t("plugins:loadingPlugins")}
); } if (loaded && items.length === 0) { return (
{t("plugins:noPluginsYet")}
); } return (
{items.map((plugin) => ( ))}
); }