From 0a6d5479c9deeb7a82fb7d8fa4bbc6b6ff26cccb Mon Sep 17 00:00:00 2001 From: nitin <142569587+ehconitin@users.noreply.github.com> Date: Wed, 3 Sep 2025 21:01:38 +0530 Subject: [PATCH] [Dashboards] - add iframe widget (#14251) closes https://github.com/twentyhq/core-team-issues/issues/1415 --- .../constants/CommandMenuPagesConfig.tsx | 5 + .../CommandMenuPageLayoutIframeConfig.tsx | 126 ++++++++++++++++++ .../CommandMenuPageLayoutWidgetTypeSelect.tsx | 28 ++-- .../command-menu/types/CommandMenuPages.ts | 1 + .../graph}/components/GraphWidgetBarChart.tsx | 0 .../components/GraphWidgetGaugeChart.tsx | 0 .../graph}/components/GraphWidgetLegend.tsx | 0 .../components/GraphWidgetNumberChart.tsx | 0 .../graph}/components/GraphWidgetPieChart.tsx | 0 .../graph}/components/GraphWidgetTooltip.tsx | 0 .../GraphWidgetBarChart.stories.tsx | 0 .../GraphWidgetGaugeChart.stories.tsx | 0 .../__stories__/GraphWidgetLegend.stories.tsx | 0 .../GraphWidgetNumberChart.stories.tsx | 0 .../GraphWidgetPieChart.stories.tsx | 0 .../GraphWidgetTooltip.stories.tsx | 0 .../graph}/types/GraphColor.ts | 0 .../graph}/types/GraphColorRegistry.ts | 0 .../graph}/types/GraphColorScheme.ts | 0 .../graph}/utils/calculateAngularGradient.ts | 0 .../graph}/utils/createGradientDef.ts | 0 .../graph}/utils/createGraphColorRegistry.ts | 0 .../graph}/utils/getColorScheme.ts | 0 .../graph}/utils/getColorSchemeByIndex.ts | 0 .../graph}/utils/getColorSchemeByName.ts | 0 .../graph}/utils/graphFormatters.ts | 0 .../iframe/components/IframeWidget.tsx | 121 +++++++++++++++++ .../PageLayoutWidgetPlaceholder.tsx | 12 +- .../PageLayoutWidgetPlaceholder.stories.tsx | 6 +- .../hooks/useCreatePageLayoutIframeWidget.ts | 80 +++++++++++ .../hooks/usePageLayoutWidgetUpdate.ts | 35 +++++ .../settings/page-layout/mocks/mockWidgets.ts | 2 +- .../states/pageLayoutEditingWidgetIdState.ts | 6 + .../states/savedPageLayoutsState.ts | 2 +- .../page-layout/utils/graphRegistry.tsx | 66 +++++++++ .../page-layout/utils/widgetRegistry.tsx | 83 +++--------- .../page-layout/SettingsPageLayoutEdit.tsx | 27 +++- 37 files changed, 522 insertions(+), 78 deletions(-) create mode 100644 packages/twenty-front/src/modules/command-menu/pages/page-layout/components/CommandMenuPageLayoutIframeConfig.tsx rename packages/twenty-front/src/modules/dashboards/{graphs => widgets/graph}/components/GraphWidgetBarChart.tsx (100%) rename packages/twenty-front/src/modules/dashboards/{graphs => widgets/graph}/components/GraphWidgetGaugeChart.tsx (100%) rename packages/twenty-front/src/modules/dashboards/{graphs => widgets/graph}/components/GraphWidgetLegend.tsx (100%) rename packages/twenty-front/src/modules/dashboards/{graphs => widgets/graph}/components/GraphWidgetNumberChart.tsx (100%) rename packages/twenty-front/src/modules/dashboards/{graphs => widgets/graph}/components/GraphWidgetPieChart.tsx (100%) rename packages/twenty-front/src/modules/dashboards/{graphs => widgets/graph}/components/GraphWidgetTooltip.tsx (100%) rename packages/twenty-front/src/modules/dashboards/{graphs => widgets/graph}/components/__stories__/GraphWidgetBarChart.stories.tsx (100%) rename packages/twenty-front/src/modules/dashboards/{graphs => widgets/graph}/components/__stories__/GraphWidgetGaugeChart.stories.tsx (100%) rename packages/twenty-front/src/modules/dashboards/{graphs => widgets/graph}/components/__stories__/GraphWidgetLegend.stories.tsx (100%) rename packages/twenty-front/src/modules/dashboards/{graphs => widgets/graph}/components/__stories__/GraphWidgetNumberChart.stories.tsx (100%) rename packages/twenty-front/src/modules/dashboards/{graphs => widgets/graph}/components/__stories__/GraphWidgetPieChart.stories.tsx (100%) rename packages/twenty-front/src/modules/dashboards/{graphs => widgets/graph}/components/__stories__/GraphWidgetTooltip.stories.tsx (100%) rename packages/twenty-front/src/modules/dashboards/{graphs => widgets/graph}/types/GraphColor.ts (100%) rename packages/twenty-front/src/modules/dashboards/{graphs => widgets/graph}/types/GraphColorRegistry.ts (100%) rename packages/twenty-front/src/modules/dashboards/{graphs => widgets/graph}/types/GraphColorScheme.ts (100%) rename packages/twenty-front/src/modules/dashboards/{graphs => widgets/graph}/utils/calculateAngularGradient.ts (100%) rename packages/twenty-front/src/modules/dashboards/{graphs => widgets/graph}/utils/createGradientDef.ts (100%) rename packages/twenty-front/src/modules/dashboards/{graphs => widgets/graph}/utils/createGraphColorRegistry.ts (100%) rename packages/twenty-front/src/modules/dashboards/{graphs => widgets/graph}/utils/getColorScheme.ts (100%) rename packages/twenty-front/src/modules/dashboards/{graphs => widgets/graph}/utils/getColorSchemeByIndex.ts (100%) rename packages/twenty-front/src/modules/dashboards/{graphs => widgets/graph}/utils/getColorSchemeByName.ts (100%) rename packages/twenty-front/src/modules/dashboards/{graphs => widgets/graph}/utils/graphFormatters.ts (100%) create mode 100644 packages/twenty-front/src/modules/dashboards/widgets/iframe/components/IframeWidget.tsx create mode 100644 packages/twenty-front/src/modules/settings/page-layout/hooks/useCreatePageLayoutIframeWidget.ts create mode 100644 packages/twenty-front/src/modules/settings/page-layout/hooks/usePageLayoutWidgetUpdate.ts create mode 100644 packages/twenty-front/src/modules/settings/page-layout/states/pageLayoutEditingWidgetIdState.ts create mode 100644 packages/twenty-front/src/modules/settings/page-layout/utils/graphRegistry.tsx diff --git a/packages/twenty-front/src/modules/command-menu/constants/CommandMenuPagesConfig.tsx b/packages/twenty-front/src/modules/command-menu/constants/CommandMenuPagesConfig.tsx index 54cbfae8b44..7c318080d6a 100644 --- a/packages/twenty-front/src/modules/command-menu/constants/CommandMenuPagesConfig.tsx +++ b/packages/twenty-front/src/modules/command-menu/constants/CommandMenuPagesConfig.tsx @@ -4,6 +4,7 @@ import { CommandMenuAskAIPage } from '@/command-menu/pages/ask-ai/components/Com import { CommandMenuCalendarEventPage } from '@/command-menu/pages/calendar-event/components/CommandMenuCalendarEventPage'; import { CommandMenuMessageThreadPage } from '@/command-menu/pages/message-thread/components/CommandMenuMessageThreadPage'; import { CommandMenuPageLayoutGraphTypeSelect } from '@/command-menu/pages/page-layout/components/CommandMenuPageLayoutGraphTypeSelect'; +import { CommandMenuPageLayoutIframeConfig } from '@/command-menu/pages/page-layout/components/CommandMenuPageLayoutIframeConfig'; import { CommandMenuPageLayoutWidgetTypeSelect } from '@/command-menu/pages/page-layout/components/CommandMenuPageLayoutWidgetTypeSelect'; import { CommandMenuMergeRecordPage } from '@/command-menu/pages/record-page/components/CommandMenuMergeRecordPage'; import { CommandMenuRecordPage } from '@/command-menu/pages/record-page/components/CommandMenuRecordPage'; @@ -48,4 +49,8 @@ export const COMMAND_MENU_PAGES_CONFIG = new Map< CommandMenuPages.PageLayoutGraphTypeSelect, , ], + [ + CommandMenuPages.PageLayoutIframeConfig, + , + ], ]); diff --git a/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/CommandMenuPageLayoutIframeConfig.tsx b/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/CommandMenuPageLayoutIframeConfig.tsx new file mode 100644 index 00000000000..b54c5e0c89d --- /dev/null +++ b/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/CommandMenuPageLayoutIframeConfig.tsx @@ -0,0 +1,126 @@ +import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu'; +import { FormTextFieldInput } from '@/object-record/record-field/ui/form-types/components/FormTextFieldInput'; +import { useCreatePageLayoutIframeWidget } from '@/settings/page-layout/hooks/useCreatePageLayoutIframeWidget'; +import { usePageLayoutWidgetUpdate } from '@/settings/page-layout/hooks/usePageLayoutWidgetUpdate'; +import { pageLayoutEditingWidgetIdState } from '@/settings/page-layout/states/pageLayoutEditingWidgetIdState'; +import { pageLayoutWidgetsState } from '@/settings/page-layout/states/pageLayoutWidgetsState'; +import styled from '@emotion/styled'; +import { useState } from 'react'; +import { useRecoilState, useRecoilValue } from 'recoil'; +import { isValidUrl } from 'twenty-shared/utils'; +import { Button } from 'twenty-ui/input'; + +const StyledContainer = styled.div` + display: flex; + flex-direction: column; + padding: ${({ theme }) => theme.spacing(2)}; + gap: ${({ theme }) => theme.spacing(3)}; +`; + +const StyledSectionTitle = styled.div` + color: ${({ theme }) => theme.font.color.tertiary}; + font-size: ${({ theme }) => theme.font.size.sm}; + font-weight: ${({ theme }) => theme.font.weight.medium}; + padding-left: ${({ theme }) => theme.spacing(1)}; +`; + +const StyledButtonContainer = styled.div` + display: flex; + justify-content: flex-end; + margin-top: ${({ theme }) => theme.spacing(2)}; +`; + +export const CommandMenuPageLayoutIframeConfig = () => { + const { closeCommandMenu } = useCommandMenu(); + const { createPageLayoutIframeWidget } = useCreatePageLayoutIframeWidget(); + const { handleUpdateWidget } = usePageLayoutWidgetUpdate(); + const [pageLayoutEditingWidgetId, setPageLayoutEditingWidgetId] = + useRecoilState(pageLayoutEditingWidgetIdState); + const pageLayoutWidgets = useRecoilValue(pageLayoutWidgetsState); + + const editingWidget = pageLayoutWidgets.find( + (w) => w.id === pageLayoutEditingWidgetId, + ); + const isEditMode = !!editingWidget; + + const [title, setTitle] = useState(editingWidget?.title || ''); + const [url, setUrl] = useState(editingWidget?.configuration?.url || ''); + const [urlError, setUrlError] = useState(''); + + const validateUrl = (urlString: string): boolean => { + const trimmedUrl = urlString.trim(); + + if (!isValidUrl(trimmedUrl)) { + setUrlError('Please enter a valid URL'); + return false; + } + + setUrlError(''); + return true; + }; + + const handleUrlChange = (value: string) => { + setUrl(value); + validateUrl(value); + }; + + const handleSubmit = () => { + if (!title.trim()) { + return; + } + + if (!validateUrl(url)) { + return; + } + + if (isEditMode && pageLayoutEditingWidgetId !== null) { + handleUpdateWidget(pageLayoutEditingWidgetId, { + title: title.trim(), + configuration: { + ...editingWidget?.configuration, + url: url.trim(), + }, + }); + setPageLayoutEditingWidgetId(null); + } else { + createPageLayoutIframeWidget(title.trim(), url.trim()); + } + + closeCommandMenu(); + }; + + const isFormValid = title.trim() && url.trim() && !urlError; + + return ( + + + {isEditMode ? 'Edit iFrame Widget' : 'Configure iFrame Widget'} + + + + + + + +