"use client"; import { useEffect, useRef, useState } from "react"; import { useRouter } from "next/navigation"; import { Group, Stack, Text, TextInput, ThemeIcon, UnstyledButton } from "@mantine/core"; import { IconApps, IconBox, IconCode, IconLayoutDashboard, IconLayoutGridAdd, IconPlugConnected, IconSearch, } from "@tabler/icons-react"; import { useSession } from "@homarr/auth/client"; import type { GroupPermissionKey } from "@homarr/definitions"; import { createModal, useModalAction } from "@homarr/modals"; import { AddBoardModal } from "@homarr/modals-collection"; import { useScopedI18n } from "@homarr/translation/client"; import type { TablerIcon } from "@homarr/ui"; import classes from "./universal-create-modal.module.css"; import type { RankedUniversalCreateAction, UniversalCreateActionGroup, UniversalCreateActionKey, } from "./universal-create-actions"; import { filterAndRankUniversalCreateActions, getUniversalCreateActionDefinitions } from "./universal-create-actions"; import type { SetupMetricEntryPoint } from "./setup-analytics"; import { useSetupAnalytics } from "./setup-analytics"; type BoardActionKey = "widget" | "app" | "integration" | "container"; export interface UniversalCreateModalProps { boardActions?: Partial void>>; entryPoint?: SetupMetricEntryPoint; } const actionIcons = { widget: IconApps, app: IconBox, integration: IconPlugConnected, container: IconLayoutGridAdd, board: IconLayoutDashboard, workshop: IconSearch, customWidget: IconCode, } satisfies Record; export const UniversalCreateModal = createModal(({ actions, innerProps }) => { const { data: session } = useSession(); const router = useRouter(); const t = useScopedI18n("universalCreate"); const { openModal: openAddBoardModal } = useModalAction(AddBoardModal); const [query, setQuery] = useState(""); const trackedOpen = useRef(false); const boardActions = innerProps.boardActions; const entryPoint = innerProps.entryPoint ?? (boardActions ? "board" : "header"); const trackSetup = useSetupAnalytics(); const permissions = (session?.user.permissions ?? []) as GroupPermissionKey[]; const definitions = getUniversalCreateActionDefinitions({ hasBoardContext: boardActions !== undefined, permissions, }); useEffect(() => { if (trackedOpen.current) return; trackedOpen.current = true; trackSetup("surface-opened", { entryPoint, hasBoardContext: boardActions !== undefined, }); }, [boardActions, entryPoint, trackSetup]); const invokeAction = (key: UniversalCreateActionKey) => { const boardAction = key in (boardActions ?? {}) ? boardActions?.[key as BoardActionKey] : undefined; actions.closeModal(); trackSetup("intent-selected", { entryPoint, intent: key, outcome: "continued", hasBoardContext: boardActions !== undefined, }); if (boardAction) { boardAction(); return; } switch (key) { case "app": router.push("/manage/apps/new"); break; case "integration": router.push("/manage/integrations/new"); break; case "board": openAddBoardModal(undefined); break; case "workshop": router.push("/manage/workshop"); break; case "customWidget": router.push("/manage/custom-widgets/new"); break; case "widget": case "container": break; } }; const resolvedActions = definitions.map( (definition) => ({ ...definition, name: t(`action.${definition.key}.label`), description: t(`action.${definition.key}.description`), keywords: t(`action.${definition.key}.keywords`).split(" "), }) satisfies RankedUniversalCreateAction, ); const visibleActions = filterAndRankUniversalCreateActions(resolvedActions, query); const groups = ["currentBoard", "library", "boards"] satisfies UniversalCreateActionGroup[]; return ( setQuery(event.currentTarget.value)} leftSection={} placeholder={t("search.placeholder")} aria-label={t("search.label")} /> {visibleActions.length === 0 ? ( {t("search.noResults")} ) : ( groups.map((group) => { const groupActions = visibleActions.filter((action) => action.group === group); if (groupActions.length === 0) return null; return ( {t(`group.${group}`)} {groupActions.map((action) => { const Icon = actionIcons[action.key]; return ( invokeAction(action.key)}> {action.name} {action.description} ); })} ); }) )} ); }).withOptions({ defaultTitle: (t) => t("universalCreate.title"), size: "lg", });