Improve security settings card grouping and description overflow (#17928)
# After - Added a separtor between the two audit logs cards - Rename the audit log card to avoid repetition - Grouped "Invite by link" and "2 factor auth" in one group - Changed the card component description to always be one line max with truncation & tooltips <img width="777" height="1278" alt="CleanShot 2026-02-13 at 17 02 36" src="https://github.com/user-attachments/assets/685c792a-c85b-4521-8c1b-bd9adedc75d9" /> <img width="976" height="690" alt="b49f2eb043b6712d013618bb0a4ef7f011cf2316e1163fbdee4c293bed036ac9" src="https://github.com/user-attachments/assets/6e17aa11-ecdb-4f98-ba50-5cd9b9c5def6" /> --------- Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com> Co-authored-by: Charles Bochet <charlesBochet@users.noreply.github.com> Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
committed by
GitHub
parent
42108e0611
commit
612f7c37a5
+5
@@ -32,6 +32,11 @@ export const StyledSettingsCardTitle = styled.div`
|
||||
margin-bottom: ${({ theme }) => theme.spacing(1)};
|
||||
`;
|
||||
|
||||
export const StyledSettingsCardTextContainer = styled.div`
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
`;
|
||||
|
||||
export const StyledSettingsCardDescription = styled.div`
|
||||
color: ${({ theme }) => theme.font.color.secondary};
|
||||
font-size: ${({ theme }) => theme.font.size.sm};
|
||||
|
||||
+14
-7
@@ -2,21 +2,26 @@ import {
|
||||
StyledSettingsCardContent,
|
||||
StyledSettingsCardDescription,
|
||||
StyledSettingsCardIcon,
|
||||
StyledSettingsCardTextContainer,
|
||||
StyledSettingsCardTitle,
|
||||
} from '@/settings/components/SettingsOptions/SettingsCardContentBase';
|
||||
import { SettingsOptionIconCustomizer } from '@/settings/components/SettingsOptions/SettingsOptionIconCustomizer';
|
||||
import styled from '@emotion/styled';
|
||||
import { type IconComponent } from 'twenty-ui/display';
|
||||
import {
|
||||
type IconComponent,
|
||||
OverflowingTextWithTooltip,
|
||||
} from 'twenty-ui/display';
|
||||
|
||||
type SettingsOptionCardContentButtonProps = {
|
||||
Icon?: IconComponent;
|
||||
title: React.ReactNode;
|
||||
description?: string | React.ReactNode;
|
||||
description?: string;
|
||||
disabled?: boolean;
|
||||
Button?: React.ReactNode;
|
||||
};
|
||||
|
||||
const StyledButtonContainer = styled.div`
|
||||
flex-shrink: 0;
|
||||
margin-left: auto;
|
||||
`;
|
||||
|
||||
@@ -34,12 +39,14 @@ export const SettingsOptionCardContentButton = ({
|
||||
<SettingsOptionIconCustomizer Icon={Icon} />
|
||||
</StyledSettingsCardIcon>
|
||||
)}
|
||||
<div>
|
||||
<StyledSettingsCardTextContainer>
|
||||
<StyledSettingsCardTitle>{title}</StyledSettingsCardTitle>
|
||||
<StyledSettingsCardDescription>
|
||||
{description}
|
||||
</StyledSettingsCardDescription>
|
||||
</div>
|
||||
{description && (
|
||||
<StyledSettingsCardDescription>
|
||||
<OverflowingTextWithTooltip text={description} />
|
||||
</StyledSettingsCardDescription>
|
||||
)}
|
||||
</StyledSettingsCardTextContainer>
|
||||
{Button && <StyledButtonContainer>{Button}</StyledButtonContainer>}
|
||||
</StyledSettingsCardContent>
|
||||
);
|
||||
|
||||
+8
-4
@@ -3,10 +3,14 @@ import {
|
||||
StyledSettingsCardContent,
|
||||
StyledSettingsCardDescription,
|
||||
StyledSettingsCardIcon,
|
||||
StyledSettingsCardTextContainer,
|
||||
StyledSettingsCardTitle,
|
||||
} from '@/settings/components/SettingsOptions/SettingsCardContentBase';
|
||||
import { SettingsOptionIconCustomizer } from '@/settings/components/SettingsOptions/SettingsOptionIconCustomizer';
|
||||
import { type IconComponent } from 'twenty-ui/display';
|
||||
import {
|
||||
type IconComponent,
|
||||
OverflowingTextWithTooltip,
|
||||
} from 'twenty-ui/display';
|
||||
|
||||
type SettingsOptionCardContentCounterProps = {
|
||||
Icon?: IconComponent;
|
||||
@@ -38,14 +42,14 @@ export const SettingsOptionCardContentCounter = ({
|
||||
<SettingsOptionIconCustomizer Icon={Icon} />
|
||||
</StyledSettingsCardIcon>
|
||||
)}
|
||||
<div>
|
||||
<StyledSettingsCardTextContainer>
|
||||
<StyledSettingsCardTitle>{title}</StyledSettingsCardTitle>
|
||||
{description && (
|
||||
<StyledSettingsCardDescription>
|
||||
{description}
|
||||
<OverflowingTextWithTooltip text={description} />
|
||||
</StyledSettingsCardDescription>
|
||||
)}
|
||||
</div>
|
||||
</StyledSettingsCardTextContainer>
|
||||
<SettingsCounter
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
|
||||
+14
-7
@@ -2,21 +2,26 @@ import {
|
||||
StyledSettingsCardContent,
|
||||
StyledSettingsCardDescription,
|
||||
StyledSettingsCardIcon,
|
||||
StyledSettingsCardTextContainer,
|
||||
StyledSettingsCardTitle,
|
||||
} from '@/settings/components/SettingsOptions/SettingsCardContentBase';
|
||||
import { SettingsOptionIconCustomizer } from '@/settings/components/SettingsOptions/SettingsOptionIconCustomizer';
|
||||
import styled from '@emotion/styled';
|
||||
import { type IconComponent } from 'twenty-ui/display';
|
||||
import {
|
||||
type IconComponent,
|
||||
OverflowingTextWithTooltip,
|
||||
} from 'twenty-ui/display';
|
||||
|
||||
type SettingsOptionCardContentSelectProps = {
|
||||
Icon?: IconComponent;
|
||||
title: React.ReactNode;
|
||||
description?: string | React.ReactNode;
|
||||
description?: string;
|
||||
disabled?: boolean;
|
||||
children?: React.ReactNode;
|
||||
};
|
||||
|
||||
const StyledSelectContainer = styled.div`
|
||||
flex-shrink: 0;
|
||||
justify-content: flex-end;
|
||||
margin-left: auto;
|
||||
max-width: 120px;
|
||||
@@ -36,12 +41,14 @@ export const SettingsOptionCardContentSelect = ({
|
||||
<SettingsOptionIconCustomizer Icon={Icon} />
|
||||
</StyledSettingsCardIcon>
|
||||
)}
|
||||
<div>
|
||||
<StyledSettingsCardTextContainer>
|
||||
<StyledSettingsCardTitle>{title}</StyledSettingsCardTitle>
|
||||
<StyledSettingsCardDescription>
|
||||
{description}
|
||||
</StyledSettingsCardDescription>
|
||||
</div>
|
||||
{description && (
|
||||
<StyledSettingsCardDescription>
|
||||
<OverflowingTextWithTooltip text={description} />
|
||||
</StyledSettingsCardDescription>
|
||||
)}
|
||||
</StyledSettingsCardTextContainer>
|
||||
<StyledSelectContainer>{children}</StyledSelectContainer>
|
||||
</StyledSettingsCardContent>
|
||||
);
|
||||
|
||||
+14
-7
@@ -3,13 +3,17 @@ import {
|
||||
StyledSettingsCardContent,
|
||||
StyledSettingsCardDescription,
|
||||
StyledSettingsCardIcon,
|
||||
StyledSettingsCardTextContainer,
|
||||
StyledSettingsCardTitle,
|
||||
} from '@/settings/components/SettingsOptions/SettingsCardContentBase';
|
||||
import { SettingsOptionIconCustomizer } from '@/settings/components/SettingsOptions/SettingsOptionIconCustomizer';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { useId } from 'react';
|
||||
import { type IconComponent } from 'twenty-ui/display';
|
||||
import {
|
||||
type IconComponent,
|
||||
OverflowingTextWithTooltip,
|
||||
} from 'twenty-ui/display';
|
||||
import { Toggle } from 'twenty-ui/input';
|
||||
|
||||
const StyledSettingsCardToggleContent = styled(StyledSettingsCardContent)`
|
||||
@@ -27,6 +31,7 @@ const StyledSettingsCardToggleButton = styled(Toggle)<{
|
||||
}>`
|
||||
align-self: ${({ toggleCentered }) =>
|
||||
toggleCentered ? 'center' : 'flex-start'};
|
||||
flex-shrink: 0;
|
||||
margin-left: auto;
|
||||
`;
|
||||
|
||||
@@ -39,7 +44,7 @@ const StyledSettingsCardToggleCover = styled.span`
|
||||
type SettingsOptionCardContentToggleProps = {
|
||||
Icon?: IconComponent;
|
||||
title: React.ReactNode;
|
||||
description?: React.ReactNode;
|
||||
description?: string;
|
||||
divider?: boolean;
|
||||
disabled?: boolean;
|
||||
advancedMode?: boolean;
|
||||
@@ -70,17 +75,19 @@ export const SettingsOptionCardContentToggle = ({
|
||||
<SettingsOptionIconCustomizer Icon={Icon} />
|
||||
</StyledSettingsCardIcon>
|
||||
)}
|
||||
<div>
|
||||
<StyledSettingsCardTextContainer>
|
||||
<StyledSettingsCardTitle>
|
||||
<label htmlFor={toggleId}>
|
||||
{title}
|
||||
<StyledSettingsCardToggleCover />
|
||||
</label>
|
||||
</StyledSettingsCardTitle>
|
||||
<StyledSettingsCardDescription>
|
||||
{description}
|
||||
</StyledSettingsCardDescription>
|
||||
</div>
|
||||
{description && (
|
||||
<StyledSettingsCardDescription>
|
||||
<OverflowingTextWithTooltip text={description} />
|
||||
</StyledSettingsCardDescription>
|
||||
)}
|
||||
</StyledSettingsCardTextContainer>
|
||||
<StyledSettingsCardToggleButton
|
||||
id={toggleId}
|
||||
value={checked}
|
||||
|
||||
+3
-2
@@ -8,7 +8,6 @@ import { SettingsOptionCardContentSelect } from '@/settings/components/SettingsO
|
||||
import { ADVANCED_SETTINGS_ANIMATION_DURATION } from '@/settings/constants/AdvancedSettingsAnimationDurations';
|
||||
import { useDateSettingsFormInitialValues } from '@/settings/data-model/fields/forms/date/hooks/useDateSettingsFormInitialValues';
|
||||
import { getDisplayFormatLabel } from '@/settings/data-model/fields/forms/date/utils/getDisplayFormatLabel';
|
||||
import { getDisplayFormatSelectDescription } from '@/settings/data-model/fields/forms/date/utils/getDisplayFormatSelectDescription';
|
||||
import { Select } from '@/ui/input/components/Select';
|
||||
import { SettingsTextInput } from '@/ui/input/components/SettingsTextInput';
|
||||
import styled from '@emotion/styled';
|
||||
@@ -71,7 +70,9 @@ export const SettingsDataModelFieldDateForm = ({
|
||||
isDateFieldCustomDisplayFormat(activeDisplayFormat);
|
||||
|
||||
const displayFormatSelectDescription =
|
||||
getDisplayFormatSelectDescription(activeDisplayFormat);
|
||||
activeDisplayFormat === FieldDateDisplayFormat.CUSTOM
|
||||
? t`Enter in Unicode format`
|
||||
: t`Choose the format used to display date value`;
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
-24
@@ -1,24 +0,0 @@
|
||||
import { FieldDateDisplayFormat } from '@/object-record/record-field/ui/types/FieldMetadata';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
|
||||
export const getDisplayFormatSelectDescription = (
|
||||
selectedDisplayFormat: FieldDateDisplayFormat,
|
||||
) => {
|
||||
if (selectedDisplayFormat === FieldDateDisplayFormat.CUSTOM) {
|
||||
return (
|
||||
<Trans>
|
||||
Enter in{' '}
|
||||
<a
|
||||
href="https://www.unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
style={{ textDecoration: 'underline', color: 'inherit' }}
|
||||
>
|
||||
Unicode
|
||||
</a>{' '}
|
||||
format
|
||||
</Trans>
|
||||
);
|
||||
}
|
||||
return <Trans>Choose the format used to display date value</Trans>;
|
||||
};
|
||||
+2
-23
@@ -1,17 +1,14 @@
|
||||
import { Trans, useLingui } from '@lingui/react/macro';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { useFormContext } from 'react-hook-form';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { DOCUMENTATION_PATHS } from 'twenty-shared/constants';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { IconLink } from 'twenty-ui/display';
|
||||
|
||||
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
|
||||
import { SettingsOptionCardContentSelect } from '@/settings/components/SettingsOptions/SettingsOptionCardContentSelect';
|
||||
import { SettingsOptionCardContentToggle } from '@/settings/components/SettingsOptions/SettingsOptionCardContentToggle';
|
||||
import { getDocumentationUrl } from '@/support/utils/getDocumentationUrl';
|
||||
import { Select } from '@/ui/input/components/Select';
|
||||
import { isAdvancedModeEnabledState } from '@/ui/navigation/navigation-drawer/states/isAdvancedModeEnabledState';
|
||||
import { RelationType } from '~/generated-metadata/graphql';
|
||||
@@ -29,12 +26,6 @@ export const SettingsDataModelFieldRelationJunctionForm = ({
|
||||
useFormContext<SettingsDataModelFieldEditFormValues>();
|
||||
|
||||
const isAdvancedModeEnabled = useRecoilValue(isAdvancedModeEnabledState);
|
||||
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
|
||||
|
||||
const documentationUrl = getDocumentationUrl({
|
||||
locale: currentWorkspaceMember?.locale,
|
||||
path: DOCUMENTATION_PATHS.USER_GUIDE_DATA_MODEL_HOW_TOS_CREATE_MANY_TO_MANY_RELATIONS,
|
||||
});
|
||||
|
||||
const { objectMetadataItem: sourceObjectMetadataItem } =
|
||||
useObjectMetadataItem({ objectNameSingular });
|
||||
@@ -163,19 +154,7 @@ export const SettingsDataModelFieldRelationJunctionForm = ({
|
||||
<SettingsOptionCardContentToggle
|
||||
Icon={IconLink}
|
||||
title={t`This is a relation to a Junction Object`}
|
||||
description={
|
||||
<Trans>
|
||||
Build many-to-many relations.{' '}
|
||||
<a
|
||||
href={documentationUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
style={{ textDecoration: 'underline', color: 'inherit' }}
|
||||
>
|
||||
Learn more
|
||||
</a>
|
||||
</Trans>
|
||||
}
|
||||
description={t`Build many-to-many relations`}
|
||||
checked={isJunctionConfigEnabled}
|
||||
onChange={handleJunctionToggle}
|
||||
divider={isJunctionConfigEnabled}
|
||||
|
||||
+1
-2
@@ -175,12 +175,11 @@ export const SettingsSecurityAuthProvidersOptionsList = () => {
|
||||
description={t`Allow the invitation of new users by sharing an invite link.`}
|
||||
checked={currentWorkspace.isPublicInviteLinkEnabled}
|
||||
advancedMode
|
||||
divider
|
||||
onChange={() =>
|
||||
handleChange(!currentWorkspace.isPublicInviteLinkEnabled)
|
||||
}
|
||||
/>
|
||||
</Card>
|
||||
<Card rounded>
|
||||
<Toggle2FA />
|
||||
</Card>
|
||||
</>
|
||||
|
||||
@@ -10,6 +10,7 @@ import { isClickHouseConfiguredState } from '@/client-config/states/isClickHouse
|
||||
import { isMultiWorkspaceEnabledState } from '@/client-config/states/isMultiWorkspaceEnabledState';
|
||||
import { SettingsOptionCardContentButton } from '@/settings/components/SettingsOptions/SettingsOptionCardContentButton';
|
||||
import { SettingsOptionCardContentCounter } from '@/settings/components/SettingsOptions/SettingsOptionCardContentCounter';
|
||||
import { Separator } from '@/settings/components/Separator';
|
||||
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
|
||||
import { SettingsSSOIdentitiesProvidersListCard } from '@/settings/security/components/SSO/SettingsSSOIdentitiesProvidersListCard';
|
||||
import { SettingsSecurityAuthBypassOptionsList } from '@/settings/security/components/SettingsSecurityAuthBypassOptionsList';
|
||||
@@ -237,7 +238,7 @@ export const SettingsSecurity = () => {
|
||||
<Card rounded>
|
||||
<SettingsOptionCardContentButton
|
||||
Icon={IconHistory}
|
||||
title={t`Audit Logs`}
|
||||
title={t`Workspace Events`}
|
||||
description={
|
||||
!isClickHouseConfigured
|
||||
? t`ClickHouse is required for audit logs. Contact your administrator.`
|
||||
@@ -260,16 +261,19 @@ export const SettingsSecurity = () => {
|
||||
}
|
||||
/>
|
||||
{isEventLogsEnabled && (
|
||||
<SettingsOptionCardContentCounter
|
||||
Icon={IconClockHour8}
|
||||
title={t`Log retention`}
|
||||
description={t`Number of days to retain audit logs (30-1095 days)`}
|
||||
value={currentWorkspace?.eventLogRetentionDays ?? 90}
|
||||
onChange={handleEventLogRetentionDaysChange}
|
||||
minValue={30}
|
||||
maxValue={1095}
|
||||
showButtons={false}
|
||||
/>
|
||||
<>
|
||||
<Separator />
|
||||
<SettingsOptionCardContentCounter
|
||||
Icon={IconClockHour8}
|
||||
title={t`Log retention`}
|
||||
description={t`Number of days to retain audit logs (30-1095 days)`}
|
||||
value={currentWorkspace?.eventLogRetentionDays ?? 90}
|
||||
onChange={handleEventLogRetentionDaysChange}
|
||||
minValue={30}
|
||||
maxValue={1095}
|
||||
showButtons={false}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Card>
|
||||
</Section>
|
||||
|
||||
Reference in New Issue
Block a user