// ======================================== // HookCard Component // ======================================== // Hook card with event badge, scope badge and action menu import * as React from 'react'; import { useIntl } from 'react-intl'; import { cn } from '@/lib/utils'; import { Card, CardContent } from '@/components/ui/Card'; import { Badge } from '@/components/ui/Badge'; import { Button } from '@/components/ui/Button'; import { Switch } from '@/components/ui/Switch'; import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, } from '@/components/ui/Dropdown'; import { Zap, MoreVertical, Edit, Trash2, Globe, Folder, Play, Clock, Terminal, } from 'lucide-react'; /** * Hook event types */ export type HookEvent = 'SessionStart' | 'UserPromptSubmit' | 'SessionEnd'; /** * Hook scope types */ export type HookScope = 'global' | 'project'; /** * Fail mode for hooks */ export type HookFailMode = 'continue' | 'block' | 'warn'; /** * Hook configuration interface */ export interface HookConfig { /** Unique hook identifier */ id: string; /** Hook name */ name: string; /** Trigger event */ event: HookEvent; /** Command to execute */ command: string; /** Description */ description?: string; /** Scope (global or project) */ scope: HookScope; /** Whether hook is enabled */ enabled: boolean; /** Whether hook is installed */ installed?: boolean; /** Whether this is a recommended hook */ isRecommended?: boolean; /** Timeout in milliseconds */ timeout?: number; /** Fail mode */ failMode?: HookFailMode; } export interface HookCardProps { /** Hook data */ hook: HookConfig; /** Called when edit action is triggered */ onEdit?: (hook: HookConfig) => void; /** Called when uninstall action is triggered */ onUninstall?: (hookId: string) => void; /** Called when toggle enabled is triggered */ onToggle?: (hookId: string, enabled: boolean) => void; /** Optional className */ className?: string; /** Show actions dropdown */ showActions?: boolean; /** Disabled state for actions */ actionsDisabled?: boolean; /** Whether this is a recommended hook card */ isRecommendedCard?: boolean; /** Called when install action is triggered (recommended hooks only) */ onInstall?: (hookId: string) => void; } // Event type configuration const eventConfig: Record< HookEvent, { variant: 'default' | 'secondary' | 'destructive' | 'success' | 'warning' | 'info'; icon: React.ReactNode } > = { SessionStart: { variant: 'success' as const, icon: }, UserPromptSubmit: { variant: 'info' as const, icon: }, SessionEnd: { variant: 'secondary' as const, icon: }, }; // Event label keys for i18n const eventLabelKeys: Record = { SessionStart: 'specs.hook.event.SessionStart', UserPromptSubmit: 'specs.hook.event.UserPromptSubmit', SessionEnd: 'specs.hook.event.SessionEnd', }; // Scope label keys for i18n const scopeLabelKeys: Record = { global: 'specs.hook.scope.global', project: 'specs.hook.scope.project', }; /** * HookCard component for displaying hook information */ export function HookCard({ hook, onEdit, onUninstall, onToggle, className, showActions = true, actionsDisabled = false, isRecommendedCard = false, onInstall, }: HookCardProps) { const { formatMessage } = useIntl(); const { variant: eventVariant, icon: eventIcon } = eventConfig[hook.event] || { variant: 'default' as const, icon: , }; const eventLabel = formatMessage({ id: eventLabelKeys[hook.event] || 'specs.hook.event.SessionStart' }); const scopeIcon = hook.scope === 'global' ? : ; const scopeLabel = formatMessage({ id: scopeLabelKeys[hook.scope] }); const handleToggle = (enabled: boolean) => { onToggle?.(hook.id, enabled); }; const handleAction = (e: React.MouseEvent, action: 'edit' | 'uninstall' | 'install') => { e.stopPropagation(); switch (action) { case 'edit': onEdit?.(hook); break; case 'uninstall': onUninstall?.(hook.id); break; case 'install': onInstall?.(hook.id); break; } }; // For recommended hooks that are not installed if (isRecommendedCard && !hook.installed) { return ( {hook.name} {scopeIcon} {scopeLabel} {hook.description && ( {hook.description} )} handleAction(e, 'install')} disabled={actionsDisabled} className="ml-4" > {formatMessage({ id: 'specs.hook.install' })} ); } return ( {/* Header */} {hook.name} {scopeIcon} {hook.description && ( {hook.description} )} {eventIcon} {eventLabel} {showActions && ( e.stopPropagation()} disabled={actionsDisabled} > {formatMessage({ id: 'common.aria.actions' })} handleAction(e, 'edit')}> {formatMessage({ id: 'specs.hook.edit' })} handleAction(e, 'uninstall')} className="text-destructive focus:text-destructive" > {formatMessage({ id: 'specs.hook.uninstall' })} )} {/* Command info */} {hook.command} {hook.timeout && ( {hook.timeout}ms )} ); } /** * Skeleton loader for HookCard */ export function HookCardSkeleton({ className }: { className?: string }) { return ( ); }
{hook.description}