"use client"; import { IconArrowUpCircle } from "@tabler/icons-react"; import { Badge } from "@kandev/ui/badge"; import { Button } from "@kandev/ui/button"; import { Switch } from "@kandev/ui/switch"; import Link from "@/components/routing/app-link"; import { PluginRepoLink } from "./plugin-repo-link"; import { PluginStatusBadge } from "./plugin-status-badge"; import { PluginErrorDiagnostic } from "./plugin-error-diagnostic"; import type { MarketplaceEntry, PluginRecord } from "@/lib/types/plugins"; type PluginRowProps = { plugin: PluginRecord; busy: boolean; /** Set when the marketplace has a newer version than the installed one. */ update?: MarketplaceEntry; /** The instance-wide auto-update default, used when the plugin has no override. */ autoUpdateDefault: boolean; /** True while this row's auto-update override request is in flight. */ autoUpdateBusy: boolean; onEnable: (plugin: PluginRecord) => void; onDisable: (plugin: PluginRecord) => void; onUninstall: (plugin: PluginRecord) => void; onUpdate?: (entry: MarketplaceEntry) => void; onSetAutoUpdate: (plugin: PluginRecord, value: boolean | null) => void; }; /** * One plugin's row. Div-based (not a ``) so it wraps/stacks naturally * on narrow viewports and inside the mobile settings sheet — no separate * mobile layout needed. */ export function PluginRow({ plugin, busy, update, autoUpdateDefault, autoUpdateBusy, onEnable, onDisable, onUninstall, onUpdate, onSetAutoUpdate, }: PluginRowProps) { const canEnable = plugin.status === "disabled" || plugin.status === "registered" || plugin.status === "error"; const canDisable = plugin.status === "active" || plugin.status === "error"; return (
{plugin.display_name} {plugin.signed === false && ( unsigned )}
{plugin.id} · v{plugin.version}
{plugin.description && (
{plugin.description}
)} {plugin.categories.length > 0 && (
{plugin.categories.map((category) => ( {category} ))}
)}
); } /** * The per-plugin auto-update control. The switch reflects the effective state * (the plugin's own override, or the instance-wide default when it has none); * toggling it sets an explicit override. Once overridden, a "Reset" affordance * clears the override so the plugin follows the global default again. */ function PluginAutoUpdateRow({ plugin, autoUpdateDefault, busy, onSetAutoUpdate, }: { plugin: PluginRecord; autoUpdateDefault: boolean; busy: boolean; onSetAutoUpdate: (plugin: PluginRecord, value: boolean | null) => void; }) { const isOverridden = plugin.auto_update !== null && plugin.auto_update !== undefined; const effective = isOverridden ? (plugin.auto_update as boolean) : autoUpdateDefault; return (
Auto-update {isOverridden && ( override )}
{isOverridden && ( )} onSetAutoUpdate(plugin, value)} className="cursor-pointer" />
); } type PluginRowActionsProps = Omit< PluginRowProps, | "onEnable" | "onDisable" | "onUninstall" | "autoUpdateDefault" | "autoUpdateBusy" | "onSetAutoUpdate" > & { canEnable: boolean; canDisable: boolean; onEnable: (plugin: PluginRecord) => void; onDisable: (plugin: PluginRecord) => void; onUninstall: (plugin: PluginRecord) => void; }; function PluginRowActions({ plugin, busy, update, canEnable, canDisable, onEnable, onDisable, onUninstall, onUpdate, }: PluginRowActionsProps) { return (
{update && onUpdate && ( )} {canEnable && ( )} {canDisable && ( )}
); }