diff --git a/packages/twenty-front/src/modules/settings/admin-panel/apps/components/SettingsAdminApps.tsx b/packages/twenty-front/src/modules/settings/admin-panel/apps/components/SettingsAdminApps.tsx index 64471e2791..3de05adbd0 100644 --- a/packages/twenty-front/src/modules/settings/admin-panel/apps/components/SettingsAdminApps.tsx +++ b/packages/twenty-front/src/modules/settings/admin-panel/apps/components/SettingsAdminApps.tsx @@ -1,5 +1,6 @@ -import { AppChip } from '@/applications/components/AppChip'; +import { ApplicationDisplay } from '@/applications/components/ApplicationDisplay'; import { useApolloAdminClient } from '@/settings/admin-panel/apollo/hooks/useApolloAdminClient'; +import { StyledNameTableCell } from '@/settings/data-model/object-details/components/SettingsObjectItemTableRowStyledComponents'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent'; @@ -209,20 +210,14 @@ const SettingsAdminAppsTableRow = ({ mobileGridAutoColumns={TABLE_GRID_MOBILE} isClickable > - - + - + {getFormattedSource(registration)} diff --git a/packages/twenty-front/src/modules/settings/admin-panel/components/SettingsAdminGeneral.tsx b/packages/twenty-front/src/modules/settings/admin-panel/components/SettingsAdminGeneral.tsx index f9ce8810ef..913dc55746 100644 --- a/packages/twenty-front/src/modules/settings/admin-panel/components/SettingsAdminGeneral.tsx +++ b/packages/twenty-front/src/modules/settings/admin-panel/components/SettingsAdminGeneral.tsx @@ -36,6 +36,10 @@ const StyledEmptyState = styled.div` padding: ${themeCssVariables.spacing[4]} 0; `; +const StyledSearchInputContainer = styled.div` + padding-bottom: ${themeCssVariables.spacing[2]}; +`; + const RECENT_USERS_GRID_TEMPLATE_COLUMNS = '1fr 2fr 1fr 36px'; const TOP_WORKSPACES_GRID_TEMPLATE_COLUMNS = '2fr 1fr 36px'; @@ -99,13 +103,15 @@ export const SettingsAdminGeneral = () => { : t`Last 10 users created. Click to impersonate.` } /> - + + + {isLoadingUsers ? ( ) : recentUsers.length === 0 ? ( @@ -114,15 +120,15 @@ export const SettingsAdminGeneral = () => { ) : ( + + {t`Name`} + {t`Email`} + {t`Workspace`} + + - - {t`Name`} - {t`Email`} - {t`Workspace`} - - {recentUsers.map((user) => ( { title={t`Top Workspaces`} description={t`Top 10 workspaces by number of users`} /> - + + + {isLoadingWorkspaces ? ( ) : topWorkspaces.length === 0 ? ( @@ -210,14 +218,14 @@ export const SettingsAdminGeneral = () => { ) : (
+ + {t`Workspace`} + {t`Users`} + + - - {t`Workspace`} - {t`Users`} - - {topWorkspaces.map((workspace) => ( { {t`No server administrators found.`} ) : (
+ + {t`Administrator`} + {t`Admin panel`} + {t`Impersonation`} + + - - {t`Administrator`} - {t`Admin panel`} - {t`Impersonation`} - - {serverAdmins.map((admin) => { const adminLabel = `${admin.firstName || ''} ${admin.lastName || ''}`.trim() || diff --git a/packages/twenty-front/src/modules/settings/components/SettingsEditableTitle.tsx b/packages/twenty-front/src/modules/settings/components/SettingsEditableTitle.tsx new file mode 100644 index 0000000000..2daeb46840 --- /dev/null +++ b/packages/twenty-front/src/modules/settings/components/SettingsEditableTitle.tsx @@ -0,0 +1,23 @@ +import { + TitleInput, + type TitleInputProps, +} from '@/ui/input/components/TitleInput'; +import { styled } from '@linaria/react'; + +const StyledTitleInputContainer = styled.div` + max-width: 420px; + min-width: 0; + width: fit-content; +`; + +export type SettingsEditableTitleProps = TitleInputProps; + +export const SettingsEditableTitle = (props: SettingsEditableTitleProps) => ( + + + +); diff --git a/packages/twenty-front/src/modules/settings/components/SettingsListItemCardContent.tsx b/packages/twenty-front/src/modules/settings/components/SettingsListItemCardContent.tsx index 0d20935cec..7b6056ad22 100644 --- a/packages/twenty-front/src/modules/settings/components/SettingsListItemCardContent.tsx +++ b/packages/twenty-front/src/modules/settings/components/SettingsListItemCardContent.tsx @@ -9,11 +9,12 @@ import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants'; const StyledRowContainer = styled.div` > * { align-items: center; + box-sizing: border-box; display: flex; - font-size: ${themeCssVariables.font.size.sm}; + font-size: ${themeCssVariables.font.size.md}; font-weight: ${themeCssVariables.font.weight.medium}; gap: ${themeCssVariables.spacing[2]}; - min-height: ${themeCssVariables.spacing[6]}; + height: ${themeCssVariables.spacing[10]}; padding: ${themeCssVariables.spacing[2]}; padding-left: ${themeCssVariables.spacing[3]}; } diff --git a/packages/twenty-front/src/modules/settings/components/layout/SettingsPageLayout.tsx b/packages/twenty-front/src/modules/settings/components/layout/SettingsPageLayout.tsx index 6031535840..f5564b9926 100644 --- a/packages/twenty-front/src/modules/settings/components/layout/SettingsPageLayout.tsx +++ b/packages/twenty-front/src/modules/settings/components/layout/SettingsPageLayout.tsx @@ -1,8 +1,9 @@ +import { SettingsEditableTitle } from '@/settings/components/SettingsEditableTitle'; import { SettingsSecondaryBar } from '@/settings/components/layout/SettingsSecondaryBar'; import { PageCardHeader } from '@/ui/layout/page/components/PageCardHeader'; import { PageCardLayout } from '@/ui/layout/page/components/PageCardLayout'; import { type BreadcrumbProps } from '@/ui/navigation/bread-crumb/components/Breadcrumb'; -import { type JSX, type ReactNode } from 'react'; +import { useId, type JSX, type ReactNode } from 'react'; import { isDefined } from 'twenty-shared/utils'; type SettingsPageLayoutProps = { @@ -25,25 +26,41 @@ export const SettingsPageLayout = ({ children, tag, titleColor, -}: SettingsPageLayoutProps) => ( - { + const titleInstanceId = useId(); + + const formattedTitle = + typeof title === 'string' ? ( + - } - secondaryBar={ - isDefined(secondaryBar) ? ( - {secondaryBar} - ) : undefined - } - > - {children} - -); + ) : ( + title + ); + + return ( + + } + secondaryBar={ + isDefined(secondaryBar) ? ( + {secondaryBar} + ) : undefined + } + > + {children} + + ); +}; diff --git a/packages/twenty-front/src/modules/settings/logic-functions/components/SettingsLogicFunctionLabelContainer.tsx b/packages/twenty-front/src/modules/settings/logic-functions/components/SettingsLogicFunctionLabelContainer.tsx index d2322e87f2..290063cafe 100644 --- a/packages/twenty-front/src/modules/settings/logic-functions/components/SettingsLogicFunctionLabelContainer.tsx +++ b/packages/twenty-front/src/modules/settings/logic-functions/components/SettingsLogicFunctionLabelContainer.tsx @@ -1,18 +1,5 @@ -import { TitleInput } from '@/ui/input/components/TitleInput'; -import { styled } from '@linaria/react'; +import { SettingsEditableTitle } from '@/settings/components/SettingsEditableTitle'; import { t } from '@lingui/core/macro'; -import { themeCssVariables } from 'twenty-ui/theme-constants'; - -const StyledHeaderTitle = styled.div` - color: ${themeCssVariables.font.color.primary}; - font-size: ${themeCssVariables.font.size.lg}; - font-weight: ${themeCssVariables.font.weight.semiBold}; - max-width: 420px; - width: fit-content; - & > input:disabled { - color: ${themeCssVariables.font.color.primary}; - } -`; type SettingsLogicFunctionLabelContainerProps = { value: string; @@ -26,15 +13,12 @@ export const SettingsLogicFunctionLabelContainer = ({ readonly = false, }: SettingsLogicFunctionLabelContainerProps) => { return ( - - - + ); }; diff --git a/packages/twenty-front/src/modules/settings/roles/role/components/SettingsRoleLabelContainer.tsx b/packages/twenty-front/src/modules/settings/roles/role/components/SettingsRoleLabelContainer.tsx index e94a739abf..31cb8bab15 100644 --- a/packages/twenty-front/src/modules/settings/roles/role/components/SettingsRoleLabelContainer.tsx +++ b/packages/twenty-front/src/modules/settings/roles/role/components/SettingsRoleLabelContainer.tsx @@ -1,34 +1,8 @@ import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState'; +import { SettingsEditableTitle } from '@/settings/components/SettingsEditableTitle'; import { useAtomFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilyStateValue'; import { useSetAtomFamilyState } from '@/ui/utilities/state/jotai/hooks/useSetAtomFamilyState'; -import { TitleInput } from '@/ui/input/components/TitleInput'; -import { styled } from '@linaria/react'; import { t } from '@lingui/core/macro'; -import { themeCssVariables } from 'twenty-ui/theme-constants'; - -const StyledHeaderTitle = styled.div` - border-radius: ${themeCssVariables.border.radius.sm}; - max-width: 420px; - padding-left: ${themeCssVariables.spacing[1]}; - padding-right: ${themeCssVariables.spacing[1]}; - width: fit-content; - - &:hover { - background: ${themeCssVariables.background.transparent.light}; - } - - & > div:hover { - background: transparent; - } - - & > div :hover { - background: transparent; - } - - & > input:disabled { - color: ${themeCssVariables.font.color.primary}; - } -`; type SettingsRoleLabelContainerProps = { roleId: string; @@ -54,15 +28,12 @@ export const SettingsRoleLabelContainer = ({ }; return ( - - - + ); }; diff --git a/packages/twenty-front/src/modules/ui/input/components/TitleInput.tsx b/packages/twenty-front/src/modules/ui/input/components/TitleInput.tsx index 502708db08..3b517a204b 100644 --- a/packages/twenty-front/src/modules/ui/input/components/TitleInput.tsx +++ b/packages/twenty-front/src/modules/ui/input/components/TitleInput.tsx @@ -11,10 +11,9 @@ import { styled } from '@linaria/react'; import { OverflowingTextWithTooltip } from 'twenty-ui/surfaces'; import { themeCssVariables } from 'twenty-ui/theme-constants'; -type InputProps = { +type CommonInputProps = { instanceId: string; value?: string; - onChange: (value: string) => void; placeholder?: string; onEnter?: () => void; onEscape?: () => void; @@ -24,22 +23,49 @@ type InputProps = { sizeVariant?: TextInputSize; }; -export type TitleInputProps = { - disabled?: boolean; - shouldFocus?: boolean; - onFocus?: () => void; -} & InputProps; +type InputProps = CommonInputProps & { + onChange?: (value: string) => void; +}; + +type TitleInputReadonlyProps = { + disabled: true; + onChange?: (value: string) => void; +}; + +type TitleInputEditableProps = { + disabled?: false; + onChange: (value: string) => void; +}; + +type TitleInputConditionallyReadonlyProps = { + disabled: boolean; + onChange: (value: string) => void; +}; + +type TitleInputEditionProps = + | TitleInputReadonlyProps + | TitleInputEditableProps + | TitleInputConditionallyReadonlyProps; + +export type TitleInputProps = CommonInputProps & + TitleInputEditionProps & { + shouldFocus?: boolean; + onFocus?: () => void; + textColor?: string; + }; const StyledDiv = styled.div<{ sizeVariant: TextInputSize; disabled?: boolean; + textColor?: string; }>` align-items: center; background: inherit; border: none; border-radius: ${themeCssVariables.border.radius.sm}; box-sizing: border-box; - color: ${themeCssVariables.font.color.primary}; + color: ${({ textColor }) => + textColor ?? themeCssVariables.font.color.primary}; cursor: ${({ disabled }) => (disabled ? 'default' : 'pointer')}; display: flex; height: ${({ sizeVariant }) => @@ -52,7 +78,7 @@ const StyledDiv = styled.div<{ : '32px'}; overflow: hidden; padding: ${themeCssVariables.spacing[0]} 5px; - :hover { + &:hover { background: ${({ disabled }) => disabled ? 'inherit' : themeCssVariables.background.transparent.light}; } @@ -148,6 +174,7 @@ export const TitleInput = ({ onShiftTab, shouldFocus, onFocus, + textColor, }: TitleInputProps) => { const [isOpened, setIsOpened] = useState(false); @@ -181,6 +208,7 @@ export const TitleInput = ({ { if (!disabled) { setIsOpened(true); diff --git a/packages/twenty-front/src/modules/ui/layout/page/components/PageCardHeader.tsx b/packages/twenty-front/src/modules/ui/layout/page/components/PageCardHeader.tsx index 19d6dae724..1e0e372940 100644 --- a/packages/twenty-front/src/modules/ui/layout/page/components/PageCardHeader.tsx +++ b/packages/twenty-front/src/modules/ui/layout/page/components/PageCardHeader.tsx @@ -23,16 +23,17 @@ type PageCardHeaderProps = { titleColor?: string; }; -const StyledHeader = styled.div` +const StyledHeader = styled.div<{ centerTitle?: boolean }>` align-items: center; background-color: ${themeCssVariables.background.secondary}; border-bottom: 1px solid ${themeCssVariables.border.color.medium}; box-sizing: border-box; - display: flex; - gap: ${themeCssVariables.spacing[2]}; + column-gap: ${themeCssVariables.spacing[2]}; + display: grid; + grid-template-columns: ${({ centerTitle }) => + centerTitle ? 'minmax(0, 1fr) auto minmax(0, 1fr)' : 'minmax(0, 1fr) auto'}; min-height: ${SIDE_PANEL_TOP_BAR_HEIGHT}px; padding: 0 ${themeCssVariables.spacing[3]}; - position: relative; width: 100%; `; @@ -40,8 +41,10 @@ const StyledLeft = styled.div` align-items: center; display: flex; gap: ${themeCssVariables.spacing[2]}; + grid-column: 1; min-width: 0; overflow: hidden; + width: 100%; `; const StyledTitle = styled.div<{ titleColor?: string }>` @@ -51,26 +54,27 @@ const StyledTitle = styled.div<{ titleColor?: string }>` display: flex; font-size: ${themeCssVariables.font.size.md}; font-weight: ${themeCssVariables.font.weight.semiBold}; - gap: ${themeCssVariables.spacing[2]}; + gap: ${themeCssVariables.spacing[1]}; min-width: 0; `; const StyledCenteredTitle = styled(StyledTitle)` - bottom: 0; + grid-column: 2; justify-content: center; - left: 50%; - position: absolute; - top: 0; - transform: translateX(-50%); + justify-self: center; + max-width: 100%; + overflow: hidden; `; -const StyledRight = styled.div` +const StyledRight = styled.div<{ centerTitle?: boolean }>` align-items: center; display: flex; - flex: 1; gap: ${themeCssVariables.spacing[2]}; + grid-column: ${({ centerTitle }) => (centerTitle ? 3 : 2)}; justify-content: flex-end; + justify-self: end; min-width: 0; + width: 100%; `; export const PageCardHeader = ({ @@ -88,6 +92,7 @@ export const PageCardHeader = ({ const hasTitleContent = !isMobile && (isDefined(icon) || isDefined(title) || isDefined(tag)); + const shouldCenterTitle = centerTitle && hasTitleContent; const titleContent = ( <> @@ -98,7 +103,7 @@ export const PageCardHeader = ({ ); return ( - + {!isNavigationDrawerExpanded && ( @@ -106,16 +111,17 @@ export const PageCardHeader = ({ {isDefined(breadcrumb) ? breadcrumb : isDefined(links) && } - {!centerTitle && hasTitleContent && ( + {!shouldCenterTitle && hasTitleContent && ( {titleContent} )} - {centerTitle && hasTitleContent && ( + {shouldCenterTitle && ( {titleContent} )} {actionButton} diff --git a/packages/twenty-front/src/modules/ui/navigation/bread-crumb/components/Breadcrumb.tsx b/packages/twenty-front/src/modules/ui/navigation/bread-crumb/components/Breadcrumb.tsx index 6ce921e187..2c4afe0087 100644 --- a/packages/twenty-front/src/modules/ui/navigation/bread-crumb/components/Breadcrumb.tsx +++ b/packages/twenty-front/src/modules/ui/navigation/bread-crumb/components/Breadcrumb.tsx @@ -13,34 +13,40 @@ export type BreadcrumbProps = { const StyledWrapper = styled.nav` align-items: center; color: ${themeCssVariables.font.color.tertiary}; - display: grid; + display: flex; font-size: ${themeCssVariables.font.size.md}; - grid-auto-flow: column; - grid-column-gap: ${themeCssVariables.spacing[1]}; + gap: ${themeCssVariables.spacing[1]}; height: ${themeCssVariables.spacing[8]}; max-width: 100%; min-width: 0; + overflow: hidden; `; -const StyledLinkContainer = styled.div` - > a { - color: inherit; - overflow: hidden; - text-decoration: none; - text-overflow: ellipsis; - white-space: nowrap; - } +const StyledCrumbContainer = styled.span<{ $isLast: boolean }>` + flex: ${({ $isLast }) => ($isLast ? '0 1 auto' : '0 0 auto')}; + min-width: 0; + overflow: hidden; +`; + +const StyledLink = styled(Link)` + color: inherit; + display: block; + overflow: hidden; + text-decoration: none; + text-overflow: ellipsis; + white-space: nowrap; `; const StyledText = styled.span` color: ${themeCssVariables.font.color.primary}; + display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; `; const StyledDivider = styled.span` - width: ${themeCssVariables.spacing[2]}; + flex: 0 0 ${themeCssVariables.spacing[2]}; `; export const Breadcrumb = ({ className, links }: BreadcrumbProps) => { @@ -54,18 +60,19 @@ export const Breadcrumb = ({ className, links }: BreadcrumbProps) => { {links.map((link, index) => { const text = typeof link.children === 'string' ? link.children : ''; + const isLast = index === links.length - 1; return ( - {link.href ? ( - - + + {link.href ? ( + {link.children} - - - ) : ( - {link.children} - )} + + ) : ( + {link.children} + )} + {index < links.length - 1 && /} ); diff --git a/packages/twenty-front/src/pages/settings/admin-panel/SettingsAdminApplicationRegistrationDetail.tsx b/packages/twenty-front/src/pages/settings/admin-panel/SettingsAdminApplicationRegistrationDetail.tsx index 0d55849cd4..2e2b258953 100644 --- a/packages/twenty-front/src/pages/settings/admin-panel/SettingsAdminApplicationRegistrationDetail.tsx +++ b/packages/twenty-front/src/pages/settings/admin-panel/SettingsAdminApplicationRegistrationDetail.tsx @@ -23,18 +23,10 @@ import { SettingsApplicationRegistrationGeneralTab } from '~/pages/settings/appl import { TabList } from '@/ui/layout/tab-list/components/TabList'; import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue'; import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState'; -import { themeCssVariables } from 'twenty-ui/theme-constants'; -import { styled } from '@linaria/react'; const REGISTRATION_DETAIL_TAB_LIST_ID = 'admin-application-registration-detail-tab-list'; -const StyledTitleContainer = styled.div` - align-items: center; - display: flex; - gap: ${themeCssVariables.spacing[2]}; -`; - export const SettingsAdminApplicationRegistrationDetail = () => { const { t } = useLingui(); const apolloAdminClient = useApolloAdminClient(); @@ -104,17 +96,15 @@ export const SettingsAdminApplicationRegistrationDetail = () => { return ( - - {registration.name} - + title={registration.name} + icon={ + } links={[ { diff --git a/packages/twenty-front/src/pages/settings/admin-panel/SettingsAdminUserDetail.tsx b/packages/twenty-front/src/pages/settings/admin-panel/SettingsAdminUserDetail.tsx index c97adfe611..0616971a4e 100644 --- a/packages/twenty-front/src/pages/settings/admin-panel/SettingsAdminUserDetail.tsx +++ b/packages/twenty-front/src/pages/settings/admin-panel/SettingsAdminUserDetail.tsx @@ -34,6 +34,7 @@ import { IconMail, IconUser, } from 'twenty-ui/icon'; +import { Avatar } from 'twenty-ui/data-display'; import { H2Title } from 'twenty-ui/typography'; import { Button } from 'twenty-ui/input'; import { Section } from 'twenty-ui/layout'; @@ -140,6 +141,15 @@ export const SettingsAdminUserDetail = () => { return ( + } links={[ { children: t`Other`, diff --git a/packages/twenty-front/src/pages/settings/admin-panel/SettingsAdminWorkspaceDetail.tsx b/packages/twenty-front/src/pages/settings/admin-panel/SettingsAdminWorkspaceDetail.tsx index c7c61b6901..49646d5f4a 100644 --- a/packages/twenty-front/src/pages/settings/admin-panel/SettingsAdminWorkspaceDetail.tsx +++ b/packages/twenty-front/src/pages/settings/admin-panel/SettingsAdminWorkspaceDetail.tsx @@ -2,6 +2,7 @@ import { useParams } from 'react-router-dom'; import { useMutation, useQuery } from '@apollo/client/react'; import { t } from '@lingui/core/macro'; +import { isNonEmptyString } from '@sniptt/guards'; import { SettingsPath } from 'twenty-shared/types'; import { getSettingsPath, isDefined } from 'twenty-shared/utils'; @@ -29,6 +30,7 @@ import { TableBody } from '@/ui/layout/table/components/TableBody'; import { TableCell } from '@/ui/layout/table/components/TableCell'; import { TableHeader } from '@/ui/layout/table/components/TableHeader'; import { TableRow } from '@/ui/layout/table/components/TableRow'; +import { DEFAULT_WORKSPACE_LOGO } from '@/ui/navigation/navigation-drawer/constants/DefaultWorkspaceLogo'; import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue'; import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; import { Avatar } from 'twenty-ui/data-display'; @@ -196,6 +198,9 @@ export const SettingsAdminWorkspaceDetail = () => { ]; const workspaceName = workspace?.name || workspaceId || ''; + const workspaceLogo = isNonEmptyString(workspace?.logo) + ? workspace.logo + : DEFAULT_WORKSPACE_LOGO; if (isLoadingWorkspace) { return ; @@ -203,6 +208,15 @@ export const SettingsAdminWorkspaceDetail = () => { return ( + } links={[ { children: t`Other`, diff --git a/packages/twenty-front/src/pages/settings/ai/SettingsAgentForm.tsx b/packages/twenty-front/src/pages/settings/ai/SettingsAgentForm.tsx index 9cbbef0f2b..896d889631 100644 --- a/packages/twenty-front/src/pages/settings/ai/SettingsAgentForm.tsx +++ b/packages/twenty-front/src/pages/settings/ai/SettingsAgentForm.tsx @@ -22,9 +22,10 @@ import { IconListCheck, IconLock, IconSettings, + useIcons, } from 'twenty-ui/icon'; import { Section } from 'twenty-ui/layout'; -import { themeCssVariables } from 'twenty-ui/theme-constants'; +import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants'; import { useMutation, useQuery } from '@apollo/client/react'; import { type CreateAgentInput, @@ -37,7 +38,7 @@ import { useNavigateSettings } from '~/hooks/useNavigateSettings'; import { useAtomFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilyStateValue'; import { useSetAtomFamilyState } from '@/ui/utilities/state/jotai/hooks/useSetAtomFamilyState'; -import { useEffect, useState } from 'react'; +import { useContext, useEffect, useState } from 'react'; import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; import { SettingsAgentDetailSkeletonLoader } from './components/SettingsAgentDetailSkeletonLoader'; import { SettingsAgentEvalsTab } from './components/SettingsAgentEvalsTab'; @@ -60,6 +61,8 @@ const StyledTabListContainer = styled.div` `; export const SettingsAgentForm = ({ mode }: { mode: 'create' | 'edit' }) => { + const { theme } = useContext(ThemeContext); + const { getIcon } = useIcons(); const { agentId = '' } = useParams<{ agentId: string }>(); const navigate = useNavigateSettings(); const navigateApp = useNavigateApp(); @@ -381,6 +384,7 @@ export const SettingsAgentForm = ({ mode }: { mode: 'create' | 'edit' }) => { ? t`Agent` : agent?.label : t`New Agent`; + const AgentIcon = getIcon(formValues.icon || 'IconRobot'); const breadcrumbText = !isCreateMode ? loading ? t`Agent` @@ -403,6 +407,9 @@ export const SettingsAgentForm = ({ mode }: { mode: 'create' | 'edit' }) => { + } actionButton={ isCreateMode ? ( input:disabled { - color: ${themeCssVariables.font.color.primary}; - } -`; - const StyledDangerButtonsContainer = styled.div` display: flex; gap: ${themeCssVariables.spacing[2]}; @@ -104,6 +94,7 @@ const DELETE_SKILL_MODAL_ID = 'delete-skill-modal'; export const SettingsSkillForm = ({ mode }: { mode: 'create' | 'edit' }) => { const { theme } = useContext(ThemeContext); + const { getIcon } = useIcons(); const { skillId = '' } = useParams<{ skillId: string }>(); const navigate = useNavigateSettings(); const navigateApp = useNavigateApp(); @@ -112,6 +103,9 @@ export const SettingsSkillForm = ({ mode }: { mode: 'create' | 'edit' }) => { const [isReadonlyMode, setIsReadonlyMode] = useState(false); const [originalFormValues, setOriginalFormValues] = useState(null); + const [initializedSkillId, setInitializedSkillId] = useState( + null, + ); const { openModal, closeModal } = useModal(); const isEditMode = mode === 'edit'; @@ -139,9 +133,13 @@ export const SettingsSkillForm = ({ mode }: { mode: 'create' | 'edit' }) => { if (data) { const skill = data?.skill; if (isDefined(skill)) { - if (!skill.isCustom) { - setIsReadonlyMode(true); + if (initializedSkillId === skill.id) { + return; } + + setInitializedSkillId(skill.id); + setIsReadonlyMode(!skill.isCustom); + const computedNameFromLabel = computeMetadataNameFromLabel(skill.label); const isLabelSyncedWithName = skill.name === computedNameFromLabel; @@ -162,7 +160,7 @@ export const SettingsSkillForm = ({ mode }: { mode: 'create' | 'edit' }) => { navigateApp(AppPath.NotFound); } } - }, [data, enqueueErrorSnackBar, navigateApp]); + }, [data, enqueueErrorSnackBar, initializedSkillId, navigateApp]); useEffect(() => { if (skillQueryError) { @@ -408,21 +406,21 @@ export const SettingsSkillForm = ({ mode }: { mode: 'create' | 'edit' }) => { ) : loading ? ( t`Skill` ) : ( - - handleFieldChange('label', value)} - placeholder={t`Skill name`} - /> - + handleFieldChange('label', value)} + placeholder={t`Skill name`} + /> ); + const SkillIcon = getIcon(formValues.icon || 'IconSparkles'); + return ( } actionButton={ isCreateMode ? ( { /> handleFieldChange('label', value)} diff --git a/packages/twenty-front/src/pages/settings/ai/SettingsToolDetail.tsx b/packages/twenty-front/src/pages/settings/ai/SettingsToolDetail.tsx index 960370d915..da71eb3fec 100644 --- a/packages/twenty-front/src/pages/settings/ai/SettingsToolDetail.tsx +++ b/packages/twenty-front/src/pages/settings/ai/SettingsToolDetail.tsx @@ -29,6 +29,7 @@ import { GetToolIndexDocument, GetToolInputSchemaDocument, } from '~/generated-metadata/graphql'; +import { SettingsToolIcon } from '~/pages/settings/ai/components/SettingsToolIcon'; import { SettingsToolParameterTable } from '~/pages/settings/ai/components/SettingsToolParameterTable'; const DELETE_TOOL_MODAL_ID = 'delete-tool-modal'; @@ -183,6 +184,13 @@ export const SettingsToolDetail = () => { (displayName ?? '') ) } + icon={ + + } links={[ { children: t`Workspace`, diff --git a/packages/twenty-front/src/pages/settings/ai/components/SettingsToolIcon.tsx b/packages/twenty-front/src/pages/settings/ai/components/SettingsToolIcon.tsx index 58e0c3899b..6597587d99 100644 --- a/packages/twenty-front/src/pages/settings/ai/components/SettingsToolIcon.tsx +++ b/packages/twenty-front/src/pages/settings/ai/components/SettingsToolIcon.tsx @@ -17,14 +17,6 @@ import { } from 'twenty-ui/icon'; import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants'; -type ApplicationInfo = { - name: string; -}; - -type MarketplaceAppInfo = { - logo?: string | null; -}; - type SettingsToolIconProps = { icon?: string | null; toolName?: string; @@ -33,6 +25,14 @@ type SettingsToolIconProps = { marketplaceApp?: MarketplaceAppInfo; }; +type ApplicationInfo = { + name: string; +}; + +type MarketplaceAppInfo = { + logo?: string | null; +}; + const getOperationIcon = (toolName: string): IconComponent | null => { if (toolName.startsWith('create_')) return IconPlus; if (toolName.startsWith('update_')) return IconEdit; @@ -95,11 +95,10 @@ export const SettingsToolIcon = ({ const { theme } = useContext(ThemeContext); const { objectMetadataItems } = useObjectMetadataItems(); - // Custom tools: application/marketplace icons if (isDefined(application) && isDefined(marketplaceApp?.logo)) { return ( > = { string: IllustrationIconText, + date: IllustrationIconCalendarEvent, + 'date-time': IllustrationIconCalendarTime, number: IllustrationIconNumbers, integer: IllustrationIconNumbers, boolean: IllustrationIconToggle, @@ -83,6 +87,11 @@ const getDisplayType = (property: SchemaProperty): string => { return property.type ?? ''; }; +const getTypeIconKey = (property: SchemaProperty): string => + isDefined(property.format) && isDefined(TYPE_ICON_MAP[property.format]) + ? property.format + : (property.type ?? ''); + export const SettingsToolParameterTable = ({ schemaProperties, requiredFields, @@ -112,7 +121,8 @@ export const SettingsToolParameterTable = ({ {entries.map(([paramName, property], index) => { const infoIconId = `param-info-${index}`; - const TypeIcon = TYPE_ICON_MAP[property.type ?? '']; + const displayType = getDisplayType(property); + const TypeIcon = TYPE_ICON_MAP[getTypeIconKey(property)]; return ( )} - {getDisplayType(property)} + {displayType} {requiredFields?.includes(paramName) ? t`Yes` : ''} diff --git a/packages/twenty-front/src/pages/settings/ai/hooks/useSettingsAgentToolsTable.ts b/packages/twenty-front/src/pages/settings/ai/hooks/useSettingsAgentToolsTable.ts index 64059aeac6..2831de21d3 100644 --- a/packages/twenty-front/src/pages/settings/ai/hooks/useSettingsAgentToolsTable.ts +++ b/packages/twenty-front/src/pages/settings/ai/hooks/useSettingsAgentToolsTable.ts @@ -62,8 +62,6 @@ export const useSettingsAgentToolsTable = () => { ]), ); - // MarketplaceApp.id IS the universal identifier — see - // marketplace-query.service.ts where `id: registration.universalIdentifier`. const marketplaceAppByUniversalIdentifier = new Map( (marketplaceAppsData?.findManyMarketplaceApps ?? []).map( (marketplaceApp) => [marketplaceApp.id, marketplaceApp], diff --git a/packages/twenty-front/src/pages/settings/applications/SettingsApplicationDetails.tsx b/packages/twenty-front/src/pages/settings/applications/SettingsApplicationDetails.tsx index 6db5a2de4b..e6946b497b 100644 --- a/packages/twenty-front/src/pages/settings/applications/SettingsApplicationDetails.tsx +++ b/packages/twenty-front/src/pages/settings/applications/SettingsApplicationDetails.tsx @@ -1,4 +1,5 @@ import { CurrentApplicationContext } from '@/applications/contexts/CurrentApplicationContext'; +import { AppChip } from '@/applications/components/AppChip'; import { useResolvedApplicationDescription } from '@/applications/hooks/useResolvedApplicationDescription'; import { isTwentyStandardApplication } from '@/applications/utils/isTwentyStandardApplication'; import { isWorkspaceCustomApplication } from '@/applications/utils/isWorkspaceCustomApplication'; @@ -313,6 +314,19 @@ export const SettingsApplicationDetails = () => { + ) : undefined + } links={[ { children: t`Workspace`, diff --git a/packages/twenty-front/src/pages/settings/applications/SettingsApplicationRegistrationDetails.tsx b/packages/twenty-front/src/pages/settings/applications/SettingsApplicationRegistrationDetails.tsx index 52d33eec34..007ddb1123 100644 --- a/packages/twenty-front/src/pages/settings/applications/SettingsApplicationRegistrationDetails.tsx +++ b/packages/twenty-front/src/pages/settings/applications/SettingsApplicationRegistrationDetails.tsx @@ -21,18 +21,10 @@ import { SettingsApplicationRegistrationGeneralTab } from '~/pages/settings/appl import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue'; import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState'; import { getAbsoluteImageUrl } from '~/utils/image/getAbsoluteImageUrl'; -import { styled } from '@linaria/react'; -import { themeCssVariables } from 'twenty-ui/theme-constants'; const REGISTRATION_DETAIL_TAB_LIST_ID = 'application-registration-detail-tab-list'; -const StyledTitleContainer = styled.div` - align-items: center; - display: flex; - gap: ${themeCssVariables.spacing[2]}; -`; - export const SettingsApplicationRegistrationDetails = () => { const { t } = useLingui(); @@ -95,17 +87,15 @@ export const SettingsApplicationRegistrationDetails = () => { return ( - - {registration.name} - + title={registration.name} + icon={ + } tag={} links={[ diff --git a/packages/twenty-front/src/pages/settings/applications/SettingsAvailableApplicationDetails.tsx b/packages/twenty-front/src/pages/settings/applications/SettingsAvailableApplicationDetails.tsx index 266a47b79e..4478ee2264 100644 --- a/packages/twenty-front/src/pages/settings/applications/SettingsAvailableApplicationDetails.tsx +++ b/packages/twenty-front/src/pages/settings/applications/SettingsAvailableApplicationDetails.tsx @@ -1,4 +1,5 @@ import { CurrentApplicationContext } from '@/applications/contexts/CurrentApplicationContext'; +import { AppChip } from '@/applications/components/AppChip'; import { SettingsApplicationInstallPermissionValidationModal } from '@/marketplace/components/SettingsApplicationInstallPermissionValidationModal'; import { useInstallMarketplaceAppWithPermissionValidation } from '@/marketplace/hooks/useInstallMarketplaceAppWithPermissionValidation'; import { useUpgradeApplication } from '@/marketplace/hooks/useUpgradeApplication'; @@ -285,6 +286,17 @@ export const SettingsAvailableApplicationDetails = () => { { children: displayName }, ]} title={displayName} + icon={ + + } > {isUnlisted && (