"use client";
import { IconArrowUpCircle, IconChevronRight } from "@tabler/icons-react";
import { useTranslation } from "react-i18next";
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";
import { SETTINGS_TYPOGRAPHY } from "@/components/settings/settings-typography";
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;
/** True when the plugin declares required settings the operator has not filled in. */
needsSetup?: 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.
*
* The whole card opens the plugin's settings page, via an overlay link and a
* trailing chevron (the pattern the workspaces list already uses). The name
* alone used to carry the link, which was indistinguishable from a heading
* until hovered — and therefore invisible on touch, where nothing hovers.
* Every control in the card sits above the overlay at z-10 so it keeps its
* own click.
*/
export function PluginRow({
plugin,
busy,
update,
autoUpdateDefault,
autoUpdateBusy,
needsSetup = false,
onEnable,
onDisable,
onUninstall,
onUpdate,
onSetAutoUpdate,
}: PluginRowProps) {
const { t } = useTranslation();
const canEnable =
plugin.status === "disabled" || plugin.status === "registered" || plugin.status === "error";
const canDisable = plugin.status === "active" || plugin.status === "error";
return (
{/*
The spacing lives on this inner wrapper, not on the card. `space-y-*`
compiles to a margin on every child bar the last, and with the overlay
link as a card child that margin shortened it by one gap, leaving the
bottom strip of the card unclickable.
*/}
);
}
/**
* The row's name, state badges and identifiers. Everything here is inert and
* sits under the card's overlay link, except the repo link, which raises
* itself to z-10 to keep its own click.
*/
function PluginRowIdentity({ plugin, needsSetup }: { plugin: PluginRecord; needsSetup: boolean }) {
const { t } = useTranslation();
return (
);
}
/**
* 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 { t } = useTranslation();
const isOverridden = plugin.auto_update !== null && plugin.auto_update !== undefined;
const effective = isOverridden ? (plugin.auto_update as boolean) : autoUpdateDefault;
return (