diff --git a/packages/twenty-front/src/modules/error-handler/components/AppFullScreenErrorFallback.tsx b/packages/twenty-front/src/modules/error-handler/components/AppFullScreenErrorFallback.tsx index d0f075c390..5224aa0a7d 100644 --- a/packages/twenty-front/src/modules/error-handler/components/AppFullScreenErrorFallback.tsx +++ b/packages/twenty-front/src/modules/error-handler/components/AppFullScreenErrorFallback.tsx @@ -1,6 +1,5 @@ import { AppErrorDisplay } from '@/error-handler/components/internal/AppErrorDisplay'; import { type AppErrorDisplayProps } from '@/error-handler/types/AppErrorDisplayProps'; -import { PageBody } from '@/ui/layout/page/components/PageBody'; import { styled } from '@linaria/react'; import { t } from '@lingui/core/macro'; import { themeCssVariables } from 'twenty-ui-deprecated/theme-constants'; @@ -8,12 +7,10 @@ import { themeCssVariables } from 'twenty-ui-deprecated/theme-constants'; type AppFullScreenErrorFallbackProps = AppErrorDisplayProps; const StyledContainer = styled.div` - background: ${themeCssVariables.background.noisy}; + background: ${themeCssVariables.background.primary}; box-sizing: border-box; display: flex; - height: 100vh; - padding-left: ${themeCssVariables.spacing[3]}; - padding-top: ${themeCssVariables.spacing[3]}; + height: 100dvh; width: 100vw; `; @@ -24,13 +21,11 @@ export const AppFullScreenErrorFallback = ({ }: AppFullScreenErrorFallbackProps) => { return ( - - - + ); }; diff --git a/packages/twenty-front/src/modules/navigation-menu-item/display/components/NavigationMenuItemSkeletonLoader.tsx b/packages/twenty-front/src/modules/navigation-menu-item/display/components/NavigationMenuItemSkeletonLoader.tsx index e1a354f55b..93cb7f7135 100644 --- a/packages/twenty-front/src/modules/navigation-menu-item/display/components/NavigationMenuItemSkeletonLoader.tsx +++ b/packages/twenty-front/src/modules/navigation-menu-item/display/components/NavigationMenuItemSkeletonLoader.tsx @@ -32,7 +32,7 @@ export const NavigationMenuItemSkeletonLoader = () => { const { theme } = useContext(ThemeContext); return ( diff --git a/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsMessageFolderIcon.tsx b/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsMessageFolderIcon.tsx index 375552346c..a055b60baf 100644 --- a/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsMessageFolderIcon.tsx +++ b/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsMessageFolderIcon.tsx @@ -9,43 +9,58 @@ type SettingsAccountsMessageFolderIconProps = { }; const StyledCardMedia = styled.div` - align-items: stretch; - border: 2px solid ${themeCssVariables.border.color.medium}; + align-items: center; + background-color: ${themeCssVariables.border.color.medium}; border-radius: ${themeCssVariables.border.radius.sm}; - color: ${themeCssVariables.font.color.light}; + box-sizing: border-box; display: flex; - flex-direction: column; - gap: ${themeCssVariables.spacing['0.5']}; - height: ${themeCssVariables.spacing[8]}; + height: 40px; justify-content: center; - padding: ${themeCssVariables.spacing['0.5']}; - width: ${themeCssVariables.spacing[6]}; + padding: 2px; + width: 32px; +`; + +const StyledCardMediaContent = styled.div` + align-items: stretch; + background-color: ${themeCssVariables.background.secondary}; + border-radius: 2px; + box-sizing: border-box; + display: flex; + flex: 1; + flex-direction: column; + gap: 2px; + height: 100%; + justify-content: center; + min-width: 0; + padding: 2px; `; const StyledFolderRow = styled.div` align-items: center; display: flex; + flex: 1 0 0; gap: 2px; + min-height: 0; `; const StyledFolderIcon = styled.div<{ isDisabled?: boolean }>` background-color: ${({ isDisabled }) => isDisabled - ? themeCssVariables.background.quaternary - : themeCssVariables.accent.accent4060}; + ? themeCssVariables.border.color.medium + : themeCssVariables.accent.accent7}; border-radius: 1px; - height: 5px; - width: 5px; + height: 100%; + width: 4.8px; `; const StyledFolderLabel = styled.div<{ isDisabled?: boolean }>` background-color: ${({ isDisabled }) => isDisabled - ? themeCssVariables.background.quaternary - : themeCssVariables.accent.accent4060}; + ? themeCssVariables.border.color.medium + : themeCssVariables.accent.accent7}; border-radius: 1px; flex: 1; - height: 5px; + height: 100%; `; export const SettingsAccountsMessageFolderIcon = ({ @@ -57,26 +72,28 @@ export const SettingsAccountsMessageFolderIcon = ({ return ( - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + ); }; diff --git a/packages/twenty-front/src/modules/settings/components/SettingsCard.tsx b/packages/twenty-front/src/modules/settings/components/SettingsCard.tsx index da59d90558..441f98cbb2 100644 --- a/packages/twenty-front/src/modules/settings/components/SettingsCard.tsx +++ b/packages/twenty-front/src/modules/settings/components/SettingsCard.tsx @@ -16,6 +16,7 @@ type SettingsCardProps = { disabled?: boolean; soon?: boolean; Icon: ReactNode; + iconColor?: string; onClick?: () => void; title: string; className?: string; @@ -82,8 +83,15 @@ const StyledDescription = styled.div` padding-left: ${themeCssVariables.spacing[7]}; `; -const StyledIconContainer = styled.div` +const StyledIconContainer = styled.div<{ + disabled?: boolean; + iconColor?: string; +}>` align-items: center; + color: ${({ disabled, iconColor }) => + disabled + ? themeCssVariables.font.color.extraLight + : (iconColor ?? 'inherit')}; display: flex; height: 24px; justify-content: center; @@ -95,6 +103,7 @@ export const SettingsCard = ({ soon, disabled = soon, Icon, + iconColor, onClick, title, className, @@ -112,7 +121,9 @@ export const SettingsCard = ({ - {Icon} + + {Icon} + {title} {soon && } diff --git a/packages/twenty-front/src/modules/settings/components/SettingsDiscoveryHeroCard.tsx b/packages/twenty-front/src/modules/settings/components/SettingsDiscoveryHeroCard.tsx index 5120af73f2..7b4c9a624d 100644 --- a/packages/twenty-front/src/modules/settings/components/SettingsDiscoveryHeroCard.tsx +++ b/packages/twenty-front/src/modules/settings/components/SettingsDiscoveryHeroCard.tsx @@ -7,7 +7,7 @@ import { useModal } from '@/ui/layout/modal/hooks/useModal'; import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { styled } from '@linaria/react'; import { useLingui } from '@lingui/react/macro'; -import { useContext } from 'react'; +import { type ReactNode, useContext } from 'react'; import { Card } from 'twenty-ui-deprecated/layout'; import { ThemeContext, @@ -43,11 +43,17 @@ const StyledOverlay = styled.div` position: absolute; `; +const StyledFooter = styled.div` + background: ${themeCssVariables.background.secondary}; + border-top: 1px solid ${themeCssVariables.border.color.medium}; +`; + type SettingsDiscoveryHeroCardProps = { lightSrc: string; darkSrc: string; instanceIdPrefix: string; tabs: SettingsCustomizeVideoModalTab[]; + footer?: ReactNode; playButtonAriaLabel?: string; }; @@ -56,6 +62,7 @@ export const SettingsDiscoveryHeroCard = ({ darkSrc, instanceIdPrefix, tabs, + footer, playButtonAriaLabel, }: SettingsDiscoveryHeroCardProps) => { const { t } = useLingui(); @@ -64,6 +71,7 @@ export const SettingsDiscoveryHeroCard = ({ const isDiscoveryVideoEnabled = useIsFeatureEnabled( FeatureFlagKey.IS_SETTINGS_DISCOVERY_HERO_ENABLED, ); + const shouldDisplayVideo = isDiscoveryVideoEnabled && tabs.length > 0; const modalInstanceId = `${instanceIdPrefix}-modal`; const tabsInstanceId = `${instanceIdPrefix}-tabs`; @@ -75,7 +83,7 @@ export const SettingsDiscoveryHeroCard = ({ - {isDiscoveryVideoEnabled && ( + {shouldDisplayVideo && ( openModal(modalInstanceId)} @@ -84,8 +92,11 @@ export const SettingsDiscoveryHeroCard = ({ )} + {footer !== undefined && footer !== null && ( + {footer} + )} - {isDiscoveryVideoEnabled && ( + {shouldDisplayVideo && ( ( + + + +); diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/SettingsObjectNewFieldSelector.tsx b/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/SettingsObjectNewFieldSelector.tsx index 1f891726a4..6728e50beb 100644 --- a/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/SettingsObjectNewFieldSelector.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/SettingsObjectNewFieldSelector.tsx @@ -63,6 +63,11 @@ const StyledSearchInputContainer = styled.div` width: 100%; `; +const StyledFieldTypeIconContainer = styled.span` + display: flex; + opacity: 0.64; +`; + export const SettingsObjectNewFieldSelector = ({ excludedFieldTypes = [], objectNamePlural, @@ -164,10 +169,12 @@ export const SettingsObjectNewFieldSelector = ({ + + + } title={config.label} /> diff --git a/packages/twenty-front/src/modules/settings/data-model/object-details/components/SettingsObjectRelationsTable.tsx b/packages/twenty-front/src/modules/settings/data-model/object-details/components/SettingsObjectRelationsTable.tsx index 530c779407..70ba60a77c 100644 --- a/packages/twenty-front/src/modules/settings/data-model/object-details/components/SettingsObjectRelationsTable.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/object-details/components/SettingsObjectRelationsTable.tsx @@ -7,11 +7,13 @@ import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; import { SortableTableHeader } from '@/ui/layout/table/components/SortableTableHeader'; import { Table } from '@/ui/layout/table/components/Table'; +import { TableBody } from '@/ui/layout/table/components/TableBody'; import { TableHeader } from '@/ui/layout/table/components/TableHeader'; import { useSortedArray } from '@/ui/layout/table/hooks/useSortedArray'; import { type TableMetadata } from '@/ui/layout/table/types/TableMetadata'; import { isAdvancedModeEnabledState } from '@/ui/navigation/navigation-drawer/states/isAdvancedModeEnabledState'; import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; +import { StyledSettingsDataModelTableBodyContainer } from '@/settings/data-model/components/SettingsDataModelTableBodyContainer'; import { styled } from '@linaria/react'; import { msg } from '@lingui/core/macro'; import { useLingui } from '@lingui/react/macro'; @@ -182,13 +184,17 @@ export const SettingsObjectRelationsTable = ({ ))} - {filteredRelationFields.map((fieldMetadataItem) => ( - - ))} + + + {filteredRelationFields.map((fieldMetadataItem) => ( + + ))} + + ); diff --git a/packages/twenty-front/src/modules/settings/playground/components/SettingsMcpSetup.tsx b/packages/twenty-front/src/modules/settings/playground/components/SettingsMcpSetup.tsx index 9b0a64d7bb..b28ec8a043 100644 --- a/packages/twenty-front/src/modules/settings/playground/components/SettingsMcpSetup.tsx +++ b/packages/twenty-front/src/modules/settings/playground/components/SettingsMcpSetup.tsx @@ -1,17 +1,44 @@ import { styled } from '@linaria/react'; import { useLingui } from '@lingui/react/macro'; +import { type ReactNode } from 'react'; import { H2Title, IconCopy } from 'twenty-ui-deprecated/display'; -import { Button } from 'twenty-ui-deprecated/input'; +import { LightIconButton } from 'twenty-ui-deprecated/input'; import { Card, CardContent, Section } from 'twenty-ui-deprecated/layout'; +import { themeCssVariables } from 'twenty-ui-deprecated/theme-constants'; import { REACT_APP_SERVER_BASE_URL } from '~/config'; import { useCopyToClipboard } from '~/hooks/useCopyToClipboard'; +const StyledMcpConfigContainer = styled.div` + position: relative; +`; + +const StyledCopyButtonContainer = styled.div` + position: absolute; + right: 0; + top: 0; +`; + const StyledPre = styled.pre` + color: ${themeCssVariables.font.color.secondary}; font-family: monospace; + line-height: 1.5; margin: 0; + overflow-x: auto; white-space: pre; `; +const StyledJsonKey = styled.span` + color: ${themeCssVariables.color.blue}; +`; + +const StyledJsonString = styled.span` + color: ${themeCssVariables.color.green}; +`; + +const StyledJsonPlaceholder = styled.span` + color: ${themeCssVariables.color.orange}; +`; + const buildMcpConfig = (serverUrl: string) => `{ "mcpServers": { @@ -24,6 +51,61 @@ const buildMcpConfig = (serverUrl: string) => } }`; +const renderJsonString = (token: string, key: string) => { + const apiKeyPlaceholder = ''; + + if (!token.includes(apiKeyPlaceholder)) { + return {token}; + } + + const [beforePlaceholder, afterPlaceholder] = token.split(apiKeyPlaceholder); + + return ( + + {beforePlaceholder} + {apiKeyPlaceholder} + {afterPlaceholder} + + ); +}; + +const getHighlightedMcpConfig = (mcpConfig: string) => { + const jsonStringTokenRegex = /("(?:[^"\\]|\\.)*")(\s*:)?/g; + const tokens: ReactNode[] = []; + let previousMatchEndIndex = 0; + let tokenIndex = 0; + let match: RegExpExecArray | null; + + while ((match = jsonStringTokenRegex.exec(mcpConfig)) !== null) { + const [fullMatch, quotedString, trailingColon] = match; + const matchIndex = match.index; + + if (matchIndex > previousMatchEndIndex) { + tokens.push(mcpConfig.slice(previousMatchEndIndex, matchIndex)); + } + + if (trailingColon) { + tokens.push( + + {quotedString} + , + ); + tokens.push(trailingColon); + } else { + tokens.push(renderJsonString(quotedString, `json-string-${tokenIndex}`)); + } + + previousMatchEndIndex = matchIndex + fullMatch.length; + tokenIndex += 1; + } + + if (previousMatchEndIndex < mcpConfig.length) { + tokens.push(mcpConfig.slice(previousMatchEndIndex)); + } + + return tokens; +}; + export const SettingsMcpSetup = () => { const { t } = useLingui(); const { copyToClipboard } = useCopyToClipboard(); @@ -36,19 +118,22 @@ export const SettingsMcpSetup = () => { description={t`Add Twenty as a Model Context Protocol (MCP) server. Paste this config into Claude Desktop, Cursor, Cline, Continue, Zed, or any other MCP-aware client.`} /> - - {mcpConfig} - -