nitin
2026-03-05 15:43:46 +05:30
committed by GitHub
parent 0e89c96170
commit 4cfd738312
23 changed files with 429 additions and 15 deletions
@@ -0,0 +1,67 @@
import { ACTION_MENU_CONFIRMATION_MODAL_RESULT_BROWSER_EVENT_NAME } from '@/action-menu/confirmation-modal/constants/ActionMenuConfirmationModalResultBrowserEventName';
import { ACTION_MENU_CONFIRMATION_MODAL_INSTANCE_ID } from '@/action-menu/confirmation-modal/constants/ActionMenuConfirmationModalId';
import { actionMenuConfirmationModalConfigState } from '@/action-menu/confirmation-modal/states/actionMenuConfirmationModalState';
import {
type ActionMenuConfirmationModalResult,
type ActionMenuConfirmationModalResultBrowserEventDetail,
} from '@/action-menu/confirmation-modal/types/ActionMenuConfirmationModalResultBrowserEventDetail';
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';
export const ActionMenuConfirmationModalManager = () => {
const actionMenuConfirmationModalConfig = useAtomStateValue(
actionMenuConfirmationModalConfigState,
);
const isModalOpened = useAtomComponentStateValue(
isModalOpenedComponentState,
ACTION_MENU_CONFIRMATION_MODAL_INSTANCE_ID,
);
const setActionMenuConfirmationModalConfig = useSetAtomState(
actionMenuConfirmationModalConfigState,
);
const callerId = actionMenuConfirmationModalConfig?.frontComponentId;
const emitConfirmationResult = (
confirmationResult: ActionMenuConfirmationModalResult,
) => {
if (!callerId) {
return;
}
window.dispatchEvent(
new CustomEvent<ActionMenuConfirmationModalResultBrowserEventDetail>(
ACTION_MENU_CONFIRMATION_MODAL_RESULT_BROWSER_EVENT_NAME,
{
detail: {
frontComponentId: callerId,
confirmationResult,
},
},
),
);
setActionMenuConfirmationModalConfig(null);
};
if (!actionMenuConfirmationModalConfig || !isModalOpened) {
return null;
}
return (
<ConfirmationModal
modalInstanceId={ACTION_MENU_CONFIRMATION_MODAL_INSTANCE_ID}
title={actionMenuConfirmationModalConfig.title}
subtitle={actionMenuConfirmationModalConfig.subtitle}
onConfirmClick={() => emitConfirmationResult('confirm')}
onClose={() => emitConfirmationResult('cancel')}
confirmButtonText={actionMenuConfirmationModalConfig.confirmButtonText}
confirmButtonAccent={
actionMenuConfirmationModalConfig.confirmButtonAccent
}
/>
);
};
@@ -0,0 +1,2 @@
export const ACTION_MENU_CONFIRMATION_MODAL_INSTANCE_ID =
'action-menu-confirmation-modal';
@@ -0,0 +1,2 @@
export const ACTION_MENU_CONFIRMATION_MODAL_RESULT_BROWSER_EVENT_NAME =
'action-menu-confirmation-modal-result';
@@ -0,0 +1,48 @@
import { useStore } from 'jotai';
import { useCallback } from 'react';
import { ACTION_MENU_CONFIRMATION_MODAL_INSTANCE_ID } from '@/action-menu/confirmation-modal/constants/ActionMenuConfirmationModalId';
import {
type ActionMenuConfirmationModalConfig,
actionMenuConfirmationModalConfigState,
} from '@/action-menu/confirmation-modal/states/actionMenuConfirmationModalState';
import { useModal } from '@/ui/layout/modal/hooks/useModal';
import { isModalOpenedComponentState } from '@/ui/layout/modal/states/isModalOpenedComponentState';
import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState';
export const useActionMenuConfirmationModal = () => {
const store = useStore();
const setActionMenuConfirmationModalConfig = useSetAtomState(
actionMenuConfirmationModalConfigState,
);
const { openModal } = useModal();
const openConfirmationModal = useCallback(
(config: ActionMenuConfirmationModalConfig) => {
const existingActionMenuConfirmationModalConfig = store.get(
actionMenuConfirmationModalConfigState.atom,
);
const isActionMenuConfirmationModalOpened = store.get(
isModalOpenedComponentState.atomFamily({
instanceId: ACTION_MENU_CONFIRMATION_MODAL_INSTANCE_ID,
}),
);
if (
existingActionMenuConfirmationModalConfig !== null ||
isActionMenuConfirmationModalOpened
) {
throw new Error(
'Action menu confirmation modal is already active for another front component',
);
}
setActionMenuConfirmationModalConfig(config);
openModal(ACTION_MENU_CONFIRMATION_MODAL_INSTANCE_ID);
},
[store, setActionMenuConfirmationModalConfig, openModal],
);
return { openConfirmationModal };
};
@@ -0,0 +1,18 @@
import { type ReactNode } from 'react';
import { createAtomState } from '@/ui/utilities/state/jotai/utils/createAtomState';
import { type ButtonAccent } from 'twenty-ui/input';
export type ActionMenuConfirmationModalConfig = {
frontComponentId: string;
title: string;
subtitle: ReactNode;
confirmButtonText?: string;
confirmButtonAccent?: ButtonAccent;
};
export const actionMenuConfirmationModalConfigState =
createAtomState<ActionMenuConfirmationModalConfig | null>({
key: 'actionMenuConfirmationModalConfigState',
defaultValue: null,
});
@@ -0,0 +1,6 @@
export type ActionMenuConfirmationModalResult = 'confirm' | 'cancel';
export type ActionMenuConfirmationModalResultBrowserEventDetail = {
frontComponentId: string;
confirmationResult: ActionMenuConfirmationModalResult;
};
@@ -1,4 +1,5 @@
import { AgentChatProvider } from '@/ai/components/AgentChatProvider';
import { ActionMenuConfirmationModalManager } from '@/action-menu/confirmation-modal/components/ActionMenuConfirmationModalManager';
import { ApolloProvider } from '@/apollo/components/ApolloProvider';
import { MetadataGater } from '@/metadata-store/components/MetadataGater';
import { IsAppMetadataReadyEffect } from '@/metadata-store/effect-components/IsAppMetadataReadyEffect';
@@ -71,6 +72,7 @@ export const AppRouterProviders = () => {
<PageFavicon />
<Outlet />
<GlobalFilePreviewModal />
<ActionMenuConfirmationModalManager />
<HeadlessFrontComponentMountRoot />
</StrictMode>
</DialogManager>
@@ -5,6 +5,7 @@ import {
} from 'twenty-sdk/front-component-renderer';
import { type AppPath, type EnqueueSnackbarParams } from 'twenty-shared/types';
import { useActionMenuConfirmationModal } from '@/action-menu/confirmation-modal/hooks/useActionMenuConfirmationModal';
import { currentUserState } from '@/auth/states/currentUserState';
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { useNavigateCommandMenu } from '@/command-menu/hooks/useNavigateCommandMenu';
@@ -31,6 +32,7 @@ export const useFrontComponentExecutionContext = ({
const { requestAccessTokenRefresh } = useRequestApplicationTokenRefresh({
frontComponentId,
});
const { openConfirmationModal } = useActionMenuConfirmationModal();
const { navigateCommandMenu } = useNavigateCommandMenu();
const setCommandMenuSearch = useSetAtomState(commandMenuSearchState);
const { getIcon } = useIcons();
@@ -70,6 +72,17 @@ export const useFrontComponentExecutionContext = ({
}
};
const openActionConfirmationModal: FrontComponentHostCommunicationApi['openActionConfirmationModal'] =
async ({ title, subtitle, confirmButtonText, confirmButtonAccent }) => {
openConfirmationModal({
frontComponentId,
title,
subtitle,
confirmButtonText,
confirmButtonAccent,
});
};
const enqueueSnackbar: FrontComponentHostCommunicationApi['enqueueSnackbar'] =
async ({
message,
@@ -125,6 +138,7 @@ export const useFrontComponentExecutionContext = ({
navigate,
requestAccessTokenRefresh,
openSidePanelPage,
openActionConfirmationModal,
enqueueSnackbar,
unmountFrontComponent,
closeSidePanel,