Files
twenty/packages/twenty-front/src/modules/command-menu-item/confirmation-modal/components/CommandMenuConfirmationModalManager.tsx
T
Raphaël Bosi abdab2fb7e [Command menu items] Create engine commands (#18681)
## Description

- Introduces a new engine command execution model that replaces the
previous approach of mapping `EngineComponentKey` to React components.
Instead, engine commands are now mounted headlessly via
`HeadlessEngineCommandMountRoot`, with their execution context populated
synchronously before mounting.
- Creates new headless command components
- Moves error handling from the SDK layer to the host app by wrapping
all mounted commands with a new `CommandMenuItemErrorBoundary`

The new flow works as follows:
- When a command menu item with an `engineComponentKey` is clicked,
`useCommandMenuItemFrontComponentCommands` calls
`useMountEngineCommand`, which synchronously reads the current context
store (object metadata, selected records, filters, view ID, etc.) and
writes a `MountedEngineCommandContext` into
`mountedEngineCommandsState`.
- The command is then mounted into `mountedEngineCommandsState`, which
triggers `HeadlessEngineCommandMountRoot` to render the corresponding
headless component from `ENGINE_COMPONENT_KEY_HEADLESS_COMPONENT_MAP`,
wrapped in `CommandMenuItemErrorBoundary`,
`ContextStoreComponentInstanceContext.Provider`, and
`EngineCommandComponentInstanceContext.Provider`.
- Each command component reads its execution context and delegates to
one of the 4 execution patterns: `HeadlessEngineCommandWrapperEffect`
(simple actions), `HeadlessConfirmationModalEngineCommandEffect`
(destructive actions needing confirmation),
`HeadlessNavigateEngineCommand` (GO_TO_* commands), or
`HeadlessOpenSidePanelPageEngineCommand` (SEARCH_RECORDS, ASK_AI,
VIEW_PREVIOUS_AI_CHATS).
- After execution, the command self-unmounts via
`useUnmountEngineCommand`, which removes the entry from
`mountedEngineCommandsState` and stops rendering the component.
2026-03-17 17:25:18 +01:00

71 lines
2.8 KiB
TypeScript

import { COMMAND_MENU_CONFIRMATION_MODAL_INSTANCE_ID } from '@/command-menu-item/confirmation-modal/constants/CommandMenuItemConfirmationModalId';
import { COMMAND_MENU_CONFIRMATION_MODAL_RESULT_BROWSER_EVENT_NAME } from '@/command-menu-item/confirmation-modal/constants/CommandMenuItemConfirmationModalResultBrowserEventName';
import { commandMenuItemConfirmationModalConfigState } from '@/command-menu-item/confirmation-modal/states/commandMenuItemConfirmationModalState';
import {
type CommandMenuConfirmationModalResult,
type CommandMenuConfirmationModalResultBrowserEventDetail,
} from '@/command-menu-item/confirmation-modal/types/CommandMenuConfirmationModalResultBrowserEventDetail';
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
import { isModalOpenedComponentState } from '@/ui/layout/modal/states/isModalOpenedComponentState';
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState';
import { isDefined } from 'twenty-shared/utils';
export const CommandMenuConfirmationModalManager = () => {
const commandMenuItemConfirmationModalConfig = useAtomStateValue(
commandMenuItemConfirmationModalConfigState,
);
const isModalOpened = useAtomComponentStateValue(
isModalOpenedComponentState,
COMMAND_MENU_CONFIRMATION_MODAL_INSTANCE_ID,
);
const setCommandMenuItemConfirmationModalConfig = useSetAtomState(
commandMenuItemConfirmationModalConfigState,
);
const caller = commandMenuItemConfirmationModalConfig?.caller;
const emitConfirmationResult = (
confirmationResult: CommandMenuConfirmationModalResult,
) => {
if (!isDefined(caller)) {
return;
}
window.dispatchEvent(
new CustomEvent<CommandMenuConfirmationModalResultBrowserEventDetail>(
COMMAND_MENU_CONFIRMATION_MODAL_RESULT_BROWSER_EVENT_NAME,
{
detail: {
caller,
confirmationResult,
},
},
),
);
setCommandMenuItemConfirmationModalConfig(null);
};
if (!commandMenuItemConfirmationModalConfig || !isModalOpened) {
return null;
}
return (
<ConfirmationModal
modalInstanceId={COMMAND_MENU_CONFIRMATION_MODAL_INSTANCE_ID}
title={commandMenuItemConfirmationModalConfig.title}
subtitle={commandMenuItemConfirmationModalConfig.subtitle}
onConfirmClick={() => emitConfirmationResult('confirm')}
onClose={() => emitConfirmationResult('cancel')}
confirmButtonText={
commandMenuItemConfirmationModalConfig.confirmButtonText
}
confirmButtonAccent={
commandMenuItemConfirmationModalConfig.confirmButtonAccent
}
/>
);
};