[FRONT COMPONENTS] Add snackbar to the API (#18136)
## PR description - Adds `enqueueSnackbar` to the front component host communication API, allowing front components to display snack bar notifications (success, error, info, warning) to the user. - Introduces `frontComponentId` to the `FrontComponentExecutionContext` and a `useFrontComponentId` hook, enabling front components to identify themselves (used for dedupe keys). - Wires error/success notifications into the `Action`, `ActionLink`, and `ActionOpenSidePanelPage` SDK components -> actions now catch errors and display them as snack bars, and `Action` supports an optional `notifyOnEnd` prop for success feedback. ## Video QA ### Success example https://github.com/user-attachments/assets/8cc53d31-d9eb-49a8-9220-f7866ec1b415 ### Error example https://github.com/user-attachments/assets/a37d65b8-0b5f-4adb-bcdb-571bb2b997c3 --------- Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
@@ -23,6 +23,8 @@ jobs:
|
||||
if: needs.changed-files-check.outputs.any_changed == 'true'
|
||||
timeout-minutes: 30
|
||||
runs-on: depot-ubuntu-24.04
|
||||
env:
|
||||
NODE_OPTIONS: '--max-old-space-size=4096'
|
||||
strategy:
|
||||
matrix:
|
||||
task: [lint, typecheck, test]
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ export const ActionListItem = ({
|
||||
action,
|
||||
onClick,
|
||||
to,
|
||||
disabled,
|
||||
disabled = false,
|
||||
}: {
|
||||
action: ActionDisplayProps;
|
||||
onClick?: () => void;
|
||||
|
||||
+43
-1
@@ -3,14 +3,16 @@ import {
|
||||
type FrontComponentExecutionContext,
|
||||
type FrontComponentHostCommunicationApi,
|
||||
} from 'twenty-sdk/front-component-renderer';
|
||||
import { type AppPath } from 'twenty-shared/types';
|
||||
import { type AppPath, type EnqueueSnackbarParams } from 'twenty-shared/types';
|
||||
|
||||
import { currentUserState } from '@/auth/states/currentUserState';
|
||||
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
|
||||
import { useNavigateCommandMenu } from '@/command-menu/hooks/useNavigateCommandMenu';
|
||||
import { commandMenuSearchState } from '@/command-menu/states/commandMenuSearchState';
|
||||
import { useUnmountHeadlessFrontComponent } from '@/front-components/hooks/useUnmountHeadlessFrontComponent';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
|
||||
import { assertUnreachable } from 'twenty-shared/utils';
|
||||
import { useIcons } from 'twenty-ui/display';
|
||||
import { useNavigateApp } from '~/hooks/useNavigateApp';
|
||||
|
||||
@@ -28,6 +30,12 @@ export const useFrontComponentExecutionContext = ({
|
||||
const setCommandMenuSearchState = useSetRecoilState(commandMenuSearchState);
|
||||
const { getIcon } = useIcons();
|
||||
const unmountHeadlessFrontComponent = useUnmountHeadlessFrontComponent();
|
||||
const {
|
||||
enqueueSuccessSnackBar,
|
||||
enqueueErrorSnackBar,
|
||||
enqueueInfoSnackBar,
|
||||
enqueueWarningSnackBar,
|
||||
} = useSnackBar();
|
||||
const { closeCommandMenu } = useCommandMenu();
|
||||
|
||||
const navigate: FrontComponentHostCommunicationApi['navigate'] = async (
|
||||
@@ -57,7 +65,40 @@ export const useFrontComponentExecutionContext = ({
|
||||
}
|
||||
};
|
||||
|
||||
const enqueueSnackbar: FrontComponentHostCommunicationApi['enqueueSnackbar'] =
|
||||
async ({
|
||||
message,
|
||||
variant,
|
||||
duration,
|
||||
detailedMessage,
|
||||
dedupeKey,
|
||||
}: EnqueueSnackbarParams) => {
|
||||
const snackBarOptions = {
|
||||
duration,
|
||||
detailedMessage,
|
||||
dedupeKey,
|
||||
};
|
||||
|
||||
switch (variant) {
|
||||
case 'error':
|
||||
enqueueErrorSnackBar({ message, options: snackBarOptions });
|
||||
break;
|
||||
case 'info':
|
||||
enqueueInfoSnackBar({ message, options: snackBarOptions });
|
||||
break;
|
||||
case 'warning':
|
||||
enqueueWarningSnackBar({ message, options: snackBarOptions });
|
||||
break;
|
||||
case 'success':
|
||||
enqueueSuccessSnackBar({ message, options: snackBarOptions });
|
||||
break;
|
||||
default:
|
||||
assertUnreachable(variant);
|
||||
}
|
||||
};
|
||||
|
||||
const executionContext: FrontComponentExecutionContext = {
|
||||
frontComponentId,
|
||||
userId: currentUser?.id ?? null,
|
||||
};
|
||||
|
||||
@@ -75,6 +116,7 @@ export const useFrontComponentExecutionContext = ({
|
||||
{
|
||||
navigate,
|
||||
openSidePanelPage,
|
||||
enqueueSnackbar,
|
||||
unmountFrontComponent,
|
||||
closeSidePanel,
|
||||
};
|
||||
|
||||
+7
-2
@@ -1,9 +1,9 @@
|
||||
import { FrontComponentErrorEffect } from '@/front-component-renderer/remote/components/FrontComponentErrorEffect';
|
||||
import { FrontComponentHostCommunicationApiEffect } from '@/front-component-renderer/remote/components/FrontComponentHostCommunicationApiEffect';
|
||||
import { FrontComponentUpdateContextEffect } from '@/front-component-renderer/remote/components/FrontComponentUpdateContextEffect';
|
||||
import { type FrontComponentExecutionContext } from '@/front-component-renderer/types/FrontComponentExecutionContext';
|
||||
import { type FrontComponentHostCommunicationApi } from '@/front-component-renderer/types/FrontComponentHostCommunicationApi';
|
||||
import { type WorkerExports } from '@/front-component-renderer/types/WorkerExports';
|
||||
import { type FrontComponentExecutionContext } from '@/sdk/front-component-api';
|
||||
import { type ThreadWebWorker } from '@quilted/threads';
|
||||
import {
|
||||
type RemoteReceiver,
|
||||
@@ -42,6 +42,8 @@ export const FrontComponentRenderer = ({
|
||||
FrontComponentHostCommunicationApi
|
||||
> | null>(null);
|
||||
const [error, setError] = useState<Error | null>(null);
|
||||
const [isExecutionContextInitialized, setIsExecutionContextInitialized] =
|
||||
useState(false);
|
||||
|
||||
const MemoizedFrontComponentWorkerEffect = useMemo(() => {
|
||||
return (
|
||||
@@ -98,11 +100,14 @@ export const FrontComponentRenderer = ({
|
||||
<FrontComponentUpdateContextEffect
|
||||
thread={thread}
|
||||
executionContext={executionContext}
|
||||
onExecutionContextInitialized={() =>
|
||||
setIsExecutionContextInitialized(true)
|
||||
}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{isDefined(receiver) && (
|
||||
{isDefined(receiver) && isExecutionContextInitialized && (
|
||||
<ThemeProvider theme={theme}>
|
||||
<RemoteRootRenderer
|
||||
receiver={receiver}
|
||||
|
||||
@@ -112,7 +112,7 @@ export type {
|
||||
HtmlThProperties,
|
||||
} from './remote/generated/remote-elements';
|
||||
export { createRemoteWorker } from './remote/worker/utils/createRemoteWorker';
|
||||
export type { FrontComponentExecutionContext } from './types/FrontComponentExecutionContext';
|
||||
export type { FrontComponentExecutionContext } from '../sdk/front-component-api';
|
||||
export type { FrontComponentHostCommunicationApi } from './types/FrontComponentHostCommunicationApi';
|
||||
export type { HostToWorkerRenderContext } from './types/HostToWorkerRenderContext';
|
||||
export type { PropertySchema } from './types/PropertySchema';
|
||||
|
||||
+10
-3
@@ -1,21 +1,28 @@
|
||||
import { type FrontComponentExecutionContext } from '@/front-component-renderer/types/FrontComponentExecutionContext';
|
||||
import { type FrontComponentHostCommunicationApi } from '@/front-component-renderer/types/FrontComponentHostCommunicationApi';
|
||||
import { type WorkerExports } from '@/front-component-renderer/types/WorkerExports';
|
||||
import { type FrontComponentExecutionContext } from '@/sdk/front-component-api';
|
||||
import { type ThreadWebWorker } from '@quilted/threads';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
type FrontComponentUpdateContextEffectProps = {
|
||||
thread: ThreadWebWorker<WorkerExports, FrontComponentHostCommunicationApi>;
|
||||
executionContext: FrontComponentExecutionContext;
|
||||
onExecutionContextInitialized: () => void;
|
||||
};
|
||||
|
||||
export const FrontComponentUpdateContextEffect = ({
|
||||
thread,
|
||||
executionContext,
|
||||
onExecutionContextInitialized,
|
||||
}: FrontComponentUpdateContextEffectProps) => {
|
||||
useEffect(() => {
|
||||
thread.imports.updateContext(executionContext).catch(() => {});
|
||||
}, [executionContext, thread]);
|
||||
const updateContext = async () => {
|
||||
await thread.imports.updateContext(executionContext).catch(() => {});
|
||||
onExecutionContextInitialized();
|
||||
};
|
||||
|
||||
updateContext();
|
||||
}, [executionContext, onExecutionContextInitialized, thread]);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
@@ -19,7 +19,7 @@ import { HTML_TAG_TO_CUSTOM_ELEMENT_TAG } from '@/sdk/front-component-api/consta
|
||||
import { setFrontComponentExecutionContext } from '@/sdk/front-component-api/context/frontComponentContext';
|
||||
import { frontComponentHostCommunicationApi } from '@/sdk/front-component-api/globals/frontComponentHostCommunicationApi';
|
||||
|
||||
import { type FrontComponentExecutionContext } from '../../types/FrontComponentExecutionContext';
|
||||
import { type FrontComponentExecutionContext } from '@/sdk/front-component-api';
|
||||
import { type FrontComponentHostCommunicationApi } from '../../types/FrontComponentHostCommunicationApi';
|
||||
import { type HostToWorkerRenderContext } from '../../types/HostToWorkerRenderContext';
|
||||
import { type WorkerExports } from '../../types/WorkerExports';
|
||||
@@ -95,6 +95,8 @@ const initializeHostCommunicationApi: WorkerExports['initializeHostCommunication
|
||||
hostApi.openSidePanelPage;
|
||||
frontComponentHostCommunicationApi.unmountFrontComponent =
|
||||
hostApi.unmountFrontComponent;
|
||||
frontComponentHostCommunicationApi.enqueueSnackbar =
|
||||
hostApi.enqueueSnackbar;
|
||||
frontComponentHostCommunicationApi.closeSidePanel = hostApi.closeSidePanel;
|
||||
};
|
||||
|
||||
|
||||
+1
@@ -1,3 +1,4 @@
|
||||
export type FrontComponentExecutionContext = {
|
||||
frontComponentId: string;
|
||||
userId: string | null;
|
||||
};
|
||||
|
||||
+2
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
type CloseSidePanelFunction,
|
||||
type EnqueueSnackbarFunction,
|
||||
type NavigateFunction,
|
||||
type OpenSidePanelPageFunction,
|
||||
type UnmountFrontComponentFunction,
|
||||
@@ -9,5 +10,6 @@ export type FrontComponentHostCommunicationApi = {
|
||||
navigate: NavigateFunction;
|
||||
openSidePanelPage: OpenSidePanelPageFunction;
|
||||
unmountFrontComponent: UnmountFrontComponentFunction;
|
||||
enqueueSnackbar: EnqueueSnackbarFunction;
|
||||
closeSidePanel: CloseSidePanelFunction;
|
||||
};
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { unmountFrontComponent } from '../front-component-api';
|
||||
import {
|
||||
enqueueSnackbar,
|
||||
getFrontComponentActionErrorDedupeKey,
|
||||
unmountFrontComponent,
|
||||
useFrontComponentId,
|
||||
} from '../front-component-api';
|
||||
|
||||
export type ActionProps = {
|
||||
execute: () => void | Promise<void>;
|
||||
@@ -9,6 +14,8 @@ export type ActionProps = {
|
||||
export const Action = ({ execute }: ActionProps) => {
|
||||
const [hasExecuted, setHasExecuted] = useState(false);
|
||||
|
||||
const frontComponentId = useFrontComponentId();
|
||||
|
||||
useEffect(() => {
|
||||
if (hasExecuted) {
|
||||
return;
|
||||
@@ -19,13 +26,22 @@ export const Action = ({ execute }: ActionProps) => {
|
||||
const run = async () => {
|
||||
try {
|
||||
await execute();
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
await enqueueSnackbar({
|
||||
message: 'Action failed',
|
||||
detailedMessage: error.message,
|
||||
variant: 'error',
|
||||
dedupeKey: getFrontComponentActionErrorDedupeKey(frontComponentId),
|
||||
});
|
||||
}
|
||||
} finally {
|
||||
await unmountFrontComponent();
|
||||
}
|
||||
};
|
||||
|
||||
run();
|
||||
}, [execute, hasExecuted]);
|
||||
}, [execute, hasExecuted, frontComponentId]);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
@@ -3,7 +3,13 @@ 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 { navigate, unmountFrontComponent } from '../front-component-api';
|
||||
import {
|
||||
enqueueSnackbar,
|
||||
getFrontComponentActionErrorDedupeKey,
|
||||
navigate,
|
||||
unmountFrontComponent,
|
||||
useFrontComponentId,
|
||||
} from '../front-component-api';
|
||||
|
||||
export type ActionLinkProps<T extends AppPath> = {
|
||||
to: T;
|
||||
@@ -20,6 +26,8 @@ export const ActionLink = <T extends AppPath>({
|
||||
}: ActionLinkProps<T>) => {
|
||||
const [hasExecuted, setHasExecuted] = useState(false);
|
||||
|
||||
const frontComponentId = useFrontComponentId();
|
||||
|
||||
useEffect(() => {
|
||||
if (hasExecuted) {
|
||||
return;
|
||||
@@ -30,13 +38,22 @@ export const ActionLink = <T extends AppPath>({
|
||||
const run = async () => {
|
||||
try {
|
||||
await navigate(to, params, queryParams, options);
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
await enqueueSnackbar({
|
||||
message: 'Action failed',
|
||||
detailedMessage: error.message,
|
||||
variant: 'error',
|
||||
dedupeKey: getFrontComponentActionErrorDedupeKey(frontComponentId),
|
||||
});
|
||||
}
|
||||
} finally {
|
||||
await unmountFrontComponent();
|
||||
}
|
||||
};
|
||||
|
||||
run();
|
||||
}, [to, params, queryParams, options, hasExecuted]);
|
||||
}, [to, params, queryParams, options, hasExecuted, frontComponentId]);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import {
|
||||
enqueueSnackbar,
|
||||
getFrontComponentActionErrorDedupeKey,
|
||||
openSidePanelPage,
|
||||
unmountFrontComponent,
|
||||
useFrontComponentId,
|
||||
} from '@/sdk/front-component-api';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
@@ -23,6 +26,8 @@ export const ActionOpenSidePanelPage = ({
|
||||
}: ActionOpenSidePanelPageProps) => {
|
||||
const [hasExecuted, setHasExecuted] = useState(false);
|
||||
|
||||
const frontComponentId = useFrontComponentId();
|
||||
|
||||
useEffect(() => {
|
||||
if (hasExecuted) {
|
||||
return;
|
||||
@@ -40,13 +45,30 @@ export const ActionOpenSidePanelPage = ({
|
||||
pageIcon,
|
||||
shouldResetSearchState,
|
||||
});
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
await enqueueSnackbar({
|
||||
message: 'Action failed',
|
||||
detailedMessage: error.message,
|
||||
variant: 'error',
|
||||
dedupeKey: getFrontComponentActionErrorDedupeKey(frontComponentId),
|
||||
});
|
||||
}
|
||||
} finally {
|
||||
await unmountFrontComponent();
|
||||
}
|
||||
};
|
||||
|
||||
run();
|
||||
}, [page, pageTitle, pageIcon, shouldResetSearchState, onClick, hasExecuted]);
|
||||
}, [
|
||||
page,
|
||||
pageTitle,
|
||||
pageIcon,
|
||||
shouldResetSearchState,
|
||||
onClick,
|
||||
hasExecuted,
|
||||
frontComponentId,
|
||||
]);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
@@ -2,8 +2,6 @@ import { type FrontComponentExecutionContext } from '../types/FrontComponentExec
|
||||
|
||||
type Listener = () => void;
|
||||
|
||||
// State is stored on globalThis so the worker's SDK instance and each
|
||||
// front component's bundled SDK copy share the same backing store.
|
||||
const CONTEXT_KEY = '__twentySdkExecutionContext__';
|
||||
const LISTENERS_KEY = '__twentySdkContextListeners__';
|
||||
|
||||
@@ -28,13 +26,12 @@ export const setFrontComponentExecutionContext = (
|
||||
}
|
||||
};
|
||||
|
||||
export const getFrontComponentExecutionContext = ():
|
||||
| FrontComponentExecutionContext
|
||||
| undefined => {
|
||||
return (globalThis as Record<string, unknown>)[CONTEXT_KEY] as
|
||||
| FrontComponentExecutionContext
|
||||
| undefined;
|
||||
};
|
||||
export const getFrontComponentExecutionContext =
|
||||
(): FrontComponentExecutionContext => {
|
||||
return (globalThis as Record<string, unknown>)[
|
||||
CONTEXT_KEY
|
||||
] as FrontComponentExecutionContext;
|
||||
};
|
||||
|
||||
export const subscribeToFrontComponentExecutionContext = (
|
||||
listener: Listener,
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import { type EnqueueSnackbarParams } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import {
|
||||
frontComponentHostCommunicationApi,
|
||||
type EnqueueSnackbarFunction,
|
||||
} from '../globals/frontComponentHostCommunicationApi';
|
||||
|
||||
export const enqueueSnackbar: EnqueueSnackbarFunction = (
|
||||
params: EnqueueSnackbarParams,
|
||||
) => {
|
||||
const enqueueSnackbarFunction =
|
||||
frontComponentHostCommunicationApi.enqueueSnackbar;
|
||||
|
||||
if (!isDefined(enqueueSnackbarFunction)) {
|
||||
throw new Error('enqueueSnackbarFunction is not set');
|
||||
}
|
||||
|
||||
return enqueueSnackbarFunction(params);
|
||||
};
|
||||
+6
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
type AppPath,
|
||||
type CommandMenuPages,
|
||||
type EnqueueSnackbarParams,
|
||||
type NavigateOptions,
|
||||
} from 'twenty-shared/types';
|
||||
import { type getAppPath } from 'twenty-shared/utils';
|
||||
@@ -21,12 +22,17 @@ export type OpenSidePanelPageFunction = (params: {
|
||||
|
||||
export type UnmountFrontComponentFunction = () => Promise<void>;
|
||||
|
||||
export type EnqueueSnackbarFunction = (
|
||||
params: EnqueueSnackbarParams,
|
||||
) => Promise<void>;
|
||||
|
||||
export type CloseSidePanelFunction = () => Promise<void>;
|
||||
|
||||
export type FrontComponentHostCommunicationApiStore = {
|
||||
navigate?: NavigateFunction;
|
||||
openSidePanelPage?: OpenSidePanelPageFunction;
|
||||
unmountFrontComponent?: UnmountFrontComponentFunction;
|
||||
enqueueSnackbar?: EnqueueSnackbarFunction;
|
||||
closeSidePanel?: CloseSidePanelFunction;
|
||||
};
|
||||
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@ import {
|
||||
import { type FrontComponentExecutionContext } from '../types/FrontComponentExecutionContext';
|
||||
|
||||
export const useFrontComponentExecutionContext = <T>(
|
||||
selector: (context: FrontComponentExecutionContext | undefined) => T,
|
||||
selector: (context: FrontComponentExecutionContext) => T,
|
||||
): T => {
|
||||
const [currentSelectedValue, setCurrentSelectedValue] = useState(() =>
|
||||
selector(getFrontComponentExecutionContext()),
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
import { type FrontComponentExecutionContext } from '../types/FrontComponentExecutionContext';
|
||||
import { useFrontComponentExecutionContext } from './useFrontComponentExecutionContext';
|
||||
|
||||
const selectFrontComponentId = (
|
||||
context: FrontComponentExecutionContext,
|
||||
): string => context.frontComponentId;
|
||||
|
||||
export const useFrontComponentId = (): string => {
|
||||
return useFrontComponentExecutionContext(selectFrontComponentId);
|
||||
};
|
||||
@@ -1,10 +1,9 @@
|
||||
import { type FrontComponentExecutionContext } from '../types/FrontComponentExecutionContext';
|
||||
import { useFrontComponentExecutionContext } from './useFrontComponentExecutionContext';
|
||||
|
||||
const selectUserId = (
|
||||
context: FrontComponentExecutionContext | undefined,
|
||||
): string | null | undefined => context?.userId;
|
||||
const selectUserId = (context: FrontComponentExecutionContext): string | null =>
|
||||
context.userId;
|
||||
|
||||
export const useUserId = (): string | null | undefined => {
|
||||
export const useUserId = (): string | null => {
|
||||
return useFrontComponentExecutionContext(selectUserId);
|
||||
};
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
export { setFrontComponentExecutionContext } from './context/frontComponentContext';
|
||||
export { closeSidePanel } from './functions/closeSidePanel';
|
||||
export { enqueueSnackbar } from './functions/enqueueSnackbar';
|
||||
export { navigate } from './functions/navigate';
|
||||
export { openSidePanelPage } from './functions/openSidePanelPage';
|
||||
export { unmountFrontComponent } from './functions/unmountFrontComponent';
|
||||
export { useFrontComponentExecutionContext } from './hooks/useFrontComponentExecutionContext';
|
||||
export { useFrontComponentId } from './hooks/useFrontComponentId';
|
||||
export { useUserId } from './hooks/useUserId';
|
||||
export type { FrontComponentExecutionContext } from './types/FrontComponentExecutionContext';
|
||||
export { getFrontComponentActionErrorDedupeKey } from './utils/getFrontComponentActionErrorDedupeKey';
|
||||
|
||||
export { ALLOWED_HTML_ELEMENTS } from './constants/AllowedHtmlElements';
|
||||
export type { AllowedHtmlElement } from './constants/AllowedHtmlElements';
|
||||
|
||||
+1
@@ -1,3 +1,4 @@
|
||||
export type FrontComponentExecutionContext = {
|
||||
frontComponentId: string;
|
||||
userId: string | null;
|
||||
};
|
||||
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
export const getFrontComponentActionErrorDedupeKey = (
|
||||
frontComponentId: string,
|
||||
): string => `${frontComponentId}-action-error`;
|
||||
@@ -74,16 +74,23 @@ export type { ActionOpenSidePanelPageProps } from './action';
|
||||
|
||||
// Front Component API exports
|
||||
export {
|
||||
enqueueSnackbar,
|
||||
getFrontComponentActionErrorDedupeKey,
|
||||
closeSidePanel,
|
||||
navigate,
|
||||
openSidePanelPage,
|
||||
unmountFrontComponent,
|
||||
useFrontComponentExecutionContext,
|
||||
useFrontComponentId,
|
||||
useUserId,
|
||||
} from './front-component-api';
|
||||
export type { FrontComponentExecutionContext } from './front-component-api';
|
||||
|
||||
export { AppPath, CommandMenuPages } from 'twenty-shared/types';
|
||||
export type {
|
||||
EnqueueSnackbarParams,
|
||||
SnackBarVariant,
|
||||
} from 'twenty-shared/types';
|
||||
|
||||
// Front Component Common exports
|
||||
export {
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
export type SnackBarVariant = 'error' | 'success' | 'info' | 'warning';
|
||||
|
||||
export type EnqueueSnackbarParams = {
|
||||
message: string;
|
||||
variant: SnackBarVariant;
|
||||
duration?: number;
|
||||
detailedMessage?: string;
|
||||
dedupeKey?: string;
|
||||
};
|
||||
@@ -53,6 +53,10 @@ export type { CompositeFieldSubFieldName } from './CompositeFieldSubFieldNameTyp
|
||||
export type { ConfigVariableValue } from './ConfigVariableValue';
|
||||
export { ConnectedAccountProvider } from './ConnectedAccountProvider';
|
||||
export { CrudOperationType } from './CrudOperationType';
|
||||
export type {
|
||||
SnackBarVariant,
|
||||
EnqueueSnackbarParams,
|
||||
} from './EnqueueSnackbarParams';
|
||||
export type { EnumFieldMetadataType } from './EnumFieldMetadataType';
|
||||
export { EventLogTable } from './EventLogTable';
|
||||
export type { ExcludeFunctions } from './ExcludeFunctions';
|
||||
|
||||
Reference in New Issue
Block a user