import { useCallback, useEffect, useMemo } from "react";import { useNavigate } from "@tanstack/react-router";import { useAtomValue } from "@effect/atom-react";import * as AsyncResult from "effect/unstable/reactivity/AsyncResult";import { PlusIcon } from "lucide-react";import { trackEvent } from "../api/analytics";import type { Integration } from "@executor-js/sdk/shared";import { IntegrationFavicon, integrationPresetIconUrl } from "./integration-favicon";import { PresetIcon } from "./preset-icon";import { integrationsOptimisticAtom } from "../api/atoms";import { useIntegrationPlugins } from "@executor-js/sdk/client";import { useCanCreateWorkspaceConnections } from "../multiplayer/use-admin-nav";import { CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator, CommandShortcut,} from "./command"; // ---------------------------------------------------------------------------// CommandPalette — global ⌘K navigator.//// Order of entries:// 1. Connected integrations (priority, shown first)// 2. Add <Plugin> actions for each available integration plugin// 3. Popular integrations (plugin presets)// --------------------------------------------------------------------------- export function CommandPalette(props: { open: boolean; onOpenChange: (open: boolean) => void }) { const { open, onOpenChange } = props; const integrationPlugins = useIntegrationPlugins(); const navigate = useNavigate(); const integrationsResult = useAtomValue(integrationsOptimisticAtom); const canCreateIntegration = useCanCreateWorkspaceConnections(); // Toggle with ⌘K / Ctrl+K useEffect(() => { const onKeyDown = (e: KeyboardEvent) => { if (e.key === "k" && (e.metaKey || e.ctrlKey)) { e.preventDefault(); onOpenChange(!open); } }; document.addEventListener("keydown", onKeyDown); return () => document.removeEventListener("keydown", onKeyDown); }, [onOpenChange, open]); const connectedIntegrations = useMemo( () => AsyncResult.match(integrationsResult, { onInitial: () => [] as Array<{ id: string; name: string; kind: string; url?: string }>, onFailure: () => [] as Array<{ id: string; name: string; kind: string; url?: string }>, onSuccess: ({ value }) => value.map((integration: Integration) => ({ id: String(integration.slug), name: integration.name || String(integration.slug), kind: integration.kind, url: integration.displayUrl, })), }), [integrationsResult], ); const presetEntries = useMemo(() => { const entries: Array<{ pluginKey: string; pluginLabel: string; presetId: string; presetName: string; presetSummary?: string; presetUrl?: string; presetIcon?: string; presetFallbackIcon?: string; }> = []; for (const plugin of integrationPlugins) { for (const preset of plugin.presets ?? []) { entries.push({ pluginKey: plugin.key, pluginLabel: plugin.label, presetId: preset.id, presetName: preset.name, presetSummary: preset.summary, presetUrl: preset.url, presetIcon: preset.icon, presetFallbackIcon: preset.fallbackIcon, }); } } return entries; }, [integrationPlugins]); const close = useCallback(() => onOpenChange(false), [onOpenChange]); const goToIntegration = useCallback( (id: string) => { close(); trackEvent("command_palette_navigated", { kind: "integration", plugin_key: id }); void navigate({ to: "/{-$orgSlug}/integrations/$namespace", params: { namespace: id } }); }, [close, navigate], ); const goToAdd = useCallback( (pluginKey: string) => { close(); trackEvent("command_palette_navigated", { kind: "add_integration", plugin_key: pluginKey }); trackEvent("integration_add_started", { plugin_key: pluginKey, via: "command_palette" }); void navigate({ to: "/{-$orgSlug}/integrations/add/$pluginKey", params: { pluginKey }, }); }, [close, navigate], ); const goToPreset = useCallback( (pluginKey: string, presetId: string, presetUrl?: string) => { close(); trackEvent("command_palette_navigated", { kind: "preset", plugin_key: pluginKey }); trackEvent("integration_add_started", { plugin_key: pluginKey, via: "command_palette", preset_id: presetId, }); const search: Record<string, string> = { preset: presetId }; if (presetUrl) search.url = presetUrl; void navigate({ to: "/{-$orgSlug}/integrations/add/$pluginKey", params: { pluginKey }, search, }); }, [close, navigate], ); if (!open) return null; return ( <CommandDialog open={open} onOpenChange={onOpenChange}> <CommandInput placeholder="Search integrations or jump to add…" /> <CommandList> <CommandEmpty>No results found.</CommandEmpty> {connectedIntegrations.length > 0 && ( <CommandGroup heading="Connected"> {connectedIntegrations.map( (s: { readonly id: string; readonly name: string; readonly kind: string; readonly url?: string; }) => ( <CommandItem key={`integration-${s.id}`} value={`connected ${s.name} ${s.id} ${s.kind}`} onSelect={() => goToIntegration(s.id)} > <IntegrationFavicon icon={integrationPresetIconUrl(s, integrationPlugins)} url={s.url} /> <span className="flex-1 truncate">{s.name}</span> <CommandShortcut>{s.kind}</CommandShortcut> </CommandItem> ), )} </CommandGroup> )} {connectedIntegrations.length > 0 && integrationPlugins.length > 0 && <CommandSeparator />} {integrationPlugins.length > 0 && ( <CommandGroup heading="Add integration"> {integrationPlugins.map((plugin) => ( <CommandItem key={`add-${plugin.key}`} disabled={!canCreateIntegration} value={`add ${plugin.label} ${plugin.key}`} onSelect={() => goToAdd(plugin.key)} > <PlusIcon /> <span className="flex-1 truncate">Add {plugin.label}</span> {!canCreateIntegration && <CommandShortcut>Admin only</CommandShortcut>} </CommandItem> ))} </CommandGroup> )} {presetEntries.length > 0 && <CommandSeparator />} {presetEntries.length > 0 && ( <CommandGroup heading="Popular integrations"> {presetEntries.map((e) => ( <CommandItem key={`preset-${e.pluginKey}-${e.presetId}`} disabled={!canCreateIntegration} value={`preset ${e.presetName} ${e.presetSummary ?? ""} ${e.pluginLabel}`} onSelect={() => goToPreset(e.pluginKey, e.presetId, e.presetUrl)} > <PresetIcon {...(e.presetIcon ? { icon: e.presetIcon } : {})} {...(e.presetFallbackIcon ? { fallbackSrc: e.presetFallbackIcon } : {})} className="size-4 shrink-0 object-contain" fallback={ <span aria-hidden className="size-4 shrink-0 rounded-sm bg-muted-foreground/20" /> } /> <span className="flex-1 truncate">{e.presetName}</span> <CommandShortcut> {canCreateIntegration ? e.pluginLabel : "Admin only"} </CommandShortcut> </CommandItem> ))} </CommandGroup> )} </CommandList> </CommandDialog> );}
import { useCallback, useEffect, useMemo } from "react";
import { useNavigate } from "@tanstack/react-router";
import { useAtomValue } from "@effect/atom-react";
import * as AsyncResult from "effect/unstable/reactivity/AsyncResult";
import { PlusIcon } from "lucide-react";
import { trackEvent } from "../api/analytics";
import type { Integration } from "@executor-js/sdk/shared";
import { IntegrationFavicon, integrationPresetIconUrl } from "./integration-favicon";
import { PresetIcon } from "./preset-icon";
import { integrationsOptimisticAtom } from "../api/atoms";
import { useIntegrationPlugins } from "@executor-js/sdk/client";
import { useCanCreateWorkspaceConnections } from "../multiplayer/use-admin-nav";
import {
CommandDialog,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
CommandSeparator,
CommandShortcut,
} from "./command";
// ---------------------------------------------------------------------------
// CommandPalette — global ⌘K navigator.
//
// Order of entries:
// 1. Connected integrations (priority, shown first)
// 2. Add <Plugin> actions for each available integration plugin
// 3. Popular integrations (plugin presets)
// ---------------------------------------------------------------------------
export function CommandPalette(props: { open: boolean; onOpenChange: (open: boolean) => void }) {
const { open, onOpenChange } = props;
const integrationPlugins = useIntegrationPlugins();
const navigate = useNavigate();
const integrationsResult = useAtomValue(integrationsOptimisticAtom);
const canCreateIntegration = useCanCreateWorkspaceConnections();
// Toggle with ⌘K / Ctrl+K
useEffect(() => {
const onKeyDown = (e: KeyboardEvent) => {
if (e.key === "k" && (e.metaKey || e.ctrlKey)) {
e.preventDefault();
onOpenChange(!open);
}
};
document.addEventListener("keydown", onKeyDown);
return () => document.removeEventListener("keydown", onKeyDown);
}, [onOpenChange, open]);
const connectedIntegrations = useMemo(
() =>
AsyncResult.match(integrationsResult, {
onInitial: () => [] as Array<{ id: string; name: string; kind: string; url?: string }>,
onFailure: () => [] as Array<{ id: string; name: string; kind: string; url?: string }>,
onSuccess: ({ value }) =>
value.map((integration: Integration) => ({
id: String(integration.slug),
name: integration.name || String(integration.slug),
kind: integration.kind,
url: integration.displayUrl,
})),
}),
[integrationsResult],
);
const presetEntries = useMemo(() => {
const entries: Array<{
pluginKey: string;
pluginLabel: string;
presetId: string;
presetName: string;
presetSummary?: string;
presetUrl?: string;
presetIcon?: string;
presetFallbackIcon?: string;
}> = [];
for (const plugin of integrationPlugins) {
for (const preset of plugin.presets ?? []) {
entries.push({
pluginKey: plugin.key,
pluginLabel: plugin.label,
presetId: preset.id,
presetName: preset.name,
presetSummary: preset.summary,
presetUrl: preset.url,
presetIcon: preset.icon,
presetFallbackIcon: preset.fallbackIcon,
});
}
}
return entries;
}, [integrationPlugins]);
const close = useCallback(() => onOpenChange(false), [onOpenChange]);
const goToIntegration = useCallback(
(id: string) => {
close();
trackEvent("command_palette_navigated", { kind: "integration", plugin_key: id });
void navigate({ to: "/{-$orgSlug}/integrations/$namespace", params: { namespace: id } });
},
[close, navigate],
);
const goToAdd = useCallback(
(pluginKey: string) => {
close();
trackEvent("command_palette_navigated", { kind: "add_integration", plugin_key: pluginKey });
trackEvent("integration_add_started", { plugin_key: pluginKey, via: "command_palette" });
void navigate({
to: "/{-$orgSlug}/integrations/add/$pluginKey",
params: { pluginKey },
});
},
[close, navigate],
);
const goToPreset = useCallback(
(pluginKey: string, presetId: string, presetUrl?: string) => {
close();
trackEvent("command_palette_navigated", { kind: "preset", plugin_key: pluginKey });
trackEvent("integration_add_started", {
plugin_key: pluginKey,
via: "command_palette",
preset_id: presetId,
});
const search: Record<string, string> = { preset: presetId };
if (presetUrl) search.url = presetUrl;
void navigate({
to: "/{-$orgSlug}/integrations/add/$pluginKey",
params: { pluginKey },
search,
});
},
[close, navigate],
);
if (!open) return null;
return (
<CommandDialog open={open} onOpenChange={onOpenChange}>
<CommandInput placeholder="Search integrations or jump to add…" />
<CommandList>
<CommandEmpty>No results found.</CommandEmpty>
{connectedIntegrations.length > 0 && (
<CommandGroup heading="Connected">
{connectedIntegrations.map(
(s: {
readonly id: string;
readonly name: string;
readonly kind: string;
readonly url?: string;
}) => (
<CommandItem
key={`integration-${s.id}`}
value={`connected ${s.name} ${s.id} ${s.kind}`}
onSelect={() => goToIntegration(s.id)}
>
<IntegrationFavicon
icon={integrationPresetIconUrl(s, integrationPlugins)}
url={s.url}
/>
<span className="flex-1 truncate">{s.name}</span>
<CommandShortcut>{s.kind}</CommandShortcut>
</CommandItem>
),
)}
</CommandGroup>
)}
{connectedIntegrations.length > 0 && integrationPlugins.length > 0 && <CommandSeparator />}
{integrationPlugins.length > 0 && (
<CommandGroup heading="Add integration">
{integrationPlugins.map((plugin) => (
<CommandItem
key={`add-${plugin.key}`}
disabled={!canCreateIntegration}
value={`add ${plugin.label} ${plugin.key}`}
onSelect={() => goToAdd(plugin.key)}
>
<PlusIcon />
<span className="flex-1 truncate">Add {plugin.label}</span>
{!canCreateIntegration && <CommandShortcut>Admin only</CommandShortcut>}
</CommandItem>
))}
</CommandGroup>
)}
{presetEntries.length > 0 && <CommandSeparator />}
{presetEntries.length > 0 && (
<CommandGroup heading="Popular integrations">
{presetEntries.map((e) => (
<CommandItem
key={`preset-${e.pluginKey}-${e.presetId}`}
disabled={!canCreateIntegration}
value={`preset ${e.presetName} ${e.presetSummary ?? ""} ${e.pluginLabel}`}
onSelect={() => goToPreset(e.pluginKey, e.presetId, e.presetUrl)}
>
<PresetIcon
{...(e.presetIcon ? { icon: e.presetIcon } : {})}
{...(e.presetFallbackIcon ? { fallbackSrc: e.presetFallbackIcon } : {})}
className="size-4 shrink-0 object-contain"
fallback={
<span
aria-hidden
className="size-4 shrink-0 rounded-sm bg-muted-foreground/20"
/>
}
/>
<span className="flex-1 truncate">{e.presetName}</span>
<CommandShortcut>
{canCreateIntegration ? e.pluginLabel : "Admin only"}
</CommandShortcut>
</CommandItem>
))}
</CommandGroup>
)}
</CommandList>
</CommandDialog>
);
}