Fix settings UI polish pass 2 (#21540)
## Summary This PR groups the requested second UI polish pass across the app shell, settings pages, data model, community, AI tools, and developer setup surfaces. ### Shell and navigation polish - Lets the app content card fill the available viewport and removes the bottom-left radius on the main container. - Uses an outside-looking container border treatment so the rounded top-left edge matches the Figma frame without a double border. - Restores the separator between the AI chat side panel and the record container. - Updates navigation drawer background/skeleton tones to use gray/3 consistently. - Simplifies the full-screen backend error fallback so the white screen fills the viewport. ### Settings page polish - Moves the layout customization action from the settings navbar into a settings card under the hero. - Tightens layout page copy now that the customization surfaces are not directly manageable yet. - Aligns role detail title sizing and hover treatment with other settings headers. - Supports settings card brand icon colors, then restores Discord and X brand logos on the Community page. - Adds the Community discovery cover, moves community/social content to the top, and reorganizes Partners and Features. - Keeps setting card subtitle and separator behavior available for the updated settings cards. ### Data model and object settings polish - Adds a shared data-model table body wrapper so object, field, and relation tables all keep the expected bottom border. - Displays the object icon in the object settings navbar. - Shares the new-field wizard parent object icon wrapper and keeps the object icon at 64% opacity in wizard headers. - Updates the new-field type selector icon treatment to match the wizard header opacity behavior. ### Integrations, tools, and API polish - Reworks the MCP setup config card with light syntax coloring and a top-right copy icon button instead of the bottom copy section. - Fixes email import icons to match the Figma asset shape. - Ensures AI tool table icons and chevrons never fall back to black. ## Validation - `npx nx lint:diff-with-main twenty-front` - `npx tsc -p packages/twenty-front/tsconfig.json --noEmit` - `git diff --check` - Manual local visual pass on `apple.localhost:3001` for companies, settings/community, data model, object settings, new-field wizard, layout settings, account import, MCP setup, AI tools, and role detail pages. ## Visual QA ### Shell and settings frame <img width="1308" height="1397" alt="Shell and settings frame before/after board" src="https://raw.githubusercontent.com/twentyhq/twenty/pr-assets/ui-fixes-2/visual-qa/ui-fixes-2/shell-settings-frame.png" /> ### Community and data model <img width="1308" height="1806" alt="Community and data model before/after board" src="https://raw.githubusercontent.com/twentyhq/twenty/pr-assets/ui-fixes-2/visual-qa/ui-fixes-2/community-data-model.png" /> ### Integrations and tools <img width="1308" height="1397" alt="Integrations and tools before/after board" src="https://raw.githubusercontent.com/twentyhq/twenty/pr-assets/ui-fixes-2/visual-qa/ui-fixes-2/integrations-tools.png" /> <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21540?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. -->
This commit is contained in:
committed by
GitHub
parent
5d0a4b8db4
commit
d06e687b77
+7
-12
@@ -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 (
|
||||
<StyledContainer>
|
||||
<PageBody>
|
||||
<AppErrorDisplay
|
||||
error={error}
|
||||
resetErrorBoundary={resetErrorBoundary}
|
||||
title={title}
|
||||
/>
|
||||
</PageBody>
|
||||
<AppErrorDisplay
|
||||
error={error}
|
||||
resetErrorBoundary={resetErrorBoundary}
|
||||
title={title}
|
||||
/>
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
||||
|
||||
+1
-1
@@ -32,7 +32,7 @@ export const NavigationMenuItemSkeletonLoader = () => {
|
||||
const { theme } = useContext(ThemeContext);
|
||||
return (
|
||||
<SkeletonTheme
|
||||
baseColor={theme.background.tertiary}
|
||||
baseColor={theme.grayScale.gray3}
|
||||
highlightColor={theme.background.transparent.lighter}
|
||||
borderRadius={4}
|
||||
>
|
||||
|
||||
+52
-35
@@ -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 (
|
||||
<StyledCardMedia className={className}>
|
||||
<StyledFolderRow>
|
||||
<StyledFolderIcon isDisabled={isSelectedFolders} />
|
||||
<StyledFolderLabel isDisabled={isSelectedFolders} />
|
||||
</StyledFolderRow>
|
||||
<StyledFolderRow>
|
||||
<StyledFolderIcon isDisabled={isSelectedFolders} />
|
||||
<StyledFolderLabel isDisabled={isSelectedFolders} />
|
||||
</StyledFolderRow>
|
||||
<StyledFolderRow>
|
||||
<StyledFolderIcon />
|
||||
<StyledFolderLabel />
|
||||
</StyledFolderRow>
|
||||
<StyledFolderRow>
|
||||
<StyledFolderIcon isDisabled={isSelectedFolders} />
|
||||
<StyledFolderLabel isDisabled={isSelectedFolders} />
|
||||
</StyledFolderRow>
|
||||
<StyledFolderRow>
|
||||
<StyledFolderIcon />
|
||||
<StyledFolderLabel />
|
||||
</StyledFolderRow>
|
||||
<StyledCardMediaContent>
|
||||
<StyledFolderRow>
|
||||
<StyledFolderIcon isDisabled={isSelectedFolders} />
|
||||
<StyledFolderLabel isDisabled={isSelectedFolders} />
|
||||
</StyledFolderRow>
|
||||
<StyledFolderRow>
|
||||
<StyledFolderIcon isDisabled={isSelectedFolders} />
|
||||
<StyledFolderLabel isDisabled={isSelectedFolders} />
|
||||
</StyledFolderRow>
|
||||
<StyledFolderRow>
|
||||
<StyledFolderIcon />
|
||||
<StyledFolderLabel />
|
||||
</StyledFolderRow>
|
||||
<StyledFolderRow>
|
||||
<StyledFolderIcon isDisabled={isSelectedFolders} />
|
||||
<StyledFolderLabel isDisabled={isSelectedFolders} />
|
||||
</StyledFolderRow>
|
||||
<StyledFolderRow>
|
||||
<StyledFolderIcon />
|
||||
<StyledFolderLabel />
|
||||
</StyledFolderRow>
|
||||
</StyledCardMediaContent>
|
||||
</StyledCardMedia>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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 = ({
|
||||
<StyledCardContentContainer>
|
||||
<CardContent>
|
||||
<StyledHeader>
|
||||
<StyledIconContainer>{Icon}</StyledIconContainer>
|
||||
<StyledIconContainer disabled={disabled} iconColor={iconColor}>
|
||||
{Icon}
|
||||
</StyledIconContainer>
|
||||
<StyledTitle disabled={disabled}>
|
||||
{title}
|
||||
{soon && <Pill label={t`Soon`} />}
|
||||
|
||||
+14
-3
@@ -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 = ({
|
||||
<Card rounded>
|
||||
<StyledCoverContainer>
|
||||
<StyledImage src={src} alt="" aria-hidden />
|
||||
{isDiscoveryVideoEnabled && (
|
||||
{shouldDisplayVideo && (
|
||||
<StyledOverlay>
|
||||
<HeroPlayButton
|
||||
onClick={() => openModal(modalInstanceId)}
|
||||
@@ -84,8 +92,11 @@ export const SettingsDiscoveryHeroCard = ({
|
||||
</StyledOverlay>
|
||||
)}
|
||||
</StyledCoverContainer>
|
||||
{footer !== undefined && footer !== null && (
|
||||
<StyledFooter>{footer}</StyledFooter>
|
||||
)}
|
||||
</Card>
|
||||
{isDiscoveryVideoEnabled && (
|
||||
{shouldDisplayVideo && (
|
||||
<SettingsCustomizeVideoModal
|
||||
modalInstanceId={modalInstanceId}
|
||||
tabsInstanceId={tabsInstanceId}
|
||||
|
||||
@@ -8,6 +8,7 @@ import { isDefined } from 'twenty-shared/utils';
|
||||
type SettingsPageLayoutProps = {
|
||||
links: BreadcrumbProps['links'];
|
||||
title?: ReactNode;
|
||||
icon?: ReactNode;
|
||||
actionButton?: ReactNode;
|
||||
secondaryBar?: ReactNode;
|
||||
children: ReactNode;
|
||||
@@ -18,6 +19,7 @@ type SettingsPageLayoutProps = {
|
||||
export const SettingsPageLayout = ({
|
||||
links,
|
||||
title,
|
||||
icon,
|
||||
actionButton,
|
||||
secondaryBar,
|
||||
children,
|
||||
@@ -29,6 +31,7 @@ export const SettingsPageLayout = ({
|
||||
<PageCardHeader
|
||||
links={links}
|
||||
title={title}
|
||||
icon={icon}
|
||||
tag={tag}
|
||||
actionButton={actionButton}
|
||||
centerTitle
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
import { styled } from '@linaria/react';
|
||||
import { themeCssVariables } from 'twenty-ui-deprecated/theme-constants';
|
||||
|
||||
export const StyledSettingsDataModelTableBodyContainer = styled.div`
|
||||
border-bottom: 1px solid ${themeCssVariables.border.color.light};
|
||||
`;
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
import {
|
||||
ObjectMetadataIcon,
|
||||
type ObjectMetadataIconInput,
|
||||
} from '@/object-metadata/components/ObjectMetadataIcon';
|
||||
import { styled } from '@linaria/react';
|
||||
|
||||
const StyledObjectIconContainer = styled.div`
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
opacity: 0.64;
|
||||
`;
|
||||
|
||||
type SettingsObjectNewFieldHeaderIconProps = {
|
||||
objectMetadataItem: ObjectMetadataIconInput;
|
||||
};
|
||||
|
||||
export const SettingsObjectNewFieldHeaderIcon = ({
|
||||
objectMetadataItem,
|
||||
}: SettingsObjectNewFieldHeaderIconProps) => (
|
||||
<StyledObjectIconContainer>
|
||||
<ObjectMetadataIcon objectMetadataItem={objectMetadataItem} />
|
||||
</StyledObjectIconContainer>
|
||||
);
|
||||
+11
-4
@@ -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 = ({
|
||||
<SettingsCard
|
||||
key={key}
|
||||
Icon={
|
||||
<config.Icon
|
||||
size={theme.icon.size.xl}
|
||||
stroke={theme.icon.stroke.sm}
|
||||
/>
|
||||
<StyledFieldTypeIconContainer>
|
||||
<config.Icon
|
||||
size={theme.icon.size.xl}
|
||||
stroke={theme.icon.stroke.sm}
|
||||
/>
|
||||
</StyledFieldTypeIconContainer>
|
||||
}
|
||||
title={config.label}
|
||||
/>
|
||||
|
||||
+13
-7
@@ -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 = ({
|
||||
))}
|
||||
<TableHeader></TableHeader>
|
||||
</TableRow>
|
||||
{filteredRelationFields.map((fieldMetadataItem) => (
|
||||
<SettingsObjectRelationItemTableRow
|
||||
key={fieldMetadataItem.id}
|
||||
fieldMetadataItem={fieldMetadataItem}
|
||||
objectMetadataItem={objectMetadataItem}
|
||||
/>
|
||||
))}
|
||||
<StyledSettingsDataModelTableBodyContainer>
|
||||
<TableBody>
|
||||
{filteredRelationFields.map((fieldMetadataItem) => (
|
||||
<SettingsObjectRelationItemTableRow
|
||||
key={fieldMetadataItem.id}
|
||||
fieldMetadataItem={fieldMetadataItem}
|
||||
objectMetadataItem={objectMetadataItem}
|
||||
/>
|
||||
))}
|
||||
</TableBody>
|
||||
</StyledSettingsDataModelTableBodyContainer>
|
||||
</Table>
|
||||
</>
|
||||
);
|
||||
|
||||
+98
-13
@@ -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 = '<YOUR_API_KEY>';
|
||||
|
||||
if (!token.includes(apiKeyPlaceholder)) {
|
||||
return <StyledJsonString key={key}>{token}</StyledJsonString>;
|
||||
}
|
||||
|
||||
const [beforePlaceholder, afterPlaceholder] = token.split(apiKeyPlaceholder);
|
||||
|
||||
return (
|
||||
<span key={key}>
|
||||
<StyledJsonString>{beforePlaceholder}</StyledJsonString>
|
||||
<StyledJsonPlaceholder>{apiKeyPlaceholder}</StyledJsonPlaceholder>
|
||||
<StyledJsonString>{afterPlaceholder}</StyledJsonString>
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
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(
|
||||
<StyledJsonKey key={`json-key-${tokenIndex}`}>
|
||||
{quotedString}
|
||||
</StyledJsonKey>,
|
||||
);
|
||||
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.`}
|
||||
/>
|
||||
<Card rounded>
|
||||
<CardContent divider>
|
||||
<StyledPre>{mcpConfig}</StyledPre>
|
||||
</CardContent>
|
||||
<CardContent>
|
||||
<Button
|
||||
title={t`Copy config`}
|
||||
Icon={IconCopy}
|
||||
size="small"
|
||||
variant="secondary"
|
||||
onClick={() =>
|
||||
copyToClipboard(mcpConfig, t`MCP config copied to clipboard`)
|
||||
}
|
||||
/>
|
||||
<StyledMcpConfigContainer>
|
||||
<StyledPre>{getHighlightedMcpConfig(mcpConfig)}</StyledPre>
|
||||
<StyledCopyButtonContainer>
|
||||
<LightIconButton
|
||||
Icon={IconCopy}
|
||||
size="small"
|
||||
accent="tertiary"
|
||||
aria-label={t`Copy MCP config`}
|
||||
title={t`Copy MCP config`}
|
||||
onClick={() =>
|
||||
copyToClipboard(mcpConfig, t`MCP config copied to clipboard`)
|
||||
}
|
||||
/>
|
||||
</StyledCopyButtonContainer>
|
||||
</StyledMcpConfigContainer>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Section>
|
||||
|
||||
+17
-4
@@ -7,11 +7,24 @@ import { t } from '@lingui/core/macro';
|
||||
import { themeCssVariables } from 'twenty-ui-deprecated/theme-constants';
|
||||
|
||||
const StyledHeaderTitle = styled.div`
|
||||
color: ${themeCssVariables.font.color.primary};
|
||||
font-size: ${themeCssVariables.font.size.lg};
|
||||
font-weight: ${themeCssVariables.font.weight.semiBold};
|
||||
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};
|
||||
}
|
||||
@@ -45,7 +58,7 @@ export const SettingsRoleLabelContainer = ({
|
||||
<TitleInput
|
||||
instanceId="role-label-input"
|
||||
disabled={!settingsDraftRole.isEditable}
|
||||
sizeVariant="md"
|
||||
sizeVariant="sm"
|
||||
value={settingsDraftRole.label}
|
||||
onChange={handleChange}
|
||||
placeholder={t`Role name`}
|
||||
|
||||
@@ -37,6 +37,7 @@ const StyledSidePanelWrapper = styled.div<{
|
||||
|
||||
const StyledSidePanel = styled.aside`
|
||||
background: ${themeCssVariables.background.primary};
|
||||
border-left: 1px solid ${themeCssVariables.border.color.medium};
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -25,7 +25,7 @@ import { AnimatePresence, LayoutGroup } from 'framer-motion';
|
||||
import { Outlet } from 'react-router-dom';
|
||||
import { themeCssVariables } from 'twenty-ui-deprecated/theme-constants';
|
||||
const StyledLayout = styled.div`
|
||||
background: ${themeCssVariables.background.tertiary};
|
||||
background: ${themeCssVariables.grayScale.gray3};
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100dvh;
|
||||
|
||||
@@ -19,17 +19,18 @@ const StyledRoot = styled.div`
|
||||
`;
|
||||
|
||||
const StyledMainCardWrapper = styled.div`
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex: 1 1 0;
|
||||
min-width: 0;
|
||||
padding-left: 1px;
|
||||
width: 0;
|
||||
`;
|
||||
|
||||
const StyledCard = styled.div`
|
||||
background: ${themeCssVariables.background.primary};
|
||||
border-left: 1px solid ${themeCssVariables.border.color.medium};
|
||||
border-radius: 12px 0 0 12px;
|
||||
border-right: 1px solid ${themeCssVariables.border.color.medium};
|
||||
border-radius: 16px 0 0 0;
|
||||
box-shadow: 0 0 0 1px ${themeCssVariables.border.color.medium};
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
|
||||
+1
-1
@@ -205,7 +205,7 @@ const StyledIcon = styled.div`
|
||||
|
||||
const StyledIconBackgroundTile = styled.div`
|
||||
align-items: center;
|
||||
background-color: ${themeCssVariables.background.tertiary};
|
||||
background-color: ${themeCssVariables.grayScale.gray3};
|
||||
border-radius: ${themeCssVariables.border.radius.md};
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@ export const NavigationDrawerSectionTitleSkeletonLoader = () => {
|
||||
const { theme } = useContext(ThemeContext);
|
||||
return (
|
||||
<SkeletonTheme
|
||||
baseColor={theme.background.tertiary}
|
||||
baseColor={theme.grayScale.gray3}
|
||||
highlightColor={theme.background.transparent.lighter}
|
||||
borderRadius={4}
|
||||
>
|
||||
|
||||
@@ -97,6 +97,7 @@ export const SettingsAgentToolsTable = ({
|
||||
<IconChevronRight
|
||||
size={theme.icon.size.md}
|
||||
stroke={theme.icon.stroke.sm}
|
||||
color={theme.font.color.tertiary}
|
||||
/>
|
||||
}
|
||||
link={getToolLink(tool)}
|
||||
|
||||
@@ -151,7 +151,7 @@ export const SettingsToolIcon = ({
|
||||
<OperationIcon
|
||||
size="12px"
|
||||
stroke={theme.icon.stroke.md}
|
||||
color={themeCssVariables.grayScale.gray10}
|
||||
color={theme.font.color.tertiary}
|
||||
/>
|
||||
</StyledOperationOverlay>
|
||||
</StyledCompositeContainer>
|
||||
@@ -175,5 +175,11 @@ export const SettingsToolIcon = ({
|
||||
);
|
||||
}
|
||||
|
||||
return <MainIcon size={16} />;
|
||||
return (
|
||||
<MainIcon
|
||||
size={16}
|
||||
stroke={theme.icon.stroke.md}
|
||||
color={theme.font.color.tertiary}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { SettingsCard } from '@/settings/components/SettingsCard';
|
||||
import { SettingsDiscoveryHeroCard } from '@/settings/components/SettingsDiscoveryHeroCard';
|
||||
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
|
||||
import { SettingsLabContent } from '@/settings/lab/components/SettingsLabContent';
|
||||
import { SettingsPageLayout } from '@/settings/components/layout/SettingsPageLayout';
|
||||
@@ -7,23 +8,50 @@ import { t } from '@lingui/core/macro';
|
||||
import { useContext } from 'react';
|
||||
import {
|
||||
H2Title,
|
||||
IconBrandX,
|
||||
IconBriefcase,
|
||||
IconTransform,
|
||||
type IconComponent,
|
||||
useIcons,
|
||||
} from 'twenty-ui-deprecated/display';
|
||||
import { Section } from 'twenty-ui-deprecated/layout';
|
||||
import { ThemeContext } from 'twenty-ui-deprecated/theme-constants';
|
||||
import { SettingsPath } from 'twenty-shared/types';
|
||||
import { getSettingsPath } from 'twenty-shared/utils';
|
||||
import {
|
||||
MOBILE_VIEWPORT,
|
||||
ThemeContext,
|
||||
themeCssVariables,
|
||||
} from 'twenty-ui-deprecated/theme-constants';
|
||||
import coverDark from '~/pages/settings/community/assets/cover-dark.png';
|
||||
import coverLight from '~/pages/settings/community/assets/cover-light.png';
|
||||
|
||||
const SETTINGS_COMMUNITY_HERO_INSTANCE_ID_PREFIX = 'settings-community-hero';
|
||||
|
||||
const StyledCardLink = styled.a`
|
||||
display: block;
|
||||
min-width: 0;
|
||||
text-decoration: none;
|
||||
`;
|
||||
|
||||
const StyledCardsGrid = styled.div`
|
||||
display: grid;
|
||||
gap: ${themeCssVariables.spacing[2]};
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
|
||||
@media (max-width: ${MOBILE_VIEWPORT}px) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledFeaturesContent = styled.div`
|
||||
display: grid;
|
||||
gap: ${themeCssVariables.spacing[4]};
|
||||
`;
|
||||
|
||||
type SettingsCommunityLink = {
|
||||
title: string;
|
||||
description: string;
|
||||
href: string;
|
||||
Icon: IconComponent;
|
||||
iconColor: string;
|
||||
cardTitle: string;
|
||||
};
|
||||
|
||||
@@ -32,63 +60,115 @@ export const SettingsCommunity = () => {
|
||||
const { getIcon } = useIcons();
|
||||
const IconBrandDiscord = getIcon('IconBrandDiscord');
|
||||
|
||||
const communityLinks: SettingsCommunityLink[] = [
|
||||
const socialLinks: SettingsCommunityLink[] = [
|
||||
{
|
||||
title: t`Partners`,
|
||||
description: t`Hire a partner to help you implement and customize Twenty.`,
|
||||
href: 'https://twenty.com/partners/list',
|
||||
Icon: IconBriefcase,
|
||||
cardTitle: t`Browse partners`,
|
||||
},
|
||||
{
|
||||
title: t`Discord`,
|
||||
description: t`Join our community to get help and share feedback.`,
|
||||
href: 'https://discord.com/invite/cx5n4Jzs57',
|
||||
Icon: IconBrandDiscord,
|
||||
iconColor: themeCssVariables.color.blue9,
|
||||
cardTitle: t`Join our Discord`,
|
||||
},
|
||||
{
|
||||
title: t`Releases`,
|
||||
description: t`Check out our latest releases`,
|
||||
href: 'https://twenty.com/releases',
|
||||
Icon: IconTransform,
|
||||
cardTitle: t`Read changelog`,
|
||||
href: 'https://x.com/twentycrm',
|
||||
Icon: IconBrandX,
|
||||
iconColor: themeCssVariables.font.color.primary,
|
||||
cardTitle: t`Follow us on X`,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<SettingsPageLayout
|
||||
title={t`Community`}
|
||||
links={[{ children: t`Other` }, { children: t`Community` }]}
|
||||
links={[
|
||||
{
|
||||
children: t`Other`,
|
||||
href: getSettingsPath(SettingsPath.Community),
|
||||
},
|
||||
{ children: t`Community` },
|
||||
]}
|
||||
>
|
||||
<SettingsPageContainer>
|
||||
{communityLinks.map(({ title, description, href, Icon, cardTitle }) => (
|
||||
<Section key={href}>
|
||||
<H2Title title={title} description={description} />
|
||||
<Section>
|
||||
<SettingsDiscoveryHeroCard
|
||||
lightSrc={coverLight}
|
||||
darkSrc={coverDark}
|
||||
instanceIdPrefix={SETTINGS_COMMUNITY_HERO_INSTANCE_ID_PREFIX}
|
||||
tabs={[]}
|
||||
/>
|
||||
</Section>
|
||||
|
||||
<Section>
|
||||
<H2Title
|
||||
title={t`Join the community`}
|
||||
description={t`Stay up to date with product news and community updates.`}
|
||||
/>
|
||||
<StyledCardsGrid>
|
||||
{socialLinks.map(({ href, Icon, iconColor, cardTitle }) => (
|
||||
<StyledCardLink
|
||||
key={href}
|
||||
href={href}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<SettingsCard
|
||||
Icon={
|
||||
<Icon
|
||||
size={theme.icon.size.md}
|
||||
stroke={theme.icon.stroke.sm}
|
||||
/>
|
||||
}
|
||||
iconColor={iconColor}
|
||||
title={cardTitle}
|
||||
/>
|
||||
</StyledCardLink>
|
||||
))}
|
||||
</StyledCardsGrid>
|
||||
</Section>
|
||||
|
||||
<Section>
|
||||
<H2Title
|
||||
title={t`Partners`}
|
||||
description={t`Hire a partner to help you implement and customize Twenty.`}
|
||||
/>
|
||||
<StyledCardLink
|
||||
href="https://twenty.com/partners/list"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<SettingsCard
|
||||
Icon={
|
||||
<IconBriefcase
|
||||
size={theme.icon.size.md}
|
||||
stroke={theme.icon.stroke.sm}
|
||||
/>
|
||||
}
|
||||
title={t`Browse partners`}
|
||||
/>
|
||||
</StyledCardLink>
|
||||
</Section>
|
||||
|
||||
<Section>
|
||||
<H2Title
|
||||
title={t`Features`}
|
||||
description={t`Try our upcoming features. Note they are still in beta. Please bear with us and report any issues you find.`}
|
||||
/>
|
||||
<StyledFeaturesContent>
|
||||
<SettingsLabContent />
|
||||
<StyledCardLink
|
||||
href={href}
|
||||
href="https://twenty.com/releases"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<SettingsCard
|
||||
Icon={
|
||||
<Icon
|
||||
<IconTransform
|
||||
size={theme.icon.size.md}
|
||||
stroke={theme.icon.stroke.sm}
|
||||
/>
|
||||
}
|
||||
title={cardTitle}
|
||||
title={t`Read changelog`}
|
||||
/>
|
||||
</StyledCardLink>
|
||||
</Section>
|
||||
))}
|
||||
|
||||
<Section>
|
||||
<H2Title
|
||||
title={t`Early access`}
|
||||
description={t`Try our upcoming features. Note they are still in beta. Please bear with us and report any issues you find.`}
|
||||
/>
|
||||
<SettingsLabContent />
|
||||
</StyledFeaturesContent>
|
||||
</Section>
|
||||
</SettingsPageContainer>
|
||||
</SettingsPageLayout>
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 544 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 560 KiB |
@@ -1,6 +1,7 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
import { ObjectMetadataIcon } from '@/object-metadata/components/ObjectMetadataIcon';
|
||||
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
|
||||
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
|
||||
import { ObjectFields } from '@/settings/data-model/object-details/components/tabs/ObjectFields';
|
||||
@@ -135,6 +136,7 @@ export const SettingsObjectDetailPage = () => {
|
||||
return (
|
||||
<SettingsPageLayout
|
||||
title={objectMetadataItem.labelPlural}
|
||||
icon={<ObjectMetadataIcon objectMetadataItem={objectMetadataItem} />}
|
||||
links={[
|
||||
{
|
||||
children: t`Workspace`,
|
||||
|
||||
@@ -5,12 +5,14 @@ import {
|
||||
OBJECT_FIELD_TABLE_ROW_GRID_TEMPLATE_COLUMNS,
|
||||
SettingsObjectFieldItemTableRow,
|
||||
} from '@/settings/data-model/object-details/components/SettingsObjectFieldItemTableRow';
|
||||
import { StyledSettingsDataModelTableBodyContainer } from '@/settings/data-model/components/SettingsDataModelTableBodyContainer';
|
||||
import { settingsObjectFieldsFamilyState } from '@/settings/data-model/object-details/states/settingsObjectFieldsFamilyState';
|
||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||
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';
|
||||
@@ -204,20 +206,24 @@ export const SettingsObjectFieldTable = ({
|
||||
))}
|
||||
<TableHeader></TableHeader>
|
||||
</TableRow>
|
||||
{filteredItems.map((objectSettingsDetailItem) => {
|
||||
const status = objectSettingsDetailItem.fieldMetadataItem.isActive
|
||||
? 'active'
|
||||
: 'disabled';
|
||||
<StyledSettingsDataModelTableBodyContainer>
|
||||
<TableBody>
|
||||
{filteredItems.map((objectSettingsDetailItem) => {
|
||||
const status = objectSettingsDetailItem.fieldMetadataItem.isActive
|
||||
? 'active'
|
||||
: 'disabled';
|
||||
|
||||
return (
|
||||
<SettingsObjectFieldItemTableRow
|
||||
key={objectSettingsDetailItem.fieldMetadataItem.id}
|
||||
settingsObjectDetailTableItem={objectSettingsDetailItem}
|
||||
status={status}
|
||||
mode={mode}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
return (
|
||||
<SettingsObjectFieldItemTableRow
|
||||
key={objectSettingsDetailItem.fieldMetadataItem.id}
|
||||
settingsObjectDetailTableItem={objectSettingsDetailItem}
|
||||
status={status}
|
||||
mode={mode}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
</StyledSettingsDataModelTableBodyContainer>
|
||||
</Table>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -5,8 +5,10 @@ import { useUpdateOneObjectMetadataItem } from '@/object-metadata/hooks/useUpdat
|
||||
import { type EnrichedObjectMetadataItem } from '@/object-metadata/types/EnrichedObjectMetadataItem';
|
||||
import { isHiddenSystemField } from '@/object-metadata/utils/isHiddenSystemField';
|
||||
import { useCombinedGetTotalCount } from '@/object-record/multiple-objects/hooks/useCombinedGetTotalCount';
|
||||
import { StyledSettingsDataModelTableBodyContainer } from '@/settings/data-model/components/SettingsDataModelTableBodyContainer';
|
||||
import { SettingsObjectMetadataItemTableRow } from '@/settings/data-model/object-details/components/SettingsObjectItemTableRow';
|
||||
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
||||
import { TableBody } from '@/ui/layout/table/components/TableBody';
|
||||
import {
|
||||
SETTINGS_OBJECT_TABLE_ROW_GRID_TEMPLATE_COLUMNS,
|
||||
SETTINGS_OBJECT_TABLE_ROW_MOBILE_MIN_WIDTH,
|
||||
@@ -236,56 +238,62 @@ export const SettingsObjectTable = ({
|
||||
)}
|
||||
<TableHeader></TableHeader>
|
||||
</TableRow>
|
||||
{filteredObjectSettingsItems.map((objectSettingsItem) => {
|
||||
const isActive = objectSettingsItem.objectMetadataItem.isActive;
|
||||
<StyledSettingsDataModelTableBodyContainer>
|
||||
<TableBody>
|
||||
{filteredObjectSettingsItems.map((objectSettingsItem) => {
|
||||
const isActive =
|
||||
objectSettingsItem.objectMetadataItem.isActive;
|
||||
|
||||
return (
|
||||
<SettingsObjectMetadataItemTableRow
|
||||
key={objectSettingsItem.objectMetadataItem.namePlural}
|
||||
objectMetadataItem={objectSettingsItem.objectMetadataItem}
|
||||
totalObjectCount={objectSettingsItem.totalObjectCount}
|
||||
action={
|
||||
isActive ? (
|
||||
<StyledIconChevronRightContainer>
|
||||
<IconChevronRight
|
||||
size={theme.icon.size.md}
|
||||
stroke={theme.icon.stroke.sm}
|
||||
/>
|
||||
</StyledIconChevronRightContainer>
|
||||
) : isDDLLocked ? null : (
|
||||
<SettingsObjectInactiveMenuDropDown
|
||||
isCustomObject={
|
||||
objectSettingsItem.objectMetadataItem.isCustom
|
||||
}
|
||||
objectMetadataItemNamePlural={
|
||||
objectSettingsItem.objectMetadataItem.namePlural
|
||||
}
|
||||
onActivate={() =>
|
||||
updateOneObjectMetadataItem({
|
||||
idToUpdate:
|
||||
objectSettingsItem.objectMetadataItem.id,
|
||||
updatePayload: { isActive: true },
|
||||
})
|
||||
}
|
||||
onDelete={() =>
|
||||
deleteOneObjectMetadataItem(
|
||||
objectSettingsItem.objectMetadataItem.id,
|
||||
)
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
link={
|
||||
isActive
|
||||
? getSettingsPath(SettingsPath.ObjectDetail, {
|
||||
objectNamePlural:
|
||||
objectSettingsItem.objectMetadataItem.namePlural,
|
||||
})
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
return (
|
||||
<SettingsObjectMetadataItemTableRow
|
||||
key={objectSettingsItem.objectMetadataItem.namePlural}
|
||||
objectMetadataItem={objectSettingsItem.objectMetadataItem}
|
||||
totalObjectCount={objectSettingsItem.totalObjectCount}
|
||||
action={
|
||||
isActive ? (
|
||||
<StyledIconChevronRightContainer>
|
||||
<IconChevronRight
|
||||
size={theme.icon.size.md}
|
||||
stroke={theme.icon.stroke.sm}
|
||||
/>
|
||||
</StyledIconChevronRightContainer>
|
||||
) : isDDLLocked ? null : (
|
||||
<SettingsObjectInactiveMenuDropDown
|
||||
isCustomObject={
|
||||
objectSettingsItem.objectMetadataItem.isCustom
|
||||
}
|
||||
objectMetadataItemNamePlural={
|
||||
objectSettingsItem.objectMetadataItem.namePlural
|
||||
}
|
||||
onActivate={() =>
|
||||
updateOneObjectMetadataItem({
|
||||
idToUpdate:
|
||||
objectSettingsItem.objectMetadataItem.id,
|
||||
updatePayload: { isActive: true },
|
||||
})
|
||||
}
|
||||
onDelete={() =>
|
||||
deleteOneObjectMetadataItem(
|
||||
objectSettingsItem.objectMetadataItem.id,
|
||||
)
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
link={
|
||||
isActive
|
||||
? getSettingsPath(SettingsPath.ObjectDetail, {
|
||||
objectNamePlural:
|
||||
objectSettingsItem.objectMetadataItem
|
||||
.namePlural,
|
||||
})
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
</StyledSettingsDataModelTableBodyContainer>
|
||||
</Table>
|
||||
</StyledScrollableContent>
|
||||
</StyledScrollWrapper>
|
||||
|
||||
+6
@@ -4,6 +4,7 @@ import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilte
|
||||
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
|
||||
import { SettingsWizardStepBar } from '@/settings/components/layout/SettingsWizardStepBar';
|
||||
import { FIELD_NAME_MAXIMUM_LENGTH } from '@/settings/data-model/constants/FieldNameMaximumLength';
|
||||
import { SettingsObjectNewFieldHeaderIcon } from '@/settings/data-model/fields/components/SettingsObjectNewFieldHeaderIcon';
|
||||
import { SettingsDataModelFieldIconLabelForm } from '@/settings/data-model/fields/forms/components/SettingsDataModelFieldIconLabelForm';
|
||||
import { SettingsDataModelFieldSettingsFormCard } from '@/settings/data-model/fields/forms/components/SettingsDataModelFieldSettingsFormCard';
|
||||
import { settingsFieldFormSchema } from '@/settings/data-model/fields/forms/validation-schemas/settingsFieldFormSchema';
|
||||
@@ -183,6 +184,11 @@ export const SettingsObjectNewFieldConfigure = () => {
|
||||
>
|
||||
<SettingsPageLayout
|
||||
title={activeObjectMetadataItem.labelPlural}
|
||||
icon={
|
||||
<SettingsObjectNewFieldHeaderIcon
|
||||
objectMetadataItem={activeObjectMetadataItem}
|
||||
/>
|
||||
}
|
||||
titleColor={themeCssVariables.font.color.tertiary}
|
||||
links={[
|
||||
{
|
||||
|
||||
+6
@@ -3,6 +3,7 @@ import { SettingsPageContainer } from '@/settings/components/SettingsPageContain
|
||||
import { SettingsPageLayout } from '@/settings/components/layout/SettingsPageLayout';
|
||||
import { SettingsWizardStepBar } from '@/settings/components/layout/SettingsWizardStepBar';
|
||||
import { SETTINGS_FIELD_TYPE_CONFIGS } from '@/settings/data-model/constants/SettingsFieldTypeConfigs';
|
||||
import { SettingsObjectNewFieldHeaderIcon } from '@/settings/data-model/fields/components/SettingsObjectNewFieldHeaderIcon';
|
||||
import { SettingsObjectNewFieldSelector } from '@/settings/data-model/fields/forms/components/SettingsObjectNewFieldSelector';
|
||||
import { type FieldType } from '@/settings/data-model/types/FieldType';
|
||||
import { type SettingsFieldType } from '@/settings/data-model/types/SettingsFieldType';
|
||||
@@ -67,6 +68,11 @@ export const SettingsObjectNewFieldSelect = () => {
|
||||
>
|
||||
<SettingsPageLayout
|
||||
title={activeObjectMetadataItem.labelPlural}
|
||||
icon={
|
||||
<SettingsObjectNewFieldHeaderIcon
|
||||
objectMetadataItem={activeObjectMetadataItem}
|
||||
/>
|
||||
}
|
||||
titleColor={themeCssVariables.font.color.tertiary}
|
||||
links={[
|
||||
{
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useEnterLayoutCustomizationMode } from '@/layout-customization/hooks/useEnterLayoutCustomizationMode';
|
||||
import { SettingsDiscoveryHeroCard } from '@/settings/components/SettingsDiscoveryHeroCard';
|
||||
import { SettingsOptionCardContentButton } from '@/settings/components/SettingsOptions/SettingsOptionCardContentButton';
|
||||
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
|
||||
import { SettingsLayoutItemsStats } from '@/settings/layout/components/SettingsLayoutItemsStats';
|
||||
import { SettingsPageLayout } from '@/settings/components/layout/SettingsPageLayout';
|
||||
@@ -70,16 +71,6 @@ export const SettingsLayout = () => {
|
||||
return (
|
||||
<SettingsPageLayout
|
||||
title={t`Layout`}
|
||||
actionButton={
|
||||
<Button
|
||||
title={t`Customize`}
|
||||
variant="primary"
|
||||
accent="blue"
|
||||
size="small"
|
||||
Icon={IconPencil}
|
||||
onClick={handleCustomize}
|
||||
/>
|
||||
}
|
||||
links={[
|
||||
{
|
||||
children: t`Workspace`,
|
||||
@@ -96,6 +87,23 @@ export const SettingsLayout = () => {
|
||||
instanceIdPrefix={SETTINGS_LAYOUT_HERO_INSTANCE_ID_PREFIX}
|
||||
tabs={heroTabs}
|
||||
playButtonAriaLabel={t`Watch customization demo`}
|
||||
footer={
|
||||
<SettingsOptionCardContentButton
|
||||
Icon={IconLayoutDashboard}
|
||||
title={t`Customize layout`}
|
||||
description={t`Customize how your workspace looks.`}
|
||||
Button={
|
||||
<Button
|
||||
title={t`Customize`}
|
||||
variant="primary"
|
||||
accent="blue"
|
||||
size="small"
|
||||
Icon={IconPencil}
|
||||
onClick={handleCustomize}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</Section>
|
||||
<Section>
|
||||
|
||||
Reference in New Issue
Block a user