refactor(command-menu-item): rename Actions to CommandMenuItem (#18489)

actions are being renamed to command menu item, they will be migrated to
server and will be served as headless front components

---------

Co-authored-by: Raphaël Bosi <71827178+bosiraphael@users.noreply.github.com>
This commit is contained in:
nitin
2026-03-09 19:33:12 +05:30
committed by GitHub
parent 06bdb5ad6a
commit 36bcc71f3d
275 changed files with 4544 additions and 4436 deletions
@@ -0,0 +1,47 @@
import { useEffect, useState } from 'react';
import {
enqueueSnackbar,
getFrontComponentCommandErrorDedupeKey,
unmountFrontComponent,
useFrontComponentId,
} from '../front-component-api';
export type CommandProps = {
execute: () => void | Promise<void>;
};
export const Command = ({ execute }: CommandProps) => {
const [hasExecuted, setHasExecuted] = useState(false);
const frontComponentId = useFrontComponentId();
useEffect(() => {
if (hasExecuted) {
return;
}
setHasExecuted(true);
const run = async () => {
try {
await execute();
} catch (error) {
if (error instanceof Error) {
await enqueueSnackbar({
message: 'Command failed',
detailedMessage: error.message,
variant: 'error',
dedupeKey: getFrontComponentCommandErrorDedupeKey(frontComponentId),
});
}
} finally {
await unmountFrontComponent();
}
};
run();
}, [execute, hasExecuted, frontComponentId]);
return null;
};
@@ -0,0 +1,59 @@
import { useEffect, useState } from 'react';
import { type NavigateOptions } from 'react-router-dom';
import { type AppPath } from 'twenty-shared/types';
import { type getAppPath } from 'twenty-shared/utils';
import {
enqueueSnackbar,
getFrontComponentCommandErrorDedupeKey,
navigate,
unmountFrontComponent,
useFrontComponentId,
} from '../front-component-api';
export type CommandLinkProps<T extends AppPath> = {
to: T;
params?: Parameters<typeof getAppPath<T>>[1];
queryParams?: Record<string, any>;
options?: NavigateOptions;
};
export const CommandLink = <T extends AppPath>({
to,
params,
queryParams,
options,
}: CommandLinkProps<T>) => {
const [hasExecuted, setHasExecuted] = useState(false);
const frontComponentId = useFrontComponentId();
useEffect(() => {
if (hasExecuted) {
return;
}
setHasExecuted(true);
const run = async () => {
try {
await navigate(to, params, queryParams, options);
} catch (error) {
if (error instanceof Error) {
await enqueueSnackbar({
message: 'Command failed',
detailedMessage: error.message,
variant: 'error',
dedupeKey: getFrontComponentCommandErrorDedupeKey(frontComponentId),
});
}
} finally {
await unmountFrontComponent();
}
};
run();
}, [to, params, queryParams, options, hasExecuted, frontComponentId]);
return null;
};
@@ -0,0 +1,77 @@
import { useEffect, useState } from 'react';
import {
enqueueSnackbar,
getFrontComponentCommandErrorDedupeKey,
openCommandConfirmationModal,
unmountFrontComponent,
useFrontComponentId,
type CommandConfirmationModalAccent,
} from '../front-component-api';
export type CommandModalProps = {
title: string;
subtitle: string;
execute: () => void | Promise<void>;
confirmButtonText?: string;
confirmButtonAccent?: CommandConfirmationModalAccent;
};
export const CommandModal = ({
title,
subtitle,
execute,
confirmButtonText,
confirmButtonAccent,
}: CommandModalProps) => {
const [hasExecuted, setHasExecuted] = useState(false);
const frontComponentId = useFrontComponentId();
useEffect(() => {
if (hasExecuted) {
return;
}
setHasExecuted(true);
const run = async () => {
try {
const commandConfirmationModalResult =
await openCommandConfirmationModal({
title,
subtitle,
confirmButtonText,
confirmButtonAccent,
});
if (commandConfirmationModalResult === 'confirm') {
await execute();
}
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
await enqueueSnackbar({
message: 'Command failed',
detailedMessage: message,
variant: 'error',
dedupeKey: getFrontComponentCommandErrorDedupeKey(frontComponentId),
});
} finally {
await unmountFrontComponent();
}
};
run();
}, [
title,
subtitle,
execute,
confirmButtonText,
confirmButtonAccent,
hasExecuted,
frontComponentId,
]);
return null;
};
@@ -0,0 +1,74 @@
import {
enqueueSnackbar,
getFrontComponentCommandErrorDedupeKey,
openSidePanelPage,
unmountFrontComponent,
useFrontComponentId,
} from '@/sdk/front-component-api';
import { useEffect, useState } from 'react';
import { type SidePanelPages } from 'twenty-shared/types';
export type CommandOpenSidePanelPageProps = {
page: SidePanelPages;
pageTitle: string;
pageIcon: string;
onClick?: () => void;
shouldResetSearchState?: boolean;
};
export const CommandOpenSidePanelPage = ({
page,
pageTitle,
pageIcon,
onClick,
shouldResetSearchState = false,
}: CommandOpenSidePanelPageProps) => {
const [hasExecuted, setHasExecuted] = useState(false);
const frontComponentId = useFrontComponentId();
useEffect(() => {
if (hasExecuted) {
return;
}
setHasExecuted(true);
const run = async () => {
onClick?.();
try {
await openSidePanelPage({
page,
pageTitle,
pageIcon,
shouldResetSearchState,
});
} catch (error) {
if (error instanceof Error) {
await enqueueSnackbar({
message: 'Command failed',
detailedMessage: error.message,
variant: 'error',
dedupeKey: getFrontComponentCommandErrorDedupeKey(frontComponentId),
});
}
} finally {
await unmountFrontComponent();
}
};
run();
}, [
page,
pageTitle,
pageIcon,
shouldResetSearchState,
onClick,
hasExecuted,
frontComponentId,
]);
return null;
};
@@ -0,0 +1,8 @@
export { Command } from './Command';
export type { CommandProps } from './Command';
export { CommandLink } from './CommandLink';
export type { CommandLinkProps } from './CommandLink';
export { CommandOpenSidePanelPage } from './CommandOpenSidePanelPage';
export type { CommandOpenSidePanelPageProps } from './CommandOpenSidePanelPage';
export { CommandModal } from './CommandModal';
export type { CommandModalProps } from './CommandModal';