From 2bd1ece95200dc53312f8ffdde173f9b9162467b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Bosi?= <71827178+bosiraphael@users.noreply.github.com> Date: Tue, 28 Jul 2026 12:08:17 +0200 Subject: [PATCH] Redesign the external link popup for front components (#23404) CleanShot 2026-07-28 at 11 52
15@2x Applies the Figma design to the confirmation popup shown before a front component navigates to an external site. New copy: "Open external link?", the destination as a pill, "Always allow links to this domain", and "Open link" as the confirm button. The popup is now its own component built on `ModalStatefulWrapper` because `ConfirmationModal`'s fixed spacing cannot produce the design's layout. The "always allow" checkbox stays checked by default, as before. The pill is a non-interactive span rather than a `RoundedLink`, so the destination cannot be opened outside the confirm flow, and it ellipsizes the path so the domain stays readable. --- .../FrontComponentExternalLinkModal.tsx | 153 ++++++++++++++++++ ...FrontComponentExternalLinkModalManager.tsx | 26 +-- ...rontComponentExternalLinkModalSubtitle.tsx | 66 -------- ...rontComponentExternalLinkModal.stories.tsx | 72 +++++++++ .../getExternalLinkDisplayUrl.test.ts | 29 ++++ .../utils/getExternalLinkDisplayUrl.ts | 12 ++ 6 files changed, 273 insertions(+), 85 deletions(-) create mode 100644 packages/twenty-front/src/modules/front-components/components/FrontComponentExternalLinkModal.tsx delete mode 100644 packages/twenty-front/src/modules/front-components/components/FrontComponentExternalLinkModalSubtitle.tsx create mode 100644 packages/twenty-front/src/modules/front-components/components/__stories__/FrontComponentExternalLinkModal.stories.tsx create mode 100644 packages/twenty-front/src/modules/front-components/utils/__tests__/getExternalLinkDisplayUrl.test.ts create mode 100644 packages/twenty-front/src/modules/front-components/utils/getExternalLinkDisplayUrl.ts diff --git a/packages/twenty-front/src/modules/front-components/components/FrontComponentExternalLinkModal.tsx b/packages/twenty-front/src/modules/front-components/components/FrontComponentExternalLinkModal.tsx new file mode 100644 index 0000000000..5aa826f579 --- /dev/null +++ b/packages/twenty-front/src/modules/front-components/components/FrontComponentExternalLinkModal.tsx @@ -0,0 +1,153 @@ +import { styled } from '@linaria/react'; +import { t } from '@lingui/core/macro'; +import { useId } from 'react'; +import { Button, Checkbox } from 'twenty-ui/input'; +import { themeCssVariables } from 'twenty-ui/theme-constants'; +import { H1Title, H1TitleFontColor } from 'twenty-ui/typography'; + +import { FRONT_COMPONENT_EXTERNAL_LINK_MODAL_ID } from '@/front-components/constants/FrontComponentExternalLinkModalId'; +import { getExternalLinkDisplayUrl } from '@/front-components/utils/getExternalLinkDisplayUrl'; +import { ModalStatefulWrapper } from '@/ui/layout/modal/components/ModalStatefulWrapper'; +import { useModal } from '@/ui/layout/modal/hooks/useModal'; + +const FRONT_COMPONENT_EXTERNAL_LINK_MODAL_WIDTH = 320; + +const StyledCenteredTitle = styled.div` + text-align: center; + + h2 { + margin-bottom: 0; + } +`; + +const StyledDestinationUrl = styled.span` + align-items: center; + align-self: center; + background-color: ${themeCssVariables.background.transparent.lighter}; + border: 1px solid ${themeCssVariables.border.color.strong}; + border-radius: ${themeCssVariables.border.radius.pill}; + color: ${themeCssVariables.font.color.primary}; + corner-shape: round; + display: inline-flex; + font-size: ${themeCssVariables.font.size.md}; + height: 20px; + justify-content: center; + max-width: 100%; + overflow: hidden; + padding: 0 ${themeCssVariables.spacing[2]}; +`; + +const StyledDestinationUrlText = styled.span` + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +`; + +const StyledActions = styled.div` + display: flex; + flex-direction: column; + gap: ${themeCssVariables.spacing[2]}; +`; + +const StyledTrustOriginRow = styled.div` + align-items: center; + display: flex; + gap: ${themeCssVariables.spacing[1]}; + justify-content: center; +`; + +const StyledTrustOriginLabel = styled.span` + color: ${themeCssVariables.font.color.primary}; + cursor: pointer; + font-size: ${themeCssVariables.font.size.sm}; +`; + +type FrontComponentExternalLinkModalProps = { + url: string; + shouldTrustOrigin: boolean; + onShouldTrustOriginChange: (shouldTrustOrigin: boolean) => void; + onConfirm: () => void; + onClose: () => void; +}; + +export const FrontComponentExternalLinkModal = ({ + url, + shouldTrustOrigin, + onShouldTrustOriginChange, + onConfirm, + onClose, +}: FrontComponentExternalLinkModalProps) => { + const { closeModal } = useModal(); + const trustOriginLabelId = useId(); + + const handleConfirmClick = () => { + closeModal(FRONT_COMPONENT_EXTERNAL_LINK_MODAL_ID); + onConfirm(); + }; + + const handleCancelClick = () => { + closeModal(FRONT_COMPONENT_EXTERNAL_LINK_MODAL_ID); + onClose(); + }; + + return ( + + + + + + + {getExternalLinkDisplayUrl(url)} + + + + + + onShouldTrustOriginChange(!shouldTrustOrigin)} + > + {t`Always allow links to this domain`} + + +