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 && (