From a0c0e7de251269087ef4f5090fe306258ae60d30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Bosi?= <71827178+bosiraphael@users.noreply.github.com> Date: Mon, 23 Feb 2026 12:25:05 +0100 Subject: [PATCH] [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 --- .github/workflows/ci-shared.yaml | 2 + .../display/components/ActionListItem.tsx | 2 +- .../useFrontComponentExecutionContext.ts | 44 ++++++++++++++++++- .../components/FrontComponentRenderer.tsx | 9 +++- .../src/front-component-renderer/index.ts | 2 +- .../FrontComponentUpdateContextEffect.tsx | 13 ++++-- .../remote/worker/remote-worker.ts | 4 +- .../types/FrontComponentExecutionContext.ts | 1 + .../FrontComponentHostCommunicationApi.ts | 2 + packages/twenty-sdk/src/sdk/action/Action.tsx | 20 ++++++++- .../twenty-sdk/src/sdk/action/ActionLink.tsx | 21 ++++++++- .../sdk/action/ActionOpenSidePanelPage.tsx | 24 +++++++++- .../context/frontComponentContext.ts | 15 +++---- .../functions/enqueueSnackbar.ts | 20 +++++++++ .../frontComponentHostCommunicationApi.ts | 6 +++ .../useFrontComponentExecutionContext.ts | 2 +- .../hooks/useFrontComponentId.ts | 10 +++++ .../front-component-api/hooks/useUserId.ts | 7 ++- .../src/sdk/front-component-api/index.ts | 3 ++ .../types/FrontComponentExecutionContext.ts | 1 + .../getFrontComponentActionErrorDedupeKey.ts | 3 ++ packages/twenty-sdk/src/sdk/index.ts | 7 +++ .../src/types/EnqueueSnackbarParams.ts | 9 ++++ packages/twenty-shared/src/types/index.ts | 4 ++ 24 files changed, 203 insertions(+), 28 deletions(-) create mode 100644 packages/twenty-sdk/src/sdk/front-component-api/functions/enqueueSnackbar.ts create mode 100644 packages/twenty-sdk/src/sdk/front-component-api/hooks/useFrontComponentId.ts create mode 100644 packages/twenty-sdk/src/sdk/front-component-api/utils/getFrontComponentActionErrorDedupeKey.ts create mode 100644 packages/twenty-shared/src/types/EnqueueSnackbarParams.ts diff --git a/.github/workflows/ci-shared.yaml b/.github/workflows/ci-shared.yaml index 8c2b625161..e147fbf6e5 100644 --- a/.github/workflows/ci-shared.yaml +++ b/.github/workflows/ci-shared.yaml @@ -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] diff --git a/packages/twenty-front/src/modules/action-menu/actions/display/components/ActionListItem.tsx b/packages/twenty-front/src/modules/action-menu/actions/display/components/ActionListItem.tsx index 15a8daca5b..d1327b60e0 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/display/components/ActionListItem.tsx +++ b/packages/twenty-front/src/modules/action-menu/actions/display/components/ActionListItem.tsx @@ -10,7 +10,7 @@ export const ActionListItem = ({ action, onClick, to, - disabled, + disabled = false, }: { action: ActionDisplayProps; onClick?: () => void; diff --git a/packages/twenty-front/src/modules/front-components/hooks/useFrontComponentExecutionContext.ts b/packages/twenty-front/src/modules/front-components/hooks/useFrontComponentExecutionContext.ts index ad9062ba97..783729c9be 100644 --- a/packages/twenty-front/src/modules/front-components/hooks/useFrontComponentExecutionContext.ts +++ b/packages/twenty-front/src/modules/front-components/hooks/useFrontComponentExecutionContext.ts @@ -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, }; diff --git a/packages/twenty-sdk/src/front-component-renderer/host/components/FrontComponentRenderer.tsx b/packages/twenty-sdk/src/front-component-renderer/host/components/FrontComponentRenderer.tsx index 4acab73495..e929f504ae 100644 --- a/packages/twenty-sdk/src/front-component-renderer/host/components/FrontComponentRenderer.tsx +++ b/packages/twenty-sdk/src/front-component-renderer/host/components/FrontComponentRenderer.tsx @@ -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(null); + const [isExecutionContextInitialized, setIsExecutionContextInitialized] = + useState(false); const MemoizedFrontComponentWorkerEffect = useMemo(() => { return ( @@ -98,11 +100,14 @@ export const FrontComponentRenderer = ({ + setIsExecutionContextInitialized(true) + } /> )} - {isDefined(receiver) && ( + {isDefined(receiver) && isExecutionContextInitialized && ( ; 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; }; diff --git a/packages/twenty-sdk/src/front-component-renderer/remote/worker/remote-worker.ts b/packages/twenty-sdk/src/front-component-renderer/remote/worker/remote-worker.ts index 00999486a3..a52f31064e 100644 --- a/packages/twenty-sdk/src/front-component-renderer/remote/worker/remote-worker.ts +++ b/packages/twenty-sdk/src/front-component-renderer/remote/worker/remote-worker.ts @@ -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; }; diff --git a/packages/twenty-sdk/src/front-component-renderer/types/FrontComponentExecutionContext.ts b/packages/twenty-sdk/src/front-component-renderer/types/FrontComponentExecutionContext.ts index 732a36147a..6de566865a 100644 --- a/packages/twenty-sdk/src/front-component-renderer/types/FrontComponentExecutionContext.ts +++ b/packages/twenty-sdk/src/front-component-renderer/types/FrontComponentExecutionContext.ts @@ -1,3 +1,4 @@ export type FrontComponentExecutionContext = { + frontComponentId: string; userId: string | null; }; diff --git a/packages/twenty-sdk/src/front-component-renderer/types/FrontComponentHostCommunicationApi.ts b/packages/twenty-sdk/src/front-component-renderer/types/FrontComponentHostCommunicationApi.ts index 20ff789d7c..e6013ab949 100644 --- a/packages/twenty-sdk/src/front-component-renderer/types/FrontComponentHostCommunicationApi.ts +++ b/packages/twenty-sdk/src/front-component-renderer/types/FrontComponentHostCommunicationApi.ts @@ -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; }; diff --git a/packages/twenty-sdk/src/sdk/action/Action.tsx b/packages/twenty-sdk/src/sdk/action/Action.tsx index 0d3242f4be..9e39e26536 100644 --- a/packages/twenty-sdk/src/sdk/action/Action.tsx +++ b/packages/twenty-sdk/src/sdk/action/Action.tsx @@ -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; @@ -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; }; diff --git a/packages/twenty-sdk/src/sdk/action/ActionLink.tsx b/packages/twenty-sdk/src/sdk/action/ActionLink.tsx index 454fd72bd8..5099274a58 100644 --- a/packages/twenty-sdk/src/sdk/action/ActionLink.tsx +++ b/packages/twenty-sdk/src/sdk/action/ActionLink.tsx @@ -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 = { to: T; @@ -20,6 +26,8 @@ export const ActionLink = ({ }: ActionLinkProps) => { const [hasExecuted, setHasExecuted] = useState(false); + const frontComponentId = useFrontComponentId(); + useEffect(() => { if (hasExecuted) { return; @@ -30,13 +38,22 @@ export const ActionLink = ({ 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; }; diff --git a/packages/twenty-sdk/src/sdk/action/ActionOpenSidePanelPage.tsx b/packages/twenty-sdk/src/sdk/action/ActionOpenSidePanelPage.tsx index bfbffb256b..3a8c8078a6 100644 --- a/packages/twenty-sdk/src/sdk/action/ActionOpenSidePanelPage.tsx +++ b/packages/twenty-sdk/src/sdk/action/ActionOpenSidePanelPage.tsx @@ -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; }; diff --git a/packages/twenty-sdk/src/sdk/front-component-api/context/frontComponentContext.ts b/packages/twenty-sdk/src/sdk/front-component-api/context/frontComponentContext.ts index 0b1117830d..de4528901d 100644 --- a/packages/twenty-sdk/src/sdk/front-component-api/context/frontComponentContext.ts +++ b/packages/twenty-sdk/src/sdk/front-component-api/context/frontComponentContext.ts @@ -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)[CONTEXT_KEY] as - | FrontComponentExecutionContext - | undefined; -}; +export const getFrontComponentExecutionContext = + (): FrontComponentExecutionContext => { + return (globalThis as Record)[ + CONTEXT_KEY + ] as FrontComponentExecutionContext; + }; export const subscribeToFrontComponentExecutionContext = ( listener: Listener, diff --git a/packages/twenty-sdk/src/sdk/front-component-api/functions/enqueueSnackbar.ts b/packages/twenty-sdk/src/sdk/front-component-api/functions/enqueueSnackbar.ts new file mode 100644 index 0000000000..02c4116088 --- /dev/null +++ b/packages/twenty-sdk/src/sdk/front-component-api/functions/enqueueSnackbar.ts @@ -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); +}; diff --git a/packages/twenty-sdk/src/sdk/front-component-api/globals/frontComponentHostCommunicationApi.ts b/packages/twenty-sdk/src/sdk/front-component-api/globals/frontComponentHostCommunicationApi.ts index b590ecd6d1..d9e5f48f0c 100644 --- a/packages/twenty-sdk/src/sdk/front-component-api/globals/frontComponentHostCommunicationApi.ts +++ b/packages/twenty-sdk/src/sdk/front-component-api/globals/frontComponentHostCommunicationApi.ts @@ -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; +export type EnqueueSnackbarFunction = ( + params: EnqueueSnackbarParams, +) => Promise; + export type CloseSidePanelFunction = () => Promise; export type FrontComponentHostCommunicationApiStore = { navigate?: NavigateFunction; openSidePanelPage?: OpenSidePanelPageFunction; unmountFrontComponent?: UnmountFrontComponentFunction; + enqueueSnackbar?: EnqueueSnackbarFunction; closeSidePanel?: CloseSidePanelFunction; }; diff --git a/packages/twenty-sdk/src/sdk/front-component-api/hooks/useFrontComponentExecutionContext.ts b/packages/twenty-sdk/src/sdk/front-component-api/hooks/useFrontComponentExecutionContext.ts index bc5e161598..b913f8aea2 100644 --- a/packages/twenty-sdk/src/sdk/front-component-api/hooks/useFrontComponentExecutionContext.ts +++ b/packages/twenty-sdk/src/sdk/front-component-api/hooks/useFrontComponentExecutionContext.ts @@ -7,7 +7,7 @@ import { import { type FrontComponentExecutionContext } from '../types/FrontComponentExecutionContext'; export const useFrontComponentExecutionContext = ( - selector: (context: FrontComponentExecutionContext | undefined) => T, + selector: (context: FrontComponentExecutionContext) => T, ): T => { const [currentSelectedValue, setCurrentSelectedValue] = useState(() => selector(getFrontComponentExecutionContext()), diff --git a/packages/twenty-sdk/src/sdk/front-component-api/hooks/useFrontComponentId.ts b/packages/twenty-sdk/src/sdk/front-component-api/hooks/useFrontComponentId.ts new file mode 100644 index 0000000000..95c2ee0beb --- /dev/null +++ b/packages/twenty-sdk/src/sdk/front-component-api/hooks/useFrontComponentId.ts @@ -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); +}; diff --git a/packages/twenty-sdk/src/sdk/front-component-api/hooks/useUserId.ts b/packages/twenty-sdk/src/sdk/front-component-api/hooks/useUserId.ts index 6035d14d57..46a85b9d03 100644 --- a/packages/twenty-sdk/src/sdk/front-component-api/hooks/useUserId.ts +++ b/packages/twenty-sdk/src/sdk/front-component-api/hooks/useUserId.ts @@ -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); }; diff --git a/packages/twenty-sdk/src/sdk/front-component-api/index.ts b/packages/twenty-sdk/src/sdk/front-component-api/index.ts index ae82fd0a35..a73ea9c4de 100644 --- a/packages/twenty-sdk/src/sdk/front-component-api/index.ts +++ b/packages/twenty-sdk/src/sdk/front-component-api/index.ts @@ -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'; diff --git a/packages/twenty-sdk/src/sdk/front-component-api/types/FrontComponentExecutionContext.ts b/packages/twenty-sdk/src/sdk/front-component-api/types/FrontComponentExecutionContext.ts index 732a36147a..6de566865a 100644 --- a/packages/twenty-sdk/src/sdk/front-component-api/types/FrontComponentExecutionContext.ts +++ b/packages/twenty-sdk/src/sdk/front-component-api/types/FrontComponentExecutionContext.ts @@ -1,3 +1,4 @@ export type FrontComponentExecutionContext = { + frontComponentId: string; userId: string | null; }; diff --git a/packages/twenty-sdk/src/sdk/front-component-api/utils/getFrontComponentActionErrorDedupeKey.ts b/packages/twenty-sdk/src/sdk/front-component-api/utils/getFrontComponentActionErrorDedupeKey.ts new file mode 100644 index 0000000000..63923f0e7e --- /dev/null +++ b/packages/twenty-sdk/src/sdk/front-component-api/utils/getFrontComponentActionErrorDedupeKey.ts @@ -0,0 +1,3 @@ +export const getFrontComponentActionErrorDedupeKey = ( + frontComponentId: string, +): string => `${frontComponentId}-action-error`; diff --git a/packages/twenty-sdk/src/sdk/index.ts b/packages/twenty-sdk/src/sdk/index.ts index 8dea045545..e4f7382472 100644 --- a/packages/twenty-sdk/src/sdk/index.ts +++ b/packages/twenty-sdk/src/sdk/index.ts @@ -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 { diff --git a/packages/twenty-shared/src/types/EnqueueSnackbarParams.ts b/packages/twenty-shared/src/types/EnqueueSnackbarParams.ts new file mode 100644 index 0000000000..b03c9853f1 --- /dev/null +++ b/packages/twenty-shared/src/types/EnqueueSnackbarParams.ts @@ -0,0 +1,9 @@ +export type SnackBarVariant = 'error' | 'success' | 'info' | 'warning'; + +export type EnqueueSnackbarParams = { + message: string; + variant: SnackBarVariant; + duration?: number; + detailedMessage?: string; + dedupeKey?: string; +}; diff --git a/packages/twenty-shared/src/types/index.ts b/packages/twenty-shared/src/types/index.ts index febd5dcfd6..7d62b061c6 100644 --- a/packages/twenty-shared/src/types/index.ts +++ b/packages/twenty-shared/src/types/index.ts @@ -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';